| author | |
| committer | |
| log | e55e6b5528bb2f01de242fcf32b172e244e98e74 |
| tree | 3a5eb3193d3d192c54ab0c2b7295a7f21861c27e |
| parent | c3f2de5e519926eb0029062fe8e782a6f9df9c05 |
| parent | 60a1ba0a8f3517356fa2941462f002a7f580545b |
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30232280 files changed, 20282 insertions(+), 18623 deletions(-)
CMakeLists.txt-3| ... | ... | @@ -436,9 +436,6 @@ set(ZIG_STAGE2_SOURCES |
| 436 | 436 | lib/std/fmt.zig |
| 437 | 437 | lib/std/fmt/parse_float.zig |
| 438 | 438 | lib/std/fs.zig |
| 439 | lib/std/fs/AtomicFile.zig | |
| 440 | lib/std/fs/Dir.zig | |
| 441 | lib/std/fs/File.zig | |
| 442 | 439 | lib/std/fs/get_app_data_dir.zig |
| 443 | 440 | lib/std/fs/path.zig |
| 444 | 441 | lib/std/hash.zig |
build.zig+27-68| ... | ... | @@ -1,18 +1,20 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const builtin = std.builtin; |
| 3 | const tests = @import("test/tests.zig"); | |
| 4 | 3 | const BufMap = std.BufMap; |
| 5 | 4 | const mem = std.mem; |
| 6 | const io = std.io; | |
| 7 | 5 | const fs = std.fs; |
| 8 | 6 | const InstallDirectoryOptions = std.Build.InstallDirectoryOptions; |
| 9 | 7 | const assert = std.debug.assert; |
| 8 | const Io = std.Io; | |
| 9 | ||
| 10 | const tests = @import("test/tests.zig"); | |
| 10 | 11 | const DevEnv = @import("src/dev.zig").Env; |
| 11 | const ValueInterpretMode = enum { direct, by_name }; | |
| 12 | 12 | |
| 13 | 13 | const zig_version: std.SemanticVersion = .{ .major = 0, .minor = 16, .patch = 0 }; |
| 14 | 14 | const stack_size = 46 * 1024 * 1024; |
| 15 | 15 | |
| 16 | const ValueInterpretMode = enum { direct, by_name }; | |
| 17 | ||
| 16 | 18 | pub fn build(b: *std.Build) !void { |
| 17 | 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 | 20 | const target = b.standardTargetOptions(.{ |
| ... | ... | @@ -306,8 +308,10 @@ pub fn build(b: *std.Build) !void { |
| 306 | 308 | |
| 307 | 309 | if (enable_llvm) { |
| 308 | 310 | const cmake_cfg = if (static_llvm) null else blk: { |
| 311 | const io = b.graph.io; | |
| 312 | const cwd: Io.Dir = .cwd(); | |
| 309 | 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 | 315 | break :blk parseConfigH(b, file_contents); |
| 312 | 316 | } else { |
| 313 | 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 | 535 | .aarch64 => 701_413_785, |
| 532 | 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 | 538 | else => 900_000_000, |
| 539 | 539 | }, |
| 540 | 540 | })); |
| ... | ... | @@ -561,30 +561,7 @@ pub fn build(b: *std.Build) !void { |
| 561 | 561 | .skip_llvm = skip_llvm, |
| 562 | 562 | .skip_libc = true, |
| 563 | 563 | .no_builtin = true, |
| 564 | .max_rss = switch (b.graph.host.result.os.tag) { | |
| 565 | .freebsd => switch (b.graph.host.result.cpu.arch) { | |
| 566 | .x86_64 => 557_892_403, | |
| 567 | else => 600_000_000, | |
| 568 | }, | |
| 569 | .linux => switch (b.graph.host.result.cpu.arch) { | |
| 570 | .aarch64 => 615_302_758, | |
| 571 | .loongarch64 => 598_974_464, | |
| 572 | .powerpc64le => 587_845_632, | |
| 573 | .riscv64 => 382_786_764, | |
| 574 | .s390x => 395_555_635, | |
| 575 | .x86_64 => 871_883_161, | |
| 576 | else => 900_000_000, | |
| 577 | }, | |
| 578 | .macos => switch (b.graph.host.result.cpu.arch) { | |
| 579 | .aarch64 => 451_389_030, | |
| 580 | else => 500_000_000, | |
| 581 | }, | |
| 582 | .windows => switch (b.graph.host.result.cpu.arch) { | |
| 583 | .x86_64 => 367_747_072, | |
| 584 | else => 400_000_000, | |
| 585 | }, | |
| 586 | else => 900_000_000, | |
| 587 | }, | |
| 564 | .max_rss = 900_000_000, | |
| 588 | 565 | })); |
| 589 | 566 | |
| 590 | 567 | test_modules_step.dependOn(tests.addModuleTests(b, .{ |
| ... | ... | @@ -647,30 +624,7 @@ pub fn build(b: *std.Build) !void { |
| 647 | 624 | .use_llvm = use_llvm, |
| 648 | 625 | .use_lld = use_llvm, |
| 649 | 626 | .zig_lib_dir = b.path("lib"), |
| 650 | .max_rss = switch (b.graph.host.result.os.tag) { | |
| 651 | .freebsd => switch (b.graph.host.result.cpu.arch) { | |
| 652 | .x86_64 => 2_188_099_584, | |
| 653 | else => 2_200_000_000, | |
| 654 | }, | |
| 655 | .linux => switch (b.graph.host.result.cpu.arch) { | |
| 656 | .aarch64 => 1_991_934_771, | |
| 657 | .loongarch64 => 1_844_538_572, | |
| 658 | .powerpc64le => 1_793_035_059, | |
| 659 | .riscv64 => 2_459_003_289, | |
| 660 | .s390x => 1_781_248_409, | |
| 661 | .x86_64 => 977_192_550, | |
| 662 | else => 2_500_000_000, | |
| 663 | }, | |
| 664 | .macos => switch (b.graph.host.result.cpu.arch) { | |
| 665 | .aarch64 => 2_062_393_344, | |
| 666 | else => 2_100_000_000, | |
| 667 | }, | |
| 668 | .windows => switch (b.graph.host.result.cpu.arch) { | |
| 669 | .x86_64 => 1_953_087_488, | |
| 670 | else => 2_000_000_000, | |
| 671 | }, | |
| 672 | else => 2_500_000_000, | |
| 673 | }, | |
| 627 | .max_rss = 2_500_000_000, | |
| 674 | 628 | }); |
| 675 | 629 | if (link_libc) { |
| 676 | 630 | unit_tests.root_module.link_libc = true; |
| ... | ... | @@ -762,7 +716,7 @@ pub fn build(b: *std.Build) !void { |
| 762 | 716 | } |
| 763 | 717 | |
| 764 | 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 | 720 | if (!skip_test_incremental) test_step.dependOn(test_incremental_step); |
| 767 | 721 | |
| 768 | 722 | if (tests.addLibcTests(b, .{ |
| ... | ... | @@ -1153,10 +1107,13 @@ const CMakeConfig = struct { |
| 1153 | 1107 | const max_config_h_bytes = 1 * 1024 * 1024; |
| 1154 | 1108 | |
| 1155 | 1109 | fn 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 | 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 | 1115 | if (config_h_or_err) |*file| { |
| 1159 | file.close(); | |
| 1116 | file.close(io); | |
| 1160 | 1117 | return path; |
| 1161 | 1118 | } else |_| { |
| 1162 | 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 | 1123 | |
| 1167 | 1124 | var check_dir = fs.path.dirname(b.graph.zig_exe).?; |
| 1168 | 1125 | while (true) { |
| 1169 | var dir = fs.cwd().openDir(check_dir, .{}) catch unreachable; | |
| 1170 | defer dir.close(); | |
| 1126 | var dir = cwd.openDir(io, check_dir, .{}) catch unreachable; | |
| 1127 | defer dir.close(io); | |
| 1171 | 1128 | |
| 1172 | 1129 | // 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 | 1131 | if (config_h_or_err) |*file| { |
| 1175 | file.close(); | |
| 1132 | file.close(io); | |
| 1176 | 1133 | return fs.path.join( |
| 1177 | 1134 | b.allocator, |
| 1178 | 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 | 1140 | } |
| 1184 | 1141 | |
| 1185 | 1142 | // 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 | 1144 | if (git_dir_or_err) |*git_dir| { |
| 1188 | git_dir.close(); | |
| 1145 | git_dir.close(io); | |
| 1189 | 1146 | return null; |
| 1190 | 1147 | } else |_| {} |
| 1191 | 1148 | |
| ... | ... | @@ -1581,6 +1538,8 @@ const llvm_libs_xtensa = [_][]const u8{ |
| 1581 | 1538 | }; |
| 1582 | 1539 | |
| 1583 | 1540 | fn generateLangRef(b: *std.Build) std.Build.LazyPath { |
| 1541 | const io = b.graph.io; | |
| 1542 | ||
| 1584 | 1543 | const doctest_exe = b.addExecutable(.{ |
| 1585 | 1544 | .name = "doctest", |
| 1586 | 1545 | .root_module = b.createModule(.{ |
| ... | ... | @@ -1590,17 +1549,17 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath { |
| 1590 | 1549 | }), |
| 1591 | 1550 | }); |
| 1592 | 1551 | |
| 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 | 1553 | std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{ |
| 1595 | 1554 | b.build_root, @errorName(err), |
| 1596 | 1555 | }); |
| 1597 | 1556 | }; |
| 1598 | defer dir.close(); | |
| 1557 | defer dir.close(io); | |
| 1599 | 1558 | |
| 1600 | 1559 | var wf = b.addWriteFiles(); |
| 1601 | 1560 | |
| 1602 | 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 | 1563 | if (std.mem.startsWith(u8, entry.name, ".") or entry.kind != .file) |
| 1605 | 1564 | continue; |
| 1606 | 1565 |
doc/langref/bad_default_value.zig+1-1| ... | ... | @@ -17,7 +17,7 @@ pub fn main() !void { |
| 17 | 17 | .maximum = 0.20, |
| 18 | 18 | }; |
| 19 | 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 | } |
| 22 | 22 | |
| 23 | 23 | const std = @import("std"); |
doc/langref/hello.zig+11-1| ... | ... | @@ -1,7 +1,17 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | |
| 3 | // See https://github.com/ziglang/zig/issues/24510 | |
| 4 | // for the plan to simplify this code. | |
| 3 | 5 | pub 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 | } |
| 6 | 16 | |
| 7 | 17 | // exe=succeed |
lib/compiler/aro/aro/Compilation.zig+21-15| ... | ... | @@ -154,7 +154,7 @@ gpa: Allocator, |
| 154 | 154 | /// Allocations in this arena live all the way until `Compilation.deinit`. |
| 155 | 155 | arena: Allocator, |
| 156 | 156 | io: Io, |
| 157 | cwd: std.fs.Dir, | |
| 157 | cwd: Io.Dir, | |
| 158 | 158 | diagnostics: *Diagnostics, |
| 159 | 159 | |
| 160 | 160 | sources: std.StringArrayHashMapUnmanaged(Source) = .empty, |
| ... | ... | @@ -181,7 +181,7 @@ pragma_handlers: std.StringArrayHashMapUnmanaged(*Pragma) = .empty, |
| 181 | 181 | /// Used by MS extensions which allow searching for includes relative to the directory of the main source file. |
| 182 | 182 | ms_cwd_source_id: ?Source.Id = null, |
| 183 | 183 | |
| 184 | pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilation { | |
| 184 | pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: Io.Dir) Compilation { | |
| 185 | 185 | return .{ |
| 186 | 186 | .gpa = gpa, |
| 187 | 187 | .arena = arena, |
| ... | ... | @@ -193,7 +193,7 @@ pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, |
| 193 | 193 | |
| 194 | 194 | /// Initialize Compilation with default environment, |
| 195 | 195 | /// pragma handlers and emulation mode set to target. |
| 196 | pub fn initDefault(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: std.fs.Dir) !Compilation { | |
| 196 | pub fn initDefault(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: Io.Dir) !Compilation { | |
| 197 | 197 | var comp: Compilation = .{ |
| 198 | 198 | .gpa = gpa, |
| 199 | 199 | .arena = arena, |
| ... | ... | @@ -1639,12 +1639,14 @@ fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kin |
| 1639 | 1639 | return error.FileNotFound; |
| 1640 | 1640 | } |
| 1641 | 1641 | |
| 1642 | const file = try comp.cwd.openFile(path, .{}); | |
| 1643 | defer file.close(); | |
| 1642 | const io = comp.io; | |
| 1643 | ||
| 1644 | const file = try comp.cwd.openFile(io, path, .{}); | |
| 1645 | defer file.close(io); | |
| 1644 | 1646 | return comp.addSourceFromFile(file, path, kind); |
| 1645 | 1647 | } |
| 1646 | 1648 | |
| 1647 | pub fn addSourceFromFile(comp: *Compilation, file: std.fs.File, path: []const u8, kind: Source.Kind) !Source { | |
| 1649 | pub fn addSourceFromFile(comp: *Compilation, file: Io.File, path: []const u8, kind: Source.Kind) !Source { | |
| 1648 | 1650 | const contents = try comp.getFileContents(file, .unlimited); |
| 1649 | 1651 | errdefer comp.gpa.free(contents); |
| 1650 | 1652 | return comp.addSourceFromOwnedBuffer(path, contents, kind); |
| ... | ... | @@ -1711,7 +1713,8 @@ pub fn initSearchPath(comp: *Compilation, includes: []const Include, verbose: bo |
| 1711 | 1713 | } |
| 1712 | 1714 | } |
| 1713 | 1715 | fn 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 | 1718 | if (verbose) { |
| 1716 | 1719 | std.debug.print("ignoring nonexistent directory \"{s}\"\n", .{include.path}); |
| 1717 | 1720 | return; |
| ... | ... | @@ -1971,12 +1974,14 @@ fn getPathContents(comp: *Compilation, path: []const u8, limit: Io.Limit) ![]u8 |
| 1971 | 1974 | return error.FileNotFound; |
| 1972 | 1975 | } |
| 1973 | 1976 | |
| 1974 | const file = try comp.cwd.openFile(path, .{}); | |
| 1975 | defer file.close(); | |
| 1977 | const io = comp.io; | |
| 1978 | ||
| 1979 | const file = try comp.cwd.openFile(io, path, .{}); | |
| 1980 | defer file.close(io); | |
| 1976 | 1981 | return comp.getFileContents(file, limit); |
| 1977 | 1982 | } |
| 1978 | 1983 | |
| 1979 | fn getFileContents(comp: *Compilation, file: std.fs.File, limit: Io.Limit) ![]u8 { | |
| 1984 | fn getFileContents(comp: *Compilation, file: Io.File, limit: Io.Limit) ![]u8 { | |
| 1980 | 1985 | var file_buf: [4096]u8 = undefined; |
| 1981 | 1986 | var file_reader = file.reader(comp.io, &file_buf); |
| 1982 | 1987 | |
| ... | ... | @@ -2158,8 +2163,9 @@ pub fn locSlice(comp: *const Compilation, loc: Source.Location) []const u8 { |
| 2158 | 2163 | } |
| 2159 | 2164 | |
| 2160 | 2165 | pub fn getSourceMTimeUncached(comp: *const Compilation, source_id: Source.Id) ?u64 { |
| 2166 | const io = comp.io; | |
| 2161 | 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 | 2169 | return std.math.cast(u64, stat.mtime.toSeconds()); |
| 2164 | 2170 | } else |_| { |
| 2165 | 2171 | return null; |
| ... | ... | @@ -2249,7 +2255,7 @@ test "addSourceFromBuffer" { |
| 2249 | 2255 | var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); |
| 2250 | 2256 | defer arena.deinit(); |
| 2251 | 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 | 2259 | defer comp.deinit(); |
| 2254 | 2260 | |
| 2255 | 2261 | const source = try comp.addSourceFromBuffer("path", str); |
| ... | ... | @@ -2263,7 +2269,7 @@ test "addSourceFromBuffer" { |
| 2263 | 2269 | var arena: std.heap.ArenaAllocator = .init(allocator); |
| 2264 | 2270 | defer arena.deinit(); |
| 2265 | 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 | 2273 | defer comp.deinit(); |
| 2268 | 2274 | |
| 2269 | 2275 | _ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n"); |
| ... | ... | @@ -2309,7 +2315,7 @@ test "addSourceFromBuffer - exhaustive check for carriage return elimination" { |
| 2309 | 2315 | var buf: [alphabet.len]u8 = @splat(alphabet[0]); |
| 2310 | 2316 | |
| 2311 | 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 | 2319 | defer comp.deinit(); |
| 2314 | 2320 | |
| 2315 | 2321 | var source_count: u32 = 0; |
| ... | ... | @@ -2337,7 +2343,7 @@ test "ignore BOM at beginning of file" { |
| 2337 | 2343 | const Test = struct { |
| 2338 | 2344 | fn run(arena: Allocator, buf: []const u8) !void { |
| 2339 | 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 | 2347 | defer comp.deinit(); |
| 2342 | 2348 | |
| 2343 | 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 | 24 | @"fatal error", |
| 25 | 25 | }; |
| 26 | 26 | |
| 27 | pub fn write(msg: Message, w: *std.Io.Writer, config: std.Io.tty.Config, details: bool) std.Io.tty.Config.SetColorError!void { | |
| 28 | try config.setColor(w, .bold); | |
| 27 | pub fn write(msg: Message, t: std.Io.Terminal, details: bool) std.Io.Terminal.SetColorError!void { | |
| 28 | const w = t.writer; | |
| 29 | try t.setColor(.bold); | |
| 29 | 30 | if (msg.location) |loc| { |
| 30 | 31 | try w.print("{s}:{d}:{d}: ", .{ loc.path, loc.line_no, loc.col }); |
| 31 | 32 | } |
| 32 | 33 | switch (msg.effective_kind) { |
| 33 | .@"fatal error", .@"error" => try config.setColor(w, .bright_red), | |
| 34 | .note => try config.setColor(w, .bright_cyan), | |
| 35 | .warning => try config.setColor(w, .bright_magenta), | |
| 34 | .@"fatal error", .@"error" => try t.setColor(.bright_red), | |
| 35 | .note => try t.setColor(.bright_cyan), | |
| 36 | .warning => try t.setColor(.bright_magenta), | |
| 36 | 37 | .off => unreachable, |
| 37 | 38 | } |
| 38 | 39 | try w.print("{s}: ", .{@tagName(msg.effective_kind)}); |
| 39 | 40 | |
| 40 | try config.setColor(w, .white); | |
| 41 | try t.setColor(.white); | |
| 41 | 42 | try w.writeAll(msg.text); |
| 42 | 43 | if (msg.opt) |some| { |
| 43 | 44 | if (msg.effective_kind == .@"error" and msg.kind != .@"error") { |
| ... | ... | @@ -55,17 +56,17 @@ pub const Message = struct { |
| 55 | 56 | |
| 56 | 57 | if (!details or msg.location == null) { |
| 57 | 58 | try w.writeAll("\n"); |
| 58 | try config.setColor(w, .reset); | |
| 59 | try t.setColor(.reset); | |
| 59 | 60 | } else { |
| 60 | 61 | const loc = msg.location.?; |
| 61 | 62 | const trailer = if (loc.end_with_splice) "\\ " else ""; |
| 62 | try config.setColor(w, .reset); | |
| 63 | try t.setColor(.reset); | |
| 63 | 64 | try w.print("\n{s}{s}\n", .{ loc.line, trailer }); |
| 64 | 65 | try w.splatByteAll(' ', loc.width); |
| 65 | try config.setColor(w, .bold); | |
| 66 | try config.setColor(w, .bright_green); | |
| 66 | try t.setColor(.bold); | |
| 67 | try t.setColor(.bright_green); | |
| 67 | 68 | try w.writeAll("^\n"); |
| 68 | try config.setColor(w, .reset); | |
| 69 | try t.setColor(.reset); | |
| 69 | 70 | } |
| 70 | 71 | try w.flush(); |
| 71 | 72 | } |
| ... | ... | @@ -290,10 +291,7 @@ pub const State = struct { |
| 290 | 291 | const Diagnostics = @This(); |
| 291 | 292 | |
| 292 | 293 | output: union(enum) { |
| 293 | to_writer: struct { | |
| 294 | writer: *std.Io.Writer, | |
| 295 | color: std.Io.tty.Config, | |
| 296 | }, | |
| 294 | to_writer: std.Io.Terminal, | |
| 297 | 295 | to_list: struct { |
| 298 | 296 | messages: std.ArrayList(Message) = .empty, |
| 299 | 297 | arena: std.heap.ArenaAllocator, |
| ... | ... | @@ -543,11 +541,11 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { |
| 543 | 541 | |
| 544 | 542 | switch (d.output) { |
| 545 | 543 | .ignore => {}, |
| 546 | .to_writer => |writer| { | |
| 547 | var config = writer.color; | |
| 548 | if (d.color == false) config = .no_color; | |
| 549 | if (d.color == true and config == .no_color) config = .escape_codes; | |
| 550 | msg.write(writer.writer, config, d.details) catch { | |
| 544 | .to_writer => |t| { | |
| 545 | var new_mode = t.mode; | |
| 546 | if (d.color == false) new_mode = .no_color; | |
| 547 | if (d.color == true and new_mode == .no_color) new_mode = .escape_codes; | |
| 548 | msg.write(.{ .writer = t.writer, .mode = new_mode }, d.details) catch { | |
| 551 | 549 | return error.FatalError; |
| 552 | 550 | }; |
| 553 | 551 | }, |
lib/compiler/aro/aro/Driver.zig+25-21| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const mem = std.mem; |
| 3 | 4 | const Allocator = mem.Allocator; |
| 4 | 5 | const process = std.process; |
| ... | ... | @@ -133,8 +134,9 @@ strip: bool = false, |
| 133 | 134 | unwindlib: ?[]const u8 = null, |
| 134 | 135 | |
| 135 | 136 | pub fn deinit(d: *Driver) void { |
| 137 | const io = d.comp.io; | |
| 136 | 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 | 140 | d.comp.gpa.free(obj); |
| 139 | 141 | } |
| 140 | 142 | d.inputs.deinit(d.comp.gpa); |
| ... | ... | @@ -1061,7 +1063,7 @@ pub fn printDiagnosticsStats(d: *Driver) void { |
| 1061 | 1063 | } |
| 1062 | 1064 | } |
| 1063 | 1065 | |
| 1064 | pub fn detectConfig(d: *Driver, file: std.fs.File) std.Io.tty.Config { | |
| 1066 | pub fn detectConfig(d: *Driver, file: Io.File) std.Io.tty.Config { | |
| 1065 | 1067 | if (d.diagnostics.color == false) return .no_color; |
| 1066 | 1068 | const force_color = d.diagnostics.color == true; |
| 1067 | 1069 | |
| ... | ... | @@ -1109,7 +1111,7 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_ |
| 1109 | 1111 | defer macro_buf.deinit(d.comp.gpa); |
| 1110 | 1112 | |
| 1111 | 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 | 1115 | if (parseArgs(d, &stdout.interface, &macro_buf, args) catch |er| switch (er) { |
| 1114 | 1116 | error.WriteFailed => return d.fatal("failed to write to stdout: {s}", .{errorDescription(er)}), |
| 1115 | 1117 | error.OutOfMemory => return error.OutOfMemory, |
| ... | ... | @@ -1286,6 +1288,8 @@ fn processSource( |
| 1286 | 1288 | d.comp.generated_buf.items.len = 0; |
| 1287 | 1289 | const prev_total = d.diagnostics.errors; |
| 1288 | 1290 | |
| 1291 | const io = d.comp.io; | |
| 1292 | ||
| 1289 | 1293 | var pp = try Preprocessor.initDefault(d.comp); |
| 1290 | 1294 | defer pp.deinit(); |
| 1291 | 1295 | |
| ... | ... | @@ -1324,13 +1328,13 @@ fn processSource( |
| 1324 | 1328 | const dep_file_name = try d.getDepFileName(source, writer_buf[0..std.fs.max_name_bytes]); |
| 1325 | 1329 | |
| 1326 | 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 | 1332 | return d.fatal("unable to create dependency file '{s}': {s}", .{ path, errorDescription(er) }) |
| 1329 | 1333 | else |
| 1330 | std.fs.File.stdout(); | |
| 1331 | defer if (dep_file_name != null) file.close(); | |
| 1334 | Io.File.stdout(); | |
| 1335 | defer if (dep_file_name != null) file.close(io); | |
| 1332 | 1336 | |
| 1333 | var file_writer = file.writer(&writer_buf); | |
| 1337 | var file_writer = file.writer(io, &writer_buf); | |
| 1334 | 1338 | dep_file.write(&file_writer.interface) catch |
| 1335 | 1339 | return d.fatal("unable to write dependency file: {s}", .{errorDescription(file_writer.err.?)}); |
| 1336 | 1340 | } |
| ... | ... | @@ -1349,13 +1353,13 @@ fn processSource( |
| 1349 | 1353 | } |
| 1350 | 1354 | |
| 1351 | 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 | 1357 | return d.fatal("unable to create output file '{s}': {s}", .{ some, errorDescription(er) }) |
| 1354 | 1358 | else |
| 1355 | std.fs.File.stdout(); | |
| 1356 | defer if (d.output_name != null) file.close(); | |
| 1359 | Io.File.stdout(); | |
| 1360 | defer if (d.output_name != null) file.close(io); | |
| 1357 | 1361 | |
| 1358 | var file_writer = file.writer(&writer_buf); | |
| 1362 | var file_writer = file.writer(io, &writer_buf); | |
| 1359 | 1363 | pp.prettyPrintTokens(&file_writer.interface, dump_mode) catch |
| 1360 | 1364 | return d.fatal("unable to write result: {s}", .{errorDescription(file_writer.err.?)}); |
| 1361 | 1365 | |
| ... | ... | @@ -1367,7 +1371,7 @@ fn processSource( |
| 1367 | 1371 | defer tree.deinit(); |
| 1368 | 1372 | |
| 1369 | 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 | 1375 | tree.dump(d.detectConfig(stdout.file), &stdout.interface) catch {}; |
| 1372 | 1376 | } |
| 1373 | 1377 | |
| ... | ... | @@ -1402,9 +1406,9 @@ fn processSource( |
| 1402 | 1406 | defer assembly.deinit(gpa); |
| 1403 | 1407 | |
| 1404 | 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 | 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); | |
| 1408 | 1412 | |
| 1409 | 1413 | assembly.writeToFile(out_file) catch |er| |
| 1410 | 1414 | return d.fatal("unable to write to output file '{s}': {s}", .{ out_file_name, errorDescription(er) }); |
| ... | ... | @@ -1416,9 +1420,9 @@ fn processSource( |
| 1416 | 1420 | // then assemble to out_file_name |
| 1417 | 1421 | var assembly_name_buf: [std.fs.max_name_bytes]u8 = undefined; |
| 1418 | 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 | 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 | 1426 | assembly.writeToFile(out_file) catch |er| |
| 1423 | 1427 | return d.fatal("unable to write to output file '{s}': {s}", .{ assembly_out_file_name, errorDescription(er) }); |
| 1424 | 1428 | try d.invokeAssembler(tc, assembly_out_file_name, out_file_name); |
| ... | ... | @@ -1431,7 +1435,7 @@ fn processSource( |
| 1431 | 1435 | defer ir.deinit(gpa); |
| 1432 | 1436 | |
| 1433 | 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 | 1439 | ir.dump(gpa, d.detectConfig(stdout.file), &stdout.interface) catch {}; |
| 1436 | 1440 | } |
| 1437 | 1441 | |
| ... | ... | @@ -1452,11 +1456,11 @@ fn processSource( |
| 1452 | 1456 | }; |
| 1453 | 1457 | defer obj.deinit(); |
| 1454 | 1458 | |
| 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 | 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); | |
| 1458 | 1462 | |
| 1459 | var file_writer = out_file.writer(&writer_buf); | |
| 1463 | var file_writer = out_file.writer(io, &writer_buf); | |
| 1460 | 1464 | obj.finish(&file_writer.interface) catch |
| 1461 | 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 | 1501 | |
| 1498 | 1502 | if (d.verbose_linker_args) { |
| 1499 | 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 | 1505 | dumpLinkerArgs(&stdout.interface, argv.items) catch { |
| 1502 | 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 | const std = @import("std"); | |
| 2 | const mem = std.mem; | |
| 3 | 1 | const builtin = @import("builtin"); |
| 4 | 2 | const is_windows = builtin.os.tag == .windows; |
| 5 | 3 | |
| 4 | const std = @import("std"); | |
| 5 | const Io = std.Io; | |
| 6 | const mem = std.std.mem; | |
| 7 | ||
| 6 | 8 | fn readFileFake(entries: []const Filesystem.Entry, path: []const u8, buf: []u8) ?[]const u8 { |
| 7 | 9 | @branchHint(.cold); |
| 8 | 10 | for (entries) |entry| { |
| ... | ... | @@ -55,8 +57,8 @@ fn existsFake(entries: []const Filesystem.Entry, path: []const u8) bool { |
| 55 | 57 | return false; |
| 56 | 58 | } |
| 57 | 59 | |
| 58 | fn canExecutePosix(path: []const u8) bool { | |
| 59 | std.posix.access(path, std.posix.X_OK) catch return false; | |
| 60 | fn canExecutePosix(io: Io, path: []const u8) bool { | |
| 61 | Io.Dir.accessAbsolute(io, path, .{ .execute = true }) catch return false; | |
| 60 | 62 | // Todo: ensure path is not a directory |
| 61 | 63 | return true; |
| 62 | 64 | } |
| ... | ... | @@ -96,7 +98,7 @@ fn findProgramByNamePosix(name: []const u8, path: ?[]const u8, buf: []u8) ?[]con |
| 96 | 98 | } |
| 97 | 99 | |
| 98 | 100 | pub const Filesystem = union(enum) { |
| 99 | real: std.fs.Dir, | |
| 101 | real: std.Io.Dir, | |
| 100 | 102 | fake: []const Entry, |
| 101 | 103 | |
| 102 | 104 | const Entry = struct { |
| ... | ... | @@ -121,7 +123,7 @@ pub const Filesystem = union(enum) { |
| 121 | 123 | base: []const u8, |
| 122 | 124 | i: usize = 0, |
| 123 | 125 | |
| 124 | fn next(self: *@This()) !?std.fs.Dir.Entry { | |
| 126 | fn next(self: *@This()) !?std.Io.Dir.Entry { | |
| 125 | 127 | while (self.i < self.entries.len) { |
| 126 | 128 | const entry = self.entries[self.i]; |
| 127 | 129 | self.i += 1; |
| ... | ... | @@ -130,7 +132,7 @@ pub const Filesystem = union(enum) { |
| 130 | 132 | const remaining = entry.path[self.base.len + 1 ..]; |
| 131 | 133 | if (std.mem.indexOfScalar(u8, remaining, std.fs.path.sep) != null) continue; |
| 132 | 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 | 136 | return .{ .name = remaining, .kind = kind }; |
| 135 | 137 | } |
| 136 | 138 | } |
| ... | ... | @@ -140,7 +142,7 @@ pub const Filesystem = union(enum) { |
| 140 | 142 | }; |
| 141 | 143 | |
| 142 | 144 | const Dir = union(enum) { |
| 143 | dir: std.fs.Dir, | |
| 145 | dir: std.Io.Dir, | |
| 144 | 146 | fake: FakeDir, |
| 145 | 147 | |
| 146 | 148 | pub fn iterate(self: Dir) Iterator { |
| ... | ... | @@ -150,19 +152,19 @@ pub const Filesystem = union(enum) { |
| 150 | 152 | }; |
| 151 | 153 | } |
| 152 | 154 | |
| 153 | pub fn close(self: *Dir) void { | |
| 155 | pub fn close(self: *Dir, io: Io) void { | |
| 154 | 156 | switch (self.*) { |
| 155 | .dir => |*d| d.close(), | |
| 157 | .dir => |*d| d.close(io), | |
| 156 | 158 | .fake => {}, |
| 157 | 159 | } |
| 158 | 160 | } |
| 159 | 161 | }; |
| 160 | 162 | |
| 161 | 163 | const Iterator = union(enum) { |
| 162 | iterator: std.fs.Dir.Iterator, | |
| 164 | iterator: std.Io.Dir.Iterator, | |
| 163 | 165 | fake: FakeDir.Iterator, |
| 164 | 166 | |
| 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 | 168 | return switch (self.*) { |
| 167 | 169 | .iterator => |*it| it.next(), |
| 168 | 170 | .fake => |*it| it.next(), |
| ... | ... | @@ -170,10 +172,10 @@ pub const Filesystem = union(enum) { |
| 170 | 172 | } |
| 171 | 173 | }; |
| 172 | 174 | |
| 173 | pub fn exists(fs: Filesystem, path: []const u8) bool { | |
| 175 | pub fn exists(fs: Filesystem, io: Io, path: []const u8) bool { | |
| 174 | 176 | switch (fs) { |
| 175 | 177 | .real => |cwd| { |
| 176 | cwd.access(path, .{}) catch return false; | |
| 178 | cwd.access(io, path, .{}) catch return false; | |
| 177 | 179 | return true; |
| 178 | 180 | }, |
| 179 | 181 | .fake => |paths| return existsFake(paths, path), |
| ... | ... | @@ -208,11 +210,11 @@ pub const Filesystem = union(enum) { |
| 208 | 210 | /// Read the file at `path` into `buf`. |
| 209 | 211 | /// Returns null if any errors are encountered |
| 210 | 212 | /// 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 | 214 | return switch (fs) { |
| 213 | 215 | .real => |cwd| { |
| 214 | const file = cwd.openFile(path, .{}) catch return null; | |
| 215 | defer file.close(); | |
| 216 | const file = cwd.openFile(io, path, .{}) catch return null; | |
| 217 | defer file.close(io); | |
| 216 | 218 | |
| 217 | 219 | const bytes_read = file.readAll(buf) catch return null; |
| 218 | 220 | return buf[0..bytes_read]; |
| ... | ... | @@ -221,9 +223,9 @@ pub const Filesystem = union(enum) { |
| 221 | 223 | }; |
| 222 | 224 | } |
| 223 | 225 | |
| 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 | 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 | 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 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const mem = std.mem; |
| 3 | 4 | const Allocator = mem.Allocator; |
| 4 | 5 | const assert = std.debug.assert; |
| ... | ... | @@ -211,7 +212,7 @@ fn checkIdentifierCodepointWarnings(p: *Parser, codepoint: u21, loc: Source.Loca |
| 211 | 212 | |
| 212 | 213 | const prev_total = p.diagnostics.total; |
| 213 | 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 | 216 | defer allocating.deinit(); |
| 216 | 217 | |
| 217 | 218 | if (!char_info.isC99IdChar(codepoint)) { |
| ... | ... | @@ -425,7 +426,7 @@ pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype) |
| 425 | 426 | if (p.diagnostics.effectiveKind(diagnostic) == .off) return; |
| 426 | 427 | |
| 427 | 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 | 430 | defer allocating.deinit(); |
| 430 | 431 | |
| 431 | 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 | 448 | }, p.pp.expansionSlice(tok_i), true); |
| 448 | 449 | } |
| 449 | 450 | |
| 450 | fn formatArgs(p: *Parser, w: *std.Io.Writer, fmt: []const u8, args: anytype) !void { | |
| 451 | fn formatArgs(p: *Parser, w: *Io.Writer, fmt: []const u8, args: anytype) !void { | |
| 451 | 452 | var i: usize = 0; |
| 452 | 453 | inline for (std.meta.fields(@TypeOf(args))) |arg_info| { |
| 453 | 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 | 477 | try w.writeAll(fmt[i..]); |
| 477 | 478 | } |
| 478 | 479 | |
| 479 | fn formatTokenId(w: *std.Io.Writer, fmt: []const u8, tok_id: Tree.Token.Id) !usize { | |
| 480 | fn formatTokenId(w: *Io.Writer, fmt: []const u8, tok_id: Tree.Token.Id) !usize { | |
| 480 | 481 | const i = Diagnostics.templateIndex(w, fmt, "{tok_id}"); |
| 481 | 482 | try w.writeAll(tok_id.symbol()); |
| 482 | 483 | return i; |
| 483 | 484 | } |
| 484 | 485 | |
| 485 | fn formatQualType(p: *Parser, w: *std.Io.Writer, fmt: []const u8, qt: QualType) !usize { | |
| 486 | fn formatQualType(p: *Parser, w: *Io.Writer, fmt: []const u8, qt: QualType) !usize { | |
| 486 | 487 | const i = Diagnostics.templateIndex(w, fmt, "{qt}"); |
| 487 | 488 | try w.writeByte('\''); |
| 488 | 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 | 502 | return i; |
| 502 | 503 | } |
| 503 | 504 | |
| 504 | fn formatResult(p: *Parser, w: *std.Io.Writer, fmt: []const u8, res: Result) !usize { | |
| 505 | fn formatResult(p: *Parser, w: *Io.Writer, fmt: []const u8, res: Result) !usize { | |
| 505 | 506 | const i = Diagnostics.templateIndex(w, fmt, "{value}"); |
| 506 | 507 | switch (res.val.opt_ref) { |
| 507 | 508 | .none => try w.writeAll("(none)"), |
| ... | ... | @@ -524,7 +525,7 @@ const Normalized = struct { |
| 524 | 525 | return .{ .str = str }; |
| 525 | 526 | } |
| 526 | 527 | |
| 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 | 529 | const i = Diagnostics.templateIndex(w, fmt, "{normalized}"); |
| 529 | 530 | var it: std.unicode.Utf8Iterator = .{ |
| 530 | 531 | .bytes = ctx.str, |
| ... | ... | @@ -558,7 +559,7 @@ const Codepoint = struct { |
| 558 | 559 | return .{ .codepoint = codepoint }; |
| 559 | 560 | } |
| 560 | 561 | |
| 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 | 563 | const i = Diagnostics.templateIndex(w, fmt, "{codepoint}"); |
| 563 | 564 | try w.print("{X:0>4}", .{ctx.codepoint}); |
| 564 | 565 | return i; |
| ... | ... | @@ -572,7 +573,7 @@ const Escaped = struct { |
| 572 | 573 | return .{ .str = str }; |
| 573 | 574 | } |
| 574 | 575 | |
| 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 | 577 | const i = Diagnostics.templateIndex(w, fmt, "{s}"); |
| 577 | 578 | try std.zig.stringEscape(ctx.str, w); |
| 578 | 579 | return i; |
| ... | ... | @@ -1453,7 +1454,7 @@ fn decl(p: *Parser) Error!bool { |
| 1453 | 1454 | return true; |
| 1454 | 1455 | } |
| 1455 | 1456 | |
| 1456 | fn staticAssertMessage(p: *Parser, cond_node: Node.Index, maybe_message: ?Result, allocating: *std.Io.Writer.Allocating) !?[]const u8 { | |
| 1457 | fn staticAssertMessage(p: *Parser, cond_node: Node.Index, maybe_message: ?Result, allocating: *Io.Writer.Allocating) !?[]const u8 { | |
| 1457 | 1458 | const w = &allocating.writer; |
| 1458 | 1459 | |
| 1459 | 1460 | const cond = cond_node.get(&p.tree); |
| ... | ... | @@ -1526,7 +1527,7 @@ fn staticAssert(p: *Parser) Error!bool { |
| 1526 | 1527 | } else { |
| 1527 | 1528 | if (!res.val.toBool(p.comp)) { |
| 1528 | 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 | 1531 | defer allocating.deinit(); |
| 1531 | 1532 | |
| 1532 | 1533 | if (p.staticAssertMessage(res_node, str, &allocating) catch return error.OutOfMemory) |message| { |
| ... | ... | @@ -9719,7 +9720,7 @@ fn primaryExpr(p: *Parser) Error!?Result { |
| 9719 | 9720 | qt = some.qt; |
| 9720 | 9721 | } else if (p.func.qt) |func_qt| { |
| 9721 | 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 | 9724 | defer allocating.deinit(); |
| 9724 | 9725 | |
| 9725 | 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 | 10609 | const arena = arena_state.allocator(); |
| 10609 | 10610 | |
| 10610 | 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 | 10613 | defer comp.deinit(); |
| 10613 | 10614 | |
| 10614 | 10615 | const file = try comp.addSourceFromBuffer("file.c", |
lib/compiler/aro/aro/Preprocessor.zig+8-5| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const mem = std.mem; |
| 3 | 4 | const Allocator = mem.Allocator; |
| 4 | 5 | const assert = std.debug.assert; |
| ... | ... | @@ -1064,11 +1065,13 @@ fn fatalNotFound(pp: *Preprocessor, tok: TokenWithExpansionLocs, filename: []con |
| 1064 | 1065 | |
| 1065 | 1066 | fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) void { |
| 1066 | 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 | 1071 | const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start }); |
| 1069 | 1072 | |
| 1070 | 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 | 1075 | const w = &stderr.interface; |
| 1073 | 1076 | |
| 1074 | 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 | 3902 | defer arena.deinit(); |
| 3900 | 3903 | |
| 3901 | 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 | 3906 | defer comp.deinit(); |
| 3904 | 3907 | |
| 3905 | 3908 | try comp.addDefaultPragmaHandlers(); |
| ... | ... | @@ -3966,7 +3969,7 @@ test "destringify" { |
| 3966 | 3969 | var arena: std.heap.ArenaAllocator = .init(gpa); |
| 3967 | 3970 | defer arena.deinit(); |
| 3968 | 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 | 3973 | defer comp.deinit(); |
| 3971 | 3974 | var pp = Preprocessor.init(&comp, .default); |
| 3972 | 3975 | defer pp.deinit(); |
| ... | ... | @@ -4029,7 +4032,7 @@ test "Include guards" { |
| 4029 | 4032 | const arena = arena_state.allocator(); |
| 4030 | 4033 | |
| 4031 | 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 | 4036 | defer comp.deinit(); |
| 4034 | 4037 | var pp = Preprocessor.init(&comp, .default); |
| 4035 | 4038 | defer pp.deinit(); |
lib/compiler/aro/aro/Tokenizer.zig+3-2| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const assert = std.debug.assert; |
| 3 | 4 | |
| 4 | 5 | const Compilation = @import("Compilation.zig"); |
| ... | ... | @@ -2326,7 +2327,7 @@ test "Tokenizer fuzz test" { |
| 2326 | 2327 | fn testOne(_: @This(), input_bytes: []const u8) anyerror!void { |
| 2327 | 2328 | var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); |
| 2328 | 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 | 2331 | defer comp.deinit(); |
| 2331 | 2332 | |
| 2332 | 2333 | const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes); |
| ... | ... | @@ -2351,7 +2352,7 @@ test "Tokenizer fuzz test" { |
| 2351 | 2352 | fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, langopts: ?LangOpts) !void { |
| 2352 | 2353 | var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); |
| 2353 | 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 | 2356 | defer comp.deinit(); |
| 2356 | 2357 | if (langopts) |provided| { |
| 2357 | 2358 | comp.langopts = provided; |
lib/compiler/aro/aro/Toolchain.zig+11-7| ... | ... | @@ -497,10 +497,11 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void { |
| 497 | 497 | const comp = d.comp; |
| 498 | 498 | const gpa = comp.gpa; |
| 499 | 499 | const arena = comp.arena; |
| 500 | const io = comp.io; | |
| 500 | 501 | try d.includes.ensureUnusedCapacity(gpa, 1); |
| 501 | 502 | if (d.resource_dir) |resource_dir| { |
| 502 | 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 | 505 | return d.fatal("Aro builtin headers not found in provided -resource-dir", .{}); |
| 505 | 506 | }; |
| 506 | 507 | d.includes.appendAssumeCapacity(.{ .kind = .system, .path = path }); |
| ... | ... | @@ -508,10 +509,10 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void { |
| 508 | 509 | } |
| 509 | 510 | var search_path = d.aro_name; |
| 510 | 511 | while (std.fs.path.dirname(search_path)) |dirname| : (search_path = dirname) { |
| 511 | var base_dir = d.comp.cwd.openDir(dirname, .{}) catch continue; | |
| 512 | defer base_dir.close(); | |
| 512 | var base_dir = d.comp.cwd.openDir(io, dirname, .{}) catch continue; | |
| 513 | defer base_dir.close(io); | |
| 513 | 514 | |
| 514 | base_dir.access("include/stddef.h", .{}) catch continue; | |
| 515 | base_dir.access(io, "include/stddef.h", .{}) catch continue; | |
| 515 | 516 | const path = try std.fs.path.join(arena, &.{ dirname, "include" }); |
| 516 | 517 | d.includes.appendAssumeCapacity(.{ .kind = .system, .path = path }); |
| 517 | 518 | break; |
| ... | ... | @@ -523,12 +524,14 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void { |
| 523 | 524 | /// Otherwise returns a slice of `buf`. If the file is larger than `buf` partial contents are returned |
| 524 | 525 | pub fn readFile(tc: *const Toolchain, path: []const u8, buf: []u8) ?[]const u8 { |
| 525 | 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 | } |
| 528 | 530 | |
| 529 | 531 | pub fn exists(tc: *const Toolchain, path: []const u8) bool { |
| 530 | 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 | 535 | return true; |
| 533 | 536 | } |
| 534 | 537 | |
| ... | ... | @@ -546,7 +549,8 @@ pub fn canExecute(tc: *const Toolchain, path: []const u8) bool { |
| 546 | 549 | } |
| 547 | 550 | |
| 548 | 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 | 554 | // Todo: ensure path is not a directory |
| 551 | 555 | return true; |
| 552 | 556 | } |
lib/compiler/aro/aro/Value.zig+6-5| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const assert = std.debug.assert; |
| 3 | 4 | const BigIntConst = std.math.big.int.Const; |
| 4 | 5 | const BigIntMutable = std.math.big.int.Mutable; |
| ... | ... | @@ -80,7 +81,7 @@ test "minUnsignedBits" { |
| 80 | 81 | defer arena_state.deinit(); |
| 81 | 82 | const arena = arena_state.allocator(); |
| 82 | 83 | |
| 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 | 85 | defer comp.deinit(); |
| 85 | 86 | const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); |
| 86 | 87 | comp.target = .fromZigTarget(try std.zig.system.resolveTargetQuery(std.testing.io, target_query)); |
| ... | ... | @@ -119,7 +120,7 @@ test "minSignedBits" { |
| 119 | 120 | defer arena_state.deinit(); |
| 120 | 121 | const arena = arena_state.allocator(); |
| 121 | 122 | |
| 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 | 124 | defer comp.deinit(); |
| 124 | 125 | const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); |
| 125 | 126 | comp.target = .fromZigTarget(try std.zig.system.resolveTargetQuery(std.testing.io, target_query)); |
| ... | ... | @@ -1080,7 +1081,7 @@ const NestedPrint = union(enum) { |
| 1080 | 1081 | }, |
| 1081 | 1082 | }; |
| 1082 | 1083 | |
| 1083 | pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 1084 | pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w: *Io.Writer) Io.Writer.Error!void { | |
| 1084 | 1085 | try w.writeByte('&'); |
| 1085 | 1086 | try w.writeAll(base); |
| 1086 | 1087 | if (!offset.isZero(comp)) { |
| ... | ... | @@ -1089,7 +1090,7 @@ pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w |
| 1089 | 1090 | } |
| 1090 | 1091 | } |
| 1091 | 1092 | |
| 1092 | pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!?NestedPrint { | |
| 1093 | pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *Io.Writer) Io.Writer.Error!?NestedPrint { | |
| 1093 | 1094 | if (qt.is(comp, .bool)) { |
| 1094 | 1095 | try w.writeAll(if (v.isZero(comp)) "false" else "true"); |
| 1095 | 1096 | return null; |
| ... | ... | @@ -1116,7 +1117,7 @@ pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *std.Io.Writer |
| 1116 | 1117 | return null; |
| 1117 | 1118 | } |
| 1118 | 1119 | |
| 1119 | pub fn printString(bytes: []const u8, qt: QualType, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 1120 | pub fn printString(bytes: []const u8, qt: QualType, comp: *const Compilation, w: *Io.Writer) Io.Writer.Error!void { | |
| 1120 | 1121 | const size: Compilation.CharUnitSize = @enumFromInt(qt.childType(comp).sizeof(comp)); |
| 1121 | 1122 | const without_null = bytes[0 .. bytes.len - @intFromEnum(size)]; |
| 1122 | 1123 | try w.writeByte('"'); |
lib/compiler/aro/backend/Assembly.zig+3-2| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const Allocator = std.mem.Allocator; |
| 3 | 4 | |
| 4 | 5 | data: []const u8, |
| ... | ... | @@ -11,8 +12,8 @@ pub fn deinit(self: *const Assembly, gpa: Allocator) void { |
| 11 | 12 | gpa.free(self.text); |
| 12 | 13 | } |
| 13 | 14 | |
| 14 | pub fn writeToFile(self: Assembly, file: std.fs.File) !void { | |
| 15 | var file_writer = file.writer(&.{}); | |
| 15 | pub fn writeToFile(self: Assembly, io: Io, file: Io.File) !void { | |
| 16 | var file_writer = file.writer(io, &.{}); | |
| 16 | 17 | |
| 17 | 18 | var buffers = [_][]const u8{ self.data, self.text }; |
| 18 | 19 | try file_writer.interface.writeSplatAll(&buffers, 1); |
lib/compiler/aro/main.zig+5-4| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const Allocator = mem.Allocator; |
| 3 | 4 | const mem = std.mem; |
| 4 | 5 | const process = std.process; |
| ... | ... | @@ -30,7 +31,7 @@ pub fn main() u8 { |
| 30 | 31 | defer arena_instance.deinit(); |
| 31 | 32 | const arena = arena_instance.allocator(); |
| 32 | 33 | |
| 33 | var threaded: std.Io.Threaded = .init(gpa); | |
| 34 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 34 | 35 | defer threaded.deinit(); |
| 35 | 36 | const io = threaded.io(); |
| 36 | 37 | |
| ... | ... | @@ -42,7 +43,7 @@ pub fn main() u8 { |
| 42 | 43 | return 1; |
| 43 | 44 | }; |
| 44 | 45 | |
| 45 | const aro_name = std.fs.selfExePathAlloc(gpa) catch { | |
| 46 | const aro_name = process.executablePathAlloc(io, gpa) catch { | |
| 46 | 47 | std.debug.print("unable to find Aro executable path\n", .{}); |
| 47 | 48 | if (fast_exit) process.exit(1); |
| 48 | 49 | return 1; |
| ... | ... | @@ -50,7 +51,7 @@ pub fn main() u8 { |
| 50 | 51 | defer gpa.free(aro_name); |
| 51 | 52 | |
| 52 | 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 | 55 | var diagnostics: Diagnostics = .{ |
| 55 | 56 | .output = .{ .to_writer = .{ |
| 56 | 57 | .color = .detect(stderr.file), |
| ... | ... | @@ -58,7 +59,7 @@ pub fn main() u8 { |
| 58 | 59 | } }, |
| 59 | 60 | }; |
| 60 | 61 | |
| 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 | 63 | error.OutOfMemory => { |
| 63 | 64 | std.debug.print("out of memory\n", .{}); |
| 64 | 65 | if (fast_exit) process.exit(1); |
lib/compiler/build_runner.zig+195-198| ... | ... | @@ -7,14 +7,13 @@ const assert = std.debug.assert; |
| 7 | 7 | const fmt = std.fmt; |
| 8 | 8 | const mem = std.mem; |
| 9 | 9 | const process = std.process; |
| 10 | const File = std.fs.File; | |
| 10 | const File = std.Io.File; | |
| 11 | 11 | const Step = std.Build.Step; |
| 12 | 12 | const Watch = std.Build.Watch; |
| 13 | 13 | const WebServer = std.Build.WebServer; |
| 14 | 14 | const Allocator = std.mem.Allocator; |
| 15 | 15 | const fatal = std.process.fatal; |
| 16 | 16 | const Writer = std.Io.Writer; |
| 17 | const tty = std.Io.tty; | |
| 18 | 17 | |
| 19 | 18 | pub const root = @import("@build"); |
| 20 | 19 | pub const dependencies = @import("@dependencies"); |
| ... | ... | @@ -40,7 +39,7 @@ pub fn main() !void { |
| 40 | 39 | |
| 41 | 40 | const args = try process.argsAlloc(arena); |
| 42 | 41 | |
| 43 | var threaded: std.Io.Threaded = .init(gpa); | |
| 42 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 44 | 43 | defer threaded.deinit(); |
| 45 | 44 | const io = threaded.io(); |
| 46 | 45 | |
| ... | ... | @@ -53,24 +52,26 @@ pub fn main() !void { |
| 53 | 52 | const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{}); |
| 54 | 53 | const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{}); |
| 55 | 54 | |
| 55 | const cwd: Io.Dir = .cwd(); | |
| 56 | ||
| 56 | 57 | const zig_lib_directory: std.Build.Cache.Directory = .{ |
| 57 | 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 | }; |
| 60 | 61 | |
| 61 | 62 | const build_root_directory: std.Build.Cache.Directory = .{ |
| 62 | 63 | .path = build_root, |
| 63 | .handle = try std.fs.cwd().openDir(build_root, .{}), | |
| 64 | .handle = try cwd.openDir(io, build_root, .{}), | |
| 64 | 65 | }; |
| 65 | 66 | |
| 66 | 67 | const local_cache_directory: std.Build.Cache.Directory = .{ |
| 67 | 68 | .path = cache_root, |
| 68 | .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}), | |
| 69 | .handle = try cwd.createDirPathOpen(io, cache_root, .{}), | |
| 69 | 70 | }; |
| 70 | 71 | |
| 71 | 72 | const global_cache_directory: std.Build.Cache.Directory = .{ |
| 72 | 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 | }; |
| 75 | 76 | |
| 76 | 77 | var graph: std.Build.Graph = .{ |
| ... | ... | @@ -79,7 +80,7 @@ pub fn main() !void { |
| 79 | 80 | .cache = .{ |
| 80 | 81 | .io = io, |
| 81 | 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 | 85 | .zig_exe = zig_exe, |
| 85 | 86 | .env_map = try process.getEnvMap(arena), |
| ... | ... | @@ -92,7 +93,7 @@ pub fn main() !void { |
| 92 | 93 | .time_report = false, |
| 93 | 94 | }; |
| 94 | 95 | |
| 95 | graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() }); | |
| 96 | graph.cache.addPrefix(.{ .path = null, .handle = cwd }); | |
| 96 | 97 | graph.cache.addPrefix(build_root_directory); |
| 97 | 98 | graph.cache.addPrefix(local_cache_directory); |
| 98 | 99 | graph.cache.addPrefix(global_cache_directory); |
| ... | ... | @@ -285,8 +286,8 @@ pub fn main() !void { |
| 285 | 286 | const next_arg = nextArg(args, &arg_idx) orelse |
| 286 | 287 | fatalWithHint("expected u16 after '{s}'", .{arg}); |
| 287 | 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 | next_arg, @errorName(err), | |
| 289 | fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {t}\n", .{ | |
| 290 | next_arg, err, | |
| 290 | 291 | }); |
| 291 | 292 | }; |
| 292 | 293 | } else if (mem.eql(u8, arg, "--webui")) { |
| ... | ... | @@ -428,14 +429,21 @@ pub fn main() !void { |
| 428 | 429 | } |
| 429 | 430 | } |
| 430 | 431 | |
| 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 | 441 | if (webui_listen != null) { |
| 432 | 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 | 443 | if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{}); |
| 434 | 444 | } |
| 435 | 445 | |
| 436 | const ttyconf = color.detectTtyConf(); | |
| 437 | ||
| 438 | const main_progress_node = std.Progress.start(.{ | |
| 446 | const main_progress_node = std.Progress.start(io, .{ | |
| 439 | 447 | .disable_printing = (color == .off), |
| 440 | 448 | }); |
| 441 | 449 | defer main_progress_node.end(); |
| ... | ... | @@ -457,7 +465,7 @@ pub fn main() !void { |
| 457 | 465 | } |
| 458 | 466 | const s = std.fs.path.sep_str; |
| 459 | 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 | 469 | .sub_path = tmp_sub_path, |
| 462 | 470 | .data = buffer.items, |
| 463 | 471 | .flags = .{ .exclusive = true }, |
| ... | ... | @@ -476,14 +484,14 @@ pub fn main() !void { |
| 476 | 484 | validateSystemLibraryOptions(builder); |
| 477 | 485 | |
| 478 | 486 | if (help_menu) { |
| 479 | var w = initStdoutWriter(); | |
| 487 | var w = initStdoutWriter(io); | |
| 480 | 488 | printUsage(builder, w) catch return stdout_writer_allocation.err.?; |
| 481 | 489 | w.flush() catch return stdout_writer_allocation.err.?; |
| 482 | 490 | return; |
| 483 | 491 | } |
| 484 | 492 | |
| 485 | 493 | if (steps_menu) { |
| 486 | var w = initStdoutWriter(); | |
| 494 | var w = initStdoutWriter(io); | |
| 487 | 495 | printSteps(builder, w) catch return stdout_writer_allocation.err.?; |
| 488 | 496 | w.flush() catch return stdout_writer_allocation.err.?; |
| 489 | 497 | return; |
| ... | ... | @@ -507,8 +515,6 @@ pub fn main() !void { |
| 507 | 515 | .error_style = error_style, |
| 508 | 516 | .multiline_errors = multiline_errors, |
| 509 | 517 | .summary = summary orelse if (watch or webui_listen != null) .line else .failures, |
| 510 | ||
| 511 | .ttyconf = ttyconf, | |
| 512 | 518 | }; |
| 513 | 519 | defer { |
| 514 | 520 | run.memory_blocked_steps.deinit(gpa); |
| ... | ... | @@ -522,10 +528,10 @@ pub fn main() !void { |
| 522 | 528 | |
| 523 | 529 | prepare(arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) { |
| 524 | 530 | error.DependencyLoopDetected => { |
| 525 | // Perhaps in the future there could be an Advanced Options flag such as | |
| 526 | // --debug-build-runner-leaks which would make this code return instead of | |
| 527 | // calling exit. | |
| 528 | std.debug.lockStdErr(); | |
| 531 | // Perhaps in the future there could be an Advanced Options flag | |
| 532 | // such as --debug-build-runner-leaks which would make this code | |
| 533 | // return instead of calling exit. | |
| 534 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; | |
| 529 | 535 | process.exit(1); |
| 530 | 536 | }, |
| 531 | 537 | else => |e| return e, |
| ... | ... | @@ -543,7 +549,6 @@ pub fn main() !void { |
| 543 | 549 | if (builtin.single_threaded) unreachable; // `fatal` above |
| 544 | 550 | break :ws .init(.{ |
| 545 | 551 | .gpa = gpa, |
| 546 | .ttyconf = ttyconf, | |
| 547 | 552 | .graph = &graph, |
| 548 | 553 | .all_steps = run.step_stack.keys(), |
| 549 | 554 | .root_prog_node = main_progress_node, |
| ... | ... | @@ -558,9 +563,9 @@ pub fn main() !void { |
| 558 | 563 | } |
| 559 | 564 | |
| 560 | 565 | rebuild: while (true) : (if (run.error_style.clearOnUpdate()) { |
| 561 | const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation); | |
| 562 | defer std.debug.unlockStderrWriter(); | |
| 563 | try bw.writeAll("\x1B[2J\x1B[3J\x1B[H"); | |
| 566 | const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); | |
| 567 | defer io.unlockStderr(); | |
| 568 | try stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H"); | |
| 564 | 569 | }) { |
| 565 | 570 | if (run.web_server) |*ws| ws.startBuild(); |
| 566 | 571 | |
| ... | ... | @@ -661,9 +666,6 @@ const Run = struct { |
| 661 | 666 | memory_blocked_steps: std.ArrayList(*Step), |
| 662 | 667 | /// Allocated into `gpa`. |
| 663 | 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, | |
| 667 | 669 | |
| 668 | 670 | claimed_rss: usize, |
| 669 | 671 | error_style: ErrorStyle, |
| ... | ... | @@ -737,7 +739,8 @@ fn runStepNames( |
| 737 | 739 | fuzz: ?std.Build.Fuzz.Mode, |
| 738 | 740 | ) !void { |
| 739 | 741 | const gpa = run.gpa; |
| 740 | const io = b.graph.io; | |
| 742 | const graph = b.graph; | |
| 743 | const io = graph.io; | |
| 741 | 744 | const step_stack = &run.step_stack; |
| 742 | 745 | |
| 743 | 746 | { |
| ... | ... | @@ -822,7 +825,7 @@ fn runStepNames( |
| 822 | 825 | } |
| 823 | 826 | if (@bitSizeOf(usize) != 64) { |
| 824 | 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 case | |
| 828 | // being compatible with file system's u64 return value. This is not the case | |
| 826 | 829 | // on 32-bit platforms. |
| 827 | 830 | // Affects or affected by issues #5185, #22523, and #22464. |
| 828 | 831 | fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); |
| ... | ... | @@ -837,7 +840,6 @@ fn runStepNames( |
| 837 | 840 | var f = std.Build.Fuzz.init( |
| 838 | 841 | gpa, |
| 839 | 842 | io, |
| 840 | run.ttyconf, | |
| 841 | 843 | step_stack.keys(), |
| 842 | 844 | parent_prog_node, |
| 843 | 845 | mode, |
| ... | ... | @@ -864,18 +866,19 @@ fn runStepNames( |
| 864 | 866 | .none => break :summary, |
| 865 | 867 | } |
| 866 | 868 | |
| 867 | const w, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation); | |
| 868 | defer std.debug.unlockStderrWriter(); | |
| 869 | const ttyconf = run.ttyconf; | |
| 869 | const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); | |
| 870 | defer io.unlockStderr(); | |
| 871 | const t = stderr.terminal(); | |
| 872 | const w = &stderr.file_writer.interface; | |
| 870 | 873 | |
| 871 | 874 | const total_count = success_count + failure_count + pending_count + skipped_count; |
| 872 | ttyconf.setColor(w, .cyan) catch {}; | |
| 873 | ttyconf.setColor(w, .bold) catch {}; | |
| 875 | t.setColor(.cyan) catch {}; | |
| 876 | t.setColor(.bold) catch {}; | |
| 874 | 877 | w.writeAll("Build Summary: ") catch {}; |
| 875 | ttyconf.setColor(w, .reset) catch {}; | |
| 878 | t.setColor(.reset) catch {}; | |
| 876 | 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 | 882 | var first = true; |
| 880 | 883 | if (skipped_count > 0) { |
| 881 | 884 | w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {}; |
| ... | ... | @@ -886,12 +889,12 @@ fn runStepNames( |
| 886 | 889 | first = false; |
| 887 | 890 | } |
| 888 | 891 | if (!first) w.writeByte(')') catch {}; |
| 889 | ttyconf.setColor(w, .reset) catch {}; | |
| 892 | t.setColor(.reset) catch {}; | |
| 890 | 893 | } |
| 891 | 894 | |
| 892 | 895 | if (test_count > 0) { |
| 893 | 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 | 898 | var first = true; |
| 896 | 899 | if (test_skip_count > 0) { |
| 897 | 900 | w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {}; |
| ... | ... | @@ -910,7 +913,7 @@ fn runStepNames( |
| 910 | 913 | first = false; |
| 911 | 914 | } |
| 912 | 915 | if (!first) w.writeByte(')') catch {}; |
| 913 | ttyconf.setColor(w, .reset) catch {}; | |
| 916 | t.setColor(.reset) catch {}; | |
| 914 | 917 | } |
| 915 | 918 | |
| 916 | 919 | w.writeAll("\n") catch {}; |
| ... | ... | @@ -924,7 +927,7 @@ fn runStepNames( |
| 924 | 927 | var print_node: PrintNode = .{ .parent = null }; |
| 925 | 928 | if (step_names.len == 0) { |
| 926 | 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 | 931 | } else { |
| 929 | 932 | const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: { |
| 930 | 933 | var i: usize = step_names.len; |
| ... | ... | @@ -943,7 +946,7 @@ fn runStepNames( |
| 943 | 946 | for (step_names, 0..) |step_name, i| { |
| 944 | 947 | const tls = b.top_level_steps.get(step_name).?; |
| 945 | 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 | 952 | w.writeByte('\n') catch {}; |
| ... | ... | @@ -960,7 +963,7 @@ fn runStepNames( |
| 960 | 963 | if (run.error_style.verboseContext()) break :code 1; // failure; print build command |
| 961 | 964 | break :code 2; // failure; do not print build command |
| 962 | 965 | }; |
| 963 | std.debug.lockStdErr(); | |
| 966 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; | |
| 964 | 967 | process.exit(code); |
| 965 | 968 | } |
| 966 | 969 | |
| ... | ... | @@ -969,33 +972,30 @@ const PrintNode = struct { |
| 969 | 972 | last: bool = false, |
| 970 | 973 | }; |
| 971 | 974 | |
| 972 | fn printPrefix(node: *PrintNode, stderr: *Writer, ttyconf: tty.Config) !void { | |
| 975 | fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void { | |
| 973 | 976 | const parent = node.parent orelse return; |
| 977 | const writer = stderr.writer; | |
| 974 | 978 | if (parent.parent == null) return; |
| 975 | try printPrefix(parent, stderr, ttyconf); | |
| 979 | try printPrefix(parent, stderr); | |
| 976 | 980 | if (parent.last) { |
| 977 | try stderr.writeAll(" "); | |
| 981 | try writer.writeAll(" "); | |
| 978 | 982 | } else { |
| 979 | try stderr.writeAll(switch (ttyconf) { | |
| 980 | .no_color, .windows_api => "| ", | |
| 983 | try writer.writeAll(switch (stderr.mode) { | |
| 981 | 984 | .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ |
| 985 | else => "| ", | |
| 982 | 986 | }); |
| 983 | 987 | } |
| 984 | 988 | } |
| 985 | 989 | |
| 986 | fn printChildNodePrefix(stderr: *Writer, ttyconf: tty.Config) !void { | |
| 987 | try stderr.writeAll(switch (ttyconf) { | |
| 988 | .no_color, .windows_api => "+- ", | |
| 990 | fn printChildNodePrefix(stderr: Io.Terminal) !void { | |
| 991 | try stderr.writer.writeAll(switch (stderr.mode) { | |
| 989 | 992 | .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─ |
| 993 | else => "+- ", | |
| 990 | 994 | }); |
| 991 | 995 | } |
| 992 | 996 | |
| 993 | fn printStepStatus( | |
| 994 | s: *Step, | |
| 995 | stderr: *Writer, | |
| 996 | ttyconf: tty.Config, | |
| 997 | run: *const Run, | |
| 998 | ) !void { | |
| 997 | fn printStepStatus(s: *Step, stderr: Io.Terminal, run: *const Run) !void { | |
| 998 | const writer = stderr.writer; | |
| 999 | 999 | switch (s.state) { |
| 1000 | 1000 | .precheck_unstarted => unreachable, |
| 1001 | 1001 | .precheck_started => unreachable, |
| ... | ... | @@ -1003,139 +1003,135 @@ fn printStepStatus( |
| 1003 | 1003 | .running => unreachable, |
| 1004 | 1004 | |
| 1005 | 1005 | .dependency_failure => { |
| 1006 | try ttyconf.setColor(stderr, .dim); | |
| 1007 | try stderr.writeAll(" transitive failure\n"); | |
| 1008 | try ttyconf.setColor(stderr, .reset); | |
| 1006 | try stderr.setColor(.dim); | |
| 1007 | try writer.writeAll(" transitive failure\n"); | |
| 1008 | try stderr.setColor(.reset); | |
| 1009 | 1009 | }, |
| 1010 | 1010 | |
| 1011 | 1011 | .success => { |
| 1012 | try ttyconf.setColor(stderr, .green); | |
| 1012 | try stderr.setColor(.green); | |
| 1013 | 1013 | if (s.result_cached) { |
| 1014 | try stderr.writeAll(" cached"); | |
| 1014 | try writer.writeAll(" cached"); | |
| 1015 | 1015 | } else if (s.test_results.test_count > 0) { |
| 1016 | 1016 | const pass_count = s.test_results.passCount(); |
| 1017 | 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 | 1019 | if (s.test_results.skip_count > 0) { |
| 1020 | try ttyconf.setColor(stderr, .reset); | |
| 1021 | try stderr.writeAll(", "); | |
| 1022 | try ttyconf.setColor(stderr, .yellow); | |
| 1023 | try stderr.print("{d} skip", .{s.test_results.skip_count}); | |
| 1020 | try stderr.setColor(.reset); | |
| 1021 | try writer.writeAll(", "); | |
| 1022 | try stderr.setColor(.yellow); | |
| 1023 | try writer.print("{d} skip", .{s.test_results.skip_count}); | |
| 1024 | 1024 | } |
| 1025 | try ttyconf.setColor(stderr, .reset); | |
| 1026 | try stderr.print(" ({d} total)", .{s.test_results.test_count}); | |
| 1025 | try stderr.setColor(.reset); | |
| 1026 | try writer.print(" ({d} total)", .{s.test_results.test_count}); | |
| 1027 | 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 | 1031 | if (s.result_duration_ns) |ns| { |
| 1032 | try ttyconf.setColor(stderr, .dim); | |
| 1032 | try stderr.setColor(.dim); | |
| 1033 | 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 | 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 | 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 | 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 | 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 | 1046 | if (s.result_peak_rss != 0) { |
| 1047 | 1047 | const rss = s.result_peak_rss; |
| 1048 | try ttyconf.setColor(stderr, .dim); | |
| 1048 | try stderr.setColor(.dim); | |
| 1049 | 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 | 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 | 1053 | } else if (rss >= 1000) { |
| 1054 | try stderr.print(" MaxRSS:{d}K", .{rss / 1000}); | |
| 1054 | try writer.print(" MaxRSS:{d}K", .{rss / 1000}); | |
| 1055 | 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 | 1062 | .skipped, .skipped_oom => |skip| { |
| 1063 | try ttyconf.setColor(stderr, .yellow); | |
| 1064 | try stderr.writeAll(" skipped"); | |
| 1063 | try stderr.setColor(.yellow); | |
| 1064 | try writer.writeAll(" skipped"); | |
| 1065 | 1065 | if (skip == .skipped_oom) { |
| 1066 | try stderr.writeAll(" (not enough memory)"); | |
| 1067 | try ttyconf.setColor(stderr, .dim); | |
| 1068 | try stderr.print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss }); | |
| 1069 | try ttyconf.setColor(stderr, .yellow); | |
| 1066 | try writer.writeAll(" (not enough memory)"); | |
| 1067 | try stderr.setColor(.dim); | |
| 1068 | try writer.print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss }); | |
| 1069 | try stderr.setColor(.yellow); | |
| 1070 | 1070 | } |
| 1071 | try stderr.writeAll("\n"); | |
| 1072 | try ttyconf.setColor(stderr, .reset); | |
| 1071 | try writer.writeAll("\n"); | |
| 1072 | try stderr.setColor(.reset); | |
| 1073 | 1073 | }, |
| 1074 | 1074 | .failure => { |
| 1075 | try printStepFailure(s, stderr, ttyconf, false); | |
| 1076 | try ttyconf.setColor(stderr, .reset); | |
| 1075 | try printStepFailure(s, stderr, false); | |
| 1076 | try stderr.setColor(.reset); | |
| 1077 | 1077 | }, |
| 1078 | 1078 | } |
| 1079 | 1079 | } |
| 1080 | 1080 | |
| 1081 | fn printStepFailure( | |
| 1082 | s: *Step, | |
| 1083 | stderr: *Writer, | |
| 1084 | ttyconf: tty.Config, | |
| 1085 | dim: bool, | |
| 1086 | ) !void { | |
| 1081 | fn printStepFailure(s: *Step, stderr: Io.Terminal, dim: bool) !void { | |
| 1082 | const w = stderr.writer; | |
| 1087 | 1083 | if (s.result_error_bundle.errorMessageCount() > 0) { |
| 1088 | try ttyconf.setColor(stderr, .red); | |
| 1089 | try stderr.print(" {d} errors\n", .{ | |
| 1084 | try stderr.setColor(.red); | |
| 1085 | try w.print(" {d} errors\n", .{ | |
| 1090 | 1086 | s.result_error_bundle.errorMessageCount(), |
| 1091 | 1087 | }); |
| 1092 | 1088 | } else if (!s.test_results.isSuccess()) { |
| 1093 | 1089 | // These first values include all of the test "statuses". Every test is either passsed, |
| 1094 | 1090 | // skipped, failed, crashed, or timed out. |
| 1095 | try ttyconf.setColor(stderr, .green); | |
| 1096 | try stderr.print(" {d} pass", .{s.test_results.passCount()}); | |
| 1097 | try ttyconf.setColor(stderr, .reset); | |
| 1098 | if (dim) try ttyconf.setColor(stderr, .dim); | |
| 1091 | try stderr.setColor(.green); | |
| 1092 | try w.print(" {d} pass", .{s.test_results.passCount()}); | |
| 1093 | try stderr.setColor(.reset); | |
| 1094 | if (dim) try stderr.setColor(.dim); | |
| 1099 | 1095 | if (s.test_results.skip_count > 0) { |
| 1100 | try stderr.writeAll(", "); | |
| 1101 | try ttyconf.setColor(stderr, .yellow); | |
| 1102 | try stderr.print("{d} skip", .{s.test_results.skip_count}); | |
| 1103 | try ttyconf.setColor(stderr, .reset); | |
| 1104 | if (dim) try ttyconf.setColor(stderr, .dim); | |
| 1096 | try w.writeAll(", "); | |
| 1097 | try stderr.setColor(.yellow); | |
| 1098 | try w.print("{d} skip", .{s.test_results.skip_count}); | |
| 1099 | try stderr.setColor(.reset); | |
| 1100 | if (dim) try stderr.setColor(.dim); | |
| 1105 | 1101 | } |
| 1106 | 1102 | if (s.test_results.fail_count > 0) { |
| 1107 | try stderr.writeAll(", "); | |
| 1108 | try ttyconf.setColor(stderr, .red); | |
| 1109 | try stderr.print("{d} fail", .{s.test_results.fail_count}); | |
| 1110 | try ttyconf.setColor(stderr, .reset); | |
| 1111 | if (dim) try ttyconf.setColor(stderr, .dim); | |
| 1103 | try w.writeAll(", "); | |
| 1104 | try stderr.setColor(.red); | |
| 1105 | try w.print("{d} fail", .{s.test_results.fail_count}); | |
| 1106 | try stderr.setColor(.reset); | |
| 1107 | if (dim) try stderr.setColor(.dim); | |
| 1112 | 1108 | } |
| 1113 | 1109 | if (s.test_results.crash_count > 0) { |
| 1114 | try stderr.writeAll(", "); | |
| 1115 | try ttyconf.setColor(stderr, .red); | |
| 1116 | try stderr.print("{d} crash", .{s.test_results.crash_count}); | |
| 1117 | try ttyconf.setColor(stderr, .reset); | |
| 1118 | if (dim) try ttyconf.setColor(stderr, .dim); | |
| 1110 | try w.writeAll(", "); | |
| 1111 | try stderr.setColor(.red); | |
| 1112 | try w.print("{d} crash", .{s.test_results.crash_count}); | |
| 1113 | try stderr.setColor(.reset); | |
| 1114 | if (dim) try stderr.setColor(.dim); | |
| 1119 | 1115 | } |
| 1120 | 1116 | if (s.test_results.timeout_count > 0) { |
| 1121 | try stderr.writeAll(", "); | |
| 1122 | try ttyconf.setColor(stderr, .red); | |
| 1123 | try stderr.print("{d} timeout", .{s.test_results.timeout_count}); | |
| 1124 | try ttyconf.setColor(stderr, .reset); | |
| 1125 | if (dim) try ttyconf.setColor(stderr, .dim); | |
| 1117 | try w.writeAll(", "); | |
| 1118 | try stderr.setColor(.red); | |
| 1119 | try w.print("{d} timeout", .{s.test_results.timeout_count}); | |
| 1120 | try stderr.setColor(.reset); | |
| 1121 | if (dim) try stderr.setColor(.dim); | |
| 1126 | 1122 | } |
| 1127 | try stderr.print(" ({d} total)", .{s.test_results.test_count}); | |
| 1123 | try w.print(" ({d} total)", .{s.test_results.test_count}); | |
| 1128 | 1124 | |
| 1129 | 1125 | // Memory leaks are intentionally written after the total, because is isn't a test *status*, |
| 1130 | 1126 | // but just a flag that any tests -- even passed ones -- can have. We also use a different |
| 1131 | 1127 | // separator, so it looks like: |
| 1132 | 1128 | // 2 pass, 1 skip, 2 fail (5 total); 2 leaks |
| 1133 | 1129 | if (s.test_results.leak_count > 0) { |
| 1134 | try stderr.writeAll("; "); | |
| 1135 | try ttyconf.setColor(stderr, .red); | |
| 1136 | try stderr.print("{d} leaks", .{s.test_results.leak_count}); | |
| 1137 | try ttyconf.setColor(stderr, .reset); | |
| 1138 | if (dim) try ttyconf.setColor(stderr, .dim); | |
| 1130 | try w.writeAll("; "); | |
| 1131 | try stderr.setColor(.red); | |
| 1132 | try w.print("{d} leaks", .{s.test_results.leak_count}); | |
| 1133 | try stderr.setColor(.reset); | |
| 1134 | if (dim) try stderr.setColor(.dim); | |
| 1139 | 1135 | } |
| 1140 | 1136 | |
| 1141 | 1137 | // It's usually not helpful to know how many error logs there were because they tend to |
| ... | ... | @@ -1148,21 +1144,21 @@ fn printStepFailure( |
| 1148 | 1144 | break :show alt_results.isSuccess(); |
| 1149 | 1145 | }; |
| 1150 | 1146 | if (show_err_logs) { |
| 1151 | try stderr.writeAll("; "); | |
| 1152 | try ttyconf.setColor(stderr, .red); | |
| 1153 | try stderr.print("{d} error logs", .{s.test_results.log_err_count}); | |
| 1154 | try ttyconf.setColor(stderr, .reset); | |
| 1155 | if (dim) try ttyconf.setColor(stderr, .dim); | |
| 1147 | try w.writeAll("; "); | |
| 1148 | try stderr.setColor(.red); | |
| 1149 | try w.print("{d} error logs", .{s.test_results.log_err_count}); | |
| 1150 | try stderr.setColor(.reset); | |
| 1151 | if (dim) try stderr.setColor(.dim); | |
| 1156 | 1152 | } |
| 1157 | 1153 | |
| 1158 | try stderr.writeAll("\n"); | |
| 1154 | try w.writeAll("\n"); | |
| 1159 | 1155 | } else if (s.result_error_msgs.items.len > 0) { |
| 1160 | try ttyconf.setColor(stderr, .red); | |
| 1161 | try stderr.writeAll(" failure\n"); | |
| 1156 | try stderr.setColor(.red); | |
| 1157 | try w.writeAll(" failure\n"); | |
| 1162 | 1158 | } else { |
| 1163 | 1159 | assert(s.result_stderr.len > 0); |
| 1164 | try ttyconf.setColor(stderr, .red); | |
| 1165 | try stderr.writeAll(" stderr\n"); | |
| 1160 | try stderr.setColor(.red); | |
| 1161 | try w.writeAll(" w\n"); | |
| 1166 | 1162 | } |
| 1167 | 1163 | } |
| 1168 | 1164 | |
| ... | ... | @@ -1170,11 +1166,11 @@ fn printTreeStep( |
| 1170 | 1166 | b: *std.Build, |
| 1171 | 1167 | s: *Step, |
| 1172 | 1168 | run: *const Run, |
| 1173 | stderr: *Writer, | |
| 1174 | ttyconf: tty.Config, | |
| 1169 | stderr: Io.Terminal, | |
| 1175 | 1170 | parent_node: *PrintNode, |
| 1176 | 1171 | step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), |
| 1177 | 1172 | ) !void { |
| 1173 | const writer = stderr.writer; | |
| 1178 | 1174 | const first = step_stack.swapRemove(s); |
| 1179 | 1175 | const summary = run.summary; |
| 1180 | 1176 | const skip = switch (summary) { |
| ... | ... | @@ -1184,26 +1180,26 @@ fn printTreeStep( |
| 1184 | 1180 | .failures => s.state == .success, |
| 1185 | 1181 | }; |
| 1186 | 1182 | if (skip) return; |
| 1187 | try printPrefix(parent_node, stderr, ttyconf); | |
| 1183 | try printPrefix(parent_node, stderr); | |
| 1188 | 1184 | |
| 1189 | 1185 | if (parent_node.parent != null) { |
| 1190 | 1186 | if (parent_node.last) { |
| 1191 | try printChildNodePrefix(stderr, ttyconf); | |
| 1187 | try printChildNodePrefix(stderr); | |
| 1192 | 1188 | } else { |
| 1193 | try stderr.writeAll(switch (ttyconf) { | |
| 1194 | .no_color, .windows_api => "+- ", | |
| 1189 | try writer.writeAll(switch (stderr.mode) { | |
| 1195 | 1190 | .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ |
| 1191 | else => "+- ", | |
| 1196 | 1192 | }); |
| 1197 | 1193 | } |
| 1198 | 1194 | } |
| 1199 | 1195 | |
| 1200 | if (!first) try ttyconf.setColor(stderr, .dim); | |
| 1196 | if (!first) try stderr.setColor(.dim); | |
| 1201 | 1197 | |
| 1202 | 1198 | // dep_prefix omitted here because it is redundant with the tree. |
| 1203 | try stderr.writeAll(s.name); | |
| 1199 | try writer.writeAll(s.name); | |
| 1204 | 1200 | |
| 1205 | 1201 | if (first) { |
| 1206 | try printStepStatus(s, stderr, ttyconf, run); | |
| 1202 | try printStepStatus(s, stderr, run); | |
| 1207 | 1203 | |
| 1208 | 1204 | const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: { |
| 1209 | 1205 | var i: usize = s.dependencies.items.len; |
| ... | ... | @@ -1225,17 +1221,17 @@ fn printTreeStep( |
| 1225 | 1221 | .parent = parent_node, |
| 1226 | 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 | 1226 | } else { |
| 1231 | 1227 | if (s.dependencies.items.len == 0) { |
| 1232 | try stderr.writeAll(" (reused)\n"); | |
| 1228 | try writer.writeAll(" (reused)\n"); | |
| 1233 | 1229 | } else { |
| 1234 | try stderr.print(" (+{d} more reused dependencies)\n", .{ | |
| 1230 | try writer.print(" (+{d} more reused dependencies)\n", .{ | |
| 1235 | 1231 | s.dependencies.items.len, |
| 1236 | 1232 | }); |
| 1237 | 1233 | } |
| 1238 | try ttyconf.setColor(stderr, .reset); | |
| 1234 | try stderr.setColor(.reset); | |
| 1239 | 1235 | } |
| 1240 | 1236 | } |
| 1241 | 1237 | |
| ... | ... | @@ -1306,7 +1302,8 @@ fn workerMakeOneStep( |
| 1306 | 1302 | prog_node: std.Progress.Node, |
| 1307 | 1303 | run: *Run, |
| 1308 | 1304 | ) void { |
| 1309 | const io = b.graph.io; | |
| 1305 | const graph = b.graph; | |
| 1306 | const io = graph.io; | |
| 1310 | 1307 | const gpa = run.gpa; |
| 1311 | 1308 | |
| 1312 | 1309 | // First, check the conditions for running this step. If they are not met, |
| ... | ... | @@ -1366,7 +1363,6 @@ fn workerMakeOneStep( |
| 1366 | 1363 | .progress_node = sub_prog_node, |
| 1367 | 1364 | .watch = run.watch, |
| 1368 | 1365 | .web_server = if (run.web_server) |*ws| ws else null, |
| 1369 | .ttyconf = run.ttyconf, | |
| 1370 | 1366 | .unit_test_timeout_ns = run.unit_test_timeout_ns, |
| 1371 | 1367 | .gpa = gpa, |
| 1372 | 1368 | }); |
| ... | ... | @@ -1376,10 +1372,11 @@ fn workerMakeOneStep( |
| 1376 | 1372 | const show_error_msgs = s.result_error_msgs.items.len > 0; |
| 1377 | 1373 | const show_stderr = s.result_stderr.len > 0; |
| 1378 | 1374 | if (show_error_msgs or show_compile_errors or show_stderr) { |
| 1379 | const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation); | |
| 1380 | defer std.debug.unlockStderrWriter(); | |
| 1381 | const ttyconf = run.ttyconf; | |
| 1382 | printErrorMessages(gpa, s, .{}, bw, ttyconf, run.error_style, run.multiline_errors) catch {}; | |
| 1375 | const stderr = io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode) catch |err| switch (err) { | |
| 1376 | error.Canceled => return, | |
| 1377 | }; | |
| 1378 | defer io.unlockStderr(); | |
| 1379 | printErrorMessages(gpa, s, .{}, stderr.terminal(), run.error_style, run.multiline_errors) catch {}; | |
| 1383 | 1380 | } |
| 1384 | 1381 | |
| 1385 | 1382 | handle_result: { |
| ... | ... | @@ -1446,11 +1443,11 @@ pub fn printErrorMessages( |
| 1446 | 1443 | gpa: Allocator, |
| 1447 | 1444 | failing_step: *Step, |
| 1448 | 1445 | options: std.zig.ErrorBundle.RenderOptions, |
| 1449 | stderr: *Writer, | |
| 1450 | ttyconf: tty.Config, | |
| 1446 | stderr: Io.Terminal, | |
| 1451 | 1447 | error_style: ErrorStyle, |
| 1452 | 1448 | multiline_errors: MultilineErrors, |
| 1453 | 1449 | ) !void { |
| 1450 | const writer = stderr.writer; | |
| 1454 | 1451 | if (error_style.verboseContext()) { |
| 1455 | 1452 | // Provide context for where these error messages are coming from by |
| 1456 | 1453 | // printing the corresponding Step subtree. |
| ... | ... | @@ -1462,70 +1459,70 @@ pub fn printErrorMessages( |
| 1462 | 1459 | } |
| 1463 | 1460 | |
| 1464 | 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 | 1463 | var indent: usize = 0; |
| 1467 | 1464 | while (step_stack.pop()) |s| : (indent += 1) { |
| 1468 | 1465 | if (indent > 0) { |
| 1469 | try stderr.splatByteAll(' ', (indent - 1) * 3); | |
| 1470 | try printChildNodePrefix(stderr, ttyconf); | |
| 1466 | try writer.splatByteAll(' ', (indent - 1) * 3); | |
| 1467 | try printChildNodePrefix(stderr); | |
| 1471 | 1468 | } |
| 1472 | 1469 | |
| 1473 | try stderr.writeAll(s.name); | |
| 1470 | try writer.writeAll(s.name); | |
| 1474 | 1471 | |
| 1475 | 1472 | if (s == failing_step) { |
| 1476 | try printStepFailure(s, stderr, ttyconf, true); | |
| 1473 | try printStepFailure(s, stderr, true); | |
| 1477 | 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 | 1479 | } else { |
| 1483 | 1480 | // Just print the failing step itself. |
| 1484 | try ttyconf.setColor(stderr, .dim); | |
| 1485 | try stderr.writeAll(failing_step.name); | |
| 1486 | try printStepFailure(failing_step, stderr, ttyconf, true); | |
| 1487 | try ttyconf.setColor(stderr, .reset); | |
| 1481 | try stderr.setColor(.dim); | |
| 1482 | try writer.writeAll(failing_step.name); | |
| 1483 | try printStepFailure(failing_step, stderr, true); | |
| 1484 | try stderr.setColor(.reset); | |
| 1488 | 1485 | } |
| 1489 | 1486 | |
| 1490 | 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 | 1489 | if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) { |
| 1493 | try stderr.writeAll("\n"); | |
| 1490 | try writer.writeAll("\n"); | |
| 1494 | 1491 | } |
| 1495 | 1492 | } |
| 1496 | 1493 | |
| 1497 | try failing_step.result_error_bundle.renderToWriter(options, stderr, ttyconf); | |
| 1494 | try failing_step.result_error_bundle.renderToTerminal(options, stderr); | |
| 1498 | 1495 | |
| 1499 | 1496 | for (failing_step.result_error_msgs.items) |msg| { |
| 1500 | try ttyconf.setColor(stderr, .red); | |
| 1501 | try stderr.writeAll("error:"); | |
| 1502 | try ttyconf.setColor(stderr, .reset); | |
| 1497 | try stderr.setColor(.red); | |
| 1498 | try writer.writeAll("error:"); | |
| 1499 | try stderr.setColor(.reset); | |
| 1503 | 1500 | if (std.mem.indexOfScalar(u8, msg, '\n') == null) { |
| 1504 | try stderr.print(" {s}\n", .{msg}); | |
| 1501 | try writer.print(" {s}\n", .{msg}); | |
| 1505 | 1502 | } else switch (multiline_errors) { |
| 1506 | 1503 | .indent => { |
| 1507 | 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 | 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}), | |
| 1514 | .none => try stderr.print(" {s}\n", .{msg}), | |
| 1510 | .newline => try writer.print("\n{s}\n", .{msg}), | |
| 1511 | .none => try writer.print(" {s}\n", .{msg}), | |
| 1515 | 1512 | } |
| 1516 | 1513 | } |
| 1517 | 1514 | |
| 1518 | 1515 | if (error_style.verboseContext()) { |
| 1519 | 1516 | if (failing_step.result_failed_command) |cmd_str| { |
| 1520 | try ttyconf.setColor(stderr, .red); | |
| 1521 | try stderr.writeAll("failed command: "); | |
| 1522 | try ttyconf.setColor(stderr, .reset); | |
| 1523 | try stderr.writeAll(cmd_str); | |
| 1524 | try stderr.writeByte('\n'); | |
| 1517 | try stderr.setColor(.red); | |
| 1518 | try writer.writeAll("failed command: "); | |
| 1519 | try stderr.setColor(.reset); | |
| 1520 | try writer.writeAll(cmd_str); | |
| 1521 | try writer.writeByte('\n'); | |
| 1525 | 1522 | } |
| 1526 | 1523 | } |
| 1527 | 1524 | |
| 1528 | try stderr.writeByte('\n'); | |
| 1525 | try writer.writeByte('\n'); | |
| 1529 | 1526 | } |
| 1530 | 1527 | |
| 1531 | 1528 | fn printSteps(builder: *std.Build, w: *Writer) !void { |
| ... | ... | @@ -1843,9 +1840,9 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void { |
| 1843 | 1840 | } |
| 1844 | 1841 | |
| 1845 | 1842 | var stdio_buffer_allocation: [256]u8 = undefined; |
| 1846 | var stdout_writer_allocation: std.fs.File.Writer = undefined; | |
| 1843 | var stdout_writer_allocation: Io.File.Writer = undefined; | |
| 1847 | 1844 | |
| 1848 | fn initStdoutWriter() *Writer { | |
| 1849 | stdout_writer_allocation = std.fs.File.stdout().writerStreaming(&stdio_buffer_allocation); | |
| 1845 | fn initStdoutWriter(io: Io) *Writer { | |
| 1846 | stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation); | |
| 1850 | 1847 | return &stdout_writer_allocation.interface; |
| 1851 | 1848 | } |
lib/compiler/libc.zig+10-10| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const mem = std.mem; |
| 3 | 4 | const LibCInstallation = std.zig.LibCInstallation; |
| 4 | 5 | |
| ... | ... | @@ -29,7 +30,7 @@ pub fn main() !void { |
| 29 | 30 | const arena = arena_instance.allocator(); |
| 30 | 31 | const gpa = arena; |
| 31 | 32 | |
| 32 | var threaded: std.Io.Threaded = .init(gpa); | |
| 33 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 33 | 34 | defer threaded.deinit(); |
| 34 | 35 | const io = threaded.io(); |
| 35 | 36 | |
| ... | ... | @@ -39,7 +40,7 @@ pub fn main() !void { |
| 39 | 40 | var input_file: ?[]const u8 = null; |
| 40 | 41 | var target_arch_os_abi: []const u8 = "native"; |
| 41 | 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 | 44 | const stdout = &stdout_writer.interface; |
| 44 | 45 | { |
| 45 | 46 | var i: usize = 2; |
| ... | ... | @@ -49,7 +50,7 @@ pub fn main() !void { |
| 49 | 50 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 50 | 51 | try stdout.writeAll(usage_libc); |
| 51 | 52 | try stdout.flush(); |
| 52 | return std.process.cleanExit(); | |
| 53 | return std.process.cleanExit(io); | |
| 53 | 54 | } else if (mem.eql(u8, arg, "-target")) { |
| 54 | 55 | if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); |
| 55 | 56 | i += 1; |
| ... | ... | @@ -77,7 +78,7 @@ pub fn main() !void { |
| 77 | 78 | if (input_file) |libc_file| { |
| 78 | 79 | const libc = try arena.create(LibCInstallation); |
| 79 | 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 | 83 | break :libc libc; |
| 83 | 84 | } else { |
| ... | ... | @@ -96,7 +97,7 @@ pub fn main() !void { |
| 96 | 97 | libc_installation, |
| 97 | 98 | ) catch |err| { |
| 98 | 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 | }; |
| 101 | 102 | |
| 102 | 103 | if (libc_dirs.libc_include_dir_list.len == 0) { |
| ... | ... | @@ -109,24 +110,23 @@ pub fn main() !void { |
| 109 | 110 | try stdout.writeByte('\n'); |
| 110 | 111 | } |
| 111 | 112 | try stdout.flush(); |
| 112 | return std.process.cleanExit(); | |
| 113 | return std.process.cleanExit(io); | |
| 113 | 114 | } |
| 114 | 115 | |
| 115 | 116 | if (input_file) |libc_file| { |
| 116 | 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 | 120 | defer libc.deinit(gpa); |
| 120 | 121 | } else { |
| 121 | 122 | if (!target_query.canDetectLibC()) { |
| 122 | 123 | fatal("unable to detect libc for non-native target", .{}); |
| 123 | 124 | } |
| 124 | var libc = LibCInstallation.findNative(.{ | |
| 125 | .allocator = gpa, | |
| 125 | var libc = LibCInstallation.findNative(gpa, io, .{ | |
| 126 | 126 | .verbose = true, |
| 127 | 127 | .target = &target, |
| 128 | 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 | 131 | defer libc.deinit(gpa); |
| 132 | 132 |
lib/compiler/objcopy.zig+24-22| ... | ... | @@ -1,12 +1,13 @@ |
| 1 | 1 | const builtin = @import("builtin"); |
| 2 | ||
| 2 | 3 | const std = @import("std"); |
| 4 | const Io = std.Io; | |
| 3 | 5 | const mem = std.mem; |
| 4 | 6 | const fs = std.fs; |
| 5 | 7 | const elf = std.elf; |
| 6 | 8 | const Allocator = std.mem.Allocator; |
| 7 | const File = std.fs.File; | |
| 9 | const File = std.Io.File; | |
| 8 | 10 | const assert = std.debug.assert; |
| 9 | ||
| 10 | 11 | const fatal = std.process.fatal; |
| 11 | 12 | const Server = std.zig.Server; |
| 12 | 13 | |
| ... | ... | @@ -24,11 +25,15 @@ pub fn main() !void { |
| 24 | 25 | var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; |
| 25 | 26 | const gpa = general_purpose_allocator.allocator(); |
| 26 | 27 | |
| 28 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 29 | defer threaded.deinit(); | |
| 30 | const io = threaded.io(); | |
| 31 | ||
| 27 | 32 | const args = try std.process.argsAlloc(arena); |
| 28 | return cmdObjCopy(gpa, arena, args[1..]); | |
| 33 | return cmdObjCopy(arena, io, args[1..]); | |
| 29 | 34 | } |
| 30 | 35 | |
| 31 | fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { | |
| 36 | fn cmdObjCopy(arena: Allocator, io: Io, args: []const []const u8) !void { | |
| 32 | 37 | var i: usize = 0; |
| 33 | 38 | var opt_out_fmt: ?std.Target.ObjectFormat = null; |
| 34 | 39 | var opt_input: ?[]const u8 = null; |
| ... | ... | @@ -56,7 +61,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void |
| 56 | 61 | fatal("unexpected positional argument: '{s}'", .{arg}); |
| 57 | 62 | } |
| 58 | 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 | 65 | } else if (mem.eql(u8, arg, "-O") or mem.eql(u8, arg, "--output-target")) { |
| 61 | 66 | i += 1; |
| 62 | 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 | 152 | const input = opt_input orelse fatal("expected input parameter", .{}); |
| 148 | 153 | const output = opt_output orelse fatal("expected output parameter", .{}); |
| 149 | 154 | |
| 150 | var threaded: std.Io.Threaded = .init(gpa); | |
| 151 | defer threaded.deinit(); | |
| 152 | const io = threaded.io(); | |
| 153 | ||
| 154 | const input_file = fs.cwd().openFile(input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err }); | |
| 155 | defer input_file.close(); | |
| 155 | const input_file = Io.Dir.cwd().openFile(io, input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err }); | |
| 156 | defer input_file.close(io); | |
| 156 | 157 | |
| 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 }); | |
| 158 | 159 | |
| 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); | |
| 160 | 161 | |
| 161 | 162 | const elf_hdr = std.elf.Header.read(&in.interface) catch |err| switch (err) { |
| 162 | 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 | 178 | } |
| 178 | 179 | }; |
| 179 | 180 | |
| 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; | |
| 181 | 182 | |
| 182 | var output_file = try fs.cwd().createFile(output, .{ .mode = mode }); | |
| 183 | defer output_file.close(); | |
| 183 | var output_file = try Io.Dir.cwd().createFile(io, output, .{ .permissions = permissions }); | |
| 184 | defer output_file.close(io); | |
| 184 | 185 | |
| 185 | var out = output_file.writer(&output_buffer); | |
| 186 | var out = output_file.writer(io, &output_buffer); | |
| 186 | 187 | |
| 187 | 188 | switch (out_fmt) { |
| 188 | 189 | .hex, .raw => { |
| ... | ... | @@ -221,8 +222,8 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void |
| 221 | 222 | try out.end(); |
| 222 | 223 | |
| 223 | 224 | if (listen) { |
| 224 | var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer); | |
| 225 | var stdout_writer = fs.File.stdout().writer(&stdout_buffer); | |
| 225 | var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer); | |
| 226 | var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); | |
| 226 | 227 | var server = try Server.init(.{ |
| 227 | 228 | .in = &stdin_reader.interface, |
| 228 | 229 | .out = &stdout_writer.interface, |
| ... | ... | @@ -234,7 +235,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void |
| 234 | 235 | const hdr = try server.receiveMessage(); |
| 235 | 236 | switch (hdr.tag) { |
| 236 | 237 | .exit => { |
| 237 | return std.process.cleanExit(); | |
| 238 | return std.process.cleanExit(io); | |
| 238 | 239 | }, |
| 239 | 240 | .update => { |
| 240 | 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 | 250 | } |
| 250 | 251 | } |
| 251 | 252 | } |
| 252 | return std.process.cleanExit(); | |
| 253 | return std.process.cleanExit(io); | |
| 253 | 254 | } |
| 254 | 255 | |
| 255 | 256 | const usage = |
| ... | ... | @@ -675,8 +676,9 @@ fn containsValidAddressRange(segments: []*BinaryElfSegment) bool { |
| 675 | 676 | } |
| 676 | 677 | |
| 677 | 678 | fn padFile(out: *File.Writer, opt_size: ?u64) !void { |
| 679 | const io = out.io; | |
| 678 | 680 | const size = opt_size orelse return; |
| 679 | try out.file.setEndPos(size); | |
| 681 | try out.file.setLength(io, size); | |
| 680 | 682 | } |
| 681 | 683 | |
| 682 | 684 | test "HexWriter.Record.Address has correct payload and checksum" { |
lib/compiler/reduce.zig+24-22| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const mem = std.mem; |
| 3 | 4 | const Allocator = std.mem.Allocator; |
| 4 | 5 | const assert = std.debug.assert; |
| ... | ... | @@ -54,6 +55,10 @@ pub fn main() !void { |
| 54 | 55 | var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; |
| 55 | 56 | const gpa = general_purpose_allocator.allocator(); |
| 56 | 57 | |
| 58 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 59 | defer threaded.deinit(); | |
| 60 | const io = threaded.io(); | |
| 61 | ||
| 57 | 62 | const args = try std.process.argsAlloc(arena); |
| 58 | 63 | |
| 59 | 64 | var opt_checker_path: ?[]const u8 = null; |
| ... | ... | @@ -68,9 +73,9 @@ pub fn main() !void { |
| 68 | 73 | const arg = args[i]; |
| 69 | 74 | if (mem.startsWith(u8, arg, "-")) { |
| 70 | 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 | 77 | try stdout.writeAll(usage); |
| 73 | return std.process.cleanExit(); | |
| 78 | return std.process.cleanExit(io); | |
| 74 | 79 | } else if (mem.eql(u8, arg, "--")) { |
| 75 | 80 | argv = args[i + 1 ..]; |
| 76 | 81 | break; |
| ... | ... | @@ -87,9 +92,7 @@ pub fn main() !void { |
| 87 | 92 | if (i >= args.len) fatal("expected 32-bit integer after {s}", .{arg}); |
| 88 | 93 | const next_arg = args[i]; |
| 89 | 94 | seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| { |
| 90 | fatal("unable to parse seed '{s}' as 32-bit integer: {s}", .{ | |
| 91 | next_arg, @errorName(err), | |
| 92 | }); | |
| 95 | fatal("unable to parse seed '{s}' as 32-bit integer: {t}", .{ next_arg, err }); | |
| 93 | 96 | }; |
| 94 | 97 | } else { |
| 95 | 98 | fatal("unrecognized parameter: '{s}'", .{arg}); |
| ... | ... | @@ -120,7 +123,7 @@ pub fn main() !void { |
| 120 | 123 | var astgen_input: std.Io.Writer.Allocating = .init(gpa); |
| 121 | 124 | defer astgen_input.deinit(); |
| 122 | 125 | |
| 123 | var tree = try parse(gpa, root_source_file_path); | |
| 126 | var tree = try parse(gpa, io, root_source_file_path); | |
| 124 | 127 | defer { |
| 125 | 128 | gpa.free(tree.source); |
| 126 | 129 | tree.deinit(gpa); |
| ... | ... | @@ -185,7 +188,7 @@ pub fn main() !void { |
| 185 | 188 | std.debug.print("{s} ", .{@tagName(t)}); |
| 186 | 189 | } |
| 187 | 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); | |
| 189 | 192 | |
| 190 | 193 | rendered.clearRetainingCapacity(); |
| 191 | 194 | try tree.render(gpa, &rendered.writer, fixups); |
| ... | ... | @@ -232,16 +235,16 @@ pub fn main() !void { |
| 232 | 235 | } |
| 233 | 236 | } |
| 234 | 237 | |
| 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 | 239 | // std.debug.print("trying this code:\n{s}\n", .{rendered.items}); |
| 237 | 240 | |
| 238 | 241 | const interestingness = try runCheck(arena, interestingness_argv.items); |
| 239 | std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{ | |
| 240 | subset_size, @tagName(interestingness), start_index, transformations.items.len, | |
| 242 | std.debug.print("{d} random transformations: {t}. {d}/{d}\n", .{ | |
| 243 | subset_size, interestingness, start_index, transformations.items.len, | |
| 241 | 244 | }); |
| 242 | 245 | switch (interestingness) { |
| 243 | 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 | 248 | gpa.free(tree.source); |
| 246 | 249 | tree.deinit(gpa); |
| 247 | 250 | tree = new_tree; |
| ... | ... | @@ -273,12 +276,12 @@ pub fn main() !void { |
| 273 | 276 | fixups.clearRetainingCapacity(); |
| 274 | 277 | rendered.clearRetainingCapacity(); |
| 275 | 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() }); | |
| 277 | 280 | |
| 278 | return std.process.cleanExit(); | |
| 281 | return std.process.cleanExit(io); | |
| 279 | 282 | } |
| 280 | 283 | std.debug.print("no more transformations found\n", .{}); |
| 281 | return std.process.cleanExit(); | |
| 284 | return std.process.cleanExit(io); | |
| 282 | 285 | } |
| 283 | 286 | |
| 284 | 287 | fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random) void { |
| ... | ... | @@ -302,11 +305,8 @@ fn termToInteresting(term: std.process.Child.Term) Interestingness { |
| 302 | 305 | }; |
| 303 | 306 | } |
| 304 | 307 | |
| 305 | fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness { | |
| 306 | const result = try std.process.Child.run(.{ | |
| 307 | .allocator = arena, | |
| 308 | .argv = argv, | |
| 309 | }); | |
| 308 | fn runCheck(arena: Allocator, io: Io, argv: []const []const u8) !Interestingness { | |
| 309 | const result = try std.process.Child.run(arena, io, .{ .argv = argv }); | |
| 310 | 310 | if (result.stderr.len != 0) |
| 311 | 311 | std.debug.print("{s}", .{result.stderr}); |
| 312 | 312 | return termToInteresting(result.term); |
| ... | ... | @@ -315,6 +315,7 @@ fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness |
| 315 | 315 | fn transformationsToFixups( |
| 316 | 316 | gpa: Allocator, |
| 317 | 317 | arena: Allocator, |
| 318 | io: Io, | |
| 318 | 319 | root_source_file_path: []const u8, |
| 319 | 320 | transforms: []const Walk.Transformation, |
| 320 | 321 | fixups: *Ast.Render.Fixups, |
| ... | ... | @@ -352,7 +353,7 @@ fn transformationsToFixups( |
| 352 | 353 | inline_imported_file.imported_string, |
| 353 | 354 | }); |
| 354 | 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 | 357 | defer { |
| 357 | 358 | gpa.free(other_file_ast.source); |
| 358 | 359 | other_file_ast.deinit(gpa); |
| ... | ... | @@ -396,8 +397,9 @@ fn transformationsToFixups( |
| 396 | 397 | }; |
| 397 | 398 | } |
| 398 | 399 | |
| 399 | fn parse(gpa: Allocator, file_path: []const u8) !Ast { | |
| 400 | const source_code = std.fs.cwd().readFileAllocOptions( | |
| 400 | fn parse(gpa: Allocator, io: Io, file_path: []const u8) !Ast { | |
| 401 | const source_code = Io.Dir.cwd().readFileAllocOptions( | |
| 402 | io, | |
| 401 | 403 | file_path, |
| 402 | 404 | gpa, |
| 403 | 405 | .limited(std.math.maxInt(u32)), |
lib/compiler/resinator/cli.zig+43-39| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const code_pages = @import("code_pages.zig"); |
| 3 | 4 | const SupportedCodePage = code_pages.SupportedCodePage; |
| 4 | 5 | const lang = @import("lang.zig"); |
| ... | ... | @@ -124,15 +125,15 @@ pub const Diagnostics = struct { |
| 124 | 125 | try self.errors.append(self.allocator, error_details); |
| 125 | 126 | } |
| 126 | 127 | |
| 127 | pub fn renderToStdErr(self: *Diagnostics, args: []const []const u8) void { | |
| 128 | const stderr, const ttyconf = std.debug.lockStderrWriter(&.{}); | |
| 129 | defer std.debug.unlockStderrWriter(); | |
| 130 | self.renderToWriter(args, stderr, ttyconf) catch return; | |
| 128 | pub fn renderToStderr(self: *Diagnostics, io: Io, args: []const []const u8) Io.Cancelable!void { | |
| 129 | const stderr = try io.lockStderr(&.{}, null); | |
| 130 | defer io.unlockStderr(); | |
| 131 | self.renderToWriter(args, stderr.terminal()) catch return; | |
| 131 | 132 | } |
| 132 | 133 | |
| 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 | 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 | } |
| 138 | 139 | |
| ... | ... | @@ -169,7 +170,7 @@ pub const Options = struct { |
| 169 | 170 | coff_options: cvtres.CoffOptions = .{}, |
| 170 | 171 | |
| 171 | 172 | pub const IoSource = union(enum) { |
| 172 | stdio: std.fs.File, | |
| 173 | stdio: Io.File, | |
| 173 | 174 | filename: []const u8, |
| 174 | 175 | }; |
| 175 | 176 | pub const AutoIncludes = enum { any, msvc, gnu, none }; |
| ... | ... | @@ -249,13 +250,13 @@ pub const Options = struct { |
| 249 | 250 | /// worlds' situation where we'll be compatible with most use-cases |
| 250 | 251 | /// of the .rc extension being omitted from the CLI args, but still |
| 251 | 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 | 254 | switch (options.input_source) { |
| 254 | 255 | .stdio => return, |
| 255 | 256 | .filename => {}, |
| 256 | 257 | } |
| 257 | 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 | 260 | error.FileNotFound => { |
| 260 | 261 | var filename_bytes = try options.allocator.alloc(u8, options.input_source.filename.len + 3); |
| 261 | 262 | @memcpy(filename_bytes[0..options.input_source.filename.len], options.input_source.filename); |
| ... | ... | @@ -418,7 +419,7 @@ pub const Arg = struct { |
| 418 | 419 | }; |
| 419 | 420 | } |
| 420 | 421 | |
| 421 | pub fn looksLikeFilepath(self: Arg) bool { | |
| 422 | pub fn looksLikeFilepath(self: Arg, io: Io) bool { | |
| 422 | 423 | const meets_min_requirements = self.prefix == .slash and isSupportedInputExtension(std.fs.path.extension(self.full)); |
| 423 | 424 | if (!meets_min_requirements) return false; |
| 424 | 425 | |
| ... | ... | @@ -437,7 +438,7 @@ pub const Arg = struct { |
| 437 | 438 | // It's still possible for a file path to look like a /fo option but not actually |
| 438 | 439 | // be one, e.g. `/foo/bar.rc`. As a last ditch effort to reduce false negatives, |
| 439 | 440 | // 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 | 442 | return true; |
| 442 | 443 | } |
| 443 | 444 | |
| ... | ... | @@ -489,7 +490,7 @@ pub const ParseError = error{ParseError} || Allocator.Error; |
| 489 | 490 | |
| 490 | 491 | /// Note: Does not run `Options.maybeAppendRC` automatically. If that behavior is desired, |
| 491 | 492 | /// it must be called separately. |
| 492 | pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagnostics) ParseError!Options { | |
| 493 | pub fn parse(allocator: Allocator, io: Io, args: []const []const u8, diagnostics: *Diagnostics) ParseError!Options { | |
| 493 | 494 | var options = Options{ .allocator = allocator }; |
| 494 | 495 | errdefer options.deinit(); |
| 495 | 496 | |
| ... | ... | @@ -529,7 +530,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn |
| 529 | 530 | } |
| 530 | 531 | |
| 531 | 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 | 534 | var err_details = Diagnostics.ErrorDetails{ .type = .note, .print_args = true, .arg_index = arg_i }; |
| 534 | 535 | try err_details.msg.appendSlice(allocator, "this argument was inferred to be a filepath, so argument parsing was terminated"); |
| 535 | 536 | try diagnostics.append(err_details); |
| ... | ... | @@ -1343,41 +1344,42 @@ test parsePercent { |
| 1343 | 1344 | try std.testing.expectError(error.InvalidFormat, parsePercent("~1")); |
| 1344 | 1345 | } |
| 1345 | 1346 | |
| 1346 | pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err_details: Diagnostics.ErrorDetails, args: []const []const u8) !void { | |
| 1347 | try config.setColor(writer, .dim); | |
| 1347 | pub fn renderErrorMessage(t: Io.Terminal, err_details: Diagnostics.ErrorDetails, args: []const []const u8) !void { | |
| 1348 | const writer = t.writer; | |
| 1349 | try t.setColor(.dim); | |
| 1348 | 1350 | try writer.writeAll("<cli>"); |
| 1349 | try config.setColor(writer, .reset); | |
| 1350 | try config.setColor(writer, .bold); | |
| 1351 | try t.setColor(.reset); | |
| 1352 | try t.setColor(.bold); | |
| 1351 | 1353 | try writer.writeAll(": "); |
| 1352 | 1354 | switch (err_details.type) { |
| 1353 | 1355 | .err => { |
| 1354 | try config.setColor(writer, .red); | |
| 1356 | try t.setColor(.red); | |
| 1355 | 1357 | try writer.writeAll("error: "); |
| 1356 | 1358 | }, |
| 1357 | 1359 | .warning => { |
| 1358 | try config.setColor(writer, .yellow); | |
| 1360 | try t.setColor(.yellow); | |
| 1359 | 1361 | try writer.writeAll("warning: "); |
| 1360 | 1362 | }, |
| 1361 | 1363 | .note => { |
| 1362 | try config.setColor(writer, .cyan); | |
| 1364 | try t.setColor(.cyan); | |
| 1363 | 1365 | try writer.writeAll("note: "); |
| 1364 | 1366 | }, |
| 1365 | 1367 | } |
| 1366 | try config.setColor(writer, .reset); | |
| 1367 | try config.setColor(writer, .bold); | |
| 1368 | try t.setColor(.reset); | |
| 1369 | try t.setColor(.bold); | |
| 1368 | 1370 | try writer.writeAll(err_details.msg.items); |
| 1369 | 1371 | try writer.writeByte('\n'); |
| 1370 | try config.setColor(writer, .reset); | |
| 1372 | try t.setColor(.reset); | |
| 1371 | 1373 | |
| 1372 | 1374 | if (!err_details.print_args) { |
| 1373 | 1375 | try writer.writeByte('\n'); |
| 1374 | 1376 | return; |
| 1375 | 1377 | } |
| 1376 | 1378 | |
| 1377 | try config.setColor(writer, .dim); | |
| 1379 | try t.setColor(.dim); | |
| 1378 | 1380 | const prefix = " ... "; |
| 1379 | 1381 | try writer.writeAll(prefix); |
| 1380 | try config.setColor(writer, .reset); | |
| 1382 | try t.setColor(.reset); | |
| 1381 | 1383 | |
| 1382 | 1384 | const arg_with_name = args[err_details.arg_index]; |
| 1383 | 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 | 1390 | |
| 1389 | 1391 | try writer.writeAll(prefix_slice); |
| 1390 | 1392 | if (before_name_slice.len > 0) { |
| 1391 | try config.setColor(writer, .dim); | |
| 1393 | try t.setColor(.dim); | |
| 1392 | 1394 | try writer.writeAll(before_name_slice); |
| 1393 | try config.setColor(writer, .reset); | |
| 1395 | try t.setColor(.reset); | |
| 1394 | 1396 | } |
| 1395 | 1397 | try writer.writeAll(name_slice); |
| 1396 | 1398 | if (after_name_slice.len > 0) { |
| 1397 | try config.setColor(writer, .dim); | |
| 1399 | try t.setColor(.dim); | |
| 1398 | 1400 | try writer.writeAll(after_name_slice); |
| 1399 | try config.setColor(writer, .reset); | |
| 1401 | try t.setColor(.reset); | |
| 1400 | 1402 | } |
| 1401 | 1403 | |
| 1402 | 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 | 1416 | if (err_details.arg_span.value_offset >= arg_with_name.len) { |
| 1415 | 1417 | try writer.writeByte(' '); |
| 1416 | 1418 | } |
| 1417 | try config.setColor(writer, .dim); | |
| 1419 | try t.setColor(.dim); | |
| 1418 | 1420 | try writer.writeAll(" ..."); |
| 1419 | try config.setColor(writer, .reset); | |
| 1421 | try t.setColor(.reset); | |
| 1420 | 1422 | } |
| 1421 | 1423 | try writer.writeByte('\n'); |
| 1422 | 1424 | |
| 1423 | try config.setColor(writer, .green); | |
| 1425 | try t.setColor(.green); | |
| 1424 | 1426 | try writer.splatByteAll(' ', prefix.len); |
| 1425 | 1427 | // Special case for when the option is *only* a prefix (e.g. invalid option: -) |
| 1426 | 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 | 1448 | } |
| 1447 | 1449 | } |
| 1448 | 1450 | try writer.writeByte('\n'); |
| 1449 | try config.setColor(writer, .reset); | |
| 1451 | try t.setColor(.reset); | |
| 1450 | 1452 | } |
| 1451 | 1453 | |
| 1452 | 1454 | fn testParse(args: []const []const u8) !Options { |
| ... | ... | @@ -1991,6 +1993,8 @@ test "parse: input and output formats" { |
| 1991 | 1993 | } |
| 1992 | 1994 | |
| 1993 | 1995 | test "maybeAppendRC" { |
| 1996 | const io = std.testing.io; | |
| 1997 | ||
| 1994 | 1998 | var tmp = std.testing.tmpDir(.{}); |
| 1995 | 1999 | defer tmp.cleanup(); |
| 1996 | 2000 | |
| ... | ... | @@ -2000,21 +2004,21 @@ test "maybeAppendRC" { |
| 2000 | 2004 | |
| 2001 | 2005 | // Create the file so that it's found. In this scenario, .rc should not get |
| 2002 | 2006 | // appended. |
| 2003 | var file = try tmp.dir.createFile("foo", .{}); | |
| 2004 | file.close(); | |
| 2005 | try options.maybeAppendRC(tmp.dir); | |
| 2007 | var file = try tmp.dir.createFile(io, "foo", .{}); | |
| 2008 | file.close(io); | |
| 2009 | try options.maybeAppendRC(io, tmp.dir); | |
| 2006 | 2010 | try std.testing.expectEqualStrings("foo", options.input_source.filename); |
| 2007 | 2011 | |
| 2008 | 2012 | // Now delete the file and try again. But this time change the input format |
| 2009 | 2013 | // to non-rc. |
| 2010 | try tmp.dir.deleteFile("foo"); | |
| 2014 | try tmp.dir.deleteFile(io, "foo"); | |
| 2011 | 2015 | options.input_format = .res; |
| 2012 | try options.maybeAppendRC(tmp.dir); | |
| 2016 | try options.maybeAppendRC(io, tmp.dir); | |
| 2013 | 2017 | try std.testing.expectEqualStrings("foo", options.input_source.filename); |
| 2014 | 2018 | |
| 2015 | 2019 | // Finally, reset the input format to rc. Since the verbatim name is no longer found |
| 2016 | 2020 | // and the input filename does not have an extension, .rc should get appended. |
| 2017 | 2021 | options.input_format = .rc; |
| 2018 | try options.maybeAppendRC(tmp.dir); | |
| 2022 | try options.maybeAppendRC(io, tmp.dir); | |
| 2019 | 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 | 34 | const errors = @import("errors.zig"); |
| 35 | 35 | |
| 36 | 36 | pub const CompileOptions = struct { |
| 37 | cwd: std.fs.Dir, | |
| 37 | cwd: std.Io.Dir, | |
| 38 | 38 | diagnostics: *Diagnostics, |
| 39 | 39 | source_mappings: ?*SourceMappings = null, |
| 40 | 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 | 96 | var search_dirs: std.ArrayList(SearchDir) = .empty; |
| 97 | 97 | defer { |
| 98 | 98 | for (search_dirs.items) |*search_dir| { |
| 99 | search_dir.deinit(allocator); | |
| 99 | search_dir.deinit(allocator, io); | |
| 100 | 100 | } |
| 101 | 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 | 106 | // If dirname returns null, then the root path will be the same as |
| 107 | 107 | // the cwd so we don't need to add it as a distinct search path. |
| 108 | 108 | if (std.fs.path.dirname(root_path)) |root_dir_path| { |
| 109 | var root_dir = try options.cwd.openDir(root_dir_path, .{}); | |
| 110 | errdefer root_dir.close(); | |
| 109 | var root_dir = try options.cwd.openDir(io, root_dir_path, .{}); | |
| 110 | errdefer root_dir.close(io); | |
| 111 | 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) | |
| 115 | const cwd_dir = options.cwd.openDir(".", .{}) catch |err| { | |
| 114 | // Re-open the passed in cwd since we want to be able to close it (Io.Dir.cwd() shouldn't be closed) | |
| 115 | const cwd_dir = options.cwd.openDir(io, ".", .{}) catch |err| { | |
| 116 | 116 | try options.diagnostics.append(.{ |
| 117 | 117 | .err = .failed_to_open_cwd, |
| 118 | 118 | .token = .{ |
| ... | ... | @@ -132,19 +132,19 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io |
| 132 | 132 | }; |
| 133 | 133 | try search_dirs.append(allocator, .{ .dir = cwd_dir, .path = null }); |
| 134 | 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 | 136 | // TODO: maybe a warning that the search path is skipped? |
| 137 | 137 | continue; |
| 138 | 138 | }; |
| 139 | errdefer dir.close(); | |
| 139 | errdefer dir.close(io); | |
| 140 | 140 | try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, extra_include_path) }); |
| 141 | 141 | } |
| 142 | 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 | 144 | // TODO: maybe a warning that the search path is skipped? |
| 145 | 145 | continue; |
| 146 | 146 | }; |
| 147 | errdefer dir.close(); | |
| 147 | errdefer dir.close(io); | |
| 148 | 148 | try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, system_include_path) }); |
| 149 | 149 | } |
| 150 | 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 | 159 | }; |
| 160 | 160 | var it = std.mem.tokenizeScalar(u8, INCLUDE, delimiter); |
| 161 | 161 | while (it.next()) |search_path| { |
| 162 | var dir = openSearchPathDir(options.cwd, search_path) catch continue; | |
| 163 | errdefer dir.close(); | |
| 162 | var dir = openSearchPathDir(options.cwd, io, search_path) catch continue; | |
| 163 | errdefer dir.close(io); | |
| 164 | 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 | 196 | arena: Allocator, |
| 197 | 197 | allocator: Allocator, |
| 198 | 198 | io: Io, |
| 199 | cwd: std.fs.Dir, | |
| 199 | cwd: std.Io.Dir, | |
| 200 | 200 | state: State = .{}, |
| 201 | 201 | diagnostics: *Diagnostics, |
| 202 | 202 | dependencies: ?*Dependencies, |
| ... | ... | @@ -388,7 +388,9 @@ pub const Compiler = struct { |
| 388 | 388 | /// matching file is invalid. That is, it does not do the `cmd` PATH searching |
| 389 | 389 | /// thing of continuing to look for matching files until it finds a valid |
| 390 | 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 | 394 | // If the path is absolute, then it is not resolved relative to any search |
| 393 | 395 | // paths, so there's no point in checking them. |
| 394 | 396 | // |
| ... | ... | @@ -404,8 +406,8 @@ pub const Compiler = struct { |
| 404 | 406 | // `/test.bin` relative to include paths and instead only treats it as |
| 405 | 407 | // an absolute path. |
| 406 | 408 | if (std.fs.path.isAbsolute(path)) { |
| 407 | const file = try utils.openFileNotDir(std.fs.cwd(), path, .{}); | |
| 408 | errdefer file.close(); | |
| 409 | const file = try utils.openFileNotDir(Io.Dir.cwd(), io, path, .{}); | |
| 410 | errdefer file.close(io); | |
| 409 | 411 | |
| 410 | 412 | if (self.dependencies) |dependencies| { |
| 411 | 413 | const duped_path = try dependencies.allocator.dupe(u8, path); |
| ... | ... | @@ -414,10 +416,10 @@ pub const Compiler = struct { |
| 414 | 416 | } |
| 415 | 417 | } |
| 416 | 418 | |
| 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 | 420 | for (self.search_dirs) |search_dir| { |
| 419 | if (utils.openFileNotDir(search_dir.dir, path, .{})) |file| { | |
| 420 | errdefer file.close(); | |
| 421 | if (utils.openFileNotDir(search_dir.dir, io, path, .{})) |file| { | |
| 422 | errdefer file.close(io); | |
| 421 | 423 | |
| 422 | 424 | if (self.dependencies) |dependencies| { |
| 423 | 425 | const searched_file_path = try std.fs.path.join(dependencies.allocator, &.{ |
| ... | ... | @@ -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 | 593 | var file_buffer: [2048]u8 = undefined; |
| 592 | 594 | var file_reader = file_handle.reader(io, &file_buffer); |
| 593 | 595 | |
| ... | ... | @@ -2892,13 +2894,13 @@ pub const Compiler = struct { |
| 2892 | 2894 | } |
| 2893 | 2895 | }; |
| 2894 | 2896 | |
| 2895 | pub const OpenSearchPathError = std.fs.Dir.OpenError; | |
| 2897 | pub const OpenSearchPathError = std.Io.Dir.OpenError; | |
| 2896 | 2898 | |
| 2897 | fn openSearchPathDir(dir: std.fs.Dir, path: []const u8) OpenSearchPathError!std.fs.Dir { | |
| 2899 | fn openSearchPathDir(dir: std.Io.Dir, io: Io, path: []const u8) OpenSearchPathError!std.Io.Dir { | |
| 2898 | 2900 | // Validate the search path to avoid possible unreachable on invalid paths, |
| 2899 | 2901 | // see https://github.com/ziglang/zig/issues/15607 for why this is currently necessary. |
| 2900 | 2902 | try validateSearchPath(path); |
| 2901 | return dir.openDir(path, .{}); | |
| 2903 | return dir.openDir(io, path, .{}); | |
| 2902 | 2904 | } |
| 2903 | 2905 | |
| 2904 | 2906 | /// Very crude attempt at validating a path. This is imperfect |
| ... | ... | @@ -2927,11 +2929,11 @@ fn validateSearchPath(path: []const u8) error{BadPathName}!void { |
| 2927 | 2929 | } |
| 2928 | 2930 | |
| 2929 | 2931 | pub const SearchDir = struct { |
| 2930 | dir: std.fs.Dir, | |
| 2932 | dir: std.Io.Dir, | |
| 2931 | 2933 | path: ?[]const u8, |
| 2932 | 2934 | |
| 2933 | pub fn deinit(self: *SearchDir, allocator: Allocator) void { | |
| 2934 | self.dir.close(); | |
| 2935 | pub fn deinit(self: *SearchDir, allocator: Allocator, io: Io) void { | |
| 2936 | self.dir.close(io); | |
| 2935 | 2937 | if (self.path) |path| { |
| 2936 | 2938 | allocator.free(path); |
| 2937 | 2939 | } |
lib/compiler/resinator/errors.zig+45-45| ... | ... | @@ -67,12 +67,12 @@ pub const Diagnostics = struct { |
| 67 | 67 | return @intCast(index); |
| 68 | 68 | } |
| 69 | 69 | |
| 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 | 71 | const io = self.io; |
| 72 | const stderr, const ttyconf = std.debug.lockStderrWriter(&.{}); | |
| 73 | defer std.debug.unlockStderrWriter(); | |
| 72 | const stderr = try io.lockStderr(&.{}, null); | |
| 73 | defer io.unlockStderr(); | |
| 74 | 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 | } |
| 78 | 78 | |
| ... | ... | @@ -169,9 +169,9 @@ pub const ErrorDetails = struct { |
| 169 | 169 | filename_string_index: FilenameStringIndex, |
| 170 | 170 | |
| 171 | 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); | |
| 173 | 173 | |
| 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 | 175 | return switch (err) { |
| 176 | 176 | inline else => |e| @field(ErrorDetails.FileOpenError.FileOpenErrorEnum, @errorName(e)), |
| 177 | 177 | }; |
| ... | ... | @@ -901,9 +901,8 @@ const truncated_str = "<...truncated...>"; |
| 901 | 901 | |
| 902 | 902 | pub fn renderErrorMessage( |
| 903 | 903 | io: Io, |
| 904 | writer: *std.Io.Writer, | |
| 905 | tty_config: std.Io.tty.Config, | |
| 906 | cwd: std.fs.Dir, | |
| 904 | t: Io.Terminal, | |
| 905 | cwd: Io.Dir, | |
| 907 | 906 | err_details: ErrorDetails, |
| 908 | 907 | source: []const u8, |
| 909 | 908 | strings: []const []const u8, |
| ... | ... | @@ -927,36 +926,37 @@ pub fn renderErrorMessage( |
| 927 | 926 | |
| 928 | 927 | const err_line = if (corresponding_span) |span| span.start_line else err_details.token.line_number; |
| 929 | 928 | |
| 930 | try tty_config.setColor(writer, .bold); | |
| 929 | const writer = t.writer; | |
| 930 | try t.setColor(.bold); | |
| 931 | 931 | if (corresponding_file) |file| { |
| 932 | 932 | try writer.writeAll(file); |
| 933 | 933 | } else { |
| 934 | try tty_config.setColor(writer, .dim); | |
| 934 | try t.setColor(.dim); | |
| 935 | 935 | try writer.writeAll("<after preprocessor>"); |
| 936 | try tty_config.setColor(writer, .reset); | |
| 937 | try tty_config.setColor(writer, .bold); | |
| 936 | try t.setColor(.reset); | |
| 937 | try t.setColor(.bold); | |
| 938 | 938 | } |
| 939 | 939 | try writer.print(":{d}:{d}: ", .{ err_line, column }); |
| 940 | 940 | switch (err_details.type) { |
| 941 | 941 | .err => { |
| 942 | try tty_config.setColor(writer, .red); | |
| 942 | try t.setColor(.red); | |
| 943 | 943 | try writer.writeAll("error: "); |
| 944 | 944 | }, |
| 945 | 945 | .warning => { |
| 946 | try tty_config.setColor(writer, .yellow); | |
| 946 | try t.setColor(.yellow); | |
| 947 | 947 | try writer.writeAll("warning: "); |
| 948 | 948 | }, |
| 949 | 949 | .note => { |
| 950 | try tty_config.setColor(writer, .cyan); | |
| 950 | try t.setColor(.cyan); | |
| 951 | 951 | try writer.writeAll("note: "); |
| 952 | 952 | }, |
| 953 | 953 | .hint => unreachable, |
| 954 | 954 | } |
| 955 | try tty_config.setColor(writer, .reset); | |
| 956 | try tty_config.setColor(writer, .bold); | |
| 955 | try t.setColor(.reset); | |
| 956 | try t.setColor(.bold); | |
| 957 | 957 | try err_details.render(writer, source, strings); |
| 958 | 958 | try writer.writeByte('\n'); |
| 959 | try tty_config.setColor(writer, .reset); | |
| 959 | try t.setColor(.reset); | |
| 960 | 960 | |
| 961 | 961 | if (!err_details.print_source_line) { |
| 962 | 962 | try writer.writeByte('\n'); |
| ... | ... | @@ -983,20 +983,20 @@ pub fn renderErrorMessage( |
| 983 | 983 | |
| 984 | 984 | try writer.writeAll(source_line_for_display.line); |
| 985 | 985 | if (source_line_for_display.truncated) { |
| 986 | try tty_config.setColor(writer, .dim); | |
| 986 | try t.setColor(.dim); | |
| 987 | 987 | try writer.writeAll(truncated_str); |
| 988 | try tty_config.setColor(writer, .reset); | |
| 988 | try t.setColor(.reset); | |
| 989 | 989 | } |
| 990 | 990 | try writer.writeByte('\n'); |
| 991 | 991 | |
| 992 | try tty_config.setColor(writer, .green); | |
| 992 | try t.setColor(.green); | |
| 993 | 993 | const num_spaces = truncated_visual_info.point_offset - truncated_visual_info.before_len; |
| 994 | 994 | try writer.splatByteAll(' ', num_spaces); |
| 995 | 995 | try writer.splatByteAll('~', truncated_visual_info.before_len); |
| 996 | 996 | try writer.writeByte('^'); |
| 997 | 997 | try writer.splatByteAll('~', truncated_visual_info.after_len); |
| 998 | 998 | try writer.writeByte('\n'); |
| 999 | try tty_config.setColor(writer, .reset); | |
| 999 | try t.setColor(.reset); | |
| 1000 | 1000 | |
| 1001 | 1001 | if (corresponding_span != null and corresponding_file != null) { |
| 1002 | 1002 | var worth_printing_lines: bool = true; |
| ... | ... | @@ -1021,22 +1021,22 @@ pub fn renderErrorMessage( |
| 1021 | 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); | |
| 1025 | 1025 | |
| 1026 | try tty_config.setColor(writer, .bold); | |
| 1026 | try t.setColor(.bold); | |
| 1027 | 1027 | if (corresponding_file) |file| { |
| 1028 | 1028 | try writer.writeAll(file); |
| 1029 | 1029 | } else { |
| 1030 | try tty_config.setColor(writer, .dim); | |
| 1030 | try t.setColor(.dim); | |
| 1031 | 1031 | try writer.writeAll("<after preprocessor>"); |
| 1032 | try tty_config.setColor(writer, .reset); | |
| 1033 | try tty_config.setColor(writer, .bold); | |
| 1032 | try t.setColor(.reset); | |
| 1033 | try t.setColor(.bold); | |
| 1034 | 1034 | } |
| 1035 | 1035 | try writer.print(":{d}:{d}: ", .{ err_line, column }); |
| 1036 | try tty_config.setColor(writer, .cyan); | |
| 1036 | try t.setColor(.cyan); | |
| 1037 | 1037 | try writer.writeAll("note: "); |
| 1038 | try tty_config.setColor(writer, .reset); | |
| 1039 | try tty_config.setColor(writer, .bold); | |
| 1038 | try t.setColor(.reset); | |
| 1039 | try t.setColor(.bold); | |
| 1040 | 1040 | try writer.writeAll("this line originated from line"); |
| 1041 | 1041 | if (corresponding_span.?.start_line != corresponding_span.?.end_line) { |
| 1042 | 1042 | try writer.print("s {}-{}", .{ corresponding_span.?.start_line, corresponding_span.?.end_line }); |
| ... | ... | @@ -1044,7 +1044,7 @@ pub fn renderErrorMessage( |
| 1044 | 1044 | try writer.print(" {}", .{corresponding_span.?.start_line}); |
| 1045 | 1045 | } |
| 1046 | 1046 | try writer.print(" of file '{s}'\n", .{corresponding_file.?}); |
| 1047 | try tty_config.setColor(writer, .reset); | |
| 1047 | try t.setColor(.reset); | |
| 1048 | 1048 | |
| 1049 | 1049 | if (!worth_printing_lines) return; |
| 1050 | 1050 | |
| ... | ... | @@ -1055,21 +1055,21 @@ pub fn renderErrorMessage( |
| 1055 | 1055 | }) |display_line| { |
| 1056 | 1056 | try writer.writeAll(display_line.line); |
| 1057 | 1057 | if (display_line.truncated) { |
| 1058 | try tty_config.setColor(writer, .dim); | |
| 1058 | try t.setColor(.dim); | |
| 1059 | 1059 | try writer.writeAll(truncated_str); |
| 1060 | try tty_config.setColor(writer, .reset); | |
| 1060 | try t.setColor(.reset); | |
| 1061 | 1061 | } |
| 1062 | 1062 | try writer.writeByte('\n'); |
| 1063 | 1063 | } |
| 1064 | 1064 | break :write_lines null; |
| 1065 | 1065 | }; |
| 1066 | 1066 | if (write_lines_err) |err| { |
| 1067 | try tty_config.setColor(writer, .red); | |
| 1067 | try t.setColor(.red); | |
| 1068 | 1068 | try writer.writeAll(" | "); |
| 1069 | try tty_config.setColor(writer, .reset); | |
| 1070 | try tty_config.setColor(writer, .dim); | |
| 1069 | try t.setColor(.reset); | |
| 1070 | try t.setColor(.dim); | |
| 1071 | 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 | 1074 | try writer.writeByte('\n'); |
| 1075 | 1075 | } |
| ... | ... | @@ -1094,13 +1094,13 @@ const CorrespondingLines = struct { |
| 1094 | 1094 | last_byte: u8 = 0, |
| 1095 | 1095 | at_eof: bool = false, |
| 1096 | 1096 | span: SourceMappings.CorrespondingSpan, |
| 1097 | file: std.fs.File, | |
| 1098 | file_reader: std.fs.File.Reader, | |
| 1097 | file: Io.File, | |
| 1098 | file_reader: Io.File.Reader, | |
| 1099 | 1099 | code_page: SupportedCodePage, |
| 1100 | 1100 | |
| 1101 | 1101 | pub fn init( |
| 1102 | 1102 | io: Io, |
| 1103 | cwd: std.fs.Dir, | |
| 1103 | cwd: Io.Dir, | |
| 1104 | 1104 | err_details: ErrorDetails, |
| 1105 | 1105 | line_for_comparison: []const u8, |
| 1106 | 1106 | corresponding_span: SourceMappings.CorrespondingSpan, |
| ... | ... | @@ -1120,12 +1120,12 @@ const CorrespondingLines = struct { |
| 1120 | 1120 | |
| 1121 | 1121 | var corresponding_lines = CorrespondingLines{ |
| 1122 | 1122 | .span = corresponding_span, |
| 1123 | .file = try utils.openFileNotDir(cwd, corresponding_file, .{}), | |
| 1123 | .file = try utils.openFileNotDir(cwd, io, corresponding_file, .{}), | |
| 1124 | 1124 | .code_page = err_details.code_page, |
| 1125 | 1125 | .file_reader = undefined, |
| 1126 | 1126 | }; |
| 1127 | 1127 | corresponding_lines.file_reader = corresponding_lines.file.reader(io, file_reader_buf); |
| 1128 | errdefer corresponding_lines.deinit(); | |
| 1128 | errdefer corresponding_lines.deinit(io); | |
| 1129 | 1129 | |
| 1130 | 1130 | try corresponding_lines.writeLineFromStreamVerbatim( |
| 1131 | 1131 | &corresponding_lines.file_reader.interface, |
| ... | ... | @@ -1221,8 +1221,8 @@ const CorrespondingLines = struct { |
| 1221 | 1221 | }; |
| 1222 | 1222 | } |
| 1223 | 1223 | |
| 1224 | pub fn deinit(self: *CorrespondingLines) void { | |
| 1225 | self.file.close(); | |
| 1224 | pub fn deinit(self: *CorrespondingLines, io: Io) void { | |
| 1225 | self.file.close(io); | |
| 1226 | 1226 | } |
| 1227 | 1227 | }; |
| 1228 | 1228 |
lib/compiler/resinator/main.zig+94-94| ... | ... | @@ -24,6 +24,10 @@ pub fn main() !void { |
| 24 | 24 | defer std.debug.assert(debug_allocator.deinit() == .ok); |
| 25 | 25 | const gpa = debug_allocator.allocator(); |
| 26 | 26 | |
| 27 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 28 | defer threaded.deinit(); | |
| 29 | const io = threaded.io(); | |
| 30 | ||
| 27 | 31 | var arena_state = std.heap.ArenaAllocator.init(gpa); |
| 28 | 32 | defer arena_state.deinit(); |
| 29 | 33 | const arena = arena_state.allocator(); |
| ... | ... | @@ -31,8 +35,8 @@ pub fn main() !void { |
| 31 | 35 | const args = try std.process.argsAlloc(arena); |
| 32 | 36 | |
| 33 | 37 | if (args.len < 2) { |
| 34 | const w, const ttyconf = std.debug.lockStderrWriter(&.{}); | |
| 35 | try renderErrorMessage(w, ttyconf, .err, "expected zig lib dir as first argument", .{}); | |
| 38 | const stderr = try io.lockStderr(&.{}, null); | |
| 39 | try renderErrorMessage(stderr.terminal(), .err, "expected zig lib dir as first argument", .{}); | |
| 36 | 40 | std.process.exit(1); |
| 37 | 41 | } |
| 38 | 42 | const zig_lib_dir = args[1]; |
| ... | ... | @@ -45,7 +49,7 @@ pub fn main() !void { |
| 45 | 49 | } |
| 46 | 50 | |
| 47 | 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 | 53 | const stdout = &stdout_writer.interface; |
| 50 | 54 | var error_handler: ErrorHandler = switch (zig_integration) { |
| 51 | 55 | true => .{ |
| ... | ... | @@ -60,35 +64,31 @@ pub fn main() !void { |
| 60 | 64 | var options = options: { |
| 61 | 65 | var cli_diagnostics = cli.Diagnostics.init(gpa); |
| 62 | 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 | 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 | 70 | std.process.exit(1); |
| 67 | 71 | }, |
| 68 | 72 | else => |e| return e, |
| 69 | 73 | }; |
| 70 | try options.maybeAppendRC(std.fs.cwd()); | |
| 74 | try options.maybeAppendRC(io, Io.Dir.cwd()); | |
| 71 | 75 | |
| 72 | 76 | if (!zig_integration) { |
| 73 | 77 | // print any warnings/notes |
| 74 | cli_diagnostics.renderToStdErr(cli_args); | |
| 78 | try cli_diagnostics.renderToStderr(io, cli_args); | |
| 75 | 79 | // If there was something printed, then add an extra newline separator |
| 76 | 80 | // so that there is a clear separation between the cli diagnostics and whatever |
| 77 | 81 | // gets printed after |
| 78 | 82 | if (cli_diagnostics.errors.items.len > 0) { |
| 79 | const stderr, _ = std.debug.lockStderrWriter(&.{}); | |
| 80 | defer std.debug.unlockStderrWriter(); | |
| 81 | try stderr.writeByte('\n'); | |
| 83 | const stderr = try io.lockStderr(&.{}, null); | |
| 84 | defer io.unlockStderr(); | |
| 85 | try stderr.file_writer.interface.writeByte('\n'); | |
| 82 | 86 | } |
| 83 | 87 | } |
| 84 | 88 | break :options options; |
| 85 | 89 | }; |
| 86 | 90 | defer options.deinit(); |
| 87 | 91 | |
| 88 | var threaded: std.Io.Threaded = .init(gpa); | |
| 89 | defer threaded.deinit(); | |
| 90 | const io = threaded.io(); | |
| 91 | ||
| 92 | 92 | if (options.print_help_and_exit) { |
| 93 | 93 | try cli.writeUsage(stdout, "zig rc"); |
| 94 | 94 | try stdout.flush(); |
| ... | ... | @@ -130,18 +130,15 @@ pub fn main() !void { |
| 130 | 130 | var stderr_buf: [512]u8 = undefined; |
| 131 | 131 | var diagnostics: aro.Diagnostics = .{ .output = output: { |
| 132 | 132 | if (zig_integration) break :output .{ .to_list = .{ .arena = .init(gpa) } }; |
| 133 | const w, const ttyconf = std.debug.lockStderrWriter(&stderr_buf); | |
| 134 | break :output .{ .to_writer = .{ | |
| 135 | .writer = w, | |
| 136 | .color = ttyconf, | |
| 137 | } }; | |
| 133 | const stderr = try io.lockStderr(&stderr_buf, null); | |
| 134 | break :output .{ .to_writer = stderr.terminal() }; | |
| 138 | 135 | } }; |
| 139 | 136 | defer { |
| 140 | 137 | diagnostics.deinit(); |
| 141 | if (!zig_integration) std.debug.unlockStderrWriter(); | |
| 138 | if (!zig_integration) std.debug.unlockStderr(); | |
| 142 | 139 | } |
| 143 | 140 | |
| 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 | 142 | defer comp.deinit(); |
| 146 | 143 | |
| 147 | 144 | var argv: std.ArrayList([]const u8) = .empty; |
| ... | ... | @@ -175,11 +172,11 @@ pub fn main() !void { |
| 175 | 172 | std.process.exit(1); |
| 176 | 173 | }, |
| 177 | 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 | 176 | std.process.exit(1); |
| 180 | 177 | }, |
| 181 | 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 | 180 | std.process.exit(1); |
| 184 | 181 | }, |
| 185 | 182 | error.OutOfMemory => |e| return e, |
| ... | ... | @@ -191,13 +188,13 @@ pub fn main() !void { |
| 191 | 188 | .stdio => |file| { |
| 192 | 189 | var file_reader = file.reader(io, &.{}); |
| 193 | 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 | 192 | std.process.exit(1); |
| 196 | 193 | }; |
| 197 | 194 | }, |
| 198 | 195 | .filename => |input_filename| { |
| 199 | break :full_input std.fs.cwd().readFileAlloc(input_filename, gpa, .unlimited) catch |err| { | |
| 200 | try error_handler.emitMessage(gpa, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) }); | |
| 196 | break :full_input Io.Dir.cwd().readFileAlloc(io, input_filename, gpa, .unlimited) catch |err| { | |
| 197 | try error_handler.emitMessage(gpa, io, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) }); | |
| 201 | 198 | std.process.exit(1); |
| 202 | 199 | }; |
| 203 | 200 | }, |
| ... | ... | @@ -209,10 +206,10 @@ pub fn main() !void { |
| 209 | 206 | if (options.preprocess == .only) { |
| 210 | 207 | switch (options.output_source) { |
| 211 | 208 | .stdio => |output_file| { |
| 212 | try output_file.writeAll(full_input); | |
| 209 | try output_file.writeStreamingAll(io, full_input); | |
| 213 | 210 | }, |
| 214 | 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 | 215 | return; |
| ... | ... | @@ -227,16 +224,16 @@ pub fn main() !void { |
| 227 | 224 | .source = .{ .memory = .empty }, |
| 228 | 225 | } |
| 229 | 226 | else if (options.input_format == .res) |
| 230 | IoStream.fromIoSource(options.input_source, .input) catch |err| { | |
| 231 | try error_handler.emitMessage(gpa, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) }); | |
| 227 | IoStream.fromIoSource(io, options.input_source, .input) catch |err| { | |
| 228 | try error_handler.emitMessage(gpa, io, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) }); | |
| 232 | 229 | std.process.exit(1); |
| 233 | 230 | } |
| 234 | 231 | else |
| 235 | IoStream.fromIoSource(options.output_source, .output) catch |err| { | |
| 236 | try error_handler.emitMessage(gpa, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); | |
| 232 | IoStream.fromIoSource(io, options.output_source, .output) catch |err| { | |
| 233 | try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); | |
| 237 | 234 | std.process.exit(1); |
| 238 | 235 | }; |
| 239 | defer res_stream.deinit(gpa); | |
| 236 | defer res_stream.deinit(gpa, io); | |
| 240 | 237 | |
| 241 | 238 | const res_data = res_data: { |
| 242 | 239 | if (options.input_format != .res) { |
| ... | ... | @@ -246,17 +243,17 @@ pub fn main() !void { |
| 246 | 243 | var mapping_results = parseAndRemoveLineCommands(gpa, full_input, full_input, .{ .initial_filename = options.input_source.filename }) catch |err| switch (err) { |
| 247 | 244 | error.InvalidLineCommand => { |
| 248 | 245 | // 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 | 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 | 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 | 252 | std.process.exit(1); |
| 256 | 253 | }, |
| 257 | 254 | error.LineNumberOverflow => { |
| 258 | 255 | // 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 | 257 | std.process.exit(1); |
| 261 | 258 | }, |
| 262 | 259 | error.OutOfMemory => |e| return e, |
| ... | ... | @@ -272,12 +269,12 @@ pub fn main() !void { |
| 272 | 269 | defer diagnostics.deinit(); |
| 273 | 270 | |
| 274 | 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 | 273 | defer res_stream_writer.deinit(&res_stream.source); |
| 277 | 274 | const output_buffered_stream = res_stream_writer.interface(); |
| 278 | 275 | |
| 279 | 276 | compile(gpa, io, final_input, output_buffered_stream, .{ |
| 280 | .cwd = std.fs.cwd(), | |
| 277 | .cwd = Io.Dir.cwd(), | |
| 281 | 278 | .diagnostics = &diagnostics, |
| 282 | 279 | .source_mappings = &mapping_results.mappings, |
| 283 | 280 | .dependencies = maybe_dependencies, |
| ... | ... | @@ -294,9 +291,9 @@ pub fn main() !void { |
| 294 | 291 | .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page, |
| 295 | 292 | }) catch |err| switch (err) { |
| 296 | 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 | 295 | // Delete the output file on error |
| 299 | res_stream.cleanupAfterError(); | |
| 296 | res_stream.cleanupAfterError(io); | |
| 300 | 297 | std.process.exit(1); |
| 301 | 298 | }, |
| 302 | 299 | else => |e| return e, |
| ... | ... | @@ -306,19 +303,19 @@ pub fn main() !void { |
| 306 | 303 | |
| 307 | 304 | // print any warnings/notes |
| 308 | 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 | } |
| 311 | 308 | |
| 312 | 309 | // write the depfile |
| 313 | 310 | if (options.depfile_path) |depfile_path| { |
| 314 | var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| { | |
| 315 | try error_handler.emitMessage(gpa, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) }); | |
| 311 | var depfile = Io.Dir.cwd().createFile(io, depfile_path, .{}) catch |err| { | |
| 312 | try error_handler.emitMessage(gpa, io, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) }); | |
| 316 | 313 | std.process.exit(1); |
| 317 | 314 | }; |
| 318 | defer depfile.close(); | |
| 315 | defer depfile.close(io); | |
| 319 | 316 | |
| 320 | 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 | 319 | switch (options.depfile_fmt) { |
| 323 | 320 | .json => { |
| 324 | 321 | var write_stream: std.json.Stringify = .{ |
| ... | ... | @@ -340,7 +337,7 @@ pub fn main() !void { |
| 340 | 337 | if (options.output_format != .coff) return; |
| 341 | 338 | |
| 342 | 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 | 341 | std.process.exit(1); |
| 345 | 342 | }; |
| 346 | 343 | }; |
| ... | ... | @@ -353,27 +350,27 @@ pub fn main() !void { |
| 353 | 350 | var res_reader: std.Io.Reader = .fixed(res_data.bytes); |
| 354 | 351 | break :resources cvtres.parseRes(gpa, &res_reader, .{ .max_size = res_data.bytes.len }) catch |err| { |
| 355 | 352 | // 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 | 354 | std.process.exit(1); |
| 358 | 355 | }; |
| 359 | 356 | }; |
| 360 | 357 | defer resources.deinit(); |
| 361 | 358 | |
| 362 | var coff_stream = IoStream.fromIoSource(options.output_source, .output) catch |err| { | |
| 363 | try error_handler.emitMessage(gpa, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); | |
| 359 | var coff_stream = IoStream.fromIoSource(io, options.output_source, .output) catch |err| { | |
| 360 | try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); | |
| 364 | 361 | std.process.exit(1); |
| 365 | 362 | }; |
| 366 | defer coff_stream.deinit(gpa); | |
| 363 | defer coff_stream.deinit(gpa, io); | |
| 367 | 364 | |
| 368 | 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); | |
| 370 | 367 | |
| 371 | 368 | var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} }; |
| 372 | 369 | cvtres.writeCoff(gpa, coff_output_buffered_stream.interface(), resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| { |
| 373 | 370 | switch (err) { |
| 374 | 371 | error.DuplicateResource => { |
| 375 | 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 | 374 | duplicate_resource.name_value, |
| 378 | 375 | fmtResourceType(duplicate_resource.type_value), |
| 379 | 376 | duplicate_resource.language, |
| ... | ... | @@ -381,8 +378,8 @@ pub fn main() !void { |
| 381 | 378 | }, |
| 382 | 379 | error.ResourceDataTooLong => { |
| 383 | 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", .{}); | |
| 385 | try error_handler.emitMessage(gpa, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{ | |
| 381 | try error_handler.emitMessage(gpa, io, .err, "resource has a data length that is too large to be written into a coff section", .{}); | |
| 382 | try error_handler.emitMessage(gpa, io, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{ | |
| 386 | 383 | overflow_resource.name_value, |
| 387 | 384 | fmtResourceType(overflow_resource.type_value), |
| 388 | 385 | overflow_resource.language, |
| ... | ... | @@ -390,19 +387,19 @@ pub fn main() !void { |
| 390 | 387 | }, |
| 391 | 388 | error.TotalResourceDataTooLong => { |
| 392 | 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", .{}); | |
| 394 | try error_handler.emitMessage(gpa, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{ | |
| 390 | try error_handler.emitMessage(gpa, io, .err, "total resource data exceeds the maximum of the coff 'size of raw data' field", .{}); | |
| 391 | try error_handler.emitMessage(gpa, io, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{ | |
| 395 | 392 | overflow_resource.name_value, |
| 396 | 393 | fmtResourceType(overflow_resource.type_value), |
| 397 | 394 | overflow_resource.language, |
| 398 | 395 | }); |
| 399 | 396 | }, |
| 400 | 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 | 401 | // Delete the output file on error |
| 405 | coff_stream.cleanupAfterError(); | |
| 402 | coff_stream.cleanupAfterError(io); | |
| 406 | 403 | std.process.exit(1); |
| 407 | 404 | }; |
| 408 | 405 | |
| ... | ... | @@ -416,58 +413,58 @@ const IoStream = struct { |
| 416 | 413 | |
| 417 | 414 | pub const IoDirection = enum { input, output }; |
| 418 | 415 | |
| 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 | 417 | return .{ |
| 421 | 418 | .name = switch (source) { |
| 422 | 419 | .filename => |filename| filename, |
| 423 | .stdio => switch (io) { | |
| 420 | .stdio => switch (io_direction) { | |
| 424 | 421 | .input => "<stdin>", |
| 425 | 422 | .output => "<stdout>", |
| 426 | 423 | }, |
| 427 | 424 | }, |
| 428 | 425 | .intermediate = false, |
| 429 | .source = try Source.fromIoSource(source, io), | |
| 426 | .source = try Source.fromIoSource(io, source, io_direction), | |
| 430 | 427 | }; |
| 431 | 428 | } |
| 432 | 429 | |
| 433 | pub fn deinit(self: *IoStream, allocator: Allocator) void { | |
| 434 | self.source.deinit(allocator); | |
| 430 | pub fn deinit(self: *IoStream, allocator: Allocator, io: Io) void { | |
| 431 | self.source.deinit(allocator, io); | |
| 435 | 432 | } |
| 436 | 433 | |
| 437 | pub fn cleanupAfterError(self: *IoStream) void { | |
| 434 | pub fn cleanupAfterError(self: *IoStream, io: Io) void { | |
| 438 | 435 | switch (self.source) { |
| 439 | 436 | .file => |file| { |
| 440 | 437 | // Delete the output file on error |
| 441 | file.close(); | |
| 438 | file.close(io); | |
| 442 | 439 | // 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 | 442 | .stdio, .memory, .closed => return, |
| 446 | 443 | } |
| 447 | 444 | } |
| 448 | 445 | |
| 449 | 446 | pub const Source = union(enum) { |
| 450 | file: std.fs.File, | |
| 451 | stdio: std.fs.File, | |
| 447 | file: Io.File, | |
| 448 | stdio: Io.File, | |
| 452 | 449 | memory: std.ArrayList(u8), |
| 453 | 450 | /// The source has been closed and any usage of the Source in this state is illegal (except deinit). |
| 454 | 451 | closed: void, |
| 455 | 452 | |
| 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 | 454 | switch (source) { |
| 458 | 455 | .filename => |filename| return .{ |
| 459 | .file = switch (io) { | |
| 460 | .input => try openFileNotDir(std.fs.cwd(), filename, .{}), | |
| 461 | .output => try std.fs.cwd().createFile(filename, .{}), | |
| 456 | .file = switch (io_direction) { | |
| 457 | .input => try openFileNotDir(Io.Dir.cwd(), io, filename, .{}), | |
| 458 | .output => try Io.Dir.cwd().createFile(io, filename, .{}), | |
| 462 | 459 | }, |
| 463 | 460 | }, |
| 464 | 461 | .stdio => |file| return .{ .stdio = file }, |
| 465 | 462 | } |
| 466 | 463 | } |
| 467 | 464 | |
| 468 | pub fn deinit(self: *Source, allocator: Allocator) void { | |
| 465 | pub fn deinit(self: *Source, allocator: Allocator, io: Io) void { | |
| 469 | 466 | switch (self.*) { |
| 470 | .file => |file| file.close(), | |
| 467 | .file => |file| file.close(io), | |
| 471 | 468 | .stdio => {}, |
| 472 | 469 | .memory => |*list| list.deinit(allocator), |
| 473 | 470 | .closed => {}, |
| ... | ... | @@ -500,10 +497,10 @@ const IoStream = struct { |
| 500 | 497 | } |
| 501 | 498 | |
| 502 | 499 | pub const Writer = union(enum) { |
| 503 | file: std.fs.File.Writer, | |
| 500 | file: Io.File.Writer, | |
| 504 | 501 | allocating: std.Io.Writer.Allocating, |
| 505 | 502 | |
| 506 | pub const Error = Allocator.Error || std.fs.File.WriteError; | |
| 503 | pub const Error = Allocator.Error || Io.File.WriteError; | |
| 507 | 504 | |
| 508 | 505 | pub fn interface(this: *@This()) *std.Io.Writer { |
| 509 | 506 | return switch (this.*) { |
| ... | ... | @@ -521,9 +518,9 @@ const IoStream = struct { |
| 521 | 518 | } |
| 522 | 519 | }; |
| 523 | 520 | |
| 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 | 522 | return switch (source.*) { |
| 526 | .file, .stdio => |file| .{ .file = file.writer(buffer) }, | |
| 523 | .file, .stdio => |file| .{ .file = file.writer(io, buffer) }, | |
| 527 | 524 | .memory => |*list| .{ .allocating = .fromArrayList(allocator, list) }, |
| 528 | 525 | .closed => unreachable, |
| 529 | 526 | }; |
| ... | ... | @@ -550,16 +547,16 @@ const LazyIncludePaths = struct { |
| 550 | 547 | else => |e| { |
| 551 | 548 | switch (e) { |
| 552 | 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 | 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 | 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 | 560 | std.process.exit(1); |
| 564 | 561 | }, |
| 565 | 562 | }; |
| ... | ... | @@ -618,7 +615,7 @@ fn getIncludePaths( |
| 618 | 615 | }; |
| 619 | 616 | const target = std.zig.resolveTargetQueryOrFatal(io, target_query); |
| 620 | 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 | 619 | if (includes == .any) { |
| 623 | 620 | // fall back to mingw |
| 624 | 621 | includes = .gnu; |
| ... | ... | @@ -644,7 +641,7 @@ fn getIncludePaths( |
| 644 | 641 | }; |
| 645 | 642 | const target = std.zig.resolveTargetQueryOrFatal(io, target_query); |
| 646 | 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 | 645 | error.OutOfMemory => |e| return e, |
| 649 | 646 | else => return error.MingwIncludesNotFound, |
| 650 | 647 | }; |
| ... | ... | @@ -664,6 +661,7 @@ const ErrorHandler = union(enum) { |
| 664 | 661 | pub fn emitCliDiagnostics( |
| 665 | 662 | self: *ErrorHandler, |
| 666 | 663 | allocator: Allocator, |
| 664 | io: Io, | |
| 667 | 665 | args: []const []const u8, |
| 668 | 666 | diagnostics: *cli.Diagnostics, |
| 669 | 667 | ) !void { |
| ... | ... | @@ -674,7 +672,7 @@ const ErrorHandler = union(enum) { |
| 674 | 672 | |
| 675 | 673 | try server.serveErrorBundle(error_bundle); |
| 676 | 674 | }, |
| 677 | .stderr => diagnostics.renderToStdErr(args), | |
| 675 | .stderr => return diagnostics.renderToStderr(io, args), | |
| 678 | 676 | } |
| 679 | 677 | } |
| 680 | 678 | |
| ... | ... | @@ -684,6 +682,7 @@ const ErrorHandler = union(enum) { |
| 684 | 682 | fail_msg: []const u8, |
| 685 | 683 | comp: *aro.Compilation, |
| 686 | 684 | ) !void { |
| 685 | const io = comp.io; | |
| 687 | 686 | switch (self.*) { |
| 688 | 687 | .server => |*server| { |
| 689 | 688 | var error_bundle = try compiler_util.aroDiagnosticsToErrorBundle( |
| ... | ... | @@ -697,9 +696,9 @@ const ErrorHandler = union(enum) { |
| 697 | 696 | }, |
| 698 | 697 | .stderr => { |
| 699 | 698 | // aro errors have already been emitted |
| 700 | const stderr, const ttyconf = std.debug.lockStderrWriter(&.{}); | |
| 701 | defer std.debug.unlockStderrWriter(); | |
| 702 | try renderErrorMessage(stderr, ttyconf, .err, "{s}", .{fail_msg}); | |
| 699 | const stderr = try io.lockStderr(&.{}, null); | |
| 700 | defer io.unlockStderr(); | |
| 701 | try renderErrorMessage(stderr.terminal(), .err, "{s}", .{fail_msg}); | |
| 703 | 702 | }, |
| 704 | 703 | } |
| 705 | 704 | } |
| ... | ... | @@ -707,7 +706,7 @@ const ErrorHandler = union(enum) { |
| 707 | 706 | pub fn emitDiagnostics( |
| 708 | 707 | self: *ErrorHandler, |
| 709 | 708 | allocator: Allocator, |
| 710 | cwd: std.fs.Dir, | |
| 709 | cwd: Io.Dir, | |
| 711 | 710 | source: []const u8, |
| 712 | 711 | diagnostics: *Diagnostics, |
| 713 | 712 | mappings: SourceMappings, |
| ... | ... | @@ -719,13 +718,14 @@ const ErrorHandler = union(enum) { |
| 719 | 718 | |
| 720 | 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 | } |
| 725 | 724 | |
| 726 | 725 | pub fn emitMessage( |
| 727 | 726 | self: *ErrorHandler, |
| 728 | 727 | allocator: Allocator, |
| 728 | io: Io, | |
| 729 | 729 | msg_type: @import("utils.zig").ErrorMessageType, |
| 730 | 730 | comptime format: []const u8, |
| 731 | 731 | args: anytype, |
| ... | ... | @@ -741,9 +741,9 @@ const ErrorHandler = union(enum) { |
| 741 | 741 | try server.serveErrorBundle(error_bundle); |
| 742 | 742 | }, |
| 743 | 743 | .stderr => { |
| 744 | const stderr, const ttyconf = std.debug.lockStderrWriter(&.{}); | |
| 745 | defer std.debug.unlockStderrWriter(); | |
| 746 | try renderErrorMessage(stderr, ttyconf, msg_type, format, args); | |
| 744 | const stderr = try io.lockStderr(&.{}, null); | |
| 745 | defer io.unlockStderr(); | |
| 746 | try renderErrorMessage(stderr.terminal(), msg_type, format, args); | |
| 747 | 747 | }, |
| 748 | 748 | } |
| 749 | 749 | } |
lib/compiler/resinator/utils.zig+22-18| ... | ... | @@ -1,6 +1,8 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 3 | 2 | |
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | ||
| 4 | 6 | pub const UncheckedSliceWriter = struct { |
| 5 | 7 | const Self = @This(); |
| 6 | 8 | |
| ... | ... | @@ -23,19 +25,20 @@ pub const UncheckedSliceWriter = struct { |
| 23 | 25 | } |
| 24 | 26 | }; |
| 25 | 27 | |
| 26 | /// Cross-platform 'std.fs.Dir.openFile' wrapper that will always return IsDir if | |
| 28 | /// Cross-platform 'Io.Dir.openFile' wrapper that will always return IsDir if | |
| 27 | 29 | /// a directory is attempted to be opened. |
| 28 | 30 | /// TODO: Remove once https://github.com/ziglang/zig/issues/5732 is addressed. |
| 29 | 31 | pub fn openFileNotDir( |
| 30 | cwd: std.fs.Dir, | |
| 32 | cwd: Io.Dir, | |
| 33 | io: Io, | |
| 31 | 34 | path: []const u8, |
| 32 | flags: std.fs.File.OpenFlags, | |
| 33 | ) (std.fs.File.OpenError || std.fs.File.StatError)!std.fs.File { | |
| 34 | const file = try cwd.openFile(path, flags); | |
| 35 | errdefer file.close(); | |
| 35 | flags: Io.File.OpenFlags, | |
| 36 | ) (Io.File.OpenError || Io.File.StatError)!Io.File { | |
| 37 | const file = try cwd.openFile(io, path, flags); | |
| 38 | errdefer file.close(io); | |
| 36 | 39 | // https://github.com/ziglang/zig/issues/5732 |
| 37 | 40 | if (builtin.os.tag != .windows) { |
| 38 | const stat = try file.stat(); | |
| 41 | const stat = try file.stat(io); | |
| 39 | 42 | |
| 40 | 43 | if (stat.kind == .directory) |
| 41 | 44 | return error.IsDir; |
| ... | ... | @@ -89,31 +92,32 @@ pub const ErrorMessageType = enum { err, warning, note }; |
| 89 | 92 | |
| 90 | 93 | /// Used for generic colored errors/warnings/notes, more context-specific error messages |
| 91 | 94 | /// are handled elsewhere. |
| 92 | pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, msg_type: ErrorMessageType, comptime format: []const u8, args: anytype) !void { | |
| 95 | pub fn renderErrorMessage(t: Io.Terminal, msg_type: ErrorMessageType, comptime format: []const u8, args: anytype) !void { | |
| 96 | const writer = t.writer; | |
| 93 | 97 | switch (msg_type) { |
| 94 | 98 | .err => { |
| 95 | try config.setColor(writer, .bold); | |
| 96 | try config.setColor(writer, .red); | |
| 99 | try t.setColor(.bold); | |
| 100 | try t.setColor(.red); | |
| 97 | 101 | try writer.writeAll("error: "); |
| 98 | 102 | }, |
| 99 | 103 | .warning => { |
| 100 | try config.setColor(writer, .bold); | |
| 101 | try config.setColor(writer, .yellow); | |
| 104 | try t.setColor(.bold); | |
| 105 | try t.setColor(.yellow); | |
| 102 | 106 | try writer.writeAll("warning: "); |
| 103 | 107 | }, |
| 104 | 108 | .note => { |
| 105 | try config.setColor(writer, .reset); | |
| 106 | try config.setColor(writer, .cyan); | |
| 109 | try t.setColor(.reset); | |
| 110 | try t.setColor(.cyan); | |
| 107 | 111 | try writer.writeAll("note: "); |
| 108 | 112 | }, |
| 109 | 113 | } |
| 110 | try config.setColor(writer, .reset); | |
| 114 | try t.setColor(.reset); | |
| 111 | 115 | if (msg_type == .err) { |
| 112 | try config.setColor(writer, .bold); | |
| 116 | try t.setColor(.bold); | |
| 113 | 117 | } |
| 114 | 118 | try writer.print(format, args); |
| 115 | 119 | try writer.writeByte('\n'); |
| 116 | try config.setColor(writer, .reset); | |
| 120 | try t.setColor(.reset); | |
| 117 | 121 | } |
| 118 | 122 | |
| 119 | 123 | pub fn isLineEndingPair(first: u8, second: u8) bool { |
lib/compiler/std-docs.zig+70-57| ... | ... | @@ -1,12 +1,14 @@ |
| 1 | 1 | const builtin = @import("builtin"); |
| 2 | ||
| 2 | 3 | const std = @import("std"); |
| 4 | const Io = std.Io; | |
| 3 | 5 | const mem = std.mem; |
| 4 | 6 | const Allocator = std.mem.Allocator; |
| 5 | 7 | const assert = std.debug.assert; |
| 6 | 8 | const Cache = std.Build.Cache; |
| 7 | 9 | |
| 8 | fn usage() noreturn { | |
| 9 | std.fs.File.stdout().writeAll( | |
| 10 | fn usage(io: Io) noreturn { | |
| 11 | Io.File.stdout().writeStreamingAll(io, | |
| 10 | 12 | \\Usage: zig std [options] |
| 11 | 13 | \\ |
| 12 | 14 | \\Options: |
| ... | ... | @@ -27,6 +29,10 @@ pub fn main() !void { |
| 27 | 29 | var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; |
| 28 | 30 | const gpa = general_purpose_allocator.allocator(); |
| 29 | 31 | |
| 32 | var threaded: Io.Threaded = .init(gpa, .{}); | |
| 33 | defer threaded.deinit(); | |
| 34 | const io = threaded.io(); | |
| 35 | ||
| 30 | 36 | var argv = try std.process.argsWithAllocator(arena); |
| 31 | 37 | defer argv.deinit(); |
| 32 | 38 | assert(argv.skip()); |
| ... | ... | @@ -34,18 +40,18 @@ pub fn main() !void { |
| 34 | 40 | const zig_exe_path = argv.next().?; |
| 35 | 41 | const global_cache_path = argv.next().?; |
| 36 | 42 | |
| 37 | var lib_dir = try std.fs.cwd().openDir(zig_lib_directory, .{}); | |
| 38 | defer lib_dir.close(); | |
| 43 | var lib_dir = try Io.Dir.cwd().openDir(io, zig_lib_directory, .{}); | |
| 44 | defer lib_dir.close(io); | |
| 39 | 45 | |
| 40 | 46 | var listen_port: u16 = 0; |
| 41 | 47 | var force_open_browser: ?bool = null; |
| 42 | 48 | while (argv.next()) |arg| { |
| 43 | 49 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 44 | usage(); | |
| 50 | usage(io); | |
| 45 | 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 | 53 | std.log.err("expected port number: {}", .{err}); |
| 48 | usage(); | |
| 54 | usage(io); | |
| 49 | 55 | }; |
| 50 | 56 | } else if (mem.eql(u8, arg, "--open-browser")) { |
| 51 | 57 | force_open_browser = true; |
| ... | ... | @@ -53,69 +59,70 @@ pub fn main() !void { |
| 53 | 59 | force_open_browser = false; |
| 54 | 60 | } else { |
| 55 | 61 | std.log.err("unrecognized argument: {s}", .{arg}); |
| 56 | usage(); | |
| 62 | usage(io); | |
| 57 | 63 | } |
| 58 | 64 | } |
| 59 | 65 | const should_open_browser = force_open_browser orelse (listen_port == 0); |
| 60 | 66 | |
| 61 | const address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable; | |
| 62 | var http_server = try address.listen(.{ | |
| 67 | const address = Io.net.IpAddress.parse("127.0.0.1", listen_port) catch unreachable; | |
| 68 | var http_server = try address.listen(io, .{ | |
| 63 | 69 | .reuse_address = true, |
| 64 | 70 | }); |
| 65 | const port = http_server.listen_address.in.getPort(); | |
| 71 | const port = http_server.socket.address.getPort(); | |
| 66 | 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 | 74 | if (should_open_browser) { |
| 69 | openBrowserTab(gpa, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| { | |
| 70 | std.log.err("unable to open browser: {s}", .{@errorName(err)}); | |
| 75 | openBrowserTab(gpa, io, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| { | |
| 76 | std.log.err("unable to open browser: {t}", .{err}); | |
| 71 | 77 | }; |
| 72 | 78 | } |
| 73 | 79 | |
| 74 | 80 | var context: Context = .{ |
| 75 | 81 | .gpa = gpa, |
| 82 | .io = io, | |
| 76 | 83 | .zig_exe_path = zig_exe_path, |
| 77 | 84 | .global_cache_path = global_cache_path, |
| 78 | 85 | .lib_dir = lib_dir, |
| 79 | 86 | .zig_lib_directory = zig_lib_directory, |
| 80 | 87 | }; |
| 81 | 88 | |
| 89 | var group: Io.Group = .init; | |
| 90 | defer group.cancel(io); | |
| 91 | ||
| 82 | 92 | while (true) { |
| 83 | const connection = try http_server.accept(); | |
| 84 | _ = std.Thread.spawn(.{}, accept, .{ &context, connection }) catch |err| { | |
| 85 | std.log.err("unable to accept connection: {s}", .{@errorName(err)}); | |
| 86 | connection.stream.close(); | |
| 87 | continue; | |
| 88 | }; | |
| 93 | const stream = try http_server.accept(io); | |
| 94 | group.async(io, accept, .{ &context, stream }); | |
| 89 | 95 | } |
| 90 | 96 | } |
| 91 | 97 | |
| 92 | fn accept(context: *Context, connection: std.net.Server.Connection) void { | |
| 93 | defer connection.stream.close(); | |
| 98 | fn accept(context: *Context, stream: Io.net.Stream) void { | |
| 99 | const io = context.io; | |
| 100 | defer stream.close(io); | |
| 94 | 101 | |
| 95 | 102 | var recv_buffer: [4000]u8 = undefined; |
| 96 | 103 | var send_buffer: [4000]u8 = undefined; |
| 97 | var conn_reader = connection.stream.reader(&recv_buffer); | |
| 98 | var conn_writer = connection.stream.writer(&send_buffer); | |
| 99 | var server = std.http.Server.init(conn_reader.interface(), &conn_writer.interface); | |
| 104 | var conn_reader = stream.reader(io, &recv_buffer); | |
| 105 | var conn_writer = stream.writer(io, &send_buffer); | |
| 106 | var server = std.http.Server.init(&conn_reader.interface, &conn_writer.interface); | |
| 100 | 107 | while (server.reader.state == .ready) { |
| 101 | 108 | var request = server.receiveHead() catch |err| switch (err) { |
| 102 | 109 | error.HttpConnectionClosing => return, |
| 103 | 110 | else => { |
| 104 | std.log.err("closing http connection: {s}", .{@errorName(err)}); | |
| 111 | std.log.err("closing http connection: {t}", .{err}); | |
| 105 | 112 | return; |
| 106 | 113 | }, |
| 107 | 114 | }; |
| 108 | 115 | serveRequest(&request, context) catch |err| switch (err) { |
| 109 | 116 | error.WriteFailed => { |
| 110 | 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 | 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 | 122 | return; |
| 116 | 123 | }, |
| 117 | 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 | 126 | return; |
| 120 | 127 | }, |
| 121 | 128 | }; |
| ... | ... | @@ -124,7 +131,8 @@ fn accept(context: *Context, connection: std.net.Server.Connection) void { |
| 124 | 131 | |
| 125 | 132 | const Context = struct { |
| 126 | 133 | gpa: Allocator, |
| 127 | lib_dir: std.fs.Dir, | |
| 134 | io: Io, | |
| 135 | lib_dir: Io.Dir, | |
| 128 | 136 | zig_lib_directory: []const u8, |
| 129 | 137 | zig_exe_path: []const u8, |
| 130 | 138 | global_cache_path: []const u8, |
| ... | ... | @@ -170,10 +178,11 @@ fn serveDocsFile( |
| 170 | 178 | content_type: []const u8, |
| 171 | 179 | ) !void { |
| 172 | 180 | const gpa = context.gpa; |
| 181 | const io = context.io; | |
| 173 | 182 | // The desired API is actually sendfile, which will require enhancing std.http.Server. |
| 174 | 183 | // We load the file with every request so that the user can make changes to the file |
| 175 | 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 | 186 | defer gpa.free(file_contents); |
| 178 | 187 | try request.respond(file_contents, .{ |
| 179 | 188 | .extra_headers = &.{ |
| ... | ... | @@ -185,6 +194,7 @@ fn serveDocsFile( |
| 185 | 194 | |
| 186 | 195 | fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { |
| 187 | 196 | const gpa = context.gpa; |
| 197 | const io = context.io; | |
| 188 | 198 | |
| 189 | 199 | var send_buffer: [0x4000]u8 = undefined; |
| 190 | 200 | var response = try request.respondStreaming(&send_buffer, .{ |
| ... | ... | @@ -196,8 +206,8 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { |
| 196 | 206 | }, |
| 197 | 207 | }); |
| 198 | 208 | |
| 199 | var std_dir = try context.lib_dir.openDir("std", .{ .iterate = true }); | |
| 200 | defer std_dir.close(); | |
| 209 | var std_dir = try context.lib_dir.openDir(io, "std", .{ .iterate = true }); | |
| 210 | defer std_dir.close(io); | |
| 201 | 211 | |
| 202 | 212 | var walker = try std_dir.walk(gpa); |
| 203 | 213 | defer walker.deinit(); |
| ... | ... | @@ -205,7 +215,7 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { |
| 205 | 215 | var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer }; |
| 206 | 216 | archiver.prefix = "std"; |
| 207 | 217 | |
| 208 | while (try walker.next()) |entry| { | |
| 218 | while (try walker.next(io)) |entry| { | |
| 209 | 219 | switch (entry.kind) { |
| 210 | 220 | .file => { |
| 211 | 221 | if (!std.mem.endsWith(u8, entry.basename, ".zig")) |
| ... | ... | @@ -215,15 +225,16 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { |
| 215 | 225 | }, |
| 216 | 226 | else => continue, |
| 217 | 227 | } |
| 218 | var file = try entry.dir.openFile(entry.basename, .{}); | |
| 219 | defer file.close(); | |
| 220 | const stat = try file.stat(); | |
| 221 | var file_reader: std.fs.File.Reader = .{ | |
| 228 | var file = try entry.dir.openFile(io, entry.basename, .{}); | |
| 229 | defer file.close(io); | |
| 230 | const stat = try file.stat(io); | |
| 231 | var file_reader: Io.File.Reader = .{ | |
| 232 | .io = io, | |
| 222 | 233 | .file = file, |
| 223 | .interface = std.fs.File.Reader.initInterface(&.{}), | |
| 234 | .interface = Io.File.Reader.initInterface(&.{}), | |
| 224 | 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 | } |
| 228 | 239 | |
| 229 | 240 | { |
| ... | ... | @@ -245,6 +256,7 @@ fn serveWasm( |
| 245 | 256 | optimize_mode: std.builtin.OptimizeMode, |
| 246 | 257 | ) !void { |
| 247 | 258 | const gpa = context.gpa; |
| 259 | const io = context.io; | |
| 248 | 260 | |
| 249 | 261 | var arena_instance = std.heap.ArenaAllocator.init(gpa); |
| 250 | 262 | defer arena_instance.deinit(); |
| ... | ... | @@ -255,7 +267,7 @@ fn serveWasm( |
| 255 | 267 | const wasm_base_path = try buildWasmBinary(arena, context, optimize_mode); |
| 256 | 268 | const bin_name = try std.zig.binNameAlloc(arena, .{ |
| 257 | 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 | 271 | .arch_os_abi = autodoc_arch_os_abi, |
| 260 | 272 | .cpu_features = autodoc_cpu_features, |
| 261 | 273 | }) catch unreachable) catch unreachable), |
| ... | ... | @@ -263,7 +275,7 @@ fn serveWasm( |
| 263 | 275 | }); |
| 264 | 276 | // std.http.Server does not have a sendfile API yet. |
| 265 | 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 | 279 | defer gpa.free(file_contents); |
| 268 | 280 | try request.respond(file_contents, .{ |
| 269 | 281 | .extra_headers = &.{ |
| ... | ... | @@ -283,6 +295,7 @@ fn buildWasmBinary( |
| 283 | 295 | optimize_mode: std.builtin.OptimizeMode, |
| 284 | 296 | ) !Cache.Path { |
| 285 | 297 | const gpa = context.gpa; |
| 298 | const io = context.io; | |
| 286 | 299 | |
| 287 | 300 | var argv: std.ArrayList([]const u8) = .empty; |
| 288 | 301 | |
| ... | ... | @@ -315,16 +328,16 @@ fn buildWasmBinary( |
| 315 | 328 | child.stdin_behavior = .Pipe; |
| 316 | 329 | child.stdout_behavior = .Pipe; |
| 317 | 330 | child.stderr_behavior = .Pipe; |
| 318 | try child.spawn(); | |
| 331 | try child.spawn(io); | |
| 319 | 332 | |
| 320 | var poller = std.Io.poll(gpa, enum { stdout, stderr }, .{ | |
| 333 | var poller = Io.poll(gpa, enum { stdout, stderr }, .{ | |
| 321 | 334 | .stdout = child.stdout.?, |
| 322 | 335 | .stderr = child.stderr.?, |
| 323 | 336 | }); |
| 324 | 337 | defer poller.deinit(); |
| 325 | 338 | |
| 326 | try sendMessage(child.stdin.?, .update); | |
| 327 | try sendMessage(child.stdin.?, .exit); | |
| 339 | try sendMessage(io, child.stdin.?, .update); | |
| 340 | try sendMessage(io, child.stdin.?, .exit); | |
| 328 | 341 | |
| 329 | 342 | var result: ?Cache.Path = null; |
| 330 | 343 | var result_error_bundle = std.zig.ErrorBundle.empty; |
| ... | ... | @@ -348,7 +361,7 @@ fn buildWasmBinary( |
| 348 | 361 | result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body); |
| 349 | 362 | }, |
| 350 | 363 | .emit_digest => { |
| 351 | var r: std.Io.Reader = .fixed(body); | |
| 364 | var r: Io.Reader = .fixed(body); | |
| 352 | 365 | const emit_digest = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable; |
| 353 | 366 | if (!emit_digest.flags.cache_hit) { |
| 354 | 367 | std.log.info("source changes detected; rebuilt wasm component", .{}); |
| ... | ... | @@ -371,10 +384,10 @@ fn buildWasmBinary( |
| 371 | 384 | } |
| 372 | 385 | |
| 373 | 386 | // Send EOF to stdin. |
| 374 | child.stdin.?.close(); | |
| 387 | child.stdin.?.close(io); | |
| 375 | 388 | child.stdin = null; |
| 376 | 389 | |
| 377 | switch (try child.wait()) { | |
| 390 | switch (try child.wait(io)) { | |
| 378 | 391 | .Exited => |code| { |
| 379 | 392 | if (code != 0) { |
| 380 | 393 | std.log.err( |
| ... | ... | @@ -394,7 +407,7 @@ fn buildWasmBinary( |
| 394 | 407 | } |
| 395 | 408 | |
| 396 | 409 | if (result_error_bundle.errorMessageCount() > 0) { |
| 397 | result_error_bundle.renderToStdErr(.{}, true); | |
| 410 | try result_error_bundle.renderToStderr(io, .{}, .auto); | |
| 398 | 411 | std.log.err("the following command failed with {d} compilation errors:\n{s}", .{ |
| 399 | 412 | result_error_bundle.errorMessageCount(), |
| 400 | 413 | try std.Build.Step.allocPrintCmd(arena, null, argv.items), |
| ... | ... | @@ -410,24 +423,24 @@ fn buildWasmBinary( |
| 410 | 423 | }; |
| 411 | 424 | } |
| 412 | 425 | |
| 413 | fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { | |
| 426 | fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { | |
| 414 | 427 | const header: std.zig.Client.Message.Header = .{ |
| 415 | 428 | .tag = tag, |
| 416 | 429 | .bytes_len = 0, |
| 417 | 430 | }; |
| 418 | var w = file.writer(&.{}); | |
| 431 | var w = file.writer(io, &.{}); | |
| 419 | 432 | w.interface.writeStruct(header, .little) catch |err| switch (err) { |
| 420 | 433 | error.WriteFailed => return w.err.?, |
| 421 | 434 | }; |
| 422 | 435 | } |
| 423 | 436 | |
| 424 | fn openBrowserTab(gpa: Allocator, url: []const u8) !void { | |
| 437 | fn openBrowserTab(gpa: Allocator, io: Io, url: []const u8) !void { | |
| 425 | 438 | // Until https://github.com/ziglang/zig/issues/19205 is implemented, we |
| 426 | 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 | } |
| 429 | 442 | |
| 430 | fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void { | |
| 443 | fn openBrowserTabThread(gpa: Allocator, io: Io, url: []const u8) !void { | |
| 431 | 444 | const main_exe = switch (builtin.os.tag) { |
| 432 | 445 | .windows => "explorer", |
| 433 | 446 | .macos => "open", |
| ... | ... | @@ -437,6 +450,6 @@ fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void { |
| 437 | 450 | child.stdin_behavior = .Ignore; |
| 438 | 451 | child.stdout_behavior = .Ignore; |
| 439 | 452 | child.stderr_behavior = .Ignore; |
| 440 | try child.spawn(); | |
| 441 | _ = try child.wait(); | |
| 453 | try child.spawn(io); | |
| 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 | 17 | var fba_buffer: [8192]u8 = undefined; |
| 18 | 18 | var stdin_buffer: [4096]u8 = undefined; |
| 19 | 19 | var stdout_buffer: [4096]u8 = undefined; |
| 20 | var runner_threaded_io: Io.Threaded = .init_single_threaded; | |
| 20 | const runner_threaded_io: Io = Io.Threaded.global_single_threaded.ioBasic(); | |
| 21 | 21 | |
| 22 | 22 | /// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether |
| 23 | 23 | /// the test runner will communicate with the build runner via `std.zig.Server`. |
| ... | ... | @@ -74,8 +74,8 @@ pub fn main() void { |
| 74 | 74 | |
| 75 | 75 | fn mainServer() !void { |
| 76 | 76 | @disableInstrumentation(); |
| 77 | var stdin_reader = std.fs.File.stdin().readerStreaming(runner_threaded_io.io(), &stdin_buffer); | |
| 78 | var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer); | |
| 77 | var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer); | |
| 78 | var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer); | |
| 79 | 79 | var server = try std.zig.Server.init(.{ |
| 80 | 80 | .in = &stdin_reader.interface, |
| 81 | 81 | .out = &stdout_writer.interface, |
| ... | ... | @@ -131,7 +131,7 @@ fn mainServer() !void { |
| 131 | 131 | |
| 132 | 132 | .run_test => { |
| 133 | 133 | testing.allocator_instance = .{}; |
| 134 | testing.io_instance = .init(testing.allocator); | |
| 134 | testing.io_instance = .init(testing.allocator, .{}); | |
| 135 | 135 | log_err_count = 0; |
| 136 | 136 | const index = try server.receiveBody_u32(); |
| 137 | 137 | const test_fn = builtin.test_functions[index]; |
| ... | ... | @@ -224,16 +224,16 @@ fn mainTerminal() void { |
| 224 | 224 | var skip_count: usize = 0; |
| 225 | 225 | var fail_count: usize = 0; |
| 226 | 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 | 228 | .root_name = "Test", |
| 229 | 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; | |
| 232 | 232 | |
| 233 | 233 | var leaks: usize = 0; |
| 234 | 234 | for (test_fn_list, 0..) |test_fn, i| { |
| 235 | 235 | testing.allocator_instance = .{}; |
| 236 | testing.io_instance = .init(testing.allocator); | |
| 236 | testing.io_instance = .init(testing.allocator, .{}); | |
| 237 | 237 | defer { |
| 238 | 238 | testing.io_instance.deinit(); |
| 239 | 239 | if (testing.allocator_instance.deinit() == .leak) leaks += 1; |
| ... | ... | @@ -318,7 +318,7 @@ pub fn log( |
| 318 | 318 | /// work-in-progress backends can handle it. |
| 319 | 319 | pub fn mainSimple() anyerror!void { |
| 320 | 320 | @disableInstrumentation(); |
| 321 | // is the backend capable of calling `std.fs.File.writeAll`? | |
| 321 | // is the backend capable of calling `Io.File.writeAll`? | |
| 322 | 322 | const enable_write = switch (builtin.zig_backend) { |
| 323 | 323 | .stage2_aarch64, .stage2_riscv64 => true, |
| 324 | 324 | else => false, |
| ... | ... | @@ -329,35 +329,37 @@ pub fn mainSimple() anyerror!void { |
| 329 | 329 | else => false, |
| 330 | 330 | }; |
| 331 | 331 | |
| 332 | testing.io_instance = .init(testing.allocator, .{}); | |
| 333 | ||
| 332 | 334 | var passed: u64 = 0; |
| 333 | 335 | var skipped: u64 = 0; |
| 334 | 336 | var failed: u64 = 0; |
| 335 | 337 | |
| 336 | 338 | // 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 {}; | |
| 338 | 340 | |
| 339 | 341 | for (builtin.test_functions) |test_fn| { |
| 340 | 342 | if (enable_write) { |
| 341 | stdout.writeAll(test_fn.name) catch {}; | |
| 342 | stdout.writeAll("... ") catch {}; | |
| 343 | stdout.writeStreamingAll(runner_threaded_io, test_fn.name) catch {}; | |
| 344 | stdout.writeStreamingAll(runner_threaded_io, "... ") catch {}; | |
| 343 | 345 | } |
| 344 | 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 | 348 | } else |err| { |
| 347 | 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 | 351 | failed += 1; |
| 350 | 352 | if (!enable_write) return err; |
| 351 | 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 | 356 | skipped += 1; |
| 355 | 357 | continue; |
| 356 | 358 | } |
| 357 | 359 | passed += 1; |
| 358 | 360 | } |
| 359 | 361 | if (enable_print) { |
| 360 | var stdout_writer = stdout.writer(&.{}); | |
| 362 | var stdout_writer = stdout.writer(runner_threaded_io, &.{}); | |
| 361 | 363 | stdout_writer.interface.print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {}; |
| 362 | 364 | } |
| 363 | 365 | if (failed != 0) std.process.exit(1); |
| ... | ... | @@ -405,15 +407,19 @@ pub fn fuzz( |
| 405 | 407 | testOne(ctx, input.toSlice()) catch |err| switch (err) { |
| 406 | 408 | error.SkipZigTest => return, |
| 407 | 409 | else => { |
| 408 | std.debug.lockStdErr(); | |
| 409 | if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace); | |
| 410 | std.debug.print("failed with error.{t}\n", .{err}); | |
| 410 | const stderr = std.debug.lockStderr(&.{}, null).terminal(); | |
| 411 | p: { | |
| 412 | if (@errorReturnTrace()) |trace| { | |
| 413 | std.debug.writeStackTrace(trace, stderr) catch break :p; | |
| 414 | } | |
| 415 | stderr.writer.print("failed with error.{t}\n", .{err}) catch break :p; | |
| 416 | } | |
| 411 | 417 | std.process.exit(1); |
| 412 | 418 | }, |
| 413 | 419 | }; |
| 414 | 420 | if (log_err_count != 0) { |
| 415 | std.debug.lockStdErr(); | |
| 416 | std.debug.print("error logs detected\n", .{}); | |
| 421 | const stderr = std.debug.lockStderr(&.{}, .no_color); | |
| 422 | stderr.interface.print("error logs detected\n", .{}) catch {}; | |
| 417 | 423 | std.process.exit(1); |
| 418 | 424 | } |
| 419 | 425 | } |
lib/compiler/translate-c/main.zig+23-18| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const assert = std.debug.assert; |
| 3 | 4 | const mem = std.mem; |
| 4 | 5 | const process = std.process; |
| ... | ... | @@ -18,7 +19,7 @@ pub fn main() u8 { |
| 18 | 19 | defer arena_instance.deinit(); |
| 19 | 20 | const arena = arena_instance.allocator(); |
| 20 | 21 | |
| 21 | var threaded: std.Io.Threaded = .init(gpa); | |
| 22 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 22 | 23 | defer threaded.deinit(); |
| 23 | 24 | const io = threaded.io(); |
| 24 | 25 | |
| ... | ... | @@ -33,11 +34,14 @@ pub fn main() u8 { |
| 33 | 34 | zig_integration = true; |
| 34 | 35 | } |
| 35 | 36 | |
| 37 | const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(); | |
| 38 | const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(); | |
| 39 | ||
| 36 | 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 | 42 | var diagnostics: aro.Diagnostics = switch (zig_integration) { |
| 39 | 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 | 45 | .writer = &stderr.interface, |
| 42 | 46 | } } }, |
| 43 | 47 | true => .{ .output = .{ .to_list = .{ |
| ... | ... | @@ -46,7 +50,7 @@ pub fn main() u8 { |
| 46 | 50 | }; |
| 47 | 51 | defer diagnostics.deinit(); |
| 48 | 52 | |
| 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 | 54 | error.OutOfMemory => { |
| 51 | 55 | std.debug.print("ran out of memory initializing C compilation\n", .{}); |
| 52 | 56 | if (fast_exit) process.exit(1); |
| ... | ... | @@ -68,7 +72,7 @@ pub fn main() u8 { |
| 68 | 72 | return 1; |
| 69 | 73 | }, |
| 70 | 74 | error.FatalError => if (zig_integration) { |
| 71 | serveErrorBundle(arena, &diagnostics) catch |bundle_err| { | |
| 75 | serveErrorBundle(arena, io, &diagnostics) catch |bundle_err| { | |
| 72 | 76 | std.debug.print("unable to serve error bundle: {}\n", .{bundle_err}); |
| 73 | 77 | if (fast_exit) process.exit(1); |
| 74 | 78 | return 1; |
| ... | ... | @@ -92,14 +96,14 @@ pub fn main() u8 { |
| 92 | 96 | return @intFromBool(comp.diagnostics.errors != 0); |
| 93 | 97 | } |
| 94 | 98 | |
| 95 | fn serveErrorBundle(arena: std.mem.Allocator, diagnostics: *const aro.Diagnostics) !void { | |
| 99 | fn serveErrorBundle(arena: std.mem.Allocator, io: Io, diagnostics: *const aro.Diagnostics) !void { | |
| 96 | 100 | const error_bundle = try compiler_util.aroDiagnosticsToErrorBundle( |
| 97 | 101 | diagnostics, |
| 98 | 102 | arena, |
| 99 | 103 | "translation failure", |
| 100 | 104 | ); |
| 101 | 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 | 107 | var server: std.zig.Server = .{ |
| 104 | 108 | .out = &stdout_writer.interface, |
| 105 | 109 | .in = undefined, |
| ... | ... | @@ -121,6 +125,7 @@ pub const usage = |
| 121 | 125 | |
| 122 | 126 | fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration: bool) !void { |
| 123 | 127 | const gpa = d.comp.gpa; |
| 128 | const io = d.comp.io; | |
| 124 | 129 | |
| 125 | 130 | const aro_args = args: { |
| 126 | 131 | var i: usize = 0; |
| ... | ... | @@ -128,13 +133,13 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration |
| 128 | 133 | args[i] = arg; |
| 129 | 134 | if (mem.eql(u8, arg, "--help")) { |
| 130 | 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 | 137 | try stdout.interface.print(usage, .{args[0]}); |
| 133 | 138 | try stdout.interface.flush(); |
| 134 | 139 | return; |
| 135 | 140 | } else if (mem.eql(u8, arg, "--version")) { |
| 136 | 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 | 143 | // TODO add version |
| 139 | 144 | try stdout.interface.writeAll("0.0.0-dev\n"); |
| 140 | 145 | try stdout.interface.flush(); |
| ... | ... | @@ -224,13 +229,13 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration |
| 224 | 229 | const dep_file_name = try d.getDepFileName(source, out_buf[0..std.fs.max_name_bytes]); |
| 225 | 230 | |
| 226 | 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 | 233 | return d.fatal("unable to create dependency file '{s}': {s}", .{ path, aro.Driver.errorDescription(er) }) |
| 229 | 234 | else |
| 230 | std.fs.File.stdout(); | |
| 231 | defer if (dep_file_name != null) file.close(); | |
| 235 | Io.File.stdout(); | |
| 236 | defer if (dep_file_name != null) file.close(io); | |
| 232 | 237 | |
| 233 | var file_writer = file.writer(&out_buf); | |
| 238 | var file_writer = file.writer(io, &out_buf); | |
| 234 | 239 | dep_file.write(&file_writer.interface) catch |
| 235 | 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 | 250 | |
| 246 | 251 | var close_out_file = false; |
| 247 | 252 | var out_file_path: []const u8 = "<stdout>"; |
| 248 | var out_file: std.fs.File = .stdout(); | |
| 249 | defer if (close_out_file) out_file.close(); | |
| 253 | var out_file: Io.File = .stdout(); | |
| 254 | defer if (close_out_file) out_file.close(io); | |
| 250 | 255 | |
| 251 | 256 | if (d.output_name) |path| blk: { |
| 252 | 257 | if (std.mem.eql(u8, path, "-")) break :blk; |
| 253 | 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 | 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 | 263 | return d.fatal("failed to create output file '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); |
| 259 | 264 | }; |
| 260 | 265 | close_out_file = true; |
| 261 | 266 | out_file_path = path; |
| 262 | 267 | } |
| 263 | 268 | |
| 264 | var out_writer = out_file.writer(&out_buf); | |
| 269 | var out_writer = out_file.writer(io, &out_buf); | |
| 265 | 270 | out_writer.interface.writeAll(rendered_zig) catch {}; |
| 266 | 271 | out_writer.interface.flush() catch {}; |
| 267 | 272 | if (out_writer.err) |write_err| |
lib/compiler_rt/emutls.zig+1-1| ... | ... | @@ -7,7 +7,7 @@ const std = @import("std"); |
| 7 | 7 | const builtin = @import("builtin"); |
| 8 | 8 | const common = @import("common.zig"); |
| 9 | 9 | |
| 10 | const abort = std.posix.abort; | |
| 10 | const abort = std.process.abort; | |
| 11 | 11 | const assert = std.debug.assert; |
| 12 | 12 | const expect = std.testing.expect; |
| 13 | 13 |
lib/fuzzer.zig+39-45| ... | ... | @@ -1,18 +1,22 @@ |
| 1 | 1 | const builtin = @import("builtin"); |
| 2 | const native_endian = builtin.cpu.arch.endian(); | |
| 3 | ||
| 2 | 4 | const std = @import("std"); |
| 5 | const Io = std.Io; | |
| 3 | 6 | const fatal = std.process.fatal; |
| 4 | 7 | const mem = std.mem; |
| 5 | 8 | const math = std.math; |
| 6 | const Allocator = mem.Allocator; | |
| 9 | const Allocator = std.mem.Allocator; | |
| 7 | 10 | const assert = std.debug.assert; |
| 8 | 11 | const panic = std.debug.panic; |
| 9 | 12 | const abi = std.Build.abi.fuzz; |
| 10 | const native_endian = builtin.cpu.arch.endian(); | |
| 11 | 13 | |
| 12 | 14 | pub const std_options = std.Options{ |
| 13 | 15 | .logFn = logOverride, |
| 14 | 16 | }; |
| 15 | 17 | |
| 18 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 19 | ||
| 16 | 20 | fn logOverride( |
| 17 | 21 | comptime level: std.log.Level, |
| 18 | 22 | comptime scope: @EnumLiteral(), |
| ... | ... | @@ -21,12 +25,12 @@ fn logOverride( |
| 21 | 25 | ) void { |
| 22 | 26 | const f = log_f orelse |
| 23 | 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}); | |
| 25 | defer f.unlock(); | |
| 28 | f.lock(io, .exclusive) catch |e| panic("failed to lock logging file: {t}", .{e}); | |
| 29 | defer f.unlock(io); | |
| 26 | 30 | |
| 27 | 31 | var buf: [256]u8 = undefined; |
| 28 | var fw = f.writer(&buf); | |
| 29 | const end = f.getEndPos() catch |e| panic("failed to get fuzzer log file end: {t}", .{e}); | |
| 32 | var fw = f.writer(io, &buf); | |
| 33 | const end = f.length(io) catch |e| panic("failed to get fuzzer log file end: {t}", .{e}); | |
| 30 | 34 | fw.seekTo(end) catch |e| panic("failed to seek to fuzzer log file end: {t}", .{e}); |
| 31 | 35 | |
| 32 | 36 | const prefix1 = comptime level.asText(); |
| ... | ... | @@ -45,7 +49,7 @@ const gpa = switch (builtin.mode) { |
| 45 | 49 | }; |
| 46 | 50 | |
| 47 | 51 | /// Part of `exec`, however seperate to allow it to be set before `exec` is. |
| 48 | var log_f: ?std.fs.File = null; | |
| 52 | var log_f: ?Io.File = null; | |
| 49 | 53 | var exec: Executable = .preinit; |
| 50 | 54 | var inst: Instrumentation = .preinit; |
| 51 | 55 | var fuzzer: Fuzzer = undefined; |
| ... | ... | @@ -59,7 +63,7 @@ const Executable = struct { |
| 59 | 63 | /// Tracks the hit count for each pc as updated by the process's instrumentation. |
| 60 | 64 | pc_counters: []u8, |
| 61 | 65 | |
| 62 | cache_f: std.fs.Dir, | |
| 66 | cache_f: Io.Dir, | |
| 63 | 67 | /// Shared copy of all pcs that have been hit stored in a memory-mapped file that can viewed |
| 64 | 68 | /// while the fuzzer is running. |
| 65 | 69 | shared_seen_pcs: MemoryMappedList, |
| ... | ... | @@ -76,16 +80,16 @@ const Executable = struct { |
| 76 | 80 | .pc_digest = undefined, |
| 77 | 81 | }; |
| 78 | 82 | |
| 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 | 84 | const pc_bitset_usizes = bitsetUsizes(pcs.len); |
| 81 | 85 | const coverage_file_name = std.fmt.hex(pc_digest); |
| 82 | 86 | comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize); |
| 83 | 87 | comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr); |
| 84 | 88 | |
| 85 | var v = cache_dir.makeOpenPath("v", .{}) catch |e| | |
| 89 | var v = cache_dir.createDirPathOpen(io, "v", .{}) catch |e| | |
| 86 | 90 | panic("failed to create directory 'v': {t}", .{e}); |
| 87 | defer v.close(); | |
| 88 | const coverage_file, const populate = if (v.createFile(&coverage_file_name, .{ | |
| 91 | defer v.close(io); | |
| 92 | const coverage_file, const populate = if (v.createFile(io, &coverage_file_name, .{ | |
| 89 | 93 | .read = true, |
| 90 | 94 | // If we create the file, we want to block other processes while we populate it |
| 91 | 95 | .lock = .exclusive, |
| ... | ... | @@ -93,7 +97,7 @@ const Executable = struct { |
| 93 | 97 | })) |f| |
| 94 | 98 | .{ f, true } |
| 95 | 99 | else |e| switch (e) { |
| 96 | error.PathAlreadyExists => .{ v.openFile(&coverage_file_name, .{ | |
| 100 | error.PathAlreadyExists => .{ v.openFile(io, &coverage_file_name, .{ | |
| 97 | 101 | .mode = .read_write, |
| 98 | 102 | .lock = .shared, |
| 99 | 103 | }) catch |e2| panic( |
| ... | ... | @@ -108,7 +112,7 @@ const Executable = struct { |
| 108 | 112 | pcs.len * @sizeOf(usize); |
| 109 | 113 | |
| 110 | 114 | if (populate) { |
| 111 | defer coverage_file.lock(.shared) catch |e| panic( | |
| 115 | defer coverage_file.lock(io, .shared) catch |e| panic( | |
| 112 | 116 | "failed to demote lock for coverage file '{s}': {t}", |
| 113 | 117 | .{ &coverage_file_name, e }, |
| 114 | 118 | ); |
| ... | ... | @@ -130,10 +134,8 @@ const Executable = struct { |
| 130 | 134 | } |
| 131 | 135 | return map; |
| 132 | 136 | } else { |
| 133 | const size = coverage_file.getEndPos() catch |e| panic( | |
| 134 | "failed to stat coverage file '{s}': {t}", | |
| 135 | .{ &coverage_file_name, e }, | |
| 136 | ); | |
| 137 | const size = coverage_file.length(io) catch |e| | |
| 138 | panic("failed to stat coverage file '{s}': {t}", .{ &coverage_file_name, e }); | |
| 137 | 139 | if (size != coverage_file_len) panic( |
| 138 | 140 | "incompatible existing coverage file '{s}' (differing lengths: {} != {})", |
| 139 | 141 | .{ &coverage_file_name, size, coverage_file_len }, |
| ... | ... | @@ -165,13 +167,11 @@ const Executable = struct { |
| 165 | 167 | pub fn init(cache_dir_path: []const u8) Executable { |
| 166 | 168 | var self: Executable = undefined; |
| 167 | 169 | |
| 168 | const cache_dir = std.fs.cwd().makeOpenPath(cache_dir_path, .{}) catch |e| panic( | |
| 169 | "failed to open directory '{s}': {t}", | |
| 170 | .{ cache_dir_path, e }, | |
| 171 | ); | |
| 172 | log_f = cache_dir.createFile("tmp/libfuzzer.log", .{ .truncate = false }) catch |e| | |
| 170 | const cache_dir = Io.Dir.cwd().createDirPathOpen(io, cache_dir_path, .{}) catch |e| | |
| 171 | panic("failed to open directory '{s}': {t}", .{ cache_dir_path, e }); | |
| 172 | log_f = cache_dir.createFile(io, "tmp/libfuzzer.log", .{ .truncate = false }) catch |e| | |
| 173 | 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 | 175 | panic("failed to open directory 'f': {t}", .{e}); |
| 176 | 176 | |
| 177 | 177 | // Linkers are expected to automatically add symbols prefixed with these for the start and |
| ... | ... | @@ -391,7 +391,7 @@ const Fuzzer = struct { |
| 391 | 391 | mutations: std.ArrayList(Mutation) = .empty, |
| 392 | 392 | |
| 393 | 393 | /// Filesystem directory containing found inputs for future runs |
| 394 | corpus_dir: std.fs.Dir, | |
| 394 | corpus_dir: Io.Dir, | |
| 395 | 395 | corpus_dir_idx: usize = 0, |
| 396 | 396 | |
| 397 | 397 | pub fn init(test_one: abi.TestOne, unit_test_name: []const u8) Fuzzer { |
| ... | ... | @@ -405,10 +405,10 @@ const Fuzzer = struct { |
| 405 | 405 | }; |
| 406 | 406 | const arena = self.arena_ctx.allocator(); |
| 407 | 407 | |
| 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 | 409 | panic("failed to open directory '{s}': {t}", .{ unit_test_name, e }); |
| 410 | 410 | self.input = in: { |
| 411 | const f = self.corpus_dir.createFile("in", .{ | |
| 411 | const f = self.corpus_dir.createFile(io, "in", .{ | |
| 412 | 412 | .read = true, |
| 413 | 413 | .truncate = false, |
| 414 | 414 | // In case any other fuzz tests are running under the same test name, |
| ... | ... | @@ -419,7 +419,7 @@ const Fuzzer = struct { |
| 419 | 419 | error.WouldBlock => @panic("input file 'in' is in use by another fuzzing process"), |
| 420 | 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 | 423 | const map = (if (size < std.heap.page_size_max) |
| 424 | 424 | MemoryMappedList.create(f, 8, std.heap.page_size_max) |
| 425 | 425 | else |
| ... | ... | @@ -445,6 +445,7 @@ const Fuzzer = struct { |
| 445 | 445 | while (true) { |
| 446 | 446 | var name_buf: [@sizeOf(usize) * 2]u8 = undefined; |
| 447 | 447 | const bytes = self.corpus_dir.readFileAlloc( |
| 448 | io, | |
| 448 | 449 | std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable, |
| 449 | 450 | arena, |
| 450 | 451 | .unlimited, |
| ... | ... | @@ -466,7 +467,7 @@ const Fuzzer = struct { |
| 466 | 467 | self.input.deinit(); |
| 467 | 468 | self.corpus.deinit(gpa); |
| 468 | 469 | self.mutations.deinit(gpa); |
| 469 | self.corpus_dir.close(); | |
| 470 | self.corpus_dir.close(io); | |
| 470 | 471 | self.arena_ctx.deinit(); |
| 471 | 472 | self.* = undefined; |
| 472 | 473 | } |
| ... | ... | @@ -573,17 +574,10 @@ const Fuzzer = struct { |
| 573 | 574 | |
| 574 | 575 | // Write new corpus to cache |
| 575 | 576 | var name_buf: [@sizeOf(usize) * 2]u8 = undefined; |
| 576 | self.corpus_dir.writeFile(.{ | |
| 577 | .sub_path = std.fmt.bufPrint( | |
| 578 | &name_buf, | |
| 579 | "{x}", | |
| 580 | .{self.corpus_dir_idx}, | |
| 581 | ) catch unreachable, | |
| 577 | self.corpus_dir.writeFile(io, .{ | |
| 578 | .sub_path = std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable, | |
| 582 | 579 | .data = bytes, |
| 583 | }) catch |e| panic( | |
| 584 | "failed to write corpus file '{x}': {t}", | |
| 585 | .{ self.corpus_dir_idx, e }, | |
| 586 | ); | |
| 580 | }) catch |e| panic("failed to write corpus file '{x}': {t}", .{ self.corpus_dir_idx, e }); | |
| 587 | 581 | self.corpus_dir_idx += 1; |
| 588 | 582 | } |
| 589 | 583 | } |
| ... | ... | @@ -1320,9 +1314,9 @@ pub const MemoryMappedList = struct { |
| 1320 | 1314 | /// How many bytes this list can hold without allocating additional memory. |
| 1321 | 1315 | capacity: usize, |
| 1322 | 1316 | /// The file is kept open so that it can be resized. |
| 1323 | file: std.fs.File, | |
| 1317 | file: Io.File, | |
| 1324 | 1318 | |
| 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 | 1320 | const ptr = try std.posix.mmap( |
| 1327 | 1321 | null, |
| 1328 | 1322 | capacity, |
| ... | ... | @@ -1338,13 +1332,13 @@ pub const MemoryMappedList = struct { |
| 1338 | 1332 | }; |
| 1339 | 1333 | } |
| 1340 | 1334 | |
| 1341 | pub fn create(file: std.fs.File, length: usize, capacity: usize) !MemoryMappedList { | |
| 1342 | try file.setEndPos(capacity); | |
| 1335 | pub fn create(file: Io.File, length: usize, capacity: usize) !MemoryMappedList { | |
| 1336 | try file.setLength(io, capacity); | |
| 1343 | 1337 | return init(file, length, capacity); |
| 1344 | 1338 | } |
| 1345 | 1339 | |
| 1346 | 1340 | pub fn deinit(l: *MemoryMappedList) void { |
| 1347 | l.file.close(); | |
| 1341 | l.file.close(io); | |
| 1348 | 1342 | std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity])); |
| 1349 | 1343 | l.* = undefined; |
| 1350 | 1344 | } |
| ... | ... | @@ -1369,7 +1363,7 @@ pub const MemoryMappedList = struct { |
| 1369 | 1363 | if (l.capacity >= new_capacity) return; |
| 1370 | 1364 | |
| 1371 | 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 | 1367 | l.* = try init(l.file, l.items.len, new_capacity); |
| 1374 | 1368 | } |
| 1375 | 1369 |
lib/init/src/main.zig+24-2| ... | ... | @@ -1,10 +1,32 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 3 | ||
| 2 | 4 | const _NAME = @import(".NAME"); |
| 3 | 5 | |
| 4 | 6 | pub fn main() !void { |
| 5 | // Prints to stderr, ignoring potential errors. | |
| 7 | // Prints to stderr, unbuffered, ignoring potential errors. | |
| 6 | 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 | } |
| 9 | 31 | |
| 10 | 32 | test "simple test" { |
lib/init/src/root.zig+7-12| ... | ... | @@ -1,17 +1,12 @@ |
| 1 | //! By convention, root.zig is the root source file when making a library. | |
| 1 | //! By convention, root.zig is the root source file when making a package. | |
| 2 | 2 | const std = @import("std"); |
| 3 | const Io = std.Io; | |
| 3 | 4 | |
| 4 | pub fn bufferedPrint() !void { | |
| 5 | // Stdout is for the actual output of your application, for example if you | |
| 6 | // are implementing gzip, then only the compressed bytes should be sent to | |
| 7 | // stdout, not any debugging messages. | |
| 8 | var stdout_buffer: [1024]u8 = undefined; | |
| 9 | var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); | |
| 10 | const stdout = &stdout_writer.interface; | |
| 11 | ||
| 12 | try stdout.print("Run `zig build test` to run the tests.\n", .{}); | |
| 13 | ||
| 14 | try stdout.flush(); // Don't forget to flush! | |
| 5 | /// This is a documentation comment to explain the `printAnotherMessage` function below. | |
| 6 | /// | |
| 7 | /// Accepting an `Io.Writer` instance is a handy way to write reusable code. | |
| 8 | pub fn printAnotherMessage(writer: *Io.Writer) Io.Writer.Error!void { | |
| 9 | try writer.print("Run `zig build test` to run the tests.\n", .{}); | |
| 15 | 10 | } |
| 16 | 11 | |
| 17 | 12 | pub fn add(a: i32, b: i32) i32 { |
lib/std/Build.zig+83-61| ... | ... | @@ -1,21 +1,20 @@ |
| 1 | const Build = @This(); | |
| 1 | 2 | const builtin = @import("builtin"); |
| 2 | 3 | |
| 3 | 4 | const std = @import("std.zig"); |
| 4 | 5 | const Io = std.Io; |
| 5 | 6 | const fs = std.fs; |
| 6 | 7 | const mem = std.mem; |
| 7 | const debug = std.debug; | |
| 8 | 8 | const panic = std.debug.panic; |
| 9 | const assert = debug.assert; | |
| 9 | const assert = std.debug.assert; | |
| 10 | 10 | const log = std.log; |
| 11 | 11 | const StringHashMap = std.StringHashMap; |
| 12 | const Allocator = mem.Allocator; | |
| 12 | const Allocator = std.mem.Allocator; | |
| 13 | 13 | const Target = std.Target; |
| 14 | 14 | const process = std.process; |
| 15 | 15 | const EnvMap = std.process.EnvMap; |
| 16 | const File = fs.File; | |
| 16 | const File = std.Io.File; | |
| 17 | 17 | const Sha256 = std.crypto.hash.sha2.Sha256; |
| 18 | const Build = @This(); | |
| 19 | 18 | const ArrayList = std.ArrayList; |
| 20 | 19 | |
| 21 | 20 | pub const Cache = @import("Build/Cache.zig"); |
| ... | ... | @@ -130,6 +129,9 @@ pub const Graph = struct { |
| 130 | 129 | dependency_cache: InitializedDepMap = .empty, |
| 131 | 130 | allow_so_scripts: ?bool = null, |
| 132 | 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 | }; |
| 134 | 136 | |
| 135 | 137 | const AvailableDeps = []const struct { []const u8, []const u8 }; |
| ... | ... | @@ -1699,21 +1701,20 @@ pub fn addCheckFile( |
| 1699 | 1701 | return Step.CheckFile.create(b, file_source, options); |
| 1700 | 1702 | } |
| 1701 | 1703 | |
| 1702 | pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Dir.StatFileError)!void { | |
| 1703 | if (b.verbose) { | |
| 1704 | log.info("truncate {s}", .{dest_path}); | |
| 1705 | } | |
| 1706 | const cwd = fs.cwd(); | |
| 1707 | var src_file = cwd.createFile(dest_path, .{}) catch |err| switch (err) { | |
| 1704 | pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.CreateDirError || Io.Dir.StatFileError)!void { | |
| 1705 | const io = b.graph.io; | |
| 1706 | if (b.verbose) log.info("truncate {s}", .{dest_path}); | |
| 1707 | const cwd = Io.Dir.cwd(); | |
| 1708 | var src_file = cwd.createFile(io, dest_path, .{}) catch |err| switch (err) { | |
| 1708 | 1709 | error.FileNotFound => blk: { |
| 1709 | 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 | 1715 | else => |e| return e, |
| 1715 | 1716 | }; |
| 1716 | src_file.close(); | |
| 1717 | src_file.close(io); | |
| 1717 | 1718 | } |
| 1718 | 1719 | |
| 1719 | 1720 | /// References a file or directory relative to the source root. |
| ... | ... | @@ -1761,7 +1762,10 @@ fn supportedWindowsProgramExtension(ext: []const u8) bool { |
| 1761 | 1762 | } |
| 1762 | 1763 | |
| 1763 | 1764 | fn 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 | 1769 | return p; |
| 1766 | 1770 | } else |err| switch (err) { |
| 1767 | 1771 | error.OutOfMemory => @panic("OOM"), |
| ... | ... | @@ -1775,7 +1779,11 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { |
| 1775 | 1779 | while (it.next()) |ext| { |
| 1776 | 1780 | if (!supportedWindowsProgramExtension(ext)) continue; |
| 1777 | 1781 | |
| 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 | 1787 | error.OutOfMemory => @panic("OOM"), |
| 1780 | 1788 | else => continue, |
| 1781 | 1789 | }; |
| ... | ... | @@ -1839,7 +1847,7 @@ pub fn runAllowFail( |
| 1839 | 1847 | child.env_map = &b.graph.env_map; |
| 1840 | 1848 | |
| 1841 | 1849 | try Step.handleVerbose2(b, null, child.env_map, argv); |
| 1842 | try child.spawn(); | |
| 1850 | try child.spawn(io); | |
| 1843 | 1851 | |
| 1844 | 1852 | var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); |
| 1845 | 1853 | const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch { |
| ... | ... | @@ -1847,7 +1855,7 @@ pub fn runAllowFail( |
| 1847 | 1855 | }; |
| 1848 | 1856 | errdefer b.allocator.free(stdout); |
| 1849 | 1857 | |
| 1850 | const term = try child.wait(); | |
| 1858 | const term = try child.wait(io); | |
| 1851 | 1859 | switch (term) { |
| 1852 | 1860 | .Exited => |code| { |
| 1853 | 1861 | if (code != 0) { |
| ... | ... | @@ -2091,7 +2099,7 @@ pub fn dependencyFromBuildZig( |
| 2091 | 2099 | } |
| 2092 | 2100 | |
| 2093 | 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 | } |
| 2096 | 2104 | |
| 2097 | 2105 | fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool { |
| ... | ... | @@ -2185,6 +2193,7 @@ fn dependencyInner( |
| 2185 | 2193 | pkg_deps: AvailableDeps, |
| 2186 | 2194 | args: anytype, |
| 2187 | 2195 | ) *Dependency { |
| 2196 | const io = b.graph.io; | |
| 2188 | 2197 | const user_input_options = userInputOptionsFromArgs(b.allocator, args); |
| 2189 | 2198 | if (b.graph.dependency_cache.getContext(.{ |
| 2190 | 2199 | .build_root_string = build_root_string, |
| ... | ... | @@ -2194,7 +2203,7 @@ fn dependencyInner( |
| 2194 | 2203 | |
| 2195 | 2204 | const build_root: std.Build.Cache.Directory = .{ |
| 2196 | 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 | 2207 | std.debug.print("unable to open '{s}': {s}\n", .{ |
| 2199 | 2208 | build_root_string, @errorName(err), |
| 2200 | 2209 | }); |
| ... | ... | @@ -2239,7 +2248,7 @@ pub const GeneratedFile = struct { |
| 2239 | 2248 | /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards. |
| 2240 | 2249 | path: ?[]const u8 = null, |
| 2241 | 2250 | |
| 2242 | /// Deprecated, see `getPath2`. | |
| 2251 | /// Deprecated, see `getPath3`. | |
| 2243 | 2252 | pub fn getPath(gen: GeneratedFile) []const u8 { |
| 2244 | 2253 | return gen.step.owner.pathFromCwd(gen.path orelse std.debug.panic( |
| 2245 | 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 | 2256 | )); |
| 2248 | 2257 | } |
| 2249 | 2258 | |
| 2259 | /// Deprecated, see `getPath3`. | |
| 2250 | 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 | 2267 | return gen.path orelse { |
| 2252 | const w, const ttyconf = debug.lockStderrWriter(&.{}); | |
| 2253 | dumpBadGetPathHelp(gen.step, w, ttyconf, src_builder, asking_step) catch {}; | |
| 2254 | debug.unlockStderrWriter(); | |
| 2268 | const graph = gen.step.owner.graph; | |
| 2269 | const io = graph.io; | |
| 2270 | const stderr = try io.lockStderr(&.{}, graph.stderr_mode); | |
| 2271 | dumpBadGetPathHelp(gen.step, stderr.terminal(), src_builder, asking_step) catch {}; | |
| 2255 | 2272 | @panic("misconfigured build script"); |
| 2256 | 2273 | }; |
| 2257 | 2274 | } |
| ... | ... | @@ -2426,22 +2443,29 @@ pub const LazyPath = union(enum) { |
| 2426 | 2443 | } |
| 2427 | 2444 | } |
| 2428 | 2445 | |
| 2429 | /// Deprecated, see `getPath3`. | |
| 2446 | /// Deprecated, see `getPath4`. | |
| 2430 | 2447 | pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 { |
| 2431 | 2448 | return getPath2(lazy_path, src_builder, null); |
| 2432 | 2449 | } |
| 2433 | 2450 | |
| 2434 | /// Deprecated, see `getPath3`. | |
| 2451 | /// Deprecated, see `getPath4`. | |
| 2435 | 2452 | pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 { |
| 2436 | 2453 | const p = getPath3(lazy_path, src_builder, asking_step); |
| 2437 | 2454 | return src_builder.pathResolve(&.{ p.root_dir.path orelse ".", p.sub_path }); |
| 2438 | 2455 | } |
| 2439 | 2456 | |
| 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 | 2464 | /// Intended to be used during the make phase only. |
| 2441 | 2465 | /// |
| 2442 | 2466 | /// `asking_step` is only used for debugging purposes; it's the step being |
| 2443 | 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 | 2469 | switch (lazy_path) { |
| 2446 | 2470 | .src_path => |sp| return .{ |
| 2447 | 2471 | .root_dir = sp.owner.build_root, |
| ... | ... | @@ -2455,12 +2479,15 @@ pub const LazyPath = union(enum) { |
| 2455 | 2479 | // TODO make gen.file.path not be absolute and use that as the |
| 2456 | 2480 | // basis for not traversing up too many directories. |
| 2457 | 2481 | |
| 2482 | const graph = src_builder.graph; | |
| 2483 | ||
| 2458 | 2484 | var file_path: Cache.Path = .{ |
| 2459 | 2485 | .root_dir = Cache.Directory.cwd(), |
| 2460 | 2486 | .sub_path = gen.file.path orelse { |
| 2461 | const w, const ttyconf = debug.lockStderrWriter(&.{}); | |
| 2462 | dumpBadGetPathHelp(gen.file.step, w, ttyconf, src_builder, asking_step) catch {}; | |
| 2463 | debug.unlockStderrWriter(); | |
| 2487 | const io = graph.io; | |
| 2488 | const stderr = try io.lockStderr(&.{}, graph.stderr_mode); | |
| 2489 | dumpBadGetPathHelp(gen.file.step, stderr.terminal(), src_builder, asking_step) catch {}; | |
| 2490 | io.unlockStderr(); | |
| 2464 | 2491 | @panic("misconfigured build script"); |
| 2465 | 2492 | }, |
| 2466 | 2493 | }; |
| ... | ... | @@ -2550,40 +2577,36 @@ fn dumpBadDirnameHelp( |
| 2550 | 2577 | comptime msg: []const u8, |
| 2551 | 2578 | args: anytype, |
| 2552 | 2579 | ) anyerror!void { |
| 2553 | const w, const tty_config = debug.lockStderrWriter(&.{}); | |
| 2554 | defer debug.unlockStderrWriter(); | |
| 2580 | const stderr = std.debug.lockStderr(&.{}).terminal(); | |
| 2581 | defer std.debug.unlockStderr(); | |
| 2582 | const w = stderr.writer; | |
| 2555 | 2583 | |
| 2556 | 2584 | try w.print(msg, args); |
| 2557 | 2585 | |
| 2558 | 2586 | if (fail_step) |s| { |
| 2559 | tty_config.setColor(w, .red) catch {}; | |
| 2587 | stderr.setColor(.red) catch {}; | |
| 2560 | 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 {}; | |
| 2562 | 2590 | |
| 2563 | s.dump(w, tty_config); | |
| 2591 | s.dump(stderr); | |
| 2564 | 2592 | } |
| 2565 | 2593 | |
| 2566 | 2594 | if (asking_step) |as| { |
| 2567 | tty_config.setColor(w, .red) catch {}; | |
| 2595 | stderr.setColor(.red) catch {}; | |
| 2568 | 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 {}; | |
| 2570 | 2598 | |
| 2571 | as.dump(w, tty_config); | |
| 2599 | as.dump(stderr); | |
| 2572 | 2600 | } |
| 2573 | 2601 | |
| 2574 | tty_config.setColor(w, .red) catch {}; | |
| 2575 | try w.writeAll(" Hope that helps. Proceeding to panic.\n"); | |
| 2576 | tty_config.setColor(w, .reset) catch {}; | |
| 2602 | stderr.setColor(.red) catch {}; | |
| 2603 | try w.writeAll(" Proceeding to panic.\n"); | |
| 2604 | stderr.setColor(.reset) catch {}; | |
| 2577 | 2605 | } |
| 2578 | 2606 | |
| 2579 | 2607 | /// In this function the stderr mutex has already been locked. |
| 2580 | pub fn dumpBadGetPathHelp( | |
| 2581 | s: *Step, | |
| 2582 | w: *std.Io.Writer, | |
| 2583 | tty_config: std.Io.tty.Config, | |
| 2584 | src_builder: *Build, | |
| 2585 | asking_step: ?*Step, | |
| 2586 | ) anyerror!void { | |
| 2608 | pub fn dumpBadGetPathHelp(s: *Step, t: Io.Terminal, src_builder: *Build, asking_step: ?*Step) anyerror!void { | |
| 2609 | const w = t.writer; | |
| 2587 | 2610 | try w.print( |
| 2588 | 2611 | \\getPath() was called on a GeneratedFile that wasn't built yet. |
| 2589 | 2612 | \\ source package path: {s} |
| ... | ... | @@ -2594,21 +2617,21 @@ pub fn dumpBadGetPathHelp( |
| 2594 | 2617 | s.name, |
| 2595 | 2618 | }); |
| 2596 | 2619 | |
| 2597 | tty_config.setColor(w, .red) catch {}; | |
| 2620 | t.setColor(.red) catch {}; | |
| 2598 | 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 {}; | |
| 2600 | 2623 | |
| 2601 | s.dump(w, tty_config); | |
| 2624 | s.dump(t); | |
| 2602 | 2625 | if (asking_step) |as| { |
| 2603 | tty_config.setColor(w, .red) catch {}; | |
| 2626 | t.setColor(.red) catch {}; | |
| 2604 | 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 {}; | |
| 2606 | 2629 | |
| 2607 | as.dump(w, tty_config); | |
| 2630 | as.dump(t); | |
| 2608 | 2631 | } |
| 2609 | tty_config.setColor(w, .red) catch {}; | |
| 2610 | try w.writeAll(" Hope that helps. Proceeding to panic.\n"); | |
| 2611 | tty_config.setColor(w, .reset) catch {}; | |
| 2632 | t.setColor(.red) catch {}; | |
| 2633 | try w.writeAll(" Proceeding to panic.\n"); | |
| 2634 | t.setColor(.reset) catch {}; | |
| 2612 | 2635 | } |
| 2613 | 2636 | |
| 2614 | 2637 | pub const InstallDir = union(enum) { |
| ... | ... | @@ -2634,13 +2657,12 @@ pub const InstallDir = union(enum) { |
| 2634 | 2657 | /// source of API breakage in the future, so keep that in mind when using this |
| 2635 | 2658 | /// function. |
| 2636 | 2659 | pub fn makeTempPath(b: *Build) []const u8 { |
| 2660 | const io = b.graph.io; | |
| 2637 | 2661 | const rand_int = std.crypto.random.int(u64); |
| 2638 | 2662 | const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); |
| 2639 | 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| { | |
| 2641 | std.debug.print("unable to make tmp path '{s}': {s}\n", .{ | |
| 2642 | result_path, @errorName(err), | |
| 2643 | }); | |
| 2664 | b.cache_root.handle.createDirPath(io, tmp_dir_sub_path) catch |err| { | |
| 2665 | std.debug.print("unable to make tmp path '{s}': {t}\n", .{ result_path, err }); | |
| 2644 | 2666 | }; |
| 2645 | 2667 | return result_path; |
| 2646 | 2668 | } |
lib/std/Build/Cache.zig+103-128| ... | ... | @@ -8,7 +8,6 @@ const builtin = @import("builtin"); |
| 8 | 8 | const std = @import("std"); |
| 9 | 9 | const Io = std.Io; |
| 10 | 10 | const crypto = std.crypto; |
| 11 | const fs = std.fs; | |
| 12 | 11 | const assert = std.debug.assert; |
| 13 | 12 | const testing = std.testing; |
| 14 | 13 | const mem = std.mem; |
| ... | ... | @@ -18,7 +17,7 @@ const log = std.log.scoped(.cache); |
| 18 | 17 | |
| 19 | 18 | gpa: Allocator, |
| 20 | 19 | io: Io, |
| 21 | manifest_dir: fs.Dir, | |
| 20 | manifest_dir: Io.Dir, | |
| 22 | 21 | hash: HashHelper = .{}, |
| 23 | 22 | /// This value is accessed from multiple threads, protected by mutex. |
| 24 | 23 | recent_problematic_timestamp: Io.Timestamp = .zero, |
| ... | ... | @@ -71,7 +70,7 @@ const PrefixedPath = struct { |
| 71 | 70 | |
| 72 | 71 | fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath { |
| 73 | 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 | 74 | errdefer gpa.free(resolved_path); |
| 76 | 75 | return findPrefixResolved(cache, resolved_path); |
| 77 | 76 | } |
| ... | ... | @@ -102,9 +101,9 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath { |
| 102 | 101 | } |
| 103 | 102 | |
| 104 | 103 | fn 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 | 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 | 107 | if (component_iterator.root() != null) { |
| 109 | 108 | return error.NotASubPath; |
| 110 | 109 | } |
| ... | ... | @@ -145,17 +144,17 @@ pub const File = struct { |
| 145 | 144 | max_file_size: ?usize, |
| 146 | 145 | /// Populated if the user calls `addOpenedFile`. |
| 147 | 146 | /// The handle is not owned here. |
| 148 | handle: ?fs.File, | |
| 147 | handle: ?Io.File, | |
| 149 | 148 | stat: Stat, |
| 150 | 149 | bin_digest: BinDigest, |
| 151 | 150 | contents: ?[]const u8, |
| 152 | 151 | |
| 153 | 152 | pub const Stat = struct { |
| 154 | inode: fs.File.INode, | |
| 153 | inode: Io.File.INode, | |
| 155 | 154 | size: u64, |
| 156 | 155 | mtime: Io.Timestamp, |
| 157 | 156 | |
| 158 | pub fn fromFs(fs_stat: fs.File.Stat) Stat { | |
| 157 | pub fn fromFs(fs_stat: Io.File.Stat) Stat { | |
| 159 | 158 | return .{ |
| 160 | 159 | .inode = fs_stat.inode, |
| 161 | 160 | .size = fs_stat.size, |
| ... | ... | @@ -178,7 +177,7 @@ pub const File = struct { |
| 178 | 177 | file.max_file_size = if (file.max_file_size) |old| @max(old, new) else new; |
| 179 | 178 | } |
| 180 | 179 | |
| 181 | pub fn updateHandle(file: *File, new_handle: ?fs.File) void { | |
| 180 | pub fn updateHandle(file: *File, new_handle: ?Io.File) void { | |
| 182 | 181 | const handle = new_handle orelse return; |
| 183 | 182 | file.handle = handle; |
| 184 | 183 | } |
| ... | ... | @@ -293,16 +292,16 @@ pub fn binToHex(bin_digest: BinDigest) HexDigest { |
| 293 | 292 | } |
| 294 | 293 | |
| 295 | 294 | pub const Lock = struct { |
| 296 | manifest_file: fs.File, | |
| 295 | manifest_file: Io.File, | |
| 297 | 296 | |
| 298 | pub fn release(lock: *Lock) void { | |
| 297 | pub fn release(lock: *Lock, io: Io) void { | |
| 299 | 298 | if (builtin.os.tag == .windows) { |
| 300 | 299 | // Windows does not guarantee that locks are immediately unlocked when |
| 301 | 300 | // the file handle is closed. See LockFileEx documentation. |
| 302 | lock.manifest_file.unlock(); | |
| 301 | lock.manifest_file.unlock(io); | |
| 303 | 302 | } |
| 304 | 303 | |
| 305 | lock.manifest_file.close(); | |
| 304 | lock.manifest_file.close(io); | |
| 306 | 305 | lock.* = undefined; |
| 307 | 306 | } |
| 308 | 307 | }; |
| ... | ... | @@ -311,7 +310,7 @@ pub const Manifest = struct { |
| 311 | 310 | cache: *Cache, |
| 312 | 311 | /// Current state for incremental hashing. |
| 313 | 312 | hash: HashHelper, |
| 314 | manifest_file: ?fs.File, | |
| 313 | manifest_file: ?Io.File, | |
| 315 | 314 | manifest_dirty: bool, |
| 316 | 315 | /// Set this flag to true before calling hit() in order to indicate that |
| 317 | 316 | /// upon a cache hit, the code using the cache will not modify the files |
| ... | ... | @@ -332,9 +331,9 @@ pub const Manifest = struct { |
| 332 | 331 | |
| 333 | 332 | pub const Diagnostic = union(enum) { |
| 334 | 333 | none, |
| 335 | manifest_create: fs.File.OpenError, | |
| 336 | manifest_read: fs.File.ReadError, | |
| 337 | manifest_lock: fs.File.LockError, | |
| 334 | manifest_create: Io.File.OpenError, | |
| 335 | manifest_read: Io.File.Reader.Error, | |
| 336 | manifest_lock: Io.File.LockError, | |
| 338 | 337 | file_open: FileOp, |
| 339 | 338 | file_stat: FileOp, |
| 340 | 339 | file_read: FileOp, |
| ... | ... | @@ -393,10 +392,10 @@ pub const Manifest = struct { |
| 393 | 392 | } |
| 394 | 393 | |
| 395 | 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 | 396 | const gpa = m.cache.gpa; |
| 398 | 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 | 399 | path.root_dir.path orelse ".", |
| 401 | 400 | path.subPathOrDot(), |
| 402 | 401 | }); |
| ... | ... | @@ -417,7 +416,7 @@ pub const Manifest = struct { |
| 417 | 416 | return addFileInner(self, prefixed_path, null, max_file_size); |
| 418 | 417 | } |
| 419 | 418 | |
| 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 | 420 | const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{}); |
| 422 | 421 | if (gop.found_existing) { |
| 423 | 422 | self.cache.gpa.free(prefixed_path.sub_path); |
| ... | ... | @@ -460,7 +459,7 @@ pub const Manifest = struct { |
| 460 | 459 | } |
| 461 | 460 | } |
| 462 | 461 | |
| 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 | 463 | assert(self.manifest_file == null); |
| 465 | 464 | return self.addDepFileMaybePost(dir, dep_file_sub_path); |
| 466 | 465 | } |
| ... | ... | @@ -503,11 +502,13 @@ pub const Manifest = struct { |
| 503 | 502 | @memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest); |
| 504 | 503 | manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*; |
| 505 | 504 | |
| 505 | const io = self.cache.io; | |
| 506 | ||
| 506 | 507 | // We'll try to open the cache with an exclusive lock, but if that would block |
| 507 | 508 | // and `want_shared_lock` is set, a shared lock might be sufficient, so we'll |
| 508 | 509 | // open with a shared lock instead. |
| 509 | 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 | 512 | .read = true, |
| 512 | 513 | .truncate = false, |
| 513 | 514 | .lock = .exclusive, |
| ... | ... | @@ -518,7 +519,7 @@ pub const Manifest = struct { |
| 518 | 519 | break; |
| 519 | 520 | } else |err| switch (err) { |
| 520 | 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 | 523 | .mode = .read_write, |
| 523 | 524 | .lock = .shared, |
| 524 | 525 | }) catch |e| { |
| ... | ... | @@ -542,7 +543,7 @@ pub const Manifest = struct { |
| 542 | 543 | return error.CacheCheckFailed; |
| 543 | 544 | } |
| 544 | 545 | |
| 545 | if (self.cache.manifest_dir.createFile(&manifest_file_path, .{ | |
| 546 | if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{ | |
| 546 | 547 | .read = true, |
| 547 | 548 | .truncate = false, |
| 548 | 549 | .lock = .exclusive, |
| ... | ... | @@ -702,7 +703,7 @@ pub const Manifest = struct { |
| 702 | 703 | const file_path = iter.rest(); |
| 703 | 704 | |
| 704 | 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 | 707 | const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; |
| 707 | 708 | const file_bin_digest = b: { |
| 708 | 709 | if (digest_str.len != hex_digest_len) return error.InvalidFormat; |
| ... | ... | @@ -758,7 +759,7 @@ pub const Manifest = struct { |
| 758 | 759 | |
| 759 | 760 | const pp = cache_hash_file.prefixed_path; |
| 760 | 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 | 763 | error.FileNotFound => { |
| 763 | 764 | // Every digest before this one has been populated successfully. |
| 764 | 765 | return .{ .miss = .{ .file_digests_populated = idx } }; |
| ... | ... | @@ -772,9 +773,9 @@ pub const Manifest = struct { |
| 772 | 773 | return error.CacheCheckFailed; |
| 773 | 774 | }, |
| 774 | 775 | }; |
| 775 | defer this_file.close(); | |
| 776 | defer this_file.close(io); | |
| 776 | 777 | |
| 777 | const actual_stat = this_file.stat() catch |err| { | |
| 778 | const actual_stat = this_file.stat(io) catch |err| { | |
| 778 | 779 | self.diagnostic = .{ .file_stat = .{ |
| 779 | 780 | .file_index = idx, |
| 780 | 781 | .err = err, |
| ... | ... | @@ -799,7 +800,7 @@ pub const Manifest = struct { |
| 799 | 800 | } |
| 800 | 801 | |
| 801 | 802 | var actual_digest: BinDigest = undefined; |
| 802 | hashFile(this_file, &actual_digest) catch |err| { | |
| 803 | hashFile(io, this_file, &actual_digest) catch |err| { | |
| 803 | 804 | self.diagnostic = .{ .file_read = .{ |
| 804 | 805 | .file_index = idx, |
| 805 | 806 | .err = err, |
| ... | ... | @@ -872,17 +873,17 @@ pub const Manifest = struct { |
| 872 | 873 | if (man.want_refresh_timestamp) { |
| 873 | 874 | man.want_refresh_timestamp = false; |
| 874 | 875 | |
| 875 | var file = man.cache.manifest_dir.createFile("timestamp", .{ | |
| 876 | var file = man.cache.manifest_dir.createFile(io, "timestamp", .{ | |
| 876 | 877 | .read = true, |
| 877 | 878 | .truncate = true, |
| 878 | 879 | }) catch |err| switch (err) { |
| 879 | 880 | error.Canceled => return error.Canceled, |
| 880 | 881 | else => return true, |
| 881 | 882 | }; |
| 882 | defer file.close(); | |
| 883 | defer file.close(io); | |
| 883 | 884 | |
| 884 | 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 | 887 | error.Canceled => return error.Canceled, |
| 887 | 888 | else => return true, |
| 888 | 889 | }; |
| ... | ... | @@ -894,19 +895,24 @@ pub const Manifest = struct { |
| 894 | 895 | } |
| 895 | 896 | |
| 896 | 897 | fn populateFileHash(self: *Manifest, ch_file: *File) !void { |
| 898 | const io = self.cache.io; | |
| 899 | ||
| 897 | 900 | if (ch_file.handle) |handle| { |
| 898 | 901 | return populateFileHashHandle(self, ch_file, handle); |
| 899 | 902 | } else { |
| 900 | 903 | const pp = ch_file.prefixed_path; |
| 901 | 904 | const dir = self.cache.prefixes()[pp.prefix].handle; |
| 902 | const handle = try dir.openFile(pp.sub_path, .{}); | |
| 903 | defer handle.close(); | |
| 905 | const handle = try dir.openFile(io, pp.sub_path, .{}); | |
| 906 | defer handle.close(io); | |
| 904 | 907 | return populateFileHashHandle(self, ch_file, handle); |
| 905 | 908 | } |
| 906 | 909 | } |
| 907 | 910 | |
| 908 | fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: fs.File) !void { | |
| 909 | const actual_stat = try handle.stat(); | |
| 911 | fn populateFileHashHandle(self: *Manifest, ch_file: *File, io_file: Io.File) !void { | |
| 912 | const io = self.cache.io; | |
| 913 | const gpa = self.cache.gpa; | |
| 914 | ||
| 915 | const actual_stat = try io_file.stat(io); | |
| 910 | 916 | ch_file.stat = .{ |
| 911 | 917 | .size = actual_stat.size, |
| 912 | 918 | .mtime = actual_stat.mtime, |
| ... | ... | @@ -920,19 +926,17 @@ pub const Manifest = struct { |
| 920 | 926 | } |
| 921 | 927 | |
| 922 | 928 | if (ch_file.max_file_size) |max_file_size| { |
| 923 | if (ch_file.stat.size > max_file_size) { | |
| 924 | return error.FileTooBig; | |
| 925 | } | |
| 929 | if (ch_file.stat.size > max_file_size) return error.FileTooBig; | |
| 926 | 930 | |
| 927 | const contents = try self.cache.gpa.alloc(u8, @as(usize, @intCast(ch_file.stat.size))); | |
| 928 | errdefer self.cache.gpa.free(contents); | |
| 931 | // Hash while reading from disk, to keep the contents in the cpu | |
| 932 | // cache while doing hashing. | |
| 933 | const contents = try gpa.alloc(u8, @intCast(ch_file.stat.size)); | |
| 934 | errdefer gpa.free(contents); | |
| 929 | 935 | |
| 930 | // Hash while reading from disk, to keep the contents in the cpu cache while | |
| 931 | // doing hashing. | |
| 932 | 936 | var hasher = hasher_init; |
| 933 | 937 | var off: usize = 0; |
| 934 | 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 | 940 | if (bytes_read == 0) break; |
| 937 | 941 | hasher.update(contents[off..][0..bytes_read]); |
| 938 | 942 | off += bytes_read; |
| ... | ... | @@ -941,7 +945,7 @@ pub const Manifest = struct { |
| 941 | 945 | |
| 942 | 946 | ch_file.contents = contents; |
| 943 | 947 | } else { |
| 944 | try hashFile(handle, &ch_file.bin_digest); | |
| 948 | try hashFile(io, io_file, &ch_file.bin_digest); | |
| 945 | 949 | } |
| 946 | 950 | |
| 947 | 951 | self.hash.hasher.update(&ch_file.bin_digest); |
| ... | ... | @@ -1064,14 +1068,15 @@ pub const Manifest = struct { |
| 1064 | 1068 | self.hash.hasher.update(&new_file.bin_digest); |
| 1065 | 1069 | } |
| 1066 | 1070 | |
| 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 | 1072 | assert(self.manifest_file != null); |
| 1069 | 1073 | return self.addDepFileMaybePost(dir, dep_file_sub_path); |
| 1070 | 1074 | } |
| 1071 | 1075 | |
| 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 | 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 | 1080 | defer gpa.free(dep_file_contents); |
| 1076 | 1081 | |
| 1077 | 1082 | var error_buf: std.ArrayList(u8) = .empty; |
| ... | ... | @@ -1130,13 +1135,13 @@ pub const Manifest = struct { |
| 1130 | 1135 | /// lock from exclusive to shared. |
| 1131 | 1136 | pub fn writeManifest(self: *Manifest) !void { |
| 1132 | 1137 | assert(self.have_exclusive_lock); |
| 1133 | ||
| 1138 | const io = self.cache.io; | |
| 1134 | 1139 | const manifest_file = self.manifest_file.?; |
| 1135 | 1140 | if (self.manifest_dirty) { |
| 1136 | 1141 | self.manifest_dirty = false; |
| 1137 | 1142 | |
| 1138 | 1143 | var buffer: [4000]u8 = undefined; |
| 1139 | var fw = manifest_file.writer(&buffer); | |
| 1144 | var fw = manifest_file.writer(io, &buffer); | |
| 1140 | 1145 | writeDirtyManifestToStream(self, &fw) catch |err| switch (err) { |
| 1141 | 1146 | error.WriteFailed => return fw.err.?, |
| 1142 | 1147 | else => |e| return e, |
| ... | ... | @@ -1148,7 +1153,7 @@ pub const Manifest = struct { |
| 1148 | 1153 | } |
| 1149 | 1154 | } |
| 1150 | 1155 | |
| 1151 | fn writeDirtyManifestToStream(self: *Manifest, fw: *fs.File.Writer) !void { | |
| 1156 | fn writeDirtyManifestToStream(self: *Manifest, fw: *Io.File.Writer) !void { | |
| 1152 | 1157 | try fw.interface.writeAll(manifest_header ++ "\n"); |
| 1153 | 1158 | for (self.files.keys()) |file| { |
| 1154 | 1159 | try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{ |
| ... | ... | @@ -1165,13 +1170,11 @@ pub const Manifest = struct { |
| 1165 | 1170 | |
| 1166 | 1171 | fn downgradeToSharedLock(self: *Manifest) !void { |
| 1167 | 1172 | if (!self.have_exclusive_lock) return; |
| 1173 | const io = self.cache.io; | |
| 1168 | 1174 | |
| 1169 | // WASI does not currently support flock, so we bypass it here. | |
| 1170 | // TODO: If/when flock is supported on WASI, this check should be removed. | |
| 1171 | // See https://github.com/WebAssembly/wasi-filesystem/issues/2 | |
| 1172 | if (builtin.os.tag != .wasi or std.process.can_spawn or !builtin.single_threaded) { | |
| 1175 | if (std.process.can_spawn or !builtin.single_threaded) { | |
| 1173 | 1176 | const manifest_file = self.manifest_file.?; |
| 1174 | try manifest_file.downgradeLock(); | |
| 1177 | try manifest_file.downgradeLock(io); | |
| 1175 | 1178 | } |
| 1176 | 1179 | |
| 1177 | 1180 | self.have_exclusive_lock = false; |
| ... | ... | @@ -1180,16 +1183,14 @@ pub const Manifest = struct { |
| 1180 | 1183 | fn upgradeToExclusiveLock(self: *Manifest) error{CacheCheckFailed}!bool { |
| 1181 | 1184 | if (self.have_exclusive_lock) return false; |
| 1182 | 1185 | assert(self.manifest_file != null); |
| 1186 | const io = self.cache.io; | |
| 1183 | 1187 | |
| 1184 | // WASI does not currently support flock, so we bypass it here. | |
| 1185 | // TODO: If/when flock is supported on WASI, this check should be removed. | |
| 1186 | // See https://github.com/WebAssembly/wasi-filesystem/issues/2 | |
| 1187 | if (builtin.os.tag != .wasi or std.process.can_spawn or !builtin.single_threaded) { | |
| 1188 | if (std.process.can_spawn or !builtin.single_threaded) { | |
| 1188 | 1189 | const manifest_file = self.manifest_file.?; |
| 1189 | 1190 | // Here we intentionally have a period where the lock is released, in case there are |
| 1190 | 1191 | // other processes holding a shared lock. |
| 1191 | manifest_file.unlock(); | |
| 1192 | manifest_file.lock(.exclusive) catch |err| { | |
| 1192 | manifest_file.unlock(io); | |
| 1193 | manifest_file.lock(io, .exclusive) catch |err| { | |
| 1193 | 1194 | self.diagnostic = .{ .manifest_lock = err }; |
| 1194 | 1195 | return error.CacheCheckFailed; |
| 1195 | 1196 | }; |
| ... | ... | @@ -1202,25 +1203,23 @@ pub const Manifest = struct { |
| 1202 | 1203 | /// The `Manifest` remains safe to deinit. |
| 1203 | 1204 | /// Don't forget to call `writeManifest` before this! |
| 1204 | 1205 | pub fn toOwnedLock(self: *Manifest) Lock { |
| 1205 | const lock: Lock = .{ | |
| 1206 | .manifest_file = self.manifest_file.?, | |
| 1207 | }; | |
| 1208 | ||
| 1209 | self.manifest_file = null; | |
| 1210 | return lock; | |
| 1206 | defer self.manifest_file = null; | |
| 1207 | return .{ .manifest_file = self.manifest_file.? }; | |
| 1211 | 1208 | } |
| 1212 | 1209 | |
| 1213 | 1210 | /// Releases the manifest file and frees any memory the Manifest was using. |
| 1214 | 1211 | /// `Manifest.hit` must be called first. |
| 1215 | 1212 | /// Don't forget to call `writeManifest` before this! |
| 1216 | 1213 | pub fn deinit(self: *Manifest) void { |
| 1214 | const io = self.cache.io; | |
| 1215 | ||
| 1217 | 1216 | if (self.manifest_file) |file| { |
| 1218 | 1217 | if (builtin.os.tag == .windows) { |
| 1219 | 1218 | // See Lock.release for why this is required on Windows |
| 1220 | file.unlock(); | |
| 1219 | file.unlock(io); | |
| 1221 | 1220 | } |
| 1222 | 1221 | |
| 1223 | file.close(); | |
| 1222 | file.close(io); | |
| 1224 | 1223 | } |
| 1225 | 1224 | for (self.files.keys()) |*file| { |
| 1226 | 1225 | file.deinit(self.cache.gpa); |
| ... | ... | @@ -1278,57 +1277,33 @@ pub const Manifest = struct { |
| 1278 | 1277 | } |
| 1279 | 1278 | }; |
| 1280 | 1279 | |
| 1281 | /// On operating systems that support symlinks, does a readlink. On other operating systems, | |
| 1282 | /// uses the file contents. Windows supports symlinks but only with elevated privileges, so | |
| 1283 | /// it is treated as not supporting symlinks. | |
| 1284 | pub 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. | |
| 1296 | pub 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 | ||
| 1305 | fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadError!void { | |
| 1306 | var buf: [1024]u8 = undefined; | |
| 1280 | fn hashFile(io: Io, file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.ReadPositionalError!void { | |
| 1281 | var buffer: [2048]u8 = undefined; | |
| 1307 | 1282 | var hasher = hasher_init; |
| 1308 | var off: u64 = 0; | |
| 1283 | var offset: u64 = 0; | |
| 1309 | 1284 | while (true) { |
| 1310 | const bytes_read = try file.pread(&buf, off); | |
| 1311 | if (bytes_read == 0) break; | |
| 1312 | hasher.update(buf[0..bytes_read]); | |
| 1313 | off += bytes_read; | |
| 1285 | const n = try file.readPositional(io, &.{&buffer}, offset); | |
| 1286 | if (n == 0) break; | |
| 1287 | hasher.update(buffer[0..n]); | |
| 1288 | offset += n; | |
| 1314 | 1289 | } |
| 1315 | 1290 | hasher.final(bin_digest); |
| 1316 | 1291 | } |
| 1317 | 1292 | |
| 1318 | 1293 | // Create/Write a file, close it, then grab its stat.mtime timestamp. |
| 1319 | fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp { | |
| 1294 | fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp { | |
| 1320 | 1295 | const test_out_file = "test-filetimestamp.tmp"; |
| 1321 | 1296 | |
| 1322 | var file = try dir.createFile(test_out_file, .{ | |
| 1297 | var file = try dir.createFile(io, test_out_file, .{ | |
| 1323 | 1298 | .read = true, |
| 1324 | 1299 | .truncate = true, |
| 1325 | 1300 | }); |
| 1326 | 1301 | defer { |
| 1327 | file.close(); | |
| 1328 | dir.deleteFile(test_out_file) catch {}; | |
| 1302 | file.close(io); | |
| 1303 | dir.deleteFile(io, test_out_file) catch {}; | |
| 1329 | 1304 | } |
| 1330 | 1305 | |
| 1331 | return (try file.stat()).mtime; | |
| 1306 | return (try file.stat(io)).mtime; | |
| 1332 | 1307 | } |
| 1333 | 1308 | |
| 1334 | 1309 | test "cache file and then recall it" { |
| ... | ... | @@ -1340,11 +1315,11 @@ test "cache file and then recall it" { |
| 1340 | 1315 | const temp_file = "test.txt"; |
| 1341 | 1316 | const temp_manifest_dir = "temp_manifest_dir"; |
| 1342 | 1317 | |
| 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" }); | |
| 1344 | 1319 | |
| 1345 | 1320 | // Wait for file timestamps to tick |
| 1346 | const initial_time = try testGetCurrentFileTimestamp(tmp.dir); | |
| 1347 | while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) { | |
| 1321 | const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); | |
| 1322 | while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) { | |
| 1348 | 1323 | try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); |
| 1349 | 1324 | } |
| 1350 | 1325 | |
| ... | ... | @@ -1355,10 +1330,10 @@ test "cache file and then recall it" { |
| 1355 | 1330 | var cache: Cache = .{ |
| 1356 | 1331 | .io = io, |
| 1357 | 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 | 1335 | cache.addPrefix(.{ .path = null, .handle = tmp.dir }); |
| 1361 | defer cache.manifest_dir.close(); | |
| 1336 | defer cache.manifest_dir.close(io); | |
| 1362 | 1337 | |
| 1363 | 1338 | { |
| 1364 | 1339 | var ch = cache.obtain(); |
| ... | ... | @@ -1406,11 +1381,11 @@ test "check that changing a file makes cache fail" { |
| 1406 | 1381 | const original_temp_file_contents = "Hello, world!\n"; |
| 1407 | 1382 | const updated_temp_file_contents = "Hello, world; but updated!\n"; |
| 1408 | 1383 | |
| 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 }); | |
| 1410 | 1385 | |
| 1411 | 1386 | // Wait for file timestamps to tick |
| 1412 | const initial_time = try testGetCurrentFileTimestamp(tmp.dir); | |
| 1413 | while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) { | |
| 1387 | const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); | |
| 1388 | while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) { | |
| 1414 | 1389 | try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); |
| 1415 | 1390 | } |
| 1416 | 1391 | |
| ... | ... | @@ -1421,10 +1396,10 @@ test "check that changing a file makes cache fail" { |
| 1421 | 1396 | var cache: Cache = .{ |
| 1422 | 1397 | .io = io, |
| 1423 | 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 | 1401 | cache.addPrefix(.{ .path = null, .handle = tmp.dir }); |
| 1427 | defer cache.manifest_dir.close(); | |
| 1402 | defer cache.manifest_dir.close(io); | |
| 1428 | 1403 | |
| 1429 | 1404 | { |
| 1430 | 1405 | var ch = cache.obtain(); |
| ... | ... | @@ -1443,7 +1418,7 @@ test "check that changing a file makes cache fail" { |
| 1443 | 1418 | try ch.writeManifest(); |
| 1444 | 1419 | } |
| 1445 | 1420 | |
| 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 }); | |
| 1447 | 1422 | |
| 1448 | 1423 | { |
| 1449 | 1424 | var ch = cache.obtain(); |
| ... | ... | @@ -1481,10 +1456,10 @@ test "no file inputs" { |
| 1481 | 1456 | var cache: Cache = .{ |
| 1482 | 1457 | .io = io, |
| 1483 | 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 | 1461 | cache.addPrefix(.{ .path = null, .handle = tmp.dir }); |
| 1487 | defer cache.manifest_dir.close(); | |
| 1462 | defer cache.manifest_dir.close(io); | |
| 1488 | 1463 | |
| 1489 | 1464 | { |
| 1490 | 1465 | var man = cache.obtain(); |
| ... | ... | @@ -1523,12 +1498,12 @@ test "Manifest with files added after initial hash work" { |
| 1523 | 1498 | const temp_file2 = "cache_hash_post_file_test2.txt"; |
| 1524 | 1499 | const temp_manifest_dir = "cache_hash_post_file_manifest_dir"; |
| 1525 | 1500 | |
| 1526 | try tmp.dir.writeFile(.{ .sub_path = temp_file1, .data = "Hello, world!\n" }); | |
| 1527 | try tmp.dir.writeFile(.{ .sub_path = temp_file2, .data = "Hello world the second!\n" }); | |
| 1501 | try tmp.dir.writeFile(io, .{ .sub_path = temp_file1, .data = "Hello, world!\n" }); | |
| 1502 | try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second!\n" }); | |
| 1528 | 1503 | |
| 1529 | 1504 | // Wait for file timestamps to tick |
| 1530 | const initial_time = try testGetCurrentFileTimestamp(tmp.dir); | |
| 1531 | while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) { | |
| 1505 | const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); | |
| 1506 | while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) { | |
| 1532 | 1507 | try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); |
| 1533 | 1508 | } |
| 1534 | 1509 | |
| ... | ... | @@ -1540,10 +1515,10 @@ test "Manifest with files added after initial hash work" { |
| 1540 | 1515 | var cache: Cache = .{ |
| 1541 | 1516 | .io = io, |
| 1542 | 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 | 1520 | cache.addPrefix(.{ .path = null, .handle = tmp.dir }); |
| 1546 | defer cache.manifest_dir.close(); | |
| 1521 | defer cache.manifest_dir.close(io); | |
| 1547 | 1522 | |
| 1548 | 1523 | { |
| 1549 | 1524 | var ch = cache.obtain(); |
| ... | ... | @@ -1575,11 +1550,11 @@ test "Manifest with files added after initial hash work" { |
| 1575 | 1550 | try testing.expect(mem.eql(u8, &digest1, &digest2)); |
| 1576 | 1551 | |
| 1577 | 1552 | // 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" }); | |
| 1579 | 1554 | |
| 1580 | 1555 | // Wait for file timestamps to tick |
| 1581 | const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir); | |
| 1582 | while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time2.nanoseconds) { | |
| 1556 | const initial_time2 = try testGetCurrentFileTimestamp(io, tmp.dir); | |
| 1557 | while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time2.nanoseconds) { | |
| 1583 | 1558 | try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); |
| 1584 | 1559 | } |
| 1585 | 1560 |
lib/std/Build/Cache/Directory.zig+8-6| ... | ... | @@ -1,7 +1,9 @@ |
| 1 | 1 | const Directory = @This(); |
| 2 | ||
| 2 | 3 | const std = @import("../../std.zig"); |
| 3 | const assert = std.debug.assert; | |
| 4 | const Io = std.Io; | |
| 4 | 5 | const fs = std.fs; |
| 6 | const assert = std.debug.assert; | |
| 5 | 7 | const fmt = std.fmt; |
| 6 | 8 | const Allocator = std.mem.Allocator; |
| 7 | 9 | |
| ... | ... | @@ -9,7 +11,7 @@ const Allocator = std.mem.Allocator; |
| 9 | 11 | /// directly, but it is needed when passing the directory to a child process. |
| 10 | 12 | /// `null` means cwd. |
| 11 | 13 | path: ?[]const u8, |
| 12 | handle: fs.Dir, | |
| 14 | handle: Io.Dir, | |
| 13 | 15 | |
| 14 | 16 | pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory { |
| 15 | 17 | return .{ |
| ... | ... | @@ -21,7 +23,7 @@ pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory { |
| 21 | 23 | pub fn cwd() Directory { |
| 22 | 24 | return .{ |
| 23 | 25 | .path = null, |
| 24 | .handle = fs.cwd(), | |
| 26 | .handle = .cwd(), | |
| 25 | 27 | }; |
| 26 | 28 | } |
| 27 | 29 | |
| ... | ... | @@ -50,8 +52,8 @@ pub fn joinZ(self: Directory, allocator: Allocator, paths: []const []const u8) ! |
| 50 | 52 | /// Whether or not the handle should be closed, or the path should be freed |
| 51 | 53 | /// is determined by usage, however this function is provided for convenience |
| 52 | 54 | /// if it happens to be what the caller needs. |
| 53 | pub fn closeAndFree(self: *Directory, gpa: Allocator) void { | |
| 54 | self.handle.close(); | |
| 55 | pub fn closeAndFree(self: *Directory, gpa: Allocator, io: Io) void { | |
| 56 | self.handle.close(io); | |
| 55 | 57 | if (self.path) |p| gpa.free(p); |
| 56 | 58 | self.* = undefined; |
| 57 | 59 | } |
| ... | ... | @@ -64,5 +66,5 @@ pub fn format(self: Directory, writer: *std.Io.Writer) std.Io.Writer.Error!void |
| 64 | 66 | } |
| 65 | 67 | |
| 66 | 68 | pub 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 | 2 | |
| 3 | 3 | const std = @import("../../std.zig"); |
| 4 | 4 | const Io = std.Io; |
| 5 | const assert = std.debug.assert; | |
| 6 | 5 | const fs = std.fs; |
| 6 | const assert = std.debug.assert; | |
| 7 | 7 | const Allocator = std.mem.Allocator; |
| 8 | 8 | const Cache = std.Build.Cache; |
| 9 | 9 | |
| ... | ... | @@ -59,58 +59,56 @@ pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Erro |
| 59 | 59 | return p.root_dir.joinZ(gpa, parts); |
| 60 | 60 | } |
| 61 | 61 | |
| 62 | pub fn openFile( | |
| 63 | p: Path, | |
| 64 | sub_path: []const u8, | |
| 65 | flags: fs.File.OpenFlags, | |
| 66 | ) !fs.File { | |
| 62 | pub fn openFile(p: Path, io: Io, sub_path: []const u8, flags: Io.File.OpenFlags) !Io.File { | |
| 67 | 63 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 68 | 64 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| 69 | 65 | break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ |
| 70 | 66 | p.sub_path, sub_path, |
| 71 | 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 | } |
| 75 | 71 | |
| 76 | 72 | pub fn openDir( |
| 77 | 73 | p: Path, |
| 74 | io: Io, | |
| 78 | 75 | sub_path: []const u8, |
| 79 | args: fs.Dir.OpenOptions, | |
| 80 | ) fs.Dir.OpenError!fs.Dir { | |
| 76 | args: Io.Dir.OpenOptions, | |
| 77 | ) Io.Dir.OpenError!Io.Dir { | |
| 81 | 78 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 82 | 79 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| 83 | 80 | break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ |
| 84 | 81 | p.sub_path, sub_path, |
| 85 | 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 | } |
| 89 | 86 | |
| 90 | pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs.Dir { | |
| 87 | pub fn createDirPathOpen(p: Path, io: Io, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io.Dir { | |
| 91 | 88 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 92 | 89 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| 93 | 90 | break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ |
| 94 | 91 | p.sub_path, sub_path, |
| 95 | 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 | } |
| 99 | 96 | |
| 100 | pub fn statFile(p: Path, sub_path: []const u8) !fs.Dir.Stat { | |
| 97 | pub fn statFile(p: Path, io: Io, sub_path: []const u8) !Io.Dir.Stat { | |
| 101 | 98 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 102 | 99 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| 103 | 100 | break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ |
| 104 | 101 | p.sub_path, sub_path, |
| 105 | 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 | } |
| 109 | 106 | |
| 110 | 107 | pub fn atomicFile( |
| 111 | 108 | p: Path, |
| 109 | io: Io, | |
| 112 | 110 | sub_path: []const u8, |
| 113 | options: fs.Dir.AtomicFileOptions, | |
| 111 | options: Io.Dir.AtomicFileOptions, | |
| 114 | 112 | buf: *[fs.max_path_bytes]u8, |
| 115 | 113 | ) !fs.AtomicFile { |
| 116 | 114 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| ... | ... | @@ -118,27 +116,27 @@ pub fn atomicFile( |
| 118 | 116 | p.sub_path, sub_path, |
| 119 | 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 | } |
| 123 | 121 | |
| 124 | pub fn access(p: Path, sub_path: []const u8, flags: Io.Dir.AccessOptions) !void { | |
| 122 | pub fn access(p: Path, io: Io, sub_path: []const u8, flags: Io.Dir.AccessOptions) !void { | |
| 125 | 123 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 126 | 124 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| 127 | 125 | break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ |
| 128 | 126 | p.sub_path, sub_path, |
| 129 | 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 | } |
| 133 | 131 | |
| 134 | pub fn makePath(p: Path, sub_path: []const u8) !void { | |
| 132 | pub fn createDirPath(p: Path, io: Io, sub_path: []const u8) !void { | |
| 135 | 133 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 136 | 134 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| 137 | 135 | break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ |
| 138 | 136 | p.sub_path, sub_path, |
| 139 | 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 | } |
| 143 | 141 | |
| 144 | 142 | pub 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 | 178 | } |
| 181 | 179 | |
| 182 | 180 | pub 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 | 182 | try writer.writeAll(self.sub_path); |
| 185 | 183 | return; |
| 186 | 184 | } |
| ... | ... | @@ -225,9 +223,9 @@ pub const TableAdapter = struct { |
| 225 | 223 | |
| 226 | 224 | pub fn hash(self: TableAdapter, a: Cache.Path) u32 { |
| 227 | 225 | _ = self; |
| 228 | const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.fd))) { | |
| 229 | .pointer => @intFromPtr(a.root_dir.handle.fd), | |
| 230 | .int => @as(u32, @bitCast(a.root_dir.handle.fd)), | |
| 226 | const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.handle))) { | |
| 227 | .pointer => @intFromPtr(a.root_dir.handle.handle), | |
| 228 | .int => @as(u32, @bitCast(a.root_dir.handle.handle)), | |
| 231 | 229 | else => @compileError("unimplemented hash function"), |
| 232 | 230 | }; |
| 233 | 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 | 9 | const log = std.log; |
| 10 | 10 | const Coverage = std.debug.Coverage; |
| 11 | 11 | const abi = Build.abi.fuzz; |
| 12 | const tty = std.Io.tty; | |
| 13 | 12 | |
| 14 | 13 | const Fuzz = @This(); |
| 15 | 14 | const build_runner = @import("root"); |
| 16 | 15 | |
| 17 | 16 | gpa: Allocator, |
| 18 | 17 | io: Io, |
| 19 | ttyconf: tty.Config, | |
| 20 | 18 | mode: Mode, |
| 21 | 19 | |
| 22 | 20 | /// Allocated into `gpa`. |
| ... | ... | @@ -77,7 +75,6 @@ const CoverageMap = struct { |
| 77 | 75 | pub fn init( |
| 78 | 76 | gpa: Allocator, |
| 79 | 77 | io: Io, |
| 80 | ttyconf: tty.Config, | |
| 81 | 78 | all_steps: []const *Build.Step, |
| 82 | 79 | root_prog_node: std.Progress.Node, |
| 83 | 80 | mode: Mode, |
| ... | ... | @@ -95,7 +92,7 @@ pub fn init( |
| 95 | 92 | if (run.producer == null) continue; |
| 96 | 93 | if (run.fuzz_tests.items.len == 0) continue; |
| 97 | 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 | } |
| 100 | 97 | |
| 101 | 98 | if (steps.items.len == 0) fatal("no fuzz tests found", .{}); |
| ... | ... | @@ -115,7 +112,6 @@ pub fn init( |
| 115 | 112 | return .{ |
| 116 | 113 | .gpa = gpa, |
| 117 | 114 | .io = io, |
| 118 | .ttyconf = ttyconf, | |
| 119 | 115 | .mode = mode, |
| 120 | 116 | .run_steps = run_steps, |
| 121 | 117 | .group = .init, |
| ... | ... | @@ -154,14 +150,16 @@ pub fn deinit(fuzz: *Fuzz) void { |
| 154 | 150 | fuzz.gpa.free(fuzz.run_steps); |
| 155 | 151 | } |
| 156 | 152 | |
| 157 | fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, ttyconf: tty.Config, parent_prog_node: std.Progress.Node) void { | |
| 158 | rebuildTestsWorkerRunFallible(run, gpa, ttyconf, parent_prog_node) catch |err| { | |
| 153 | fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) void { | |
| 154 | rebuildTestsWorkerRunFallible(run, gpa, parent_prog_node) catch |err| { | |
| 159 | 155 | const compile = run.producer.?; |
| 160 | 156 | log.err("step '{s}': failed to rebuild in fuzz mode: {t}", .{ compile.step.name, err }); |
| 161 | 157 | }; |
| 162 | 158 | } |
| 163 | 159 | |
| 164 | fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: tty.Config, parent_prog_node: std.Progress.Node) !void { | |
| 160 | fn 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 | 163 | const compile = run.producer.?; |
| 166 | 164 | const prog_node = parent_prog_node.start(compile.step.name, 0); |
| 167 | 165 | defer prog_node.end(); |
| ... | ... | @@ -174,9 +172,9 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: tty.Co |
| 174 | 172 | |
| 175 | 173 | if (show_error_msgs or show_compile_errors or show_stderr) { |
| 176 | 174 | var buf: [256]u8 = undefined; |
| 177 | const w, _ = std.debug.lockStderrWriter(&buf); | |
| 178 | defer std.debug.unlockStderrWriter(); | |
| 179 | build_runner.printErrorMessages(gpa, &compile.step, .{}, w, ttyconf, .verbose, .indent) catch {}; | |
| 175 | const stderr = try io.lockStderr(&buf, graph.stderr_mode); | |
| 176 | defer io.unlockStderr(); | |
| 177 | build_runner.printErrorMessages(gpa, &compile.step, .{}, stderr.terminal(), .verbose, .indent) catch {}; | |
| 180 | 178 | } |
| 181 | 179 | |
| 182 | 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 | 184 | run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename); |
| 187 | 185 | } |
| 188 | 186 | |
| 189 | fn fuzzWorkerRun( | |
| 190 | fuzz: *Fuzz, | |
| 191 | run: *Step.Run, | |
| 192 | unit_test_index: u32, | |
| 193 | ) void { | |
| 194 | const gpa = run.step.owner.allocator; | |
| 187 | fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_index: u32) void { | |
| 188 | const owner = run.step.owner; | |
| 189 | const gpa = owner.allocator; | |
| 190 | const graph = owner.graph; | |
| 191 | const io = graph.io; | |
| 195 | 192 | const test_name = run.cached_test_metadata.?.testName(unit_test_index); |
| 196 | 193 | |
| 197 | 194 | const prog_node = fuzz.prog_node.start(test_name, 0); |
| ... | ... | @@ -200,9 +197,11 @@ fn fuzzWorkerRun( |
| 200 | 197 | run.rerunInFuzzMode(fuzz, unit_test_index, prog_node) catch |err| switch (err) { |
| 201 | 198 | error.MakeFailed => { |
| 202 | 199 | var buf: [256]u8 = undefined; |
| 203 | const w, _ = std.debug.lockStderrWriter(&buf); | |
| 204 | defer std.debug.unlockStderrWriter(); | |
| 205 | build_runner.printErrorMessages(gpa, &run.step, .{}, w, fuzz.ttyconf, .verbose, .indent) catch {}; | |
| 200 | const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) { | |
| 201 | error.Canceled => return, | |
| 202 | }; | |
| 203 | defer io.unlockStderr(); | |
| 204 | build_runner.printErrorMessages(gpa, &run.step, .{}, stderr.terminal(), .verbose, .indent) catch {}; | |
| 206 | 205 | return; |
| 207 | 206 | }, |
| 208 | 207 | else => { |
| ... | ... | @@ -360,12 +359,13 @@ fn coverageRunCancelable(fuzz: *Fuzz) Io.Cancelable!void { |
| 360 | 359 | fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutOfMemory, AlreadyReported, Canceled }!void { |
| 361 | 360 | assert(fuzz.mode == .forever); |
| 362 | 361 | const ws = fuzz.mode.forever.ws; |
| 362 | const gpa = fuzz.gpa; | |
| 363 | 363 | const io = fuzz.io; |
| 364 | 364 | |
| 365 | 365 | try fuzz.coverage_mutex.lock(io); |
| 366 | 366 | defer fuzz.coverage_mutex.unlock(io); |
| 367 | 367 | |
| 368 | const gop = try fuzz.coverage_files.getOrPut(fuzz.gpa, coverage_id); | |
| 368 | const gop = try fuzz.coverage_files.getOrPut(gpa, coverage_id); | |
| 369 | 369 | if (gop.found_existing) { |
| 370 | 370 | // We are fuzzing the same executable with multiple threads. |
| 371 | 371 | // 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 | 383 | .entry_points = .{}, |
| 384 | 384 | .start_timestamp = ws.now(), |
| 385 | 385 | }; |
| 386 | errdefer gop.value_ptr.coverage.deinit(fuzz.gpa); | |
| 386 | errdefer gop.value_ptr.coverage.deinit(gpa); | |
| 387 | 387 | |
| 388 | 388 | const rebuilt_exe_path = run_step.rebuilt_executable.?; |
| 389 | 389 | const target = run_step.producer.?.rootModuleTarget(); |
| 390 | 390 | var debug_info = std.debug.Info.load( |
| 391 | fuzz.gpa, | |
| 391 | gpa, | |
| 392 | io, | |
| 392 | 393 | rebuilt_exe_path, |
| 393 | 394 | &gop.value_ptr.coverage, |
| 394 | 395 | target.ofmt, |
| ... | ... | @@ -399,21 +400,21 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO |
| 399 | 400 | }); |
| 400 | 401 | return error.AlreadyReported; |
| 401 | 402 | }; |
| 402 | defer debug_info.deinit(fuzz.gpa); | |
| 403 | defer debug_info.deinit(gpa); | |
| 403 | 404 | |
| 404 | 405 | const coverage_file_path: Build.Cache.Path = .{ |
| 405 | 406 | .root_dir = run_step.step.owner.cache_root, |
| 406 | 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 | 410 | log.err("step '{s}': failed to load coverage file '{f}': {t}", .{ |
| 410 | 411 | run_step.step.name, coverage_file_path, err, |
| 411 | 412 | }); |
| 412 | 413 | return error.AlreadyReported; |
| 413 | 414 | }; |
| 414 | defer coverage_file.close(); | |
| 415 | defer coverage_file.close(io); | |
| 415 | 416 | |
| 416 | const file_size = coverage_file.getEndPos() catch |err| { | |
| 417 | const file_size = coverage_file.length(io) catch |err| { | |
| 417 | 418 | log.err("unable to check len of coverage file '{f}': {t}", .{ coverage_file_path, err }); |
| 418 | 419 | return error.AlreadyReported; |
| 419 | 420 | }; |
| ... | ... | @@ -433,14 +434,14 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO |
| 433 | 434 | |
| 434 | 435 | const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); |
| 435 | 436 | const pcs = header.pcAddrs(); |
| 436 | const source_locations = try fuzz.gpa.alloc(Coverage.SourceLocation, pcs.len); | |
| 437 | errdefer fuzz.gpa.free(source_locations); | |
| 437 | const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len); | |
| 438 | errdefer gpa.free(source_locations); | |
| 438 | 439 | |
| 439 | 440 | // Unfortunately the PCs array that LLVM gives us from the 8-bit PC |
| 440 | 441 | // counters feature is not sorted. |
| 441 | 442 | var sorted_pcs: std.MultiArrayList(struct { pc: u64, index: u32, sl: Coverage.SourceLocation }) = .{}; |
| 442 | defer sorted_pcs.deinit(fuzz.gpa); | |
| 443 | try sorted_pcs.resize(fuzz.gpa, pcs.len); | |
| 443 | defer sorted_pcs.deinit(gpa); | |
| 444 | try sorted_pcs.resize(gpa, pcs.len); | |
| 444 | 445 | @memcpy(sorted_pcs.items(.pc), pcs); |
| 445 | 446 | for (sorted_pcs.items(.index), 0..) |*v, i| v.* = @intCast(i); |
| 446 | 447 | sorted_pcs.sortUnstable(struct { |
| ... | ... | @@ -451,7 +452,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO |
| 451 | 452 | } |
| 452 | 453 | }{ .addrs = sorted_pcs.items(.pc) }); |
| 453 | 454 | |
| 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 | 456 | log.err("failed to resolve addresses to source locations: {t}", .{err}); |
| 456 | 457 | return error.AlreadyReported; |
| 457 | 458 | }; |
| ... | ... | @@ -528,12 +529,12 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void { |
| 528 | 529 | .root_dir = cov.run.step.owner.cache_root, |
| 529 | 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 | 533 | fatal("step '{s}': failed to load coverage file '{f}': {t}", .{ |
| 533 | 534 | cov.run.step.name, coverage_file_path, err, |
| 534 | 535 | }); |
| 535 | 536 | }; |
| 536 | defer coverage_file.close(); | |
| 537 | defer coverage_file.close(io); | |
| 537 | 538 | |
| 538 | 539 | const fuzz_abi = std.Build.abi.fuzz; |
| 539 | 540 | var rbuf: [0x1000]u8 = undefined; |
lib/std/Build/Step.zig+29-29| ... | ... | @@ -117,7 +117,6 @@ pub const MakeOptions = struct { |
| 117 | 117 | // it currently breaks because `std.net.Address` doesn't work there. Work around for now. |
| 118 | 118 | .wasm32 => void, |
| 119 | 119 | }, |
| 120 | ttyconf: std.Io.tty.Config, | |
| 121 | 120 | /// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds. |
| 122 | 121 | unit_test_timeout_ns: ?u64, |
| 123 | 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 | 328 | } |
| 330 | 329 | |
| 331 | 330 | /// For debugging purposes, prints identifying information about this Step. |
| 332 | pub fn dump(step: *Step, w: *Io.Writer, tty_config: Io.tty.Config) void { | |
| 331 | pub fn dump(step: *Step, t: Io.Terminal) void { | |
| 332 | const w = t.writer; | |
| 333 | 333 | if (step.debug_stack_trace.instruction_addresses.len > 0) { |
| 334 | 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 | 336 | } else { |
| 337 | 337 | const field = "debug_stack_frames_count"; |
| 338 | 338 | comptime assert(@hasField(Build, field)); |
| 339 | tty_config.setColor(w, .yellow) catch {}; | |
| 339 | t.setColor(.yellow) catch {}; | |
| 340 | 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 | } |
| 344 | 344 | |
| ... | ... | @@ -350,6 +350,7 @@ pub fn captureChildProcess( |
| 350 | 350 | argv: []const []const u8, |
| 351 | 351 | ) !std.process.Child.RunResult { |
| 352 | 352 | const arena = s.owner.allocator; |
| 353 | const io = s.owner.graph.io; | |
| 353 | 354 | |
| 354 | 355 | // If an error occurs, it's happened in this command: |
| 355 | 356 | assert(s.result_failed_command == null); |
| ... | ... | @@ -358,8 +359,7 @@ pub fn captureChildProcess( |
| 358 | 359 | try handleChildProcUnsupported(s); |
| 359 | 360 | try handleVerbose(s.owner, null, argv); |
| 360 | 361 | |
| 361 | const result = std.process.Child.run(.{ | |
| 362 | .allocator = arena, | |
| 362 | const result = std.process.Child.run(arena, io, .{ | |
| 363 | 363 | .argv = argv, |
| 364 | 364 | .progress_node = progress_node, |
| 365 | 365 | }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); |
| ... | ... | @@ -401,6 +401,9 @@ pub fn evalZigProcess( |
| 401 | 401 | web_server: ?*Build.WebServer, |
| 402 | 402 | gpa: Allocator, |
| 403 | 403 | ) !?Path { |
| 404 | const b = s.owner; | |
| 405 | const io = b.graph.io; | |
| 406 | ||
| 404 | 407 | // If an error occurs, it's happened in this command: |
| 405 | 408 | assert(s.result_failed_command == null); |
| 406 | 409 | s.result_failed_command = try allocPrintCmd(gpa, null, argv); |
| ... | ... | @@ -411,7 +414,7 @@ pub fn evalZigProcess( |
| 411 | 414 | const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) { |
| 412 | 415 | error.BrokenPipe => { |
| 413 | 416 | // Process restart required. |
| 414 | const term = zp.child.wait() catch |e| { | |
| 417 | const term = zp.child.wait(io) catch |e| { | |
| 415 | 418 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); |
| 416 | 419 | }; |
| 417 | 420 | _ = term; |
| ... | ... | @@ -427,7 +430,7 @@ pub fn evalZigProcess( |
| 427 | 430 | |
| 428 | 431 | if (s.result_error_msgs.items.len > 0 and result == null) { |
| 429 | 432 | // Crash detected. |
| 430 | const term = zp.child.wait() catch |e| { | |
| 433 | const term = zp.child.wait(io) catch |e| { | |
| 431 | 434 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); |
| 432 | 435 | }; |
| 433 | 436 | s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; |
| ... | ... | @@ -439,7 +442,6 @@ pub fn evalZigProcess( |
| 439 | 442 | return result; |
| 440 | 443 | } |
| 441 | 444 | assert(argv.len != 0); |
| 442 | const b = s.owner; | |
| 443 | 445 | const arena = b.allocator; |
| 444 | 446 | |
| 445 | 447 | try handleChildProcUnsupported(s); |
| ... | ... | @@ -453,7 +455,7 @@ pub fn evalZigProcess( |
| 453 | 455 | child.request_resource_usage_statistics = true; |
| 454 | 456 | child.progress_node = prog_node; |
| 455 | 457 | |
| 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 }); | |
| 457 | 459 | |
| 458 | 460 | const zp = try gpa.create(ZigProcess); |
| 459 | 461 | zp.* = .{ |
| ... | ... | @@ -474,10 +476,10 @@ pub fn evalZigProcess( |
| 474 | 476 | |
| 475 | 477 | if (!watch) { |
| 476 | 478 | // Send EOF to stdin. |
| 477 | zp.child.stdin.?.close(); | |
| 479 | zp.child.stdin.?.close(io); | |
| 478 | 480 | zp.child.stdin = null; |
| 479 | 481 | |
| 480 | const term = zp.child.wait() catch |err| { | |
| 482 | const term = zp.child.wait(io) catch |err| { | |
| 481 | 483 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], err }); |
| 482 | 484 | }; |
| 483 | 485 | s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; |
| ... | ... | @@ -504,36 +506,34 @@ pub fn evalZigProcess( |
| 504 | 506 | return result; |
| 505 | 507 | } |
| 506 | 508 | |
| 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. | |
| 508 | 510 | pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !Io.Dir.PrevStatus { |
| 509 | 511 | const b = s.owner; |
| 510 | 512 | const io = b.graph.io; |
| 511 | 513 | const src_path = src_lazy_path.getPath3(b, s); |
| 512 | 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| { | |
| 514 | return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ | |
| 515 | src_path, dest_path, err, | |
| 516 | }); | |
| 517 | }; | |
| 515 | return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| | |
| 516 | return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err }); | |
| 518 | 517 | } |
| 519 | 518 | |
| 520 | /// Wrapper around `std.fs.Dir.makePathStatus` that handles verbose and error output. | |
| 521 | pub fn installDir(s: *Step, dest_path: []const u8) !std.fs.Dir.MakePathStatus { | |
| 519 | /// Wrapper around `Io.Dir.createDirPathStatus` that handles verbose and error output. | |
| 520 | pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.CreatePathStatus { | |
| 522 | 521 | const b = s.owner; |
| 522 | const io = b.graph.io; | |
| 523 | 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 | 525 | return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err }); |
| 526 | }; | |
| 527 | 526 | } |
| 528 | 527 | |
| 529 | 528 | fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.WebServer, gpa: Allocator) !?Path { |
| 530 | 529 | const b = s.owner; |
| 531 | 530 | const arena = b.allocator; |
| 531 | const io = b.graph.io; | |
| 532 | 532 | |
| 533 | 533 | var timer = try std.time.Timer.start(); |
| 534 | 534 | |
| 535 | try sendMessage(zp.child.stdin.?, .update); | |
| 536 | if (!watch) try sendMessage(zp.child.stdin.?, .exit); | |
| 535 | try sendMessage(io, zp.child.stdin.?, .update); | |
| 536 | if (!watch) try sendMessage(io, zp.child.stdin.?, .exit); | |
| 537 | 537 | |
| 538 | 538 | var result: ?Path = null; |
| 539 | 539 | |
| ... | ... | @@ -670,12 +670,12 @@ fn clearZigProcess(s: *Step, gpa: Allocator) void { |
| 670 | 670 | } |
| 671 | 671 | } |
| 672 | 672 | |
| 673 | fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { | |
| 673 | fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { | |
| 674 | 674 | const header: std.zig.Client.Message.Header = .{ |
| 675 | 675 | .tag = tag, |
| 676 | 676 | .bytes_len = 0, |
| 677 | 677 | }; |
| 678 | var w = file.writer(&.{}); | |
| 678 | var w = file.writer(io, &.{}); | |
| 679 | 679 | w.interface.writeStruct(header, .little) catch |err| switch (err) { |
| 680 | 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 | 898 | try addWatchInputFromPath(step, .{ |
| 899 | 899 | .root_dir = .{ |
| 900 | 900 | .path = null, |
| 901 | .handle = std.fs.cwd(), | |
| 901 | .handle = Io.Dir.cwd(), | |
| 902 | 902 | }, |
| 903 | 903 | .sub_path = std.fs.path.dirname(path_string) orelse "", |
| 904 | 904 | }, std.fs.path.basename(path_string)); |
| ... | ... | @@ -923,7 +923,7 @@ pub fn addDirectoryWatchInput(step: *Step, lazy_directory: Build.LazyPath) Alloc |
| 923 | 923 | try addDirectoryWatchInputFromPath(step, .{ |
| 924 | 924 | .root_dir = .{ |
| 925 | 925 | .path = null, |
| 926 | .handle = std.fs.cwd(), | |
| 926 | .handle = Io.Dir.cwd(), | |
| 927 | 927 | }, |
| 928 | 928 | .sub_path = path_string, |
| 929 | 929 | }); |
lib/std/Build/Step/CheckFile.zig+4-1| ... | ... | @@ -3,7 +3,9 @@ |
| 3 | 3 | //! TODO: generalize the code in std.testing.expectEqualStrings and make this |
| 4 | 4 | //! CheckFile step produce those helpful diagnostics when there is not a match. |
| 5 | 5 | const CheckFile = @This(); |
| 6 | ||
| 6 | 7 | const std = @import("std"); |
| 8 | const Io = std.Io; | |
| 7 | 9 | const Step = std.Build.Step; |
| 8 | 10 | const fs = std.fs; |
| 9 | 11 | const mem = std.mem; |
| ... | ... | @@ -49,11 +51,12 @@ pub fn setName(check_file: *CheckFile, name: []const u8) void { |
| 49 | 51 | fn make(step: *Step, options: Step.MakeOptions) !void { |
| 50 | 52 | _ = options; |
| 51 | 53 | const b = step.owner; |
| 54 | const io = b.graph.io; | |
| 52 | 55 | const check_file: *CheckFile = @fieldParentPtr("step", step); |
| 53 | 56 | try step.singleUnchangingWatchInput(check_file.source); |
| 54 | 57 | |
| 55 | 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 | 60 | return step.fail("unable to read '{s}': {s}", .{ |
| 58 | 61 | src_path, @errorName(err), |
| 59 | 62 | }); |
lib/std/Build/Step/CheckObject.zig+2| ... | ... | @@ -547,12 +547,14 @@ pub fn checkComputeCompare( |
| 547 | 547 | fn make(step: *Step, make_options: Step.MakeOptions) !void { |
| 548 | 548 | _ = make_options; |
| 549 | 549 | const b = step.owner; |
| 550 | const io = b.graph.io; | |
| 550 | 551 | const gpa = b.allocator; |
| 551 | 552 | const check_object: *CheckObject = @fieldParentPtr("step", step); |
| 552 | 553 | try step.singleUnchangingWatchInput(check_object.source); |
| 553 | 554 | |
| 554 | 555 | const src_path = check_object.source.getPath3(b, step); |
| 555 | 556 | const contents = src_path.root_dir.handle.readFileAllocOptions( |
| 557 | io, | |
| 556 | 558 | src_path.sub_path, |
| 557 | 559 | gpa, |
| 558 | 560 | .limited(check_object.max_bytes), |
lib/std/Build/Step/Compile.zig+35-24| ... | ... | @@ -1,12 +1,15 @@ |
| 1 | const Compile = @This(); | |
| 1 | 2 | const builtin = @import("builtin"); |
| 3 | ||
| 2 | 4 | const std = @import("std"); |
| 5 | const Io = std.Io; | |
| 3 | 6 | const mem = std.mem; |
| 4 | 7 | const fs = std.fs; |
| 5 | 8 | const assert = std.debug.assert; |
| 6 | 9 | const panic = std.debug.panic; |
| 7 | 10 | const StringHashMap = std.StringHashMap; |
| 8 | 11 | const Sha256 = std.crypto.hash.sha2.Sha256; |
| 9 | const Allocator = mem.Allocator; | |
| 12 | const Allocator = std.mem.Allocator; | |
| 10 | 13 | const Step = std.Build.Step; |
| 11 | 14 | const LazyPath = std.Build.LazyPath; |
| 12 | 15 | const PkgConfigPkg = std.Build.PkgConfigPkg; |
| ... | ... | @@ -15,7 +18,6 @@ const RunError = std.Build.RunError; |
| 15 | 18 | const Module = std.Build.Module; |
| 16 | 19 | const InstallDir = std.Build.InstallDir; |
| 17 | 20 | const GeneratedFile = std.Build.GeneratedFile; |
| 18 | const Compile = @This(); | |
| 19 | 21 | const Path = std.Build.Cache.Path; |
| 20 | 22 | |
| 21 | 23 | pub const base_id: Step.Id = .compile; |
| ... | ... | @@ -920,20 +922,24 @@ const CliNamedModules = struct { |
| 920 | 922 | } |
| 921 | 923 | }; |
| 922 | 924 | |
| 923 | fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking_step: ?*Step) []const u8 { | |
| 925 | fn 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 | 930 | const maybe_path: ?*GeneratedFile = @field(compile, tag_name); |
| 925 | 931 | |
| 926 | 932 | const generated_file = maybe_path orelse { |
| 927 | const w, const ttyconf = std.debug.lockStderrWriter(&.{}); | |
| 928 | std.Build.dumpBadGetPathHelp(&compile.step, w, ttyconf, compile.step.owner, asking_step) catch {}; | |
| 929 | std.debug.unlockStderrWriter(); | |
| 933 | const stderr = try io.lockStderr(&.{}, graph.stderr_mode); | |
| 934 | std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {}; | |
| 935 | io.unlockStderr(); | |
| 930 | 936 | @panic("missing emit option for " ++ tag_name); |
| 931 | 937 | }; |
| 932 | 938 | |
| 933 | 939 | const path = generated_file.path orelse { |
| 934 | const w, const ttyconf = std.debug.lockStderrWriter(&.{}); | |
| 935 | std.Build.dumpBadGetPathHelp(&compile.step, w, ttyconf, compile.step.owner, asking_step) catch {}; | |
| 936 | std.debug.unlockStderrWriter(); | |
| 940 | const stderr = try io.lockStderr(&.{}, graph.stderr_mode); | |
| 941 | std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {}; | |
| 942 | io.unlockStderr(); | |
| 937 | 943 | @panic(tag_name ++ " is null. Is there a missing step dependency?"); |
| 938 | 944 | }; |
| 939 | 945 | |
| ... | ... | @@ -1147,9 +1153,9 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { |
| 1147 | 1153 | // For everything else, we directly link |
| 1148 | 1154 | // against the library file. |
| 1149 | 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 | 1157 | else |
| 1152 | other.getGeneratedFilePath("generated_bin", &compile.step); | |
| 1158 | try other.getGeneratedFilePath("generated_bin", &compile.step); | |
| 1153 | 1159 | |
| 1154 | 1160 | try zig_args.append(full_path_lib); |
| 1155 | 1161 | total_linker_objects += 1; |
| ... | ... | @@ -1561,19 +1567,22 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { |
| 1561 | 1567 | } |
| 1562 | 1568 | |
| 1563 | 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 | 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 | 1575 | return step.fail("unable to open prefix directory '{s}': {s}", .{ |
| 1567 | 1576 | search_prefix, @errorName(err), |
| 1568 | 1577 | }); |
| 1569 | 1578 | }; |
| 1570 | defer prefix_dir.close(); | |
| 1579 | defer prefix_dir.close(io); | |
| 1571 | 1580 | |
| 1572 | 1581 | // Avoid passing -L and -I flags for nonexistent directories. |
| 1573 | 1582 | // This prevents a warning, that should probably be upgraded to an error in Zig's |
| 1574 | 1583 | // CLI parsing code, when the linker sees an -L directory that does not exist. |
| 1575 | 1584 | |
| 1576 | if (prefix_dir.access("lib", .{})) |_| { | |
| 1585 | if (prefix_dir.access(io, "lib", .{})) |_| { | |
| 1577 | 1586 | try zig_args.appendSlice(&.{ |
| 1578 | 1587 | "-L", b.pathJoin(&.{ search_prefix, "lib" }), |
| 1579 | 1588 | }); |
| ... | ... | @@ -1584,7 +1593,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { |
| 1584 | 1593 | }), |
| 1585 | 1594 | } |
| 1586 | 1595 | |
| 1587 | if (prefix_dir.access("include", .{})) |_| { | |
| 1596 | if (prefix_dir.access(io, "include", .{})) |_| { | |
| 1588 | 1597 | try zig_args.appendSlice(&.{ |
| 1589 | 1598 | "-I", b.pathJoin(&.{ search_prefix, "include" }), |
| 1590 | 1599 | }); |
| ... | ... | @@ -1660,7 +1669,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { |
| 1660 | 1669 | args_length += arg.len + 1; // +1 to account for null terminator |
| 1661 | 1670 | } |
| 1662 | 1671 | if (args_length >= 30 * 1024) { |
| 1663 | try b.cache_root.handle.makePath("args"); | |
| 1672 | try b.cache_root.handle.createDirPath(io, "args"); | |
| 1664 | 1673 | |
| 1665 | 1674 | const args_to_escape = zig_args.items[2..]; |
| 1666 | 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 | 1702 | _ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash}); |
| 1694 | 1703 | |
| 1695 | 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 | 1706 | // The args file is already present from a previous run. |
| 1698 | 1707 | } else |err| switch (err) { |
| 1699 | 1708 | error.FileNotFound => { |
| 1700 | try b.cache_root.handle.makePath("tmp"); | |
| 1709 | try b.cache_root.handle.createDirPath(io, "tmp"); | |
| 1701 | 1710 | const rand_int = std.crypto.random.int(u64); |
| 1702 | 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 }); | |
| 1704 | defer b.cache_root.handle.deleteFile(tmp_path) catch { | |
| 1712 | try b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_path, .data = args }); | |
| 1713 | defer b.cache_root.handle.deleteFile(io, tmp_path) catch { | |
| 1705 | 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 | 1717 | error.PathAlreadyExists => { |
| 1709 | 1718 | // The args file was created by another concurrent build process. |
| 1710 | 1719 | }, |
| ... | ... | @@ -1816,18 +1825,20 @@ pub fn doAtomicSymLinks( |
| 1816 | 1825 | filename_name_only: []const u8, |
| 1817 | 1826 | ) !void { |
| 1818 | 1827 | const b = step.owner; |
| 1828 | const io = b.graph.io; | |
| 1819 | 1829 | const out_dir = fs.path.dirname(output_path) orelse "."; |
| 1820 | 1830 | const out_basename = fs.path.basename(output_path); |
| 1821 | 1831 | // sym link for libfoo.so.1 to libfoo.so.1.2.3 |
| 1822 | 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 | 1835 | return step.fail("unable to symlink {s} -> {s}: {s}", .{ |
| 1825 | 1836 | major_only_path, out_basename, @errorName(err), |
| 1826 | 1837 | }); |
| 1827 | 1838 | }; |
| 1828 | 1839 | // sym link for libfoo.so to libfoo.so.1 |
| 1829 | 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 | 1842 | return step.fail("Unable to symlink {s} -> {s}: {s}", .{ |
| 1832 | 1843 | name_only_path, filename_major_only, @errorName(err), |
| 1833 | 1844 | }); |
| ... | ... | @@ -1897,7 +1908,7 @@ fn checkCompileErrors(compile: *Compile) !void { |
| 1897 | 1908 | try actual_eb.renderToWriter(.{ |
| 1898 | 1909 | .include_reference_trace = false, |
| 1899 | 1910 | .include_source_line = false, |
| 1900 | }, &aw.writer, .no_color); | |
| 1911 | }, &aw.writer); | |
| 1901 | 1912 | break :ae try aw.toOwnedSlice(); |
| 1902 | 1913 | }; |
| 1903 | 1914 |
lib/std/Build/Step/ConfigHeader.zig+8-5| ... | ... | @@ -1,5 +1,7 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const ConfigHeader = @This(); |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 3 | 5 | const Step = std.Build.Step; |
| 4 | 6 | const Allocator = std.mem.Allocator; |
| 5 | 7 | const Writer = std.Io.Writer; |
| ... | ... | @@ -182,6 +184,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 182 | 184 | |
| 183 | 185 | const gpa = b.allocator; |
| 184 | 186 | const arena = b.allocator; |
| 187 | const io = b.graph.io; | |
| 185 | 188 | |
| 186 | 189 | var man = b.graph.cache.obtain(); |
| 187 | 190 | defer man.deinit(); |
| ... | ... | @@ -205,7 +208,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 205 | 208 | .autoconf_undef, .autoconf_at => |file_source| { |
| 206 | 209 | try bw.writeAll(c_generated_line); |
| 207 | 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 | 212 | return step.fail("unable to read autoconf input file '{s}': {s}", .{ |
| 210 | 213 | src_path, @errorName(err), |
| 211 | 214 | }); |
| ... | ... | @@ -219,7 +222,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 219 | 222 | .cmake => |file_source| { |
| 220 | 223 | try bw.writeAll(c_generated_line); |
| 221 | 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 | 226 | return step.fail("unable to read cmake input file '{s}': {s}", .{ |
| 224 | 227 | src_path, @errorName(err), |
| 225 | 228 | }); |
| ... | ... | @@ -255,13 +258,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 255 | 258 | const sub_path = b.pathJoin(&.{ "o", &digest, config_header.include_path }); |
| 256 | 259 | const sub_path_dirname = std.fs.path.dirname(sub_path).?; |
| 257 | 260 | |
| 258 | b.cache_root.handle.makePath(sub_path_dirname) catch |err| { | |
| 261 | b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| { | |
| 259 | 262 | return step.fail("unable to make path '{f}{s}': {s}", .{ |
| 260 | 263 | b.cache_root, sub_path_dirname, @errorName(err), |
| 261 | 264 | }); |
| 262 | 265 | }; |
| 263 | 266 | |
| 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 | 268 | return step.fail("unable to write file '{f}{s}': {s}", .{ |
| 266 | 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 | 119 | _ = options; |
| 120 | 120 | const install_artifact: *InstallArtifact = @fieldParentPtr("step", step); |
| 121 | 121 | const b = step.owner; |
| 122 | const io = b.graph.io; | |
| 122 | 123 | |
| 123 | 124 | var all_cached = true; |
| 124 | 125 | |
| ... | ... | @@ -163,15 +164,15 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 163 | 164 | const src_dir_path = dir.source.getPath3(b, step); |
| 164 | 165 | const full_h_prefix = b.getInstallPath(h_dir, dir.dest_rel_path); |
| 165 | 166 | |
| 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 | 168 | return step.fail("unable to open source directory '{f}': {s}", .{ |
| 168 | 169 | src_dir_path, @errorName(err), |
| 169 | 170 | }); |
| 170 | 171 | }; |
| 171 | defer src_dir.close(); | |
| 172 | defer src_dir.close(io); | |
| 172 | 173 | |
| 173 | 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 | 176 | for (dir.options.exclude_extensions) |ext| { |
| 176 | 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 | 58 | fn make(step: *Step, options: Step.MakeOptions) !void { |
| 59 | 59 | _ = options; |
| 60 | 60 | const b = step.owner; |
| 61 | const io = b.graph.io; | |
| 61 | 62 | const install_dir: *InstallDir = @fieldParentPtr("step", step); |
| 62 | 63 | step.clearWatchInputs(); |
| 63 | 64 | const arena = b.allocator; |
| 64 | 65 | const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir); |
| 65 | 66 | const src_dir_path = install_dir.options.source_dir.getPath3(b, step); |
| 66 | 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 | return step.fail("unable to open source directory '{f}': {s}", .{ | |
| 69 | src_dir_path, @errorName(err), | |
| 70 | }); | |
| 68 | var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { | |
| 69 | return step.fail("unable to open source directory '{f}': {t}", .{ src_dir_path, err }); | |
| 71 | 70 | }; |
| 72 | defer src_dir.close(); | |
| 71 | defer src_dir.close(io); | |
| 73 | 72 | var it = try src_dir.walk(arena); |
| 74 | 73 | var all_cached = true; |
| 75 | next_entry: while (try it.next()) |entry| { | |
| 74 | next_entry: while (try it.next(io)) |entry| { | |
| 76 | 75 | for (install_dir.options.exclude_extensions) |ext| { |
| 77 | 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 | 3 | |
| 4 | 4 | const Allocator = std.mem.Allocator; |
| 5 | 5 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 6 | const File = std.fs.File; | |
| 6 | const File = std.Io.File; | |
| 7 | 7 | const InstallDir = std.Build.InstallDir; |
| 8 | 8 | const Step = std.Build.Step; |
| 9 | 9 | const elf = std.elf; |
| ... | ... | @@ -143,6 +143,7 @@ pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath { |
| 143 | 143 | fn make(step: *Step, options: Step.MakeOptions) !void { |
| 144 | 144 | const prog_node = options.progress_node; |
| 145 | 145 | const b = step.owner; |
| 146 | const io = b.graph.io; | |
| 146 | 147 | const objcopy: *ObjCopy = @fieldParentPtr("step", step); |
| 147 | 148 | try step.singleUnchangingWatchInput(objcopy.input_file); |
| 148 | 149 | |
| ... | ... | @@ -176,7 +177,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 176 | 177 | const cache_path = "o" ++ fs.path.sep_str ++ digest; |
| 177 | 178 | const full_dest_path = try b.cache_root.join(b.allocator, &.{ cache_path, objcopy.basename }); |
| 178 | 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 | 181 | return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) }); |
| 181 | 182 | }; |
| 182 | 183 |
lib/std/Build/Step/Options.zig+28-29| ... | ... | @@ -1,12 +1,13 @@ |
| 1 | const std = @import("std"); | |
| 1 | const Options = @This(); | |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | const Io = std.Io; | |
| 3 | 6 | const fs = std.fs; |
| 4 | 7 | const Step = std.Build.Step; |
| 5 | 8 | const GeneratedFile = std.Build.GeneratedFile; |
| 6 | 9 | const LazyPath = std.Build.LazyPath; |
| 7 | 10 | |
| 8 | const Options = @This(); | |
| 9 | ||
| 10 | 11 | pub const base_id: Step.Id = .options; |
| 11 | 12 | |
| 12 | 13 | step: Step, |
| ... | ... | @@ -441,6 +442,7 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { |
| 441 | 442 | _ = make_options; |
| 442 | 443 | |
| 443 | 444 | const b = step.owner; |
| 445 | const io = b.graph.io; | |
| 444 | 446 | const options: *Options = @fieldParentPtr("step", step); |
| 445 | 447 | |
| 446 | 448 | for (options.args.items) |item| { |
| ... | ... | @@ -468,18 +470,15 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { |
| 468 | 470 | |
| 469 | 471 | // Optimize for the hot path. Stat the file, and if it already exists, |
| 470 | 472 | // cache hit. |
| 471 | if (b.cache_root.handle.access(sub_path, .{})) |_| { | |
| 473 | if (b.cache_root.handle.access(io, sub_path, .{})) |_| { | |
| 472 | 474 | // This is the hot path, success. |
| 473 | 475 | step.result_cached = true; |
| 474 | 476 | return; |
| 475 | 477 | } else |outer_err| switch (outer_err) { |
| 476 | 478 | error.FileNotFound => { |
| 477 | 479 | const sub_dirname = fs.path.dirname(sub_path).?; |
| 478 | b.cache_root.handle.makePath(sub_dirname) catch |e| { | |
| 479 | return step.fail("unable to make path '{f}{s}': {s}", .{ | |
| 480 | b.cache_root, sub_dirname, @errorName(e), | |
| 481 | }); | |
| 482 | }; | |
| 480 | b.cache_root.handle.createDirPath(io, sub_dirname) catch |e| | |
| 481 | return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, sub_dirname, e }); | |
| 483 | 482 | |
| 484 | 483 | const rand_int = std.crypto.random.int(u64); |
| 485 | 484 | const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ |
| ... | ... | @@ -487,40 +486,40 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { |
| 487 | 486 | basename; |
| 488 | 487 | const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?; |
| 489 | 488 | |
| 490 | b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| { | |
| 491 | return step.fail("unable to make temporary directory '{f}{s}': {s}", .{ | |
| 492 | b.cache_root, tmp_sub_path_dirname, @errorName(err), | |
| 489 | b.cache_root.handle.createDirPath(io, tmp_sub_path_dirname) catch |err| { | |
| 490 | return step.fail("unable to make temporary directory '{f}{s}': {t}", .{ | |
| 491 | b.cache_root, tmp_sub_path_dirname, err, | |
| 493 | 492 | }); |
| 494 | 493 | }; |
| 495 | 494 | |
| 496 | b.cache_root.handle.writeFile(.{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| { | |
| 497 | return step.fail("unable to write options to '{f}{s}': {s}", .{ | |
| 498 | b.cache_root, tmp_sub_path, @errorName(err), | |
| 495 | b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| { | |
| 496 | return step.fail("unable to write options to '{f}{s}': {t}", .{ | |
| 497 | b.cache_root, tmp_sub_path, err, | |
| 499 | 498 | }); |
| 500 | 499 | }; |
| 501 | 500 | |
| 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 | 502 | error.PathAlreadyExists => { |
| 504 | 503 | // Other process beat us to it. Clean up the temp file. |
| 505 | b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| { | |
| 506 | try step.addError("warning: unable to delete temp file '{f}{s}': {s}", .{ | |
| 507 | b.cache_root, tmp_sub_path, @errorName(e), | |
| 504 | b.cache_root.handle.deleteFile(io, tmp_sub_path) catch |e| { | |
| 505 | try step.addError("warning: unable to delete temp file '{f}{s}': {t}", .{ | |
| 506 | b.cache_root, tmp_sub_path, e, | |
| 508 | 507 | }); |
| 509 | 508 | }; |
| 510 | 509 | step.result_cached = true; |
| 511 | 510 | return; |
| 512 | 511 | }, |
| 513 | 512 | else => { |
| 514 | return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {s}", .{ | |
| 515 | b.cache_root, tmp_sub_path, | |
| 516 | b.cache_root, sub_path, | |
| 517 | @errorName(err), | |
| 513 | return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {t}", .{ | |
| 514 | b.cache_root, tmp_sub_path, | |
| 515 | b.cache_root, sub_path, | |
| 516 | err, | |
| 518 | 517 | }); |
| 519 | 518 | }, |
| 520 | 519 | }; |
| 521 | 520 | }, |
| 522 | else => |e| return step.fail("unable to access options file '{f}{s}': {s}", .{ | |
| 523 | b.cache_root, sub_path, @errorName(e), | |
| 521 | else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{ | |
| 522 | b.cache_root, sub_path, e, | |
| 524 | 523 | }), |
| 525 | 524 | } |
| 526 | 525 | } |
| ... | ... | @@ -544,11 +543,11 @@ test Options { |
| 544 | 543 | .cache = .{ |
| 545 | 544 | .io = io, |
| 546 | 545 | .gpa = arena.allocator(), |
| 547 | .manifest_dir = std.fs.cwd(), | |
| 546 | .manifest_dir = Io.Dir.cwd(), | |
| 548 | 547 | }, |
| 549 | 548 | .zig_exe = "test", |
| 550 | 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 | 551 | .host = .{ |
| 553 | 552 | .query = .{}, |
| 554 | 553 | .result = try std.zig.system.resolveTargetQuery(io, .{}), |
| ... | ... | @@ -559,8 +558,8 @@ test Options { |
| 559 | 558 | |
| 560 | 559 | var builder = try std.Build.create( |
| 561 | 560 | &graph, |
| 562 | .{ .path = "test", .handle = std.fs.cwd() }, | |
| 563 | .{ .path = "test", .handle = std.fs.cwd() }, | |
| 561 | .{ .path = "test", .handle = Io.Dir.cwd() }, | |
| 562 | .{ .path = "test", .handle = Io.Dir.cwd() }, | |
| 564 | 563 | &.{}, |
| 565 | 564 | ); |
| 566 | 565 |
lib/std/Build/Step/RemoveDir.zig+4-7| ... | ... | @@ -27,6 +27,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 27 | 27 | _ = options; |
| 28 | 28 | |
| 29 | 29 | const b = step.owner; |
| 30 | const io = b.graph.io; | |
| 30 | 31 | const remove_dir: *RemoveDir = @fieldParentPtr("step", step); |
| 31 | 32 | |
| 32 | 33 | step.clearWatchInputs(); |
| ... | ... | @@ -34,15 +35,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 34 | 35 | |
| 35 | 36 | const full_doomed_path = remove_dir.doomed_path.getPath2(b, step); |
| 36 | 37 | |
| 37 | b.build_root.handle.deleteTree(full_doomed_path) catch |err| { | |
| 38 | b.build_root.handle.deleteTree(io, full_doomed_path) catch |err| { | |
| 38 | 39 | if (b.build_root.path) |base| { |
| 39 | return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{ | |
| 40 | base, full_doomed_path, @errorName(err), | |
| 41 | }); | |
| 40 | return step.fail("unable to recursively delete path '{s}/{s}': {t}", .{ base, full_doomed_path, err }); | |
| 42 | 41 | } else { |
| 43 | return step.fail("unable to recursively delete path '{s}': {s}", .{ | |
| 44 | full_doomed_path, @errorName(err), | |
| 45 | }); | |
| 42 | return step.fail("unable to recursively delete path '{s}': {t}", .{ full_doomed_path, err }); | |
| 46 | 43 | } |
| 47 | 44 | }; |
| 48 | 45 | } |
lib/std/Build/Step/Run.zig+130-120| ... | ... | @@ -1,15 +1,16 @@ |
| 1 | const std = @import("std"); | |
| 1 | const Run = @This(); | |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | const Io = std.Io; | |
| 3 | 6 | const Build = std.Build; |
| 4 | const Step = Build.Step; | |
| 5 | const fs = std.fs; | |
| 7 | const Step = std.Build.Step; | |
| 8 | const Dir = std.Io.Dir; | |
| 6 | 9 | const mem = std.mem; |
| 7 | 10 | const process = std.process; |
| 8 | const EnvMap = process.EnvMap; | |
| 11 | const EnvMap = std.process.EnvMap; | |
| 9 | 12 | const assert = std.debug.assert; |
| 10 | const Path = Build.Cache.Path; | |
| 11 | ||
| 12 | const Run = @This(); | |
| 13 | const Path = std.Build.Cache.Path; | |
| 13 | 14 | |
| 14 | 15 | pub const base_id: Step.Id = .run; |
| 15 | 16 | |
| ... | ... | @@ -25,19 +26,7 @@ cwd: ?Build.LazyPath, |
| 25 | 26 | env_map: ?*EnvMap, |
| 26 | 27 | |
| 27 | 28 | /// Controls the `NO_COLOR` and `CLICOLOR_FORCE` environment variables. |
| 28 | color: enum { | |
| 29 | /// `CLICOLOR_FORCE` is set, and `NO_COLOR` is unset. | |
| 30 | enable, | |
| 31 | /// `NO_COLOR` is set, and `CLICOLOR_FORCE` is unset. | |
| 32 | disable, | |
| 33 | /// If the build runner is using color, equivalent to `.enable`. Otherwise, equivalent to `.disable`. | |
| 34 | inherit, | |
| 35 | /// If stderr is captured or checked, equivalent to `.disable`. Otherwise, equivalent to `.inherit`. | |
| 36 | auto, | |
| 37 | /// The build runner does not modify the `CLICOLOR_FORCE` or `NO_COLOR` environment variables. | |
| 38 | /// They are treated like normal variables, so can be controlled through `setEnvironmentVariable`. | |
| 39 | manual, | |
| 40 | } = .auto, | |
| 29 | color: Color = .auto, | |
| 41 | 30 | |
| 42 | 31 | /// When `true` prevents `ZIG_PROGRESS` environment variable from being passed |
| 43 | 32 | /// to the child process, which otherwise would be used for the child to send |
| ... | ... | @@ -111,6 +100,20 @@ rebuilt_executable: ?Path, |
| 111 | 100 | /// If this Run step was produced by a Compile step, it is tracked here. |
| 112 | 101 | producer: ?*Step.Compile, |
| 113 | 102 | |
| 103 | pub 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 | ||
| 114 | 117 | pub const StdIn = union(enum) { |
| 115 | 118 | none, |
| 116 | 119 | bytes: []const u8, |
| ... | ... | @@ -564,7 +567,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void { |
| 564 | 567 | if (prev_path) |pp| { |
| 565 | 568 | const new_path = b.fmt("{s}{c}{s}", .{ |
| 566 | 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 | 571 | search_path, |
| 569 | 572 | }); |
| 570 | 573 | env_map.put(key, new_path) catch @panic("OOM"); |
| ... | ... | @@ -747,7 +750,7 @@ fn checksContainStderr(checks: []const StdIo.Check) bool { |
| 747 | 750 | fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 { |
| 748 | 751 | const b = run.step.owner; |
| 749 | 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 | 754 | // Absolute paths don't need changing. |
| 752 | 755 | return path_str; |
| 753 | 756 | } |
| ... | ... | @@ -755,19 +758,19 @@ fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 { |
| 755 | 758 | const child_lazy_cwd = run.cwd orelse break :rel path_str; |
| 756 | 759 | const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM"); |
| 757 | 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 | 763 | // Not every path can be made relative, e.g. if the path and the child cwd are on different |
| 761 | 764 | // disk designators on Windows. In that case, `relative` will return an absolute path which we can |
| 762 | 765 | // just return. |
| 763 | if (std.fs.path.isAbsolute(child_cwd_rel)) { | |
| 766 | if (Dir.path.isAbsolute(child_cwd_rel)) { | |
| 764 | 767 | return child_cwd_rel; |
| 765 | 768 | } |
| 766 | 769 | // We're not done yet. In some cases this path must be prefixed with './': |
| 767 | 770 | // * On POSIX, the executable name cannot be a single component like 'foo' |
| 768 | 771 | // * Some executables might treat a leading '-' like a flag, which we must avoid |
| 769 | 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 | } |
| 772 | 775 | |
| 773 | 776 | const IndexedOutput = struct { |
| ... | ... | @@ -845,13 +848,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 845 | 848 | errdefer result.deinit(); |
| 846 | 849 | result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory; |
| 847 | 850 | |
| 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 | 852 | return step.fail( |
| 850 | 853 | "unable to open input file '{f}': {t}", |
| 851 | 854 | .{ file_path, err }, |
| 852 | 855 | ); |
| 853 | 856 | }; |
| 854 | defer file.close(); | |
| 857 | defer file.close(io); | |
| 855 | 858 | |
| 856 | 859 | var buf: [1024]u8 = undefined; |
| 857 | 860 | var file_reader = file.reader(io, &buf); |
| ... | ... | @@ -964,15 +967,15 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 964 | 967 | &digest, |
| 965 | 968 | ); |
| 966 | 969 | |
| 967 | const output_dir_path = "o" ++ fs.path.sep_str ++ &digest; | |
| 970 | const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest; | |
| 968 | 971 | for (output_placeholders.items) |placeholder| { |
| 969 | 972 | const output_sub_path = b.pathJoin(&.{ output_dir_path, placeholder.output.basename }); |
| 970 | 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 | 975 | .output_directory => output_sub_path, |
| 973 | 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 | 979 | return step.fail("unable to make path '{f}{s}': {s}", .{ |
| 977 | 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 | 997 | |
| 995 | 998 | // We do not know the final output paths yet, use temp paths to run the command. |
| 996 | 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); | |
| 998 | 1001 | |
| 999 | 1002 | for (output_placeholders.items) |placeholder| { |
| 1000 | 1003 | const output_components = .{ tmp_dir_path, placeholder.output.basename }; |
| 1001 | 1004 | const output_sub_path = b.pathJoin(&output_components); |
| 1002 | 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 | 1007 | .output_directory => output_sub_path, |
| 1005 | 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 | 1011 | return step.fail("unable to make path '{f}{s}': {s}", .{ |
| 1009 | 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 | 1025 | |
| 1023 | 1026 | try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, options, null); |
| 1024 | 1027 | |
| 1025 | const dep_file_dir = std.fs.cwd(); | |
| 1028 | const dep_file_dir = Dir.cwd(); | |
| 1026 | 1029 | const dep_file_basename = dep_output_file.generated_file.getPath2(b, step); |
| 1027 | 1030 | if (has_side_effects) |
| 1028 | 1031 | try man.addDepFile(dep_file_dir, dep_file_basename) |
| ... | ... | @@ -1039,29 +1042,23 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 1039 | 1042 | |
| 1040 | 1043 | // Rename into place |
| 1041 | 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; | |
| 1043 | 1046 | |
| 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 | 1048 | if (err == error.PathAlreadyExists) { |
| 1046 | b.cache_root.handle.deleteTree(o_sub_path) catch |del_err| { | |
| 1047 | return step.fail("unable to remove dir '{f}'{s}: {s}", .{ | |
| 1048 | b.cache_root, | |
| 1049 | tmp_dir_path, | |
| 1050 | @errorName(del_err), | |
| 1049 | b.cache_root.handle.deleteTree(io, o_sub_path) catch |del_err| { | |
| 1050 | return step.fail("unable to remove dir '{f}'{s}: {t}", .{ | |
| 1051 | b.cache_root, tmp_dir_path, del_err, | |
| 1051 | 1052 | }); |
| 1052 | 1053 | }; |
| 1053 | b.cache_root.handle.rename(tmp_dir_path, o_sub_path) catch |retry_err| { | |
| 1054 | return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{ | |
| 1055 | b.cache_root, tmp_dir_path, | |
| 1056 | b.cache_root, o_sub_path, | |
| 1057 | @errorName(retry_err), | |
| 1054 | b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |retry_err| { | |
| 1055 | return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{ | |
| 1056 | b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, retry_err, | |
| 1058 | 1057 | }); |
| 1059 | 1058 | }; |
| 1060 | 1059 | } else { |
| 1061 | return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{ | |
| 1062 | b.cache_root, tmp_dir_path, | |
| 1063 | b.cache_root, o_sub_path, | |
| 1064 | @errorName(err), | |
| 1060 | return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{ | |
| 1061 | b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, err, | |
| 1065 | 1062 | }); |
| 1066 | 1063 | } |
| 1067 | 1064 | }; |
| ... | ... | @@ -1110,8 +1107,8 @@ pub fn rerunInFuzzMode( |
| 1110 | 1107 | errdefer result.deinit(); |
| 1111 | 1108 | result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory; |
| 1112 | 1109 | |
| 1113 | const file = try file_path.root_dir.handle.openFile(file_path.subPathOrDot(), .{}); | |
| 1114 | defer file.close(); | |
| 1110 | const file = try file_path.root_dir.handle.openFile(io, file_path.subPathOrDot(), .{}); | |
| 1111 | defer file.close(io); | |
| 1115 | 1112 | |
| 1116 | 1113 | var buf: [1024]u8 = undefined; |
| 1117 | 1114 | var file_reader = file.reader(io, &buf); |
| ... | ... | @@ -1144,12 +1141,11 @@ pub fn rerunInFuzzMode( |
| 1144 | 1141 | |
| 1145 | 1142 | const has_side_effects = false; |
| 1146 | 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 | 1145 | try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{ |
| 1149 | 1146 | .progress_node = prog_node, |
| 1150 | 1147 | .watch = undefined, // not used by `runCommand` |
| 1151 | 1148 | .web_server = null, // only needed for time reports |
| 1152 | .ttyconf = fuzz.ttyconf, | |
| 1153 | 1149 | .unit_test_timeout_ns = null, // don't time out fuzz tests for now |
| 1154 | 1150 | .gpa = fuzz.gpa, |
| 1155 | 1151 | }, .{ |
| ... | ... | @@ -1240,6 +1236,7 @@ fn runCommand( |
| 1240 | 1236 | const b = step.owner; |
| 1241 | 1237 | const arena = b.allocator; |
| 1242 | 1238 | const gpa = options.gpa; |
| 1239 | const io = b.graph.io; | |
| 1243 | 1240 | |
| 1244 | 1241 | const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null; |
| 1245 | 1242 | |
| ... | ... | @@ -1260,33 +1257,6 @@ fn runCommand( |
| 1260 | 1257 | }; |
| 1261 | 1258 | defer env_map.deinit(); |
| 1262 | 1259 | |
| 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 | 1260 | const opt_generic_result = spawnChildAndCollect(run, argv, &env_map, has_side_effects, options, fuzz_context) catch |err| term: { |
| 1291 | 1261 | // InvalidExe: cpu arch mismatch |
| 1292 | 1262 | // FileNotFound: can happen with a wrong dynamic linker path |
| ... | ... | @@ -1308,7 +1278,7 @@ fn runCommand( |
| 1308 | 1278 | const need_cross_libc = exe.is_linking_libc and |
| 1309 | 1279 | (root_target.isGnuLibC() or (root_target.isMuslLibC() and exe.linkage == .dynamic)); |
| 1310 | 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 | 1282 | .qemu_fixes_dl = need_cross_libc and b.libc_runtimes_dir != null, |
| 1313 | 1283 | .link_libc = exe.is_linking_libc, |
| 1314 | 1284 | })) { |
| ... | ... | @@ -1468,8 +1438,8 @@ fn runCommand( |
| 1468 | 1438 | captured.output.generated_file.path = output_path; |
| 1469 | 1439 | |
| 1470 | 1440 | const sub_path = b.pathJoin(&output_components); |
| 1471 | const sub_path_dirname = fs.path.dirname(sub_path).?; | |
| 1472 | b.cache_root.handle.makePath(sub_path_dirname) catch |err| { | |
| 1441 | const sub_path_dirname = Dir.path.dirname(sub_path).?; | |
| 1442 | b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| { | |
| 1473 | 1443 | return step.fail("unable to make path '{f}{s}': {s}", .{ |
| 1474 | 1444 | b.cache_root, sub_path_dirname, @errorName(err), |
| 1475 | 1445 | }); |
| ... | ... | @@ -1480,7 +1450,7 @@ fn runCommand( |
| 1480 | 1450 | .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace), |
| 1481 | 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 | 1454 | return step.fail("unable to write file '{f}{s}': {s}", .{ |
| 1485 | 1455 | b.cache_root, sub_path, @errorName(err), |
| 1486 | 1456 | }); |
| ... | ... | @@ -1589,6 +1559,8 @@ fn spawnChildAndCollect( |
| 1589 | 1559 | ) !?EvalGenericResult { |
| 1590 | 1560 | const b = run.step.owner; |
| 1591 | 1561 | const arena = b.allocator; |
| 1562 | const graph = b.graph; | |
| 1563 | const io = graph.io; | |
| 1592 | 1564 | |
| 1593 | 1565 | if (fuzz_context != null) { |
| 1594 | 1566 | assert(!has_side_effects); |
| ... | ... | @@ -1654,8 +1626,12 @@ fn spawnChildAndCollect( |
| 1654 | 1626 | if (!run.disable_zig_progress and !inherit) { |
| 1655 | 1627 | child.progress_node = options.progress_node; |
| 1656 | 1628 | } |
| 1657 | if (inherit) std.debug.lockStdErr(); | |
| 1658 | defer if (inherit) std.debug.unlockStdErr(); | |
| 1629 | const terminal_mode: Io.Terminal.Mode = if (inherit) m: { | |
| 1630 | const stderr = try io.lockStderr(&.{}, graph.stderr_mode); | |
| 1631 | break :m stderr.terminal_mode; | |
| 1632 | } else .no_color; | |
| 1633 | defer if (inherit) io.unlockStderr(); | |
| 1634 | try setColorEnvironmentVariables(run, env_map, terminal_mode); | |
| 1659 | 1635 | var timer = try std.time.Timer.start(); |
| 1660 | 1636 | const res = try evalGeneric(run, &child); |
| 1661 | 1637 | run.step.result_duration_ns = timer.read(); |
| ... | ... | @@ -1663,6 +1639,35 @@ fn spawnChildAndCollect( |
| 1663 | 1639 | } |
| 1664 | 1640 | } |
| 1665 | 1641 | |
| 1642 | fn 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 | ||
| 1666 | 1671 | const StdioPollEnum = enum { stdout, stderr }; |
| 1667 | 1672 | |
| 1668 | 1673 | fn evalZigTest( |
| ... | ... | @@ -1671,8 +1676,10 @@ fn evalZigTest( |
| 1671 | 1676 | options: Step.MakeOptions, |
| 1672 | 1677 | fuzz_context: ?FuzzContext, |
| 1673 | 1678 | ) !EvalZigTestResult { |
| 1674 | const gpa = run.step.owner.allocator; | |
| 1675 | const arena = run.step.owner.allocator; | |
| 1679 | const step_owner = run.step.owner; | |
| 1680 | const gpa = step_owner.allocator; | |
| 1681 | const arena = step_owner.allocator; | |
| 1682 | const io = step_owner.graph.io; | |
| 1676 | 1683 | |
| 1677 | 1684 | // We will update this every time a child runs. |
| 1678 | 1685 | run.step.result_peak_rss = 0; |
| ... | ... | @@ -1691,14 +1698,14 @@ fn evalZigTest( |
| 1691 | 1698 | }; |
| 1692 | 1699 | |
| 1693 | 1700 | while (true) { |
| 1694 | try child.spawn(); | |
| 1701 | try child.spawn(io); | |
| 1695 | 1702 | var poller = std.Io.poll(gpa, StdioPollEnum, .{ |
| 1696 | 1703 | .stdout = child.stdout.?, |
| 1697 | 1704 | .stderr = child.stderr.?, |
| 1698 | 1705 | }); |
| 1699 | 1706 | var child_killed = false; |
| 1700 | 1707 | defer if (!child_killed) { |
| 1701 | _ = child.kill() catch {}; | |
| 1708 | _ = child.kill(io) catch {}; | |
| 1702 | 1709 | poller.deinit(); |
| 1703 | 1710 | run.step.result_peak_rss = @max( |
| 1704 | 1711 | run.step.result_peak_rss, |
| ... | ... | @@ -1724,11 +1731,11 @@ fn evalZigTest( |
| 1724 | 1731 | run.step.result_stderr = try arena.dupe(u8, poller.reader(.stderr).buffered()); |
| 1725 | 1732 | |
| 1726 | 1733 | // Clean up everything and wait for the child to exit. |
| 1727 | child.stdin.?.close(); | |
| 1734 | child.stdin.?.close(io); | |
| 1728 | 1735 | child.stdin = null; |
| 1729 | 1736 | poller.deinit(); |
| 1730 | 1737 | child_killed = true; |
| 1731 | const term = try child.wait(); | |
| 1738 | const term = try child.wait(io); | |
| 1732 | 1739 | run.step.result_peak_rss = @max( |
| 1733 | 1740 | run.step.result_peak_rss, |
| 1734 | 1741 | child.resource_usage_statistics.getMaxRss() orelse 0, |
| ... | ... | @@ -1744,11 +1751,11 @@ fn evalZigTest( |
| 1744 | 1751 | poller.reader(.stderr).tossBuffered(); |
| 1745 | 1752 | |
| 1746 | 1753 | // Clean up everything and wait for the child to exit. |
| 1747 | child.stdin.?.close(); | |
| 1754 | child.stdin.?.close(io); | |
| 1748 | 1755 | child.stdin = null; |
| 1749 | 1756 | poller.deinit(); |
| 1750 | 1757 | child_killed = true; |
| 1751 | const term = try child.wait(); | |
| 1758 | const term = try child.wait(io); | |
| 1752 | 1759 | run.step.result_peak_rss = @max( |
| 1753 | 1760 | run.step.result_peak_rss, |
| 1754 | 1761 | child.resource_usage_statistics.getMaxRss() orelse 0, |
| ... | ... | @@ -1836,6 +1843,7 @@ fn pollZigTest( |
| 1836 | 1843 | switch (ctx.fuzz.mode) { |
| 1837 | 1844 | .forever => { |
| 1838 | 1845 | sendRunFuzzTestMessage( |
| 1846 | io, | |
| 1839 | 1847 | child.stdin.?, |
| 1840 | 1848 | ctx.unit_test_index, |
| 1841 | 1849 | .forever, |
| ... | ... | @@ -1844,6 +1852,7 @@ fn pollZigTest( |
| 1844 | 1852 | }, |
| 1845 | 1853 | .limit => |limit| { |
| 1846 | 1854 | sendRunFuzzTestMessage( |
| 1855 | io, | |
| 1847 | 1856 | child.stdin.?, |
| 1848 | 1857 | ctx.unit_test_index, |
| 1849 | 1858 | .iterations, |
| ... | ... | @@ -1853,11 +1862,11 @@ fn pollZigTest( |
| 1853 | 1862 | } |
| 1854 | 1863 | } else if (opt_metadata.*) |*md| { |
| 1855 | 1864 | // 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 | 1866 | } else { |
| 1858 | 1867 | // Running unit tests normally |
| 1859 | 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 | } |
| 1862 | 1871 | |
| 1863 | 1872 | var active_test_index: ?u32 = null; |
| ... | ... | @@ -1973,7 +1982,7 @@ fn pollZigTest( |
| 1973 | 1982 | active_test_index = null; |
| 1974 | 1983 | if (timer) |*t| t.reset(); |
| 1975 | 1984 | |
| 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 | 1987 | .test_started => { |
| 1979 | 1988 | active_test_index = opt_metadata.*.?.next_index - 1; |
| ... | ... | @@ -2022,7 +2031,7 @@ fn pollZigTest( |
| 2022 | 2031 | active_test_index = null; |
| 2023 | 2032 | if (timer) |*t| md.ns_per_test[tr_hdr.index] = t.lap(); |
| 2024 | 2033 | |
| 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 | 2036 | .coverage_id => { |
| 2028 | 2037 | coverage_id = body_r.takeInt(u64, .little) catch unreachable; |
| ... | ... | @@ -2093,7 +2102,7 @@ pub const CachedTestMetadata = struct { |
| 2093 | 2102 | } |
| 2094 | 2103 | }; |
| 2095 | 2104 | |
| 2096 | fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { | |
| 2105 | fn requestNextTest(io: Io, in: Io.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { | |
| 2097 | 2106 | while (metadata.next_index < metadata.names.len) { |
| 2098 | 2107 | const i = metadata.next_index; |
| 2099 | 2108 | metadata.next_index += 1; |
| ... | ... | @@ -2104,31 +2113,31 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr |
| 2104 | 2113 | if (sub_prog_node.*) |n| n.end(); |
| 2105 | 2114 | sub_prog_node.* = metadata.prog_node.start(name, 0); |
| 2106 | 2115 | |
| 2107 | try sendRunTestMessage(in, .run_test, i); | |
| 2116 | try sendRunTestMessage(io, in, .run_test, i); | |
| 2108 | 2117 | return; |
| 2109 | 2118 | } else { |
| 2110 | 2119 | 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 | } |
| 2114 | 2123 | |
| 2115 | fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { | |
| 2124 | fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { | |
| 2116 | 2125 | const header: std.zig.Client.Message.Header = .{ |
| 2117 | 2126 | .tag = tag, |
| 2118 | 2127 | .bytes_len = 0, |
| 2119 | 2128 | }; |
| 2120 | var w = file.writer(&.{}); | |
| 2129 | var w = file.writer(io, &.{}); | |
| 2121 | 2130 | w.interface.writeStruct(header, .little) catch |err| switch (err) { |
| 2122 | 2131 | error.WriteFailed => return w.err.?, |
| 2123 | 2132 | }; |
| 2124 | 2133 | } |
| 2125 | 2134 | |
| 2126 | fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index: u32) !void { | |
| 2135 | fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, index: u32) !void { | |
| 2127 | 2136 | const header: std.zig.Client.Message.Header = .{ |
| 2128 | 2137 | .tag = tag, |
| 2129 | 2138 | .bytes_len = 4, |
| 2130 | 2139 | }; |
| 2131 | var w = file.writer(&.{}); | |
| 2140 | var w = file.writer(io, &.{}); | |
| 2132 | 2141 | w.interface.writeStruct(header, .little) catch |err| switch (err) { |
| 2133 | 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 | 2147 | } |
| 2139 | 2148 | |
| 2140 | 2149 | fn sendRunFuzzTestMessage( |
| 2141 | file: std.fs.File, | |
| 2150 | io: Io, | |
| 2151 | file: Io.File, | |
| 2142 | 2152 | index: u32, |
| 2143 | 2153 | kind: std.Build.abi.fuzz.LimitKind, |
| 2144 | 2154 | amount_or_instance: u64, |
| ... | ... | @@ -2147,7 +2157,7 @@ fn sendRunFuzzTestMessage( |
| 2147 | 2157 | .tag = .start_fuzzing, |
| 2148 | 2158 | .bytes_len = 4 + 1 + 8, |
| 2149 | 2159 | }; |
| 2150 | var w = file.writer(&.{}); | |
| 2160 | var w = file.writer(io, &.{}); | |
| 2151 | 2161 | w.interface.writeStruct(header, .little) catch |err| switch (err) { |
| 2152 | 2162 | error.WriteFailed => return w.err.?, |
| 2153 | 2163 | }; |
| ... | ... | @@ -2167,30 +2177,30 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { |
| 2167 | 2177 | const io = b.graph.io; |
| 2168 | 2178 | const arena = b.allocator; |
| 2169 | 2179 | |
| 2170 | try child.spawn(); | |
| 2171 | errdefer _ = child.kill() catch {}; | |
| 2180 | try child.spawn(io); | |
| 2181 | errdefer _ = child.kill(io) catch {}; | |
| 2172 | 2182 | |
| 2173 | 2183 | try child.waitForSpawn(); |
| 2174 | 2184 | |
| 2175 | 2185 | switch (run.stdin) { |
| 2176 | 2186 | .bytes => |bytes| { |
| 2177 | child.stdin.?.writeAll(bytes) catch |err| { | |
| 2178 | return run.step.fail("unable to write stdin: {s}", .{@errorName(err)}); | |
| 2187 | child.stdin.?.writeStreamingAll(io, bytes) catch |err| { | |
| 2188 | return run.step.fail("unable to write stdin: {t}", .{err}); | |
| 2179 | 2189 | }; |
| 2180 | child.stdin.?.close(); | |
| 2190 | child.stdin.?.close(io); | |
| 2181 | 2191 | child.stdin = null; |
| 2182 | 2192 | }, |
| 2183 | 2193 | .lazy_path => |lazy_path| { |
| 2184 | 2194 | const path = lazy_path.getPath3(b, &run.step); |
| 2185 | const file = path.root_dir.handle.openFile(path.subPathOrDot(), .{}) catch |err| { | |
| 2186 | return run.step.fail("unable to open stdin file: {s}", .{@errorName(err)}); | |
| 2195 | const file = path.root_dir.handle.openFile(io, path.subPathOrDot(), .{}) catch |err| { | |
| 2196 | return run.step.fail("unable to open stdin file: {t}", .{err}); | |
| 2187 | 2197 | }; |
| 2188 | defer file.close(); | |
| 2198 | defer file.close(io); | |
| 2189 | 2199 | // TODO https://github.com/ziglang/zig/issues/23955 |
| 2190 | 2200 | var read_buffer: [1024]u8 = undefined; |
| 2191 | 2201 | var file_reader = file.reader(io, &read_buffer); |
| 2192 | 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 | 2204 | _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { |
| 2195 | 2205 | error.ReadFailed => return run.step.fail("failed to read from {f}: {t}", .{ |
| 2196 | 2206 | path, file_reader.err.?, |
| ... | ... | @@ -2204,7 +2214,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { |
| 2204 | 2214 | stdin_writer.err.?, |
| 2205 | 2215 | }), |
| 2206 | 2216 | }; |
| 2207 | child.stdin.?.close(); | |
| 2217 | child.stdin.?.close(io); | |
| 2208 | 2218 | child.stdin = null; |
| 2209 | 2219 | }, |
| 2210 | 2220 | .none => {}, |
| ... | ... | @@ -2263,7 +2273,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { |
| 2263 | 2273 | run.step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0; |
| 2264 | 2274 | |
| 2265 | 2275 | return .{ |
| 2266 | .term = try child.wait(), | |
| 2276 | .term = try child.wait(io), | |
| 2267 | 2277 | .stdout = stdout_bytes, |
| 2268 | 2278 | .stderr = stderr_bytes, |
| 2269 | 2279 | }; |
| ... | ... | @@ -2276,7 +2286,7 @@ fn addPathForDynLibs(run: *Run, artifact: *Step.Compile) void { |
| 2276 | 2286 | if (compile.root_module.resolved_target.?.result.os.tag == .windows and |
| 2277 | 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 | 78 | var any_miss = false; |
| 79 | 79 | for (usf.output_source_files.items) |output_source_file| { |
| 80 | 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 | 82 | return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err }); |
| 83 | 83 | }; |
| 84 | 84 | } |
| 85 | 85 | switch (output_source_file.contents) { |
| 86 | 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 | 88 | return step.fail("unable to write file '{f}{s}': {t}", .{ |
| 89 | 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 | 99 | .cwd(), |
| 100 | 100 | io, |
| 101 | 101 | source_path, |
| 102 | b.build_root.handle.adaptToNewApi(), | |
| 102 | b.build_root.handle, | |
| 103 | 103 | output_source_file.sub_path, |
| 104 | 104 | .{}, |
| 105 | 105 | ) catch |err| { |
lib/std/Build/Step/WriteFile.zig+15-22| ... | ... | @@ -206,9 +206,9 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 206 | 206 | } |
| 207 | 207 | } |
| 208 | 208 | |
| 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 | 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]); | |
| 212 | 212 | |
| 213 | 213 | for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| { |
| 214 | 214 | man.hash.addBytes(dir.sub_path); |
| ... | ... | @@ -218,7 +218,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 218 | 218 | const need_derived_inputs = try step.addDirectoryWatchInput(dir.source); |
| 219 | 219 | const src_dir_path = dir.source.getPath3(b, step); |
| 220 | 220 | |
| 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 | 222 | return step.fail("unable to open source directory '{f}': {s}", .{ |
| 223 | 223 | src_dir_path, @errorName(err), |
| 224 | 224 | }); |
| ... | ... | @@ -228,7 +228,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 228 | 228 | |
| 229 | 229 | var it = try src_dir.walk(gpa); |
| 230 | 230 | defer it.deinit(); |
| 231 | while (try it.next()) |entry| { | |
| 231 | while (try it.next(io)) |entry| { | |
| 232 | 232 | if (!dir.options.pathIncluded(entry.path)) continue; |
| 233 | 233 | |
| 234 | 234 | switch (entry.kind) { |
| ... | ... | @@ -259,16 +259,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 259 | 259 | |
| 260 | 260 | write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest }); |
| 261 | 261 | |
| 262 | var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { | |
| 263 | return step.fail("unable to make path '{f}{s}': {s}", .{ | |
| 264 | b.cache_root, cache_path, @errorName(err), | |
| 265 | }); | |
| 266 | }; | |
| 267 | defer cache_dir.close(); | |
| 262 | var cache_dir = b.cache_root.handle.createDirPathOpen(io, cache_path, .{}) catch |err| | |
| 263 | return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, cache_path, err }); | |
| 264 | defer cache_dir.close(io); | |
| 268 | 265 | |
| 269 | 266 | for (write_file.files.items) |file| { |
| 270 | 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 | 269 | return step.fail("unable to make path '{f}{s}{c}{s}': {t}", .{ |
| 273 | 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 | 273 | } |
| 277 | 274 | switch (file.contents) { |
| 278 | 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 | 277 | return step.fail("unable to write file '{f}{s}{c}{s}': {t}", .{ |
| 281 | 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 | 281 | }, |
| 285 | 282 | .copy => |file_source| { |
| 286 | 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 | 285 | return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {t}", .{ |
| 289 | 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 | 300 | const dest_dirname = dir.sub_path; |
| 304 | 301 | |
| 305 | 302 | if (dest_dirname.len != 0) { |
| 306 | cache_dir.makePath(dest_dirname) catch |err| { | |
| 303 | cache_dir.createDirPath(io, dest_dirname) catch |err| { | |
| 307 | 304 | return step.fail("unable to make path '{f}{s}{c}{s}': {s}", .{ |
| 308 | 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 | 309 | |
| 313 | 310 | var it = try already_open_dir.walk(gpa); |
| 314 | 311 | defer it.deinit(); |
| 315 | while (try it.next()) |entry| { | |
| 312 | while (try it.next(io)) |entry| { | |
| 316 | 313 | if (!dir.options.pathIncluded(entry.path)) continue; |
| 317 | 314 | |
| 318 | 315 | const src_entry_path = try src_dir_path.join(arena, entry.path); |
| 319 | 316 | const dest_path = b.pathJoin(&.{ dest_dirname, entry.path }); |
| 320 | 317 | switch (entry.kind) { |
| 321 | .directory => try cache_dir.makePath(dest_path), | |
| 318 | .directory => try cache_dir.createDirPath(io, dest_path), | |
| 322 | 319 | .file => { |
| 323 | 320 | const prev_status = Io.Dir.updateFile( |
| 324 | src_entry_path.root_dir.handle.adaptToNewApi(), | |
| 321 | src_entry_path.root_dir.handle, | |
| 325 | 322 | io, |
| 326 | 323 | src_entry_path.sub_path, |
| 327 | cache_dir.adaptToNewApi(), | |
| 324 | cache_dir, | |
| 328 | 325 | dest_path, |
| 329 | 326 | .{}, |
| 330 | 327 | ) catch |err| { |
| ... | ... | @@ -341,7 +338,3 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 341 | 338 | |
| 342 | 339 | try step.writeManifest(&man); |
| 343 | 340 | } |
| 344 | ||
| 345 | fn 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 | 122 | }) catch return error.NameTooLong; |
| 123 | 123 | const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer); |
| 124 | 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 | 126 | const stack_lfh: FileHandle = .{ .handle = stack_ptr }; |
| 127 | 127 | return stack_lfh.clone(gpa); |
| 128 | 128 | } |
| ... | ... | @@ -222,7 +222,7 @@ const Os = switch (builtin.os.tag) { |
| 222 | 222 | posix.fanotify_mark(fan_fd, .{ |
| 223 | 223 | .ADD = true, |
| 224 | 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 | 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 | 275 | posix.fanotify_mark(fan_fd, .{ |
| 276 | 276 | .REMOVE = true, |
| 277 | 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 | 279 | error.FileNotFound => {}, // Expected, harmless. |
| 280 | 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 | 350 | } |
| 351 | 351 | |
| 352 | 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 | 354 | // It's necessary in order to get the specific flags that are required when calling ReadDirectoryChangesW. |
| 355 | 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 | 357 | const sub_path = path.subPathOrDot(); |
| 358 | 358 | const sub_path_w = try windows.sliceToPrefixedFileW(root_fd, sub_path); |
| 359 | 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 | 681 | if (!gop.found_existing) { |
| 682 | 682 | const skip_open_dir = path.sub_path.len == 0; |
| 683 | 683 | const dir_fd = if (skip_open_dir) |
| 684 | path.root_dir.handle.fd | |
| 684 | path.root_dir.handle.handle | |
| 685 | 685 | else |
| 686 | posix.openat(path.root_dir.handle.fd, path.sub_path, dir_open_flags, 0) catch |err| { | |
| 687 | fatal("failed to open directory {f}: {s}", .{ path, @errorName(err) }); | |
| 686 | posix.openat(path.root_dir.handle.handle, path.sub_path, dir_open_flags, 0) catch |err| { | |
| 687 | fatal("failed to open directory {f}: {t}", .{ path, err }); | |
| 688 | 688 | }; |
| 689 | 689 | // Empirically the dir has to stay open or else no events are triggered. |
| 690 | 690 | errdefer if (!skip_open_dir) posix.close(dir_fd); |
| ... | ... | @@ -750,7 +750,7 @@ const Os = switch (builtin.os.tag) { |
| 750 | 750 | // to access that data via the dir_fd field. |
| 751 | 751 | const path = w.dir_table.keys()[i]; |
| 752 | 752 | const dir_fd = if (path.sub_path.len == 0) |
| 753 | path.root_dir.handle.fd | |
| 753 | path.root_dir.handle.handle | |
| 754 | 754 | else |
| 755 | 755 | handles.items(.dir_fd)[i]; |
| 756 | 756 | assert(dir_fd != -1); |
| ... | ... | @@ -761,7 +761,7 @@ const Os = switch (builtin.os.tag) { |
| 761 | 761 | const last_dir_fd = fd: { |
| 762 | 762 | const last_path = w.dir_table.keys()[handles.len - 1]; |
| 763 | 763 | const last_dir_fd = if (last_path.sub_path.len == 0) |
| 764 | last_path.root_dir.handle.fd | |
| 764 | last_path.root_dir.handle.handle | |
| 765 | 765 | else |
| 766 | 766 | handles.items(.dir_fd)[handles.len - 1]; |
| 767 | 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 | 102 | }; |
| 103 | 103 | } |
| 104 | 104 | |
| 105 | pub fn deinit(fse: *FsEvents, gpa: Allocator) void { | |
| 105 | pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void { | |
| 106 | 106 | dispatch_release(fse.waiting_semaphore); |
| 107 | 107 | dispatch_release(fse.dispatch_queue); |
| 108 | fse.core_services.close(); | |
| 108 | fse.core_services.close(io); | |
| 109 | 109 | |
| 110 | 110 | gpa.free(fse.watch_roots); |
| 111 | 111 | fse.watch_paths.deinit(gpa); |
| ... | ... | @@ -487,6 +487,7 @@ const FSEventStreamEventFlags = packed struct(u32) { |
| 487 | 487 | }; |
| 488 | 488 | |
| 489 | 489 | const std = @import("std"); |
| 490 | const Io = std.Io; | |
| 490 | 491 | const assert = std.debug.assert; |
| 491 | 492 | const Allocator = std.mem.Allocator; |
| 492 | 493 | const watch_log = std.log.scoped(.watch); |
lib/std/Build/WebServer.zig+17-19| ... | ... | @@ -2,7 +2,6 @@ gpa: Allocator, |
| 2 | 2 | graph: *const Build.Graph, |
| 3 | 3 | all_steps: []const *Build.Step, |
| 4 | 4 | listen_address: net.IpAddress, |
| 5 | ttyconf: Io.tty.Config, | |
| 6 | 5 | root_prog_node: std.Progress.Node, |
| 7 | 6 | watch: bool, |
| 8 | 7 | |
| ... | ... | @@ -52,7 +51,6 @@ pub fn notifyUpdate(ws: *WebServer) void { |
| 52 | 51 | |
| 53 | 52 | pub const Options = struct { |
| 54 | 53 | gpa: Allocator, |
| 55 | ttyconf: Io.tty.Config, | |
| 56 | 54 | graph: *const std.Build.Graph, |
| 57 | 55 | all_steps: []const *Build.Step, |
| 58 | 56 | root_prog_node: std.Progress.Node, |
| ... | ... | @@ -98,7 +96,6 @@ pub fn init(opts: Options) WebServer { |
| 98 | 96 | |
| 99 | 97 | return .{ |
| 100 | 98 | .gpa = opts.gpa, |
| 101 | .ttyconf = opts.ttyconf, | |
| 102 | 99 | .graph = opts.graph, |
| 103 | 100 | .all_steps = all_steps, |
| 104 | 101 | .listen_address = opts.listen_address, |
| ... | ... | @@ -129,6 +126,7 @@ pub fn init(opts: Options) WebServer { |
| 129 | 126 | } |
| 130 | 127 | pub fn deinit(ws: *WebServer) void { |
| 131 | 128 | const gpa = ws.gpa; |
| 129 | const io = ws.graph.io; | |
| 132 | 130 | |
| 133 | 131 | gpa.free(ws.step_names_trailing); |
| 134 | 132 | gpa.free(ws.step_status_bits); |
| ... | ... | @@ -139,7 +137,7 @@ pub fn deinit(ws: *WebServer) void { |
| 139 | 137 | gpa.free(ws.time_report_update_times); |
| 140 | 138 | |
| 141 | 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 | 141 | t.join(); |
| 144 | 142 | } |
| 145 | 143 | if (ws.tcp_server) |*s| s.deinit(); |
| ... | ... | @@ -217,9 +215,9 @@ pub fn finishBuild(ws: *WebServer, opts: struct { |
| 217 | 215 | else => {}, |
| 218 | 216 | } |
| 219 | 217 | if (@bitSizeOf(usize) != 64) { |
| 220 | // Current implementation depends on posix.mmap()'s second parameter, `length: usize`, | |
| 221 | // being compatible with `std.fs.getEndPos() u64`'s return value. This is not the case | |
| 222 | // on 32-bit platforms. | |
| 218 | // Current implementation depends on posix.mmap()'s second | |
| 219 | // parameter, `length: usize`, being compatible with file system's | |
| 220 | // u64 return value. This is not the case on 32-bit platforms. | |
| 223 | 221 | // Affects or affected by issues #5185, #22523, and #22464. |
| 224 | 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 | 230 | ws.fuzz = Fuzz.init( |
| 233 | 231 | ws.gpa, |
| 234 | 232 | ws.graph.io, |
| 235 | ws.ttyconf, | |
| 236 | 233 | ws.all_steps, |
| 237 | 234 | ws.root_prog_node, |
| 238 | 235 | .{ .forever = .{ .ws = ws } }, |
| ... | ... | @@ -468,11 +465,12 @@ pub fn serveFile( |
| 468 | 465 | content_type: []const u8, |
| 469 | 466 | ) !void { |
| 470 | 467 | const gpa = ws.gpa; |
| 468 | const io = ws.graph.io; | |
| 471 | 469 | // The desired API is actually sendfile, which will require enhancing http.Server. |
| 472 | 470 | // We load the file with every request so that the user can make changes to the file |
| 473 | 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| { | |
| 475 | log.err("failed to read '{f}': {s}", .{ path, @errorName(err) }); | |
| 472 | const file_contents = path.root_dir.handle.readFileAlloc(io, path.sub_path, gpa, .limited(10 * 1024 * 1024)) catch |err| { | |
| 473 | log.err("failed to read '{f}': {t}", .{ path, err }); | |
| 476 | 474 | return error.AlreadyReported; |
| 477 | 475 | }; |
| 478 | 476 | defer gpa.free(file_contents); |
| ... | ... | @@ -503,14 +501,14 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons |
| 503 | 501 | var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer }; |
| 504 | 502 | |
| 505 | 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 | 505 | log.err("failed to open '{f}': {s}", .{ path, @errorName(err) }); |
| 508 | 506 | continue; |
| 509 | 507 | }; |
| 510 | defer file.close(); | |
| 511 | const stat = try file.stat(); | |
| 508 | defer file.close(io); | |
| 509 | const stat = try file.stat(io); | |
| 512 | 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); | |
| 514 | 512 | |
| 515 | 513 | // TODO: this logic is completely bogus -- obviously so, because `path.root_dir.path` can |
| 516 | 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 | 576 | child.stdin_behavior = .Pipe; |
| 579 | 577 | child.stdout_behavior = .Pipe; |
| 580 | 578 | child.stderr_behavior = .Pipe; |
| 581 | try child.spawn(); | |
| 579 | try child.spawn(io); | |
| 582 | 580 | |
| 583 | 581 | var poller = Io.poll(gpa, enum { stdout, stderr }, .{ |
| 584 | 582 | .stdout = child.stdout.?, |
| ... | ... | @@ -586,7 +584,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 586 | 584 | }); |
| 587 | 585 | defer poller.deinit(); |
| 588 | 586 | |
| 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 | 588 | .{ .tag = .update, .bytes_len = 0 }, |
| 591 | 589 | .{ .tag = .exit, .bytes_len = 0 }, |
| 592 | 590 | }))); |
| ... | ... | @@ -634,10 +632,10 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 634 | 632 | } |
| 635 | 633 | |
| 636 | 634 | // Send EOF to stdin. |
| 637 | child.stdin.?.close(); | |
| 635 | child.stdin.?.close(io); | |
| 638 | 636 | child.stdin = null; |
| 639 | 637 | |
| 640 | switch (try child.wait()) { | |
| 638 | switch (try child.wait(io)) { | |
| 641 | 639 | .Exited => |code| { |
| 642 | 640 | if (code != 0) { |
| 643 | 641 | log.err( |
| ... | ... | @@ -657,7 +655,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 657 | 655 | } |
| 658 | 656 | |
| 659 | 657 | if (result_error_bundle.errorMessageCount() > 0) { |
| 660 | result_error_bundle.renderToStdErr(.{}, .auto); | |
| 658 | try result_error_bundle.renderToStderr(io, .{}, .auto); | |
| 661 | 659 | log.err("the following command failed with {d} compilation errors:\n{s}", .{ |
| 662 | 660 | result_error_bundle.errorMessageCount(), |
| 663 | 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 | 82 | pub const Reader = @import("Io/Reader.zig"); |
| 83 | 83 | pub const Writer = @import("Io/Writer.zig"); |
| 84 | 84 | |
| 85 | pub const tty = @import("Io/tty.zig"); | |
| 86 | ||
| 87 | 85 | pub fn poll( |
| 88 | 86 | gpa: Allocator, |
| 89 | 87 | comptime StreamEnum: type, |
| ... | ... | @@ -528,14 +526,13 @@ pub fn Poller(comptime StreamEnum: type) type { |
| 528 | 526 | /// Given an enum, returns a struct with fields of that enum, each field |
| 529 | 527 | /// representing an I/O stream for polling. |
| 530 | 528 | pub 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 | } |
| 533 | 531 | |
| 534 | 532 | test { |
| 535 | 533 | _ = net; |
| 536 | 534 | _ = Reader; |
| 537 | 535 | _ = Writer; |
| 538 | _ = tty; | |
| 539 | 536 | _ = Evented; |
| 540 | 537 | _ = Threaded; |
| 541 | 538 | _ = @import("Io/test.zig"); |
| ... | ... | @@ -662,27 +659,66 @@ pub const VTable = struct { |
| 662 | 659 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, |
| 663 | 660 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, |
| 664 | 661 | |
| 665 | dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void, | |
| 666 | dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void, | |
| 667 | dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, | |
| 662 | dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, | |
| 663 | dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus, | |
| 664 | dirCreateDirPathOpen: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions, Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir, | |
| 665 | dirOpenDir: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.OpenError!Dir, | |
| 668 | 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, | |
| 670 | dirAccess: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.AccessOptions) Dir.AccessError!void, | |
| 671 | dirCreateFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File, | |
| 672 | dirOpenFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File, | |
| 673 | dirOpenDir: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.OpenError!Dir, | |
| 674 | dirClose: *const fn (?*anyopaque, Dir) void, | |
| 667 | dirStatFile: *const fn (?*anyopaque, Dir, []const u8, Dir.StatFileOptions) Dir.StatFileError!File.Stat, | |
| 668 | dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void, | |
| 669 | dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File, | |
| 670 | dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File, | |
| 671 | dirClose: *const fn (?*anyopaque, []const Dir) void, | |
| 672 | dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize, | |
| 673 | dirRealPath: *const fn (?*anyopaque, Dir, out_buffer: []u8) Dir.RealPathError!usize, | |
| 674 | dirRealPathFile: *const fn (?*anyopaque, Dir, path_name: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize, | |
| 675 | dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void, | |
| 676 | dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void, | |
| 677 | dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void, | |
| 678 | dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void, | |
| 679 | dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize, | |
| 680 | dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void, | |
| 681 | dirSetFileOwner: *const fn (?*anyopaque, Dir, []const u8, ?File.Uid, ?File.Gid, Dir.SetFileOwnerOptions) Dir.SetFileOwnerError!void, | |
| 682 | dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void, | |
| 683 | dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void, | |
| 684 | dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, | |
| 685 | dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, | |
| 686 | dirHardLink: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8, Dir.HardLinkOptions) Dir.HardLinkError!void, | |
| 687 | ||
| 675 | 688 | fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, |
| 676 | fileClose: *const fn (?*anyopaque, File) void, | |
| 677 | fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize, | |
| 678 | fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize, | |
| 689 | fileLength: *const fn (?*anyopaque, File) File.LengthError!u64, | |
| 690 | fileClose: *const fn (?*anyopaque, []const File) void, | |
| 691 | fileWriteStreaming: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize) File.Writer.Error!usize, | |
| 692 | fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize, | |
| 693 | fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize, | |
| 694 | fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize, | |
| 679 | 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 | 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 | 699 | fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void, |
| 684 | 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, | |
| 686 | 722 | |
| 687 | 723 | now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, |
| 688 | 724 | sleep: *const fn (?*anyopaque, Timeout) SleepError!void, |
| ... | ... | @@ -698,7 +734,8 @@ pub const VTable = struct { |
| 698 | 734 | /// Returns 0 on end of stream. |
| 699 | 735 | netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize, |
| 700 | 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 | 739 | netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, |
| 703 | 740 | netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, |
| 704 | 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 | 760 | |
| 724 | 761 | pub const Dir = @import("Io/Dir.zig"); |
| 725 | 762 | pub const File = @import("Io/File.zig"); |
| 763 | pub const Terminal = @import("Io/Terminal.zig"); | |
| 726 | 764 | |
| 727 | 765 | pub const Clock = enum { |
| 728 | 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 | 1315 | /// wakeups are possible. It remains the caller's responsibility to differentiate between these |
| 1278 | 1316 | /// three possible wake-up reasons if necessary. |
| 1279 | 1317 | pub fn futexWaitTimeout(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, expected: T, timeout: Timeout) Cancelable!void { |
| 1280 | comptime assert(@sizeOf(T) == 4); | |
| 1281 | const expected_raw: *align(1) const u32 = @ptrCast(&expected); | |
| 1282 | return io.vtable.futexWait(io.userdata, @ptrCast(ptr), expected_raw.*, timeout); | |
| 1318 | const expected_int: u32 = switch (@typeInfo(T)) { | |
| 1319 | .@"enum" => @bitCast(@intFromEnum(expected)), | |
| 1320 | else => @bitCast(expected), | |
| 1321 | }; | |
| 1322 | return io.vtable.futexWait(io.userdata, @ptrCast(ptr), expected_int, timeout); | |
| 1283 | 1323 | } |
| 1284 | 1324 | /// Same as `futexWait`, except does not introduce a cancelation point. |
| 1285 | 1325 | /// |
| 1286 | 1326 | /// For a description of cancelation and cancelation points, see `Future.cancel`. |
| 1287 | 1327 | pub fn futexWaitUncancelable(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, expected: T) void { |
| 1288 | comptime assert(@sizeOf(T) == @sizeOf(u32)); | |
| 1289 | const expected_raw: *align(1) const u32 = @ptrCast(&expected); | |
| 1290 | io.vtable.futexWaitUncancelable(io.userdata, @ptrCast(ptr), expected_raw.*); | |
| 1328 | const expected_int: u32 = switch (@typeInfo(T)) { | |
| 1329 | .@"enum" => @bitCast(@intFromEnum(expected)), | |
| 1330 | else => @bitCast(expected), | |
| 1331 | }; | |
| 1332 | io.vtable.futexWaitUncancelable(io.userdata, @ptrCast(ptr), expected_int); | |
| 1291 | 1333 | } |
| 1292 | 1334 | /// Unblocks pending futex waits on `ptr`, up to a limit of `max_waiters` calls. |
| 1293 | 1335 | pub 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 | 1581 | } |
| 1540 | 1582 | } |
| 1541 | 1583 | |
| 1584 | pub const WaitTimeoutError = error{Timeout} || Cancelable; | |
| 1585 | ||
| 1542 | 1586 | /// Blocks the calling thread until either the logical boolean is set, the timeout expires, or a |
| 1543 | 1587 | /// spurious wakeup occurs. If the timeout expires or a spurious wakeup occurs, `error.Timeout` |
| 1544 | 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 | 1590 | if (@cmpxchgStrong(Event, event, .unset, .waiting, .acquire, .acquire)) |prev| switch (prev) { |
| 1547 | 1591 | .unset => unreachable, |
| 1548 | 1592 | .waiting => assert(!builtin.single_threaded), // invalid state |
| ... | ... | @@ -1555,7 +1599,7 @@ pub const Event = enum(u32) { |
| 1555 | 1599 | // waiters would wake up when a *new waiter* was added. So it's easiest to just leave |
| 1556 | 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 | 1603 | switch (@atomicLoad(Event, event, .acquire)) { |
| 1560 | 1604 | .unset => unreachable, // `reset` called before pending `wait` returned |
| 1561 | 1605 | .waiting => return error.Timeout, |
| ... | ... | @@ -2136,3 +2180,35 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) { |
| 2136 | 2180 | else => unreachable, |
| 2137 | 2181 | } |
| 2138 | 2182 | } |
| 2183 | ||
| 2184 | pub 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` | |
| 2203 | pub 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. | |
| 2208 | pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!?LockedStderr { | |
| 2209 | return io.vtable.tryLockStderr(io.userdata, buffer, terminal_mode); | |
| 2210 | } | |
| 2211 | ||
| 2212 | pub 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 | 1 | const Dir = @This(); |
| 2 | const root = @import("root"); | |
| 2 | 3 | |
| 3 | 4 | const builtin = @import("builtin"); |
| 4 | 5 | const native_os = builtin.os.tag; |
| ... | ... | @@ -6,11 +7,71 @@ const native_os = builtin.os.tag; |
| 6 | 7 | const std = @import("../std.zig"); |
| 7 | 8 | const Io = std.Io; |
| 8 | 9 | const File = Io.File; |
| 10 | const assert = std.debug.assert; | |
| 11 | const Allocator = std.mem.Allocator; | |
| 9 | 12 | |
| 10 | 13 | handle: Handle, |
| 11 | 14 | |
| 12 | pub const Mode = Io.File.Mode; | |
| 13 | pub const default_mode: Mode = 0o755; | |
| 15 | pub const path = std.fs.path; | |
| 16 | ||
| 17 | /// The maximum length of a file path that the operating system will accept. | |
| 18 | /// | |
| 19 | /// Paths, including those returned from file system operations, may be longer | |
| 20 | /// than this length, but such paths cannot be successfully passed back in | |
| 21 | /// other file system operations. However, all path components returned by file | |
| 22 | /// system operations are assumed to fit into a `u8` array of this length. | |
| 23 | /// | |
| 24 | /// The byte count includes room for a null sentinel byte. | |
| 25 | /// | |
| 26 | /// * On Windows, `[]u8` file paths are encoded as | |
| 27 | /// [WTF-8](https://wtf-8.codeberg.page/). | |
| 28 | /// * On WASI, `[]u8` file paths are encoded as valid UTF-8. | |
| 29 | /// * On other platforms, `[]u8` file paths are opaque sequences of bytes with | |
| 30 | /// no particular encoding. | |
| 31 | pub 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. | |
| 52 | pub 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 | ||
| 70 | pub const Entry = struct { | |
| 71 | name: []const u8, | |
| 72 | kind: File.Kind, | |
| 73 | inode: File.INode, | |
| 74 | }; | |
| 14 | 75 | |
| 15 | 76 | /// Returns a handle to the current working directory. |
| 16 | 77 | /// |
| ... | ... | @@ -20,6 +81,8 @@ pub const default_mode: Mode = 0o755; |
| 20 | 81 | /// Closing the returned `Dir` is checked illegal behavior. |
| 21 | 82 | /// |
| 22 | 83 | /// On POSIX targets, this function is comptime-callable. |
| 84 | /// | |
| 85 | /// On WASI, the value this returns is application-configurable. | |
| 23 | 86 | pub fn cwd() Dir { |
| 24 | 87 | return switch (native_os) { |
| 25 | 88 | .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle }, |
| ... | ... | @@ -28,9 +91,314 @@ pub fn cwd() Dir { |
| 28 | 91 | }; |
| 29 | 92 | } |
| 30 | 93 | |
| 94 | pub 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`. | |
| 176 | pub 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 | ||
| 207 | pub 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). | |
| 214 | pub fn iterateAssumeFirstIteration(dir: Dir) Iterator { | |
| 215 | return .init(dir, .reading); | |
| 216 | } | |
| 217 | ||
| 218 | pub 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`. | |
| 323 | pub 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 | ||
| 338 | pub 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` | |
| 393 | pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker { | |
| 394 | return .{ .inner = try walkSelectively(dir, allocator) }; | |
| 395 | } | |
| 396 | ||
| 31 | 397 | pub const Handle = std.posix.fd_t; |
| 32 | 398 | |
| 33 | 399 | pub const PathNameError = error{ |
| 400 | /// Returned when an insufficient buffer is provided that cannot fit the | |
| 401 | /// path name. | |
| 34 | 402 | NameTooLong, |
| 35 | 403 | /// File system cannot encode the requested file name bytes. |
| 36 | 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 | 437 | return io.vtable.dirAccess(io.userdata, dir, sub_path, options); |
| 70 | 438 | } |
| 71 | 439 | |
| 440 | pub 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 | ||
| 72 | 445 | pub const OpenError = error{ |
| 73 | 446 | FileNotFound, |
| 74 | 447 | NotDir, |
| ... | ... | @@ -108,8 +481,17 @@ pub fn openDir(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) Ope |
| 108 | 481 | return io.vtable.dirOpenDir(io.userdata, dir, sub_path, options); |
| 109 | 482 | } |
| 110 | 483 | |
| 484 | pub 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 | ||
| 111 | 489 | pub 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 | ||
| 493 | pub fn closeMany(io: Io, dirs: []const Dir) void { | |
| 494 | return io.vtable.dirClose(io.userdata, dirs); | |
| 113 | 495 | } |
| 114 | 496 | |
| 115 | 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 | 507 | return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags); |
| 126 | 508 | } |
| 127 | 509 | |
| 510 | pub 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 | 515 | /// Creates, opens, or overwrites a file with write access. |
| 129 | 516 | /// |
| 130 | 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 | 523 | return io.vtable.dirCreateFile(io.userdata, dir, sub_path, flags); |
| 137 | 524 | } |
| 138 | 525 | |
| 526 | pub fn createFileAbsolute(io: Io, absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File { | |
| 527 | return createFile(.cwd(), io, absolute_path, flags); | |
| 528 | } | |
| 529 | ||
| 139 | 530 | pub const WriteFileOptions = struct { |
| 140 | 531 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| 141 | 532 | /// On WASI, `sub_path` should be encoded as valid UTF-8. |
| ... | ... | @@ -145,13 +536,13 @@ pub const WriteFileOptions = struct { |
| 145 | 536 | flags: File.CreateFlags = .{}, |
| 146 | 537 | }; |
| 147 | 538 | |
| 148 | pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable; | |
| 539 | pub const WriteFileError = File.Writer.Error || File.OpenError; | |
| 149 | 540 | |
| 150 | 541 | /// Writes content to the file system, using the file creation flags provided. |
| 151 | 542 | pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void { |
| 152 | 543 | var file = try dir.createFile(io, options.sub_path, options.flags); |
| 153 | 544 | defer file.close(io); |
| 154 | try file.writeAll(io, options.data); | |
| 545 | try file.writeStreamingAll(io, options.data); | |
| 155 | 546 | } |
| 156 | 547 | |
| 157 | 548 | pub const PrevStatus = enum { |
| ... | ... | @@ -161,10 +552,10 @@ pub const PrevStatus = enum { |
| 161 | 552 | |
| 162 | 553 | pub const UpdateFileError = File.OpenError; |
| 163 | 554 | |
| 164 | /// Check the file size, mtime, and mode of `source_path` and `dest_path`. If | |
| 555 | /// Check the file size, mtime, and permissions of `source_path` and `dest_path`. If | |
| 165 | 556 | /// they are equal, does nothing. Otherwise, atomically copies `source_path` to |
| 166 | 557 | /// `dest_path`, creating the parent directory hierarchy as needed. The |
| 167 | /// destination file gains the mtime, atime, and mode of the source file so | |
| 558 | /// destination file gains the mtime, atime, and permissions of the source file so | |
| 168 | 559 | /// that the next call to `updateFile` will not need a copy. |
| 169 | 560 | /// |
| 170 | 561 | /// Returns the previous status of the file before updating. |
| ... | ... | @@ -179,13 +570,13 @@ pub fn updateFile( |
| 179 | 570 | dest_dir: Dir, |
| 180 | 571 | /// If directories in this path do not exist, they are created. |
| 181 | 572 | dest_path: []const u8, |
| 182 | options: std.fs.Dir.CopyFileOptions, | |
| 573 | options: CopyFileOptions, | |
| 183 | 574 | ) !PrevStatus { |
| 184 | 575 | var src_file = try source_dir.openFile(io, source_path, .{}); |
| 185 | 576 | defer src_file.close(io); |
| 186 | 577 | |
| 187 | 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 | 580 | check_dest_stat: { |
| 190 | 581 | const dest_stat = blk: { |
| 191 | 582 | var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) { |
| ... | ... | @@ -199,19 +590,19 @@ pub fn updateFile( |
| 199 | 590 | |
| 200 | 591 | if (src_stat.size == dest_stat.size and |
| 201 | 592 | src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and |
| 202 | actual_mode == dest_stat.mode) | |
| 593 | actual_permissions == dest_stat.permissions) | |
| 203 | 594 | { |
| 204 | 595 | return .fresh; |
| 205 | 596 | } |
| 206 | 597 | } |
| 207 | 598 | |
| 208 | if (std.fs.path.dirname(dest_path)) |dirname| { | |
| 209 | try dest_dir.makePath(io, dirname); | |
| 599 | if (path.dirname(dest_path)) |dirname| { | |
| 600 | try dest_dir.createDirPath(io, dirname); | |
| 210 | 601 | } |
| 211 | 602 | |
| 212 | 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, .{ | |
| 214 | .mode = actual_mode, | |
| 604 | var atomic_file = try dest_dir.atomicFile(io, dest_path, .{ | |
| 605 | .permissions = actual_permissions, | |
| 215 | 606 | .write_buffer = &buffer, |
| 216 | 607 | }); |
| 217 | 608 | defer atomic_file.deinit(); |
| ... | ... | @@ -224,7 +615,7 @@ pub fn updateFile( |
| 224 | 615 | error.WriteFailed => return atomic_file.file_writer.err.?, |
| 225 | 616 | }; |
| 226 | 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 | 619 | try atomic_file.renameIntoPlace(); |
| 229 | 620 | return .stale; |
| 230 | 621 | } |
| ... | ... | @@ -242,7 +633,11 @@ pub const ReadFileError = File.OpenError || File.Reader.Error; |
| 242 | 633 | /// * On WASI, `file_path` should be encoded as valid UTF-8. |
| 243 | 634 | /// * On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. |
| 244 | 635 | pub 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 | 641 | defer file.close(io); |
| 247 | 642 | |
| 248 | 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 | 648 | return buffer[0..n]; |
| 254 | 649 | } |
| 255 | 650 | |
| 256 | pub const MakeError = error{ | |
| 651 | pub const CreateDirError = error{ | |
| 257 | 652 | /// In WASI, this error may occur when the file descriptor does |
| 258 | 653 | /// not hold the required rights to create a new directory relative to it. |
| 259 | 654 | AccessDenied, |
| ... | ... | @@ -279,21 +674,36 @@ pub const MakeError = error{ |
| 279 | 674 | /// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. |
| 280 | 675 | /// |
| 281 | 676 | /// Related: |
| 282 | /// * `makePath` | |
| 283 | /// * `makeDirAbsolute` | |
| 284 | pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8) MakeError!void { | |
| 285 | return io.vtable.dirMake(io.userdata, dir, sub_path, default_mode); | |
| 677 | /// * `createDirPath` | |
| 678 | /// * `createDirAbsolute` | |
| 679 | pub fn createDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) CreateDirError!void { | |
| 680 | return io.vtable.dirCreateDir(io.userdata, dir, sub_path, permissions); | |
| 681 | } | |
| 682 | ||
| 683 | /// Create a new directory, based on an absolute path. | |
| 684 | /// | |
| 685 | /// Asserts that the path is absolute. See `createDir` for a function that | |
| 686 | /// operates on both absolute and relative paths. | |
| 687 | /// | |
| 688 | /// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | |
| 689 | /// On WASI, `absolute_path` should be encoded as valid UTF-8. | |
| 690 | /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. | |
| 691 | pub 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 | } |
| 287 | 695 | |
| 288 | pub const MakePathError = MakeError || StatPathError; | |
| 696 | test createDirAbsolute {} | |
| 289 | 697 | |
| 290 | /// Calls makeDir iteratively to make an entire path, creating any parent | |
| 291 | /// directories that do not exist. | |
| 698 | pub const CreateDirPathError = CreateDirError || StatFileError; | |
| 699 | ||
| 700 | /// Creates parent directories with default permissions as necessary to ensure | |
| 701 | /// `sub_path` exists as a directory. | |
| 292 | 702 | /// |
| 293 | 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 system | |
| 296 | /// may have been modified regardless. | |
| 705 | /// This function may not be atomic. If it returns an error, the file system | |
| 706 | /// may have been modified. | |
| 297 | 707 | /// |
| 298 | 708 | /// Fails on an empty path with `error.BadPathName` as that is not a path that |
| 299 | 709 | /// can be created. |
| ... | ... | @@ -309,48 +719,29 @@ pub const MakePathError = MakeError || StatPathError; |
| 309 | 719 | /// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`, |
| 310 | 720 | /// meaning a `sub_path` like "first/../second" will create both a `./first` |
| 311 | 721 | /// and a `./second` directory. |
| 312 | pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void { | |
| 313 | _ = try makePathStatus(dir, io, sub_path); | |
| 722 | /// | |
| 723 | /// See also: | |
| 724 | /// * `createDirPathStatus` | |
| 725 | pub 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 | } |
| 315 | 728 | |
| 316 | pub const MakePathStatus = enum { existed, created }; | |
| 729 | pub const CreatePathStatus = enum { existed, created }; | |
| 317 | 730 | |
| 318 | /// Same as `makePath` except returns whether the path already existed or was | |
| 731 | /// Same as `createDirPath` except returns whether the path already existed or was | |
| 319 | 732 | /// successfully created. |
| 320 | pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus { | |
| 321 | var it = std.fs.path.componentIterator(sub_path); | |
| 322 | var status: MakePathStatus = .existed; | |
| 323 | var component = it.last() orelse return error.BadPathName; | |
| 324 | while (true) { | |
| 325 | if (makeDir(dir, io, component.path)) { | |
| 326 | status = .created; | |
| 327 | } else |err| switch (err) { | |
| 328 | error.PathAlreadyExists => { | |
| 329 | // stat the file and return an error if it's not a directory | |
| 330 | // this is important because otherwise a dangling symlink | |
| 331 | // could cause an infinite loop | |
| 332 | check_dir: { | |
| 333 | // workaround for windows, see https://github.com/ziglang/zig/issues/16738 | |
| 334 | const fstat = statPath(dir, io, component.path, .{}) catch |stat_err| switch (stat_err) { | |
| 335 | error.IsDir => break :check_dir, | |
| 336 | else => |e| return e, | |
| 337 | }; | |
| 338 | if (fstat.kind != .directory) return error.NotDir; | |
| 339 | } | |
| 340 | }, | |
| 341 | error.FileNotFound => |e| { | |
| 342 | component = it.previous() orelse return e; | |
| 343 | continue; | |
| 344 | }, | |
| 345 | else => |e| return e, | |
| 346 | } | |
| 347 | component = it.next() orelse return status; | |
| 348 | } | |
| 733 | pub fn createDirPathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) CreateDirPathError!CreatePathStatus { | |
| 734 | return io.vtable.dirCreateDirPath(io.userdata, dir, sub_path, permissions); | |
| 349 | 735 | } |
| 350 | 736 | |
| 351 | pub const MakeOpenPathError = MakeError || OpenError || StatPathError; | |
| 737 | pub const CreateDirPathOpenError = CreateDirError || OpenError || StatFileError; | |
| 738 | ||
| 739 | pub const CreateDirPathOpenOptions = struct { | |
| 740 | open_options: OpenOptions = .{}, | |
| 741 | permissions: Permissions = .default_dir, | |
| 742 | }; | |
| 352 | 743 | |
| 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 | 746 | /// When this operation is canceled, it may leave the file system in a |
| 356 | 747 | /// partially modified state. |
| ... | ... | @@ -358,8 +749,8 @@ pub const MakeOpenPathError = MakeError || OpenError || StatPathError; |
| 358 | 749 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| 359 | 750 | /// On WASI, `sub_path` should be encoded as valid UTF-8. |
| 360 | 751 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. |
| 361 | pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) MakeOpenPathError!Dir { | |
| 362 | return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options); | |
| 752 | pub fn createDirPathOpen(dir: Dir, io: Io, sub_path: []const u8, options: CreateDirPathOpenOptions) CreateDirPathOpenError!Dir { | |
| 753 | return io.vtable.dirCreateDirPathOpen(io.userdata, dir, sub_path, options.permissions, options.open_options); | |
| 363 | 754 | } |
| 364 | 755 | |
| 365 | 756 | pub const Stat = File.Stat; |
| ... | ... | @@ -369,9 +760,9 @@ pub fn stat(dir: Dir, io: Io) StatError!Stat { |
| 369 | 760 | return io.vtable.dirStat(io.userdata, dir); |
| 370 | 761 | } |
| 371 | 762 | |
| 372 | pub const StatPathError = File.OpenError || File.StatError; | |
| 763 | pub const StatFileError = File.OpenError || File.StatError; | |
| 373 | 764 | |
| 374 | pub const StatPathOptions = struct { | |
| 765 | pub const StatFileOptions = struct { | |
| 375 | 766 | follow_symlinks: bool = true, |
| 376 | 767 | }; |
| 377 | 768 | |
| ... | ... | @@ -387,6 +778,1074 @@ pub const StatPathOptions = struct { |
| 387 | 778 | /// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). |
| 388 | 779 | /// * On WASI, `sub_path` should be encoded as valid UTF-8. |
| 389 | 780 | /// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. |
| 390 | pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat { | |
| 391 | return io.vtable.dirStatPath(io.userdata, dir, sub_path, options); | |
| 781 | pub fn statFile(dir: Dir, io: Io, sub_path: []const u8, options: StatFileOptions) StatFileError!Stat { | |
| 782 | return io.vtable.dirStatFile(io.userdata, dir, sub_path, options); | |
| 783 | } | |
| 784 | ||
| 785 | pub 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. | |
| 794 | pub fn realPath(dir: Dir, io: Io, out_buffer: []u8) RealPathError!usize { | |
| 795 | return io.vtable.dirRealPath(io.userdata, dir, out_buffer); | |
| 796 | } | |
| 797 | ||
| 798 | pub 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`. | |
| 811 | pub 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 | ||
| 815 | pub 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`. | |
| 826 | pub 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`. | |
| 842 | pub 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`. | |
| 856 | pub 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 | ||
| 862 | pub 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. | |
| 886 | pub fn deleteFile(dir: Dir, io: Io, sub_path: []const u8) DeleteFileError!void { | |
| 887 | return io.vtable.dirDeleteFile(io.userdata, dir, sub_path); | |
| 888 | } | |
| 889 | ||
| 890 | pub 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 | ||
| 895 | test deleteFileAbsolute {} | |
| 896 | ||
| 897 | pub 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. | |
| 919 | pub 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. | |
| 928 | pub 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 | ||
| 933 | pub 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. | |
| 976 | pub 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 | ||
| 986 | pub 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 | ||
| 993 | pub const HardLinkOptions = struct { | |
| 994 | follow_symlinks: bool = true, | |
| 995 | }; | |
| 996 | ||
| 997 | pub 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 | ||
| 1015 | pub 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. | |
| 1031 | pub const SymLinkFlags = struct { | |
| 1032 | is_directory: bool = false, | |
| 1033 | }; | |
| 1034 | ||
| 1035 | pub 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. | |
| 1063 | pub 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 | ||
| 1073 | pub 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. | |
| 1090 | pub 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 | ||
| 1129 | pub 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. | |
| 1162 | pub 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. | |
| 1171 | pub 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 | ||
| 1176 | pub 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. | |
| 1190 | pub 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`. | |
| 1210 | pub 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 | ||
| 1237 | pub 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. | |
| 1266 | pub 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. | |
| 1467 | pub fn deleteTreeMinStackSize(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { | |
| 1468 | return dir.deleteTreeMinStackSizeWithKindHint(io, sub_path, .file); | |
| 1469 | } | |
| 1470 | ||
| 1471 | fn 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. | |
| 1587 | fn 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 | ||
| 1653 | pub const CopyFileOptions = struct { | |
| 1654 | /// When this is `null` the permissions are copied from the source file. | |
| 1655 | permissions: ?File.Permissions = null, | |
| 1656 | }; | |
| 1657 | ||
| 1658 | pub 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. | |
| 1675 | pub 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. | |
| 1714 | pub 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 | ||
| 1726 | test copyFileAbsolute {} | |
| 1727 | ||
| 1728 | pub 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. | |
| 1744 | pub 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 | ||
| 1757 | pub const SetPermissionsError = File.SetPermissionsError; | |
| 1758 | pub 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`. | |
| 1766 | pub fn setPermissions(dir: Dir, io: Io, new_permissions: File.Permissions) SetPermissionsError!void { | |
| 1767 | return io.vtable.dirSetPermissions(io.userdata, dir, new_permissions); | |
| 1768 | } | |
| 1769 | ||
| 1770 | pub 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 | ||
| 1778 | pub const SetFilePermissionsOptions = struct { | |
| 1779 | follow_symlinks: bool = true, | |
| 1780 | }; | |
| 1781 | ||
| 1782 | /// Also known as "fchmodat". | |
| 1783 | pub 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 | ||
| 1793 | pub 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. | |
| 1802 | pub 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 | ||
| 1806 | pub const SetFileOwnerError = PathNameError || SetOwnerError; | |
| 1807 | ||
| 1808 | pub const SetFileOwnerOptions = struct { | |
| 1809 | follow_symlinks: bool = true, | |
| 1810 | }; | |
| 1811 | ||
| 1812 | /// Also known as "fchownat". | |
| 1813 | pub 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 | ||
| 1824 | pub const SetTimestampsError = File.SetTimestampsError || PathNameError; | |
| 1825 | ||
| 1826 | pub 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. | |
| 1833 | pub 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. | |
| 1849 | pub 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 | 7 | const std = @import("../std.zig"); |
| 8 | 8 | const Io = std.Io; |
| 9 | 9 | const assert = std.debug.assert; |
| 10 | const Dir = std.Io.Dir; | |
| 10 | 11 | |
| 11 | 12 | handle: Handle, |
| 12 | 13 | |
| 14 | pub const Reader = @import("File/Reader.zig"); | |
| 15 | pub const Writer = @import("File/Writer.zig"); | |
| 16 | pub const Atomic = @import("File/Atomic.zig"); | |
| 17 | ||
| 13 | 18 | pub const Handle = std.posix.fd_t; |
| 14 | pub const Mode = std.posix.mode_t; | |
| 15 | 19 | pub const INode = std.posix.ino_t; |
| 20 | pub const NLink = std.posix.nlink_t; | |
| 21 | pub const Uid = std.posix.uid_t; | |
| 22 | pub const Gid = std.posix.gid_t; | |
| 16 | 23 | |
| 17 | 24 | pub const Kind = enum { |
| 18 | 25 | block_device, |
| ... | ... | @@ -41,9 +48,9 @@ pub const Stat = struct { |
| 41 | 48 | /// The FileIndex on Windows is similar. It is a number for a file that |
| 42 | 49 | /// is unique to each filesystem. |
| 43 | 50 | inode: INode, |
| 51 | nlink: NLink, | |
| 44 | 52 | size: u64, |
| 45 | /// This is available on POSIX systems and is always 0 otherwise. | |
| 46 | mode: Mode, | |
| 53 | permissions: Permissions, | |
| 47 | 54 | kind: Kind, |
| 48 | 55 | /// Last access time in nanoseconds, relative to UTC 1970-01-01. |
| 49 | 56 | atime: Io.Timestamp, |
| ... | ... | @@ -95,6 +102,26 @@ pub const Lock = enum { |
| 95 | 102 | pub const OpenFlags = struct { |
| 96 | 103 | mode: OpenMode = .read_only, |
| 97 | 104 | |
| 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 | 125 | /// Open the file with an advisory lock to coordinate with other processes |
| 99 | 126 | /// accessing it at the same time. An exclusive lock will prevent other |
| 100 | 127 | /// processes from acquiring a lock. A shared lock will prevent other |
| ... | ... | @@ -141,7 +168,51 @@ pub const OpenFlags = struct { |
| 141 | 168 | } |
| 142 | 169 | }; |
| 143 | 170 | |
| 144 | pub const CreateFlags = std.fs.File.CreateFlags; | |
| 171 | pub 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 | }; | |
| 145 | 216 | |
| 146 | 217 | pub const OpenError = error{ |
| 147 | 218 | SharingViolation, |
| ... | ... | @@ -149,7 +220,6 @@ pub const OpenError = error{ |
| 149 | 220 | NoDevice, |
| 150 | 221 | /// On Windows, `\\server` or `\\server\share` was not found. |
| 151 | 222 | NetworkNotFound, |
| 152 | ProcessNotFound, | |
| 153 | 223 | /// On Windows, antivirus software is enabled by default. It can be |
| 154 | 224 | /// disabled, but Windows Update sometimes ignores the user's preference |
| 155 | 225 | /// and re-enables it. When enabled, antivirus software on Windows |
| ... | ... | @@ -178,7 +248,9 @@ pub const OpenError = error{ |
| 178 | 248 | /// The file is too large to be opened. This error is unreachable |
| 179 | 249 | /// for 64-bit targets, as well as when opening directories. |
| 180 | 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 | 254 | IsDir, |
| 183 | 255 | /// A new path cannot be created because the device has no room for the new file. |
| 184 | 256 | /// This error is only reachable when the `CREAT` flag is provided. |
| ... | ... | @@ -189,7 +261,7 @@ pub const OpenError = error{ |
| 189 | 261 | /// The path already exists and the `CREAT` and `EXCL` flags were provided. |
| 190 | 262 | PathAlreadyExists, |
| 191 | 263 | DeviceBusy, |
| 192 | FileLocksNotSupported, | |
| 264 | FileLocksUnsupported, | |
| 193 | 265 | /// One of these three things: |
| 194 | 266 | /// * pathname refers to an executable image which is currently being |
| 195 | 267 | /// executed and write access was requested. |
| ... | ... | @@ -204,451 +276,418 @@ pub const OpenError = error{ |
| 204 | 276 | } || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; |
| 205 | 277 | |
| 206 | 278 | pub 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 | } |
| 209 | 281 | |
| 210 | pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || std.posix.FlockError; | |
| 211 | ||
| 212 | pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File { | |
| 213 | return io.vtable.openSelfExe(io.userdata, flags); | |
| 282 | pub fn closeMany(io: Io, files: []const File) void { | |
| 283 | return io.vtable.fileClose(io.userdata, files); | |
| 214 | 284 | } |
| 215 | 285 | |
| 216 | pub const ReadPositionalError = Reader.Error || error{Unseekable}; | |
| 286 | pub const SyncError = error{ | |
| 287 | InputOutput, | |
| 288 | NoSpaceLeft, | |
| 289 | DiskQuota, | |
| 290 | AccessDenied, | |
| 291 | } || Io.Cancelable || Io.UnexpectedError; | |
| 217 | 292 | |
| 218 | pub fn readPositional(file: File, io: Io, buffer: [][]u8, offset: u64) ReadPositionalError!usize { | |
| 219 | return io.vtable.fileReadPositional(io.userdata, file, buffer, offset); | |
| 293 | /// Blocks until all pending file contents and metadata modifications for the | |
| 294 | /// file have been synchronized with the underlying filesystem. | |
| 295 | /// | |
| 296 | /// This does not ensure that metadata for the directory containing the file | |
| 297 | /// has also reached disk. | |
| 298 | pub fn sync(file: File, io: Io) SyncError!void { | |
| 299 | return io.vtable.fileSync(io.userdata, file); | |
| 220 | 300 | } |
| 221 | 301 | |
| 222 | pub const WriteStreamingError = error{} || Io.UnexpectedError || Io.Cancelable; | |
| 223 | ||
| 224 | pub fn writeStreaming(file: File, io: Io, buffer: [][]const u8) WriteStreamingError!usize { | |
| 225 | return file.fileWriteStreaming(io, buffer); | |
| 302 | /// Test whether the file refers to a terminal (similar to libc "isatty"). | |
| 303 | /// | |
| 304 | /// See also: | |
| 305 | /// * `enableAnsiEscapeCodes` | |
| 306 | /// * `supportsAnsiEscapeCodes`. | |
| 307 | pub fn isTty(file: File, io: Io) Io.Cancelable!bool { | |
| 308 | return io.vtable.fileIsTty(io.userdata, file); | |
| 226 | 309 | } |
| 227 | 310 | |
| 228 | pub const WritePositionalError = WriteStreamingError || error{Unseekable}; | |
| 311 | pub const EnableAnsiEscapeCodesError = error{ | |
| 312 | NotTerminalDevice, | |
| 313 | } || Io.Cancelable || Io.UnexpectedError; | |
| 229 | 314 | |
| 230 | pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize { | |
| 231 | return io.vtable.fileWritePositional(io.userdata, file, buffer, offset); | |
| 315 | pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError!void { | |
| 316 | return io.vtable.fileEnableAnsiEscapeCodes(io.userdata, file); | |
| 232 | 317 | } |
| 233 | 318 | |
| 234 | pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File { | |
| 235 | assert(std.fs.path.isAbsolute(absolute_path)); | |
| 236 | return Io.Dir.cwd().openFile(io, absolute_path, flags); | |
| 319 | /// Test whether ANSI escape codes will be treated as such without | |
| 320 | /// attempting to enable support for ANSI escape codes. | |
| 321 | pub fn supportsAnsiEscapeCodes(file: File, io: Io) Io.Cancelable!bool { | |
| 322 | return io.vtable.fileSupportsAnsiEscapeCodes(io.userdata, file); | |
| 237 | 323 | } |
| 238 | 324 | |
| 239 | /// Defaults to positional reading; falls back to streaming. | |
| 325 | pub 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 not | |
| 242 | /// affected. | |
| 243 | pub fn reader(file: File, io: Io, buffer: []u8) Reader { | |
| 244 | return .init(file, io, buffer); | |
| 336 | /// The file offset after this call is left unchanged. | |
| 337 | pub fn setLength(file: File, io: Io, new_length: u64) SetLengthError!void { | |
| 338 | return io.vtable.fileSetLength(io.userdata, file, new_length); | |
| 245 | 339 | } |
| 246 | 340 | |
| 247 | /// Positional is more threadsafe, since the global seek position is not | |
| 248 | /// affected, but when such syscalls are not available, preemptively | |
| 249 | /// initializing in streaming mode skips a failed syscall. | |
| 250 | pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader { | |
| 251 | return .initStreaming(file, io, buffer); | |
| 341 | pub const LengthError = StatError; | |
| 342 | ||
| 343 | /// Retrieve the ending byte index of the file. | |
| 344 | /// | |
| 345 | /// Sometimes cheaper than `stat` if only the length is needed. | |
| 346 | pub fn length(file: File, io: Io) LengthError!u64 { | |
| 347 | return io.vtable.fileLength(io.userdata, file); | |
| 252 | 348 | } |
| 253 | 349 | |
| 254 | pub const SeekError = error{ | |
| 255 | Unseekable, | |
| 256 | /// The file descriptor does not hold the required rights to seek on it. | |
| 350 | pub const SetPermissionsError = error{ | |
| 257 | 351 | AccessDenied, |
| 352 | PermissionDenied, | |
| 353 | InputOutput, | |
| 354 | SymLinkLoop, | |
| 355 | FileNotFound, | |
| 356 | SystemResources, | |
| 357 | ReadOnlyFileSystem, | |
| 258 | 358 | } || Io.Cancelable || Io.UnexpectedError; |
| 259 | 359 | |
| 260 | /// Memoizes key information about a file handle such as: | |
| 261 | /// * The size from calling stat, or the error that occurred therein. | |
| 262 | /// * The current seek position. | |
| 263 | /// * The error that occurred when trying to seek. | |
| 264 | /// * Whether reading should be done positionally or streaming. | |
| 265 | /// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`) | |
| 266 | /// versus plain variants (e.g. `read`). | |
| 360 | /// Also known as "chmod". | |
| 267 | 361 | /// |
| 268 | /// Fulfills the `Io.Reader` interface. | |
| 269 | pub const Reader = struct { | |
| 270 | io: Io, | |
| 271 | file: File, | |
| 272 | err: ?Error = null, | |
| 273 | mode: Reader.Mode = .positional, | |
| 274 | /// Tracks the true seek position in the file. To obtain the logical | |
| 275 | /// position, use `logicalPos`. | |
| 276 | pos: u64 = 0, | |
| 277 | size: ?u64 = null, | |
| 278 | size_err: ?SizeError = null, | |
| 279 | seek_err: ?Reader.SeekError = null, | |
| 280 | interface: Io.Reader, | |
| 281 | ||
| 282 | pub const Error = error{ | |
| 283 | InputOutput, | |
| 284 | SystemResources, | |
| 285 | IsDir, | |
| 286 | BrokenPipe, | |
| 287 | ConnectionResetByPeer, | |
| 288 | Timeout, | |
| 289 | /// In WASI, EBADF is mapped to this error because it is returned when | |
| 290 | /// trying to read a directory file descriptor as if it were a file. | |
| 291 | NotOpenForReading, | |
| 292 | SocketUnconnected, | |
| 293 | /// This error occurs when no global event loop is configured, | |
| 294 | /// and reading from the file descriptor would block. | |
| 295 | WouldBlock, | |
| 296 | /// In WASI, this error occurs when the file descriptor does | |
| 297 | /// not hold the required rights to read from it. | |
| 298 | AccessDenied, | |
| 299 | /// This error occurs in Linux if the process to be read from | |
| 300 | /// no longer exists. | |
| 301 | ProcessNotFound, | |
| 302 | /// Unable to read file due to lock. | |
| 303 | LockViolation, | |
| 304 | } || Io.Cancelable || Io.UnexpectedError; | |
| 305 | ||
| 306 | pub const SizeError = std.os.windows.GetFileSizeError || StatError || error{ | |
| 307 | /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. | |
| 308 | Streaming, | |
| 309 | }; | |
| 310 | ||
| 311 | pub const SeekError = File.SeekError || error{ | |
| 312 | /// Seeking fell back to reading, and reached the end before the requested seek position. | |
| 313 | /// `pos` remains at the end of the file. | |
| 314 | EndOfStream, | |
| 315 | /// Seeking fell back to reading, which failed. | |
| 316 | ReadFailed, | |
| 317 | }; | |
| 318 | ||
| 319 | pub const Mode = enum { | |
| 320 | streaming, | |
| 321 | positional, | |
| 322 | /// Avoid syscalls other than `read` and `readv`. | |
| 323 | streaming_reading, | |
| 324 | /// Avoid syscalls other than `pread` and `preadv`. | |
| 325 | positional_reading, | |
| 326 | /// Indicates reading cannot continue because of a seek failure. | |
| 327 | failure, | |
| 328 | ||
| 329 | pub fn toStreaming(m: @This()) @This() { | |
| 330 | return switch (m) { | |
| 331 | .positional, .streaming => .streaming, | |
| 332 | .positional_reading, .streaming_reading => .streaming_reading, | |
| 333 | .failure => .failure, | |
| 334 | }; | |
| 335 | } | |
| 336 | ||
| 337 | pub fn toReading(m: @This()) @This() { | |
| 338 | return switch (m) { | |
| 339 | .positional, .positional_reading => .positional_reading, | |
| 340 | .streaming, .streaming_reading => .streaming_reading, | |
| 341 | .failure => .failure, | |
| 342 | }; | |
| 343 | } | |
| 344 | }; | |
| 345 | ||
| 346 | pub fn initInterface(buffer: []u8) Io.Reader { | |
| 347 | return .{ | |
| 348 | .vtable = &.{ | |
| 349 | .stream = Reader.stream, | |
| 350 | .discard = Reader.discard, | |
| 351 | .readVec = Reader.readVec, | |
| 352 | }, | |
| 353 | .buffer = buffer, | |
| 354 | .seek = 0, | |
| 355 | .end = 0, | |
| 356 | }; | |
| 357 | } | |
| 362 | /// The process must have the correct privileges in order to do this | |
| 363 | /// successfully, or must have the effective user ID matching the owner of the | |
| 364 | /// file. | |
| 365 | pub fn setPermissions(file: File, io: Io, new_permissions: Permissions) SetPermissionsError!void { | |
| 366 | return io.vtable.fileSetPermissions(io.userdata, file, new_permissions); | |
| 367 | } | |
| 358 | 368 | |
| 359 | pub fn init(file: File, io: Io, buffer: []u8) Reader { | |
| 360 | return .{ | |
| 361 | .io = io, | |
| 362 | .file = file, | |
| 363 | .interface = initInterface(buffer), | |
| 364 | }; | |
| 365 | } | |
| 369 | pub const SetOwnerError = error{ | |
| 370 | AccessDenied, | |
| 371 | PermissionDenied, | |
| 372 | InputOutput, | |
| 373 | SymLinkLoop, | |
| 374 | FileNotFound, | |
| 375 | SystemResources, | |
| 376 | ReadOnlyFileSystem, | |
| 377 | } || Io.Cancelable || Io.UnexpectedError; | |
| 366 | 378 | |
| 367 | /// Takes a legacy `std.fs.File` to help with upgrading. | |
| 368 | pub fn initAdapted(file: std.fs.File, io: Io, buffer: []u8) Reader { | |
| 369 | return .init(.{ .handle = file.handle }, io, buffer); | |
| 370 | } | |
| 379 | /// Also known as "chown". | |
| 380 | /// | |
| 381 | /// The process must have the correct privileges in order to do this | |
| 382 | /// successfully. The group may be changed by the owner of the file to any | |
| 383 | /// group of which the owner is a member. If the owner or group is specified as | |
| 384 | /// `null`, the ID is not changed. | |
| 385 | pub fn setOwner(file: File, io: Io, owner: ?Uid, group: ?Gid) SetOwnerError!void { | |
| 386 | return io.vtable.fileSetOwner(io.userdata, file, owner, group); | |
| 387 | } | |
| 371 | 388 | |
| 372 | pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader { | |
| 373 | return .{ | |
| 374 | .io = io, | |
| 375 | .file = file, | |
| 376 | .interface = initInterface(buffer), | |
| 377 | .size = size, | |
| 378 | }; | |
| 379 | } | |
| 389 | /// Cross-platform representation of permissions on a file. | |
| 390 | /// | |
| 391 | /// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes". | |
| 392 | pub const Permissions = std.Options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) { | |
| 393 | default_file = 0, | |
| 394 | _, | |
| 395 | ||
| 396 | pub const default_dir: @This() = .default_file; | |
| 397 | pub const executable_file: @This() = .default_file; | |
| 398 | pub const has_executable_bit = false; | |
| 399 | ||
| 400 | const windows = std.os.windows; | |
| 380 | 401 | |
| 381 | /// Positional is more threadsafe, since the global seek position is not | |
| 382 | /// affected, but when such syscalls are not available, preemptively | |
| 383 | /// initializing in streaming mode skips a failed syscall. | |
| 384 | pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader { | |
| 385 | return .{ | |
| 386 | .io = io, | |
| 387 | .file = file, | |
| 388 | .interface = Reader.initInterface(buffer), | |
| 389 | .mode = .streaming, | |
| 390 | .seek_err = error.Unseekable, | |
| 391 | .size_err = error.Streaming, | |
| 392 | }; | |
| 402 | pub fn toAttributes(self: @This()) windows.FILE.ATTRIBUTE { | |
| 403 | return @bitCast(@intFromEnum(self)); | |
| 393 | 404 | } |
| 394 | 405 | |
| 395 | pub fn getSize(r: *Reader) SizeError!u64 { | |
| 396 | return r.size orelse { | |
| 397 | if (r.size_err) |err| return err; | |
| 398 | if (stat(r.file, r.io)) |st| { | |
| 399 | if (st.kind == .file) { | |
| 400 | r.size = st.size; | |
| 401 | return st.size; | |
| 402 | } else { | |
| 403 | r.mode = r.mode.toStreaming(); | |
| 404 | r.size_err = error.Streaming; | |
| 405 | return error.Streaming; | |
| 406 | } | |
| 407 | } else |err| { | |
| 408 | r.size_err = err; | |
| 409 | return err; | |
| 410 | } | |
| 411 | }; | |
| 406 | pub fn readOnly(self: @This()) bool { | |
| 407 | const attributes = toAttributes(self); | |
| 408 | return attributes & windows.FILE_ATTRIBUTE_READONLY != 0; | |
| 412 | 409 | } |
| 413 | 410 | |
| 414 | pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { | |
| 415 | const io = r.io; | |
| 416 | switch (r.mode) { | |
| 417 | .positional, .positional_reading => { | |
| 418 | setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); | |
| 419 | }, | |
| 420 | .streaming, .streaming_reading => { | |
| 421 | const seek_err = r.seek_err orelse e: { | |
| 422 | if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) { | |
| 423 | setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); | |
| 424 | return; | |
| 425 | } else |err| { | |
| 426 | r.seek_err = err; | |
| 427 | break :e err; | |
| 428 | } | |
| 429 | }; | |
| 430 | var remaining = std.math.cast(u64, offset) orelse return seek_err; | |
| 431 | while (remaining > 0) { | |
| 432 | remaining -= discard(&r.interface, .limited64(remaining)) catch |err| { | |
| 433 | r.seek_err = err; | |
| 434 | return err; | |
| 435 | }; | |
| 436 | } | |
| 437 | r.interface.tossBuffered(); | |
| 438 | }, | |
| 439 | .failure => return r.seek_err.?, | |
| 440 | } | |
| 411 | pub fn setReadOnly(self: @This(), read_only: bool) @This() { | |
| 412 | const attributes = toAttributes(self); | |
| 413 | return @enumFromInt(if (read_only) | |
| 414 | attributes | windows.FILE_ATTRIBUTE_READONLY | |
| 415 | else | |
| 416 | attributes & ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY)); | |
| 417 | } | |
| 418 | } else if (std.posix.mode_t != u0) enum(std.posix.mode_t) { | |
| 419 | /// This is the default mode given to POSIX operating systems for creating | |
| 420 | /// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first, | |
| 421 | /// since most people would expect "-rw-r--r--", for example, when using | |
| 422 | /// the `touch` command, which would correspond to `0o644`. However, POSIX | |
| 423 | /// libc implementations use `0o666` inside `fopen` and then rely on the | |
| 424 | /// process-scoped "umask" setting to adjust this number for file creation. | |
| 425 | default_file = 0o666, | |
| 426 | default_dir = 0o755, | |
| 427 | executable_file = 0o777, | |
| 428 | _, | |
| 429 | ||
| 430 | pub const has_executable_bit = native_os != .wasi; | |
| 431 | ||
| 432 | pub fn toMode(self: @This()) std.posix.mode_t { | |
| 433 | return @intFromEnum(self); | |
| 441 | 434 | } |
| 442 | 435 | |
| 443 | /// Repositions logical read offset relative to the beginning of the file. | |
| 444 | pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { | |
| 445 | const io = r.io; | |
| 446 | switch (r.mode) { | |
| 447 | .positional, .positional_reading => { | |
| 448 | setLogicalPos(r, offset); | |
| 449 | }, | |
| 450 | .streaming, .streaming_reading => { | |
| 451 | const logical_pos = logicalPos(r); | |
| 452 | if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos)); | |
| 453 | if (r.seek_err) |err| return err; | |
| 454 | io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| { | |
| 455 | r.seek_err = err; | |
| 456 | return err; | |
| 457 | }; | |
| 458 | setLogicalPos(r, offset); | |
| 459 | }, | |
| 460 | .failure => return r.seek_err.?, | |
| 461 | } | |
| 436 | pub fn fromMode(mode: std.posix.mode_t) @This() { | |
| 437 | return @enumFromInt(mode); | |
| 462 | 438 | } |
| 463 | 439 | |
| 464 | pub fn logicalPos(r: *const Reader) u64 { | |
| 465 | return r.pos - r.interface.bufferedLen(); | |
| 440 | /// Returns `true` if and only if no class has write permissions. | |
| 441 | pub fn readOnly(self: @This()) bool { | |
| 442 | const mode = toMode(self); | |
| 443 | return mode & 0o222 == 0; | |
| 466 | 444 | } |
| 467 | 445 | |
| 468 | fn setLogicalPos(r: *Reader, offset: u64) void { | |
| 469 | const logical_pos = r.logicalPos(); | |
| 470 | if (offset < logical_pos or offset >= r.pos) { | |
| 471 | r.interface.tossBuffered(); | |
| 472 | r.pos = offset; | |
| 473 | } else r.interface.toss(@intCast(offset - logical_pos)); | |
| 446 | /// Enables write permission for all classes. | |
| 447 | pub fn setReadOnly(self: @This(), read_only: bool) @This() { | |
| 448 | const mode = toMode(self); | |
| 449 | const o222 = @as(std.posix.mode_t, 0o222); | |
| 450 | return @enumFromInt(if (read_only) mode & ~o222 else mode | o222); | |
| 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 | }; | |
| 475 | 458 | |
| 476 | /// Number of slices to store on the stack, when trying to send as many byte | |
| 477 | /// vectors through the underlying read calls as possible. | |
| 478 | const max_buffers_len = 16; | |
| 459 | pub const SetTimestampsError = error{ | |
| 460 | /// times is NULL, or both nsec values are UTIME_NOW, and either: | |
| 461 | /// * the effective user ID of the caller does not match the owner | |
| 462 | /// of the file, the caller does not have write access to the | |
| 463 | /// file, and the caller is not privileged (Linux: does not have | |
| 464 | /// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability); | |
| 465 | /// or, | |
| 466 | /// * the file is marked immutable (see chattr(1)). | |
| 467 | AccessDenied, | |
| 468 | /// The caller attempted to change one or both timestamps to a value | |
| 469 | /// other than the current time, or to change one of the timestamps | |
| 470 | /// to the current time while leaving the other timestamp unchanged, | |
| 471 | /// (i.e., times is not NULL, neither nsec field is UTIME_NOW, | |
| 472 | /// and neither nsec field is UTIME_OMIT) and either: | |
| 473 | /// * the caller's effective user ID does not match the owner of | |
| 474 | /// file, and the caller is not privileged (Linux: does not have | |
| 475 | /// the CAP_FOWNER capability); or, | |
| 476 | /// * the file is marked append-only or immutable (see chattr(1)). | |
| 477 | PermissionDenied, | |
| 478 | ReadOnlyFileSystem, | |
| 479 | } || Io.Cancelable || Io.UnexpectedError; | |
| 479 | 480 | |
| 480 | fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { | |
| 481 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); | |
| 482 | return streamMode(r, w, limit, r.mode); | |
| 483 | } | |
| 481 | /// The granularity that ultimately is stored depends on the combination of | |
| 482 | /// operating system and file system. When a value as provided that exceeds | |
| 483 | /// this range, the value is clamped to the maximum. | |
| 484 | pub 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 | } | |
| 484 | 492 | |
| 485 | pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize { | |
| 486 | switch (mode) { | |
| 487 | .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) { | |
| 488 | error.Unimplemented => { | |
| 489 | r.mode = r.mode.toReading(); | |
| 490 | return 0; | |
| 491 | }, | |
| 492 | else => |e| return e, | |
| 493 | }, | |
| 494 | .positional_reading => { | |
| 495 | const dest = limit.slice(try w.writableSliceGreedy(1)); | |
| 496 | var data: [1][]u8 = .{dest}; | |
| 497 | const n = try readVecPositional(r, &data); | |
| 498 | w.advance(n); | |
| 499 | return n; | |
| 500 | }, | |
| 501 | .streaming_reading => { | |
| 502 | const dest = limit.slice(try w.writableSliceGreedy(1)); | |
| 503 | var data: [1][]u8 = .{dest}; | |
| 504 | const n = try readVecStreaming(r, &data); | |
| 505 | w.advance(n); | |
| 506 | return n; | |
| 507 | }, | |
| 508 | .failure => return error.ReadFailed, | |
| 509 | } | |
| 510 | } | |
| 493 | /// Sets the accessed and modification timestamps of `file` to the current wall | |
| 494 | /// clock time. | |
| 495 | /// | |
| 496 | /// The granularity that ultimately is stored depends on the combination of | |
| 497 | /// operating system and file system. | |
| 498 | pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { | |
| 499 | return io.vtable.fileSetTimestampsNow(io.userdata, file); | |
| 500 | } | |
| 511 | 501 | |
| 512 | fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { | |
| 513 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); | |
| 514 | switch (r.mode) { | |
| 515 | .positional, .positional_reading => return readVecPositional(r, data), | |
| 516 | .streaming, .streaming_reading => return readVecStreaming(r, data), | |
| 517 | .failure => return error.ReadFailed, | |
| 518 | } | |
| 519 | } | |
| 502 | /// Returns 0 on stream end or if `buffer` has no space available for data. | |
| 503 | /// | |
| 504 | /// See also: | |
| 505 | /// * `reader` | |
| 506 | pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize { | |
| 507 | return io.vtable.fileReadStreaming(io.userdata, file, buffer); | |
| 508 | } | |
| 520 | 509 | |
| 521 | fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize { | |
| 522 | const io = r.io; | |
| 523 | var iovecs_buffer: [max_buffers_len][]u8 = undefined; | |
| 524 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); | |
| 525 | const dest = iovecs_buffer[0..dest_n]; | |
| 526 | assert(dest[0].len > 0); | |
| 527 | const n = io.vtable.fileReadPositional(io.userdata, r.file, dest, r.pos) catch |err| switch (err) { | |
| 528 | error.Unseekable => { | |
| 529 | r.mode = r.mode.toStreaming(); | |
| 530 | const pos = r.pos; | |
| 531 | if (pos != 0) { | |
| 532 | r.pos = 0; | |
| 533 | r.seekBy(@intCast(pos)) catch { | |
| 534 | r.mode = .failure; | |
| 535 | return error.ReadFailed; | |
| 536 | }; | |
| 537 | } | |
| 538 | return 0; | |
| 539 | }, | |
| 540 | else => |e| { | |
| 541 | r.err = e; | |
| 542 | return error.ReadFailed; | |
| 543 | }, | |
| 544 | }; | |
| 545 | if (n == 0) { | |
| 546 | r.size = r.pos; | |
| 547 | return error.EndOfStream; | |
| 548 | } | |
| 549 | r.pos += n; | |
| 550 | if (n > data_size) { | |
| 551 | r.interface.end += n - data_size; | |
| 552 | return data_size; | |
| 553 | } | |
| 554 | return n; | |
| 555 | } | |
| 510 | pub const ReadPositionalError = Reader.Error || error{Unseekable}; | |
| 556 | 511 | |
| 557 | fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize { | |
| 558 | const io = r.io; | |
| 559 | var iovecs_buffer: [max_buffers_len][]u8 = undefined; | |
| 560 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); | |
| 561 | const dest = iovecs_buffer[0..dest_n]; | |
| 562 | assert(dest[0].len > 0); | |
| 563 | const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| { | |
| 564 | r.err = err; | |
| 565 | return error.ReadFailed; | |
| 566 | }; | |
| 567 | if (n == 0) { | |
| 568 | r.size = r.pos; | |
| 569 | return error.EndOfStream; | |
| 570 | } | |
| 571 | r.pos += n; | |
| 572 | if (n > data_size) { | |
| 573 | r.interface.end += n - data_size; | |
| 574 | return data_size; | |
| 575 | } | |
| 576 | return n; | |
| 577 | } | |
| 512 | /// Returns 0 on stream end or if `buffer` has no space available for data. | |
| 513 | /// | |
| 514 | /// See also: | |
| 515 | /// * `reader` | |
| 516 | pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) ReadPositionalError!usize { | |
| 517 | return io.vtable.fileReadPositional(io.userdata, file, buffer, offset); | |
| 518 | } | |
| 519 | ||
| 520 | pub const WritePositionalError = Writer.Error || error{Unseekable}; | |
| 521 | ||
| 522 | /// See also: | |
| 523 | /// * `writer` | |
| 524 | pub fn writePositional(file: File, io: Io, buffer: []const []const u8, offset: u64) WritePositionalError!usize { | |
| 525 | return io.vtable.fileWritePositional(io.userdata, file, &.{}, buffer, 1, offset); | |
| 526 | } | |
| 527 | ||
| 528 | /// Equivalent to creating a positional writer, writing `bytes`, and then flushing. | |
| 529 | pub fn writePositionalAll(file: File, io: Io, bytes: []const u8, offset: u64) WritePositionalError!void { | |
| 530 | var index: usize = 0; | |
| 531 | while (index < bytes.len) | |
| 532 | index += try io.vtable.fileWritePositional(io.userdata, file, &.{}, &.{bytes[index..]}, 1, offset + index); | |
| 533 | } | |
| 534 | ||
| 535 | pub 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 | ||
| 541 | pub 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` | |
| 550 | pub fn reader(file: File, io: Io, buffer: []u8) Reader { | |
| 551 | return .init(file, io, buffer); | |
| 552 | } | |
| 578 | 553 | |
| 579 | fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { | |
| 580 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); | |
| 581 | const io = r.io; | |
| 582 | const file = r.file; | |
| 583 | switch (r.mode) { | |
| 584 | .positional, .positional_reading => { | |
| 585 | const size = r.getSize() catch { | |
| 586 | r.mode = r.mode.toStreaming(); | |
| 587 | return 0; | |
| 588 | }; | |
| 589 | const logical_pos = logicalPos(r); | |
| 590 | const delta = @min(@intFromEnum(limit), size - logical_pos); | |
| 591 | setLogicalPos(r, logical_pos + delta); | |
| 592 | return delta; | |
| 593 | }, | |
| 594 | .streaming, .streaming_reading => { | |
| 595 | // Unfortunately we can't seek forward without knowing the | |
| 596 | // size because the seek syscalls provided to us will not | |
| 597 | // return the true end position if a seek would exceed the | |
| 598 | // end. | |
| 599 | fallback: { | |
| 600 | if (r.size_err == null and r.seek_err == null) break :fallback; | |
| 601 | ||
| 602 | const buffered_len = r.interface.bufferedLen(); | |
| 603 | var remaining = @intFromEnum(limit); | |
| 604 | if (remaining <= buffered_len) { | |
| 605 | r.interface.seek += remaining; | |
| 606 | return remaining; | |
| 607 | } | |
| 608 | remaining -= buffered_len; | |
| 609 | r.interface.seek = 0; | |
| 610 | r.interface.end = 0; | |
| 611 | ||
| 612 | var trash_buffer: [128]u8 = undefined; | |
| 613 | var data: [1][]u8 = .{trash_buffer[0..@min(trash_buffer.len, remaining)]}; | |
| 614 | var iovecs_buffer: [max_buffers_len][]u8 = undefined; | |
| 615 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data); | |
| 616 | const dest = iovecs_buffer[0..dest_n]; | |
| 617 | assert(dest[0].len > 0); | |
| 618 | const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| { | |
| 619 | r.err = err; | |
| 620 | return error.ReadFailed; | |
| 621 | }; | |
| 622 | if (n == 0) { | |
| 623 | r.size = r.pos; | |
| 624 | return error.EndOfStream; | |
| 625 | } | |
| 626 | r.pos += n; | |
| 627 | if (n > data_size) { | |
| 628 | r.interface.end += n - data_size; | |
| 629 | remaining -= data_size; | |
| 630 | } else { | |
| 631 | remaining -= n; | |
| 632 | } | |
| 633 | return @intFromEnum(limit) - remaining; | |
| 634 | } | |
| 635 | const size = r.getSize() catch return 0; | |
| 636 | const n = @min(size - r.pos, std.math.maxInt(i64), @intFromEnum(limit)); | |
| 637 | io.vtable.fileSeekBy(io.userdata, file, n) catch |err| { | |
| 638 | r.seek_err = err; | |
| 639 | return 0; | |
| 640 | }; | |
| 641 | r.pos += n; | |
| 642 | return n; | |
| 643 | }, | |
| 644 | .failure => return error.ReadFailed, | |
| 645 | } | |
| 554 | /// Equivalent to creating a positional reader and reading multiple times to fill `buffer`. | |
| 555 | /// | |
| 556 | /// Returns number of bytes read into `buffer`. If less than `buffer.len`, end of file occurred. | |
| 557 | /// | |
| 558 | /// See also: | |
| 559 | /// * `reader` | |
| 560 | pub fn readPositionalAll(file: File, io: Io, buffer: []u8, offset: u64) ReadPositionalError!usize { | |
| 561 | var index: usize = 0; | |
| 562 | while (index != buffer.len) { | |
| 563 | const amt = try file.readPositional(io, &.{buffer[index..]}, offset + index); | |
| 564 | if (amt == 0) break; | |
| 565 | index += amt; | |
| 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` | |
| 576 | pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader { | |
| 577 | return .initStreaming(file, io, buffer); | |
| 578 | } | |
| 647 | 579 | |
| 648 | /// Returns whether the stream is at the logical end. | |
| 649 | pub fn atEnd(r: *Reader) bool { | |
| 650 | // Even if stat fails, size is set when end is encountered. | |
| 651 | const size = r.size orelse return false; | |
| 652 | return size - logicalPos(r) == 0; | |
| 580 | /// Defaults to positional reading; falls back to streaming. | |
| 581 | /// | |
| 582 | /// Positional is more threadsafe, since the global seek position is not | |
| 583 | /// affected. | |
| 584 | pub 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. | |
| 591 | pub 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. | |
| 596 | pub 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 | ||
| 603 | pub 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. | |
| 613 | pub 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. | |
| 618 | pub 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. | |
| 628 | pub fn tryLock(file: File, io: Io, l: Lock) LockError!bool { | |
| 629 | return io.vtable.fileTryLock(io.userdata, file, l); | |
| 630 | } | |
| 631 | ||
| 632 | pub 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. | |
| 636 | pub fn downgradeLock(file: File, io: Io) LockError!void { | |
| 637 | return io.vtable.fileDowngradeLock(io.userdata, file); | |
| 638 | } | |
| 639 | ||
| 640 | pub 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. | |
| 685 | pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize { | |
| 686 | return io.vtable.fileRealPath(io.userdata, file, out_buffer); | |
| 687 | } | |
| 688 | ||
| 689 | test { | |
| 690 | _ = Reader; | |
| 691 | _ = Writer; | |
| 692 | _ = Atomic; | |
| 693 | } |
lib/std/Io/File/Atomic.zig created+102| ... | ... | @@ -0,0 +1,102 @@ |
| 1 | const Atomic = @This(); | |
| 2 | ||
| 3 | const std = @import("../../std.zig"); | |
| 4 | const Io = std.Io; | |
| 5 | const File = std.Io.File; | |
| 6 | const Dir = std.Io.Dir; | |
| 7 | const assert = std.debug.assert; | |
| 8 | ||
| 9 | file_writer: File.Writer, | |
| 10 | random_integer: u64, | |
| 11 | dest_basename: []const u8, | |
| 12 | file_open: bool, | |
| 13 | file_exists: bool, | |
| 14 | close_dir_on_deinit: bool, | |
| 15 | dir: Dir, | |
| 16 | ||
| 17 | pub const InitError = File.OpenError; | |
| 18 | ||
| 19 | /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. | |
| 20 | pub 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(). | |
| 51 | pub 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 | ||
| 69 | pub const FlushError = File.Writer.Error; | |
| 70 | ||
| 71 | pub 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 | ||
| 77 | pub 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). | |
| 83 | pub 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 | ||
| 96 | pub const FinishError = FlushError || RenameIntoPlaceError; | |
| 97 | ||
| 98 | /// Combination of `flush` followed by `renameIntoPlace`. | |
| 99 | pub 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. | |
| 10 | const Reader = @This(); | |
| 11 | ||
| 12 | const std = @import("../../std.zig"); | |
| 13 | const Io = std.Io; | |
| 14 | const File = std.Io.File; | |
| 15 | const assert = std.debug.assert; | |
| 16 | ||
| 17 | io: Io, | |
| 18 | file: File, | |
| 19 | err: ?Error = null, | |
| 20 | mode: Mode = .positional, | |
| 21 | /// Tracks the true seek position in the file. To obtain the logical position, | |
| 22 | /// use `logicalPos`. | |
| 23 | pos: u64 = 0, | |
| 24 | size: ?u64 = null, | |
| 25 | size_err: ?SizeError = null, | |
| 26 | seek_err: ?SeekError = null, | |
| 27 | interface: Io.Reader, | |
| 28 | ||
| 29 | pub 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 | ||
| 52 | pub 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 | ||
| 57 | pub 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 | ||
| 65 | pub 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 | ||
| 92 | pub 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 | ||
| 105 | pub fn init(file: File, io: Io, buffer: []u8) Reader { | |
| 106 | return .{ | |
| 107 | .io = io, | |
| 108 | .file = file, | |
| 109 | .interface = initInterface(buffer), | |
| 110 | }; | |
| 111 | } | |
| 112 | ||
| 113 | pub 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. | |
| 125 | pub 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 | ||
| 136 | pub 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 | ||
| 155 | pub 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. | |
| 185 | pub 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 | ||
| 205 | pub fn logicalPos(r: *const Reader) u64 { | |
| 206 | return r.pos - r.interface.bufferedLen(); | |
| 207 | } | |
| 208 | ||
| 209 | fn 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. | |
| 219 | const max_buffers_len = 16; | |
| 220 | ||
| 221 | fn 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 | ||
| 226 | pub 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 | ||
| 253 | fn 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 | ||
| 262 | fn 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 | ||
| 298 | fn 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 | ||
| 320 | fn 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. | |
| 390 | pub 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 @@ |
| 1 | const Writer = @This(); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const is_windows = builtin.os.tag == .windows; | |
| 4 | ||
| 5 | const std = @import("../../std.zig"); | |
| 6 | const Io = std.Io; | |
| 7 | const File = std.Io.File; | |
| 8 | const assert = std.debug.assert; | |
| 9 | ||
| 10 | io: Io, | |
| 11 | file: File, | |
| 12 | err: ?Error = null, | |
| 13 | mode: Mode = .positional, | |
| 14 | /// Tracks the true seek position in the file. To obtain the logical position, | |
| 15 | /// use `logicalPos`. | |
| 16 | pos: u64 = 0, | |
| 17 | write_file_err: ?WriteFileError = null, | |
| 18 | seek_err: ?SeekError = null, | |
| 19 | interface: Io.Writer, | |
| 20 | ||
| 21 | pub const Mode = File.Reader.Mode; | |
| 22 | ||
| 23 | pub 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 | ||
| 48 | pub 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 | ||
| 57 | pub const SeekError = Io.File.SeekError; | |
| 58 | ||
| 59 | pub 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. | |
| 71 | pub 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. | |
| 81 | pub 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 | ||
| 90 | pub fn initInterface(buffer: []u8) Io.Writer { | |
| 91 | return .{ | |
| 92 | .vtable = &.{ | |
| 93 | .drain = drain, | |
| 94 | .sendFile = sendFile, | |
| 95 | }, | |
| 96 | .buffer = buffer, | |
| 97 | }; | |
| 98 | } | |
| 99 | ||
| 100 | pub 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 | ||
| 112 | pub 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 | ||
| 121 | fn 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 | ||
| 146 | fn 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 | ||
| 157 | pub 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 | ||
| 168 | fn 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 | ||
| 200 | fn 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 | ||
| 220 | pub fn seekTo(w: *Writer, offset: u64) (SeekError || Io.Writer.Error)!void { | |
| 221 | try w.interface.flush(); | |
| 222 | try seekToUnbuffered(w, offset); | |
| 223 | } | |
| 224 | ||
| 225 | pub fn logicalPos(w: *const Writer) u64 { | |
| 226 | return w.pos + w.interface.end; | |
| 227 | } | |
| 228 | ||
| 229 | /// Asserts that no data is currently buffered. | |
| 230 | pub 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 | ||
| 249 | pub 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. | |
| 258 | pub 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 | 1093 | .PERM => return error.PermissionDenied, |
| 1094 | 1094 | .EXIST => return error.PathAlreadyExists, |
| 1095 | 1095 | .BUSY => return error.DeviceBusy, |
| 1096 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 1096 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 1097 | 1097 | .AGAIN => return error.WouldBlock, |
| 1098 | 1098 | .TXTBSY => return error.FileBusy, |
| 1099 | 1099 | .NXIO => return error.NoDevice, |
| ... | ... | @@ -1201,7 +1201,7 @@ fn fileOpen( |
| 1201 | 1201 | .PERM => return error.PermissionDenied, |
| 1202 | 1202 | .EXIST => return error.PathAlreadyExists, |
| 1203 | 1203 | .BUSY => return error.DeviceBusy, |
| 1204 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 1204 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 1205 | 1205 | .AGAIN => return error.WouldBlock, |
| 1206 | 1206 | .TXTBSY => return error.FileBusy, |
| 1207 | 1207 | .NXIO => return error.NoDevice, |
lib/std/Io/Kqueue.zig+10-10| ... | ... | @@ -869,11 +869,11 @@ pub fn io(k: *Kqueue) Io { |
| 869 | 869 | .conditionWaitUncancelable = conditionWaitUncancelable, |
| 870 | 870 | .conditionWake = conditionWake, |
| 871 | 871 | |
| 872 | .dirMake = dirMake, | |
| 873 | .dirMakePath = dirMakePath, | |
| 874 | .dirMakeOpenPath = dirMakeOpenPath, | |
| 872 | .dirCreateDir = dirCreateDir, | |
| 873 | .dirCreateDirPath = dirCreateDirPath, | |
| 874 | .dirCreateDirPathOpen = dirCreateDirPathOpen, | |
| 875 | 875 | .dirStat = dirStat, |
| 876 | .dirStatPath = dirStatPath, | |
| 876 | .dirStatFile = dirStatFile, | |
| 877 | 877 | |
| 878 | 878 | .fileStat = fileStat, |
| 879 | 879 | .dirAccess = dirAccess, |
| ... | ... | @@ -888,7 +888,7 @@ pub fn io(k: *Kqueue) Io { |
| 888 | 888 | .fileReadPositional = fileReadPositional, |
| 889 | 889 | .fileSeekBy = fileSeekBy, |
| 890 | 890 | .fileSeekTo = fileSeekTo, |
| 891 | .openSelfExe = openSelfExe, | |
| 891 | .openExecutable = openExecutable, | |
| 892 | 892 | |
| 893 | 893 | .now = now, |
| 894 | 894 | .sleep = sleep, |
| ... | ... | @@ -1114,7 +1114,7 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition. |
| 1114 | 1114 | k.yield(waiting_fiber, .reschedule); |
| 1115 | 1115 | } |
| 1116 | 1116 | |
| 1117 | fn dirMake(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void { | |
| 1117 | fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.CreateDirError!void { | |
| 1118 | 1118 | const k: *Kqueue = @ptrCast(@alignCast(userdata)); |
| 1119 | 1119 | _ = k; |
| 1120 | 1120 | _ = dir; |
| ... | ... | @@ -1122,7 +1122,7 @@ fn dirMake(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode |
| 1122 | 1122 | _ = mode; |
| 1123 | 1123 | @panic("TODO"); |
| 1124 | 1124 | } |
| 1125 | fn dirMakePath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void { | |
| 1125 | fn dirCreateDirPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.CreateDirError!void { | |
| 1126 | 1126 | const k: *Kqueue = @ptrCast(@alignCast(userdata)); |
| 1127 | 1127 | _ = k; |
| 1128 | 1128 | _ = dir; |
| ... | ... | @@ -1130,7 +1130,7 @@ fn dirMakePath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir. |
| 1130 | 1130 | _ = mode; |
| 1131 | 1131 | @panic("TODO"); |
| 1132 | 1132 | } |
| 1133 | fn dirMakeOpenPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.OpenOptions) Dir.MakeOpenPathError!Dir { | |
| 1133 | fn dirCreateDirPathOpen(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir { | |
| 1134 | 1134 | const k: *Kqueue = @ptrCast(@alignCast(userdata)); |
| 1135 | 1135 | _ = k; |
| 1136 | 1136 | _ = dir; |
| ... | ... | @@ -1144,7 +1144,7 @@ fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { |
| 1144 | 1144 | _ = dir; |
| 1145 | 1145 | @panic("TODO"); |
| 1146 | 1146 | } |
| 1147 | fn dirStatPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.StatPathOptions) Dir.StatPathError!File.Stat { | |
| 1147 | fn dirStatFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.StatPathOptions) Dir.StatFileError!File.Stat { | |
| 1148 | 1148 | const k: *Kqueue = @ptrCast(@alignCast(userdata)); |
| 1149 | 1149 | _ = k; |
| 1150 | 1150 | _ = dir; |
| ... | ... | @@ -1246,7 +1246,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, absolute_offset: u64) File.Seek |
| 1246 | 1246 | _ = absolute_offset; |
| 1247 | 1247 | @panic("TODO"); |
| 1248 | 1248 | } |
| 1249 | fn openSelfExe(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenSelfExeError!File { | |
| 1249 | fn openExecutable(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenExecutableError!File { | |
| 1250 | 1250 | const k: *Kqueue = @ptrCast(@alignCast(userdata)); |
| 1251 | 1251 | _ = k; |
| 1252 | 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. | |
| 3 | const Terminal = @This(); | |
| 4 | ||
| 5 | const builtin = @import("builtin"); | |
| 6 | const is_windows = builtin.os.tag == .windows; | |
| 7 | ||
| 8 | const std = @import("std"); | |
| 9 | const Io = std.Io; | |
| 10 | const File = std.Io.File; | |
| 11 | ||
| 12 | writer: *Io.Writer, | |
| 13 | mode: Mode, | |
| 14 | ||
| 15 | pub 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 | ||
| 37 | pub 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 | ||
| 81 | pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; | |
| 82 | ||
| 83 | pub 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 | 3 | const builtin = @import("builtin"); |
| 4 | 4 | const native_os = builtin.os.tag; |
| 5 | 5 | const is_windows = native_os == .windows; |
| 6 | const windows = std.os.windows; | |
| 7 | const ws2_32 = std.os.windows.ws2_32; | |
| 6 | const is_darwin = native_os.isDarwin(); | |
| 8 | 7 | const is_debug = builtin.mode == .Debug; |
| 9 | 8 | |
| 10 | 9 | const std = @import("../std.zig"); |
| 11 | 10 | const Io = std.Io; |
| 12 | 11 | const net = std.Io.net; |
| 12 | const File = std.Io.File; | |
| 13 | const Dir = std.Io.Dir; | |
| 13 | 14 | const HostName = std.Io.net.HostName; |
| 14 | 15 | const IpAddress = std.Io.net.IpAddress; |
| 15 | 16 | const Allocator = std.mem.Allocator; |
| 16 | 17 | const Alignment = std.mem.Alignment; |
| 17 | 18 | const assert = std.debug.assert; |
| 18 | 19 | const posix = std.posix; |
| 20 | const windows = std.os.windows; | |
| 21 | const ws2_32 = std.os.windows.ws2_32; | |
| 19 | 22 | |
| 20 | 23 | /// Thread-safe. |
| 21 | 24 | allocator: Allocator, |
| ... | ... | @@ -26,23 +29,7 @@ join_requested: bool = false, |
| 26 | 29 | stack_size: usize, |
| 27 | 30 | /// All threads are spawned detached; this is how we wait until they all exit. |
| 28 | 31 | wait_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`. | |
| 39 | 32 | async_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`. | |
| 46 | 33 | concurrent_limit: Io.Limit = .unlimited, |
| 47 | 34 | /// Error from calling `std.Thread.getCpuCount` in `init`. |
| 48 | 35 | cpu_count_error: ?std.Thread.CpuCountError, |
| ... | ... | @@ -52,17 +39,7 @@ cpu_count_error: ?std.Thread.CpuCountError, |
| 52 | 39 | busy_count: usize = 0, |
| 53 | 40 | main_thread: Thread, |
| 54 | 41 | pid: Pid = .unknown, |
| 55 | /// When a cancel request is made, blocking syscalls can be unblocked by | |
| 56 | /// issuing a signal. However, if the signal arrives after the check and before | |
| 57 | /// the syscall instruction, it is missed. | |
| 58 | /// | |
| 59 | /// This option solves the race condition by retrying the signal delivery | |
| 60 | /// until it is acknowledged, with an exponential backoff. | |
| 61 | /// | |
| 62 | /// Unfortunately, trying again until the cancellation request is acknowledged | |
| 63 | /// has been observed to be relatively slow, and usually strong cancellation | |
| 64 | /// guarantees are not needed, so this defaults to off. | |
| 65 | robust_cancel: RobustCancel = .disabled, | |
| 42 | robust_cancel: RobustCancel, | |
| 66 | 43 | |
| 67 | 44 | wsa: if (is_windows) Wsa else struct {} = .{}, |
| 68 | 45 | |
| ... | ... | @@ -70,6 +47,64 @@ have_signal_handler: bool, |
| 70 | 47 | old_sig_io: if (have_sig_io) posix.Sigaction else void, |
| 71 | 48 | old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, |
| 72 | 49 | |
| 50 | use_sendfile: UseSendfile = .default, | |
| 51 | use_copy_file_range: UseCopyFileRange = .default, | |
| 52 | use_fcopyfile: UseFcopyfile = .default, | |
| 53 | use_fchmodat2: UseFchmodat2 = .default, | |
| 54 | ||
| 55 | stderr_writer: File.Writer = .{ | |
| 56 | .io = undefined, | |
| 57 | .interface = Io.File.Writer.initInterface(&.{}), | |
| 58 | .file = if (is_windows) undefined else .stderr(), | |
| 59 | .mode = .streaming, | |
| 60 | }, | |
| 61 | stderr_mode: Io.Terminal.Mode = .no_color, | |
| 62 | stderr_writer_initialized: bool = false, | |
| 63 | ||
| 64 | argv0: Argv0, | |
| 65 | environ: Environ, | |
| 66 | ||
| 67 | pub const Argv0 = switch (native_os) { | |
| 68 | .openbsd, .haiku => struct { | |
| 69 | value: ?[*:0]const u8 = null, | |
| 70 | }, | |
| 71 | else => struct {}, | |
| 72 | }; | |
| 73 | ||
| 74 | pub 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 | ||
| 73 | 108 | pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { |
| 74 | 109 | enabled, |
| 75 | 110 | disabled, |
| ... | ... | @@ -82,6 +117,42 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) { |
| 82 | 117 | _, |
| 83 | 118 | } else enum(u0) { unknown = 0 }; |
| 84 | 119 | |
| 120 | pub 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 | ||
| 129 | pub 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 | ||
| 138 | pub 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 | ||
| 147 | pub 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 | ||
| 85 | 156 | const Thread = struct { |
| 86 | 157 | /// The value that needs to be passed to pthread_kill or tgkill in order to |
| 87 | 158 | /// send a signal. |
| ... | ... | @@ -164,13 +235,6 @@ const Thread = struct { |
| 164 | 235 | ) orelse return; |
| 165 | 236 | } |
| 166 | 237 | |
| 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 | 238 | fn currentSignalId() SignaleeId { |
| 175 | 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 | 293 | .INTR => {}, // caller's responsibility to retry |
| 230 | 294 | .AGAIN => {}, // ptr.* != expect |
| 231 | 295 | .INVAL => {}, // possibly timeout overflow |
| 232 | .TIMEDOUT => {}, // timeout | |
| 296 | .TIMEDOUT => {}, | |
| 233 | 297 | .FAULT => recoverableOsBugDetected(), // ptr was invalid |
| 234 | 298 | else => recoverableOsBugDetected(), |
| 235 | 299 | } |
| ... | ... | @@ -308,18 +372,25 @@ const Thread = struct { |
| 308 | 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 | } |
| 314 | 385 | |
| 315 | 386 | fn futexWake(ptr: *const u32, max_waiters: u32) void { |
| 316 | 387 | @branchHint(.cold); |
| 388 | assert(max_waiters != 0); | |
| 317 | 389 | |
| 318 | 390 | if (builtin.single_threaded) return; // nothing to wake up |
| 319 | 391 | |
| 320 | 392 | if (builtin.cpu.arch.isWasm()) { |
| 321 | 393 | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| 322 | assert(max_waiters != 0); | |
| 323 | 394 | const woken_count = asm volatile ( |
| 324 | 395 | \\local.get %[ptr] |
| 325 | 396 | \\local.get %[waiters] |
| ... | ... | @@ -364,7 +435,6 @@ const Thread = struct { |
| 364 | 435 | } |
| 365 | 436 | }, |
| 366 | 437 | .windows => { |
| 367 | assert(max_waiters != 0); | |
| 368 | 438 | switch (max_waiters) { |
| 369 | 439 | 1 => windows.ntdll.RtlWakeAddressSingle(ptr), |
| 370 | 440 | else => windows.ntdll.RtlWakeAddressAll(ptr), |
| ... | ... | @@ -385,7 +455,11 @@ const Thread = struct { |
| 385 | 455 | 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 | 579 | } |
| 506 | 580 | }; |
| 507 | 581 | |
| 582 | pub 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 | 623 | /// Related: |
| 509 | 624 | /// * `init_single_threaded` |
| 510 | 625 | pub fn init( |
| ... | ... | @@ -516,6 +631,7 @@ pub fn init( |
| 516 | 631 | /// If these functions are avoided, then `Allocator.failing` may be passed |
| 517 | 632 | /// here. |
| 518 | 633 | gpa: Allocator, |
| 634 | options: InitOptions, | |
| 519 | 635 | ) Threaded { |
| 520 | 636 | if (builtin.single_threaded) return .init_single_threaded; |
| 521 | 637 | |
| ... | ... | @@ -523,8 +639,9 @@ pub fn init( |
| 523 | 639 | |
| 524 | 640 | var t: Threaded = .{ |
| 525 | 641 | .allocator = gpa, |
| 526 | .stack_size = std.Thread.SpawnConfig.default_stack_size, | |
| 527 | .async_limit = if (cpu_count) |n| .limited(n - 1) else |_| .nothing, | |
| 642 | .stack_size = options.stack_size, | |
| 643 | .async_limit = options.async_limit orelse if (cpu_count) |n| .limited(n - 1) else |_| .nothing, | |
| 644 | .concurrent_limit = options.concurrent_limit, | |
| 528 | 645 | .cpu_count_error = if (cpu_count) |_| null else |e| e, |
| 529 | 646 | .old_sig_io = undefined, |
| 530 | 647 | .old_sig_pipe = undefined, |
| ... | ... | @@ -532,8 +649,11 @@ pub fn init( |
| 532 | 649 | .main_thread = .{ |
| 533 | 650 | .signal_id = Thread.currentSignalId(), |
| 534 | 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 | }; |
| 538 | 658 | |
| 539 | 659 | if (posix.Sigaction != void) { |
| ... | ... | @@ -570,10 +690,26 @@ pub const init_single_threaded: Threaded = .{ |
| 570 | 690 | .main_thread = .{ |
| 571 | 691 | .signal_id = undefined, |
| 572 | 692 | .current_closure = null, |
| 573 | .cancel_protection = undefined, | |
| 693 | .cancel_protection = .unblocked, | |
| 574 | 694 | }, |
| 695 | .robust_cancel = .disabled, | |
| 696 | .argv0 = .{}, | |
| 697 | .environ = .{}, | |
| 575 | 698 | }; |
| 576 | 699 | |
| 700 | var 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. | |
| 711 | pub const global_single_threaded: *Threaded = &global_single_threaded_instance; | |
| 712 | ||
| 577 | 713 | pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { |
| 578 | 714 | t.mutex.lock(); |
| 579 | 715 | defer t.mutex.unlock(); |
| ... | ... | @@ -607,7 +743,7 @@ fn worker(t: *Threaded) void { |
| 607 | 743 | var thread: Thread = .{ |
| 608 | 744 | .signal_id = Thread.currentSignalId(), |
| 609 | 745 | .current_closure = null, |
| 610 | .cancel_protection = undefined, | |
| 746 | .cancel_protection = .unblocked, | |
| 611 | 747 | }; |
| 612 | 748 | Thread.current = &thread; |
| 613 | 749 | |
| ... | ... | @@ -652,25 +788,64 @@ pub fn io(t: *Threaded) Io { |
| 652 | 788 | .futexWaitUncancelable = futexWaitUncancelable, |
| 653 | 789 | .futexWake = futexWake, |
| 654 | 790 | |
| 655 | .dirMake = dirMake, | |
| 656 | .dirMakePath = dirMakePath, | |
| 657 | .dirMakeOpenPath = dirMakeOpenPath, | |
| 791 | .dirCreateDir = dirCreateDir, | |
| 792 | .dirCreateDirPath = dirCreateDirPath, | |
| 793 | .dirCreateDirPathOpen = dirCreateDirPathOpen, | |
| 658 | 794 | .dirStat = dirStat, |
| 659 | .dirStatPath = dirStatPath, | |
| 660 | .fileStat = fileStat, | |
| 795 | .dirStatFile = dirStatFile, | |
| 661 | 796 | .dirAccess = dirAccess, |
| 662 | 797 | .dirCreateFile = dirCreateFile, |
| 663 | 798 | .dirOpenFile = dirOpenFile, |
| 664 | 799 | .dirOpenDir = dirOpenDir, |
| 665 | 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 | 819 | .fileClose = fileClose, |
| 667 | 820 | .fileWriteStreaming = fileWriteStreaming, |
| 668 | 821 | .fileWritePositional = fileWritePositional, |
| 822 | .fileWriteFileStreaming = fileWriteFileStreaming, | |
| 823 | .fileWriteFilePositional = fileWriteFilePositional, | |
| 669 | 824 | .fileReadStreaming = fileReadStreaming, |
| 670 | 825 | .fileReadPositional = fileReadPositional, |
| 671 | 826 | .fileSeekBy = fileSeekBy, |
| 672 | 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, | |
| 674 | 849 | |
| 675 | 850 | .now = now, |
| 676 | 851 | .sleep = sleep, |
| ... | ... | @@ -708,6 +883,7 @@ pub fn io(t: *Threaded) Io { |
| 708 | 883 | .windows => netWriteWindows, |
| 709 | 884 | else => netWritePosix, |
| 710 | 885 | }, |
| 886 | .netWriteFile = netWriteFile, | |
| 711 | 887 | .netSend = switch (native_os) { |
| 712 | 888 | .windows => netSendWindows, |
| 713 | 889 | else => netSendPosix, |
| ... | ... | @@ -748,25 +924,64 @@ pub fn ioBasic(t: *Threaded) Io { |
| 748 | 924 | .futexWaitUncancelable = futexWaitUncancelable, |
| 749 | 925 | .futexWake = futexWake, |
| 750 | 926 | |
| 751 | .dirMake = dirMake, | |
| 752 | .dirMakePath = dirMakePath, | |
| 753 | .dirMakeOpenPath = dirMakeOpenPath, | |
| 927 | .dirCreateDir = dirCreateDir, | |
| 928 | .dirCreateDirPath = dirCreateDirPath, | |
| 929 | .dirCreateDirPathOpen = dirCreateDirPathOpen, | |
| 754 | 930 | .dirStat = dirStat, |
| 755 | .dirStatPath = dirStatPath, | |
| 756 | .fileStat = fileStat, | |
| 931 | .dirStatFile = dirStatFile, | |
| 757 | 932 | .dirAccess = dirAccess, |
| 758 | 933 | .dirCreateFile = dirCreateFile, |
| 759 | 934 | .dirOpenFile = dirOpenFile, |
| 760 | 935 | .dirOpenDir = dirOpenDir, |
| 761 | 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 | 955 | .fileClose = fileClose, |
| 763 | 956 | .fileWriteStreaming = fileWriteStreaming, |
| 764 | 957 | .fileWritePositional = fileWritePositional, |
| 958 | .fileWriteFileStreaming = fileWriteFileStreaming, | |
| 959 | .fileWriteFilePositional = fileWriteFilePositional, | |
| 765 | 960 | .fileReadStreaming = fileReadStreaming, |
| 766 | 961 | .fileReadPositional = fileReadPositional, |
| 767 | 962 | .fileSeekBy = fileSeekBy, |
| 768 | 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, | |
| 770 | 985 | |
| 771 | 986 | .now = now, |
| 772 | 987 | .sleep = sleep, |
| ... | ... | @@ -780,6 +995,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 780 | 995 | .netClose = netCloseUnavailable, |
| 781 | 996 | .netRead = netReadUnavailable, |
| 782 | 997 | .netWrite = netWriteUnavailable, |
| 998 | .netWriteFile = netWriteFileUnavailable, | |
| 783 | 999 | .netSend = netSendUnavailable, |
| 784 | 1000 | .netReceive = netReceiveUnavailable, |
| 785 | 1001 | .netInterfaceNameResolve = netInterfaceNameResolveUnavailable, |
| ... | ... | @@ -789,7 +1005,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 789 | 1005 | }; |
| 790 | 1006 | } |
| 791 | 1007 | |
| 792 | pub const socket_flags_unsupported = native_os.isDarwin() or native_os == .haiku; | |
| 1008 | pub const socket_flags_unsupported = is_darwin or native_os == .haiku; | |
| 793 | 1009 | const have_accept4 = !socket_flags_unsupported; |
| 794 | 1010 | const have_flock_open_flags = @hasField(posix.O, "EXLOCK"); |
| 795 | 1011 | const have_networking = native_os != .wasi; |
| ... | ... | @@ -805,12 +1021,47 @@ const have_preadv = switch (native_os) { |
| 805 | 1021 | }; |
| 806 | 1022 | const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO"); |
| 807 | 1023 | const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE"); |
| 1024 | const have_sendfile = if (builtin.link_libc) @TypeOf(std.c.sendfile) != void else native_os == .linux; | |
| 1025 | const have_copy_file_range = switch (native_os) { | |
| 1026 | .linux, .freebsd => true, | |
| 1027 | else => false, | |
| 1028 | }; | |
| 1029 | const have_fcopyfile = is_darwin; | |
| 1030 | const 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 })); | |
| 1033 | const have_fchmodat_flags = native_os != .linux or | |
| 1034 | (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); | |
| 1035 | ||
| 1036 | const have_fchown = switch (native_os) { | |
| 1037 | .wasi, .windows => false, | |
| 1038 | else => true, | |
| 1039 | }; | |
| 1040 | ||
| 1041 | const have_fchmod = switch (native_os) { | |
| 1042 | .windows => false, | |
| 1043 | .wasi => builtin.link_libc, | |
| 1044 | else => true, | |
| 1045 | }; | |
| 808 | 1046 | |
| 809 | 1047 | const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; |
| 810 | 1048 | const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; |
| 811 | 1049 | const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat; |
| 812 | 1050 | const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek; |
| 813 | 1051 | const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv; |
| 1052 | const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate; | |
| 1053 | const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev; | |
| 1054 | const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile; | |
| 1055 | const 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 | }); | |
| 1064 | const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux; | |
| 814 | 1065 | |
| 815 | 1066 | /// Trailing data: |
| 816 | 1067 | /// 1. context |
| ... | ... | @@ -1018,7 +1269,6 @@ const GroupClosure = struct { |
| 1018 | 1269 | const group = gc.group; |
| 1019 | 1270 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); |
| 1020 | 1271 | const event: *Io.Event = @ptrCast(&group.context); |
| 1021 | ||
| 1022 | 1272 | current_thread.current_closure = closure; |
| 1023 | 1273 | current_thread.cancel_protection = .unblocked; |
| 1024 | 1274 | |
| ... | ... | @@ -1304,6 +1554,7 @@ fn cancel( |
| 1304 | 1554 | } |
| 1305 | 1555 | |
| 1306 | 1556 | fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void { |
| 1557 | if (builtin.single_threaded) unreachable; // Deadlock. | |
| 1307 | 1558 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1308 | 1559 | const current_thread = Thread.getCurrent(t); |
| 1309 | 1560 | const t_io = ioBasic(t); |
| ... | ... | @@ -1311,37 +1562,30 @@ fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io. |
| 1311 | 1562 | const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null; |
| 1312 | 1563 | break :ns std.math.lossyCast(u64, d.raw.toNanoseconds()); |
| 1313 | 1564 | }; |
| 1314 | switch (native_os) { | |
| 1315 | .illumos, .netbsd, .openbsd => @panic("TODO"), | |
| 1316 | else => try current_thread.futexWaitTimed(ptr, expected, timeout_ns), | |
| 1317 | } | |
| 1565 | return Thread.futexWaitTimed(current_thread, ptr, expected, timeout_ns); | |
| 1318 | 1566 | } |
| 1319 | 1567 | |
| 1320 | 1568 | fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void { |
| 1569 | if (builtin.single_threaded) unreachable; // Deadlock. | |
| 1321 | 1570 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1322 | 1571 | _ = t; |
| 1323 | switch (native_os) { | |
| 1324 | .illumos, .netbsd, .openbsd => @panic("TODO"), | |
| 1325 | else => Thread.futexWaitUncancelable(ptr, expected), | |
| 1326 | } | |
| 1572 | Thread.futexWaitUncancelable(ptr, expected); | |
| 1327 | 1573 | } |
| 1328 | 1574 | |
| 1329 | 1575 | fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 1576 | if (builtin.single_threaded) unreachable; // Nothing to wake up. | |
| 1330 | 1577 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1331 | 1578 | _ = t; |
| 1332 | switch (native_os) { | |
| 1333 | .illumos, .netbsd, .openbsd => @panic("TODO"), | |
| 1334 | else => Thread.futexWake(ptr, max_waiters), | |
| 1335 | } | |
| 1579 | Thread.futexWake(ptr, max_waiters); | |
| 1336 | 1580 | } |
| 1337 | 1581 | |
| 1338 | const dirMake = switch (native_os) { | |
| 1339 | .windows => dirMakeWindows, | |
| 1340 | .wasi => dirMakeWasi, | |
| 1341 | else => dirMakePosix, | |
| 1582 | const dirCreateDir = switch (native_os) { | |
| 1583 | .windows => dirCreateDirWindows, | |
| 1584 | .wasi => dirCreateDirWasi, | |
| 1585 | else => dirCreateDirPosix, | |
| 1342 | 1586 | }; |
| 1343 | 1587 | |
| 1344 | fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { | |
| 1588 | fn dirCreateDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { | |
| 1345 | 1589 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1346 | 1590 | const current_thread = Thread.getCurrent(t); |
| 1347 | 1591 | |
| ... | ... | @@ -1350,7 +1594,7 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: |
| 1350 | 1594 | |
| 1351 | 1595 | try current_thread.beginSyscall(); |
| 1352 | 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 | 1598 | .SUCCESS => { |
| 1355 | 1599 | current_thread.endSyscall(); |
| 1356 | 1600 | return; |
| ... | ... | @@ -1359,7 +1603,6 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: |
| 1359 | 1603 | try current_thread.checkCancel(); |
| 1360 | 1604 | continue; |
| 1361 | 1605 | }, |
| 1362 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 1363 | 1606 | else => |e| { |
| 1364 | 1607 | current_thread.endSyscall(); |
| 1365 | 1608 | switch (e) { |
| ... | ... | @@ -1387,8 +1630,8 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: |
| 1387 | 1630 | } |
| 1388 | 1631 | } |
| 1389 | 1632 | |
| 1390 | fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { | |
| 1391 | if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, mode); | |
| 1633 | fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { | |
| 1634 | if (builtin.link_libc) return dirCreateDirPosix(userdata, dir, sub_path, permissions); | |
| 1392 | 1635 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1393 | 1636 | const current_thread = Thread.getCurrent(t); |
| 1394 | 1637 | try current_thread.beginSyscall(); |
| ... | ... | @@ -1402,7 +1645,6 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I |
| 1402 | 1645 | try current_thread.checkCancel(); |
| 1403 | 1646 | continue; |
| 1404 | 1647 | }, |
| 1405 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 1406 | 1648 | else => |e| { |
| 1407 | 1649 | current_thread.endSyscall(); |
| 1408 | 1650 | switch (e) { |
| ... | ... | @@ -1429,13 +1671,13 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I |
| 1429 | 1671 | } |
| 1430 | 1672 | } |
| 1431 | 1673 | |
| 1432 | fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { | |
| 1674 | fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { | |
| 1433 | 1675 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1434 | 1676 | const current_thread = Thread.getCurrent(t); |
| 1435 | 1677 | try current_thread.checkCancel(); |
| 1436 | 1678 | |
| 1437 | 1679 | const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); |
| 1438 | _ = mode; | |
| 1680 | _ = permissions; // TODO use this value | |
| 1439 | 1681 | const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{ |
| 1440 | 1682 | .dir = dir.handle, |
| 1441 | 1683 | .access_mask = .{ |
| ... | ... | @@ -1455,62 +1697,75 @@ fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode |
| 1455 | 1697 | windows.CloseHandle(sub_dir_handle); |
| 1456 | 1698 | } |
| 1457 | 1699 | |
| 1458 | const dirMakePath = switch (native_os) { | |
| 1459 | .windows => dirMakePathWindows, | |
| 1460 | else => dirMakePathPosix, | |
| 1461 | }; | |
| 1462 | ||
| 1463 | fn dirMakePathPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { | |
| 1700 | fn dirCreateDirPath( | |
| 1701 | userdata: ?*anyopaque, | |
| 1702 | dir: Dir, | |
| 1703 | sub_path: []const u8, | |
| 1704 | permissions: Dir.Permissions, | |
| 1705 | ) Dir.CreateDirPathError!Dir.CreatePathStatus { | |
| 1464 | 1706 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1465 | _ = t; | |
| 1466 | _ = dir; | |
| 1467 | _ = sub_path; | |
| 1468 | _ = mode; | |
| 1469 | @panic("TODO implement dirMakePathPosix"); | |
| 1470 | } | |
| 1471 | 1707 | |
| 1472 | fn dirMakePathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { | |
| 1473 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 1474 | _ = t; | |
| 1475 | _ = dir; | |
| 1476 | _ = sub_path; | |
| 1477 | _ = mode; | |
| 1478 | @panic("TODO implement dirMakePathWindows"); | |
| 1708 | var it = std.fs.path.componentIterator(sub_path); | |
| 1709 | var status: Dir.CreatePathStatus = .existed; | |
| 1710 | var component = it.last() orelse return error.BadPathName; | |
| 1711 | while (true) { | |
| 1712 | if (dirCreateDir(t, dir, component.path, permissions)) |_| { | |
| 1713 | status = .created; | |
| 1714 | } else |err| switch (err) { | |
| 1715 | error.PathAlreadyExists => { | |
| 1716 | // stat the file and return an error if it's not a directory | |
| 1717 | // this is important because otherwise a dangling symlink | |
| 1718 | // could cause an infinite loop | |
| 1719 | const fstat = try dirStatFile(t, dir, component.path, .{}); | |
| 1720 | if (fstat.kind != .directory) return error.NotDir; | |
| 1721 | }, | |
| 1722 | error.FileNotFound => |e| { | |
| 1723 | component = it.previous() orelse return e; | |
| 1724 | continue; | |
| 1725 | }, | |
| 1726 | else => |e| return e, | |
| 1727 | } | |
| 1728 | component = it.next() orelse return status; | |
| 1729 | } | |
| 1479 | 1730 | } |
| 1480 | 1731 | |
| 1481 | const dirMakeOpenPath = switch (native_os) { | |
| 1482 | .windows => dirMakeOpenPathWindows, | |
| 1483 | .wasi => dirMakeOpenPathWasi, | |
| 1484 | else => dirMakeOpenPathPosix, | |
| 1732 | const dirCreateDirPathOpen = switch (native_os) { | |
| 1733 | .windows => dirCreateDirPathOpenWindows, | |
| 1734 | .wasi => dirCreateDirPathOpenWasi, | |
| 1735 | else => dirCreateDirPathOpenPosix, | |
| 1485 | 1736 | }; |
| 1486 | 1737 | |
| 1487 | fn dirMakeOpenPathPosix( | |
| 1738 | fn dirCreateDirPathOpenPosix( | |
| 1488 | 1739 | userdata: ?*anyopaque, |
| 1489 | dir: Io.Dir, | |
| 1740 | dir: Dir, | |
| 1490 | 1741 | sub_path: []const u8, |
| 1491 | options: Io.Dir.OpenOptions, | |
| 1492 | ) Io.Dir.MakeOpenPathError!Io.Dir { | |
| 1742 | permissions: Dir.Permissions, | |
| 1743 | options: Dir.OpenOptions, | |
| 1744 | ) Dir.CreateDirPathOpenError!Dir { | |
| 1493 | 1745 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1494 | 1746 | const t_io = ioBasic(t); |
| 1495 | 1747 | return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) { |
| 1496 | 1748 | error.FileNotFound => { |
| 1497 | try dir.makePath(t_io, sub_path); | |
| 1749 | _ = try dir.createDirPathStatus(t_io, sub_path, permissions); | |
| 1498 | 1750 | return dirOpenDirPosix(t, dir, sub_path, options); |
| 1499 | 1751 | }, |
| 1500 | 1752 | else => |e| return e, |
| 1501 | 1753 | }; |
| 1502 | 1754 | } |
| 1503 | 1755 | |
| 1504 | fn dirMakeOpenPathWindows( | |
| 1756 | fn dirCreateDirPathOpenWindows( | |
| 1505 | 1757 | userdata: ?*anyopaque, |
| 1506 | dir: Io.Dir, | |
| 1758 | dir: Dir, | |
| 1507 | 1759 | sub_path: []const u8, |
| 1508 | options: Io.Dir.OpenOptions, | |
| 1509 | ) Io.Dir.MakeOpenPathError!Io.Dir { | |
| 1760 | permissions: Dir.Permissions, | |
| 1761 | options: Dir.OpenOptions, | |
| 1762 | ) Dir.CreateDirPathOpenError!Dir { | |
| 1510 | 1763 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1511 | 1764 | const current_thread = Thread.getCurrent(t); |
| 1512 | 1765 | const w = windows; |
| 1513 | 1766 | |
| 1767 | _ = permissions; // TODO apply these permissions | |
| 1768 | ||
| 1514 | 1769 | var it = std.fs.path.componentIterator(sub_path); |
| 1515 | 1770 | // If there are no components in the path, then create a dummy component with the full path. |
| 1516 | 1771 | var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{ |
| ... | ... | @@ -1526,7 +1781,7 @@ fn dirMakeOpenPathWindows( |
| 1526 | 1781 | const is_last = it.peekNext() == null; |
| 1527 | 1782 | const create_disposition: w.FILE.CREATE_DISPOSITION = if (is_last) .OPEN_IF else .CREATE; |
| 1528 | 1783 | |
| 1529 | var result: Io.Dir = .{ .handle = undefined }; | |
| 1784 | var result: Dir = .{ .handle = undefined }; | |
| 1530 | 1785 | |
| 1531 | 1786 | const path_len_bytes: u16 = @intCast(sub_path_w.len * 2); |
| 1532 | 1787 | var nt_name: w.UNICODE_STRING = .{ |
| ... | ... | @@ -1584,16 +1839,10 @@ fn dirMakeOpenPathWindows( |
| 1584 | 1839 | // stat the file and return an error if it's not a directory |
| 1585 | 1840 | // this is important because otherwise a dangling symlink |
| 1586 | 1841 | // could cause an infinite loop |
| 1587 | check_dir: { | |
| 1588 | // workaround for windows, see https://github.com/ziglang/zig/issues/16738 | |
| 1589 | const fstat = dirStatPathWindows(t, dir, component.path, .{ | |
| 1590 | .follow_symlinks = options.follow_symlinks, | |
| 1591 | }) catch |stat_err| switch (stat_err) { | |
| 1592 | error.IsDir => break :check_dir, | |
| 1593 | else => |e| return e, | |
| 1594 | }; | |
| 1595 | if (fstat.kind != .directory) return error.NotDir; | |
| 1596 | } | |
| 1842 | const fstat = try dirStatFileWindows(t, dir, component.path, .{ | |
| 1843 | .follow_symlinks = options.follow_symlinks, | |
| 1844 | }); | |
| 1845 | if (fstat.kind != .directory) return error.NotDir; | |
| 1597 | 1846 | |
| 1598 | 1847 | component = it.next().?; |
| 1599 | 1848 | continue; |
| ... | ... | @@ -1616,46 +1865,51 @@ fn dirMakeOpenPathWindows( |
| 1616 | 1865 | } |
| 1617 | 1866 | } |
| 1618 | 1867 | |
| 1619 | fn dirMakeOpenPathWasi( | |
| 1868 | fn dirCreateDirPathOpenWasi( | |
| 1620 | 1869 | userdata: ?*anyopaque, |
| 1621 | dir: Io.Dir, | |
| 1870 | dir: Dir, | |
| 1622 | 1871 | sub_path: []const u8, |
| 1623 | options: Io.Dir.OpenOptions, | |
| 1624 | ) Io.Dir.MakeOpenPathError!Io.Dir { | |
| 1872 | permissions: Dir.Permissions, | |
| 1873 | options: Dir.OpenOptions, | |
| 1874 | ) Dir.CreateDirPathOpenError!Dir { | |
| 1625 | 1875 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1626 | 1876 | const t_io = ioBasic(t); |
| 1627 | 1877 | return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) { |
| 1628 | 1878 | error.FileNotFound => { |
| 1629 | try dir.makePath(t_io, sub_path); | |
| 1879 | _ = try dir.createDirPathStatus(t_io, sub_path, permissions); | |
| 1630 | 1880 | return dirOpenDirWasi(t, dir, sub_path, options); |
| 1631 | 1881 | }, |
| 1632 | 1882 | else => |e| return e, |
| 1633 | 1883 | }; |
| 1634 | 1884 | } |
| 1635 | 1885 | |
| 1636 | fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat { | |
| 1886 | fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { | |
| 1637 | 1887 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1638 | _ = t; | |
| 1639 | _ = dir; | |
| 1640 | @panic("TODO implement dirStat"); | |
| 1888 | const file: File = .{ .handle = dir.handle }; | |
| 1889 | return fileStat(t, file); | |
| 1641 | 1890 | } |
| 1642 | 1891 | |
| 1643 | const dirStatPath = switch (native_os) { | |
| 1644 | .linux => dirStatPathLinux, | |
| 1645 | .windows => dirStatPathWindows, | |
| 1646 | .wasi => dirStatPathWasi, | |
| 1647 | else => dirStatPathPosix, | |
| 1892 | const dirStatFile = switch (native_os) { | |
| 1893 | .linux => dirStatFileLinux, | |
| 1894 | .windows => dirStatFileWindows, | |
| 1895 | .wasi => dirStatFileWasi, | |
| 1896 | else => dirStatFilePosix, | |
| 1648 | 1897 | }; |
| 1649 | 1898 | |
| 1650 | fn dirStatPathLinux( | |
| 1899 | fn dirStatFileLinux( | |
| 1651 | 1900 | userdata: ?*anyopaque, |
| 1652 | dir: Io.Dir, | |
| 1901 | dir: Dir, | |
| 1653 | 1902 | sub_path: []const u8, |
| 1654 | options: Io.Dir.StatPathOptions, | |
| 1655 | ) Io.Dir.StatPathError!Io.File.Stat { | |
| 1903 | options: Dir.StatFileOptions, | |
| 1904 | ) Dir.StatFileError!File.Stat { | |
| 1656 | 1905 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1657 | 1906 | const current_thread = Thread.getCurrent(t); |
| 1658 | 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; | |
| 1659 | 1913 | |
| 1660 | 1914 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 1661 | 1915 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| ... | ... | @@ -1666,30 +1920,15 @@ fn dirStatPathLinux( |
| 1666 | 1920 | try current_thread.beginSyscall(); |
| 1667 | 1921 | while (true) { |
| 1668 | 1922 | var statx = std.mem.zeroes(linux.Statx); |
| 1669 | const rc = linux.statx( | |
| 1670 | dir.handle, | |
| 1671 | sub_path_posix, | |
| 1672 | flags, | |
| 1673 | .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true }, | |
| 1674 | &statx, | |
| 1675 | ); | |
| 1676 | switch (linux.errno(rc)) { | |
| 1923 | switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_mask, &statx))) { | |
| 1677 | 1924 | .SUCCESS => { |
| 1678 | 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 | 1926 | return statFromLinux(&statx); |
| 1687 | 1927 | }, |
| 1688 | 1928 | .INTR => { |
| 1689 | 1929 | try current_thread.checkCancel(); |
| 1690 | 1930 | continue; |
| 1691 | 1931 | }, |
| 1692 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 1693 | 1932 | else => |e| { |
| 1694 | 1933 | current_thread.endSyscall(); |
| 1695 | 1934 | switch (e) { |
| ... | ... | @@ -1709,12 +1948,12 @@ fn dirStatPathLinux( |
| 1709 | 1948 | } |
| 1710 | 1949 | } |
| 1711 | 1950 | |
| 1712 | fn dirStatPathPosix( | |
| 1951 | fn dirStatFilePosix( | |
| 1713 | 1952 | userdata: ?*anyopaque, |
| 1714 | dir: Io.Dir, | |
| 1953 | dir: Dir, | |
| 1715 | 1954 | sub_path: []const u8, |
| 1716 | options: Io.Dir.StatPathOptions, | |
| 1717 | ) Io.Dir.StatPathError!Io.File.Stat { | |
| 1955 | options: Dir.StatFileOptions, | |
| 1956 | ) Dir.StatFileError!File.Stat { | |
| 1718 | 1957 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1719 | 1958 | const current_thread = Thread.getCurrent(t); |
| 1720 | 1959 | |
| ... | ... | @@ -1723,10 +1962,14 @@ fn dirStatPathPosix( |
| 1723 | 1962 | |
| 1724 | 1963 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; |
| 1725 | 1964 | |
| 1965 | return posixStatFile(current_thread, dir.handle, sub_path_posix, flags); | |
| 1966 | } | |
| 1967 | ||
| 1968 | fn posixStatFile(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatFileError!File.Stat { | |
| 1726 | 1969 | try current_thread.beginSyscall(); |
| 1727 | 1970 | while (true) { |
| 1728 | 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 | 1973 | .SUCCESS => { |
| 1731 | 1974 | current_thread.endSyscall(); |
| 1732 | 1975 | return statFromPosix(&stat); |
| ... | ... | @@ -1735,7 +1978,6 @@ fn dirStatPathPosix( |
| 1735 | 1978 | try current_thread.checkCancel(); |
| 1736 | 1979 | continue; |
| 1737 | 1980 | }, |
| 1738 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 1739 | 1981 | else => |e| { |
| 1740 | 1982 | current_thread.endSyscall(); |
| 1741 | 1983 | switch (e) { |
| ... | ... | @@ -1757,12 +1999,12 @@ fn dirStatPathPosix( |
| 1757 | 1999 | } |
| 1758 | 2000 | } |
| 1759 | 2001 | |
| 1760 | fn dirStatPathWindows( | |
| 2002 | fn dirStatFileWindows( | |
| 1761 | 2003 | userdata: ?*anyopaque, |
| 1762 | dir: Io.Dir, | |
| 2004 | dir: Dir, | |
| 1763 | 2005 | sub_path: []const u8, |
| 1764 | options: Io.Dir.StatPathOptions, | |
| 1765 | ) Io.Dir.StatPathError!Io.File.Stat { | |
| 2006 | options: Dir.StatFileOptions, | |
| 2007 | ) Dir.StatFileError!File.Stat { | |
| 1766 | 2008 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1767 | 2009 | const file = try dirOpenFileWindows(t, dir, sub_path, .{ |
| 1768 | 2010 | .follow_symlinks = options.follow_symlinks, |
| ... | ... | @@ -1771,13 +2013,13 @@ fn dirStatPathWindows( |
| 1771 | 2013 | return fileStatWindows(t, file); |
| 1772 | 2014 | } |
| 1773 | 2015 | |
| 1774 | fn dirStatPathWasi( | |
| 2016 | fn dirStatFileWasi( | |
| 1775 | 2017 | userdata: ?*anyopaque, |
| 1776 | dir: Io.Dir, | |
| 2018 | dir: Dir, | |
| 1777 | 2019 | sub_path: []const u8, |
| 1778 | options: Io.Dir.StatPathOptions, | |
| 1779 | ) Io.Dir.StatPathError!Io.File.Stat { | |
| 1780 | if (builtin.link_libc) return dirStatPathPosix(userdata, dir, sub_path, options); | |
| 2020 | options: Dir.StatFileOptions, | |
| 2021 | ) Dir.StatFileError!File.Stat { | |
| 2022 | if (builtin.link_libc) return dirStatFilePosix(userdata, dir, sub_path, options); | |
| 1781 | 2023 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1782 | 2024 | const current_thread = Thread.getCurrent(t); |
| 1783 | 2025 | const wasi = std.os.wasi; |
| ... | ... | @@ -1796,7 +2038,6 @@ fn dirStatPathWasi( |
| 1796 | 2038 | try current_thread.checkCancel(); |
| 1797 | 2039 | continue; |
| 1798 | 2040 | }, |
| 1799 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 1800 | 2041 | else => |e| { |
| 1801 | 2042 | current_thread.endSyscall(); |
| 1802 | 2043 | switch (e) { |
| ... | ... | @@ -1817,6 +2058,51 @@ fn dirStatPathWasi( |
| 1817 | 2058 | } |
| 1818 | 2059 | } |
| 1819 | 2060 | |
| 2061 | fn 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 | ||
| 1820 | 2106 | const fileStat = switch (native_os) { |
| 1821 | 2107 | .linux => fileStatLinux, |
| 1822 | 2108 | .windows => fileStatWindows, |
| ... | ... | @@ -1824,7 +2110,7 @@ const fileStat = switch (native_os) { |
| 1824 | 2110 | else => fileStatPosix, |
| 1825 | 2111 | }; |
| 1826 | 2112 | |
| 1827 | fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { | |
| 2113 | fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { | |
| 1828 | 2114 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1829 | 2115 | const current_thread = Thread.getCurrent(t); |
| 1830 | 2116 | |
| ... | ... | @@ -1842,7 +2128,6 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File |
| 1842 | 2128 | try current_thread.checkCancel(); |
| 1843 | 2129 | continue; |
| 1844 | 2130 | }, |
| 1845 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 1846 | 2131 | else => |e| { |
| 1847 | 2132 | current_thread.endSyscall(); |
| 1848 | 2133 | switch (e) { |
| ... | ... | @@ -1857,38 +2142,28 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File |
| 1857 | 2142 | } |
| 1858 | 2143 | } |
| 1859 | 2144 | |
| 1860 | fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { | |
| 2145 | fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { | |
| 1861 | 2146 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1862 | 2147 | const current_thread = Thread.getCurrent(t); |
| 1863 | 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; | |
| 1864 | 2154 | |
| 1865 | 2155 | try current_thread.beginSyscall(); |
| 1866 | 2156 | while (true) { |
| 1867 | 2157 | var statx = std.mem.zeroes(linux.Statx); |
| 1868 | const rc = linux.statx( | |
| 1869 | file.handle, | |
| 1870 | "", | |
| 1871 | linux.AT.EMPTY_PATH, | |
| 1872 | .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true }, | |
| 1873 | &statx, | |
| 1874 | ); | |
| 1875 | switch (linux.errno(rc)) { | |
| 2158 | switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_mask, &statx))) { | |
| 1876 | 2159 | .SUCCESS => { |
| 1877 | 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 | 2161 | return statFromLinux(&statx); |
| 1886 | 2162 | }, |
| 1887 | 2163 | .INTR => { |
| 1888 | 2164 | try current_thread.checkCancel(); |
| 1889 | 2165 | continue; |
| 1890 | 2166 | }, |
| 1891 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 1892 | 2167 | else => |e| { |
| 1893 | 2168 | current_thread.endSyscall(); |
| 1894 | 2169 | switch (e) { |
| ... | ... | @@ -1908,7 +2183,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File |
| 1908 | 2183 | } |
| 1909 | 2184 | } |
| 1910 | 2185 | |
| 1911 | fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { | |
| 2186 | fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { | |
| 1912 | 2187 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1913 | 2188 | const current_thread = Thread.getCurrent(t); |
| 1914 | 2189 | try current_thread.checkCancel(); |
| ... | ... | @@ -1922,14 +2197,14 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi |
| 1922 | 2197 | // size provided. This is treated as success because the type of variable-length information that this would be relevant for |
| 1923 | 2198 | // (name, volume name, etc) we don't care about. |
| 1924 | 2199 | .BUFFER_OVERFLOW => {}, |
| 1925 | .INVALID_PARAMETER => unreachable, | |
| 2200 | .INVALID_PARAMETER => |err| return windows.statusBug(err), | |
| 1926 | 2201 | .ACCESS_DENIED => return error.AccessDenied, |
| 1927 | 2202 | else => return windows.unexpectedStatus(rc), |
| 1928 | 2203 | } |
| 1929 | 2204 | return .{ |
| 1930 | 2205 | .inode = info.InternalInformation.IndexNumber, |
| 1931 | 2206 | .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)), |
| 1932 | .mode = 0, | |
| 2207 | .permissions = .default_file, | |
| 1933 | 2208 | .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: { |
| 1934 | 2209 | var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined; |
| 1935 | 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 | 2212 | .SUCCESS => {}, |
| 1938 | 2213 | // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors |
| 1939 | 2214 | // 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 | 2216 | .ACCESS_DENIED => return error.AccessDenied, |
| 1942 | 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 | 2226 | .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime), |
| 1952 | 2227 | .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime), |
| 1953 | 2228 | .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime), |
| 2229 | .nlink = 0, | |
| 1954 | 2230 | }; |
| 1955 | 2231 | } |
| 1956 | 2232 | |
| 1957 | fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { | |
| 2233 | fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { | |
| 1958 | 2234 | if (builtin.link_libc) return fileStatPosix(userdata, file); |
| 1959 | 2235 | |
| 1960 | 2236 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| ... | ... | @@ -1972,7 +2248,6 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File. |
| 1972 | 2248 | try current_thread.checkCancel(); |
| 1973 | 2249 | continue; |
| 1974 | 2250 | }, |
| 1975 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 1976 | 2251 | else => |e| { |
| 1977 | 2252 | current_thread.endSyscall(); |
| 1978 | 2253 | switch (e) { |
| ... | ... | @@ -1996,10 +2271,10 @@ const dirAccess = switch (native_os) { |
| 1996 | 2271 | |
| 1997 | 2272 | fn dirAccessPosix( |
| 1998 | 2273 | userdata: ?*anyopaque, |
| 1999 | dir: Io.Dir, | |
| 2274 | dir: Dir, | |
| 2000 | 2275 | sub_path: []const u8, |
| 2001 | options: Io.Dir.AccessOptions, | |
| 2002 | ) Io.Dir.AccessError!void { | |
| 2276 | options: Dir.AccessOptions, | |
| 2277 | ) Dir.AccessError!void { | |
| 2003 | 2278 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2004 | 2279 | const current_thread = Thread.getCurrent(t); |
| 2005 | 2280 | |
| ... | ... | @@ -2024,7 +2299,6 @@ fn dirAccessPosix( |
| 2024 | 2299 | try current_thread.checkCancel(); |
| 2025 | 2300 | continue; |
| 2026 | 2301 | }, |
| 2027 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2028 | 2302 | else => |e| { |
| 2029 | 2303 | current_thread.endSyscall(); |
| 2030 | 2304 | switch (e) { |
| ... | ... | @@ -2050,10 +2324,10 @@ fn dirAccessPosix( |
| 2050 | 2324 | |
| 2051 | 2325 | fn dirAccessWasi( |
| 2052 | 2326 | userdata: ?*anyopaque, |
| 2053 | dir: Io.Dir, | |
| 2327 | dir: Dir, | |
| 2054 | 2328 | sub_path: []const u8, |
| 2055 | options: Io.Dir.AccessOptions, | |
| 2056 | ) Io.Dir.AccessError!void { | |
| 2329 | options: Dir.AccessOptions, | |
| 2330 | ) Dir.AccessError!void { | |
| 2057 | 2331 | if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options); |
| 2058 | 2332 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2059 | 2333 | const current_thread = Thread.getCurrent(t); |
| ... | ... | @@ -2074,7 +2348,6 @@ fn dirAccessWasi( |
| 2074 | 2348 | try current_thread.checkCancel(); |
| 2075 | 2349 | continue; |
| 2076 | 2350 | }, |
| 2077 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2078 | 2351 | else => |e| { |
| 2079 | 2352 | current_thread.endSyscall(); |
| 2080 | 2353 | switch (e) { |
| ... | ... | @@ -2123,10 +2396,10 @@ fn dirAccessWasi( |
| 2123 | 2396 | |
| 2124 | 2397 | fn dirAccessWindows( |
| 2125 | 2398 | userdata: ?*anyopaque, |
| 2126 | dir: Io.Dir, | |
| 2399 | dir: Dir, | |
| 2127 | 2400 | sub_path: []const u8, |
| 2128 | options: Io.Dir.AccessOptions, | |
| 2129 | ) Io.Dir.AccessError!void { | |
| 2401 | options: Dir.AccessOptions, | |
| 2402 | ) Dir.AccessError!void { | |
| 2130 | 2403 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2131 | 2404 | const current_thread = Thread.getCurrent(t); |
| 2132 | 2405 | try current_thread.checkCancel(); |
| ... | ... | @@ -2175,10 +2448,10 @@ const dirCreateFile = switch (native_os) { |
| 2175 | 2448 | |
| 2176 | 2449 | fn dirCreateFilePosix( |
| 2177 | 2450 | userdata: ?*anyopaque, |
| 2178 | dir: Io.Dir, | |
| 2451 | dir: Dir, | |
| 2179 | 2452 | sub_path: []const u8, |
| 2180 | flags: Io.File.CreateFlags, | |
| 2181 | ) Io.File.OpenError!Io.File { | |
| 2453 | flags: File.CreateFlags, | |
| 2454 | ) File.OpenError!File { | |
| 2182 | 2455 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2183 | 2456 | const current_thread = Thread.getCurrent(t); |
| 2184 | 2457 | |
| ... | ... | @@ -2211,7 +2484,7 @@ fn dirCreateFilePosix( |
| 2211 | 2484 | |
| 2212 | 2485 | try current_thread.beginSyscall(); |
| 2213 | 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 | 2488 | switch (posix.errno(rc)) { |
| 2216 | 2489 | .SUCCESS => { |
| 2217 | 2490 | current_thread.endSyscall(); |
| ... | ... | @@ -2221,7 +2494,6 @@ fn dirCreateFilePosix( |
| 2221 | 2494 | try current_thread.checkCancel(); |
| 2222 | 2495 | continue; |
| 2223 | 2496 | }, |
| 2224 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2225 | 2497 | else => |e| { |
| 2226 | 2498 | current_thread.endSyscall(); |
| 2227 | 2499 | switch (e) { |
| ... | ... | @@ -2238,14 +2510,14 @@ fn dirCreateFilePosix( |
| 2238 | 2510 | .NFILE => return error.SystemFdQuotaExceeded, |
| 2239 | 2511 | .NODEV => return error.NoDevice, |
| 2240 | 2512 | .NOENT => return error.FileNotFound, |
| 2241 | .SRCH => return error.ProcessNotFound, | |
| 2513 | .SRCH => return error.FileNotFound, // Linux when accessing procfs. | |
| 2242 | 2514 | .NOMEM => return error.SystemResources, |
| 2243 | 2515 | .NOSPC => return error.NoSpaceLeft, |
| 2244 | 2516 | .NOTDIR => return error.NotDir, |
| 2245 | 2517 | .PERM => return error.PermissionDenied, |
| 2246 | 2518 | .EXIST => return error.PathAlreadyExists, |
| 2247 | 2519 | .BUSY => return error.DeviceBusy, |
| 2248 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 2520 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 2249 | 2521 | .AGAIN => return error.WouldBlock, |
| 2250 | 2522 | .TXTBSY => return error.FileBusy, |
| 2251 | 2523 | .NXIO => return error.NoDevice, |
| ... | ... | @@ -2276,7 +2548,6 @@ fn dirCreateFilePosix( |
| 2276 | 2548 | try current_thread.checkCancel(); |
| 2277 | 2549 | continue; |
| 2278 | 2550 | }, |
| 2279 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2280 | 2551 | else => |e| { |
| 2281 | 2552 | current_thread.endSyscall(); |
| 2282 | 2553 | switch (e) { |
| ... | ... | @@ -2284,7 +2555,7 @@ fn dirCreateFilePosix( |
| 2284 | 2555 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 2285 | 2556 | .NOLCK => return error.SystemResources, |
| 2286 | 2557 | .AGAIN => return error.WouldBlock, |
| 2287 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 2558 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 2288 | 2559 | else => |err| return posix.unexpectedErrno(err), |
| 2289 | 2560 | } |
| 2290 | 2561 | }, |
| ... | ... | @@ -2338,10 +2609,10 @@ fn dirCreateFilePosix( |
| 2338 | 2609 | |
| 2339 | 2610 | fn dirCreateFileWindows( |
| 2340 | 2611 | userdata: ?*anyopaque, |
| 2341 | dir: Io.Dir, | |
| 2612 | dir: Dir, | |
| 2342 | 2613 | sub_path: []const u8, |
| 2343 | flags: Io.File.CreateFlags, | |
| 2344 | ) Io.File.OpenError!Io.File { | |
| 2614 | flags: File.CreateFlags, | |
| 2615 | ) File.OpenError!File { | |
| 2345 | 2616 | const w = windows; |
| 2346 | 2617 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2347 | 2618 | const current_thread = Thread.getCurrent(t); |
| ... | ... | @@ -2367,35 +2638,42 @@ fn dirCreateFileWindows( |
| 2367 | 2638 | .OPEN_IF, |
| 2368 | 2639 | }); |
| 2369 | 2640 | errdefer w.CloseHandle(handle); |
| 2641 | ||
| 2370 | 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 | 2643 | const exclusive = switch (flags.lock) { |
| 2374 | 2644 | .none => return .{ .handle = handle }, |
| 2375 | 2645 | .shared => false, |
| 2376 | 2646 | .exclusive => true, |
| 2377 | 2647 | }; |
| 2378 | try w.LockFile( | |
| 2648 | const status = w.ntdll.NtLockFile( | |
| 2379 | 2649 | handle, |
| 2380 | 2650 | null, |
| 2381 | 2651 | null, |
| 2382 | 2652 | null, |
| 2383 | 2653 | &io_status_block, |
| 2384 | &range_off, | |
| 2385 | &range_len, | |
| 2654 | &windows_lock_range_off, | |
| 2655 | &windows_lock_range_len, | |
| 2386 | 2656 | null, |
| 2387 | 2657 | @intFromBool(flags.lock_nonblocking), |
| 2388 | 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 | 2668 | return .{ .handle = handle }; |
| 2391 | 2669 | } |
| 2392 | 2670 | |
| 2393 | 2671 | fn dirCreateFileWasi( |
| 2394 | 2672 | userdata: ?*anyopaque, |
| 2395 | dir: Io.Dir, | |
| 2673 | dir: Dir, | |
| 2396 | 2674 | sub_path: []const u8, |
| 2397 | flags: Io.File.CreateFlags, | |
| 2398 | ) Io.File.OpenError!Io.File { | |
| 2675 | flags: File.CreateFlags, | |
| 2676 | ) File.OpenError!File { | |
| 2399 | 2677 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2400 | 2678 | const current_thread = Thread.getCurrent(t); |
| 2401 | 2679 | const wasi = std.os.wasi; |
| ... | ... | @@ -2436,7 +2714,6 @@ fn dirCreateFileWasi( |
| 2436 | 2714 | try current_thread.checkCancel(); |
| 2437 | 2715 | continue; |
| 2438 | 2716 | }, |
| 2439 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2440 | 2717 | else => |e| { |
| 2441 | 2718 | current_thread.endSyscall(); |
| 2442 | 2719 | switch (e) { |
| ... | ... | @@ -2476,10 +2753,10 @@ const dirOpenFile = switch (native_os) { |
| 2476 | 2753 | |
| 2477 | 2754 | fn dirOpenFilePosix( |
| 2478 | 2755 | userdata: ?*anyopaque, |
| 2479 | dir: Io.Dir, | |
| 2756 | dir: Dir, | |
| 2480 | 2757 | sub_path: []const u8, |
| 2481 | flags: Io.File.OpenFlags, | |
| 2482 | ) Io.File.OpenError!Io.File { | |
| 2758 | flags: File.OpenFlags, | |
| 2759 | ) File.OpenError!File { | |
| 2483 | 2760 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2484 | 2761 | const current_thread = Thread.getCurrent(t); |
| 2485 | 2762 | |
| ... | ... | @@ -2490,6 +2767,7 @@ fn dirOpenFilePosix( |
| 2490 | 2767 | .wasi => .{ |
| 2491 | 2768 | .read = flags.mode != .write_only, |
| 2492 | 2769 | .write = flags.mode != .read_only, |
| 2770 | .NOFOLLOW = !flags.follow_symlinks, | |
| 2493 | 2771 | }, |
| 2494 | 2772 | else => .{ |
| 2495 | 2773 | .ACCMODE = switch (flags.mode) { |
| ... | ... | @@ -2497,11 +2775,13 @@ fn dirOpenFilePosix( |
| 2497 | 2775 | .write_only => .WRONLY, |
| 2498 | 2776 | .read_write => .RDWR, |
| 2499 | 2777 | }, |
| 2778 | .NOFOLLOW = !flags.follow_symlinks, | |
| 2500 | 2779 | }, |
| 2501 | 2780 | }; |
| 2502 | 2781 | if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; |
| 2503 | 2782 | if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; |
| 2504 | 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; | |
| 2505 | 2785 | |
| 2506 | 2786 | // Use the O locking flags if the os supports them to acquire the lock |
| 2507 | 2787 | // atomically. Note that the NONBLOCK flag is removed after the openat() |
| ... | ... | @@ -2530,7 +2810,6 @@ fn dirOpenFilePosix( |
| 2530 | 2810 | try current_thread.checkCancel(); |
| 2531 | 2811 | continue; |
| 2532 | 2812 | }, |
| 2533 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2534 | 2813 | else => |e| { |
| 2535 | 2814 | current_thread.endSyscall(); |
| 2536 | 2815 | switch (e) { |
| ... | ... | @@ -2547,14 +2826,14 @@ fn dirOpenFilePosix( |
| 2547 | 2826 | .NFILE => return error.SystemFdQuotaExceeded, |
| 2548 | 2827 | .NODEV => return error.NoDevice, |
| 2549 | 2828 | .NOENT => return error.FileNotFound, |
| 2550 | .SRCH => return error.ProcessNotFound, | |
| 2829 | .SRCH => return error.FileNotFound, // Linux when opening procfs files. | |
| 2551 | 2830 | .NOMEM => return error.SystemResources, |
| 2552 | 2831 | .NOSPC => return error.NoSpaceLeft, |
| 2553 | 2832 | .NOTDIR => return error.NotDir, |
| 2554 | 2833 | .PERM => return error.PermissionDenied, |
| 2555 | 2834 | .EXIST => return error.PathAlreadyExists, |
| 2556 | 2835 | .BUSY => return error.DeviceBusy, |
| 2557 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 2836 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 2558 | 2837 | .AGAIN => return error.WouldBlock, |
| 2559 | 2838 | .TXTBSY => return error.FileBusy, |
| 2560 | 2839 | .NXIO => return error.NoDevice, |
| ... | ... | @@ -2566,6 +2845,18 @@ fn dirOpenFilePosix( |
| 2566 | 2845 | }; |
| 2567 | 2846 | errdefer posix.close(fd); |
| 2568 | 2847 | |
| 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 | 2860 | if (have_flock and !have_flock_open_flags and flags.lock != .none) { |
| 2570 | 2861 | const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0; |
| 2571 | 2862 | const lock_flags = switch (flags.lock) { |
| ... | ... | @@ -2584,7 +2875,6 @@ fn dirOpenFilePosix( |
| 2584 | 2875 | try current_thread.checkCancel(); |
| 2585 | 2876 | continue; |
| 2586 | 2877 | }, |
| 2587 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2588 | 2878 | else => |e| { |
| 2589 | 2879 | current_thread.endSyscall(); |
| 2590 | 2880 | switch (e) { |
| ... | ... | @@ -2592,7 +2882,7 @@ fn dirOpenFilePosix( |
| 2592 | 2882 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 2593 | 2883 | .NOLCK => return error.SystemResources, |
| 2594 | 2884 | .AGAIN => return error.WouldBlock, |
| 2595 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 2885 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 2596 | 2886 | else => |err| return posix.unexpectedErrno(err), |
| 2597 | 2887 | } |
| 2598 | 2888 | }, |
| ... | ... | @@ -2613,7 +2903,6 @@ fn dirOpenFilePosix( |
| 2613 | 2903 | try current_thread.checkCancel(); |
| 2614 | 2904 | continue; |
| 2615 | 2905 | }, |
| 2616 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2617 | 2906 | else => |err| { |
| 2618 | 2907 | current_thread.endSyscall(); |
| 2619 | 2908 | return posix.unexpectedErrno(err); |
| ... | ... | @@ -2634,7 +2923,6 @@ fn dirOpenFilePosix( |
| 2634 | 2923 | try current_thread.checkCancel(); |
| 2635 | 2924 | continue; |
| 2636 | 2925 | }, |
| 2637 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2638 | 2926 | else => |err| { |
| 2639 | 2927 | current_thread.endSyscall(); |
| 2640 | 2928 | return posix.unexpectedErrno(err); |
| ... | ... | @@ -2648,10 +2936,10 @@ fn dirOpenFilePosix( |
| 2648 | 2936 | |
| 2649 | 2937 | fn dirOpenFileWindows( |
| 2650 | 2938 | userdata: ?*anyopaque, |
| 2651 | dir: Io.Dir, | |
| 2939 | dir: Dir, | |
| 2652 | 2940 | sub_path: []const u8, |
| 2653 | flags: Io.File.OpenFlags, | |
| 2654 | ) Io.File.OpenError!Io.File { | |
| 2941 | flags: File.OpenFlags, | |
| 2942 | ) File.OpenError!File { | |
| 2655 | 2943 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2656 | 2944 | const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); |
| 2657 | 2945 | const sub_path_w = sub_path_w_array.span(); |
| ... | ... | @@ -2663,10 +2951,11 @@ pub fn dirOpenFileWtf16( |
| 2663 | 2951 | t: *Threaded, |
| 2664 | 2952 | dir_handle: ?windows.HANDLE, |
| 2665 | 2953 | sub_path_w: [:0]const u16, |
| 2666 | flags: Io.File.OpenFlags, | |
| 2667 | ) Io.File.OpenError!Io.File { | |
| 2668 | if (std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; | |
| 2669 | if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; | |
| 2954 | flags: File.OpenFlags, | |
| 2955 | ) File.OpenError!File { | |
| 2956 | const allow_directory = flags.allow_directory and !flags.isWrite(); | |
| 2957 | if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; | |
| 2958 | if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; | |
| 2670 | 2959 | const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; |
| 2671 | 2960 | const current_thread = Thread.getCurrent(t); |
| 2672 | 2961 | const w = windows; |
| ... | ... | @@ -2711,7 +3000,7 @@ pub fn dirOpenFileWtf16( |
| 2711 | 3000 | .OPEN, |
| 2712 | 3001 | .{ |
| 2713 | 3002 | .IO = if (flags.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, |
| 2714 | .NON_DIRECTORY_FILE = true, | |
| 3003 | .NON_DIRECTORY_FILE = !allow_directory, | |
| 2715 | 3004 | .OPEN_REPARSE_POINT = !flags.follow_symlinks, |
| 2716 | 3005 | }, |
| 2717 | 3006 | null, |
| ... | ... | @@ -2763,34 +3052,39 @@ pub fn dirOpenFileWtf16( |
| 2763 | 3052 | }; |
| 2764 | 3053 | errdefer w.CloseHandle(handle); |
| 2765 | 3054 | |
| 2766 | const range_off: w.LARGE_INTEGER = 0; | |
| 2767 | const range_len: w.LARGE_INTEGER = 1; | |
| 2768 | 3055 | const exclusive = switch (flags.lock) { |
| 2769 | 3056 | .none => return .{ .handle = handle }, |
| 2770 | 3057 | .shared => false, |
| 2771 | 3058 | .exclusive => true, |
| 2772 | 3059 | }; |
| 2773 | try w.LockFile( | |
| 3060 | const status = w.ntdll.NtLockFile( | |
| 2774 | 3061 | handle, |
| 2775 | 3062 | null, |
| 2776 | 3063 | null, |
| 2777 | 3064 | null, |
| 2778 | 3065 | &io_status_block, |
| 2779 | &range_off, | |
| 2780 | &range_len, | |
| 3066 | &windows_lock_range_off, | |
| 3067 | &windows_lock_range_len, | |
| 2781 | 3068 | null, |
| 2782 | 3069 | @intFromBool(flags.lock_nonblocking), |
| 2783 | 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 | 3079 | return .{ .handle = handle }; |
| 2786 | 3080 | } |
| 2787 | 3081 | |
| 2788 | 3082 | fn dirOpenFileWasi( |
| 2789 | 3083 | userdata: ?*anyopaque, |
| 2790 | dir: Io.Dir, | |
| 3084 | dir: Dir, | |
| 2791 | 3085 | sub_path: []const u8, |
| 2792 | flags: Io.File.OpenFlags, | |
| 2793 | ) Io.File.OpenError!Io.File { | |
| 3086 | flags: File.OpenFlags, | |
| 3087 | ) File.OpenError!File { | |
| 2794 | 3088 | if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags); |
| 2795 | 3089 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2796 | 3090 | const current_thread = Thread.getCurrent(t); |
| ... | ... | @@ -2827,15 +3121,13 @@ fn dirOpenFileWasi( |
| 2827 | 3121 | while (true) { |
| 2828 | 3122 | switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { |
| 2829 | 3123 | .SUCCESS => { |
| 2830 | errdefer posix.close(fd); | |
| 2831 | 3124 | current_thread.endSyscall(); |
| 2832 | return .{ .handle = fd }; | |
| 3125 | break; | |
| 2833 | 3126 | }, |
| 2834 | 3127 | .INTR => { |
| 2835 | 3128 | try current_thread.checkCancel(); |
| 2836 | 3129 | continue; |
| 2837 | 3130 | }, |
| 2838 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2839 | 3131 | else => |e| { |
| 2840 | 3132 | current_thread.endSyscall(); |
| 2841 | 3133 | switch (e) { |
| ... | ... | @@ -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 | } |
| 2867 | 3174 | |
| 2868 | 3175 | const dirOpenDir = switch (native_os) { |
| ... | ... | @@ -2874,10 +3181,10 @@ const dirOpenDir = switch (native_os) { |
| 2874 | 3181 | /// This function is also used for WASI when libc is linked. |
| 2875 | 3182 | fn dirOpenDirPosix( |
| 2876 | 3183 | userdata: ?*anyopaque, |
| 2877 | dir: Io.Dir, | |
| 3184 | dir: Dir, | |
| 2878 | 3185 | sub_path: []const u8, |
| 2879 | options: Io.Dir.OpenOptions, | |
| 2880 | ) Io.Dir.OpenError!Io.Dir { | |
| 3186 | options: Dir.OpenOptions, | |
| 3187 | ) Dir.OpenError!Dir { | |
| 2881 | 3188 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2882 | 3189 | |
| 2883 | 3190 | if (is_windows) { |
| ... | ... | @@ -2919,7 +3226,6 @@ fn dirOpenDirPosix( |
| 2919 | 3226 | try current_thread.checkCancel(); |
| 2920 | 3227 | continue; |
| 2921 | 3228 | }, |
| 2922 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2923 | 3229 | else => |e| { |
| 2924 | 3230 | current_thread.endSyscall(); |
| 2925 | 3231 | switch (e) { |
| ... | ... | @@ -2948,10 +3254,10 @@ fn dirOpenDirPosix( |
| 2948 | 3254 | |
| 2949 | 3255 | fn dirOpenDirHaiku( |
| 2950 | 3256 | userdata: ?*anyopaque, |
| 2951 | dir: Io.Dir, | |
| 3257 | dir: Dir, | |
| 2952 | 3258 | sub_path: []const u8, |
| 2953 | options: Io.Dir.OpenOptions, | |
| 2954 | ) Io.Dir.OpenError!Io.Dir { | |
| 3259 | options: Dir.OpenOptions, | |
| 3260 | ) Dir.OpenError!Dir { | |
| 2955 | 3261 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2956 | 3262 | const current_thread = Thread.getCurrent(t); |
| 2957 | 3263 | |
| ... | ... | @@ -2972,7 +3278,6 @@ fn dirOpenDirHaiku( |
| 2972 | 3278 | try current_thread.checkCancel(); |
| 2973 | 3279 | continue; |
| 2974 | 3280 | }, |
| 2975 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 2976 | 3281 | else => |e| { |
| 2977 | 3282 | current_thread.endSyscall(); |
| 2978 | 3283 | switch (e) { |
| ... | ... | @@ -2999,10 +3304,10 @@ fn dirOpenDirHaiku( |
| 2999 | 3304 | |
| 3000 | 3305 | pub fn dirOpenDirWindows( |
| 3001 | 3306 | t: *Io.Threaded, |
| 3002 | dir: Io.Dir, | |
| 3307 | dir: Dir, | |
| 3003 | 3308 | sub_path_w: [:0]const u16, |
| 3004 | options: Io.Dir.OpenOptions, | |
| 3005 | ) Io.Dir.OpenError!Io.Dir { | |
| 3309 | options: Dir.OpenOptions, | |
| 3310 | ) Dir.OpenError!Dir { | |
| 3006 | 3311 | const current_thread = Thread.getCurrent(t); |
| 3007 | 3312 | const w = windows; |
| 3008 | 3313 | |
| ... | ... | @@ -3013,7 +3318,7 @@ pub fn dirOpenDirWindows( |
| 3013 | 3318 | .Buffer = @constCast(sub_path_w.ptr), |
| 3014 | 3319 | }; |
| 3015 | 3320 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 3016 | var result: Io.Dir = .{ .handle = undefined }; | |
| 3321 | var result: Dir = .{ .handle = undefined }; | |
| 3017 | 3322 | try current_thread.checkCancel(); |
| 3018 | 3323 | const rc = w.ntdll.NtCreateFile( |
| 3019 | 3324 | &result.handle, |
| ... | ... | @@ -3068,147 +3373,4298 @@ pub fn dirOpenDirWindows( |
| 3068 | 3373 | } |
| 3069 | 3374 | } |
| 3070 | 3375 | |
| 3071 | const MakeOpenDirAccessMaskWOptions = struct { | |
| 3072 | no_follow: bool, | |
| 3073 | create_disposition: u32, | |
| 3074 | }; | |
| 3075 | ||
| 3076 | fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void { | |
| 3376 | fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { | |
| 3077 | 3377 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3078 | 3378 | _ = t; |
| 3079 | posix.close(dir.handle); | |
| 3080 | } | |
| 3379 | for (dirs) |dir| posix.close(dir.handle); | |
| 3380 | } | |
| 3381 | ||
| 3382 | const 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 | }; | |
| 3081 | 3392 | |
| 3082 | fn dirOpenDirWasi( | |
| 3083 | userdata: ?*anyopaque, | |
| 3084 | dir: Io.Dir, | |
| 3085 | sub_path: []const u8, | |
| 3086 | options: Io.Dir.OpenOptions, | |
| 3087 | ) Io.Dir.OpenError!Io.Dir { | |
| 3088 | if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options); | |
| 3393 | fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { | |
| 3394 | const linux = std.os.linux; | |
| 3089 | 3395 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3090 | 3396 | const current_thread = Thread.getCurrent(t); |
| 3091 | const wasi = std.os.wasi; | |
| 3092 | ||
| 3093 | var base: std.os.wasi.rights_t = .{ | |
| 3094 | .FD_FILESTAT_GET = true, | |
| 3095 | .FD_FDSTAT_SET_FLAGS = true, | |
| 3096 | .FD_FILESTAT_SET_TIMES = true, | |
| 3097 | }; | |
| 3098 | if (options.access_sub_paths) { | |
| 3099 | base.FD_READDIR = true; | |
| 3100 | base.PATH_CREATE_DIRECTORY = true; | |
| 3101 | base.PATH_CREATE_FILE = true; | |
| 3102 | base.PATH_LINK_SOURCE = true; | |
| 3103 | base.PATH_LINK_TARGET = true; | |
| 3104 | base.PATH_OPEN = true; | |
| 3105 | base.PATH_READLINK = true; | |
| 3106 | base.PATH_RENAME_SOURCE = true; | |
| 3107 | base.PATH_RENAME_TARGET = true; | |
| 3108 | base.PATH_FILESTAT_GET = true; | |
| 3109 | base.PATH_FILESTAT_SET_SIZE = true; | |
| 3110 | base.PATH_FILESTAT_SET_TIMES = true; | |
| 3111 | base.PATH_SYMLINK = true; | |
| 3112 | base.PATH_REMOVE_DIRECTORY = true; | |
| 3113 | base.PATH_UNLINK_FILE = true; | |
| 3114 | } | |
| 3115 | ||
| 3116 | const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks }; | |
| 3117 | const oflags: wasi.oflags_t = .{ .DIRECTORY = true }; | |
| 3118 | const fdflags: wasi.fdflags_t = .{}; | |
| 3119 | var fd: posix.fd_t = undefined; | |
| 3120 | try current_thread.beginSyscall(); | |
| 3121 | while (true) { | |
| 3122 | switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) { | |
| 3123 | .SUCCESS => { | |
| 3124 | current_thread.endSyscall(); | |
| 3125 | return .{ .handle = fd }; | |
| 3126 | }, | |
| 3127 | .INTR => { | |
| 3128 | try current_thread.checkCancel(); | |
| 3129 | continue; | |
| 3130 | }, | |
| 3131 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3132 | else => |e| { | |
| 3133 | current_thread.endSyscall(); | |
| 3134 | switch (e) { | |
| 3135 | .FAULT => |err| return errnoBug(err), | |
| 3136 | .INVAL => return error.BadPathName, | |
| 3137 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. | |
| 3138 | .ACCES => return error.AccessDenied, | |
| 3139 | .LOOP => return error.SymLinkLoop, | |
| 3140 | .MFILE => return error.ProcessFdQuotaExceeded, | |
| 3141 | .NAMETOOLONG => return error.NameTooLong, | |
| 3142 | .NFILE => return error.SystemFdQuotaExceeded, | |
| 3143 | .NODEV => return error.NoDevice, | |
| 3144 | .NOENT => return error.FileNotFound, | |
| 3145 | .NOMEM => return error.SystemResources, | |
| 3146 | .NOTDIR => return error.NotDir, | |
| 3147 | .PERM => return error.PermissionDenied, | |
| 3148 | .BUSY => return error.DeviceBusy, | |
| 3149 | .NOTCAPABLE => return error.AccessDenied, | |
| 3150 | .ILSEQ => return error.BadPathName, | |
| 3151 | else => |err| return posix.unexpectedErrno(err), | |
| 3397 | var buffer_index: usize = 0; | |
| 3398 | while (buffer.len - buffer_index != 0) { | |
| 3399 | if (dr.end - dr.index == 0) { | |
| 3400 | // Refill the buffer, unless we've already created references to | |
| 3401 | // buffered data. | |
| 3402 | if (buffer_index != 0) break; | |
| 3403 | if (dr.state == .reset) { | |
| 3404 | posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) { | |
| 3405 | error.Unseekable => return error.Unexpected, | |
| 3406 | else => |e| return e, | |
| 3407 | }; | |
| 3408 | dr.state = .reading; | |
| 3409 | } | |
| 3410 | try current_thread.beginSyscall(); | |
| 3411 | const n = while (true) { | |
| 3412 | const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); | |
| 3413 | switch (linux.errno(rc)) { | |
| 3414 | .SUCCESS => { | |
| 3415 | current_thread.endSyscall(); | |
| 3416 | break rc; | |
| 3417 | }, | |
| 3418 | .INTR => { | |
| 3419 | try current_thread.checkCancel(); | |
| 3420 | continue; | |
| 3421 | }, | |
| 3422 | else => |e| { | |
| 3423 | current_thread.endSyscall(); | |
| 3424 | switch (e) { | |
| 3425 | .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. | |
| 3426 | .FAULT => |err| return errnoBug(err), | |
| 3427 | .NOTDIR => |err| return errnoBug(err), | |
| 3428 | // To be consistent across platforms, iteration | |
| 3429 | // ends if the directory being iterated is deleted | |
| 3430 | // during iteration. This matches the behavior of | |
| 3431 | // non-Linux, non-WASI UNIX platforms. | |
| 3432 | .NOENT => { | |
| 3433 | dr.state = .finished; | |
| 3434 | return 0; | |
| 3435 | }, | |
| 3436 | // This can occur when reading /proc/$PID/net, or | |
| 3437 | // if the provided buffer is too small. Neither | |
| 3438 | // scenario is intended to be handled by this API. | |
| 3439 | .INVAL => return error.Unexpected, | |
| 3440 | .ACCES => return error.AccessDenied, // Lacking permission to iterate this directory. | |
| 3441 | else => |err| return posix.unexpectedErrno(err), | |
| 3442 | } | |
| 3443 | }, | |
| 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 | } |
| 3157 | 3490 | |
| 3158 | fn fileClose(userdata: ?*anyopaque, file: Io.File) void { | |
| 3491 | fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { | |
| 3159 | 3492 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3160 | _ = t; | |
| 3161 | posix.close(file.handle); | |
| 3162 | } | |
| 3493 | const current_thread = Thread.getCurrent(t); | |
| 3494 | const Header = extern struct { | |
| 3495 | seek: i64, | |
| 3496 | }; | |
| 3497 | const header: *Header = @ptrCast(dr.buffer.ptr); | |
| 3498 | const header_end: usize = @sizeOf(Header); | |
| 3499 | if (dr.index < header_end) { | |
| 3500 | // Initialize header. | |
| 3501 | dr.index = header_end; | |
| 3502 | dr.end = header_end; | |
| 3503 | header.* = .{ .seek = 0 }; | |
| 3504 | } | |
| 3505 | var buffer_index: usize = 0; | |
| 3506 | while (buffer.len - buffer_index != 0) { | |
| 3507 | if (dr.end - dr.index == 0) { | |
| 3508 | // Refill the buffer, unless we've already created references to | |
| 3509 | // buffered data. | |
| 3510 | if (buffer_index != 0) break; | |
| 3511 | if (dr.state == .reset) { | |
| 3512 | posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) { | |
| 3513 | error.Unseekable => return error.Unexpected, | |
| 3514 | else => |e| return e, | |
| 3515 | }; | |
| 3516 | dr.state = .reading; | |
| 3517 | } | |
| 3518 | const dents_buffer = dr.buffer[header_end..]; | |
| 3519 | try current_thread.beginSyscall(); | |
| 3520 | const n: usize = while (true) { | |
| 3521 | const rc = posix.system.getdirentries(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, &header.seek); | |
| 3522 | switch (posix.errno(rc)) { | |
| 3523 | .SUCCESS => { | |
| 3524 | current_thread.endSyscall(); | |
| 3525 | break @intCast(rc); | |
| 3526 | }, | |
| 3527 | .INTR => { | |
| 3528 | try current_thread.checkCancel(); | |
| 3529 | continue; | |
| 3530 | }, | |
| 3531 | else => |e| { | |
| 3532 | current_thread.endSyscall(); | |
| 3533 | switch (e) { | |
| 3534 | .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. | |
| 3535 | .FAULT => |err| return errnoBug(err), | |
| 3536 | .NOTDIR => |err| return errnoBug(err), | |
| 3537 | .INVAL => |err| return errnoBug(err), | |
| 3538 | else => |err| return posix.unexpectedErrno(err), | |
| 3539 | } | |
| 3540 | }, | |
| 3541 | } | |
| 3542 | }; | |
| 3543 | if (n == 0) { | |
| 3544 | dr.state = .finished; | |
| 3545 | return 0; | |
| 3546 | } | |
| 3547 | dr.index = header_end; | |
| 3548 | dr.end = header_end + n; | |
| 3549 | } | |
| 3550 | const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); | |
| 3551 | const next_index = dr.index + darwin_entry.reclen; | |
| 3552 | dr.index = next_index; | |
| 3553 | ||
| 3554 | const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen]; | |
| 3555 | if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) | |
| 3556 | continue; | |
| 3557 | ||
| 3558 | const entry_kind: File.Kind = switch (darwin_entry.type) { | |
| 3559 | posix.DT.BLK => .block_device, | |
| 3560 | posix.DT.CHR => .character_device, | |
| 3561 | posix.DT.DIR => .directory, | |
| 3562 | posix.DT.FIFO => .named_pipe, | |
| 3563 | posix.DT.LNK => .sym_link, | |
| 3564 | posix.DT.REG => .file, | |
| 3565 | posix.DT.SOCK => .unix_domain_socket, | |
| 3566 | posix.DT.WHT => .whiteout, | |
| 3567 | else => .unknown, | |
| 3568 | }; | |
| 3569 | buffer[buffer_index] = .{ | |
| 3570 | .name = name, | |
| 3571 | .kind = entry_kind, | |
| 3572 | .inode = darwin_entry.ino, | |
| 3573 | }; | |
| 3574 | buffer_index += 1; | |
| 3575 | } | |
| 3576 | return buffer_index; | |
| 3577 | } | |
| 3578 | ||
| 3579 | fn 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 | ||
| 3673 | fn 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 | ||
| 3740 | fn 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 | ||
| 3747 | fn 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 | ||
| 3893 | fn 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 | ||
| 3999 | fn 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 | ||
| 4006 | const dirRealPathFile = switch (native_os) { | |
| 4007 | .windows => dirRealPathFileWindows, | |
| 4008 | else => dirRealPathFilePosix, | |
| 4009 | }; | |
| 4010 | ||
| 4011 | fn 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 | ||
| 4038 | fn 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 | ||
| 4050 | fn 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 | ||
| 4143 | const dirRealPath = switch (native_os) { | |
| 4144 | .windows => dirRealPathWindows, | |
| 4145 | else => dirRealPathPosix, | |
| 4146 | }; | |
| 4147 | ||
| 4148 | fn 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 | ||
| 4155 | fn 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 | ||
| 4161 | const fileRealPath = switch (native_os) { | |
| 4162 | .windows => fileRealPathWindows, | |
| 4163 | else => fileRealPathPosix, | |
| 4164 | }; | |
| 4165 | ||
| 4166 | fn 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 | ||
| 4173 | fn 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 | ||
| 4180 | fn 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 | ||
| 4284 | const dirDeleteFile = switch (native_os) { | |
| 4285 | .windows => dirDeleteFileWindows, | |
| 4286 | .wasi => dirDeleteFileWasi, | |
| 4287 | else => dirDeleteFilePosix, | |
| 4288 | }; | |
| 4289 | ||
| 4290 | fn 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 | ||
| 4297 | fn 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 | ||
| 4339 | fn 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 | ||
| 4416 | const dirDeleteDir = switch (native_os) { | |
| 4417 | .windows => dirDeleteDirWindows, | |
| 4418 | .wasi => dirDeleteDirWasi, | |
| 4419 | else => dirDeleteDirPosix, | |
| 4420 | }; | |
| 4421 | ||
| 4422 | fn 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 | ||
| 4429 | fn 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 | ||
| 4572 | fn 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 | ||
| 4616 | fn 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 | ||
| 4661 | const dirRename = switch (native_os) { | |
| 4662 | .windows => dirRenameWindows, | |
| 4663 | .wasi => dirRenameWasi, | |
| 4664 | else => dirRenamePosix, | |
| 4665 | }; | |
| 4666 | ||
| 4667 | fn 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 | ||
| 4783 | fn 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 | ||
| 4833 | fn 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 | ||
| 4886 | const dirSymLink = switch (native_os) { | |
| 4887 | .windows => dirSymLinkWindows, | |
| 4888 | .wasi => dirSymLinkWasi, | |
| 4889 | else => dirSymLinkPosix, | |
| 4890 | }; | |
| 4891 | ||
| 4892 | fn 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 | ||
| 5017 | fn 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 | ||
| 5064 | fn 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 | ||
| 5114 | const dirReadLink = switch (native_os) { | |
| 5115 | .windows => dirReadLinkWindows, | |
| 5116 | .wasi => dirReadLinkWasi, | |
| 5117 | else => dirReadLinkPosix, | |
| 5118 | }; | |
| 5119 | ||
| 5120 | fn 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 | ||
| 5137 | fn 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 | ||
| 5176 | fn 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 | ||
| 5216 | const dirSetPermissions = switch (native_os) { | |
| 5217 | .windows => dirSetPermissionsWindows, | |
| 5218 | else => dirSetPermissionsPosix, | |
| 5219 | }; | |
| 5220 | ||
| 5221 | fn 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 | ||
| 5229 | fn 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 | ||
| 5236 | fn 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 | ||
| 5257 | fn 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 | ||
| 5344 | fn 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 | ||
| 5459 | const dirSetOwner = switch (native_os) { | |
| 5460 | .windows => dirSetOwnerUnsupported, | |
| 5461 | else => dirSetOwnerPosix, | |
| 5462 | }; | |
| 5463 | ||
| 5464 | fn 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 | ||
| 5472 | fn 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 | ||
| 5481 | fn 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 | ||
| 5512 | fn 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 | ||
| 5536 | const fileSync = switch (native_os) { | |
| 5537 | .windows => fileSyncWindows, | |
| 5538 | .wasi => fileSyncWasi, | |
| 5539 | else => fileSyncPosix, | |
| 5540 | }; | |
| 5541 | ||
| 5542 | fn 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 | ||
| 5560 | fn 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 | ||
| 5587 | fn 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 | ||
| 5614 | fn 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 | ||
| 5620 | fn 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 | ||
| 5691 | fn 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 | ||
| 5726 | fn 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 | ||
| 5732 | fn 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 | ||
| 5754 | fn 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 | ||
| 5820 | fn 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 | ||
| 5902 | fn 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 | ||
| 5911 | fn 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 | ||
| 5945 | fn 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 | ||
| 5976 | fn 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 | ||
| 6029 | fn 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 | ||
| 6075 | fn 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 | ||
| 6159 | fn 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 | ||
| 6219 | const windows_lock_range_off: windows.LARGE_INTEGER = 0; | |
| 6220 | const windows_lock_range_len: windows.LARGE_INTEGER = 1; | |
| 6221 | ||
| 6222 | fn 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 | ||
| 6301 | fn 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 | ||
| 6385 | fn 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 | ||
| 6422 | fn 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 | ||
| 6497 | fn 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 | ||
| 6572 | fn 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 | ||
| 6680 | fn 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 | ||
| 6686 | const fileReadStreaming = switch (native_os) { | |
| 6687 | .windows => fileReadStreamingWindows, | |
| 6688 | else => fileReadStreamingPosix, | |
| 6689 | }; | |
| 6690 | ||
| 6691 | fn 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 | ||
| 6778 | fn 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 | ||
| 6808 | fn 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 | ||
| 6904 | const fileReadPositional = switch (native_os) { | |
| 6905 | .windows => fileReadPositionalWindows, | |
| 6906 | else => fileReadPositionalPosix, | |
| 6907 | }; | |
| 6908 | ||
| 6909 | fn 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 | ||
| 6952 | fn 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 | ||
| 7047 | fn 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 | ||
| 7091 | fn 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 | ||
| 7146 | fn 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 | ||
| 7187 | fn 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 | ||
| 7413 | fn 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 | }; | |
| 3163 | 7467 | |
| 3164 | const fileReadStreaming = switch (native_os) { | |
| 3165 | .windows => fileReadStreamingWindows, | |
| 3166 | else => fileReadStreamingPosix, | |
| 3167 | }; | |
| 7468 | if (iovlen == 0) return 0; | |
| 7469 | ||
| 7470 | if (native_os == .wasi and !builtin.link_libc) { | |
| 7471 | var n_written: usize = undefined; | |
| 7472 | try current_thread.beginSyscall(); | |
| 7473 | while (true) { | |
| 7474 | switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) { | |
| 7475 | .SUCCESS => { | |
| 7476 | current_thread.endSyscall(); | |
| 7477 | return n_written; | |
| 7478 | }, | |
| 7479 | .INTR => { | |
| 7480 | try current_thread.checkCancel(); | |
| 7481 | continue; | |
| 7482 | }, | |
| 7483 | else => |e| { | |
| 7484 | current_thread.endSyscall(); | |
| 7485 | switch (e) { | |
| 7486 | .INVAL => |err| return errnoBug(err), | |
| 7487 | .FAULT => |err| return errnoBug(err), | |
| 7488 | .AGAIN => |err| return errnoBug(err), | |
| 7489 | .BADF => return error.NotOpenForWriting, // can be a race condition. | |
| 7490 | .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. | |
| 7491 | .DQUOT => return error.DiskQuota, | |
| 7492 | .FBIG => return error.FileTooBig, | |
| 7493 | .IO => return error.InputOutput, | |
| 7494 | .NOSPC => return error.NoSpaceLeft, | |
| 7495 | .PERM => return error.PermissionDenied, | |
| 7496 | .PIPE => return error.BrokenPipe, | |
| 7497 | .NOTCAPABLE => return error.AccessDenied, | |
| 7498 | .NXIO => return error.Unseekable, | |
| 7499 | .SPIPE => return error.Unseekable, | |
| 7500 | .OVERFLOW => return error.Unseekable, | |
| 7501 | else => |err| return posix.unexpectedErrno(err), | |
| 7502 | } | |
| 7503 | }, | |
| 7504 | } | |
| 7505 | } | |
| 7506 | } | |
| 7507 | ||
| 7508 | try current_thread.beginSyscall(); | |
| 7509 | while (true) { | |
| 7510 | const rc = pwritev_sym(file.handle, &iovecs, @intCast(iovlen), @bitCast(offset)); | |
| 7511 | switch (posix.errno(rc)) { | |
| 7512 | .SUCCESS => { | |
| 7513 | current_thread.endSyscall(); | |
| 7514 | return @intCast(rc); | |
| 7515 | }, | |
| 7516 | .INTR => { | |
| 7517 | try current_thread.checkCancel(); | |
| 7518 | continue; | |
| 7519 | }, | |
| 7520 | else => |e| { | |
| 7521 | current_thread.endSyscall(); | |
| 7522 | switch (e) { | |
| 7523 | .INVAL => |err| return errnoBug(err), | |
| 7524 | .FAULT => |err| return errnoBug(err), | |
| 7525 | .AGAIN => return error.WouldBlock, | |
| 7526 | .BADF => return error.NotOpenForWriting, // Usually a race condition. | |
| 7527 | .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. | |
| 7528 | .DQUOT => return error.DiskQuota, | |
| 7529 | .FBIG => return error.FileTooBig, | |
| 7530 | .IO => return error.InputOutput, | |
| 7531 | .NOSPC => return error.NoSpaceLeft, | |
| 7532 | .PERM => return error.PermissionDenied, | |
| 7533 | .PIPE => return error.BrokenPipe, | |
| 7534 | .CONNRESET => |err| return errnoBug(err), // Not a socket handle. | |
| 7535 | .BUSY => return error.DeviceBusy, | |
| 7536 | .TXTBSY => return error.FileBusy, | |
| 7537 | .NXIO => return error.Unseekable, | |
| 7538 | .SPIPE => return error.Unseekable, | |
| 7539 | .OVERFLOW => return error.Unseekable, | |
| 7540 | else => |err| return posix.unexpectedErrno(err), | |
| 7541 | } | |
| 7542 | }, | |
| 7543 | } | |
| 7544 | } | |
| 7545 | } | |
| 7546 | ||
| 7547 | fn 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 | } | |
| 3168 | 7584 | |
| 3169 | fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize { | |
| 7585 | fn 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 | 7592 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3171 | 7593 | const current_thread = Thread.getCurrent(t); |
| 3172 | 7594 | |
| 3173 | var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; | |
| 3174 | var i: usize = 0; | |
| 3175 | for (data) |buf| { | |
| 3176 | if (iovecs_buffer.len - i == 0) break; | |
| 3177 | if (buf.len != 0) { | |
| 3178 | iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len }; | |
| 3179 | i += 1; | |
| 7595 | if (is_windows) { | |
| 7596 | if (header.len != 0) { | |
| 7597 | return writeFileStreamingWindows(current_thread, file.handle, header); | |
| 7598 | } | |
| 7599 | for (data[0 .. data.len - 1]) |buf| { | |
| 7600 | if (buf.len == 0) continue; | |
| 7601 | return writeFileStreamingWindows(current_thread, file.handle, buf); | |
| 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]; | |
| 3183 | assert(dest[0].len > 0); | |
| 7607 | ||
| 7608 | var iovecs: [max_iovecs_len]posix.iovec_const = undefined; | |
| 7609 | var iovlen: iovlen_t = 0; | |
| 7610 | addBuf(&iovecs, &iovlen, header); | |
| 7611 | for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes); | |
| 7612 | const pattern = data[data.len - 1]; | |
| 7613 | if (iovecs.len - iovlen != 0) switch (splat) { | |
| 7614 | 0 => {}, | |
| 7615 | 1 => addBuf(&iovecs, &iovlen, pattern), | |
| 7616 | else => switch (pattern.len) { | |
| 7617 | 0 => {}, | |
| 7618 | 1 => { | |
| 7619 | var backup_buffer: [splat_buffer_size]u8 = undefined; | |
| 7620 | const splat_buffer = &backup_buffer; | |
| 7621 | const memset_len = @min(splat_buffer.len, splat); | |
| 7622 | const buf = splat_buffer[0..memset_len]; | |
| 7623 | @memset(buf, pattern[0]); | |
| 7624 | addBuf(&iovecs, &iovlen, buf); | |
| 7625 | var remaining_splat = splat - buf.len; | |
| 7626 | while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) { | |
| 7627 | assert(buf.len == splat_buffer.len); | |
| 7628 | addBuf(&iovecs, &iovlen, splat_buffer); | |
| 7629 | remaining_splat -= splat_buffer.len; | |
| 7630 | } | |
| 7631 | addBuf(&iovecs, &iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); | |
| 7632 | }, | |
| 7633 | else => for (0..@min(splat, iovecs.len - iovlen)) |_| { | |
| 7634 | addBuf(&iovecs, &iovlen, pattern); | |
| 7635 | }, | |
| 7636 | }, | |
| 7637 | }; | |
| 7638 | ||
| 7639 | if (iovlen == 0) return 0; | |
| 3184 | 7640 | |
| 3185 | 7641 | if (native_os == .wasi and !builtin.link_libc) { |
| 7642 | var n_written: usize = undefined; | |
| 3186 | 7643 | try current_thread.beginSyscall(); |
| 3187 | 7644 | while (true) { |
| 3188 | var nread: usize = undefined; | |
| 3189 | switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) { | |
| 7645 | switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) { | |
| 3190 | 7646 | .SUCCESS => { |
| 3191 | 7647 | current_thread.endSyscall(); |
| 3192 | return nread; | |
| 7648 | return n_written; | |
| 3193 | 7649 | }, |
| 3194 | 7650 | .INTR => { |
| 3195 | 7651 | try current_thread.checkCancel(); |
| 3196 | 7652 | continue; |
| 3197 | 7653 | }, |
| 3198 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3199 | 7654 | else => |e| { |
| 3200 | 7655 | current_thread.endSyscall(); |
| 3201 | 7656 | switch (e) { |
| 3202 | 7657 | .INVAL => |err| return errnoBug(err), |
| 3203 | 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 | 7664 | .IO => return error.InputOutput, |
| 3206 | .ISDIR => return error.IsDir, | |
| 3207 | .NOBUFS => return error.SystemResources, | |
| 3208 | .NOMEM => return error.SystemResources, | |
| 3209 | .NOTCONN => return error.SocketUnconnected, | |
| 3210 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 3211 | .TIMEDOUT => return error.Timeout, | |
| 7665 | .NOSPC => return error.NoSpaceLeft, | |
| 7666 | .PERM => return error.PermissionDenied, | |
| 7667 | .PIPE => return error.BrokenPipe, | |
| 3212 | 7668 | .NOTCAPABLE => return error.AccessDenied, |
| 3213 | 7669 | else => |err| return posix.unexpectedErrno(err), |
| 3214 | 7670 | } |
| ... | ... | @@ -3219,7 +7675,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io |
| 3219 | 7675 | |
| 3220 | 7676 | try current_thread.beginSyscall(); |
| 3221 | 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 | 7679 | switch (posix.errno(rc)) { |
| 3224 | 7680 | .SUCCESS => { |
| 3225 | 7681 | current_thread.endSyscall(); |
| ... | ... | @@ -3229,25 +7685,22 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io |
| 3229 | 7685 | try current_thread.checkCancel(); |
| 3230 | 7686 | continue; |
| 3231 | 7687 | }, |
| 3232 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3233 | 7688 | else => |e| { |
| 3234 | 7689 | current_thread.endSyscall(); |
| 3235 | 7690 | switch (e) { |
| 3236 | 7691 | .INVAL => |err| return errnoBug(err), |
| 3237 | 7692 | .FAULT => |err| return errnoBug(err), |
| 3238 | .SRCH => return error.ProcessNotFound, | |
| 3239 | 7693 | .AGAIN => return error.WouldBlock, |
| 3240 | .BADF => |err| { | |
| 3241 | if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. | |
| 3242 | return errnoBug(err); // File descriptor used after closed. | |
| 3243 | }, | |
| 7694 | .BADF => return error.NotOpenForWriting, // Can be a race condition. | |
| 7695 | .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. | |
| 7696 | .DQUOT => return error.DiskQuota, | |
| 7697 | .FBIG => return error.FileTooBig, | |
| 3244 | 7698 | .IO => return error.InputOutput, |
| 3245 | .ISDIR => return error.IsDir, | |
| 3246 | .NOBUFS => return error.SystemResources, | |
| 3247 | .NOMEM => return error.SystemResources, | |
| 3248 | .NOTCONN => return error.SocketUnconnected, | |
| 3249 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 3250 | .TIMEDOUT => return error.Timeout, | |
| 7699 | .NOSPC => return error.NoSpaceLeft, | |
| 7700 | .PERM => return error.PermissionDenied, | |
| 7701 | .PIPE => return error.BrokenPipe, | |
| 7702 | .CONNRESET => |err| return errnoBug(err), // Not a socket handle. | |
| 7703 | .BUSY => return error.DeviceBusy, | |
| 3251 | 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 | 7708 | } |
| 3256 | 7709 | } |
| 3257 | 7710 | |
| 3258 | fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize { | |
| 3259 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 3260 | const current_thread = Thread.getCurrent(t); | |
| 3261 | ||
| 3262 | const DWORD = windows.DWORD; | |
| 3263 | var index: usize = 0; | |
| 3264 | while (data[index].len == 0) index += 1; | |
| 3265 | const buffer = data[index]; | |
| 3266 | const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); | |
| 7711 | fn writeFileStreamingWindows( | |
| 7712 | current_thread: *Thread, | |
| 7713 | handle: windows.HANDLE, | |
| 7714 | bytes: []const u8, | |
| 7715 | ) File.Writer.Error!usize { | |
| 7716 | try current_thread.checkCancel(); | |
| 3267 | 7717 | |
| 3268 | while (true) { | |
| 3269 | try current_thread.checkCancel(); | |
| 3270 | var n: DWORD = undefined; | |
| 3271 | if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) | |
| 3272 | return n; | |
| 7718 | var bytes_written: windows.DWORD = undefined; | |
| 7719 | const adjusted_len = std.math.lossyCast(u32, bytes.len); | |
| 7720 | if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) == 0) { | |
| 3273 | 7721 | switch (windows.GetLastError()) { |
| 3274 | .IO_PENDING => |err| return windows.errorBug(err), | |
| 3275 | .OPERATION_ABORTED => continue, | |
| 3276 | .BROKEN_PIPE => return 0, | |
| 3277 | .HANDLE_EOF => return 0, | |
| 3278 | .NETNAME_DELETED => return error.ConnectionResetByPeer, | |
| 7722 | .INVALID_USER_BUFFER => return error.SystemResources, | |
| 7723 | .NOT_ENOUGH_MEMORY => return error.SystemResources, | |
| 7724 | .OPERATION_ABORTED => return error.Canceled, | |
| 7725 | .NOT_ENOUGH_QUOTA => return error.SystemResources, | |
| 7726 | .NO_DATA => return error.BrokenPipe, | |
| 7727 | .INVALID_HANDLE => return error.NotOpenForWriting, | |
| 3279 | 7728 | .LOCK_VIOLATION => return error.LockViolation, |
| 3280 | 7729 | .ACCESS_DENIED => return error.AccessDenied, |
| 3281 | .INVALID_HANDLE => return error.NotOpenForReading, | |
| 7730 | .WORKING_SET_QUOTA => return error.SystemResources, | |
| 3282 | 7731 | else => |err| return windows.unexpectedError(err), |
| 3283 | 7732 | } |
| 3284 | 7733 | } |
| 7734 | return bytes_written; | |
| 3285 | 7735 | } |
| 3286 | 7736 | |
| 3287 | fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize { | |
| 7737 | fn 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 | 7744 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3289 | const current_thread = Thread.getCurrent(t); | |
| 3290 | ||
| 3291 | if (!have_preadv) @compileError("TODO"); | |
| 3292 | ||
| 3293 | var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; | |
| 3294 | var i: usize = 0; | |
| 3295 | for (data) |buf| { | |
| 3296 | if (iovecs_buffer.len - i == 0) break; | |
| 3297 | if (buf.len != 0) { | |
| 3298 | iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len }; | |
| 3299 | i += 1; | |
| 7745 | const reader_buffered = file_reader.interface.buffered(); | |
| 7746 | if (reader_buffered.len >= @intFromEnum(limit)) { | |
| 7747 | const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); | |
| 7748 | file_reader.interface.toss(n -| header.len); | |
| 7749 | return n; | |
| 7750 | } | |
| 7751 | const file_limit = @intFromEnum(limit) - reader_buffered.len; | |
| 7752 | const out_fd = file.handle; | |
| 7753 | const in_fd = file_reader.file.handle; | |
| 7754 | ||
| 7755 | if (file_reader.size) |size| { | |
| 7756 | if (size - file_reader.pos == 0) { | |
| 7757 | if (reader_buffered.len != 0) { | |
| 7758 | const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); | |
| 7759 | file_reader.interface.toss(n -| header.len); | |
| 7760 | return n; | |
| 7761 | } else { | |
| 7762 | return error.EndOfStream; | |
| 7763 | } | |
| 3300 | 7764 | } |
| 3301 | 7765 | } |
| 3302 | const dest = iovecs_buffer[0..i]; | |
| 3303 | assert(dest[0].len > 0); | |
| 3304 | 7766 | |
| 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 | 7962 | try current_thread.beginSyscall(); |
| 3307 | while (true) { | |
| 3308 | var nread: usize = undefined; | |
| 3309 | switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) { | |
| 7963 | const n: usize = while (true) { | |
| 7964 | const rc = sendfile_sym(out_fd, in_fd, off_ptr, count); | |
| 7965 | switch (posix.errno(rc)) { | |
| 3310 | 7966 | .SUCCESS => { |
| 3311 | 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 | 7977 | .INTR => { |
| 3315 | 7978 | try current_thread.checkCancel(); |
| 3316 | 7979 | continue; |
| 3317 | 7980 | }, |
| 3318 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3319 | 7981 | else => |e| { |
| 3320 | 7982 | current_thread.endSyscall(); |
| 3321 | switch (e) { | |
| 3322 | .INVAL => |err| return errnoBug(err), | |
| 3323 | .FAULT => |err| return errnoBug(err), | |
| 3324 | .AGAIN => |err| return errnoBug(err), | |
| 3325 | .BADF => return error.NotOpenForReading, // File operation on directory. | |
| 7983 | assert(error.Unexpected == switch (e) { | |
| 7984 | .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket | |
| 7985 | .AGAIN => return error.WouldBlock, | |
| 3326 | 7986 | .IO => return error.InputOutput, |
| 3327 | .ISDIR => return error.IsDir, | |
| 3328 | .NOBUFS => return error.SystemResources, | |
| 7987 | .PIPE => return error.BrokenPipe, | |
| 3329 | 7988 | .NOMEM => return error.SystemResources, |
| 3330 | .NOTCONN => return error.SocketUnconnected, | |
| 3331 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 3332 | .TIMEDOUT => return error.Timeout, | |
| 3333 | .NXIO => return error.Unseekable, | |
| 3334 | .SPIPE => return error.Unseekable, | |
| 3335 | .OVERFLOW => return error.Unseekable, | |
| 3336 | .NOTCAPABLE => return error.AccessDenied, | |
| 3337 | else => |err| return posix.unexpectedErrno(err), | |
| 3338 | } | |
| 7989 | .NXIO, .SPIPE => { | |
| 7990 | file_reader.mode = file_reader.mode.toStreaming(); | |
| 7991 | const pos = file_reader.pos; | |
| 7992 | if (pos != 0) { | |
| 7993 | file_reader.pos = 0; | |
| 7994 | file_reader.seekBy(@intCast(pos)) catch { | |
| 7995 | file_reader.mode = .failure; | |
| 7996 | return error.ReadFailed; | |
| 7997 | }; | |
| 7998 | } | |
| 7999 | return 0; | |
| 8000 | }, | |
| 8001 | .BADF => |err| errnoBug(err), // Always a race condition. | |
| 8002 | .FAULT => |err| errnoBug(err), // Segmentation fault. | |
| 8003 | .OVERFLOW => |err| errnoBug(err), // We avoid passing too large of a `count`. | |
| 8004 | else => |err| posix.unexpectedErrno(err), | |
| 8005 | }); | |
| 8006 | // Give calling code chance to observe the error before trying | |
| 8007 | // something else. | |
| 8008 | @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); | |
| 8009 | return 0; | |
| 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 | } |
| 3343 | 8020 | |
| 3344 | try current_thread.beginSyscall(); | |
| 3345 | while (true) { | |
| 3346 | const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset)); | |
| 3347 | switch (posix.errno(rc)) { | |
| 3348 | .SUCCESS => { | |
| 3349 | current_thread.endSyscall(); | |
| 3350 | return @bitCast(rc); | |
| 3351 | }, | |
| 3352 | .INTR => { | |
| 3353 | try current_thread.checkCancel(); | |
| 3354 | continue; | |
| 8021 | if (have_copy_file_range) cfr: { | |
| 8022 | if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; | |
| 8023 | if (header.len != 0 or reader_buffered.len != 0) { | |
| 8024 | const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); | |
| 8025 | file_reader.interface.toss(n -| header.len); | |
| 8026 | return n; | |
| 8027 | } | |
| 8028 | var off_in: i64 = undefined; | |
| 8029 | const off_in_ptr: ?*i64 = switch (file_reader.mode) { | |
| 8030 | .positional_simple, .streaming_simple => return error.Unimplemented, | |
| 8031 | .positional => p: { | |
| 8032 | off_in = @intCast(file_reader.pos); | |
| 8033 | break :p &off_in; | |
| 8034 | }, | |
| 8035 | .streaming => null, | |
| 8036 | .failure => return error.ReadFailed, | |
| 8037 | }; | |
| 8038 | const current_thread = Thread.getCurrent(t); | |
| 8039 | const n: usize = switch (native_os) { | |
| 8040 | .linux => n: { | |
| 8041 | try current_thread.beginSyscall(); | |
| 8042 | while (true) { | |
| 8043 | const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0); | |
| 8044 | switch (linux_copy_file_range_sys.errno(rc)) { | |
| 8045 | .SUCCESS => { | |
| 8046 | current_thread.endSyscall(); | |
| 8047 | break :n @intCast(rc); | |
| 8048 | }, | |
| 8049 | .INTR => { | |
| 8050 | try current_thread.checkCancel(); | |
| 8051 | continue; | |
| 8052 | }, | |
| 8053 | .OPNOTSUPP, .INVAL, .NOSYS => { | |
| 8054 | // Give calling code chance to observe before trying | |
| 8055 | // something else. | |
| 8056 | current_thread.endSyscall(); | |
| 8057 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); | |
| 8058 | return 0; | |
| 8059 | }, | |
| 8060 | else => |e| { | |
| 8061 | current_thread.endSyscall(); | |
| 8062 | assert(error.Unexpected == switch (e) { | |
| 8063 | .FBIG => return error.FileTooBig, | |
| 8064 | .IO => return error.InputOutput, | |
| 8065 | .NOMEM => return error.SystemResources, | |
| 8066 | .NOSPC => return error.NoSpaceLeft, | |
| 8067 | .OVERFLOW => |err| errnoBug(err), // We avoid passing too large a count. | |
| 8068 | .PERM => return error.PermissionDenied, | |
| 8069 | .BUSY => return error.DeviceBusy, | |
| 8070 | .TXTBSY => return error.FileBusy, | |
| 8071 | // copy_file_range can still work but not on | |
| 8072 | // this pair of file descriptors. | |
| 8073 | .XDEV => return error.Unimplemented, | |
| 8074 | .ISDIR => |err| errnoBug(err), | |
| 8075 | .BADF => |err| errnoBug(err), | |
| 8076 | else => |err| posix.unexpectedErrno(err), | |
| 8077 | }); | |
| 8078 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); | |
| 8079 | return 0; | |
| 8080 | }, | |
| 8081 | } | |
| 8082 | } | |
| 3355 | 8083 | }, |
| 3356 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3357 | else => |e| { | |
| 3358 | current_thread.endSyscall(); | |
| 3359 | switch (e) { | |
| 3360 | .INVAL => |err| return errnoBug(err), | |
| 3361 | .FAULT => |err| return errnoBug(err), | |
| 3362 | .SRCH => return error.ProcessNotFound, | |
| 3363 | .AGAIN => return error.WouldBlock, | |
| 3364 | .BADF => |err| { | |
| 3365 | if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. | |
| 3366 | return errnoBug(err); // File descriptor used after closed. | |
| 3367 | }, | |
| 3368 | .IO => return error.InputOutput, | |
| 3369 | .ISDIR => return error.IsDir, | |
| 3370 | .NOBUFS => return error.SystemResources, | |
| 3371 | .NOMEM => return error.SystemResources, | |
| 3372 | .NOTCONN => return error.SocketUnconnected, | |
| 3373 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 3374 | .TIMEDOUT => return error.Timeout, | |
| 3375 | .NXIO => return error.Unseekable, | |
| 3376 | .SPIPE => return error.Unseekable, | |
| 3377 | .OVERFLOW => return error.Unseekable, | |
| 3378 | else => |err| return posix.unexpectedErrno(err), | |
| 8084 | .freebsd => n: { | |
| 8085 | try current_thread.beginSyscall(); | |
| 8086 | while (true) { | |
| 8087 | const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0); | |
| 8088 | switch (std.c.errno(rc)) { | |
| 8089 | .SUCCESS => { | |
| 8090 | current_thread.endSyscall(); | |
| 8091 | break :n @intCast(rc); | |
| 8092 | }, | |
| 8093 | .INTR => { | |
| 8094 | try current_thread.checkCancel(); | |
| 8095 | continue; | |
| 8096 | }, | |
| 8097 | .OPNOTSUPP, .INVAL, .NOSYS => { | |
| 8098 | // Give calling code chance to observe before trying | |
| 8099 | // something else. | |
| 8100 | current_thread.endSyscall(); | |
| 8101 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); | |
| 8102 | return 0; | |
| 8103 | }, | |
| 8104 | else => |e| { | |
| 8105 | current_thread.endSyscall(); | |
| 8106 | assert(error.Unexpected == switch (e) { | |
| 8107 | .FBIG => return error.FileTooBig, | |
| 8108 | .IO => return error.InputOutput, | |
| 8109 | .INTEGRITY => return error.CorruptedData, | |
| 8110 | .NOSPC => return error.NoSpaceLeft, | |
| 8111 | .ISDIR => |err| errnoBug(err), | |
| 8112 | .BADF => |err| errnoBug(err), | |
| 8113 | else => |err| posix.unexpectedErrno(err), | |
| 8114 | }); | |
| 8115 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); | |
| 8116 | return 0; | |
| 8117 | }, | |
| 8118 | } | |
| 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 | } | |
| 3384 | 8130 | |
| 3385 | const fileReadPositional = switch (native_os) { | |
| 3386 | .windows => fileReadPositionalWindows, | |
| 3387 | else => fileReadPositionalPosix, | |
| 3388 | }; | |
| 8131 | return error.Unimplemented; | |
| 8132 | } | |
| 3389 | 8133 | |
| 3390 | fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize { | |
| 8134 | fn 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 | 8141 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3392 | const current_thread = Thread.getCurrent(t); | |
| 3393 | ||
| 3394 | const DWORD = windows.DWORD; | |
| 3395 | ||
| 3396 | var index: usize = 0; | |
| 3397 | while (data[index].len == 0) index += 1; | |
| 3398 | const buffer = data[index]; | |
| 3399 | const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); | |
| 3400 | ||
| 3401 | var overlapped: windows.OVERLAPPED = .{ | |
| 3402 | .Internal = 0, | |
| 3403 | .InternalHigh = 0, | |
| 3404 | .DUMMYUNIONNAME = .{ | |
| 3405 | .DUMMYSTRUCTNAME = .{ | |
| 3406 | .Offset = @truncate(offset), | |
| 3407 | .OffsetHigh = @truncate(offset >> 32), | |
| 3408 | }, | |
| 3409 | }, | |
| 3410 | .hEvent = null, | |
| 3411 | }; | |
| 3412 | ||
| 3413 | while (true) { | |
| 3414 | try current_thread.checkCancel(); | |
| 3415 | var n: DWORD = undefined; | |
| 3416 | if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) | |
| 3417 | return n; | |
| 3418 | switch (windows.GetLastError()) { | |
| 3419 | .IO_PENDING => |err| return windows.errorBug(err), | |
| 3420 | .OPERATION_ABORTED => continue, | |
| 3421 | .BROKEN_PIPE => return 0, | |
| 3422 | .HANDLE_EOF => return 0, | |
| 3423 | .NETNAME_DELETED => return error.ConnectionResetByPeer, | |
| 3424 | .LOCK_VIOLATION => return error.LockViolation, | |
| 3425 | .ACCESS_DENIED => return error.AccessDenied, | |
| 3426 | .INVALID_HANDLE => return error.NotOpenForReading, | |
| 3427 | else => |err| return windows.unexpectedError(err), | |
| 3428 | } | |
| 3429 | } | |
| 8142 | _ = t; | |
| 8143 | _ = socket_handle; | |
| 8144 | _ = header; | |
| 8145 | _ = file_reader; | |
| 8146 | _ = limit; | |
| 8147 | @panic("TODO implement netWriteFile"); | |
| 3430 | 8148 | } |
| 3431 | 8149 | |
| 3432 | fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void { | |
| 8150 | fn 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 | 8157 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3434 | 8158 | _ = t; |
| 3435 | _ = file; | |
| 3436 | _ = offset; | |
| 3437 | @panic("TODO implement fileSeekBy"); | |
| 8159 | _ = socket_handle; | |
| 8160 | _ = header; | |
| 8161 | _ = file_reader; | |
| 8162 | _ = limit; | |
| 8163 | return error.NetworkDown; | |
| 3438 | 8164 | } |
| 3439 | 8165 | |
| 3440 | fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void { | |
| 8166 | fn 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 | 8174 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3442 | const current_thread = Thread.getCurrent(t); | |
| 3443 | const fd = file.handle; | |
| 3444 | ||
| 3445 | if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { | |
| 3446 | try current_thread.beginSyscall(); | |
| 3447 | while (true) { | |
| 3448 | var result: u64 = undefined; | |
| 3449 | switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) { | |
| 3450 | .SUCCESS => { | |
| 3451 | current_thread.endSyscall(); | |
| 3452 | return; | |
| 3453 | }, | |
| 3454 | .INTR => { | |
| 3455 | try current_thread.checkCancel(); | |
| 3456 | continue; | |
| 3457 | }, | |
| 3458 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3459 | else => |e| { | |
| 3460 | current_thread.endSyscall(); | |
| 3461 | switch (e) { | |
| 3462 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. | |
| 3463 | .INVAL => return error.Unseekable, | |
| 3464 | .OVERFLOW => return error.Unseekable, | |
| 3465 | .SPIPE => return error.Unseekable, | |
| 3466 | .NXIO => return error.Unseekable, | |
| 3467 | else => |err| return posix.unexpectedErrno(err), | |
| 3468 | } | |
| 3469 | }, | |
| 8175 | const reader_buffered = file_reader.interface.buffered(); | |
| 8176 | if (reader_buffered.len >= @intFromEnum(limit)) { | |
| 8177 | const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); | |
| 8178 | file_reader.interface.toss(n -| header.len); | |
| 8179 | return n; | |
| 8180 | } | |
| 8181 | const out_fd = file.handle; | |
| 8182 | const in_fd = file_reader.file.handle; | |
| 8183 | ||
| 8184 | if (file_reader.size) |size| { | |
| 8185 | if (size - file_reader.pos == 0) { | |
| 8186 | if (reader_buffered.len != 0) { | |
| 8187 | const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); | |
| 8188 | file_reader.interface.toss(n -| header.len); | |
| 8189 | return n; | |
| 8190 | } else { | |
| 8191 | return error.EndOfStream; | |
| 3470 | 8192 | } |
| 3471 | 8193 | } |
| 3472 | 8194 | } |
| 3473 | 8195 | |
| 3474 | if (native_os == .windows) { | |
| 3475 | try current_thread.checkCancel(); | |
| 3476 | return windows.SetFilePointerEx_BEGIN(fd, offset); | |
| 3477 | } | |
| 3478 | ||
| 3479 | if (native_os == .wasi and !builtin.link_libc) while (true) { | |
| 3480 | var new_offset: std.os.wasi.filesize_t = undefined; | |
| 3481 | try current_thread.beginSyscall(); | |
| 3482 | switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { | |
| 3483 | .SUCCESS => { | |
| 3484 | current_thread.endSyscall(); | |
| 3485 | return; | |
| 3486 | }, | |
| 3487 | .INTR => { | |
| 3488 | try current_thread.checkCancel(); | |
| 3489 | continue; | |
| 3490 | }, | |
| 3491 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3492 | else => |e| { | |
| 3493 | current_thread.endSyscall(); | |
| 3494 | switch (e) { | |
| 3495 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. | |
| 3496 | .INVAL => return error.Unseekable, | |
| 3497 | .OVERFLOW => return error.Unseekable, | |
| 3498 | .SPIPE => return error.Unseekable, | |
| 3499 | .NXIO => return error.Unseekable, | |
| 3500 | .NOTCAPABLE => return error.AccessDenied, | |
| 3501 | else => |err| return posix.unexpectedErrno(err), | |
| 3502 | } | |
| 3503 | }, | |
| 8196 | if (have_copy_file_range) cfr: { | |
| 8197 | if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; | |
| 8198 | if (header.len != 0 or reader_buffered.len != 0) { | |
| 8199 | const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); | |
| 8200 | file_reader.interface.toss(n -| header.len); | |
| 8201 | return n; | |
| 3504 | 8202 | } |
| 3505 | }; | |
| 3506 | ||
| 3507 | if (posix.SEEK == void) return error.Unseekable; | |
| 3508 | ||
| 3509 | try current_thread.beginSyscall(); | |
| 3510 | while (true) { | |
| 3511 | switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) { | |
| 3512 | .SUCCESS => { | |
| 3513 | current_thread.endSyscall(); | |
| 3514 | return; | |
| 3515 | }, | |
| 3516 | .INTR => { | |
| 3517 | try current_thread.checkCancel(); | |
| 3518 | continue; | |
| 8203 | var off_in: i64 = undefined; | |
| 8204 | const off_in_ptr: ?*i64 = switch (file_reader.mode) { | |
| 8205 | .positional_simple, .streaming_simple => return error.Unimplemented, | |
| 8206 | .positional => p: { | |
| 8207 | off_in = @intCast(file_reader.pos); | |
| 8208 | break :p &off_in; | |
| 8209 | }, | |
| 8210 | .streaming => null, | |
| 8211 | .failure => return error.ReadFailed, | |
| 8212 | }; | |
| 8213 | var off_out: i64 = @intCast(offset); | |
| 8214 | const current_thread = Thread.getCurrent(t); | |
| 8215 | const n: usize = switch (native_os) { | |
| 8216 | .linux => n: { | |
| 8217 | try current_thread.beginSyscall(); | |
| 8218 | while (true) { | |
| 8219 | const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0); | |
| 8220 | switch (linux_copy_file_range_sys.errno(rc)) { | |
| 8221 | .SUCCESS => { | |
| 8222 | current_thread.endSyscall(); | |
| 8223 | break :n @intCast(rc); | |
| 8224 | }, | |
| 8225 | .INTR => { | |
| 8226 | try current_thread.checkCancel(); | |
| 8227 | continue; | |
| 8228 | }, | |
| 8229 | .OPNOTSUPP, .INVAL, .NOSYS => { | |
| 8230 | // Give calling code chance to observe before trying | |
| 8231 | // something else. | |
| 8232 | current_thread.endSyscall(); | |
| 8233 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); | |
| 8234 | return 0; | |
| 8235 | }, | |
| 8236 | else => |e| { | |
| 8237 | current_thread.endSyscall(); | |
| 8238 | assert(error.Unexpected == switch (e) { | |
| 8239 | .FBIG => return error.FileTooBig, | |
| 8240 | .IO => return error.InputOutput, | |
| 8241 | .NOMEM => return error.SystemResources, | |
| 8242 | .NOSPC => return error.NoSpaceLeft, | |
| 8243 | .OVERFLOW => return error.Unseekable, | |
| 8244 | .NXIO => return error.Unseekable, | |
| 8245 | .SPIPE => return error.Unseekable, | |
| 8246 | .PERM => return error.PermissionDenied, | |
| 8247 | .TXTBSY => return error.FileBusy, | |
| 8248 | // copy_file_range can still work but not on | |
| 8249 | // this pair of file descriptors. | |
| 8250 | .XDEV => return error.Unimplemented, | |
| 8251 | .ISDIR => |err| errnoBug(err), | |
| 8252 | .BADF => |err| errnoBug(err), | |
| 8253 | else => |err| posix.unexpectedErrno(err), | |
| 8254 | }); | |
| 8255 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); | |
| 8256 | return 0; | |
| 8257 | }, | |
| 8258 | } | |
| 8259 | } | |
| 3519 | 8260 | }, |
| 3520 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3521 | else => |e| { | |
| 3522 | current_thread.endSyscall(); | |
| 3523 | switch (e) { | |
| 3524 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. | |
| 3525 | .INVAL => return error.Unseekable, | |
| 3526 | .OVERFLOW => return error.Unseekable, | |
| 3527 | .SPIPE => return error.Unseekable, | |
| 3528 | .NXIO => return error.Unseekable, | |
| 3529 | else => |err| return posix.unexpectedErrno(err), | |
| 8261 | .freebsd => n: { | |
| 8262 | try current_thread.beginSyscall(); | |
| 8263 | while (true) { | |
| 8264 | const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0); | |
| 8265 | switch (std.c.errno(rc)) { | |
| 8266 | .SUCCESS => { | |
| 8267 | current_thread.endSyscall(); | |
| 8268 | break :n @intCast(rc); | |
| 8269 | }, | |
| 8270 | .INTR => { | |
| 8271 | try current_thread.checkCancel(); | |
| 8272 | continue; | |
| 8273 | }, | |
| 8274 | .OPNOTSUPP, .INVAL, .NOSYS => { | |
| 8275 | // Give calling code chance to observe before trying | |
| 8276 | // something else. | |
| 8277 | current_thread.endSyscall(); | |
| 8278 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); | |
| 8279 | return 0; | |
| 8280 | }, | |
| 8281 | else => |e| { | |
| 8282 | current_thread.endSyscall(); | |
| 8283 | assert(error.Unexpected == switch (e) { | |
| 8284 | .FBIG => return error.FileTooBig, | |
| 8285 | .IO => return error.InputOutput, | |
| 8286 | .INTEGRITY => return error.CorruptedData, | |
| 8287 | .NOSPC => return error.NoSpaceLeft, | |
| 8288 | .OVERFLOW => return error.Unseekable, | |
| 8289 | .NXIO => return error.Unseekable, | |
| 8290 | .SPIPE => return error.Unseekable, | |
| 8291 | .ISDIR => |err| errnoBug(err), | |
| 8292 | .BADF => |err| errnoBug(err), | |
| 8293 | else => |err| posix.unexpectedErrno(err), | |
| 8294 | }); | |
| 8295 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); | |
| 8296 | return 0; | |
| 8297 | }, | |
| 8298 | } | |
| 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 | ||
| 3536 | fn 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 | ||
| 3553 | fn 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 | } | |
| 3568 | 8356 | |
| 3569 | fn fileWriteStreaming(userdata: ?*anyopaque, file: Io.File, buffer: [][]const u8) Io.File.WriteStreamingError!usize { | |
| 3570 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 3571 | _ = t; | |
| 3572 | while (true) { | |
| 3573 | _ = file; | |
| 3574 | _ = buffer; | |
| 3575 | @panic("TODO implement fileWriteStreaming"); | |
| 3576 | } | |
| 8357 | return error.Unimplemented; | |
| 3577 | 8358 | } |
| 3578 | 8359 | |
| 3579 | 8360 | fn 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 | 8454 | try current_thread.checkCancel(); |
| 3674 | 8455 | continue; |
| 3675 | 8456 | }, |
| 3676 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3677 | 8457 | else => |e| { |
| 3678 | 8458 | current_thread.endSyscall(); |
| 3679 | 8459 | switch (e) { |
| ... | ... | @@ -3751,7 +8531,6 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 3751 | 8531 | try current_thread.checkCancel(); |
| 3752 | 8532 | continue; |
| 3753 | 8533 | }, |
| 3754 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3755 | 8534 | // This prong handles success as well as unexpected errors. |
| 3756 | 8535 | else => return current_thread.endSyscall(), |
| 3757 | 8536 | } |
| ... | ... | @@ -3825,7 +8604,6 @@ fn netListenIpPosix( |
| 3825 | 8604 | try current_thread.checkCancel(); |
| 3826 | 8605 | continue; |
| 3827 | 8606 | }, |
| 3828 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3829 | 8607 | else => |e| { |
| 3830 | 8608 | current_thread.endSyscall(); |
| 3831 | 8609 | switch (e) { |
| ... | ... | @@ -3989,7 +8767,6 @@ fn netListenUnixPosix( |
| 3989 | 8767 | try current_thread.checkCancel(); |
| 3990 | 8768 | continue; |
| 3991 | 8769 | }, |
| 3992 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 3993 | 8770 | else => |e| { |
| 3994 | 8771 | current_thread.endSyscall(); |
| 3995 | 8772 | switch (e) { |
| ... | ... | @@ -4113,7 +8890,6 @@ fn posixBindUnix( |
| 4113 | 8890 | try current_thread.checkCancel(); |
| 4114 | 8891 | continue; |
| 4115 | 8892 | }, |
| 4116 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4117 | 8893 | else => |e| { |
| 4118 | 8894 | current_thread.endSyscall(); |
| 4119 | 8895 | switch (e) { |
| ... | ... | @@ -4158,7 +8934,6 @@ fn posixBind( |
| 4158 | 8934 | try current_thread.checkCancel(); |
| 4159 | 8935 | continue; |
| 4160 | 8936 | }, |
| 4161 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4162 | 8937 | else => |e| { |
| 4163 | 8938 | current_thread.endSyscall(); |
| 4164 | 8939 | switch (e) { |
| ... | ... | @@ -4194,7 +8969,6 @@ fn posixConnect( |
| 4194 | 8969 | try current_thread.checkCancel(); |
| 4195 | 8970 | continue; |
| 4196 | 8971 | }, |
| 4197 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4198 | 8972 | else => |e| { |
| 4199 | 8973 | current_thread.endSyscall(); |
| 4200 | 8974 | switch (e) { |
| ... | ... | @@ -4241,7 +9015,6 @@ fn posixConnectUnix( |
| 4241 | 9015 | try current_thread.checkCancel(); |
| 4242 | 9016 | continue; |
| 4243 | 9017 | }, |
| 4244 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4245 | 9018 | else => |e| { |
| 4246 | 9019 | current_thread.endSyscall(); |
| 4247 | 9020 | switch (e) { |
| ... | ... | @@ -4286,7 +9059,6 @@ fn posixGetSockName( |
| 4286 | 9059 | try current_thread.checkCancel(); |
| 4287 | 9060 | continue; |
| 4288 | 9061 | }, |
| 4289 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4290 | 9062 | else => |e| { |
| 4291 | 9063 | current_thread.endSyscall(); |
| 4292 | 9064 | switch (e) { |
| ... | ... | @@ -4354,7 +9126,6 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name |
| 4354 | 9126 | try current_thread.checkCancel(); |
| 4355 | 9127 | continue; |
| 4356 | 9128 | }, |
| 4357 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4358 | 9129 | else => |e| { |
| 4359 | 9130 | current_thread.endSyscall(); |
| 4360 | 9131 | switch (e) { |
| ... | ... | @@ -4684,7 +9455,6 @@ fn openSocketPosix( |
| 4684 | 9455 | switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { |
| 4685 | 9456 | .SUCCESS => break, |
| 4686 | 9457 | .INTR => continue, |
| 4687 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4688 | 9458 | else => |err| { |
| 4689 | 9459 | current_thread.endSyscall(); |
| 4690 | 9460 | return posix.unexpectedErrno(err); |
| ... | ... | @@ -4698,7 +9468,6 @@ fn openSocketPosix( |
| 4698 | 9468 | try current_thread.checkCancel(); |
| 4699 | 9469 | continue; |
| 4700 | 9470 | }, |
| 4701 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4702 | 9471 | else => |e| { |
| 4703 | 9472 | current_thread.endSyscall(); |
| 4704 | 9473 | switch (e) { |
| ... | ... | @@ -4800,7 +9569,6 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve |
| 4800 | 9569 | try current_thread.checkCancel(); |
| 4801 | 9570 | continue; |
| 4802 | 9571 | }, |
| 4803 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4804 | 9572 | else => |e| { |
| 4805 | 9573 | current_thread.endSyscall(); |
| 4806 | 9574 | switch (e) { |
| ... | ... | @@ -4909,7 +9677,6 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. |
| 4909 | 9677 | try current_thread.checkCancel(); |
| 4910 | 9678 | continue; |
| 4911 | 9679 | }, |
| 4912 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4913 | 9680 | else => |e| { |
| 4914 | 9681 | current_thread.endSyscall(); |
| 4915 | 9682 | switch (e) { |
| ... | ... | @@ -4942,7 +9709,6 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. |
| 4942 | 9709 | try current_thread.checkCancel(); |
| 4943 | 9710 | continue; |
| 4944 | 9711 | }, |
| 4945 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4946 | 9712 | else => |e| { |
| 4947 | 9713 | current_thread.endSyscall(); |
| 4948 | 9714 | switch (e) { |
| ... | ... | @@ -5178,7 +9944,6 @@ fn netSendOne( |
| 5178 | 9944 | try current_thread.checkCancel(); |
| 5179 | 9945 | continue; |
| 5180 | 9946 | }, |
| 5181 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 5182 | 9947 | else => |e| { |
| 5183 | 9948 | current_thread.endSyscall(); |
| 5184 | 9949 | switch (e) { |
| ... | ... | @@ -5255,7 +10020,6 @@ fn netSendMany( |
| 5255 | 10020 | try current_thread.checkCancel(); |
| 5256 | 10021 | continue; |
| 5257 | 10022 | }, |
| 5258 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 5259 | 10023 | else => |e| { |
| 5260 | 10024 | current_thread.endSyscall(); |
| 5261 | 10025 | switch (e) { |
| ... | ... | @@ -5388,7 +10152,6 @@ fn netReceivePosix( |
| 5388 | 10152 | continue :recv; |
| 5389 | 10153 | }, |
| 5390 | 10154 | .INTR => continue, |
| 5391 | .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, | |
| 5392 | 10155 | |
| 5393 | 10156 | .FAULT => |err| return .{ errnoBug(err), message_i }, |
| 5394 | 10157 | .INVAL => |err| return .{ errnoBug(err), message_i }, |
| ... | ... | @@ -5397,7 +10160,6 @@ fn netReceivePosix( |
| 5397 | 10160 | } |
| 5398 | 10161 | }, |
| 5399 | 10162 | .INTR => continue, |
| 5400 | .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, | |
| 5401 | 10163 | |
| 5402 | 10164 | .BADF => |err| return .{ errnoBug(err), message_i }, |
| 5403 | 10165 | .NFILE => return .{ error.SystemFdQuotaExceeded, message_i }, |
| ... | ... | @@ -5496,7 +10258,7 @@ fn netWritePosix( |
| 5496 | 10258 | addBuf(&iovecs, &msg.iovlen, splat_buffer); |
| 5497 | 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 | 10263 | else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| { |
| 5502 | 10264 | addBuf(&iovecs, &msg.iovlen, pattern); |
| ... | ... | @@ -5504,6 +10266,7 @@ fn netWritePosix( |
| 5504 | 10266 | }, |
| 5505 | 10267 | }; |
| 5506 | 10268 | const flags = posix.MSG.NOSIGNAL; |
| 10269 | ||
| 5507 | 10270 | try current_thread.beginSyscall(); |
| 5508 | 10271 | while (true) { |
| 5509 | 10272 | const rc = posix.system.sendmsg(fd, &msg, flags); |
| ... | ... | @@ -5516,7 +10279,6 @@ fn netWritePosix( |
| 5516 | 10279 | try current_thread.checkCancel(); |
| 5517 | 10280 | continue; |
| 5518 | 10281 | }, |
| 5519 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 5520 | 10282 | else => |e| { |
| 5521 | 10283 | current_thread.endSyscall(); |
| 5522 | 10284 | switch (e) { |
| ... | ... | @@ -5580,7 +10342,7 @@ fn netWriteWindows( |
| 5580 | 10342 | addWsaBuf(&iovecs, &len, splat_buffer); |
| 5581 | 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 | 10347 | else => for (0..@min(splat, iovecs.len - len)) |_| { |
| 5586 | 10348 | addWsaBuf(&iovecs, &len, pattern); |
| ... | ... | @@ -5614,8 +10376,7 @@ fn netWriteWindows( |
| 5614 | 10376 | else => |err| err, |
| 5615 | 10377 | }; |
| 5616 | 10378 | switch (wsa_error) { |
| 5617 | .EINTR => continue, | |
| 5618 | .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return current_thread.endSyscallCanceled(), | |
| 10379 | .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, | |
| 5619 | 10380 | .NOTINITIALISED => { |
| 5620 | 10381 | try initializeWsa(t); |
| 5621 | 10382 | continue; |
| ... | ... | @@ -5667,7 +10428,14 @@ fn netWriteUnavailable( |
| 5667 | 10428 | return error.NetworkDown; |
| 5668 | 10429 | } |
| 5669 | 10430 | |
| 5670 | fn 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. | |
| 10433 | const iovlen_t = switch (native_os) { | |
| 10434 | .wasi => u32, | |
| 10435 | else => @FieldType(posix.msghdr_const, "iovlen"), | |
| 10436 | }; | |
| 10437 | ||
| 10438 | fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void { | |
| 5671 | 10439 | // OS checks ptr addr before length so zero length vectors must be omitted. |
| 5672 | 10440 | if (bytes.len == 0) return; |
| 5673 | 10441 | if (v.len - i.* == 0) return; |
| ... | ... | @@ -5675,18 +10443,18 @@ fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), |
| 5675 | 10443 | i.* += 1; |
| 5676 | 10444 | } |
| 5677 | 10445 | |
| 5678 | fn netClose(userdata: ?*anyopaque, handle: net.Socket.Handle) void { | |
| 10446 | fn netClose(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void { | |
| 5679 | 10447 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5680 | 10448 | _ = t; |
| 5681 | 10449 | switch (native_os) { |
| 5682 | .windows => closeSocketWindows(handle), | |
| 5683 | else => posix.close(handle), | |
| 10450 | .windows => for (handles) |handle| closeSocketWindows(handle), | |
| 10451 | else => for (handles) |handle| posix.close(handle), | |
| 5684 | 10452 | } |
| 5685 | 10453 | } |
| 5686 | 10454 | |
| 5687 | fn netCloseUnavailable(userdata: ?*anyopaque, handle: net.Socket.Handle) void { | |
| 10455 | fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void { | |
| 5688 | 10456 | _ = userdata; |
| 5689 | _ = handle; | |
| 10457 | _ = handles; | |
| 5690 | 10458 | unreachable; // How you gonna close something that was impossible to open? |
| 5691 | 10459 | } |
| 5692 | 10460 | |
| ... | ... | @@ -5727,7 +10495,6 @@ fn netInterfaceNameResolve( |
| 5727 | 10495 | try current_thread.checkCancel(); |
| 5728 | 10496 | continue; |
| 5729 | 10497 | }, |
| 5730 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 5731 | 10498 | else => |e| { |
| 5732 | 10499 | current_thread.endSyscall(); |
| 5733 | 10500 | switch (e) { |
| ... | ... | @@ -5993,7 +10760,7 @@ fn netLookupFallible( |
| 5993 | 10760 | // TODO use dnsres_getaddrinfo |
| 5994 | 10761 | } |
| 5995 | 10762 | |
| 5996 | if (native_os.isDarwin()) { | |
| 10763 | if (is_darwin) { | |
| 5997 | 10764 | // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution |
| 5998 | 10765 | } |
| 5999 | 10766 | |
| ... | ... | @@ -6031,7 +10798,6 @@ fn netLookupFallible( |
| 6031 | 10798 | try current_thread.checkCancel(); |
| 6032 | 10799 | continue; |
| 6033 | 10800 | }, |
| 6034 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 6035 | 10801 | else => |e| { |
| 6036 | 10802 | current_thread.endSyscall(); |
| 6037 | 10803 | return posix.unexpectedErrno(e); |
| ... | ... | @@ -6078,6 +10844,140 @@ fn netLookupFallible( |
| 6078 | 10844 | return error.OptionUnsupported; |
| 6079 | 10845 | } |
| 6080 | 10846 | |
| 10847 | fn 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 | ||
| 10858 | fn 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 | ||
| 10869 | fn 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 | ||
| 10903 | fn 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 | ||
| 10916 | fn 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 | ||
| 6081 | 10981 | pub const PosixAddress = extern union { |
| 6082 | 10982 | any: posix.sockaddr, |
| 6083 | 10983 | in: posix.sockaddr.in, |
| ... | ... | @@ -6269,14 +11169,30 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t { |
| 6269 | 11169 | }; |
| 6270 | 11170 | } |
| 6271 | 11171 | |
| 6272 | fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat { | |
| 11172 | const 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 | ||
| 11183 | fn 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 | 11188 | const atime = stx.atime; |
| 6274 | 11189 | const mtime = stx.mtime; |
| 6275 | 11190 | const ctime = stx.ctime; |
| 6276 | 11191 | return .{ |
| 6277 | 11192 | .inode = stx.ino, |
| 11193 | .nlink = stx.nlink, | |
| 6278 | 11194 | .size = stx.size, |
| 6279 | .mode = stx.mode, | |
| 11195 | .permissions = .fromMode(stx.mode), | |
| 6280 | 11196 | .kind = switch (stx.mode & std.os.linux.S.IFMT) { |
| 6281 | 11197 | std.os.linux.S.IFDIR => .directory, |
| 6282 | 11198 | std.os.linux.S.IFCHR => .character_device, |
| ... | ... | @@ -6293,14 +11209,15 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat { |
| 6293 | 11209 | }; |
| 6294 | 11210 | } |
| 6295 | 11211 | |
| 6296 | fn statFromPosix(st: *const posix.Stat) Io.File.Stat { | |
| 11212 | fn statFromPosix(st: *const posix.Stat) File.Stat { | |
| 6297 | 11213 | const atime = st.atime(); |
| 6298 | 11214 | const mtime = st.mtime(); |
| 6299 | 11215 | const ctime = st.ctime(); |
| 6300 | 11216 | return .{ |
| 6301 | 11217 | .inode = st.ino, |
| 11218 | .nlink = st.nlink, | |
| 6302 | 11219 | .size = @bitCast(st.size), |
| 6303 | .mode = st.mode, | |
| 11220 | .permissions = .fromMode(st.mode), | |
| 6304 | 11221 | .kind = k: { |
| 6305 | 11222 | const m = st.mode & posix.S.IFMT; |
| 6306 | 11223 | switch (m) { |
| ... | ... | @@ -6327,11 +11244,12 @@ fn statFromPosix(st: *const posix.Stat) Io.File.Stat { |
| 6327 | 11244 | }; |
| 6328 | 11245 | } |
| 6329 | 11246 | |
| 6330 | fn statFromWasi(st: *const std.os.wasi.filestat_t) Io.File.Stat { | |
| 11247 | fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat { | |
| 6331 | 11248 | return .{ |
| 6332 | 11249 | .inode = st.ino, |
| 11250 | .nlink = st.nlink, | |
| 6333 | 11251 | .size = @bitCast(st.size), |
| 6334 | .mode = 0, | |
| 11252 | .permissions = .default_file, | |
| 6335 | 11253 | .kind = switch (st.filetype) { |
| 6336 | 11254 | .BLOCK_DEVICE => .block_device, |
| 6337 | 11255 | .CHARACTER_DEVICE => .character_device, |
| ... | ... | @@ -6352,13 +11270,20 @@ fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp { |
| 6352 | 11270 | } |
| 6353 | 11271 | |
| 6354 | 11272 | fn 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 | 11280 | return .{ |
| 6356 | 11281 | .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)), |
| 6357 | 11282 | .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)), |
| 6358 | 11283 | }; |
| 6359 | 11284 | } |
| 6360 | 11285 | |
| 6361 | fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNameError![:0]u8 { | |
| 11286 | fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 { | |
| 6362 | 11287 | if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName; |
| 6363 | 11288 | // >= rather than > to make room for the null byte |
| 6364 | 11289 | if (file_path.len >= buffer.len) return error.NameTooLong; |
| ... | ... | @@ -6605,7 +11530,7 @@ fn lookupHosts( |
| 6605 | 11530 | options: HostName.LookupOptions, |
| 6606 | 11531 | ) !void { |
| 6607 | 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 | 11534 | error.FileNotFound, |
| 6610 | 11535 | error.NotDir, |
| 6611 | 11536 | error.AccessDenied, |
| ... | ... | @@ -6809,6 +11734,506 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { |
| 6809 | 11734 | |
| 6810 | 11735 | fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {} |
| 6811 | 11736 | |
| 11737 | const 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 | ||
| 12122 | fn 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 | ||
| 6812 | 12237 | test { |
| 6813 | 12238 | _ = @import("Threaded/test.zig"); |
| 6814 | 12239 | } |
lib/std/Io/Threaded/test.zig+7-5| ... | ... | @@ -1,3 +1,5 @@ |
| 1 | //! Tests belong here if they access internal state of std.Io.Threaded or | |
| 2 | //! otherwise assume details of that particular implementation. | |
| 1 | 3 | const builtin = @import("builtin"); |
| 2 | 4 | |
| 3 | 5 | const std = @import("std"); |
| ... | ... | @@ -11,7 +13,7 @@ test "concurrent vs main prevents deadlock via oversubscription" { |
| 11 | 13 | return error.SkipZigTest; |
| 12 | 14 | } |
| 13 | 15 | |
| 14 | var threaded: Io.Threaded = .init(std.testing.allocator); | |
| 16 | var threaded: Io.Threaded = .init(std.testing.allocator, .{}); | |
| 15 | 17 | defer threaded.deinit(); |
| 16 | 18 | const io = threaded.io(); |
| 17 | 19 | |
| ... | ... | @@ -44,7 +46,7 @@ test "concurrent vs concurrent prevents deadlock via oversubscription" { |
| 44 | 46 | return error.SkipZigTest; |
| 45 | 47 | } |
| 46 | 48 | |
| 47 | var threaded: Io.Threaded = .init(std.testing.allocator); | |
| 49 | var threaded: Io.Threaded = .init(std.testing.allocator, .{}); | |
| 48 | 50 | defer threaded.deinit(); |
| 49 | 51 | const io = threaded.io(); |
| 50 | 52 | |
| ... | ... | @@ -78,7 +80,7 @@ test "async/concurrent context and result alignment" { |
| 78 | 80 | var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined; |
| 79 | 81 | var fba: std.heap.FixedBufferAllocator = .init(&buffer); |
| 80 | 82 | |
| 81 | var threaded: std.Io.Threaded = .init(fba.allocator()); | |
| 83 | var threaded: std.Io.Threaded = .init(fba.allocator(), .{}); | |
| 82 | 84 | defer threaded.deinit(); |
| 83 | 85 | const io = threaded.io(); |
| 84 | 86 | |
| ... | ... | @@ -111,7 +113,7 @@ test "Group.async context alignment" { |
| 111 | 113 | var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined; |
| 112 | 114 | var fba: std.heap.FixedBufferAllocator = .init(&buffer); |
| 113 | 115 | |
| 114 | var threaded: std.Io.Threaded = .init(fba.allocator()); | |
| 116 | var threaded: std.Io.Threaded = .init(fba.allocator(), .{}); | |
| 115 | 117 | defer threaded.deinit(); |
| 116 | 118 | const io = threaded.io(); |
| 117 | 119 | |
| ... | ... | @@ -131,7 +133,7 @@ fn returnArray() [32]u8 { |
| 131 | 133 | } |
| 132 | 134 | |
| 133 | 135 | test "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 | 137 | defer threaded.deinit(); |
| 136 | 138 | const io = threaded.io(); |
| 137 | 139 |
lib/std/Io/Writer.zig+15-14| ... | ... | @@ -1,7 +1,8 @@ |
| 1 | const Writer = @This(); | |
| 2 | ||
| 1 | 3 | const builtin = @import("builtin"); |
| 2 | 4 | const native_endian = builtin.target.cpu.arch.endian(); |
| 3 | 5 | |
| 4 | const Writer = @This(); | |
| 5 | 6 | const std = @import("../std.zig"); |
| 6 | 7 | const assert = std.debug.assert; |
| 7 | 8 | const Limit = std.Io.Limit; |
| ... | ... | @@ -960,7 +961,7 @@ pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllE |
| 960 | 961 | const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) { |
| 961 | 962 | error.EndOfStream => break, |
| 962 | 963 | error.Unimplemented => { |
| 963 | file_reader.mode = file_reader.mode.toReading(); | |
| 964 | file_reader.mode = file_reader.mode.toSimple(); | |
| 964 | 965 | remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining)); |
| 965 | 966 | break; |
| 966 | 967 | }, |
| ... | ... | @@ -2834,14 +2835,14 @@ test "discarding sendFile" { |
| 2834 | 2835 | var tmp_dir = testing.tmpDir(.{}); |
| 2835 | 2836 | defer tmp_dir.cleanup(); |
| 2836 | 2837 | |
| 2837 | const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); | |
| 2838 | defer file.close(); | |
| 2838 | const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); | |
| 2839 | defer file.close(io); | |
| 2839 | 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 | 2842 | try file_writer.interface.writeByte('h'); |
| 2842 | 2843 | try file_writer.interface.flush(); |
| 2843 | 2844 | |
| 2844 | var file_reader = file_writer.moveToReader(io); | |
| 2845 | var file_reader = file_writer.moveToReader(); | |
| 2845 | 2846 | try file_reader.seekTo(0); |
| 2846 | 2847 | |
| 2847 | 2848 | var w_buffer: [256]u8 = undefined; |
| ... | ... | @@ -2856,14 +2857,14 @@ test "allocating sendFile" { |
| 2856 | 2857 | var tmp_dir = testing.tmpDir(.{}); |
| 2857 | 2858 | defer tmp_dir.cleanup(); |
| 2858 | 2859 | |
| 2859 | const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); | |
| 2860 | defer file.close(); | |
| 2860 | const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); | |
| 2861 | defer file.close(io); | |
| 2861 | 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 | 2864 | try file_writer.interface.writeAll("abcd"); |
| 2864 | 2865 | try file_writer.interface.flush(); |
| 2865 | 2866 | |
| 2866 | var file_reader = file_writer.moveToReader(io); | |
| 2867 | var file_reader = file_writer.moveToReader(); | |
| 2867 | 2868 | try file_reader.seekTo(0); |
| 2868 | 2869 | try file_reader.interface.fill(2); |
| 2869 | 2870 | |
| ... | ... | @@ -2880,14 +2881,14 @@ test sendFileReading { |
| 2880 | 2881 | var tmp_dir = testing.tmpDir(.{}); |
| 2881 | 2882 | defer tmp_dir.cleanup(); |
| 2882 | 2883 | |
| 2883 | const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); | |
| 2884 | defer file.close(); | |
| 2884 | const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); | |
| 2885 | defer file.close(io); | |
| 2885 | 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 | 2888 | try file_writer.interface.writeAll("abcd"); |
| 2888 | 2889 | try file_writer.interface.flush(); |
| 2889 | 2890 | |
| 2890 | var file_reader = file_writer.moveToReader(io); | |
| 2891 | var file_reader = file_writer.moveToReader(); | |
| 2891 | 2892 | try file_reader.seekTo(0); |
| 2892 | 2893 | try file_reader.interface.fill(2); |
| 2893 | 2894 |
lib/std/Io/net.zig+22-3| ... | ... | @@ -1043,7 +1043,11 @@ pub const Socket = struct { |
| 1043 | 1043 | |
| 1044 | 1044 | /// Leaves `address` in a valid state. |
| 1045 | 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 | } |
| 1048 | 1052 | |
| 1049 | 1053 | pub const SendError = error{ |
| ... | ... | @@ -1184,7 +1188,7 @@ pub const Stream = struct { |
| 1184 | 1188 | const max_iovecs_len = 8; |
| 1185 | 1189 | |
| 1186 | 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 | } |
| 1189 | 1193 | |
| 1190 | 1194 | pub const Reader = struct { |
| ... | ... | @@ -1256,6 +1260,7 @@ pub const Stream = struct { |
| 1256 | 1260 | interface: Io.Writer, |
| 1257 | 1261 | stream: Stream, |
| 1258 | 1262 | err: ?Error = null, |
| 1263 | write_file_err: ?WriteFileError = null, | |
| 1259 | 1264 | |
| 1260 | 1265 | pub const Error = error{ |
| 1261 | 1266 | /// Another TCP Fast Open is already in progress. |
| ... | ... | @@ -1285,12 +1290,19 @@ pub const Stream = struct { |
| 1285 | 1290 | SocketNotBound, |
| 1286 | 1291 | } || Io.UnexpectedError || Io.Cancelable; |
| 1287 | 1292 | |
| 1293 | pub const WriteFileError = error{ | |
| 1294 | NetworkDown, | |
| 1295 | } || Io.Cancelable || Io.UnexpectedError; | |
| 1296 | ||
| 1288 | 1297 | pub fn init(stream: Stream, io: Io, buffer: []u8) Writer { |
| 1289 | 1298 | return .{ |
| 1290 | 1299 | .io = io, |
| 1291 | 1300 | .stream = stream, |
| 1292 | 1301 | .interface = .{ |
| 1293 | .vtable = &.{ .drain = drain }, | |
| 1302 | .vtable = &.{ | |
| 1303 | .drain = drain, | |
| 1304 | .sendFile = sendFile, | |
| 1305 | }, | |
| 1294 | 1306 | .buffer = buffer, |
| 1295 | 1307 | }, |
| 1296 | 1308 | }; |
| ... | ... | @@ -1307,6 +1319,13 @@ pub const Stream = struct { |
| 1307 | 1319 | }; |
| 1308 | 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 | }; |
| 1311 | 1330 | |
| 1312 | 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 | 343 | .attempts = 2, |
| 344 | 344 | }; |
| 345 | 345 | |
| 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 | 347 | error.FileNotFound, |
| 348 | 348 | error.NotDir, |
| 349 | 349 | error.AccessDenied, |
lib/std/Io/net/test.zig+9-4| ... | ... | @@ -232,8 +232,10 @@ test "listen on an in use port" { |
| 232 | 232 | fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void { |
| 233 | 233 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 234 | 234 | |
| 235 | const io = testing.io; | |
| 236 | ||
| 235 | 237 | const connection = try net.tcpConnectToHost(allocator, name, port); |
| 236 | defer connection.close(); | |
| 238 | defer connection.close(io); | |
| 237 | 239 | |
| 238 | 240 | var buf: [100]u8 = undefined; |
| 239 | 241 | const len = try connection.read(&buf); |
| ... | ... | @@ -244,8 +246,10 @@ fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyer |
| 244 | 246 | fn testClient(addr: net.IpAddress) anyerror!void { |
| 245 | 247 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 246 | 248 | |
| 249 | const io = testing.io; | |
| 250 | ||
| 247 | 251 | const socket_file = try net.tcpConnectToAddress(addr); |
| 248 | defer socket_file.close(); | |
| 252 | defer socket_file.close(io); | |
| 249 | 253 | |
| 250 | 254 | var buf: [100]u8 = undefined; |
| 251 | 255 | const len = try socket_file.read(&buf); |
| ... | ... | @@ -267,6 +271,7 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 267 | 271 | if (builtin.single_threaded) return error.SkipZigTest; |
| 268 | 272 | if (!net.has_unix_sockets) return error.SkipZigTest; |
| 269 | 273 | 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 | |
| 270 | 275 | |
| 271 | 276 | const io = testing.io; |
| 272 | 277 | |
| ... | ... | @@ -274,7 +279,7 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 274 | 279 | defer testing.allocator.free(socket_path); |
| 275 | 280 | |
| 276 | 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 {}; | |
| 278 | 283 | |
| 279 | 284 | var server = try socket_addr.listen(io, .{}); |
| 280 | 285 | defer server.socket.close(io); |
| ... | ... | @@ -330,7 +335,7 @@ test "non-blocking tcp server" { |
| 330 | 335 | try testing.expectError(error.WouldBlock, accept_err); |
| 331 | 336 | |
| 332 | 337 | const socket_file = try net.tcpConnectToAddress(server.socket.address); |
| 333 | defer socket_file.close(); | |
| 338 | defer socket_file.close(io); | |
| 334 | 339 | |
| 335 | 340 | var stream = try server.accept(io); |
| 336 | 341 | defer stream.close(io); |
lib/std/Io/test.zig+108-52| ... | ... | @@ -3,16 +3,17 @@ const native_endian = builtin.cpu.arch.endian(); |
| 3 | 3 | |
| 4 | 4 | const std = @import("std"); |
| 5 | 5 | const Io = std.Io; |
| 6 | const testing = std.testing; | |
| 7 | const expect = std.testing.expect; | |
| 8 | const expectEqual = std.testing.expectEqual; | |
| 9 | const expectError = std.testing.expectError; | |
| 10 | 6 | const DefaultPrng = std.Random.DefaultPrng; |
| 11 | 7 | const mem = std.mem; |
| 12 | 8 | const fs = std.fs; |
| 13 | const File = std.fs.File; | |
| 9 | const File = std.Io.File; | |
| 14 | 10 | const assert = std.debug.assert; |
| 15 | 11 | |
| 12 | const testing = std.testing; | |
| 13 | const expect = std.testing.expect; | |
| 14 | const expectEqual = std.testing.expectEqual; | |
| 15 | const expectError = std.testing.expectError; | |
| 16 | const expectEqualStrings = std.testing.expectEqualStrings; | |
| 16 | 17 | const tmpDir = std.testing.tmpDir; |
| 17 | 18 | |
| 18 | 19 | test "write a file, read it, then delete it" { |
| ... | ... | @@ -27,10 +28,10 @@ test "write a file, read it, then delete it" { |
| 27 | 28 | random.bytes(data[0..]); |
| 28 | 29 | const tmp_file_name = "temp_test_file.txt"; |
| 29 | 30 | { |
| 30 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 31 | defer file.close(); | |
| 31 | var file = try tmp.dir.createFile(io, tmp_file_name, .{}); | |
| 32 | defer file.close(io); | |
| 32 | 33 | |
| 33 | var file_writer = file.writer(&.{}); | |
| 34 | var file_writer = file.writer(io, &.{}); | |
| 34 | 35 | const st = &file_writer.interface; |
| 35 | 36 | try st.print("begin", .{}); |
| 36 | 37 | try st.writeAll(&data); |
| ... | ... | @@ -40,14 +41,14 @@ test "write a file, read it, then delete it" { |
| 40 | 41 | |
| 41 | 42 | { |
| 42 | 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 | } |
| 45 | 46 | |
| 46 | 47 | { |
| 47 | var file = try tmp.dir.openFile(tmp_file_name, .{}); | |
| 48 | defer file.close(); | |
| 48 | var file = try tmp.dir.openFile(io, tmp_file_name, .{}); | |
| 49 | defer file.close(io); | |
| 49 | 50 | |
| 50 | const file_size = try file.getEndPos(); | |
| 51 | const file_size = try file.length(io); | |
| 51 | 52 | const expected_file_size: u64 = "begin".len + data.len + "end".len; |
| 52 | 53 | try expectEqual(expected_file_size, file_size); |
| 53 | 54 | |
| ... | ... | @@ -60,71 +61,126 @@ test "write a file, read it, then delete it" { |
| 60 | 61 | try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data)); |
| 61 | 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 | } |
| 65 | 66 | |
| 66 | test "File seek ops" { | |
| 67 | test "File.Writer.seekTo" { | |
| 67 | 68 | var tmp = tmpDir(.{}); |
| 68 | 69 | defer tmp.cleanup(); |
| 69 | 70 | |
| 71 | const io = testing.io; | |
| 72 | ||
| 73 | var data: [8192]u8 = undefined; | |
| 74 | @memset(&data, 0x55); | |
| 75 | ||
| 70 | 76 | const tmp_file_name = "temp_test_file.txt"; |
| 71 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 72 | defer file.close(); | |
| 73 | ||
| 74 | try file.writeAll(&([_]u8{0x55} ** 8192)); | |
| 75 | ||
| 76 | // Seek to the end | |
| 77 | try file.seekFromEnd(0); | |
| 78 | try expect((try file.getPos()) == try file.getEndPos()); | |
| 79 | // Negative delta | |
| 80 | try file.seekBy(-4096); | |
| 81 | try expect((try file.getPos()) == 4096); | |
| 82 | // Positive delta | |
| 83 | try file.seekBy(10); | |
| 84 | try expect((try file.getPos()) == 4106); | |
| 85 | // Absolute position | |
| 86 | try file.seekTo(1234); | |
| 87 | try expect((try file.getPos()) == 1234); | |
| 77 | var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true }); | |
| 78 | defer file.close(io); | |
| 79 | ||
| 80 | var fw = file.writerStreaming(io, &.{}); | |
| 81 | ||
| 82 | try fw.interface.writeAll(&data); | |
| 83 | try expect(fw.logicalPos() == try file.length(io)); | |
| 84 | try fw.seekTo(1234); | |
| 85 | try expect(fw.logicalPos() == 1234); | |
| 88 | 86 | } |
| 89 | 87 | |
| 90 | test "setEndPos" { | |
| 88 | test "File.setLength" { | |
| 89 | const io = testing.io; | |
| 90 | ||
| 91 | 91 | var tmp = tmpDir(.{}); |
| 92 | 92 | defer tmp.cleanup(); |
| 93 | 93 | |
| 94 | 94 | const tmp_file_name = "temp_test_file.txt"; |
| 95 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 96 | defer file.close(); | |
| 95 | var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true }); | |
| 96 | defer file.close(io); | |
| 97 | ||
| 98 | var fw = file.writerStreaming(io, &.{}); | |
| 97 | 99 | |
| 98 | 100 | // Verify that the file size changes and the file offset is not moved |
| 99 | try expect((try file.getEndPos()) == 0); | |
| 100 | try expect((try file.getPos()) == 0); | |
| 101 | try file.setEndPos(8192); | |
| 102 | try expect((try file.getEndPos()) == 8192); | |
| 103 | try expect((try file.getPos()) == 0); | |
| 104 | try file.seekTo(100); | |
| 105 | try file.setEndPos(4096); | |
| 106 | try expect((try file.getEndPos()) == 4096); | |
| 107 | try expect((try file.getPos()) == 100); | |
| 108 | try file.setEndPos(0); | |
| 109 | try expect((try file.getEndPos()) == 0); | |
| 110 | try expect((try file.getPos()) == 100); | |
| 101 | try expect((try file.length(io)) == 0); | |
| 102 | try expect(fw.logicalPos() == 0); | |
| 103 | try file.setLength(io, 8192); | |
| 104 | try expect((try file.length(io)) == 8192); | |
| 105 | try expect(fw.logicalPos() == 0); | |
| 106 | try fw.seekTo(100); | |
| 107 | try file.setLength(io, 4096); | |
| 108 | try expect((try file.length(io)) == 4096); | |
| 109 | try expect(fw.logicalPos() == 100); | |
| 110 | try file.setLength(io, 0); | |
| 111 | try expect((try file.length(io)) == 0); | |
| 112 | try expect(fw.logicalPos() == 100); | |
| 111 | 113 | } |
| 112 | 114 | |
| 113 | test "updateTimes" { | |
| 115 | test "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 | ||
| 166 | test "setTimestamps" { | |
| 167 | const io = testing.io; | |
| 168 | ||
| 114 | 169 | var tmp = tmpDir(.{}); |
| 115 | 170 | defer tmp.cleanup(); |
| 116 | 171 | |
| 117 | 172 | const tmp_file_name = "just_a_temporary_file.txt"; |
| 118 | var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true }); | |
| 119 | defer file.close(); | |
| 173 | var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true }); | |
| 174 | defer file.close(io); | |
| 120 | 175 | |
| 121 | const stat_old = try file.stat(); | |
| 176 | const stat_old = try file.stat(io); | |
| 122 | 177 | // Set atime and mtime to 5s before |
| 123 | try file.updateTimes( | |
| 178 | try file.setTimestamps( | |
| 179 | io, | |
| 124 | 180 | stat_old.atime.subDuration(.fromSeconds(5)), |
| 125 | 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 | 184 | try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds); |
| 129 | 185 | try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds); |
| 130 | 186 | } |
lib/std/Io/tty.zig deleted-131| ... | ... | @@ -1,131 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const File = std.fs.File; | |
| 4 | const process = std.process; | |
| 5 | const windows = std.os.windows; | |
| 6 | const native_os = builtin.os.tag; | |
| 7 | ||
| 8 | pub 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. | |
| 32 | pub 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 | 1 | //! This API is non-allocating, non-fallible, thread-safe, and lock-free. |
| 2 | const Progress = @This(); | |
| 2 | 3 | |
| 3 | const std = @import("std"); | |
| 4 | 4 | const builtin = @import("builtin"); |
| 5 | const is_big_endian = builtin.cpu.arch.endian() == .big; | |
| 6 | const is_windows = builtin.os.tag == .windows; | |
| 7 | ||
| 8 | const std = @import("std"); | |
| 9 | const Io = std.Io; | |
| 5 | 10 | const windows = std.os.windows; |
| 6 | 11 | const testing = std.testing; |
| 7 | 12 | const assert = std.debug.assert; |
| 8 | const Progress = @This(); | |
| 9 | 13 | const posix = std.posix; |
| 10 | const is_big_endian = builtin.cpu.arch.endian() == .big; | |
| 11 | const is_windows = builtin.os.tag == .windows; | |
| 12 | 14 | const Writer = std.Io.Writer; |
| 13 | 15 | |
| 14 | /// `null` if the current node (and its children) should | |
| 15 | /// not print on update() | |
| 16 | terminal: std.fs.File, | |
| 16 | /// Currently this API only supports this value being set to stderr, which | |
| 17 | /// happens automatically inside `start`. | |
| 18 | terminal: Io.File, | |
| 19 | ||
| 20 | io: Io, | |
| 17 | 21 | |
| 18 | 22 | terminal_mode: TerminalMode, |
| 19 | 23 | |
| 20 | update_thread: ?std.Thread, | |
| 24 | update_worker: ?Io.Future(void), | |
| 21 | 25 | |
| 22 | 26 | /// Atomically set by SIGWINCH as well as the root done() function. |
| 23 | redraw_event: std.Thread.ResetEvent, | |
| 27 | redraw_event: Io.Event, | |
| 24 | 28 | /// Indicates a request to shut down and reset global state. |
| 25 | 29 | /// Accessed atomically. |
| 26 | 30 | done: bool, |
| ... | ... | @@ -48,6 +52,8 @@ node_freelist: Freelist, |
| 48 | 52 | /// value may at times temporarily exceed the node count. |
| 49 | 53 | node_end_index: u32, |
| 50 | 54 | |
| 55 | start_failure: StartFailure, | |
| 56 | ||
| 51 | 57 | pub const Status = enum { |
| 52 | 58 | /// Indicates the application is progressing towards completion of a task. |
| 53 | 59 | /// Unless the application is interactive, this is the only status the |
| ... | ... | @@ -93,9 +99,9 @@ pub const Options = struct { |
| 93 | 99 | /// Must be at least 200 bytes. |
| 94 | 100 | draw_buffer: []u8 = &default_draw_buffer, |
| 95 | 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 | 103 | /// 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 | 105 | /// If provided, causes the progress item to have a denominator. |
| 100 | 106 | /// 0 means unknown. |
| 101 | 107 | estimated_total_items: usize = 0, |
| ... | ... | @@ -121,20 +127,20 @@ pub const Node = struct { |
| 121 | 127 | name: [max_name_len]u8 align(@alignOf(usize)), |
| 122 | 128 | |
| 123 | 129 | /// Not thread-safe. |
| 124 | fn getIpcFd(s: Storage) ?posix.fd_t { | |
| 125 | return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(posix.fd_t)) { | |
| 130 | fn getIpcFd(s: Storage) ?Io.File.Handle { | |
| 131 | return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) { | |
| 126 | 132 | .int => @bitCast(s.completed_count), |
| 127 | 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 | 135 | } else null; |
| 130 | 136 | } |
| 131 | 137 | |
| 132 | 138 | /// Thread-safe. |
| 133 | fn setIpcFd(s: *Storage, fd: posix.fd_t) void { | |
| 134 | const integer: u32 = switch (@typeInfo(posix.fd_t)) { | |
| 139 | fn setIpcFd(s: *Storage, fd: Io.File.Handle) void { | |
| 140 | const integer: u32 = switch (@typeInfo(Io.File.Handle)) { | |
| 135 | 141 | .int => @bitCast(fd), |
| 136 | 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 | 145 | // `estimated_total_count` max int indicates the special state that |
| 140 | 146 | // causes `completed_count` to be treated as a file descriptor, so |
| ... | ... | @@ -327,13 +333,14 @@ pub const Node = struct { |
| 327 | 333 | } |
| 328 | 334 | } else { |
| 329 | 335 | @atomicStore(bool, &global_progress.done, true, .monotonic); |
| 330 | global_progress.redraw_event.set(); | |
| 331 | if (global_progress.update_thread) |thread| thread.join(); | |
| 336 | const io = global_progress.io; | |
| 337 | global_progress.redraw_event.set(io); | |
| 338 | if (global_progress.update_worker) |*worker| worker.await(io); | |
| 332 | 339 | } |
| 333 | 340 | } |
| 334 | 341 | |
| 335 | 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 | 344 | const index = node.index.unwrap() orelse return; |
| 338 | 345 | assert(fd >= 0); |
| 339 | 346 | assert(fd != posix.STDOUT_FILENO); |
| ... | ... | @@ -344,14 +351,14 @@ pub const Node = struct { |
| 344 | 351 | |
| 345 | 352 | /// Posix-only. Thread-safe. Assumes the node is storing an IPC file |
| 346 | 353 | /// descriptor. |
| 347 | pub fn getIpcFd(node: Node) ?posix.fd_t { | |
| 354 | pub fn getIpcFd(node: Node) ?Io.File.Handle { | |
| 348 | 355 | const index = node.index.unwrap() orelse return null; |
| 349 | 356 | const storage = storageByIndex(index); |
| 350 | 357 | const int = @atomicLoad(u32, &storage.completed_count, .monotonic); |
| 351 | return switch (@typeInfo(posix.fd_t)) { | |
| 358 | return switch (@typeInfo(Io.File.Handle)) { | |
| 352 | 359 | .int => @bitCast(int), |
| 353 | 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 | } |
| 357 | 364 | |
| ... | ... | @@ -389,9 +396,10 @@ pub const Node = struct { |
| 389 | 396 | }; |
| 390 | 397 | |
| 391 | 398 | var global_progress: Progress = .{ |
| 399 | .io = undefined, | |
| 392 | 400 | .terminal = undefined, |
| 393 | 401 | .terminal_mode = .off, |
| 394 | .update_thread = null, | |
| 402 | .update_worker = null, | |
| 395 | 403 | .redraw_event = .unset, |
| 396 | 404 | .refresh_rate_ns = undefined, |
| 397 | 405 | .initial_delay_ns = undefined, |
| ... | ... | @@ -401,6 +409,7 @@ var global_progress: Progress = .{ |
| 401 | 409 | .done = false, |
| 402 | 410 | .need_clear = false, |
| 403 | 411 | .status = .working, |
| 412 | .start_failure = .unstarted, | |
| 404 | 413 | |
| 405 | 414 | .node_parents = &node_parents_buffer, |
| 406 | 415 | .node_storage = &node_storage_buffer, |
| ... | ... | @@ -409,6 +418,13 @@ var global_progress: Progress = .{ |
| 409 | 418 | .node_end_index = 0, |
| 410 | 419 | }; |
| 411 | 420 | |
| 421 | pub 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 | ||
| 412 | 428 | const node_storage_buffer_len = 83; |
| 413 | 429 | var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined; |
| 414 | 430 | var 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 | 451 | /// Asserts there is only one global Progress instance. |
| 436 | 452 | /// |
| 437 | 453 | /// Call `Node.end` when done. |
| 438 | pub fn start(options: Options) Node { | |
| 454 | /// | |
| 455 | /// If an error occurs, `start_failure` will be populated. | |
| 456 | pub fn start(io: Io, options: Options) Node { | |
| 439 | 457 | // Ensure there is only 1 global Progress object. |
| 440 | 458 | if (global_progress.node_end_index != 0) { |
| 441 | 459 | debug_start_trace.dump(); |
| ... | ... | @@ -450,21 +468,24 @@ pub fn start(options: Options) Node { |
| 450 | 468 | |
| 451 | 469 | assert(options.draw_buffer.len >= 200); |
| 452 | 470 | global_progress.draw_buffer = options.draw_buffer; |
| 453 | global_progress.refresh_rate_ns = options.refresh_rate_ns; | |
| 454 | global_progress.initial_delay_ns = options.initial_delay_ns; | |
| 471 | global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds()); | |
| 472 | global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds()); | |
| 455 | 473 | |
| 456 | 474 | if (noop_impl) |
| 457 | 475 | return Node.none; |
| 458 | 476 | |
| 477 | global_progress.io = io; | |
| 478 | ||
| 459 | 479 | if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| { |
| 460 | global_progress.update_thread = std.Thread.spawn(.{}, ipcThreadRun, .{ | |
| 461 | @as(posix.fd_t, switch (@typeInfo(posix.fd_t)) { | |
| 480 | global_progress.update_worker = io.concurrent(ipcThreadRun, .{ | |
| 481 | io, | |
| 482 | @as(Io.File, .{ .handle = switch (@typeInfo(Io.File.Handle)) { | |
| 462 | 483 | .int => ipc_fd, |
| 463 | 484 | .pointer => @ptrFromInt(ipc_fd), |
| 464 | else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)), | |
| 465 | }), | |
| 485 | else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), | |
| 486 | } }), | |
| 466 | 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 | 489 | return Node.none; |
| 469 | 490 | }; |
| 470 | 491 | } else |env_err| switch (env_err) { |
| ... | ... | @@ -472,14 +493,21 @@ pub fn start(options: Options) Node { |
| 472 | 493 | if (options.disable_printing) { |
| 473 | 494 | return Node.none; |
| 474 | 495 | } |
| 475 | const stderr: std.fs.File = .stderr(); | |
| 496 | const stderr: Io.File = .stderr(); | |
| 476 | 497 | global_progress.terminal = stderr; |
| 477 | if (stderr.getOrEnableAnsiEscapeSupport()) { | |
| 498 | if (stderr.enableAnsiEscapeCodes(io)) |_| { | |
| 478 | 499 | global_progress.terminal_mode = .ansi_escape_codes; |
| 479 | } else if (is_windows and stderr.isTty()) { | |
| 480 | global_progress.terminal_mode = TerminalMode{ .windows_api = .{ | |
| 481 | .code_page = windows.kernel32.GetConsoleOutputCP(), | |
| 482 | } }; | |
| 500 | } else |_| if (is_windows) { | |
| 501 | if (stderr.isTty(io)) |is_tty| { | |
| 502 | if (is_tty) global_progress.terminal_mode = TerminalMode{ .windows_api = .{ | |
| 503 | .code_page = windows.kernel32.GetConsoleOutputCP(), | |
| 504 | } }; | |
| 505 | } else |err| switch (err) { | |
| 506 | error.Canceled => { | |
| 507 | io.recancel(); | |
| 508 | return Node.none; | |
| 509 | }, | |
| 510 | } | |
| 483 | 511 | } |
| 484 | 512 | |
| 485 | 513 | if (global_progress.terminal_mode == .off) { |
| ... | ... | @@ -497,17 +525,17 @@ pub fn start(options: Options) Node { |
| 497 | 525 | |
| 498 | 526 | if (switch (global_progress.terminal_mode) { |
| 499 | 527 | .off => unreachable, // handled a few lines above |
| 500 | .ansi_escape_codes => std.Thread.spawn(.{}, updateThreadRun, .{}), | |
| 501 | .windows_api => if (is_windows) std.Thread.spawn(.{}, windowsApiUpdateThreadRun, .{}) else unreachable, | |
| 502 | }) |thread| { | |
| 503 | global_progress.update_thread = thread; | |
| 528 | .ansi_escape_codes => io.concurrent(updateThreadRun, .{io}), | |
| 529 | .windows_api => if (is_windows) io.concurrent(windowsApiUpdateThreadRun, .{io}) else unreachable, | |
| 530 | }) |future| { | |
| 531 | global_progress.update_worker = future; | |
| 504 | 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 | 534 | return Node.none; |
| 507 | 535 | } |
| 508 | 536 | }, |
| 509 | 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 | 539 | return Node.none; |
| 512 | 540 | }, |
| 513 | 541 | } |
| ... | ... | @@ -521,48 +549,52 @@ pub fn setStatus(new_status: Status) void { |
| 521 | 549 | } |
| 522 | 550 | |
| 523 | 551 | /// Returns whether a resize is needed to learn the terminal size. |
| 524 | fn wait(timeout_ns: u64) bool { | |
| 525 | const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_| true else |err| switch (err) { | |
| 526 | error.Timeout => false, | |
| 552 | fn wait(io: Io, timeout_ns: u64) bool { | |
| 553 | const timeout: Io.Timeout = .{ .duration = .{ | |
| 554 | .clock = .awake, | |
| 555 | .raw = .fromNanoseconds(timeout_ns), | |
| 556 | } }; | |
| 557 | const resize_flag = if (global_progress.redraw_event.waitTimeout(io, timeout)) |_| true else |err| switch (err) { | |
| 558 | error.Timeout, error.Canceled => false, | |
| 527 | 559 | }; |
| 528 | 560 | global_progress.redraw_event.reset(); |
| 529 | 561 | return resize_flag or (global_progress.cols == 0); |
| 530 | 562 | } |
| 531 | 563 | |
| 532 | fn updateThreadRun() void { | |
| 564 | fn updateThreadRun(io: Io) void { | |
| 533 | 565 | // Store this data in the thread so that it does not need to be part of the |
| 534 | 566 | // linker data of the main executable. |
| 535 | 567 | var serialized_buffer: Serialized.Buffer = undefined; |
| 536 | 568 | |
| 537 | 569 | { |
| 538 | const resize_flag = wait(global_progress.initial_delay_ns); | |
| 570 | const resize_flag = wait(io, global_progress.initial_delay_ns); | |
| 539 | 571 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; |
| 540 | 572 | maybeUpdateSize(resize_flag); |
| 541 | 573 | |
| 542 | 574 | const buffer, _ = computeRedraw(&serialized_buffer); |
| 543 | if (stderr_mutex.tryLock()) { | |
| 544 | defer stderr_mutex.unlock(); | |
| 545 | write(buffer) catch return; | |
| 575 | if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { | |
| 576 | defer io.unlockStderr(); | |
| 546 | 577 | global_progress.need_clear = true; |
| 578 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | |
| 547 | 579 | } |
| 548 | 580 | } |
| 549 | 581 | |
| 550 | 582 | while (true) { |
| 551 | const resize_flag = wait(global_progress.refresh_rate_ns); | |
| 583 | const resize_flag = wait(io, global_progress.refresh_rate_ns); | |
| 552 | 584 | |
| 553 | 585 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) { |
| 554 | stderr_mutex.lock(); | |
| 555 | defer stderr_mutex.unlock(); | |
| 556 | return clearWrittenWithEscapeCodes() catch {}; | |
| 586 | const stderr = io.lockStderr(&.{}, null) catch return; | |
| 587 | defer io.unlockStderr(); | |
| 588 | return clearWrittenWithEscapeCodes(stderr.file_writer) catch {}; | |
| 557 | 589 | } |
| 558 | 590 | |
| 559 | 591 | maybeUpdateSize(resize_flag); |
| 560 | 592 | |
| 561 | 593 | const buffer, _ = computeRedraw(&serialized_buffer); |
| 562 | if (stderr_mutex.tryLock()) { | |
| 563 | defer stderr_mutex.unlock(); | |
| 564 | write(buffer) catch return; | |
| 594 | if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { | |
| 595 | defer io.unlockStderr(); | |
| 565 | 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 | 607 | _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null); |
| 576 | 608 | } |
| 577 | 609 | |
| 578 | fn windowsApiUpdateThreadRun() void { | |
| 610 | fn windowsApiUpdateThreadRun(io: Io) void { | |
| 579 | 611 | var serialized_buffer: Serialized.Buffer = undefined; |
| 580 | 612 | |
| 581 | 613 | { |
| 582 | const resize_flag = wait(global_progress.initial_delay_ns); | |
| 614 | const resize_flag = wait(io, global_progress.initial_delay_ns); | |
| 583 | 615 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; |
| 584 | 616 | maybeUpdateSize(resize_flag); |
| 585 | 617 | |
| 586 | 618 | const buffer, const nl_n = computeRedraw(&serialized_buffer); |
| 587 | if (stderr_mutex.tryLock()) { | |
| 588 | defer stderr_mutex.unlock(); | |
| 619 | if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { | |
| 620 | defer io.unlockStderr(); | |
| 589 | 621 | windowsApiWriteMarker(); |
| 590 | write(buffer) catch return; | |
| 591 | 622 | global_progress.need_clear = true; |
| 623 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | |
| 592 | 624 | windowsApiMoveToMarker(nl_n) catch return; |
| 593 | 625 | } |
| 594 | 626 | } |
| 595 | 627 | |
| 596 | 628 | while (true) { |
| 597 | const resize_flag = wait(global_progress.refresh_rate_ns); | |
| 629 | const resize_flag = wait(io, global_progress.refresh_rate_ns); | |
| 598 | 630 | |
| 599 | 631 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) { |
| 600 | stderr_mutex.lock(); | |
| 601 | defer stderr_mutex.unlock(); | |
| 632 | _ = io.lockStderr(&.{}, null) catch return; | |
| 633 | defer io.unlockStderr(); | |
| 602 | 634 | return clearWrittenWindowsApi() catch {}; |
| 603 | 635 | } |
| 604 | 636 | |
| 605 | 637 | maybeUpdateSize(resize_flag); |
| 606 | 638 | |
| 607 | 639 | const buffer, const nl_n = computeRedraw(&serialized_buffer); |
| 608 | if (stderr_mutex.tryLock()) { | |
| 609 | defer stderr_mutex.unlock(); | |
| 640 | if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { | |
| 641 | defer io.unlockStderr(); | |
| 610 | 642 | clearWrittenWindowsApi() catch return; |
| 611 | 643 | windowsApiWriteMarker(); |
| 612 | write(buffer) catch return; | |
| 613 | 644 | global_progress.need_clear = true; |
| 645 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | |
| 614 | 646 | windowsApiMoveToMarker(nl_n) catch return; |
| 615 | 647 | } |
| 616 | 648 | } |
| 617 | 649 | } |
| 618 | 650 | |
| 619 | /// Allows the caller to freely write to stderr until `unlockStdErr` is called. | |
| 620 | /// | |
| 621 | /// During the lock, any `std.Progress` information is cleared from the terminal. | |
| 622 | /// | |
| 623 | /// The lock is recursive; the same thread may hold the lock multiple times. | |
| 624 | pub fn lockStdErr() void { | |
| 625 | stderr_mutex.lock(); | |
| 626 | clearWrittenWithEscapeCodes() catch {}; | |
| 627 | } | |
| 628 | ||
| 629 | pub fn unlockStdErr() void { | |
| 630 | stderr_mutex.unlock(); | |
| 631 | } | |
| 632 | ||
| 633 | /// Protected by `stderr_mutex`. | |
| 634 | const stderr_writer: *Writer = &stderr_file_writer.interface; | |
| 635 | /// Protected by `stderr_mutex`. | |
| 636 | var 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. | |
| 648 | pub 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 | ||
| 657 | pub fn unlockStderrWriter() void { | |
| 658 | stderr_writer.flush() catch {}; | |
| 659 | stderr_writer.end = 0; | |
| 660 | stderr_writer.buffer = &.{}; | |
| 661 | stderr_mutex.unlock(); | |
| 662 | } | |
| 663 | ||
| 664 | fn ipcThreadRun(fd: posix.fd_t) anyerror!void { | |
| 651 | fn ipcThreadRun(io: Io, file: Io.File) void { | |
| 665 | 652 | // Store this data in the thread so that it does not need to be part of the |
| 666 | 653 | // linker data of the main executable. |
| 667 | 654 | var serialized_buffer: Serialized.Buffer = undefined; |
| 668 | 655 | |
| 669 | 656 | { |
| 670 | _ = wait(global_progress.initial_delay_ns); | |
| 657 | _ = wait(io, global_progress.initial_delay_ns); | |
| 671 | 658 | |
| 672 | 659 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) |
| 673 | 660 | return; |
| 674 | 661 | |
| 675 | 662 | const serialized = serialize(&serialized_buffer); |
| 676 | writeIpc(fd, serialized) catch |err| switch (err) { | |
| 663 | writeIpc(io, file, serialized) catch |err| switch (err) { | |
| 677 | 664 | error.BrokenPipe => return, |
| 678 | 665 | }; |
| 679 | 666 | } |
| 680 | 667 | |
| 681 | 668 | while (true) { |
| 682 | _ = wait(global_progress.refresh_rate_ns); | |
| 669 | _ = wait(io, global_progress.refresh_rate_ns); | |
| 683 | 670 | |
| 684 | 671 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) |
| 685 | 672 | return; |
| 686 | 673 | |
| 687 | 674 | const serialized = serialize(&serialized_buffer); |
| 688 | writeIpc(fd, serialized) catch |err| switch (err) { | |
| 675 | writeIpc(io, file, serialized) catch |err| switch (err) { | |
| 689 | 676 | error.BrokenPipe => return, |
| 690 | 677 | }; |
| 691 | 678 | } |
| ... | ... | @@ -784,11 +771,10 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { |
| 784 | 771 | } |
| 785 | 772 | } |
| 786 | 773 | |
| 787 | fn clearWrittenWithEscapeCodes() anyerror!void { | |
| 774 | pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error!void { | |
| 788 | 775 | if (noop_impl or !global_progress.need_clear) return; |
| 789 | ||
| 776 | try file_writer.interface.writeAll(clear ++ progress_remove); | |
| 790 | 777 | global_progress.need_clear = false; |
| 791 | try write(clear ++ progress_remove); | |
| 792 | 778 | } |
| 793 | 779 | |
| 794 | 780 | /// U+25BA or ► |
| ... | ... | @@ -948,11 +934,11 @@ const SavedMetadata = struct { |
| 948 | 934 | const Fd = enum(i32) { |
| 949 | 935 | _, |
| 950 | 936 | |
| 951 | fn init(fd: posix.fd_t) Fd { | |
| 937 | fn init(fd: Io.File.Handle) Fd { | |
| 952 | 938 | return @enumFromInt(if (is_windows) @as(isize, @bitCast(@intFromPtr(fd))) else fd); |
| 953 | 939 | } |
| 954 | 940 | |
| 955 | fn get(fd: Fd) posix.fd_t { | |
| 941 | fn get(fd: Fd) Io.File.Handle { | |
| 956 | 942 | return if (is_windows) |
| 957 | 943 | @ptrFromInt(@as(usize, @bitCast(@as(isize, @intFromEnum(fd))))) |
| 958 | 944 | else |
| ... | ... | @@ -963,6 +949,7 @@ const Fd = enum(i32) { |
| 963 | 949 | var ipc_metadata_len: u8 = 0; |
| 964 | 950 | |
| 965 | 951 | fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize { |
| 952 | const io = global_progress.io; | |
| 966 | 953 | const ipc_metadata_fds_copy = &serialized_buffer.ipc_metadata_fds_copy; |
| 967 | 954 | const ipc_metadata_copy = &serialized_buffer.ipc_metadata_copy; |
| 968 | 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 | 968 | 0.., |
| 982 | 969 | ) |main_parent, *main_storage, main_index| { |
| 983 | 970 | if (main_parent == .unused) continue; |
| 984 | const fd = main_storage.getIpcFd() orelse continue; | |
| 985 | const opt_saved_metadata = findOld(fd, old_ipc_metadata_fds, old_ipc_metadata); | |
| 971 | const file: Io.File = .{ .handle = main_storage.getIpcFd() orelse continue }; | |
| 972 | const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); | |
| 986 | 973 | var bytes_read: usize = 0; |
| 987 | 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 | 976 | error.WouldBlock => break, |
| 990 | 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 | 979 | main_storage.completed_count = 0; |
| 993 | 980 | main_storage.estimated_total_count = 0; |
| 994 | 981 | continue :main_loop; |
| ... | ... | @@ -1014,7 +1001,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff |
| 1014 | 1001 | // Ignore all but the last message on the pipe. |
| 1015 | 1002 | var input: []u8 = pipe_buf[0..bytes_read]; |
| 1016 | 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 | 1005 | continue; |
| 1019 | 1006 | } |
| 1020 | 1007 | |
| ... | ... | @@ -1024,7 +1011,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff |
| 1024 | 1011 | if (input.len < expected_bytes) { |
| 1025 | 1012 | // Ignore short reads. We'll handle the next full message when it comes instead. |
| 1026 | 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 | 1015 | continue :main_loop; |
| 1029 | 1016 | } |
| 1030 | 1017 | if (input.len > expected_bytes) { |
| ... | ... | @@ -1042,7 +1029,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff |
| 1042 | 1029 | const nodes_len: u8 = @intCast(@min(parents.len - 1, serialized_buffer.storage.len - serialized_len)); |
| 1043 | 1030 | |
| 1044 | 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 | 1033 | ipc_metadata[ipc_metadata_len] = .{ |
| 1047 | 1034 | .remaining_read_trash_bytes = 0, |
| 1048 | 1035 | .start_index = @intCast(serialized_len), |
| ... | ... | @@ -1100,7 +1087,7 @@ fn copyRoot(dest: *Node.Storage, src: *align(1) Node.Storage) void { |
| 1100 | 1087 | } |
| 1101 | 1088 | |
| 1102 | 1089 | fn findOld( |
| 1103 | ipc_fd: posix.fd_t, | |
| 1090 | ipc_fd: Io.File.Handle, | |
| 1104 | 1091 | old_metadata_fds: []Fd, |
| 1105 | 1092 | old_metadata: []SavedMetadata, |
| 1106 | 1093 | ) ?*SavedMetadata { |
| ... | ... | @@ -1118,7 +1105,7 @@ fn useSavedIpcData( |
| 1118 | 1105 | main_index: usize, |
| 1119 | 1106 | opt_saved_metadata: ?*SavedMetadata, |
| 1120 | 1107 | remaining_read_trash_bytes: u16, |
| 1121 | fd: posix.fd_t, | |
| 1108 | fd: Io.File.Handle, | |
| 1122 | 1109 | ) usize { |
| 1123 | 1110 | const parents_copy = &serialized_buffer.parents_copy; |
| 1124 | 1111 | const storage_copy = &serialized_buffer.storage_copy; |
| ... | ... | @@ -1415,13 +1402,9 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool { |
| 1415 | 1402 | return nl_n + 2 < p.rows; |
| 1416 | 1403 | } |
| 1417 | 1404 | |
| 1418 | fn write(buf: []const u8) anyerror!void { | |
| 1419 | try global_progress.terminal.writeAll(buf); | |
| 1420 | } | |
| 1421 | ||
| 1422 | 1405 | var remaining_write_trash_bytes: usize = 0; |
| 1423 | 1406 | |
| 1424 | fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void { | |
| 1407 | fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void { | |
| 1425 | 1408 | // Byteswap if necessary to ensure little endian over the pipe. This is |
| 1426 | 1409 | // needed because the parent or child process might be running in qemu. |
| 1427 | 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 | 1415 | const storage = std.mem.sliceAsBytes(serialized.storage); |
| 1433 | 1416 | const parents = std.mem.sliceAsBytes(serialized.parents); |
| 1434 | 1417 | |
| 1435 | var vecs: [3]posix.iovec_const = .{ | |
| 1436 | .{ .base = header.ptr, .len = header.len }, | |
| 1437 | .{ .base = storage.ptr, .len = storage.len }, | |
| 1438 | .{ .base = parents.ptr, .len = parents.len }, | |
| 1439 | }; | |
| 1418 | var vecs: [3][]const u8 = .{ header, storage, parents }; | |
| 1440 | 1419 | |
| 1441 | 1420 | // Ensures the packet can fit in the pipe buffer. |
| 1442 | 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 | 1426 | // We do this in a separate write call to give a better chance for the |
| 1448 | 1427 | // writev below to be in a single packet. |
| 1449 | 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 | 1430 | remaining_write_trash_bytes -= written; |
| 1452 | 1431 | continue; |
| 1453 | 1432 | } else |err| switch (err) { |
| 1454 | 1433 | error.WouldBlock => return, |
| 1455 | 1434 | error.BrokenPipe => return error.BrokenPipe, |
| 1456 | 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 | 1437 | return error.BrokenPipe; |
| 1459 | 1438 | }, |
| 1460 | 1439 | } |
| ... | ... | @@ -1462,7 +1441,7 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void { |
| 1462 | 1441 | |
| 1463 | 1442 | // If this write would block we do not want to keep trying, but we need to |
| 1464 | 1443 | // know if a partial message was written. |
| 1465 | if (writevNonblock(fd, &vecs)) |written| { | |
| 1444 | if (writevNonblock(io, file, &vecs)) |written| { | |
| 1466 | 1445 | const total = header.len + storage.len + parents.len; |
| 1467 | 1446 | if (written < total) { |
| 1468 | 1447 | remaining_write_trash_bytes = total - written; |
| ... | ... | @@ -1471,13 +1450,13 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void { |
| 1471 | 1450 | error.WouldBlock => {}, |
| 1472 | 1451 | error.BrokenPipe => return error.BrokenPipe, |
| 1473 | 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 | 1454 | return error.BrokenPipe; |
| 1476 | 1455 | }, |
| 1477 | 1456 | } |
| 1478 | 1457 | } |
| 1479 | 1458 | |
| 1480 | fn writevNonblock(fd: posix.fd_t, iov: []posix.iovec_const) posix.WriteError!usize { | |
| 1459 | fn writevNonblock(io: Io, file: Io.File, iov: [][]const u8) Io.File.Writer.Error!usize { | |
| 1481 | 1460 | var iov_index: usize = 0; |
| 1482 | 1461 | var written: usize = 0; |
| 1483 | 1462 | var total_written: usize = 0; |
| ... | ... | @@ -1486,9 +1465,9 @@ fn writevNonblock(fd: posix.fd_t, iov: []posix.iovec_const) posix.WriteError!usi |
| 1486 | 1465 | written >= iov[iov_index].len |
| 1487 | 1466 | else |
| 1488 | 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 | 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 | 1471 | if (written == 0) return total_written; |
| 1493 | 1472 | total_written += written; |
| 1494 | 1473 | } |
| ... | ... | @@ -1538,7 +1517,7 @@ fn handleSigWinch(sig: posix.SIG, info: *const posix.siginfo_t, ctx_ptr: ?*anyop |
| 1538 | 1517 | _ = info; |
| 1539 | 1518 | _ = ctx_ptr; |
| 1540 | 1519 | assert(sig == .WINCH); |
| 1541 | global_progress.redraw_event.set(); | |
| 1520 | global_progress.redraw_event.set(global_progress.io); | |
| 1542 | 1521 | } |
| 1543 | 1522 | |
| 1544 | 1523 | const have_sigwinch = switch (builtin.os.tag) { |
| ... | ... | @@ -1563,11 +1542,6 @@ const have_sigwinch = switch (builtin.os.tag) { |
| 1563 | 1542 | else => false, |
| 1564 | 1543 | }; |
| 1565 | 1544 | |
| 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. | |
| 1569 | var stderr_mutex = std.Thread.Mutex.Recursive.init; | |
| 1570 | ||
| 1571 | 1545 | fn copyAtomicStore(dest: []align(@alignOf(usize)) u8, src: []const u8) void { |
| 1572 | 1546 | assert(dest.len == src.len); |
| 1573 | 1547 | const chunked_len = dest.len / @sizeOf(usize); |
lib/std/Random/benchmark.zig+4-2| ... | ... | @@ -1,7 +1,9 @@ |
| 1 | 1 | // zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig |
| 2 | 2 | |
| 3 | const std = @import("std"); | |
| 4 | 3 | const builtin = @import("builtin"); |
| 4 | ||
| 5 | const std = @import("std"); | |
| 6 | const Io = std.Io; | |
| 5 | 7 | const time = std.time; |
| 6 | 8 | const Timer = time.Timer; |
| 7 | 9 | const Random = std.Random; |
| ... | ... | @@ -123,7 +125,7 @@ fn mode(comptime x: comptime_int) comptime_int { |
| 123 | 125 | |
| 124 | 126 | pub fn main() !void { |
| 125 | 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 | 129 | const stdout = &stdout_writer.interface; |
| 128 | 130 | |
| 129 | 131 | var buffer: [1024]u8 = undefined; |
lib/std/Thread.zig+19-17| ... | ... | @@ -7,6 +7,7 @@ const target = builtin.target; |
| 7 | 7 | const native_os = builtin.os.tag; |
| 8 | 8 | |
| 9 | 9 | const std = @import("std.zig"); |
| 10 | const Io = std.Io; | |
| 10 | 11 | const math = std.math; |
| 11 | 12 | const assert = std.debug.assert; |
| 12 | 13 | const posix = std.posix; |
| ... | ... | @@ -174,9 +175,9 @@ pub const SetNameError = error{ |
| 174 | 175 | Unsupported, |
| 175 | 176 | Unexpected, |
| 176 | 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; | |
| 178 | 179 | |
| 179 | pub fn setName(self: Thread, name: []const u8) SetNameError!void { | |
| 180 | pub fn setName(self: Thread, io: Io, name: []const u8) SetNameError!void { | |
| 180 | 181 | if (name.len > max_name_len) return error.NameTooLong; |
| 181 | 182 | |
| 182 | 183 | const name_with_terminator = blk: { |
| ... | ... | @@ -207,10 +208,10 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { |
| 207 | 208 | var buf: [32]u8 = undefined; |
| 208 | 209 | const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); |
| 209 | 210 | |
| 210 | const file = try std.fs.cwd().openFile(path, .{ .mode = .write_only }); | |
| 211 | defer file.close(); | |
| 211 | const file = try Io.Dir.cwd().openFile(io, path, .{ .mode = .write_only }); | |
| 212 | defer file.close(io); | |
| 212 | 213 | |
| 213 | try file.writeAll(name); | |
| 214 | try file.writeStreamingAll(io, name); | |
| 214 | 215 | return; |
| 215 | 216 | }, |
| 216 | 217 | .windows => { |
| ... | ... | @@ -292,7 +293,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { |
| 292 | 293 | pub const GetNameError = error{ |
| 293 | 294 | Unsupported, |
| 294 | 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; | |
| 296 | 297 | |
| 297 | 298 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| 298 | 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 | 322 | var buf: [32]u8 = undefined; |
| 322 | 323 | const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); |
| 323 | 324 | |
| 324 | var threaded: std.Io.Threaded = .init_single_threaded; | |
| 325 | const io = threaded.ioBasic(); | |
| 325 | const io = std.Options.debug_io; | |
| 326 | 326 | |
| 327 | const file = try std.fs.cwd().openFile(path, .{}); | |
| 328 | defer file.close(); | |
| 327 | const file = try Io.Dir.cwd().openFile(io, path, .{}); | |
| 328 | defer file.close(io); | |
| 329 | 329 | |
| 330 | 330 | var file_reader = file.readerStreaming(io, &.{}); |
| 331 | 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 | 1675 | } |
| 1676 | 1676 | }; |
| 1677 | 1677 | |
| 1678 | fn testThreadName(thread: *Thread) !void { | |
| 1678 | fn testThreadName(io: Io, thread: *Thread) !void { | |
| 1679 | 1679 | const testCases = &[_][]const u8{ |
| 1680 | 1680 | "mythread", |
| 1681 | 1681 | "b" ** max_name_len, |
| 1682 | 1682 | }; |
| 1683 | 1683 | |
| 1684 | 1684 | inline for (testCases) |tc| { |
| 1685 | try thread.setName(tc); | |
| 1685 | try thread.setName(io, tc); | |
| 1686 | 1686 | |
| 1687 | 1687 | var name_buffer: [max_name_len:0]u8 = undefined; |
| 1688 | 1688 | |
| ... | ... | @@ -1697,6 +1697,8 @@ fn testThreadName(thread: *Thread) !void { |
| 1697 | 1697 | test "setName, getName" { |
| 1698 | 1698 | if (builtin.single_threaded) return error.SkipZigTest; |
| 1699 | 1699 | |
| 1700 | const io = testing.io; | |
| 1701 | ||
| 1700 | 1702 | const Context = struct { |
| 1701 | 1703 | start_wait_event: ResetEvent = .unset, |
| 1702 | 1704 | test_done_event: ResetEvent = .unset, |
| ... | ... | @@ -1710,11 +1712,11 @@ test "setName, getName" { |
| 1710 | 1712 | ctx.start_wait_event.wait(); |
| 1711 | 1713 | |
| 1712 | 1714 | switch (native_os) { |
| 1713 | .windows => testThreadName(&ctx.thread) catch |err| switch (err) { | |
| 1715 | .windows => testThreadName(io, &ctx.thread) catch |err| switch (err) { | |
| 1714 | 1716 | error.Unsupported => return error.SkipZigTest, |
| 1715 | 1717 | else => return err, |
| 1716 | 1718 | }, |
| 1717 | else => try testThreadName(&ctx.thread), | |
| 1719 | else => try testThreadName(io, &ctx.thread), | |
| 1718 | 1720 | } |
| 1719 | 1721 | |
| 1720 | 1722 | // Signal our test is done |
| ... | ... | @@ -1734,14 +1736,14 @@ test "setName, getName" { |
| 1734 | 1736 | |
| 1735 | 1737 | switch (native_os) { |
| 1736 | 1738 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { |
| 1737 | const res = thread.setName("foobar"); | |
| 1739 | const res = thread.setName(io, "foobar"); | |
| 1738 | 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 | 1743 | error.Unsupported => return error.SkipZigTest, |
| 1742 | 1744 | else => return err, |
| 1743 | 1745 | }, |
| 1744 | else => try testThreadName(&thread), | |
| 1746 | else => try testThreadName(io, &thread), | |
| 1745 | 1747 | } |
| 1746 | 1748 | |
| 1747 | 1749 | context.thread_done_event.set(); |
lib/std/c.zig+7-5| ... | ... | @@ -148,9 +148,10 @@ pub const dev_t = switch (native_os) { |
| 148 | 148 | pub const mode_t = switch (native_os) { |
| 149 | 149 | .linux => linux.mode_t, |
| 150 | 150 | .emscripten => emscripten.mode_t, |
| 151 | .openbsd, .haiku, .netbsd, .illumos, .wasi, .windows => u32, | |
| 151 | .openbsd, .haiku, .netbsd, .illumos, .windows => u32, | |
| 152 | 152 | // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L44 |
| 153 | 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 | 155 | else => u0, |
| 155 | 156 | }; |
| 156 | 157 | |
| ... | ... | @@ -160,9 +161,10 @@ pub const nlink_t = switch (native_os) { |
| 160 | 161 | .wasi => c_ulonglong, |
| 161 | 162 | // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L45 |
| 162 | 163 | .freebsd, .serenity => u64, |
| 163 | .openbsd, .netbsd, .illumos => u32, | |
| 164 | .openbsd, .netbsd, .dragonfly, .illumos => u32, | |
| 164 | 165 | .haiku => i32, |
| 165 | else => void, | |
| 166 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => u16, | |
| 167 | else => u0, | |
| 166 | 168 | }; |
| 167 | 169 | |
| 168 | 170 | pub 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 | 10610 | pub extern "c" fn mremap(addr: ?*align(page_size) const anyopaque, old_len: usize, new_len: usize, flags: MREMAP, ...) *anyopaque; |
| 10609 | 10611 | pub extern "c" fn mprotect(addr: *align(page_size) anyopaque, len: usize, prot: c_uint) c_int; |
| 10610 | 10612 | pub extern "c" fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) c_int; |
| 10611 | pub extern "c" fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: c_int) c_int; | |
| 10613 | pub extern "c" fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: c_uint) c_int; | |
| 10612 | 10614 | pub extern "c" fn unlink(path: [*:0]const u8) c_int; |
| 10613 | 10615 | pub extern "c" fn unlinkat(dirfd: fd_t, path: [*:0]const u8, flags: c_uint) c_int; |
| 10614 | 10616 | pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8; |
| ... | ... | @@ -10740,7 +10742,7 @@ pub extern "c" fn free(?*anyopaque) void; |
| 10740 | 10742 | pub extern "c" fn futimes(fd: fd_t, times: ?*[2]timeval) c_int; |
| 10741 | 10743 | pub extern "c" fn utimes(path: [*:0]const u8, times: ?*[2]timeval) c_int; |
| 10742 | 10744 | |
| 10743 | pub extern "c" fn utimensat(dirfd: fd_t, pathname: [*:0]const u8, times: ?*[2]timespec, flags: u32) c_int; | |
| 10745 | pub extern "c" fn utimensat(dirfd: fd_t, pathname: [*:0]const u8, times: ?*const [2]timespec, flags: u32) c_int; | |
| 10744 | 10746 | pub extern "c" fn futimens(fd: fd_t, times: ?*const [2]timespec) c_int; |
| 10745 | 10747 | |
| 10746 | 10748 | pub extern "c" fn pthread_create( |
lib/std/c/darwin.zig+1-1| ... | ... | @@ -349,7 +349,7 @@ pub const VM = struct { |
| 349 | 349 | pub const exception_type_t = c_int; |
| 350 | 350 | |
| 351 | 351 | pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32; |
| 352 | pub extern "c" fn _NSGetExecutablePath(buf: [*:0]u8, bufsize: *u32) c_int; | |
| 352 | pub extern "c" fn _NSGetExecutablePath(buf: [*]u8, bufsize: *u32) c_int; | |
| 353 | 353 | pub extern "c" fn _dyld_image_count() u32; |
| 354 | 354 | pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header; |
| 355 | 355 | pub 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 | 250 | /// Reserved for future cap_rights |
| 251 | 251 | _cap_spare: u64, |
| 252 | 252 | /// Path to file, if any. |
| 253 | path: [PATH_MAX - 1:0]u8, | |
| 253 | path: [PATH_MAX]u8, | |
| 254 | 254 | |
| 255 | 255 | comptime { |
| 256 | 256 | assert(@sizeOf(@This()) == KINFO_FILE_SIZE); |
lib/std/crypto/Certificate/Bundle.zig+16-16| ... | ... | @@ -9,8 +9,8 @@ const builtin = @import("builtin"); |
| 9 | 9 | |
| 10 | 10 | const std = @import("../../std.zig"); |
| 11 | 11 | const Io = std.Io; |
| 12 | const Dir = std.Io.Dir; | |
| 12 | 13 | const assert = std.debug.assert; |
| 13 | const fs = std.fs; | |
| 14 | 14 | const mem = std.mem; |
| 15 | 15 | const crypto = std.crypto; |
| 16 | 16 | const Allocator = std.mem.Allocator; |
| ... | ... | @@ -171,17 +171,17 @@ fn rescanWindows(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanW |
| 171 | 171 | cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len); |
| 172 | 172 | } |
| 173 | 173 | |
| 174 | pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError; | |
| 174 | pub const AddCertsFromDirPathError = Io.File.OpenError || AddCertsFromDirError; | |
| 175 | 175 | |
| 176 | 176 | pub fn addCertsFromDirPath( |
| 177 | 177 | cb: *Bundle, |
| 178 | 178 | gpa: Allocator, |
| 179 | 179 | io: Io, |
| 180 | dir: fs.Dir, | |
| 180 | dir: Io.Dir, | |
| 181 | 181 | sub_dir_path: []const u8, |
| 182 | 182 | ) AddCertsFromDirPathError!void { |
| 183 | var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true }); | |
| 184 | defer iterable_dir.close(); | |
| 183 | var iterable_dir = try dir.openDir(io, sub_dir_path, .{ .iterate = true }); | |
| 184 | defer iterable_dir.close(io); | |
| 185 | 185 | return addCertsFromDir(cb, gpa, io, iterable_dir); |
| 186 | 186 | } |
| 187 | 187 | |
| ... | ... | @@ -192,27 +192,27 @@ pub fn addCertsFromDirPathAbsolute( |
| 192 | 192 | now: Io.Timestamp, |
| 193 | 193 | abs_dir_path: []const u8, |
| 194 | 194 | ) AddCertsFromDirPathError!void { |
| 195 | assert(fs.path.isAbsolute(abs_dir_path)); | |
| 196 | var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true }); | |
| 197 | defer iterable_dir.close(); | |
| 195 | assert(Dir.path.isAbsolute(abs_dir_path)); | |
| 196 | var iterable_dir = try Dir.openDirAbsolute(io, abs_dir_path, .{ .iterate = true }); | |
| 197 | defer iterable_dir.close(io); | |
| 198 | 198 | return addCertsFromDir(cb, gpa, io, now, iterable_dir); |
| 199 | 199 | } |
| 200 | 200 | |
| 201 | 201 | pub const AddCertsFromDirError = AddCertsFromFilePathError; |
| 202 | 202 | |
| 203 | pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, iterable_dir: fs.Dir) AddCertsFromDirError!void { | |
| 203 | pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, iterable_dir: Io.Dir) AddCertsFromDirError!void { | |
| 204 | 204 | var it = iterable_dir.iterate(); |
| 205 | while (try it.next()) |entry| { | |
| 205 | while (try it.next(io)) |entry| { | |
| 206 | 206 | switch (entry.kind) { |
| 207 | 207 | .file, .sym_link => {}, |
| 208 | 208 | else => continue, |
| 209 | 209 | } |
| 210 | 210 | |
| 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 | } |
| 214 | 214 | |
| 215 | pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError || Io.Clock.Error; | |
| 215 | pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError || Io.Clock.Error; | |
| 216 | 216 | |
| 217 | 217 | pub fn addCertsFromFilePathAbsolute( |
| 218 | 218 | cb: *Bundle, |
| ... | ... | @@ -221,8 +221,8 @@ pub fn addCertsFromFilePathAbsolute( |
| 221 | 221 | now: Io.Timestamp, |
| 222 | 222 | abs_file_path: []const u8, |
| 223 | 223 | ) AddCertsFromFilePathError!void { |
| 224 | var file = try fs.openFileAbsolute(abs_file_path, .{}); | |
| 225 | defer file.close(); | |
| 224 | var file = try Io.Dir.openFileAbsolute(io, abs_file_path, .{}); | |
| 225 | defer file.close(io); | |
| 226 | 226 | var file_reader = file.reader(io, &.{}); |
| 227 | 227 | return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds()); |
| 228 | 228 | } |
| ... | ... | @@ -242,8 +242,8 @@ pub fn addCertsFromFilePath( |
| 242 | 242 | } |
| 243 | 243 | |
| 244 | 244 | pub const AddCertsFromFileError = Allocator.Error || |
| 245 | fs.File.GetSeekPosError || | |
| 246 | fs.File.ReadError || | |
| 245 | Io.File.Reader.Error || | |
| 246 | Io.File.Reader.SizeError || | |
| 247 | 247 | ParseCertError || |
| 248 | 248 | std.base64.Error || |
| 249 | 249 | error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker, Streaming }; |
lib/std/crypto/Certificate/Bundle/macos.zig+2-3| ... | ... | @@ -6,7 +6,7 @@ const mem = std.mem; |
| 6 | 6 | const Allocator = std.mem.Allocator; |
| 7 | 7 | const Bundle = @import("../Bundle.zig"); |
| 8 | 8 | |
| 9 | pub const RescanMacError = Allocator.Error || fs.File.OpenError || fs.File.ReadError || fs.File.SeekError || Bundle.ParseCertError || error{EndOfStream}; | |
| 9 | pub const RescanMacError = Allocator.Error || Io.File.OpenError || Io.File.Reader.Error || Io.File.SeekError || Bundle.ParseCertError || error{EndOfStream}; | |
| 10 | 10 | |
| 11 | 11 | pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanMacError!void { |
| 12 | 12 | cb.bytes.clearRetainingCapacity(); |
| ... | ... | @@ -17,9 +17,8 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanM |
| 17 | 17 | "/Library/Keychains/System.keychain", |
| 18 | 18 | }; |
| 19 | 19 | |
| 20 | _ = io; // TODO migrate file system to use std.Io | |
| 21 | 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 | 22 | error.StreamTooLong => return error.FileTooBig, |
| 24 | 23 | else => |e| return e, |
| 25 | 24 | }; |
lib/std/crypto/benchmark.zig+6-4| ... | ... | @@ -1,10 +1,12 @@ |
| 1 | 1 | // zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig |
| 2 | 2 | |
| 3 | const std = @import("std"); | |
| 4 | 3 | const builtin = @import("builtin"); |
| 4 | ||
| 5 | const std = @import("std"); | |
| 6 | const Io = std.Io; | |
| 5 | 7 | const mem = std.mem; |
| 6 | 8 | const time = std.time; |
| 7 | const Timer = time.Timer; | |
| 9 | const Timer = std.time.Timer; | |
| 8 | 10 | const crypto = std.crypto; |
| 9 | 11 | |
| 10 | 12 | const KiB = 1024; |
| ... | ... | @@ -504,7 +506,7 @@ fn mode(comptime x: comptime_int) comptime_int { |
| 504 | 506 | pub fn main() !void { |
| 505 | 507 | // Size of buffer is about size of printed message. |
| 506 | 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 | 510 | const stdout = &stdout_writer.interface; |
| 509 | 511 | |
| 510 | 512 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| ... | ... | @@ -554,7 +556,7 @@ pub fn main() !void { |
| 554 | 556 | } |
| 555 | 557 | } |
| 556 | 558 | |
| 557 | var io_threaded = std.Io.Threaded.init(arena_allocator); | |
| 559 | var io_threaded = std.Io.Threaded.init(arena_allocator, .{}); | |
| 558 | 560 | defer io_threaded.deinit(); |
| 559 | 561 | const io = io_threaded.io(); |
| 560 | 562 |
lib/std/crypto/codecs/asn1/test.zig+3-3| ... | ... | @@ -73,8 +73,8 @@ test AllTypes { |
| 73 | 73 | try std.testing.expectEqualSlices(u8, encoded, buf); |
| 74 | 74 | |
| 75 | 75 | // Use this to update test file. |
| 76 | // const dir = try std.fs.cwd().openDir("lib/std/crypto/asn1", .{}); | |
| 77 | // var file = try dir.createFile(path, .{}); | |
| 78 | // defer file.close(); | |
| 76 | // const dir = try Io.Dir.cwd().openDir(io, "lib/std/crypto/asn1", .{}); | |
| 77 | // var file = try dir.createFile(io, path, .{}); | |
| 78 | // defer file.close(io); | |
| 79 | 79 | // try file.writeAll(buf); |
| 80 | 80 | } |
lib/std/crypto/tls.zig-1| ... | ... | @@ -32,7 +32,6 @@ |
| 32 | 32 | |
| 33 | 33 | const std = @import("../std.zig"); |
| 34 | 34 | const Tls = @This(); |
| 35 | const net = std.net; | |
| 36 | 35 | const mem = std.mem; |
| 37 | 36 | const crypto = std.crypto; |
| 38 | 37 | const assert = std.debug.assert; |
lib/std/debug.zig+250-278| ... | ... | @@ -1,14 +1,13 @@ |
| 1 | 1 | const std = @import("std.zig"); |
| 2 | 2 | const Io = std.Io; |
| 3 | 3 | const Writer = std.Io.Writer; |
| 4 | const tty = std.Io.tty; | |
| 5 | 4 | const math = std.math; |
| 6 | 5 | const mem = std.mem; |
| 7 | 6 | const posix = std.posix; |
| 8 | 7 | const fs = std.fs; |
| 9 | 8 | const testing = std.testing; |
| 10 | 9 | const Allocator = mem.Allocator; |
| 11 | const File = std.fs.File; | |
| 10 | const File = std.Io.File; | |
| 12 | 11 | const windows = std.os.windows; |
| 13 | 12 | |
| 14 | 13 | const builtin = @import("builtin"); |
| ... | ... | @@ -60,7 +59,7 @@ pub const cpu_context = @import("debug/cpu_context.zig"); |
| 60 | 59 | /// }; |
| 61 | 60 | /// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's |
| 62 | 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 | /// ``` |
| 65 | 64 | pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo")) |
| 66 | 65 | root.debug.SelfInfo |
| ... | ... | @@ -262,53 +261,54 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { |
| 262 | 261 | else => true, |
| 263 | 262 | }; |
| 264 | 263 | |
| 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 | 266 | /// During the lock, any `std.Progress` information is cleared from the terminal. |
| 268 | pub fn lockStdErr() void { | |
| 269 | std.Progress.lockStdErr(); | |
| 270 | } | |
| 271 | ||
| 272 | pub 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` multiple | |
| 281 | /// times. The primary motivation is that this allows the panic handler to safely dump the stack | |
| 282 | /// trace and panic message even if the mutex was held at the panic site. | |
| 273 | /// The returned `Writer` does not need to be manually flushed: flushing is | |
| 274 | /// performed automatically when the matching `unlockStderr` call occurs. | |
| 283 | 275 | /// |
| 284 | /// The returned `Writer` does not need to be manually flushed: flushing is performed automatically | |
| 285 | /// when the matching `unlockStderrWriter` call occurs. | |
| 286 | pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } { | |
| 287 | const global = struct { | |
| 288 | var conf: ?tty.Config = null; | |
| 276 | /// This is a low-level debugging primitive that bypasses the `Io` interface, | |
| 277 | /// writing directly to stderr using the most basic syscalls available. This | |
| 278 | /// function does not switch threads, switch stacks, or suspend. | |
| 279 | /// | |
| 280 | /// Alternatively, use the higher-level `Io.lockStderr` to integrate with the | |
| 281 | /// application's chosen `Io` implementation. | |
| 282 | pub 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 | } |
| 297 | 290 | |
| 298 | pub fn unlockStderrWriter() void { | |
| 299 | std.Progress.unlockStderrWriter(); | |
| 291 | pub fn unlockStderr() void { | |
| 292 | const io = std.Options.debug_io; | |
| 293 | io.unlockStderr(); | |
| 300 | 294 | } |
| 301 | 295 | |
| 302 | /// Print to stderr, silently returning on failure. Intended for use in "printf | |
| 303 | /// debugging". Use `std.log` functions for proper logging. | |
| 296 | /// Writes to stderr, ignoring errors. | |
| 297 | /// | |
| 298 | /// This is a low-level debugging primitive that bypasses the `Io` interface, | |
| 299 | /// writing directly to stderr using the most basic syscalls available. This | |
| 300 | /// function does not switch threads, switch stacks, or suspend. | |
| 304 | 301 | /// |
| 305 | 302 | /// Uses a 64-byte buffer for formatted printing which is flushed before this |
| 306 | 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. | |
| 307 | 307 | pub fn print(comptime fmt: []const u8, args: anytype) void { |
| 308 | 308 | var buffer: [64]u8 = undefined; |
| 309 | const bw, _ = lockStderrWriter(&buffer); | |
| 310 | defer unlockStderrWriter(); | |
| 311 | nosuspend bw.print(fmt, args) catch return; | |
| 309 | const stderr = lockStderr(&buffer); | |
| 310 | defer unlockStderr(); | |
| 311 | stderr.file_writer.interface.print(fmt, args) catch return; | |
| 312 | 312 | } |
| 313 | 313 | |
| 314 | 314 | /// Marked `inline` to propagate a comptime-known error to callers. |
| ... | ... | @@ -323,43 +323,44 @@ pub inline fn getSelfDebugInfo() !*SelfInfo { |
| 323 | 323 | /// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned. |
| 324 | 324 | /// Obtains the stderr mutex while dumping. |
| 325 | 325 | pub fn dumpHex(bytes: []const u8) void { |
| 326 | const bw, const ttyconf = lockStderrWriter(&.{}); | |
| 327 | defer unlockStderrWriter(); | |
| 328 | dumpHexFallible(bw, ttyconf, bytes) catch {}; | |
| 326 | const stderr = lockStderr(&.{}).terminal(); | |
| 327 | defer unlockStderr(); | |
| 328 | dumpHexFallible(stderr, bytes) catch {}; | |
| 329 | 329 | } |
| 330 | 330 | |
| 331 | 331 | /// Prints a hexadecimal view of the bytes, returning any error that occurs. |
| 332 | pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !void { | |
| 332 | pub fn dumpHexFallible(t: Io.Terminal, bytes: []const u8) !void { | |
| 333 | const w = t.writer; | |
| 333 | 334 | var chunks = mem.window(u8, bytes, 16, 16); |
| 334 | 335 | while (chunks.next()) |window| { |
| 335 | 336 | // 1. Print the address. |
| 336 | 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 | 339 | // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more. |
| 339 | 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 tty_config.setColor(bw, .reset); | |
| 341 | try w.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); | |
| 342 | try t.setColor(.reset); | |
| 342 | 343 | |
| 343 | 344 | // 2. Print the bytes. |
| 344 | 345 | for (window, 0..) |byte, index| { |
| 345 | try bw.print("{X:0>2} ", .{byte}); | |
| 346 | if (index == 7) try bw.writeByte(' '); | |
| 346 | try w.print("{X:0>2} ", .{byte}); | |
| 347 | if (index == 7) try w.writeByte(' '); | |
| 347 | 348 | } |
| 348 | try bw.writeByte(' '); | |
| 349 | try w.writeByte(' '); | |
| 349 | 350 | if (window.len < 16) { |
| 350 | 351 | var missing_columns = (16 - window.len) * 3; |
| 351 | 352 | if (window.len < 8) missing_columns += 1; |
| 352 | try bw.splatByteAll(' ', missing_columns); | |
| 353 | try w.splatByteAll(' ', missing_columns); | |
| 353 | 354 | } |
| 354 | 355 | |
| 355 | 356 | // 3. Print the characters. |
| 356 | 357 | for (window) |byte| { |
| 357 | 358 | if (std.ascii.isPrint(byte)) { |
| 358 | try bw.writeByte(byte); | |
| 359 | try w.writeByte(byte); | |
| 359 | 360 | } else { |
| 360 | 361 | // Related: https://github.com/ziglang/zig/issues/7600 |
| 361 | if (tty_config == .windows_api) { | |
| 362 | try bw.writeByte('.'); | |
| 362 | if (t.mode == .windows_api) { | |
| 363 | try w.writeByte('.'); | |
| 363 | 364 | continue; |
| 364 | 365 | } |
| 365 | 366 | |
| ... | ... | @@ -367,24 +368,25 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) ! |
| 367 | 368 | // We don't want to do this for all control codes because most control codes apart from |
| 368 | 369 | // the ones that Zig has escape sequences for are likely not very useful to print as symbols. |
| 369 | 370 | switch (byte) { |
| 370 | '\n' => try bw.writeAll("␊"), | |
| 371 | '\r' => try bw.writeAll("␍"), | |
| 372 | '\t' => try bw.writeAll("␉"), | |
| 373 | else => try bw.writeByte('.'), | |
| 371 | '\n' => try w.writeAll("␊"), | |
| 372 | '\r' => try w.writeAll("␍"), | |
| 373 | '\t' => try w.writeAll("␉"), | |
| 374 | else => try w.writeByte('.'), | |
| 374 | 375 | } |
| 375 | 376 | } |
| 376 | 377 | } |
| 377 | try bw.writeByte('\n'); | |
| 378 | try w.writeByte('\n'); | |
| 378 | 379 | } |
| 379 | 380 | } |
| 380 | 381 | |
| 381 | 382 | test dumpHexFallible { |
| 383 | const gpa = testing.allocator; | |
| 382 | 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 | 386 | defer aw.deinit(); |
| 385 | 387 | |
| 386 | try dumpHexFallible(&aw.writer, .no_color, bytes); | |
| 387 | const expected = try std.fmt.allocPrint(std.testing.allocator, | |
| 388 | try dumpHexFallible(.{ .writer = &aw.writer, .mode = .no_color }, bytes); | |
| 389 | const expected = try std.fmt.allocPrint(gpa, | |
| 388 | 390 | \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ |
| 389 | 391 | \\{x:0>[2]} 01 12 13 ... |
| 390 | 392 | \\ |
| ... | ... | @@ -393,8 +395,8 @@ test dumpHexFallible { |
| 393 | 395 | @intFromPtr(bytes.ptr) + 16, |
| 394 | 396 | @sizeOf(usize) * 2, |
| 395 | 397 | }); |
| 396 | defer std.testing.allocator.free(expected); | |
| 397 | try std.testing.expectEqualStrings(expected, aw.written()); | |
| 398 | defer gpa.free(expected); | |
| 399 | try testing.expectEqualStrings(expected, aw.written()); | |
| 398 | 400 | } |
| 399 | 401 | |
| 400 | 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 | 411 | /// away, and in fact the optimizer is able to use the assertion in its |
| 410 | 412 | /// heuristics. |
| 411 | 413 | /// |
| 412 | /// Inside a test block, it is best to use the `std.testing` module rather than | |
| 414 | /// Inside a test block, it is best to use the `testing` module rather than | |
| 413 | 415 | /// this function, because this function may not detect a test failure in |
| 414 | 416 | /// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert |
| 415 | 417 | /// function is the correct function to use. |
| ... | ... | @@ -483,10 +485,7 @@ const use_trap_panic = switch (builtin.zig_backend) { |
| 483 | 485 | }; |
| 484 | 486 | |
| 485 | 487 | /// Dumps a stack trace to standard error, then aborts. |
| 486 | pub fn defaultPanic( | |
| 487 | msg: []const u8, | |
| 488 | first_trace_addr: ?usize, | |
| 489 | ) noreturn { | |
| 488 | pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn { | |
| 490 | 489 | @branchHint(.cold); |
| 491 | 490 | |
| 492 | 491 | if (use_trap_panic) @trap(); |
| ... | ... | @@ -522,7 +521,7 @@ pub fn defaultPanic( |
| 522 | 521 | } |
| 523 | 522 | @trap(); |
| 524 | 523 | }, |
| 525 | .cuda, .amdhsa => std.posix.abort(), | |
| 524 | .cuda, .amdhsa => std.process.abort(), | |
| 526 | 525 | .plan9 => { |
| 527 | 526 | var status: [std.os.plan9.ERRMAX]u8 = undefined; |
| 528 | 527 | const len = @min(msg.len, status.len - 1); |
| ... | ... | @@ -546,26 +545,27 @@ pub fn defaultPanic( |
| 546 | 545 | _ = panicking.fetchAdd(1, .seq_cst); |
| 547 | 546 | |
| 548 | 547 | trace: { |
| 549 | const stderr, const tty_config = lockStderrWriter(&.{}); | |
| 550 | defer unlockStderrWriter(); | |
| 548 | const stderr = lockStderr(&.{}).terminal(); | |
| 549 | defer unlockStderr(); | |
| 550 | const writer = stderr.writer; | |
| 551 | 551 | |
| 552 | 552 | if (builtin.single_threaded) { |
| 553 | stderr.print("panic: ", .{}) catch break :trace; | |
| 553 | writer.print("panic: ", .{}) catch break :trace; | |
| 554 | 554 | } else { |
| 555 | 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; | |
| 559 | 559 | |
| 560 | 560 | if (@errorReturnTrace()) |t| if (t.index > 0) { |
| 561 | stderr.writeAll("error return context:\n") catch break :trace; | |
| 562 | writeStackTrace(t, stderr, tty_config) catch break :trace; | |
| 563 | stderr.writeAll("\nstack trace:\n") catch break :trace; | |
| 561 | writer.writeAll("error return context:\n") catch break :trace; | |
| 562 | writeStackTrace(t, stderr) catch break :trace; | |
| 563 | writer.writeAll("\nstack trace:\n") catch break :trace; | |
| 564 | 564 | }; |
| 565 | 565 | writeCurrentStackTrace(.{ |
| 566 | 566 | .first_address = first_trace_addr orelse @returnAddress(), |
| 567 | 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 | } |
| 570 | 570 | |
| 571 | 571 | waitForOtherThreadToFinishPanicking(); |
| ... | ... | @@ -575,12 +575,13 @@ pub fn defaultPanic( |
| 575 | 575 | // A panic happened while trying to print a previous panic message. |
| 576 | 576 | // We're still holding the mutex but that's fine as we're going to |
| 577 | 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 | 581 | else => {}, // Panicked while printing the recursive panic message. |
| 581 | 582 | } |
| 582 | 583 | |
| 583 | posix.abort(); | |
| 584 | std.process.abort(); | |
| 584 | 585 | } |
| 585 | 586 | |
| 586 | 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 | 622 | var it: StackIterator = .init(options.context); |
| 622 | 623 | defer it.deinit(); |
| 623 | 624 | if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace; |
| 625 | ||
| 626 | const io = std.Options.debug_io; | |
| 627 | ||
| 624 | 628 | var total_frames: usize = 0; |
| 625 | 629 | var index: usize = 0; |
| 626 | 630 | var wait_for = options.first_address; |
| 627 | 631 | // Ideally, we would iterate the whole stack so that the `index` in the returned trace was |
| 628 | 632 | // indicative of how many frames were skipped. However, this has a significant runtime cost |
| 629 | 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 | 635 | .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break, |
| 632 | 636 | .end => break, |
| 633 | 637 | .frame => |ret_addr| { |
| ... | ... | @@ -653,37 +657,36 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: |
| 653 | 657 | /// Write the current stack trace to `writer`, annotated with source locations. |
| 654 | 658 | /// |
| 655 | 659 | /// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing. |
| 656 | pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void { | |
| 657 | var threaded: Io.Threaded = .init_single_threaded; | |
| 658 | const io = threaded.ioBasic(); | |
| 659 | ||
| 660 | pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void { | |
| 661 | const writer = t.writer; | |
| 660 | 662 | if (!std.options.allow_stack_tracing) { |
| 661 | tty_config.setColor(writer, .dim) catch {}; | |
| 663 | t.setColor(.dim) catch {}; | |
| 662 | 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 | 666 | return; |
| 665 | 667 | } |
| 666 | 668 | const di_gpa = getDebugInfoAllocator(); |
| 667 | 669 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 668 | 670 | error.UnsupportedTarget => { |
| 669 | tty_config.setColor(writer, .dim) catch {}; | |
| 671 | t.setColor(.dim) catch {}; | |
| 670 | 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 | 674 | return; |
| 673 | 675 | }, |
| 674 | 676 | }; |
| 675 | 677 | var it: StackIterator = .init(options.context); |
| 676 | 678 | defer it.deinit(); |
| 677 | 679 | if (!it.stratOk(options.allow_unsafe_unwind)) { |
| 678 | tty_config.setColor(writer, .dim) catch {}; | |
| 680 | t.setColor(.dim) catch {}; | |
| 679 | 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 | 683 | return; |
| 682 | 684 | } |
| 683 | 685 | var total_frames: usize = 0; |
| 684 | 686 | var wait_for = options.first_address; |
| 685 | 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 | 690 | .switch_to_fp => |unwind_error| { |
| 688 | 691 | switch (StackIterator.fp_usability) { |
| 689 | 692 | .useless, .unsafe => {}, |
| ... | ... | @@ -700,31 +703,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri |
| 700 | 703 | error.Unexpected => "unexpected error", |
| 701 | 704 | }; |
| 702 | 705 | if (it.stratOk(options.allow_unsafe_unwind)) { |
| 703 | tty_config.setColor(writer, .dim) catch {}; | |
| 706 | t.setColor(.dim) catch {}; | |
| 704 | 707 | try writer.print( |
| 705 | 708 | "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n", |
| 706 | 709 | .{ module_name, unwind_error.address, caption }, |
| 707 | 710 | ); |
| 708 | tty_config.setColor(writer, .reset) catch {}; | |
| 711 | t.setColor(.reset) catch {}; | |
| 709 | 712 | } else { |
| 710 | tty_config.setColor(writer, .dim) catch {}; | |
| 713 | t.setColor(.dim) catch {}; | |
| 711 | 714 | try writer.print( |
| 712 | 715 | "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n", |
| 713 | 716 | .{ module_name, unwind_error.address, caption }, |
| 714 | 717 | ); |
| 715 | tty_config.setColor(writer, .reset) catch {}; | |
| 718 | t.setColor(.reset) catch {}; | |
| 716 | 719 | return; |
| 717 | 720 | } |
| 718 | 721 | }, |
| 719 | 722 | .end => break, |
| 720 | 723 | .frame => |ret_addr| { |
| 721 | 724 | if (total_frames > 10_000) { |
| 722 | tty_config.setColor(writer, .dim) catch {}; | |
| 725 | t.setColor(.dim) catch {}; | |
| 723 | 726 | try writer.print( |
| 724 | 727 | "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n", |
| 725 | 728 | .{total_frames}, |
| 726 | 729 | ); |
| 727 | tty_config.setColor(writer, .reset) catch {}; | |
| 730 | t.setColor(.reset) catch {}; | |
| 728 | 731 | return; |
| 729 | 732 | } |
| 730 | 733 | total_frames += 1; |
| ... | ... | @@ -734,7 +737,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri |
| 734 | 737 | } |
| 735 | 738 | // `ret_addr` is the return address, which is *after* the function call. |
| 736 | 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 | 741 | printed_any_frame = true; |
| 739 | 742 | }, |
| 740 | 743 | }; |
| ... | ... | @@ -742,8 +745,8 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri |
| 742 | 745 | } |
| 743 | 746 | /// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors. |
| 744 | 747 | pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { |
| 745 | const stderr, const tty_config = lockStderrWriter(&.{}); | |
| 746 | defer unlockStderrWriter(); | |
| 748 | const stderr = lockStderr(&.{}).terminal(); | |
| 749 | defer unlockStderr(); | |
| 747 | 750 | writeCurrentStackTrace(.{ |
| 748 | 751 | .first_address = a: { |
| 749 | 752 | if (options.first_address) |a| break :a a; |
| ... | ... | @@ -752,33 +755,30 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { |
| 752 | 755 | }, |
| 753 | 756 | .context = options.context, |
| 754 | 757 | .allow_unsafe_unwind = options.allow_unsafe_unwind, |
| 755 | }, stderr, tty_config) catch |err| switch (err) { | |
| 758 | }, stderr) catch |err| switch (err) { | |
| 756 | 759 | error.WriteFailed => {}, |
| 757 | 760 | }; |
| 758 | 761 | } |
| 759 | 762 | |
| 760 | 763 | pub const FormatStackTrace = struct { |
| 761 | 764 | stack_trace: StackTrace, |
| 762 | tty_config: tty.Config, | |
| 765 | terminal_mode: Io.Terminal.Mode = .no_color, | |
| 763 | 766 | |
| 764 | pub fn format(context: @This(), writer: *Io.Writer) Io.Writer.Error!void { | |
| 765 | try writer.writeAll("\n"); | |
| 766 | try writeStackTrace(&context.stack_trace, writer, context.tty_config); | |
| 767 | pub fn format(fst: FormatStackTrace, writer: *Writer) Writer.Error!void { | |
| 768 | try writer.writeByte('\n'); | |
| 769 | try writeStackTrace(&fst.stack_trace, .{ .writer = writer, .mode = fst.terminal_mode }); | |
| 767 | 770 | } |
| 768 | 771 | }; |
| 769 | 772 | |
| 770 | 773 | /// Write a previously captured stack trace to `writer`, annotated with source locations. |
| 771 | pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void { | |
| 774 | pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void { | |
| 775 | const writer = t.writer; | |
| 772 | 776 | if (!std.options.allow_stack_tracing) { |
| 773 | tty_config.setColor(writer, .dim) catch {}; | |
| 777 | t.setColor(.dim) catch {}; | |
| 774 | 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 | 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(); | |
| 782 | 782 | |
| 783 | 783 | // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if |
| 784 | 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 | 787 | const di_gpa = getDebugInfoAllocator(); |
| 788 | 788 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 789 | 789 | error.UnsupportedTarget => { |
| 790 | tty_config.setColor(writer, .dim) catch {}; | |
| 790 | t.setColor(.dim) catch {}; | |
| 791 | 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 | 793 | return; |
| 794 | 794 | }, |
| 795 | 795 | }; |
| 796 | const io = std.Options.debug_io; | |
| 796 | 797 | const captured_frames = @min(n_frames, st.instruction_addresses.len); |
| 797 | 798 | for (st.instruction_addresses[0..captured_frames]) |ret_addr| { |
| 798 | 799 | // `ret_addr` is the return address, which is *after* the function call. |
| 799 | 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 | 803 | if (n_frames > captured_frames) { |
| 803 | tty_config.setColor(writer, .bold) catch {}; | |
| 804 | t.setColor(.bold) catch {}; | |
| 804 | 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 | 809 | /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. |
| 809 | 810 | pub fn dumpStackTrace(st: *const StackTrace) void { |
| 810 | const stderr, const tty_config = lockStderrWriter(&.{}); | |
| 811 | defer unlockStderrWriter(); | |
| 812 | writeStackTrace(st, stderr, tty_config) catch |err| switch (err) { | |
| 811 | const stderr = lockStderr(&.{}).terminal(); | |
| 812 | defer unlockStderr(); | |
| 813 | writeStackTrace(st, stderr) catch |err| switch (err) { | |
| 813 | 814 | error.WriteFailed => {}, |
| 814 | 815 | }; |
| 815 | 816 | } |
| ... | ... | @@ -960,7 +961,7 @@ const StackIterator = union(enum) { |
| 960 | 961 | }, |
| 961 | 962 | }; |
| 962 | 963 | |
| 963 | fn next(it: *StackIterator) Result { | |
| 964 | fn next(it: *StackIterator, io: Io) Result { | |
| 964 | 965 | switch (it.*) { |
| 965 | 966 | .ctx_first => |context_ptr| { |
| 966 | 967 | // After the first frame, start actually unwinding. |
| ... | ... | @@ -976,7 +977,7 @@ const StackIterator = union(enum) { |
| 976 | 977 | .di => |*unwind_context| { |
| 977 | 978 | const di = getSelfDebugInfo() catch unreachable; |
| 978 | 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 | 981 | const pc = unwind_context.pc; |
| 981 | 982 | const fp = unwind_context.getFp(); |
| 982 | 983 | it.* = .{ .fp = fp }; |
| ... | ... | @@ -1104,170 +1105,146 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { |
| 1104 | 1105 | return ptr; |
| 1105 | 1106 | } |
| 1106 | 1107 | |
| 1107 | fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void { | |
| 1108 | fn printSourceAtAddress( | |
| 1109 | gpa: Allocator, | |
| 1110 | io: Io, | |
| 1111 | debug_info: *SelfInfo, | |
| 1112 | t: Io.Terminal, | |
| 1113 | address: usize, | |
| 1114 | ) Writer.Error!void { | |
| 1108 | 1115 | const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) { |
| 1109 | 1116 | error.MissingDebugInfo, |
| 1110 | 1117 | error.UnsupportedDebugInfo, |
| 1111 | 1118 | error.InvalidDebugInfo, |
| 1112 | 1119 | => .unknown, |
| 1113 | 1120 | error.ReadFailed, error.Unexpected, error.Canceled => s: { |
| 1114 | tty_config.setColor(writer, .dim) catch {}; | |
| 1115 | try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); | |
| 1116 | tty_config.setColor(writer, .reset) catch {}; | |
| 1121 | t.setColor(.dim) catch {}; | |
| 1122 | try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); | |
| 1123 | t.setColor(.reset) catch {}; | |
| 1117 | 1124 | break :s .unknown; |
| 1118 | 1125 | }, |
| 1119 | 1126 | error.OutOfMemory => s: { |
| 1120 | tty_config.setColor(writer, .dim) catch {}; | |
| 1121 | try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); | |
| 1122 | tty_config.setColor(writer, .reset) catch {}; | |
| 1127 | t.setColor(.dim) catch {}; | |
| 1128 | try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); | |
| 1129 | t.setColor(.reset) catch {}; | |
| 1123 | 1130 | break :s .unknown; |
| 1124 | 1131 | }, |
| 1125 | 1132 | }; |
| 1126 | 1133 | defer if (symbol.source_location) |sl| gpa.free(sl.file_name); |
| 1127 | 1134 | return printLineInfo( |
| 1128 | writer, | |
| 1135 | io, | |
| 1136 | t, | |
| 1129 | 1137 | symbol.source_location, |
| 1130 | 1138 | address, |
| 1131 | 1139 | symbol.name orelse "???", |
| 1132 | 1140 | symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???", |
| 1133 | tty_config, | |
| 1134 | 1141 | ); |
| 1135 | 1142 | } |
| 1136 | 1143 | fn printLineInfo( |
| 1137 | writer: *Writer, | |
| 1144 | io: Io, | |
| 1145 | t: Io.Terminal, | |
| 1138 | 1146 | source_location: ?SourceLocation, |
| 1139 | 1147 | address: usize, |
| 1140 | 1148 | symbol_name: []const u8, |
| 1141 | 1149 | compile_unit_name: []const u8, |
| 1142 | tty_config: tty.Config, | |
| 1143 | 1150 | ) Writer.Error!void { |
| 1144 | nosuspend { | |
| 1145 | tty_config.setColor(writer, .bold) catch {}; | |
| 1151 | const writer = t.writer; | |
| 1152 | t.setColor(.bold) catch {}; | |
| 1146 | 1153 | |
| 1147 | if (source_location) |*sl| { | |
| 1148 | try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); | |
| 1149 | } else { | |
| 1150 | try writer.writeAll("???:?:?"); | |
| 1151 | } | |
| 1154 | if (source_location) |*sl| { | |
| 1155 | try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); | |
| 1156 | } else { | |
| 1157 | try writer.writeAll("???:?:?"); | |
| 1158 | } | |
| 1152 | 1159 | |
| 1153 | tty_config.setColor(writer, .reset) catch {}; | |
| 1154 | try writer.writeAll(": "); | |
| 1155 | tty_config.setColor(writer, .dim) catch {}; | |
| 1156 | try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); | |
| 1157 | tty_config.setColor(writer, .reset) catch {}; | |
| 1158 | try writer.writeAll("\n"); | |
| 1159 | ||
| 1160 | // Show the matching source code line if possible | |
| 1161 | if (source_location) |sl| { | |
| 1162 | if (printLineFromFile(writer, sl)) { | |
| 1163 | if (sl.column > 0) { | |
| 1164 | // The caret already takes one char | |
| 1165 | const space_needed = @as(usize, @intCast(sl.column - 1)); | |
| 1166 | ||
| 1167 | try writer.splatByteAll(' ', space_needed); | |
| 1168 | tty_config.setColor(writer, .green) catch {}; | |
| 1169 | try writer.writeAll("^"); | |
| 1170 | tty_config.setColor(writer, .reset) catch {}; | |
| 1171 | } | |
| 1172 | try writer.writeAll("\n"); | |
| 1173 | } else |_| { | |
| 1174 | // Ignore all errors; it's a better UX to just print the source location without the | |
| 1175 | // corresponding line number. The user can always open the source file themselves. | |
| 1160 | t.setColor(.reset) catch {}; | |
| 1161 | try writer.writeAll(": "); | |
| 1162 | t.setColor(.dim) catch {}; | |
| 1163 | try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); | |
| 1164 | t.setColor(.reset) catch {}; | |
| 1165 | try writer.writeAll("\n"); | |
| 1166 | ||
| 1167 | // Show the matching source code line if possible | |
| 1168 | if (source_location) |sl| { | |
| 1169 | if (printLineFromFile(io, writer, sl)) { | |
| 1170 | if (sl.column > 0) { | |
| 1171 | // The caret already takes one char | |
| 1172 | const space_needed = @as(usize, @intCast(sl.column - 1)); | |
| 1173 | ||
| 1174 | try writer.splatByteAll(' ', space_needed); | |
| 1175 | t.setColor(.green) catch {}; | |
| 1176 | try writer.writeAll("^"); | |
| 1177 | t.setColor(.reset) catch {}; | |
| 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 | } |
| 1180 | fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void { | |
| 1186 | fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !void { | |
| 1181 | 1187 | // Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`. |
| 1182 | 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 | } |
| 1185 | 1191 | |
| 1186 | 1192 | // Need this to always block even in async I/O mode, because this could potentially |
| 1187 | 1193 | // be called from e.g. the event loop code crashing. |
| 1188 | var f = try fs.cwd().openFile(source_location.file_name, .{}); | |
| 1189 | defer f.close(); | |
| 1190 | // TODO fstat and make sure that the file has the correct size | |
| 1191 | ||
| 1192 | var buf: [4096]u8 = undefined; | |
| 1193 | var amt_read = try f.read(buf[0..]); | |
| 1194 | const line_start = seek: { | |
| 1195 | var current_line_start: usize = 0; | |
| 1196 | var next_line: usize = 1; | |
| 1197 | while (next_line != source_location.line) { | |
| 1198 | const slice = buf[current_line_start..amt_read]; | |
| 1199 | if (mem.findScalar(u8, slice, '\n')) |pos| { | |
| 1200 | next_line += 1; | |
| 1201 | if (pos == slice.len - 1) { | |
| 1202 | amt_read = try f.read(buf[0..]); | |
| 1203 | current_line_start = 0; | |
| 1204 | } else current_line_start += pos + 1; | |
| 1205 | } else if (amt_read < buf.len) { | |
| 1206 | return error.EndOfFile; | |
| 1207 | } else { | |
| 1208 | amt_read = try f.read(buf[0..]); | |
| 1209 | current_line_start = 0; | |
| 1210 | } | |
| 1211 | } | |
| 1212 | break :seek current_line_start; | |
| 1213 | }; | |
| 1214 | const slice = buf[line_start..amt_read]; | |
| 1215 | if (mem.findScalar(u8, slice, '\n')) |pos| { | |
| 1216 | const line = slice[0 .. pos + 1]; | |
| 1217 | mem.replaceScalar(u8, line, '\t', ' '); | |
| 1218 | return writer.writeAll(line); | |
| 1219 | } else { // Line is the last inside the buffer, and requires another read to find delimiter. Alternatively the file ends. | |
| 1220 | mem.replaceScalar(u8, slice, '\t', ' '); | |
| 1221 | try writer.writeAll(slice); | |
| 1222 | while (amt_read == buf.len) { | |
| 1223 | amt_read = try f.read(buf[0..]); | |
| 1224 | if (mem.findScalar(u8, buf[0..amt_read], '\n')) |pos| { | |
| 1225 | const line = buf[0 .. pos + 1]; | |
| 1226 | mem.replaceScalar(u8, line, '\t', ' '); | |
| 1227 | return writer.writeAll(line); | |
| 1228 | } else { | |
| 1229 | const line = buf[0..amt_read]; | |
| 1230 | mem.replaceScalar(u8, line, '\t', ' '); | |
| 1231 | try writer.writeAll(line); | |
| 1232 | } | |
| 1194 | const cwd: Io.Dir = .cwd(); | |
| 1195 | var file = try cwd.openFile(io, source_location.file_name, .{}); | |
| 1196 | defer file.close(io); | |
| 1197 | ||
| 1198 | var buffer: [4096]u8 = undefined; | |
| 1199 | var file_reader: File.Reader = .init(file, io, &buffer); | |
| 1200 | var line_index: usize = 0; | |
| 1201 | const r = &file_reader.interface; | |
| 1202 | while (true) { | |
| 1203 | line_index += 1; | |
| 1204 | if (line_index == source_location.line) { | |
| 1205 | // TODO delete hard tabs from the language | |
| 1206 | _ = try r.streamDelimiterEnding(writer, '\n'); | |
| 1207 | try writer.writeByte('\n'); | |
| 1208 | return; | |
| 1233 | 1209 | } |
| 1234 | // Make sure printing last line of file inserts extra newline | |
| 1235 | try writer.writeByte('\n'); | |
| 1210 | _ = try r.discardDelimiterInclusive('\n'); | |
| 1236 | 1211 | } |
| 1237 | 1212 | } |
| 1238 | 1213 | |
| 1239 | 1214 | test 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 | 1219 | defer aw.deinit(); |
| 1242 | 1220 | const output_stream = &aw.writer; |
| 1243 | 1221 | |
| 1244 | const allocator = std.testing.allocator; | |
| 1245 | 1222 | const join = std.fs.path.join; |
| 1246 | const expectError = std.testing.expectError; | |
| 1247 | const expectEqualStrings = std.testing.expectEqualStrings; | |
| 1223 | const expectError = testing.expectError; | |
| 1224 | const expectEqualStrings = testing.expectEqualStrings; | |
| 1248 | 1225 | |
| 1249 | var test_dir = std.testing.tmpDir(.{}); | |
| 1226 | var test_dir = testing.tmpDir(.{}); | |
| 1250 | 1227 | defer test_dir.cleanup(); |
| 1251 | 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..] }); | |
| 1253 | defer allocator.free(test_dir_path); | |
| 1229 | const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] }); | |
| 1230 | defer gpa.free(test_dir_path); | |
| 1254 | 1231 | |
| 1255 | 1232 | // Cases |
| 1256 | 1233 | { |
| 1257 | const path = try join(allocator, &.{ test_dir_path, "one_line.zig" }); | |
| 1258 | defer allocator.free(path); | |
| 1259 | try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); | |
| 1234 | const path = try join(gpa, &.{ test_dir_path, "one_line.zig" }); | |
| 1235 | defer gpa.free(path); | |
| 1236 | try test_dir.dir.writeFile(io, .{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); | |
| 1260 | 1237 | |
| 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 })); | |
| 1262 | 1239 | |
| 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 | 1241 | try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written()); |
| 1265 | 1242 | aw.clearRetainingCapacity(); |
| 1266 | 1243 | } |
| 1267 | 1244 | { |
| 1268 | const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" }); | |
| 1269 | defer allocator.free(path); | |
| 1270 | try test_dir.dir.writeFile(.{ | |
| 1245 | const path = try fs.path.join(gpa, &.{ test_dir_path, "three_lines.zig" }); | |
| 1246 | defer gpa.free(path); | |
| 1247 | try test_dir.dir.writeFile(io, .{ | |
| 1271 | 1248 | .sub_path = "three_lines.zig", |
| 1272 | 1249 | .data = |
| 1273 | 1250 | \\1 |
| ... | ... | @@ -1276,90 +1253,90 @@ test printLineFromFile { |
| 1276 | 1253 | , |
| 1277 | 1254 | }); |
| 1278 | 1255 | |
| 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 | 1257 | try expectEqualStrings("1\n", aw.written()); |
| 1281 | 1258 | aw.clearRetainingCapacity(); |
| 1282 | 1259 | |
| 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 | 1261 | try expectEqualStrings("3\n", aw.written()); |
| 1285 | 1262 | aw.clearRetainingCapacity(); |
| 1286 | 1263 | } |
| 1287 | 1264 | { |
| 1288 | const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{}); | |
| 1289 | defer file.close(); | |
| 1290 | const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); | |
| 1291 | defer allocator.free(path); | |
| 1265 | const file = try test_dir.dir.createFile(io, "line_overlaps_page_boundary.zig", .{}); | |
| 1266 | defer file.close(io); | |
| 1267 | const path = try fs.path.join(gpa, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); | |
| 1268 | defer gpa.free(path); | |
| 1292 | 1269 | |
| 1293 | 1270 | const overlap = 10; |
| 1294 | 1271 | var buf: [16]u8 = undefined; |
| 1295 | var file_writer = file.writer(&buf); | |
| 1272 | var file_writer = file.writer(io, &buf); | |
| 1296 | 1273 | const writer = &file_writer.interface; |
| 1297 | 1274 | try writer.splatByteAll('a', std.heap.page_size_min - overlap); |
| 1298 | 1275 | try writer.writeByte('\n'); |
| 1299 | 1276 | try writer.splatByteAll('a', overlap); |
| 1300 | 1277 | try writer.flush(); |
| 1301 | 1278 | |
| 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 | 1280 | try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written()); |
| 1304 | 1281 | aw.clearRetainingCapacity(); |
| 1305 | 1282 | } |
| 1306 | 1283 | { |
| 1307 | const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{}); | |
| 1308 | defer file.close(); | |
| 1309 | const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); | |
| 1310 | defer allocator.free(path); | |
| 1284 | const file = try test_dir.dir.createFile(io, "file_ends_on_page_boundary.zig", .{}); | |
| 1285 | defer file.close(io); | |
| 1286 | const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); | |
| 1287 | defer gpa.free(path); | |
| 1311 | 1288 | |
| 1312 | var file_writer = file.writer(&.{}); | |
| 1289 | var file_writer = file.writer(io, &.{}); | |
| 1313 | 1290 | const writer = &file_writer.interface; |
| 1314 | 1291 | try writer.splatByteAll('a', std.heap.page_size_max); |
| 1315 | 1292 | |
| 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 | 1294 | try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written()); |
| 1318 | 1295 | aw.clearRetainingCapacity(); |
| 1319 | 1296 | } |
| 1320 | 1297 | { |
| 1321 | const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{}); | |
| 1322 | defer file.close(); | |
| 1323 | const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); | |
| 1324 | defer allocator.free(path); | |
| 1298 | const file = try test_dir.dir.createFile(io, "very_long_first_line_spanning_multiple_pages.zig", .{}); | |
| 1299 | defer file.close(io); | |
| 1300 | const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); | |
| 1301 | defer gpa.free(path); | |
| 1325 | 1302 | |
| 1326 | var file_writer = file.writer(&.{}); | |
| 1303 | var file_writer = file.writer(io, &.{}); | |
| 1327 | 1304 | const writer = &file_writer.interface; |
| 1328 | 1305 | try writer.splatByteAll('a', 3 * std.heap.page_size_max); |
| 1329 | 1306 | |
| 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 })); | |
| 1331 | 1308 | |
| 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 | 1310 | try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written()); |
| 1334 | 1311 | aw.clearRetainingCapacity(); |
| 1335 | 1312 | |
| 1336 | 1313 | try writer.writeAll("a\na"); |
| 1337 | 1314 | |
| 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 | 1316 | try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written()); |
| 1340 | 1317 | aw.clearRetainingCapacity(); |
| 1341 | 1318 | |
| 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 | 1320 | try expectEqualStrings("a\n", aw.written()); |
| 1344 | 1321 | aw.clearRetainingCapacity(); |
| 1345 | 1322 | } |
| 1346 | 1323 | { |
| 1347 | const file = try test_dir.dir.createFile("file_of_newlines.zig", .{}); | |
| 1348 | defer file.close(); | |
| 1349 | const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" }); | |
| 1350 | defer allocator.free(path); | |
| 1324 | const file = try test_dir.dir.createFile(io, "file_of_newlines.zig", .{}); | |
| 1325 | defer file.close(io); | |
| 1326 | const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" }); | |
| 1327 | defer gpa.free(path); | |
| 1351 | 1328 | |
| 1352 | var file_writer = file.writer(&.{}); | |
| 1329 | var file_writer = file.writer(io, &.{}); | |
| 1353 | 1330 | const writer = &file_writer.interface; |
| 1354 | 1331 | const real_file_start = 3 * std.heap.page_size_min; |
| 1355 | 1332 | try writer.splatByteAll('\n', real_file_start); |
| 1356 | 1333 | try writer.writeAll("abc\ndef"); |
| 1357 | 1334 | |
| 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 | 1336 | try expectEqualStrings("abc\n", aw.written()); |
| 1360 | 1337 | aw.clearRetainingCapacity(); |
| 1361 | 1338 | |
| 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 | 1340 | try expectEqualStrings("def\n", aw.written()); |
| 1364 | 1341 | aw.clearRetainingCapacity(); |
| 1365 | 1342 | } |
| ... | ... | @@ -1563,19 +1540,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex |
| 1563 | 1540 | _ = panicking.fetchAdd(1, .seq_cst); |
| 1564 | 1541 | |
| 1565 | 1542 | trace: { |
| 1566 | const stderr, const tty_config = lockStderrWriter(&.{}); | |
| 1567 | defer unlockStderrWriter(); | |
| 1543 | const stderr = lockStderr(&.{}).terminal(); | |
| 1544 | defer unlockStderr(); | |
| 1568 | 1545 | |
| 1569 | 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 | 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 | 1551 | if (opt_ctx) |context| { |
| 1575 | 1552 | writeCurrentStackTrace(.{ |
| 1576 | 1553 | .context = context, |
| 1577 | 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 | 1561 | // A segfault happened while trying to print a previous panic message. |
| 1585 | 1562 | // We're still holding the mutex but that's fine as we're going to |
| 1586 | 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 | 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 | 1570 | // We cannot allow the signal handler to return because when it runs the original instruction |
| 1593 | 1571 | // again, the memory may be mapped and undefined behavior would occur rather than repeating |
| 1594 | 1572 | // the segfault. So we simply abort here. |
| 1595 | posix.abort(); | |
| 1573 | std.process.abort(); | |
| 1596 | 1574 | } |
| 1597 | 1575 | |
| 1598 | 1576 | pub fn dumpStackPointerAddr(prefix: []const u8) void { |
| ... | ... | @@ -1616,20 +1594,14 @@ test "manage resources correctly" { |
| 1616 | 1594 | return @returnAddress(); |
| 1617 | 1595 | } |
| 1618 | 1596 | }; |
| 1619 | const gpa = std.testing.allocator; | |
| 1620 | var threaded: Io.Threaded = .init_single_threaded; | |
| 1621 | const io = threaded.ioBasic(); | |
| 1622 | var discarding: Io.Writer.Discarding = .init(&.{}); | |
| 1597 | const gpa = testing.allocator; | |
| 1598 | const io = testing.io; | |
| 1599 | ||
| 1600 | var discarding: Writer.Discarding = .init(&.{}); | |
| 1623 | 1601 | var di: SelfInfo = .init; |
| 1624 | 1602 | defer di.deinit(gpa); |
| 1625 | try printSourceAtAddress( | |
| 1626 | gpa, | |
| 1627 | io, | |
| 1628 | &di, | |
| 1629 | &discarding.writer, | |
| 1630 | S.showMyTrace(), | |
| 1631 | .no_color, | |
| 1632 | ); | |
| 1603 | const t: Io.Terminal = .{ .writer = &discarding.writer, .mode = .no_color }; | |
| 1604 | try printSourceAtAddress(gpa, io, &di, t, S.showMyTrace()); | |
| 1633 | 1605 | } |
| 1634 | 1606 | |
| 1635 | 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 | 1662 | pub fn dump(t: @This()) void { |
| 1691 | 1663 | if (!enabled) return; |
| 1692 | 1664 | |
| 1693 | const stderr, const tty_config = lockStderrWriter(&.{}); | |
| 1694 | defer unlockStderrWriter(); | |
| 1665 | const stderr = lockStderr(&.{}).terminal(); | |
| 1666 | defer unlockStderr(); | |
| 1695 | 1667 | const end = @min(t.index, size); |
| 1696 | 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 | 1670 | var frames_array_mutable = frames_array; |
| 1699 | 1671 | const frames = mem.sliceTo(frames_array_mutable[0..], 0); |
| 1700 | 1672 | const stack_trace: StackTrace = .{ |
| 1701 | 1673 | .index = frames.len, |
| 1702 | 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 | 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 | 1680 | t.index - end, |
| 1709 | 1681 | }) catch return; |
| 1710 | 1682 | } |
lib/std/debug/ElfFile.zig+30-20| ... | ... | @@ -1,5 +1,13 @@ |
| 1 | 1 | //! A helper type for loading an ELF file and collecting its DWARF debug information, unwind |
| 2 | 2 | //! information, and symbol table. |
| 3 | const ElfFile = @This(); | |
| 4 | ||
| 5 | const std = @import("std"); | |
| 6 | const Io = std.Io; | |
| 7 | const Endian = std.builtin.Endian; | |
| 8 | const Dwarf = std.debug.Dwarf; | |
| 9 | const Allocator = std.mem.Allocator; | |
| 10 | const elf = std.elf; | |
| 3 | 11 | |
| 4 | 12 | is_64: bool, |
| 5 | 13 | endian: Endian, |
| ... | ... | @@ -115,7 +123,8 @@ pub const LoadError = error{ |
| 115 | 123 | |
| 116 | 124 | pub fn load( |
| 117 | 125 | gpa: Allocator, |
| 118 | elf_file: std.fs.File, | |
| 126 | io: Io, | |
| 127 | elf_file: Io.File, | |
| 119 | 128 | opt_build_id: ?[]const u8, |
| 120 | 129 | di_search_paths: *const DebugInfoSearchPaths, |
| 121 | 130 | ) LoadError!ElfFile { |
| ... | ... | @@ -123,7 +132,7 @@ pub fn load( |
| 123 | 132 | errdefer arena_instance.deinit(); |
| 124 | 133 | const arena = arena_instance.allocator(); |
| 125 | 134 | |
| 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 | 136 | error.CrcMismatch => unreachable, // we passed crc as null |
| 128 | 137 | else => |e| return e, |
| 129 | 138 | }; |
| ... | ... | @@ -148,7 +157,7 @@ pub fn load( |
| 148 | 157 | if (build_id.len < 3) break :build_id; |
| 149 | 158 | |
| 150 | 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 | 161 | global_debug, |
| 153 | 162 | build_id[0..1], |
| 154 | 163 | build_id[1..], |
| ... | ... | @@ -156,7 +165,7 @@ pub fn load( |
| 156 | 165 | } |
| 157 | 166 | |
| 158 | 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 | 169 | components[0], |
| 161 | 170 | components[1], |
| 162 | 171 | build_id, |
| ... | ... | @@ -173,18 +182,18 @@ pub fn load( |
| 173 | 182 | |
| 174 | 183 | const exe_dir = di_search_paths.exe_dir orelse break :debug_link; |
| 175 | 184 | |
| 176 | if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/{s}", .{ | |
| 185 | if (try loadSeparateDebugFile(arena, io, &result, debug_crc, "{s}/{s}", .{ | |
| 177 | 186 | exe_dir, |
| 178 | 187 | debug_filename, |
| 179 | 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 | 190 | exe_dir, |
| 182 | 191 | debug_filename, |
| 183 | 192 | })) |mapped| break :load_di mapped; |
| 184 | 193 | for (di_search_paths.global_debug) |global_debug| { |
| 185 | 194 | // This looks like a bug; it isn't. They really do embed the absolute path to the |
| 186 | 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 | 197 | global_debug, |
| 189 | 198 | exe_dir, |
| 190 | 199 | debug_filename, |
| ... | ... | @@ -358,12 +367,19 @@ const Section = struct { |
| 358 | 367 | const Array = std.enums.EnumArray(Section.Id, ?Section); |
| 359 | 368 | }; |
| 360 | 369 | |
| 361 | fn 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 { | |
| 370 | fn 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 | 378 | const path = try std.fmt.allocPrint(arena, fmt, args); |
| 363 | const elf_file = std.fs.cwd().openFile(path, .{}) catch return null; | |
| 364 | defer elf_file.close(); | |
| 379 | const elf_file = Io.Dir.cwd().openFile(io, path, .{}) catch return null; | |
| 380 | defer elf_file.close(io); | |
| 365 | 381 | |
| 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 | 383 | error.OutOfMemory => |e| return e, |
| 368 | 384 | error.CrcMismatch => return null, |
| 369 | 385 | else => return null, |
| ... | ... | @@ -408,13 +424,14 @@ const LoadInnerResult = struct { |
| 408 | 424 | }; |
| 409 | 425 | fn loadInner( |
| 410 | 426 | arena: Allocator, |
| 411 | elf_file: std.fs.File, | |
| 427 | io: Io, | |
| 428 | elf_file: Io.File, | |
| 412 | 429 | opt_crc: ?u32, |
| 413 | 430 | ) (LoadError || error{ CrcMismatch, Streaming, Canceled })!LoadInnerResult { |
| 414 | 431 | const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: { |
| 415 | 432 | const file_len = std.math.cast( |
| 416 | 433 | usize, |
| 417 | elf_file.getEndPos() catch |err| switch (err) { | |
| 434 | elf_file.length(io) catch |err| switch (err) { | |
| 418 | 435 | error.PermissionDenied => unreachable, // not asking for PROT_EXEC |
| 419 | 436 | else => |e| return e, |
| 420 | 437 | }, |
| ... | ... | @@ -529,10 +546,3 @@ fn loadInner( |
| 529 | 546 | .mapped_mem = mapped_mem, |
| 530 | 547 | }; |
| 531 | 548 | } |
| 532 | ||
| 533 | const std = @import("std"); | |
| 534 | const Endian = std.builtin.Endian; | |
| 535 | const Dwarf = std.debug.Dwarf; | |
| 536 | const ElfFile = @This(); | |
| 537 | const Allocator = std.mem.Allocator; | |
| 538 | const elf = std.elf; |
lib/std/debug/Info.zig+20-10| ... | ... | @@ -5,19 +5,18 @@ |
| 5 | 5 | //! Unlike `std.debug.SelfInfo`, this API does not assume the debug information |
| 6 | 6 | //! in question happens to match the host CPU architecture, OS, or other target |
| 7 | 7 | //! properties. |
| 8 | const Info = @This(); | |
| 8 | 9 | |
| 9 | 10 | const std = @import("../std.zig"); |
| 11 | const Io = std.Io; | |
| 10 | 12 | const Allocator = std.mem.Allocator; |
| 11 | 13 | const Path = std.Build.Cache.Path; |
| 12 | 14 | const assert = std.debug.assert; |
| 13 | 15 | const Coverage = std.debug.Coverage; |
| 14 | 16 | const SourceLocation = std.debug.Coverage.SourceLocation; |
| 15 | ||
| 16 | 17 | const ElfFile = std.debug.ElfFile; |
| 17 | 18 | const MachOFile = std.debug.MachOFile; |
| 18 | 19 | |
| 19 | const Info = @This(); | |
| 20 | ||
| 21 | 20 | impl: union(enum) { |
| 22 | 21 | elf: ElfFile, |
| 23 | 22 | macho: MachOFile, |
| ... | ... | @@ -25,15 +24,25 @@ impl: union(enum) { |
| 25 | 24 | /// Externally managed, outlives this `Info` instance. |
| 26 | 25 | coverage: *Coverage, |
| 27 | 26 | |
| 28 | pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || MachOFile.Error || std.debug.Dwarf.ScanError || error{ MissingDebugInfo, UnsupportedDebugInfo }; | |
| 27 | pub const LoadError = error{ | |
| 28 | MissingDebugInfo, | |
| 29 | UnsupportedDebugInfo, | |
| 30 | } || Io.File.OpenError || ElfFile.LoadError || MachOFile.Error || std.debug.Dwarf.ScanError; | |
| 29 | 31 | |
| 30 | pub fn load(gpa: Allocator, path: Path, coverage: *Coverage, format: std.Target.ObjectFormat, arch: std.Target.Cpu.Arch) LoadError!Info { | |
| 32 | pub 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 | 40 | switch (format) { |
| 32 | 41 | .elf => { |
| 33 | var file = try path.root_dir.handle.openFile(path.sub_path, .{}); | |
| 34 | defer file.close(); | |
| 42 | var file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); | |
| 43 | defer file.close(io); | |
| 35 | 44 | |
| 36 | var elf_file: ElfFile = try .load(gpa, file, null, &.none); | |
| 45 | var elf_file: ElfFile = try .load(gpa, io, file, null, &.none); | |
| 37 | 46 | errdefer elf_file.deinit(gpa); |
| 38 | 47 | |
| 39 | 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 | 58 | const path_str = try path.toString(gpa); |
| 50 | 59 | defer gpa.free(path_str); |
| 51 | 60 | |
| 52 | var macho_file: MachOFile = try .load(gpa, path_str, arch); | |
| 61 | var macho_file: MachOFile = try .load(gpa, io, path_str, arch); | |
| 53 | 62 | errdefer macho_file.deinit(gpa); |
| 54 | 63 | |
| 55 | 64 | return .{ |
| ... | ... | @@ -76,6 +85,7 @@ pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError || error{U |
| 76 | 85 | pub fn resolveAddresses( |
| 77 | 86 | info: *Info, |
| 78 | 87 | gpa: Allocator, |
| 88 | io: Io, | |
| 79 | 89 | /// Asserts the addresses are in ascending order. |
| 80 | 90 | sorted_pc_addrs: []const u64, |
| 81 | 91 | /// Asserts its length equals length of `sorted_pc_addrs`. |
| ... | ... | @@ -88,7 +98,7 @@ pub fn resolveAddresses( |
| 88 | 98 | // Resolving all of the addresses at once unfortunately isn't so easy in Mach-O binaries |
| 89 | 99 | // due to split debug information. For now, we'll just resolve the addreses one by one. |
| 90 | 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 | 102 | error.InvalidMachO, error.InvalidDwarf => return error.InvalidDebugInfo, |
| 93 | 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 | 27 | posix.munmap(mf.mapped_memory); |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | pub fn load(gpa: Allocator, path: []const u8, arch: std.Target.Cpu.Arch) Error!MachOFile { | |
| 30 | pub fn load(gpa: Allocator, io: Io, path: []const u8, arch: std.Target.Cpu.Arch) Error!MachOFile { | |
| 31 | 31 | switch (arch) { |
| 32 | 32 | .x86_64, .aarch64 => {}, |
| 33 | 33 | else => unreachable, |
| 34 | 34 | } |
| 35 | 35 | |
| 36 | const all_mapped_memory = try mapDebugInfoFile(path); | |
| 36 | const all_mapped_memory = try mapDebugInfoFile(io, path); | |
| 37 | 37 | errdefer posix.munmap(all_mapped_memory); |
| 38 | 38 | |
| 39 | 39 | // 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 | 239 | .text_vmaddr = text_vmaddr, |
| 240 | 240 | }; |
| 241 | 241 | } |
| 242 | pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, vaddr: u64) !struct { *Dwarf, u64 } { | |
| 242 | pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, io: Io, vaddr: u64) !struct { *Dwarf, u64 } { | |
| 243 | 243 | const symbol = Symbol.find(mf.symbols, vaddr) orelse return error.MissingDebugInfo; |
| 244 | 244 | |
| 245 | 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 | 254 | const gop = try mf.ofiles.getOrPut(gpa, symbol.ofile); |
| 255 | 255 | if (!gop.found_existing) { |
| 256 | 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 | 259 | const of = &(gop.value_ptr.* catch |err| return err); |
| 260 | 260 | |
| ... | ... | @@ -356,7 +356,7 @@ test { |
| 356 | 356 | _ = Symbol; |
| 357 | 357 | } |
| 358 | 358 | |
| 359 | fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile { | |
| 359 | fn loadOFile(gpa: Allocator, io: Io, o_file_name: []const u8) !OFile { | |
| 360 | 360 | const all_mapped_memory, const mapped_ofile = map: { |
| 361 | 361 | const open_paren = paren: { |
| 362 | 362 | if (std.mem.endsWith(u8, o_file_name, ")")) { |
| ... | ... | @@ -365,7 +365,7 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile { |
| 365 | 365 | } |
| 366 | 366 | } |
| 367 | 367 | // 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 | 369 | break :map .{ m, m }; |
| 370 | 370 | }; |
| 371 | 371 | |
| ... | ... | @@ -373,7 +373,7 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile { |
| 373 | 373 | |
| 374 | 374 | const archive_path = o_file_name[0..open_paren]; |
| 375 | 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 | 377 | errdefer posix.munmap(mapped_archive); |
| 378 | 378 | |
| 379 | 379 | var ar_reader: Io.Reader = .fixed(mapped_archive); |
| ... | ... | @@ -511,16 +511,16 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile { |
| 511 | 511 | } |
| 512 | 512 | |
| 513 | 513 | /// Uses `mmap` to map the file at `path` into memory. |
| 514 | fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 { | |
| 515 | const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 514 | fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 { | |
| 515 | const file = Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) { | |
| 516 | 516 | error.FileNotFound => return error.MissingDebugInfo, |
| 517 | 517 | else => return error.ReadFailed, |
| 518 | 518 | }; |
| 519 | defer file.close(); | |
| 519 | defer file.close(io); | |
| 520 | 520 | |
| 521 | 521 | const file_len = std.math.cast( |
| 522 | 522 | usize, |
| 523 | file.getEndPos() catch return error.ReadFailed, | |
| 523 | file.length(io) catch return error.ReadFailed, | |
| 524 | 524 | ) orelse return error.ReadFailed; |
| 525 | 525 | |
| 526 | 526 | return posix.mmap( |
lib/std/debug/Pdb.zig+1-1| ... | ... | @@ -1,5 +1,5 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | const File = std.fs.File; | |
| 2 | const File = std.Io.File; | |
| 3 | 3 | const Allocator = std.mem.Allocator; |
| 4 | 4 | const pdb = std.pdb; |
| 5 | 5 | const 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 | 29 | } |
| 30 | 30 | |
| 31 | 31 | pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { |
| 32 | _ = io; | |
| 33 | 32 | const module = try si.findModule(gpa, address, .exclusive); |
| 34 | 33 | defer si.rwlock.unlock(); |
| 35 | 34 | |
| 36 | 35 | const vaddr = address - module.load_offset; |
| 37 | 36 | |
| 38 | const loaded_elf = try module.getLoadedElf(gpa); | |
| 37 | const loaded_elf = try module.getLoadedElf(gpa, io); | |
| 39 | 38 | if (loaded_elf.file.dwarf) |*dwarf| { |
| 40 | 39 | if (!loaded_elf.scanned_dwarf) { |
| 41 | 40 | dwarf.open(gpa, native_endian) catch |err| switch (err) { |
| ... | ... | @@ -180,7 +179,7 @@ comptime { |
| 180 | 179 | } |
| 181 | 180 | } |
| 182 | 181 | pub const UnwindContext = Dwarf.SelfUnwinder; |
| 183 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | |
| 182 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize { | |
| 184 | 183 | comptime assert(can_unwind); |
| 185 | 184 | |
| 186 | 185 | { |
| ... | ... | @@ -201,7 +200,7 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error |
| 201 | 200 | @memset(si.unwind_cache.?, .empty); |
| 202 | 201 | } |
| 203 | 202 | |
| 204 | const unwind_sections = try module.getUnwindSections(gpa); | |
| 203 | const unwind_sections = try module.getUnwindSections(gpa, io); | |
| 205 | 204 | for (unwind_sections) |*unwind| { |
| 206 | 205 | if (context.computeRules(gpa, unwind, module.load_offset, null)) |entry| { |
| 207 | 206 | entry.populate(si.unwind_cache.?); |
| ... | ... | @@ -261,12 +260,12 @@ const Module = struct { |
| 261 | 260 | }; |
| 262 | 261 | |
| 263 | 262 | /// Assumes we already hold an exclusive lock. |
| 264 | fn getUnwindSections(mod: *Module, gpa: Allocator) Error![]Dwarf.Unwind { | |
| 265 | if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa); | |
| 263 | fn getUnwindSections(mod: *Module, gpa: Allocator, io: Io) Error![]Dwarf.Unwind { | |
| 264 | if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa, io); | |
| 266 | 265 | const us = &(mod.unwind.? catch |err| return err); |
| 267 | 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 | 269 | var us: UnwindSections = .{ |
| 271 | 270 | .buf = undefined, |
| 272 | 271 | .len = 0, |
| ... | ... | @@ -284,7 +283,7 @@ const Module = struct { |
| 284 | 283 | } else { |
| 285 | 284 | // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame` |
| 286 | 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 | 287 | // If both are present, we can't just pick one -- the info could be split between them. |
| 289 | 288 | // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one. |
| 290 | 289 | if (loaded.file.debug_frame) |*debug_frame| { |
| ... | ... | @@ -319,24 +318,24 @@ const Module = struct { |
| 319 | 318 | } |
| 320 | 319 | |
| 321 | 320 | /// Assumes we already hold an exclusive lock. |
| 322 | fn getLoadedElf(mod: *Module, gpa: Allocator) Error!*LoadedElf { | |
| 323 | if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa); | |
| 321 | fn getLoadedElf(mod: *Module, gpa: Allocator, io: Io) Error!*LoadedElf { | |
| 322 | if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io); | |
| 324 | 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 | 326 | const load_result = if (mod.name.len > 0) res: { |
| 328 | var file = std.fs.cwd().openFile(mod.name, .{}) catch return error.MissingDebugInfo; | |
| 329 | defer file.close(); | |
| 330 | break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name)); | |
| 327 | var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo; | |
| 328 | defer file.close(io); | |
| 329 | break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name)); | |
| 331 | 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 | 332 | error.OutOfMemory => |e| return e, |
| 334 | 333 | else => return error.ReadFailed, |
| 335 | 334 | }; |
| 336 | 335 | defer gpa.free(path); |
| 337 | var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo; | |
| 338 | defer file.close(); | |
| 339 | break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path)); | |
| 336 | var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo; | |
| 337 | defer file.close(io); | |
| 338 | break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path)); | |
| 340 | 339 | }; |
| 341 | 340 | |
| 342 | 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 | 21 | } |
| 22 | 22 | |
| 23 | 23 | pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { |
| 24 | _ = io; | |
| 25 | 24 | const module = try si.findModule(gpa, address); |
| 26 | 25 | defer si.mutex.unlock(); |
| 27 | 26 | |
| 28 | const file = try module.getFile(gpa); | |
| 27 | const file = try module.getFile(gpa, io); | |
| 29 | 28 | |
| 30 | 29 | // This is not necessarily the same as the vmaddr_slide that dyld would report. This is |
| 31 | 30 | // 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 | 38 | |
| 40 | 39 | const vaddr = address - vaddr_offset; |
| 41 | 40 | |
| 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 | 42 | // Return at least the symbol name if available. |
| 44 | 43 | return .{ |
| 45 | 44 | .name = try file.lookupSymbolName(vaddr), |
| ... | ... | @@ -107,7 +106,8 @@ pub const UnwindContext = std.debug.Dwarf.SelfUnwinder; |
| 107 | 106 | /// Unwind a frame using MachO compact unwind info (from `__unwind_info`). |
| 108 | 107 | /// If the compact encoding can't encode a way to unwind a frame, it will |
| 109 | 108 | /// defer unwinding to DWARF, in which case `__eh_frame` will be used if available. |
| 110 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | |
| 109 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize { | |
| 110 | _ = io; | |
| 111 | 111 | return unwindFrameInner(si, gpa, context) catch |err| switch (err) { |
| 112 | 112 | error.InvalidDebugInfo, |
| 113 | 113 | error.MissingDebugInfo, |
| ... | ... | @@ -546,12 +546,12 @@ const Module = struct { |
| 546 | 546 | }; |
| 547 | 547 | } |
| 548 | 548 | |
| 549 | fn getFile(module: *Module, gpa: Allocator) Error!*MachOFile { | |
| 549 | fn getFile(module: *Module, gpa: Allocator, io: Io) Error!*MachOFile { | |
| 550 | 550 | if (module.file == null) { |
| 551 | 551 | const path = std.mem.span( |
| 552 | 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 | 555 | error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo, |
| 556 | 556 | error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e, |
| 557 | 557 | }; |
| ... | ... | @@ -615,14 +615,14 @@ test { |
| 615 | 615 | } |
| 616 | 616 | |
| 617 | 617 | /// Uses `mmap` to map the file at `path` into memory. |
| 618 | fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 { | |
| 619 | const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 618 | fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 { | |
| 619 | const file = Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) { | |
| 620 | 620 | error.FileNotFound => return error.MissingDebugInfo, |
| 621 | 621 | else => return error.ReadFailed, |
| 622 | 622 | }; |
| 623 | defer file.close(); | |
| 623 | defer file.close(io); | |
| 624 | 624 | |
| 625 | const file_end_pos = file.getEndPos() catch |err| switch (err) { | |
| 625 | const file_end_pos = file.length(io) catch |err| switch (err) { | |
| 626 | 626 | error.Unexpected => |e| return e, |
| 627 | 627 | else => return error.ReadFailed, |
| 628 | 628 | }; |
lib/std/debug/SelfInfo/Windows.zig+15-15| ... | ... | @@ -149,8 +149,9 @@ pub const UnwindContext = struct { |
| 149 | 149 | return ctx.cur.getRegs().bp; |
| 150 | 150 | } |
| 151 | 151 | }; |
| 152 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | |
| 152 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize { | |
| 153 | 153 | _ = si; |
| 154 | _ = io; | |
| 154 | 155 | _ = gpa; |
| 155 | 156 | |
| 156 | 157 | const current_regs = context.cur.getRegs(); |
| ... | ... | @@ -204,14 +205,14 @@ const Module = struct { |
| 204 | 205 | coff_section_headers: []coff.SectionHeader, |
| 205 | 206 | |
| 206 | 207 | const MappedFile = struct { |
| 207 | file: fs.File, | |
| 208 | file: Io.File, | |
| 208 | 209 | section_handle: windows.HANDLE, |
| 209 | 210 | section_view: []const u8, |
| 210 | fn deinit(mf: *const MappedFile) void { | |
| 211 | fn deinit(mf: *const MappedFile, io: Io) void { | |
| 211 | 212 | const process_handle = windows.GetCurrentProcess(); |
| 212 | 213 | assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mf.section_view.ptr)) == .SUCCESS); |
| 213 | 214 | windows.CloseHandle(mf.section_handle); |
| 214 | mf.file.close(); | |
| 215 | mf.file.close(io); | |
| 215 | 216 | } |
| 216 | 217 | }; |
| 217 | 218 | |
| ... | ... | @@ -222,7 +223,7 @@ const Module = struct { |
| 222 | 223 | pdb.file_reader.file.close(io); |
| 223 | 224 | pdb.deinit(); |
| 224 | 225 | } |
| 225 | if (di.mapped_file) |*mf| mf.deinit(); | |
| 226 | if (di.mapped_file) |*mf| mf.deinit(io); | |
| 226 | 227 | |
| 227 | 228 | var arena = di.arena.promote(gpa); |
| 228 | 229 | arena.deinit(); |
| ... | ... | @@ -314,8 +315,8 @@ const Module = struct { |
| 314 | 315 | ); |
| 315 | 316 | if (len == 0) return error.MissingDebugInfo; |
| 316 | 317 | const name_w = name_buffer[0 .. len + 4 :0]; |
| 317 | var threaded: Io.Threaded = .init_single_threaded; | |
| 318 | const coff_file = threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) { | |
| 318 | // TODO eliminate the reference to Io.Threaded.global_single_threaded here | |
| 319 | const coff_file = Io.Threaded.global_single_threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) { | |
| 319 | 320 | error.Canceled => |e| return e, |
| 320 | 321 | error.Unexpected => |e| return e, |
| 321 | 322 | error.FileNotFound => return error.MissingDebugInfo, |
| ... | ... | @@ -331,7 +332,6 @@ const Module = struct { |
| 331 | 332 | error.SystemResources, |
| 332 | 333 | error.WouldBlock, |
| 333 | 334 | error.AccessDenied, |
| 334 | error.ProcessNotFound, | |
| 335 | 335 | error.PermissionDenied, |
| 336 | 336 | error.NoSpaceLeft, |
| 337 | 337 | error.DeviceBusy, |
| ... | ... | @@ -343,7 +343,7 @@ const Module = struct { |
| 343 | 343 | error.AntivirusInterference, |
| 344 | 344 | error.ProcessFdQuotaExceeded, |
| 345 | 345 | error.SystemFdQuotaExceeded, |
| 346 | error.FileLocksNotSupported, | |
| 346 | error.FileLocksUnsupported, | |
| 347 | 347 | error.FileBusy, |
| 348 | 348 | => return error.ReadFailed, |
| 349 | 349 | }; |
| ... | ... | @@ -387,12 +387,12 @@ const Module = struct { |
| 387 | 387 | const section_view = section_view_ptr.?[0..coff_len]; |
| 388 | 388 | coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo; |
| 389 | 389 | break :mapped .{ |
| 390 | .file = .adaptFromNewApi(coff_file), | |
| 390 | .file = coff_file, | |
| 391 | 391 | .section_handle = section_handle, |
| 392 | 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); | |
| 396 | 396 | |
| 397 | 397 | const coff_image_base = coff_obj.getImageBase(); |
| 398 | 398 | |
| ... | ... | @@ -432,22 +432,22 @@ const Module = struct { |
| 432 | 432 | break :pdb null; |
| 433 | 433 | }; |
| 434 | 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 | 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 | 438 | error.OutOfMemory, error.Unexpected => |e| return e, |
| 439 | 439 | else => return error.ReadFailed, |
| 440 | 440 | }; |
| 441 | 441 | defer gpa.free(self_dir); |
| 442 | 442 | const abs_path = try fs.path.join(gpa, &.{ self_dir, path }); |
| 443 | 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 | 446 | const pdb_file = pdb_file_open_result catch |err| switch (err) { |
| 447 | 447 | error.FileNotFound, error.IsDir => break :pdb null, |
| 448 | 448 | else => return error.ReadFailed, |
| 449 | 449 | }; |
| 450 | errdefer pdb_file.close(); | |
| 450 | errdefer pdb_file.close(io); | |
| 451 | 451 | |
| 452 | 452 | const pdb_reader = try arena.create(Io.File.Reader); |
| 453 | 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 | 14 | pub fn call(msg: []const u8, ra: ?usize) noreturn { |
| 15 | 15 | @branchHint(.cold); |
| 16 | 16 | _ = ra; |
| 17 | std.debug.lockStdErr(); | |
| 18 | const stderr: std.fs.File = .stderr(); | |
| 19 | stderr.writeAll(msg) catch {}; | |
| 17 | const stderr_writer = &std.debug.lockStderr(&.{}).file_writer.interface; | |
| 18 | stderr_writer.writeAll(msg) catch {}; | |
| 20 | 19 | @trap(); |
| 21 | 20 | } |
| 22 | 21 |
lib/std/dynamic_library.zig+39-37| ... | ... | @@ -1,10 +1,12 @@ |
| 1 | const std = @import("std.zig"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 2 | const native_os = builtin.os.tag; | |
| 3 | ||
| 4 | const std = @import("std.zig"); | |
| 5 | const Io = std.Io; | |
| 3 | 6 | const mem = std.mem; |
| 4 | 7 | const testing = std.testing; |
| 5 | 8 | const elf = std.elf; |
| 6 | 9 | const windows = std.os.windows; |
| 7 | const native_os = builtin.os.tag; | |
| 8 | 10 | const posix = std.posix; |
| 9 | 11 | |
| 10 | 12 | /// Cross-platform dynamic library loading and symbol lookup. |
| ... | ... | @@ -53,11 +55,11 @@ pub const DynLib = struct { |
| 53 | 55 | // An iterator is provided in order to traverse the linked list in a idiomatic |
| 54 | 56 | // fashion. |
| 55 | 57 | const LinkMap = extern struct { |
| 56 | l_addr: usize, | |
| 57 | l_name: [*:0]const u8, | |
| 58 | l_ld: ?*elf.Dyn, | |
| 59 | l_next: ?*LinkMap, | |
| 60 | l_prev: ?*LinkMap, | |
| 58 | addr: usize, | |
| 59 | name: [*:0]const u8, | |
| 60 | ld: ?*elf.Dyn, | |
| 61 | next: ?*LinkMap, | |
| 62 | prev: ?*LinkMap, | |
| 61 | 63 | |
| 62 | 64 | pub const Iterator = struct { |
| 63 | 65 | current: ?*LinkMap, |
| ... | ... | @@ -68,7 +70,7 @@ const LinkMap = extern struct { |
| 68 | 70 | |
| 69 | 71 | pub fn next(self: *Iterator) ?*LinkMap { |
| 70 | 72 | if (self.current) |it| { |
| 71 | self.current = it.l_next; | |
| 73 | self.current = it.next; | |
| 72 | 74 | return it; |
| 73 | 75 | } |
| 74 | 76 | return null; |
| ... | ... | @@ -77,10 +79,10 @@ const LinkMap = extern struct { |
| 77 | 79 | }; |
| 78 | 80 | |
| 79 | 81 | const RDebug = extern struct { |
| 80 | r_version: i32, | |
| 81 | r_map: ?*LinkMap, | |
| 82 | r_brk: usize, | |
| 83 | r_ldbase: usize, | |
| 82 | version: i32, | |
| 83 | map: ?*LinkMap, | |
| 84 | brk: usize, | |
| 85 | ldbase: usize, | |
| 84 | 86 | }; |
| 85 | 87 | |
| 86 | 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 | 107 | elf.DT_DEBUG => { |
| 106 | 108 | const ptr = @as(?*RDebug, @ptrFromInt(_DYNAMIC[i].d_val)); |
| 107 | 109 | if (ptr) |r_debug| { |
| 108 | if (r_debug.r_version != 1) return error.InvalidExe; | |
| 109 | break :init r_debug.r_map; | |
| 110 | if (r_debug.version != 1) return error.InvalidExe; | |
| 111 | break :init r_debug.map; | |
| 110 | 112 | } |
| 111 | 113 | }, |
| 112 | 114 | elf.DT_PLTGOT => { |
| ... | ... | @@ -155,24 +157,24 @@ pub const ElfDynLib = struct { |
| 155 | 157 | dt_gnu_hash: *elf.gnu_hash.Header, |
| 156 | 158 | }; |
| 157 | 159 | |
| 158 | fn openPath(path: []const u8) !std.fs.Dir { | |
| 160 | fn openPath(io: Io, path: []const u8) !Io.Dir { | |
| 159 | 161 | if (path.len == 0) return error.NotDir; |
| 160 | 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 | 164 | while (parts.next()) |part| { |
| 163 | const child = try parent.openDir(part, .{}); | |
| 164 | parent.close(); | |
| 165 | const child = try parent.openDir(io, part, .{}); | |
| 166 | parent.close(io); | |
| 165 | 167 | parent = child; |
| 166 | 168 | } |
| 167 | 169 | return parent; |
| 168 | 170 | } |
| 169 | 171 | |
| 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 | 173 | var paths = std.mem.tokenizeScalar(u8, search_path, delim); |
| 172 | 174 | while (paths.next()) |p| { |
| 173 | var dir = openPath(p) catch continue; | |
| 174 | defer dir.close(); | |
| 175 | const fd = posix.openat(dir.fd, file_name, .{ | |
| 175 | var dir = openPath(io, p) catch continue; | |
| 176 | defer dir.close(io); | |
| 177 | const fd = posix.openat(dir.handle, file_name, .{ | |
| 176 | 178 | .ACCMODE = .RDONLY, |
| 177 | 179 | .CLOEXEC = true, |
| 178 | 180 | }, 0) catch continue; |
| ... | ... | @@ -181,10 +183,10 @@ pub const ElfDynLib = struct { |
| 181 | 183 | return null; |
| 182 | 184 | } |
| 183 | 185 | |
| 184 | fn resolveFromParent(dir_path: []const u8, file_name: []const u8) ?posix.fd_t { | |
| 185 | var dir = std.fs.cwd().openDir(dir_path, .{}) catch return null; | |
| 186 | defer dir.close(); | |
| 187 | return posix.openat(dir.fd, file_name, .{ | |
| 186 | fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?posix.fd_t { | |
| 187 | var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch return null; | |
| 188 | defer dir.close(io); | |
| 189 | return posix.openat(dir.handle, file_name, .{ | |
| 188 | 190 | .ACCMODE = .RDONLY, |
| 189 | 191 | .CLOEXEC = true, |
| 190 | 192 | }, 0) catch null; |
| ... | ... | @@ -195,7 +197,7 @@ pub const ElfDynLib = struct { |
| 195 | 197 | // - DT_RPATH of the calling binary is not used as a search path |
| 196 | 198 | // - DT_RUNPATH of the calling binary is not used as a search path |
| 197 | 199 | // - /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 | 201 | // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname |
| 200 | 202 | if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| { |
| 201 | 203 | return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); |
| ... | ... | @@ -206,25 +208,27 @@ pub const ElfDynLib = struct { |
| 206 | 208 | std.os.linux.getegid() == std.os.linux.getgid()) |
| 207 | 209 | { |
| 208 | 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 | 212 | return fd; |
| 211 | 213 | } |
| 212 | 214 | } |
| 213 | 215 | } |
| 214 | 216 | |
| 215 | 217 | // Lastly the directories /lib and /usr/lib are searched (in this exact order) |
| 216 | if (resolveFromParent("/lib", path_or_name)) |fd| return fd; | |
| 217 | if (resolveFromParent("/usr/lib", path_or_name)) |fd| return fd; | |
| 218 | if (resolveFromParent(io, "/lib", path_or_name)) |fd| return fd; | |
| 219 | if (resolveFromParent(io, "/usr/lib", path_or_name)) |fd| return fd; | |
| 218 | 220 | return error.FileNotFound; |
| 219 | 221 | } |
| 220 | 222 | |
| 221 | 223 | /// Trusts the file. Malicious file will be able to execute arbitrary code. |
| 222 | 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 | 228 | defer posix.close(fd); |
| 225 | 229 | |
| 226 | const file: std.fs.File = .{ .handle = fd }; | |
| 227 | const stat = try file.stat(); | |
| 230 | const file: Io.File = .{ .handle = fd }; | |
| 231 | const stat = try file.stat(io); | |
| 228 | 232 | const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; |
| 229 | 233 | |
| 230 | 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 | 553 | } |
| 550 | 554 | |
| 551 | 555 | test "ElfDynLib" { |
| 552 | if (native_os != .linux) { | |
| 553 | return error.SkipZigTest; | |
| 554 | } | |
| 555 | ||
| 556 | if (native_os != .linux) return error.SkipZigTest; | |
| 556 | 557 | try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so")); |
| 558 | try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so")); | |
| 557 | 559 | } |
| 558 | 560 | |
| 559 | 561 | /// Separated to avoid referencing `WindowsDynLib`, because its field types may not |
lib/std/fs.zig+7-555| ... | ... | @@ -1,576 +1,28 @@ |
| 1 | 1 | //! File System. |
| 2 | const builtin = @import("builtin"); | |
| 3 | const native_os = builtin.os.tag; | |
| 4 | 2 | |
| 5 | 3 | const std = @import("std.zig"); |
| 6 | const Io = std.Io; | |
| 7 | const root = @import("root"); | |
| 8 | const mem = std.mem; | |
| 9 | const base64 = std.base64; | |
| 10 | const crypto = std.crypto; | |
| 11 | const Allocator = std.mem.Allocator; | |
| 12 | const assert = std.debug.assert; | |
| 13 | const posix = std.posix; | |
| 14 | const windows = std.os.windows; | |
| 15 | 4 | |
| 16 | const is_darwin = native_os.isDarwin(); | |
| 17 | ||
| 18 | pub const AtomicFile = @import("fs/AtomicFile.zig"); | |
| 19 | pub const Dir = @import("fs/Dir.zig"); | |
| 20 | pub const File = @import("fs/File.zig"); | |
| 5 | /// Deprecated, use `std.Io.Dir.path`. | |
| 21 | 6 | pub const path = @import("fs/path.zig"); |
| 22 | ||
| 23 | pub const has_executable_bit = switch (native_os) { | |
| 24 | .windows, .wasi => false, | |
| 25 | else => true, | |
| 26 | }; | |
| 27 | ||
| 28 | 7 | pub const wasi = @import("fs/wasi.zig"); |
| 29 | 8 | |
| 30 | // TODO audit these APIs with respect to Dir and absolute paths | |
| 31 | ||
| 32 | pub const realpath = posix.realpath; | |
| 33 | pub const realpathZ = posix.realpathZ; | |
| 34 | pub const realpathW = posix.realpathW; | |
| 35 | pub const realpathW2 = posix.realpathW2; | |
| 36 | ||
| 37 | 9 | pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; |
| 38 | 10 | pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; |
| 39 | 11 | |
| 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. | |
| 54 | pub 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. | |
| 75 | pub 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 | ||
| 93 | 12 | pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; |
| 94 | 13 | |
| 95 | 14 | /// Base64 encoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem. |
| 96 | pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null); | |
| 15 | pub const base64_encoder = std.base64.Base64Encoder.init(base64_alphabet, null); | |
| 97 | 16 | |
| 98 | 17 | /// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem. |
| 99 | pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null); | |
| 100 | ||
| 101 | /// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path` | |
| 102 | /// are absolute. See `Dir.copyFile` for a function that operates on both | |
| 103 | /// absolute and relative paths. | |
| 104 | /// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | |
| 105 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 106 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 107 | pub 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 | ||
| 118 | test 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. | |
| 126 | pub fn makeDirAbsolute(absolute_path: []const u8) !void { | |
| 127 | assert(path.isAbsolute(absolute_path)); | |
| 128 | return posix.mkdir(absolute_path, Dir.default_mode); | |
| 129 | } | |
| 130 | ||
| 131 | test makeDirAbsolute {} | |
| 132 | ||
| 133 | /// Same as `makeDirAbsolute` except the parameter is null-terminated. | |
| 134 | pub 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 | ||
| 139 | test 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. | |
| 145 | pub 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. | |
| 151 | pub 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. | |
| 160 | pub 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. | |
| 167 | pub 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` | |
| 174 | pub 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. | |
| 179 | pub 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`. | |
| 184 | pub 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 | ||
| 194 | pub 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. | |
| 207 | pub 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. | |
| 213 | pub 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. | |
| 226 | pub 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. | |
| 239 | pub 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. | |
| 252 | pub 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. | |
| 264 | pub 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. | |
| 277 | pub 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. | |
| 295 | pub 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. | |
| 308 | pub 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`. | |
| 327 | pub 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 | ||
| 337 | pub const OpenSelfExeError = Io.File.OpenSelfExeError; | |
| 338 | ||
| 339 | /// Deprecated in favor of `Io.File.openSelfExe`. | |
| 340 | pub 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 | |
| 355 | pub 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, | |
| 18 | pub const base64_decoder = std.base64.Base64Decoder.init(base64_alphabet, null); | |
| 381 | 19 | |
| 382 | /// On Windows, antivirus software is enabled by default. It can be | |
| 383 | /// disabled, but Windows Update sometimes ignores the user's preference | |
| 384 | /// and re-enables it. When enabled, antivirus software on Windows | |
| 385 | /// intercepts file system operations and makes them significantly slower | |
| 386 | /// in addition to possibly failing with this error code. | |
| 387 | AntivirusInterference, | |
| 388 | ||
| 389 | /// On Windows, the volume does not contain a recognized file system. File | |
| 390 | /// system drivers might not be loaded, or the volume may be corrupt. | |
| 391 | UnrecognizedVolume, | |
| 392 | ||
| 393 | Canceled, | |
| 394 | } || posix.SysCtlError; | |
| 395 | ||
| 396 | /// `selfExePath` except allocates the result on the heap. | |
| 397 | /// Caller owns returned memory. | |
| 398 | pub 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 | |
| 422 | pub 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. | |
| 532 | pub 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. | |
| 548 | pub 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`. | |
| 559 | pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 { | |
| 560 | // Use of max_path_bytes here is valid as the realpath function does not | |
| 561 | // have a variant that takes an arbitrary-size buffer. | |
| 562 | // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008 | |
| 563 | // NULL out parameter (GNU's canonicalize_file_name) to handle overelong | |
| 564 | // paths. musl supports passing NULL but restricts the output to PATH_MAX | |
| 565 | // anyway. | |
| 566 | var buf: [max_path_bytes]u8 = undefined; | |
| 567 | return allocator.dupe(u8, try posix.realpath(pathname, &buf)); | |
| 568 | } | |
| 20 | /// Deprecated, use `std.Io.Dir.max_path_bytes`. | |
| 21 | pub const max_path_bytes = std.Io.Dir.max_path_bytes; | |
| 22 | /// Deprecated, use `std.Io.Dir.max_name_bytes`. | |
| 23 | pub const max_name_bytes = std.Io.Dir.max_name_bytes; | |
| 569 | 24 | |
| 570 | 25 | test { |
| 571 | _ = AtomicFile; | |
| 572 | _ = Dir; | |
| 573 | _ = File; | |
| 574 | 26 | _ = path; |
| 575 | 27 | _ = @import("fs/test.zig"); |
| 576 | 28 | _ = @import("fs/get_app_data_dir.zig"); |
lib/std/fs/AtomicFile.zig deleted-94| ... | ... | @@ -1,94 +0,0 @@ |
| 1 | const AtomicFile = @This(); | |
| 2 | const std = @import("../std.zig"); | |
| 3 | const File = std.fs.File; | |
| 4 | const Dir = std.fs.Dir; | |
| 5 | const fs = std.fs; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const posix = std.posix; | |
| 8 | ||
| 9 | file_writer: File.Writer, | |
| 10 | random_integer: u64, | |
| 11 | dest_basename: []const u8, | |
| 12 | file_open: bool, | |
| 13 | file_exists: bool, | |
| 14 | close_dir_on_deinit: bool, | |
| 15 | dir: Dir, | |
| 16 | ||
| 17 | pub const InitError = File.OpenError; | |
| 18 | ||
| 19 | /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. | |
| 20 | pub 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(). | |
| 47 | pub 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 | ||
| 63 | pub const FlushError = File.WriteError; | |
| 64 | ||
| 65 | pub 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 | ||
| 71 | pub 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). | |
| 77 | pub 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 | ||
| 88 | pub const FinishError = FlushError || RenameIntoPlaceError; | |
| 89 | ||
| 90 | /// Combination of `flush` followed by `renameIntoPlace`. | |
| 91 | pub 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`. | |
| 2 | const Dir = @This(); | |
| 3 | ||
| 4 | const builtin = @import("builtin"); | |
| 5 | const native_os = builtin.os.tag; | |
| 6 | ||
| 7 | const std = @import("../std.zig"); | |
| 8 | const Io = std.Io; | |
| 9 | const File = std.fs.File; | |
| 10 | const AtomicFile = std.fs.AtomicFile; | |
| 11 | const base64_encoder = fs.base64_encoder; | |
| 12 | const posix = std.posix; | |
| 13 | const mem = std.mem; | |
| 14 | const path = fs.path; | |
| 15 | const fs = std.fs; | |
| 16 | const Allocator = std.mem.Allocator; | |
| 17 | const assert = std.debug.assert; | |
| 18 | const linux = std.os.linux; | |
| 19 | const windows = std.os.windows; | |
| 20 | const have_flock = @TypeOf(posix.system.flock) != void; | |
| 21 | ||
| 22 | fd: Handle, | |
| 23 | ||
| 24 | pub const Handle = posix.fd_t; | |
| 25 | ||
| 26 | pub const default_mode = 0o755; | |
| 27 | ||
| 28 | pub const Entry = struct { | |
| 29 | name: []const u8, | |
| 30 | kind: Kind, | |
| 31 | ||
| 32 | pub const Kind = File.Kind; | |
| 33 | }; | |
| 34 | ||
| 35 | const IteratorError = error{ | |
| 36 | AccessDenied, | |
| 37 | PermissionDenied, | |
| 38 | SystemResources, | |
| 39 | } || posix.UnexpectedError; | |
| 40 | ||
| 41 | pub 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 | ||
| 602 | pub 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). | |
| 609 | pub fn iterateAssumeFirstIteration(self: Dir) Iterator { | |
| 610 | return self.iterateImpl(false); | |
| 611 | } | |
| 612 | ||
| 613 | fn 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 | ||
| 669 | pub 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`. | |
| 769 | pub 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 | ||
| 784 | pub 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`. | |
| 843 | pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { | |
| 844 | return .{ | |
| 845 | .inner = try walkSelectively(self, allocator), | |
| 846 | }; | |
| 847 | } | |
| 848 | ||
| 849 | pub const OpenError = Io.Dir.OpenError; | |
| 850 | ||
| 851 | pub fn close(self: *Dir) void { | |
| 852 | posix.close(self.fd); | |
| 853 | self.* = undefined; | |
| 854 | } | |
| 855 | ||
| 856 | /// Deprecated in favor of `Io.Dir.openFile`. | |
| 857 | pub 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`. | |
| 864 | pub 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`. | |
| 872 | pub const MakeError = Io.Dir.MakeError; | |
| 873 | ||
| 874 | /// Deprecated in favor of `Io.Dir.makeDir`. | |
| 875 | pub 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`. | |
| 882 | pub 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`. | |
| 887 | pub 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`. | |
| 892 | pub 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`. | |
| 897 | pub const MakePathStatus = Io.Dir.MakePathStatus; | |
| 898 | /// Deprecated in favor of `Io.Dir.MakePathError`. | |
| 899 | pub const MakePathError = Io.Dir.MakePathError; | |
| 900 | ||
| 901 | /// Deprecated in favor of `Io.Dir.makePathStatus`. | |
| 902 | pub 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`. | |
| 909 | pub 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 | ||
| 915 | pub 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`. | |
| 928 | pub 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`. | |
| 950 | pub 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`. | |
| 994 | pub 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`. | |
| 1013 | pub 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 | ||
| 1036 | pub const RealPathAllocError = RealPathError || Allocator.Error; | |
| 1037 | ||
| 1038 | /// Same as `Dir.realpath` except caller must free the returned memory. | |
| 1039 | /// See also `Dir.realpath`. | |
| 1040 | pub 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. | |
| 1058 | pub 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`. | |
| 1074 | pub const OpenOptions = Io.Dir.OpenOptions; | |
| 1075 | ||
| 1076 | /// Deprecated in favor of `Io.Dir.openDir`. | |
| 1077 | pub 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 | ||
| 1083 | pub 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. | |
| 1090 | pub 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. | |
| 1106 | pub 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. | |
| 1125 | pub 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 | ||
| 1132 | pub 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. | |
| 1160 | pub 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. | |
| 1176 | pub 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. | |
| 1185 | pub 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 | ||
| 1192 | pub 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. | |
| 1201 | pub 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. | |
| 1206 | pub 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. | |
| 1212 | pub 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. | |
| 1221 | pub 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. | |
| 1232 | pub 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. | |
| 1267 | pub 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. | |
| 1277 | pub 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. | |
| 1293 | pub 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. | |
| 1312 | pub 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 | ||
| 1350 | pub 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. | |
| 1358 | pub 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. | |
| 1377 | pub 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. | |
| 1382 | pub 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. | |
| 1401 | pub 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`. | |
| 1406 | pub 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 | ||
| 1412 | pub 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. | |
| 1426 | pub 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 | |
| 1447 | pub 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 | ||
| 1472 | pub 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. | |
| 1509 | pub 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. | |
| 1708 | pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void { | |
| 1709 | return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file); | |
| 1710 | } | |
| 1711 | ||
| 1712 | fn 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. | |
| 1827 | fn 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 | ||
| 1892 | pub const WriteFileError = File.WriteError || File.OpenError; | |
| 1893 | ||
| 1894 | pub 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. | |
| 1904 | pub 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`. | |
| 1911 | pub const AccessError = Io.Dir.AccessError; | |
| 1912 | ||
| 1913 | /// Deprecated in favor of `Io.Dir.access`. | |
| 1914 | pub 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 | ||
| 1920 | pub const CopyFileOptions = struct { | |
| 1921 | /// When this is `null` the mode is copied from the source file. | |
| 1922 | override_mode: ?File.Mode = null, | |
| 1923 | }; | |
| 1924 | ||
| 1925 | pub 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 | |
| 1944 | pub 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 | ||
| 1979 | pub 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. | |
| 1993 | pub 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 | ||
| 2006 | pub const Stat = File.Stat; | |
| 2007 | pub const StatError = File.StatError; | |
| 2008 | ||
| 2009 | /// Deprecated in favor of `Io.Dir.stat`. | |
| 2010 | pub fn stat(self: Dir) StatError!Stat { | |
| 2011 | const file: File = .{ .handle = self.fd }; | |
| 2012 | return file.stat(); | |
| 2013 | } | |
| 2014 | ||
| 2015 | pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError; | |
| 2016 | ||
| 2017 | /// Deprecated in favor of `Io.Dir.statPath`. | |
| 2018 | pub 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 | ||
| 2024 | pub 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 }`. | |
| 2031 | pub 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. | |
| 2042 | pub 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 | ||
| 2047 | pub const ChownError = File.ChownError; | |
| 2048 | ||
| 2049 | const Permissions = File.Permissions; | |
| 2050 | pub const SetPermissionsError = File.SetPermissionsError; | |
| 2051 | ||
| 2052 | /// Sets permissions according to the provided `Permissions` struct. | |
| 2053 | /// This method is *NOT* available on WASI | |
| 2054 | pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void { | |
| 2055 | const file: File = .{ .handle = self.fd }; | |
| 2056 | try file.setPermissions(permissions); | |
| 2057 | } | |
| 2058 | ||
| 2059 | pub fn adaptToNewApi(dir: Dir) Io.Dir { | |
| 2060 | return .{ .handle = dir.fd }; | |
| 2061 | } | |
| 2062 | ||
| 2063 | pub fn adaptFromNewApi(dir: Io.Dir) Dir { | |
| 2064 | return .{ .fd = dir.handle }; | |
| 2065 | } |
lib/std/fs/File.zig deleted-1437| ... | ... | @@ -1,1437 +0,0 @@ |
| 1 | const File = @This(); | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | const native_os = builtin.os.tag; | |
| 5 | const is_windows = native_os == .windows; | |
| 6 | ||
| 7 | const std = @import("../std.zig"); | |
| 8 | const Io = std.Io; | |
| 9 | const Os = std.builtin.Os; | |
| 10 | const Allocator = std.mem.Allocator; | |
| 11 | const posix = std.posix; | |
| 12 | const math = std.math; | |
| 13 | const assert = std.debug.assert; | |
| 14 | const linux = std.os.linux; | |
| 15 | const windows = std.os.windows; | |
| 16 | const maxInt = std.math.maxInt; | |
| 17 | const Alignment = std.mem.Alignment; | |
| 18 | ||
| 19 | /// The OS-specific file descriptor or file handle. | |
| 20 | handle: Handle, | |
| 21 | ||
| 22 | pub const Handle = Io.File.Handle; | |
| 23 | pub const Mode = Io.File.Mode; | |
| 24 | pub const INode = Io.File.INode; | |
| 25 | pub const Uid = posix.uid_t; | |
| 26 | pub const Gid = posix.gid_t; | |
| 27 | pub 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. | |
| 35 | pub const default_mode: Mode = if (Mode == u0) 0 else 0o666; | |
| 36 | ||
| 37 | /// Deprecated in favor of `Io.File.OpenError`. | |
| 38 | pub const OpenError = Io.File.OpenError || error{WouldBlock}; | |
| 39 | /// Deprecated in favor of `Io.File.OpenMode`. | |
| 40 | pub const OpenMode = Io.File.OpenMode; | |
| 41 | /// Deprecated in favor of `Io.File.Lock`. | |
| 42 | pub const Lock = Io.File.Lock; | |
| 43 | /// Deprecated in favor of `Io.File.OpenFlags`. | |
| 44 | pub const OpenFlags = Io.File.OpenFlags; | |
| 45 | ||
| 46 | pub 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 | ||
| 94 | pub fn stdout() File { | |
| 95 | return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdOutput else posix.STDOUT_FILENO }; | |
| 96 | } | |
| 97 | ||
| 98 | pub fn stderr() File { | |
| 99 | return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdError else posix.STDERR_FILENO }; | |
| 100 | } | |
| 101 | ||
| 102 | pub 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. | |
| 108 | pub fn close(self: File) void { | |
| 109 | if (is_windows) { | |
| 110 | windows.CloseHandle(self.handle); | |
| 111 | } else { | |
| 112 | posix.close(self.handle); | |
| 113 | } | |
| 114 | } | |
| 115 | ||
| 116 | pub 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. | |
| 123 | pub 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`. | |
| 129 | pub fn isTty(self: File) bool { | |
| 130 | return posix.isatty(self.handle); | |
| 131 | } | |
| 132 | ||
| 133 | pub 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`. | |
| 194 | pub 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`. | |
| 227 | pub 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 | ||
| 254 | pub const SetEndPosError = posix.TruncateError; | |
| 255 | ||
| 256 | /// Shrinks or expands the file. | |
| 257 | /// The file offset after this call is left unchanged. | |
| 258 | pub fn setEndPos(self: File, length: u64) SetEndPosError!void { | |
| 259 | try posix.ftruncate(self.handle, length); | |
| 260 | } | |
| 261 | ||
| 262 | pub const SeekError = posix.SeekError; | |
| 263 | ||
| 264 | /// Repositions read/write file offset relative to the current offset. | |
| 265 | /// TODO: integrate with async I/O | |
| 266 | pub 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 | |
| 272 | pub 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 | |
| 278 | pub fn seekTo(self: File, offset: u64) SeekError!void { | |
| 279 | return posix.lseek_SET(self.handle, offset); | |
| 280 | } | |
| 281 | ||
| 282 | pub const GetSeekPosError = posix.SeekError || StatError; | |
| 283 | ||
| 284 | /// TODO: integrate with async I/O | |
| 285 | pub fn getPos(self: File) GetSeekPosError!u64 { | |
| 286 | return posix.lseek_CUR_get(self.handle); | |
| 287 | } | |
| 288 | ||
| 289 | pub const GetEndPosError = std.os.windows.GetFileSizeError || StatError; | |
| 290 | ||
| 291 | /// TODO: integrate with async I/O | |
| 292 | pub 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 | ||
| 299 | pub const ModeError = StatError; | |
| 300 | ||
| 301 | /// TODO: integrate with async I/O | |
| 302 | pub fn mode(self: File) ModeError!Mode { | |
| 303 | if (builtin.os.tag == .windows) { | |
| 304 | return 0; | |
| 305 | } | |
| 306 | return (try self.stat()).mode; | |
| 307 | } | |
| 308 | ||
| 309 | pub const Stat = Io.File.Stat; | |
| 310 | ||
| 311 | pub const StatError = posix.FStatError; | |
| 312 | ||
| 313 | /// Returns `Stat` containing basic information about the `File`. | |
| 314 | pub 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 | ||
| 320 | pub 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. | |
| 326 | pub fn chmod(self: File, new_mode: Mode) ChmodError!void { | |
| 327 | try posix.fchmod(self.handle, new_mode); | |
| 328 | } | |
| 329 | ||
| 330 | pub 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. | |
| 337 | pub 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. | |
| 344 | pub 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 | ||
| 367 | pub 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 | ||
| 388 | pub 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 | ||
| 468 | pub const SetPermissionsError = ChmodError; | |
| 469 | ||
| 470 | /// Sets permissions according to the provided `Permissions` struct. | |
| 471 | /// This method is *NOT* available on WASI | |
| 472 | pub 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 | ||
| 504 | pub 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 | |
| 511 | pub 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 | ||
| 536 | pub const ReadError = posix.ReadError; | |
| 537 | pub const PReadError = posix.PReadError; | |
| 538 | ||
| 539 | pub 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 | |
| 549 | pub 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`. | |
| 558 | pub 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 | |
| 569 | pub 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 | |
| 582 | pub 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 | ||
| 592 | pub const WriteError = posix.WriteError; | |
| 593 | pub const PWriteError = posix.PWriteError; | |
| 594 | ||
| 595 | pub 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 | ||
| 603 | pub 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`. | |
| 611 | pub 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 | |
| 620 | pub 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 | |
| 629 | pub 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 | |
| 643 | pub 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`. | |
| 654 | pub const CopyRangeError = posix.CopyFileRangeError; | |
| 655 | ||
| 656 | /// Deprecated in favor of `Writer`. | |
| 657 | pub 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`. | |
| 664 | pub 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`. | |
| 679 | pub const Reader = Io.File.Reader; | |
| 680 | ||
| 681 | pub 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. | |
| 1242 | pub 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. | |
| 1249 | pub 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. | |
| 1257 | pub 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. | |
| 1264 | pub fn writerStreaming(file: File, buffer: []u8) Writer { | |
| 1265 | return .initStreaming(file, buffer); | |
| 1266 | } | |
| 1267 | ||
| 1268 | const range_off: windows.LARGE_INTEGER = 0; | |
| 1269 | const range_len: windows.LARGE_INTEGER = 1; | |
| 1270 | ||
| 1271 | pub 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 | |
| 1283 | pub 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. | |
| 1319 | pub 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 | |
| 1350 | pub 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 | |
| 1390 | pub 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 | ||
| 1431 | pub fn adaptToNewApi(file: File) Io.File { | |
| 1432 | return .{ .handle = file.handle }; | |
| 1433 | } | |
| 1434 | ||
| 1435 | pub 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 | 872 | |
| 873 | 873 | /// This function is like a series of `cd` statements executed one after another. |
| 874 | 874 | /// 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 | 876 | /// ".." components may persist in the resolved path if the resolved path is relative or drive-relative. |
| 877 | 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 | 1095 | |
| 1096 | 1096 | /// This function is like a series of `cd` statements executed one after another. |
| 1097 | 1097 | /// 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 | 1099 | /// ".." components may persist in the resolved path if the resolved path is relative. |
| 1100 | 1100 | /// The result does not have a trailing path separator. |
| 1101 | 1101 | /// 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 | 3 | |
| 4 | 4 | const std = @import("../std.zig"); |
| 5 | 5 | const Io = std.Io; |
| 6 | const testing = std.testing; | |
| 7 | const fs = std.fs; | |
| 8 | 6 | const mem = std.mem; |
| 7 | const Allocator = std.mem.Allocator; | |
| 9 | 8 | const wasi = std.os.wasi; |
| 10 | 9 | const windows = std.os.windows; |
| 11 | const posix = std.posix; | |
| 12 | ||
| 13 | 10 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 14 | const Dir = std.fs.Dir; | |
| 15 | const File = std.fs.File; | |
| 16 | const tmpDir = testing.tmpDir; | |
| 17 | const SymLinkFlags = std.fs.Dir.SymLinkFlags; | |
| 11 | const Dir = std.Io.Dir; | |
| 12 | const File = std.Io.File; | |
| 13 | const SymLinkFlags = std.Io.Dir.SymLinkFlags; | |
| 14 | ||
| 15 | const testing = std.testing; | |
| 16 | const expect = std.testing.expect; | |
| 17 | const expectEqual = std.testing.expectEqual; | |
| 18 | const expectEqualSlices = std.testing.expectEqualSlices; | |
| 19 | const expectEqualStrings = std.testing.expectEqualStrings; | |
| 20 | const expectError = std.testing.expectError; | |
| 21 | const tmpDir = std.testing.tmpDir; | |
| 18 | 22 | |
| 19 | 23 | const PathType = enum { |
| 20 | 24 | relative, |
| 21 | 25 | absolute, |
| 22 | 26 | unc, |
| 23 | 27 | |
| 24 | pub fn isSupported(self: PathType, target_os: std.Target.Os) bool { | |
| 28 | fn isSupported(self: PathType, target_os: std.Target.Os) bool { | |
| 25 | 29 | return switch (self) { |
| 26 | 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 | 50 | .unc => target_os.tag == .windows, |
| 29 | 51 | }; |
| 30 | 52 | } |
| 31 | 53 | |
| 32 | pub const TransformError = posix.RealPathError || error{OutOfMemory}; | |
| 33 | pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8; | |
| 54 | const TransformError = Dir.RealPathError || error{OutOfMemory}; | |
| 55 | const TransformFn = fn (Allocator, Io, Dir, relative_path: [:0]const u8) TransformError![:0]const u8; | |
| 34 | 56 | |
| 35 | pub fn getTransformFn(comptime path_type: PathType) TransformFn { | |
| 57 | fn getTransformFn(comptime path_type: PathType) TransformFn { | |
| 36 | 58 | switch (path_type) { |
| 37 | 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 | 61 | _ = allocator; |
| 62 | _ = io; | |
| 40 | 63 | _ = dir; |
| 41 | 64 | return relative_path; |
| 42 | 65 | } |
| 43 | 66 | }.transform, |
| 44 | 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 | 69 | // The final path may not actually exist which would cause realpath to fail. |
| 47 | 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; | |
| 49 | const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf); | |
| 50 | return fs.path.joinZ(allocator, &.{ dir_path, relative_path }); | |
| 71 | var fd_path_buf: [Dir.max_path_bytes]u8 = undefined; | |
| 72 | const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)]; | |
| 73 | return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }); | |
| 51 | 74 | } |
| 52 | 75 | }.transform, |
| 53 | 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 | 78 | // Any drive absolute path (C:\foo) can be converted into a UNC path by |
| 56 | 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; | |
| 58 | const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf); | |
| 80 | var fd_path_buf: [Dir.max_path_bytes]u8 = undefined; | |
| 81 | const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)]; | |
| 59 | 82 | const windows_path_type = windows.getWin32PathType(u8, dir_path); |
| 60 | 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 | 85 | .drive_absolute => { |
| 63 | 86 | // `C:\<...>` -> `\\127.0.0.1\C$\<...>` |
| 64 | 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 | 89 | path[prepended.len + 1] = '$'; |
| 67 | 90 | return path; |
| 68 | 91 | }, |
| ... | ... | @@ -80,10 +103,10 @@ const TestContext = struct { |
| 80 | 103 | path_sep: u8, |
| 81 | 104 | arena: ArenaAllocator, |
| 82 | 105 | tmp: testing.TmpDir, |
| 83 | dir: std.fs.Dir, | |
| 106 | dir: Dir, | |
| 84 | 107 | transform_fn: *const PathType.TransformFn, |
| 85 | 108 | |
| 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 | 110 | const tmp = tmpDir(.{ .iterate = true }); |
| 88 | 111 | return .{ |
| 89 | 112 | .io = testing.io, |
| ... | ... | @@ -107,7 +130,7 @@ const TestContext = struct { |
| 107 | 130 | /// `TestContext.deinit`. |
| 108 | 131 | pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 { |
| 109 | 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 | 134 | if (native_os == .windows) { |
| 112 | 135 | const transformed_sep_path = try allocator.dupeZ(u8, transformed_path); |
| 113 | 136 | std.mem.replaceScalar(u8, transformed_sep_path, switch (self.path_sep) { |
| ... | ... | @@ -150,7 +173,7 @@ fn testWithAllSupportedPathTypes(test_func: anytype) !void { |
| 150 | 173 | |
| 151 | 174 | fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep: u8, test_func: anytype) !void { |
| 152 | 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; | |
| 154 | 177 | |
| 155 | 178 | var ctx = TestContext.init(path_type, path_sep, testing.allocator, path_type.getTransformFn()); |
| 156 | 179 | defer ctx.deinit(); |
| ... | ... | @@ -160,8 +183,8 @@ fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep: |
| 160 | 183 | |
| 161 | 184 | // For use in test setup. If the symlink creation fails on Windows with |
| 162 | 185 | // AccessDenied, then make the test failure silent (it is not a Zig failure). |
| 163 | fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void { | |
| 164 | return dir.symLink(target, link, flags) catch |err| switch (err) { | |
| 186 | fn setupSymlink(io: Io, dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void { | |
| 187 | return dir.symLink(io, target, link, flags) catch |err| switch (err) { | |
| 165 | 188 | // Symlink requires admin privileges on windows, so this test can legitimately fail. |
| 166 | 189 | error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err, |
| 167 | 190 | else => return err, |
| ... | ... | @@ -170,50 +193,43 @@ fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFl |
| 170 | 193 | |
| 171 | 194 | // For use in test setup. If the symlink creation fails on Windows with |
| 172 | 195 | // AccessDenied, then make the test failure silent (it is not a Zig failure). |
| 173 | fn setupSymlinkAbsolute(target: []const u8, link: []const u8, flags: SymLinkFlags) !void { | |
| 174 | return fs.symLinkAbsolute(target, link, flags) catch |err| switch (err) { | |
| 196 | fn setupSymlinkAbsolute(io: Io, target: []const u8, link: []const u8, flags: SymLinkFlags) !void { | |
| 197 | return Dir.symLinkAbsolute(io, target, link, flags) catch |err| switch (err) { | |
| 175 | 198 | error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err, |
| 176 | 199 | else => return err, |
| 177 | 200 | }; |
| 178 | 201 | } |
| 179 | 202 | |
| 180 | 203 | test "Dir.readLink" { |
| 204 | const io = testing.io; | |
| 205 | ||
| 181 | 206 | try testWithAllSupportedPathTypes(struct { |
| 182 | 207 | fn impl(ctx: *TestContext) !void { |
| 183 | 208 | // Create some targets |
| 184 | 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 | 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); | |
| 188 | 213 | |
| 189 | 214 | // On Windows, symlink targets always use the canonical path separator |
| 190 | 215 | const canonical_file_target_path = try ctx.toCanonicalPathSep(file_target_path); |
| 191 | 216 | const canonical_dir_target_path = try ctx.toCanonicalPathSep(dir_target_path); |
| 192 | 217 | |
| 193 | 218 | // test 1: symlink to a file |
| 194 | try setupSymlink(ctx.dir, file_target_path, "symlink1", .{}); | |
| 195 | try testReadLink(ctx.dir, canonical_file_target_path, "symlink1"); | |
| 196 | if (builtin.os.tag == .windows) { | |
| 197 | try testReadLinkW(testing.allocator, ctx.dir, canonical_file_target_path, "symlink1"); | |
| 198 | } | |
| 219 | try setupSymlink(io, ctx.dir, file_target_path, "symlink1", .{}); | |
| 220 | try testReadLink(io, ctx.dir, canonical_file_target_path, "symlink1"); | |
| 199 | 221 | |
| 200 | 222 | // test 2: symlink to a directory (can be different on Windows) |
| 201 | try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true }); | |
| 202 | try testReadLink(ctx.dir, canonical_dir_target_path, "symlink2"); | |
| 203 | if (builtin.os.tag == .windows) { | |
| 204 | try testReadLinkW(testing.allocator, ctx.dir, canonical_dir_target_path, "symlink2"); | |
| 205 | } | |
| 223 | try setupSymlink(io, ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true }); | |
| 224 | try testReadLink(io, ctx.dir, canonical_dir_target_path, "symlink2"); | |
| 206 | 225 | |
| 207 | 226 | // 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 | 228 | const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file); |
| 210 | var subdir = try ctx.dir.makeOpenPath("subdir", .{}); | |
| 211 | defer subdir.close(); | |
| 212 | try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{}); | |
| 213 | try testReadLink(subdir, canonical_parent_file, "relative-link.txt"); | |
| 214 | if (builtin.os.tag == .windows) { | |
| 215 | try testReadLinkW(testing.allocator, subdir, canonical_parent_file, "relative-link.txt"); | |
| 216 | } | |
| 229 | var subdir = try ctx.dir.createDirPathOpen(io, "subdir", .{}); | |
| 230 | defer subdir.close(io); | |
| 231 | try setupSymlink(io, subdir, canonical_parent_file, "relative-link.txt", .{}); | |
| 232 | try testReadLink(io, subdir, canonical_parent_file, "relative-link.txt"); | |
| 217 | 233 | } |
| 218 | 234 | }.impl); |
| 219 | 235 | } |
| ... | ... | @@ -221,55 +237,39 @@ test "Dir.readLink" { |
| 221 | 237 | test "Dir.readLink on non-symlinks" { |
| 222 | 238 | try testWithAllSupportedPathTypes(struct { |
| 223 | 239 | fn impl(ctx: *TestContext) !void { |
| 240 | const io = ctx.io; | |
| 224 | 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 | 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); | |
| 228 | 245 | |
| 229 | 246 | // file |
| 230 | var buffer: [fs.max_path_bytes]u8 = undefined; | |
| 231 | try std.testing.expectError(error.NotLink, ctx.dir.readLink(file_path, &buffer)); | |
| 232 | if (builtin.os.tag == .windows) { | |
| 233 | var file_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.fd, file_path); | |
| 234 | try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(file_path_w.span(), &file_path_w.data)); | |
| 235 | } | |
| 247 | var buffer: [Dir.max_path_bytes]u8 = undefined; | |
| 248 | try std.testing.expectError(error.NotLink, ctx.dir.readLink(io, file_path, &buffer)); | |
| 236 | 249 | |
| 237 | 250 | // dir |
| 238 | try std.testing.expectError(error.NotLink, ctx.dir.readLink(dir_path, &buffer)); | |
| 239 | if (builtin.os.tag == .windows) { | |
| 240 | var dir_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.fd, dir_path); | |
| 241 | try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(dir_path_w.span(), &dir_path_w.data)); | |
| 242 | } | |
| 251 | try std.testing.expectError(error.NotLink, ctx.dir.readLink(io, dir_path, &buffer)); | |
| 243 | 252 | } |
| 244 | 253 | }.impl); |
| 245 | 254 | } |
| 246 | 255 | |
| 247 | fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { | |
| 248 | var buffer: [fs.max_path_bytes]u8 = undefined; | |
| 249 | const actual = try dir.readLink(symlink_path, buffer[0..]); | |
| 250 | try testing.expectEqualStrings(target_path, actual); | |
| 251 | } | |
| 252 | ||
| 253 | fn 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); | |
| 256 | fn testReadLink(io: Io, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { | |
| 257 | var buffer: [Dir.max_path_bytes]u8 = undefined; | |
| 258 | const actual = buffer[0..try dir.readLink(io, symlink_path, &buffer)]; | |
| 259 | try expectEqualStrings(target_path, actual); | |
| 262 | 260 | } |
| 263 | 261 | |
| 264 | fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { | |
| 265 | var buffer: [fs.max_path_bytes]u8 = undefined; | |
| 266 | const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]); | |
| 267 | try testing.expectEqualStrings(target_path, given); | |
| 262 | fn testReadLinkAbsolute(io: Io, target_path: []const u8, symlink_path: []const u8) !void { | |
| 263 | var buffer: [Dir.max_path_bytes]u8 = undefined; | |
| 264 | const given = buffer[0..try Dir.readLinkAbsolute(io, symlink_path, &buffer)]; | |
| 265 | try expectEqualStrings(target_path, given); | |
| 268 | 266 | } |
| 269 | 267 | |
| 270 | 268 | test "File.stat on a File that is a symlink returns Kind.sym_link" { |
| 271 | // This test requires getting a file descriptor of a symlink which | |
| 272 | // is not possible on all targets | |
| 269 | const io = testing.io; | |
| 270 | ||
| 271 | // This test requires getting a file descriptor of a symlink which is not | |
| 272 | // possible on all targets. | |
| 273 | 273 | switch (builtin.target.os.tag) { |
| 274 | 274 | .windows, .linux => {}, |
| 275 | 275 | else => return error.SkipZigTest, |
| ... | ... | @@ -278,99 +278,35 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { |
| 278 | 278 | try testWithAllSupportedPathTypes(struct { |
| 279 | 279 | fn impl(ctx: *TestContext) !void { |
| 280 | 280 | const dir_target_path = try ctx.transformPath("subdir"); |
| 281 | try ctx.dir.makeDir(dir_target_path); | |
| 282 | ||
| 283 | try setupSymlink(ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); | |
| 284 | ||
| 285 | var symlink: Dir = switch (builtin.target.os.tag) { | |
| 286 | .windows => windows_symlink: { | |
| 287 | const sub_path_w = try windows.cStrToPrefixedFileW(ctx.dir.fd, "symlink"); | |
| 288 | ||
| 289 | var handle: windows.HANDLE = undefined; | |
| 290 | ||
| 291 | const path_len_bytes = @as(u16, @intCast(sub_path_w.span().len * 2)); | |
| 292 | var nt_name = windows.UNICODE_STRING{ | |
| 293 | .Length = path_len_bytes, | |
| 294 | .MaximumLength = path_len_bytes, | |
| 295 | .Buffer = @constCast(&sub_path_w.data), | |
| 296 | }; | |
| 297 | var attr: windows.OBJECT_ATTRIBUTES = .{ | |
| 298 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | |
| 299 | .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else ctx.dir.fd, | |
| 300 | .Attributes = .{}, | |
| 301 | .ObjectName = &nt_name, | |
| 302 | .SecurityDescriptor = null, | |
| 303 | .SecurityQualityOfService = null, | |
| 304 | }; | |
| 305 | var io: windows.IO_STATUS_BLOCK = undefined; | |
| 306 | const rc = windows.ntdll.NtCreateFile( | |
| 307 | &handle, | |
| 308 | .{ | |
| 309 | .SPECIFIC = .{ .FILE_DIRECTORY = .{ | |
| 310 | .READ_EA = true, | |
| 311 | .TRAVERSE = true, | |
| 312 | .READ_ATTRIBUTES = true, | |
| 313 | } }, | |
| 314 | .STANDARD = .{ | |
| 315 | .RIGHTS = .READ, | |
| 316 | .SYNCHRONIZE = true, | |
| 317 | }, | |
| 318 | }, | |
| 319 | &attr, | |
| 320 | &io, | |
| 321 | null, | |
| 322 | .{ .NORMAL = true }, | |
| 323 | .VALID_FLAGS, | |
| 324 | .OPEN, | |
| 325 | .{ | |
| 326 | .DIRECTORY_FILE = true, | |
| 327 | .IO = .SYNCHRONOUS_NONALERT, | |
| 328 | .OPEN_FOR_BACKUP_INTENT = true, | |
| 329 | .OPEN_REPARSE_POINT = true, // the important thing here | |
| 330 | }, | |
| 331 | null, | |
| 332 | 0, | |
| 333 | ); | |
| 281 | try ctx.dir.createDir(io, dir_target_path, .default_dir); | |
| 334 | 282 | |
| 335 | switch (rc) { | |
| 336 | .SUCCESS => break :windows_symlink .{ .fd = handle }, | |
| 337 | else => return windows.unexpectedStatus(rc), | |
| 338 | } | |
| 339 | }, | |
| 340 | .linux => linux_symlink: { | |
| 341 | const sub_path_c = try posix.toPosixPath("symlink"); | |
| 342 | // the O_NOFOLLOW | O_PATH combination can obtain a fd to a symlink | |
| 343 | // note that if O_DIRECTORY is set, then this will error with ENOTDIR | |
| 344 | const flags: posix.O = .{ | |
| 345 | .NOFOLLOW = true, | |
| 346 | .PATH = true, | |
| 347 | .ACCMODE = .RDONLY, | |
| 348 | .CLOEXEC = true, | |
| 349 | }; | |
| 350 | const fd = try posix.openatZ(ctx.dir.fd, &sub_path_c, flags, 0); | |
| 351 | break :linux_symlink Dir{ .fd = fd }; | |
| 352 | }, | |
| 353 | else => unreachable, | |
| 354 | }; | |
| 355 | defer symlink.close(); | |
| 283 | try setupSymlink(io, ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); | |
| 284 | ||
| 285 | var symlink: File = try ctx.dir.openFile(io, "symlink", .{ | |
| 286 | .follow_symlinks = false, | |
| 287 | .path_only = true, | |
| 288 | }); | |
| 289 | defer symlink.close(io); | |
| 356 | 290 | |
| 357 | const stat = try symlink.stat(); | |
| 358 | try testing.expectEqual(File.Kind.sym_link, stat.kind); | |
| 291 | const stat = try symlink.stat(io); | |
| 292 | try expectEqual(File.Kind.sym_link, stat.kind); | |
| 359 | 293 | } |
| 360 | 294 | }.impl); |
| 361 | 295 | } |
| 362 | 296 | |
| 363 | 297 | test "openDir" { |
| 298 | const io = testing.io; | |
| 299 | ||
| 364 | 300 | try testWithAllSupportedPathTypes(struct { |
| 365 | 301 | fn impl(ctx: *TestContext) !void { |
| 366 | 302 | const allocator = ctx.arena.allocator(); |
| 367 | 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); | |
| 369 | 305 | |
| 370 | 306 | for ([_][]const u8{ "", ".", ".." }) |sub_path| { |
| 371 | const dir_path = try fs.path.join(allocator, &.{ subdir_path, sub_path }); | |
| 372 | var dir = try ctx.dir.openDir(dir_path, .{}); | |
| 373 | defer dir.close(); | |
| 307 | const dir_path = try Dir.path.join(allocator, &.{ subdir_path, sub_path }); | |
| 308 | var dir = try ctx.dir.openDir(io, dir_path, .{}); | |
| 309 | defer dir.close(io); | |
| 374 | 310 | } |
| 375 | 311 | } |
| 376 | 312 | }.impl); |
| ... | ... | @@ -380,79 +316,87 @@ test "accessAbsolute" { |
| 380 | 316 | if (native_os == .wasi) return error.SkipZigTest; |
| 381 | 317 | if (native_os == .openbsd) return error.SkipZigTest; |
| 382 | 318 | |
| 319 | const io = testing.io; | |
| 320 | const gpa = testing.allocator; | |
| 321 | ||
| 383 | 322 | var tmp = tmpDir(.{}); |
| 384 | 323 | defer tmp.cleanup(); |
| 385 | 324 | |
| 386 | const base_path = try tmp.dir.realpathAlloc(testing.allocator, "."); | |
| 387 | defer testing.allocator.free(base_path); | |
| 325 | const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa); | |
| 326 | defer gpa.free(base_path); | |
| 388 | 327 | |
| 389 | try fs.accessAbsolute(base_path, .{}); | |
| 328 | try Dir.accessAbsolute(io, base_path, .{}); | |
| 390 | 329 | } |
| 391 | 330 | |
| 392 | 331 | test "openDirAbsolute" { |
| 393 | 332 | if (native_os == .wasi) return error.SkipZigTest; |
| 394 | 333 | if (native_os == .openbsd) return error.SkipZigTest; |
| 395 | 334 | |
| 335 | const io = testing.io; | |
| 336 | const gpa = testing.allocator; | |
| 337 | ||
| 396 | 338 | var tmp = tmpDir(.{}); |
| 397 | 339 | defer tmp.cleanup(); |
| 398 | 340 | |
| 399 | const tmp_ino = (try tmp.dir.stat()).inode; | |
| 341 | const tmp_ino = (try tmp.dir.stat(io)).inode; | |
| 400 | 342 | |
| 401 | try tmp.dir.makeDir("subdir"); | |
| 402 | const sub_path = try tmp.dir.realpathAlloc(testing.allocator, "subdir"); | |
| 403 | defer testing.allocator.free(sub_path); | |
| 343 | try tmp.dir.createDir(io, "subdir", .default_dir); | |
| 344 | const sub_path = try tmp.dir.realPathFileAlloc(io, "subdir", gpa); | |
| 345 | defer gpa.free(sub_path); | |
| 404 | 346 | |
| 405 | 347 | // Can open sub_path |
| 406 | var tmp_sub = try fs.openDirAbsolute(sub_path, .{}); | |
| 407 | defer tmp_sub.close(); | |
| 348 | var tmp_sub = try Dir.openDirAbsolute(io, sub_path, .{}); | |
| 349 | defer tmp_sub.close(io); | |
| 408 | 350 | |
| 409 | const sub_ino = (try tmp_sub.stat()).inode; | |
| 351 | const sub_ino = (try tmp_sub.stat(io)).inode; | |
| 410 | 352 | |
| 411 | 353 | { |
| 412 | 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 | 356 | defer testing.allocator.free(dir_path); |
| 415 | 357 | |
| 416 | var dir = try fs.openDirAbsolute(dir_path, .{}); | |
| 417 | defer dir.close(); | |
| 358 | var dir = try Dir.openDirAbsolute(io, dir_path, .{}); | |
| 359 | defer dir.close(io); | |
| 418 | 360 | |
| 419 | const ino = (try dir.stat()).inode; | |
| 420 | try testing.expectEqual(tmp_ino, ino); | |
| 361 | const ino = (try dir.stat(io)).inode; | |
| 362 | try expectEqual(tmp_ino, ino); | |
| 421 | 363 | } |
| 422 | 364 | |
| 423 | 365 | { |
| 424 | 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 | 368 | defer testing.allocator.free(dir_path); |
| 427 | 369 | |
| 428 | var dir = try fs.openDirAbsolute(dir_path, .{}); | |
| 429 | defer dir.close(); | |
| 370 | var dir = try Dir.openDirAbsolute(io, dir_path, .{}); | |
| 371 | defer dir.close(io); | |
| 430 | 372 | |
| 431 | const ino = (try dir.stat()).inode; | |
| 432 | try testing.expectEqual(sub_ino, ino); | |
| 373 | const ino = (try dir.stat(io)).inode; | |
| 374 | try expectEqual(sub_ino, ino); | |
| 433 | 375 | } |
| 434 | 376 | |
| 435 | 377 | { |
| 436 | 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 | 380 | defer testing.allocator.free(dir_path); |
| 439 | 381 | |
| 440 | var dir = try fs.openDirAbsolute(dir_path, .{}); | |
| 441 | defer dir.close(); | |
| 382 | var dir = try Dir.openDirAbsolute(io, dir_path, .{}); | |
| 383 | defer dir.close(io); | |
| 442 | 384 | |
| 443 | const ino = (try dir.stat()).inode; | |
| 444 | try testing.expectEqual(tmp_ino, ino); | |
| 385 | const ino = (try dir.stat(io)).inode; | |
| 386 | try expectEqual(tmp_ino, ino); | |
| 445 | 387 | } |
| 446 | 388 | } |
| 447 | 389 | |
| 448 | 390 | test "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 | 394 | if (native_os == .wasi and err == error.PermissionDenied) { |
| 451 | 395 | return; // This is okay. WASI disallows escaping from the fs sandbox |
| 452 | 396 | } |
| 453 | 397 | return err; |
| 454 | 398 | }; |
| 455 | defer dir.close(); | |
| 399 | defer dir.close(io); | |
| 456 | 400 | } |
| 457 | 401 | |
| 458 | 402 | test "openDir non-cwd parent '..'" { |
| ... | ... | @@ -461,69 +405,76 @@ test "openDir non-cwd parent '..'" { |
| 461 | 405 | else => {}, |
| 462 | 406 | } |
| 463 | 407 | |
| 408 | const io = testing.io; | |
| 409 | const gpa = testing.allocator; | |
| 410 | ||
| 464 | 411 | var tmp = tmpDir(.{}); |
| 465 | 412 | defer tmp.cleanup(); |
| 466 | 413 | |
| 467 | var subdir = try tmp.dir.makeOpenPath("subdir", .{}); | |
| 468 | defer subdir.close(); | |
| 414 | var subdir = try tmp.dir.createDirPathOpen(io, "subdir", .{}); | |
| 415 | defer subdir.close(io); | |
| 469 | 416 | |
| 470 | var dir = try subdir.openDir("..", .{}); | |
| 471 | defer dir.close(); | |
| 417 | var dir = try subdir.openDir(io, "..", .{}); | |
| 418 | defer dir.close(io); | |
| 472 | 419 | |
| 473 | const expected_path = try tmp.dir.realpathAlloc(testing.allocator, "."); | |
| 474 | defer testing.allocator.free(expected_path); | |
| 420 | const expected_path = try tmp.dir.realPathFileAlloc(io, ".", gpa); | |
| 421 | defer gpa.free(expected_path); | |
| 475 | 422 | |
| 476 | const actual_path = try dir.realpathAlloc(testing.allocator, "."); | |
| 477 | defer testing.allocator.free(actual_path); | |
| 423 | const actual_path = try dir.realPathFileAlloc(io, ".", gpa); | |
| 424 | defer gpa.free(actual_path); | |
| 478 | 425 | |
| 479 | try testing.expectEqualStrings(expected_path, actual_path); | |
| 426 | try expectEqualStrings(expected_path, actual_path); | |
| 480 | 427 | } |
| 481 | 428 | |
| 482 | 429 | test "readLinkAbsolute" { |
| 483 | 430 | if (native_os == .wasi) return error.SkipZigTest; |
| 484 | 431 | if (native_os == .openbsd) return error.SkipZigTest; |
| 485 | 432 | |
| 433 | const io = testing.io; | |
| 434 | ||
| 486 | 435 | var tmp = tmpDir(.{}); |
| 487 | 436 | defer tmp.cleanup(); |
| 488 | 437 | |
| 489 | 438 | // Create some targets |
| 490 | try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" }); | |
| 491 | try tmp.dir.makeDir("subdir"); | |
| 439 | try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = "nonsense" }); | |
| 440 | try tmp.dir.createDir(io, "subdir", .default_dir); | |
| 492 | 441 | |
| 493 | 442 | // Get base abs path |
| 494 | var arena = ArenaAllocator.init(testing.allocator); | |
| 495 | defer arena.deinit(); | |
| 496 | const allocator = arena.allocator(); | |
| 443 | var arena_allocator = ArenaAllocator.init(testing.allocator); | |
| 444 | defer arena_allocator.deinit(); | |
| 445 | const arena = arena_allocator.allocator(); | |
| 497 | 446 | |
| 498 | const base_path = try tmp.dir.realpathAlloc(allocator, "."); | |
| 447 | const base_path = try tmp.dir.realPathFileAlloc(io, ".", arena); | |
| 499 | 448 | |
| 500 | 449 | { |
| 501 | const target_path = try fs.path.join(allocator, &.{ base_path, "file.txt" }); | |
| 502 | const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" }); | |
| 450 | const target_path = try Dir.path.join(arena, &.{ base_path, "file.txt" }); | |
| 451 | const symlink_path = try Dir.path.join(arena, &.{ base_path, "symlink1" }); | |
| 503 | 452 | |
| 504 | 453 | // Create symbolic link by path |
| 505 | try setupSymlinkAbsolute(target_path, symlink_path, .{}); | |
| 506 | try testReadLinkAbsolute(target_path, symlink_path); | |
| 454 | try setupSymlinkAbsolute(io, target_path, symlink_path, .{}); | |
| 455 | try testReadLinkAbsolute(io, target_path, symlink_path); | |
| 507 | 456 | } |
| 508 | 457 | { |
| 509 | const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" }); | |
| 510 | const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" }); | |
| 458 | const target_path = try Dir.path.join(arena, &.{ base_path, "subdir" }); | |
| 459 | const symlink_path = try Dir.path.join(arena, &.{ base_path, "symlink2" }); | |
| 511 | 460 | |
| 512 | 461 | // Create symbolic link to a directory by path |
| 513 | try setupSymlinkAbsolute(target_path, symlink_path, .{ .is_directory = true }); | |
| 514 | try testReadLinkAbsolute(target_path, symlink_path); | |
| 462 | try setupSymlinkAbsolute(io, target_path, symlink_path, .{ .is_directory = true }); | |
| 463 | try testReadLinkAbsolute(io, target_path, symlink_path); | |
| 515 | 464 | } |
| 516 | 465 | } |
| 517 | 466 | |
| 518 | 467 | test "Dir.Iterator" { |
| 468 | const io = testing.io; | |
| 469 | ||
| 519 | 470 | var tmp_dir = tmpDir(.{ .iterate = true }); |
| 520 | 471 | defer tmp_dir.cleanup(); |
| 521 | 472 | |
| 522 | 473 | // First, create a couple of entries to iterate over. |
| 523 | const file = try tmp_dir.dir.createFile("some_file", .{}); | |
| 524 | file.close(); | |
| 474 | const file = try tmp_dir.dir.createFile(io, "some_file", .{}); | |
| 475 | file.close(io); | |
| 525 | 476 | |
| 526 | try tmp_dir.dir.makeDir("some_dir"); | |
| 477 | try tmp_dir.dir.createDir(io, "some_dir", .default_dir); | |
| 527 | 478 | |
| 528 | 479 | var arena = ArenaAllocator.init(testing.allocator); |
| 529 | 480 | defer arena.deinit(); |
| ... | ... | @@ -533,19 +484,21 @@ test "Dir.Iterator" { |
| 533 | 484 | |
| 534 | 485 | // Create iterator. |
| 535 | 486 | var iter = tmp_dir.dir.iterate(); |
| 536 | while (try iter.next()) |entry| { | |
| 487 | while (try iter.next(io)) |entry| { | |
| 537 | 488 | // We cannot just store `entry` as on Windows, we're re-using the name buffer |
| 538 | 489 | // which means we'll actually share the `name` pointer between entries! |
| 539 | 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 | } |
| 542 | 493 | |
| 543 | try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' | |
| 544 | try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); | |
| 545 | try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); | |
| 494 | try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' | |
| 495 | try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 })); | |
| 496 | try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 })); | |
| 546 | 497 | } |
| 547 | 498 | |
| 548 | 499 | test "Dir.Iterator many entries" { |
| 500 | const io = testing.io; | |
| 501 | ||
| 549 | 502 | var tmp_dir = tmpDir(.{ .iterate = true }); |
| 550 | 503 | defer tmp_dir.cleanup(); |
| 551 | 504 | |
| ... | ... | @@ -554,8 +507,8 @@ test "Dir.Iterator many entries" { |
| 554 | 507 | var buf: [4]u8 = undefined; // Enough to store "1024". |
| 555 | 508 | while (i < num) : (i += 1) { |
| 556 | 509 | const name = try std.fmt.bufPrint(&buf, "{}", .{i}); |
| 557 | const file = try tmp_dir.dir.createFile(name, .{}); | |
| 558 | file.close(); | |
| 510 | const file = try tmp_dir.dir.createFile(io, name, .{}); | |
| 511 | file.close(io); | |
| 559 | 512 | } |
| 560 | 513 | |
| 561 | 514 | var arena = ArenaAllocator.init(testing.allocator); |
| ... | ... | @@ -566,29 +519,31 @@ test "Dir.Iterator many entries" { |
| 566 | 519 | |
| 567 | 520 | // Create iterator. |
| 568 | 521 | var iter = tmp_dir.dir.iterate(); |
| 569 | while (try iter.next()) |entry| { | |
| 522 | while (try iter.next(io)) |entry| { | |
| 570 | 523 | // We cannot just store `entry` as on Windows, we're re-using the name buffer |
| 571 | 524 | // which means we'll actually share the `name` pointer between entries! |
| 572 | 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 | } |
| 575 | 528 | |
| 576 | 529 | i = 0; |
| 577 | 530 | while (i < num) : (i += 1) { |
| 578 | 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 | } |
| 582 | 535 | |
| 583 | 536 | test "Dir.Iterator twice" { |
| 537 | const io = testing.io; | |
| 538 | ||
| 584 | 539 | var tmp_dir = tmpDir(.{ .iterate = true }); |
| 585 | 540 | defer tmp_dir.cleanup(); |
| 586 | 541 | |
| 587 | 542 | // First, create a couple of entries to iterate over. |
| 588 | const file = try tmp_dir.dir.createFile("some_file", .{}); | |
| 589 | file.close(); | |
| 543 | const file = try tmp_dir.dir.createFile(io, "some_file", .{}); | |
| 544 | file.close(io); | |
| 590 | 545 | |
| 591 | try tmp_dir.dir.makeDir("some_dir"); | |
| 546 | try tmp_dir.dir.createDir(io, "some_dir", .default_dir); | |
| 592 | 547 | |
| 593 | 548 | var arena = ArenaAllocator.init(testing.allocator); |
| 594 | 549 | defer arena.deinit(); |
| ... | ... | @@ -600,28 +555,30 @@ test "Dir.Iterator twice" { |
| 600 | 555 | |
| 601 | 556 | // Create iterator. |
| 602 | 557 | var iter = tmp_dir.dir.iterate(); |
| 603 | while (try iter.next()) |entry| { | |
| 558 | while (try iter.next(io)) |entry| { | |
| 604 | 559 | // We cannot just store `entry` as on Windows, we're re-using the name buffer |
| 605 | 560 | // which means we'll actually share the `name` pointer between entries! |
| 606 | 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 | } |
| 609 | 564 | |
| 610 | try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' | |
| 611 | try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); | |
| 612 | try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); | |
| 565 | try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' | |
| 566 | try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 })); | |
| 567 | try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 })); | |
| 613 | 568 | } |
| 614 | 569 | } |
| 615 | 570 | |
| 616 | 571 | test "Dir.Iterator reset" { |
| 572 | const io = testing.io; | |
| 573 | ||
| 617 | 574 | var tmp_dir = tmpDir(.{ .iterate = true }); |
| 618 | 575 | defer tmp_dir.cleanup(); |
| 619 | 576 | |
| 620 | 577 | // First, create a couple of entries to iterate over. |
| 621 | const file = try tmp_dir.dir.createFile("some_file", .{}); | |
| 622 | file.close(); | |
| 578 | const file = try tmp_dir.dir.createFile(io, "some_file", .{}); | |
| 579 | file.close(io); | |
| 623 | 580 | |
| 624 | try tmp_dir.dir.makeDir("some_dir"); | |
| 581 | try tmp_dir.dir.createDir(io, "some_dir", .default_dir); | |
| 625 | 582 | |
| 626 | 583 | var arena = ArenaAllocator.init(testing.allocator); |
| 627 | 584 | defer arena.deinit(); |
| ... | ... | @@ -634,48 +591,45 @@ test "Dir.Iterator reset" { |
| 634 | 591 | while (i < 2) : (i += 1) { |
| 635 | 592 | var entries = std.array_list.Managed(Dir.Entry).init(allocator); |
| 636 | 593 | |
| 637 | while (try iter.next()) |entry| { | |
| 594 | while (try iter.next(io)) |entry| { | |
| 638 | 595 | // We cannot just store `entry` as on Windows, we're re-using the name buffer |
| 639 | 596 | // which means we'll actually share the `name` pointer between entries! |
| 640 | 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 | } |
| 643 | 600 | |
| 644 | try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' | |
| 645 | try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); | |
| 646 | try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); | |
| 601 | try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' | |
| 602 | try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 })); | |
| 603 | try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 })); | |
| 647 | 604 | |
| 648 | iter.reset(); | |
| 605 | iter.reader.reset(); | |
| 649 | 606 | } |
| 650 | 607 | } |
| 651 | 608 | |
| 652 | 609 | test "Dir.Iterator but dir is deleted during iteration" { |
| 610 | const io = testing.io; | |
| 611 | ||
| 653 | 612 | var tmp = std.testing.tmpDir(.{}); |
| 654 | 613 | defer tmp.cleanup(); |
| 655 | 614 | |
| 656 | 615 | // Create directory and setup an iterator for it |
| 657 | var subdir = try tmp.dir.makeOpenPath("subdir", .{ .iterate = true }); | |
| 658 | defer subdir.close(); | |
| 616 | var subdir = try tmp.dir.createDirPathOpen(io, "subdir", .{ .open_options = .{ .iterate = true } }); | |
| 617 | defer subdir.close(io); | |
| 659 | 618 | |
| 660 | 619 | var iterator = subdir.iterate(); |
| 661 | 620 | |
| 662 | 621 | // 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"); | |
| 664 | 623 | |
| 665 | 624 | // Then, before iterating, delete the directory that we're iterating. |
| 666 | 625 | // This is a contrived reproduction, but this could happen outside of the program, in another thread, etc. |
| 667 | 626 | // If we get an error while trying to delete, we can skip this test (this will happen on platforms |
| 668 | 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; | |
| 670 | 629 | |
| 671 | 630 | // Now, when we try to iterate, the next call should return null immediately. |
| 672 | const entry = try iterator.next(); | |
| 673 | try std.testing.expect(entry == null); | |
| 674 | ||
| 675 | // On Linux, we can opt-in to receiving a more specific error by calling `nextLinux` | |
| 676 | if (native_os == .linux) { | |
| 677 | try std.testing.expectError(error.DirNotFound, iterator.nextLinux()); | |
| 678 | } | |
| 631 | const entry = try iterator.next(io); | |
| 632 | try testing.expect(entry == null); | |
| 679 | 633 | } |
| 680 | 634 | |
| 681 | 635 | fn 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 | 643 | return false; |
| 690 | 644 | } |
| 691 | 645 | |
| 692 | test "Dir.realpath smoke test" { | |
| 693 | if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest; | |
| 646 | test "Dir.realPath smoke test" { | |
| 647 | if (native_os == .wasi) return error.SkipZigTest; | |
| 694 | 648 | |
| 695 | 649 | try testWithAllSupportedPathTypes(struct { |
| 696 | 650 | fn impl(ctx: *TestContext) !void { |
| 697 | const allocator = ctx.arena.allocator(); | |
| 651 | const io = ctx.io; | |
| 652 | const arena = ctx.arena.allocator(); | |
| 698 | 653 | const test_file_path = try ctx.transformPath("test_file"); |
| 699 | 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; | |
| 701 | 656 | |
| 702 | 657 | // FileNotFound if the path doesn't exist |
| 703 | try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_file_path)); | |
| 704 | try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf)); | |
| 705 | try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_dir_path)); | |
| 706 | try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf)); | |
| 658 | try expectError(error.FileNotFound, ctx.dir.realPathFileAlloc(io, test_file_path, arena)); | |
| 659 | try expectError(error.FileNotFound, ctx.dir.realPathFile(io, test_file_path, &buf)); | |
| 660 | try expectError(error.FileNotFound, ctx.dir.realPathFileAlloc(io, test_dir_path, arena)); | |
| 661 | try expectError(error.FileNotFound, ctx.dir.realPathFile(io, test_dir_path, &buf)); | |
| 707 | 662 | |
| 708 | 663 | // Now create the file and dir |
| 709 | try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" }); | |
| 710 | try ctx.dir.makeDir(test_dir_path); | |
| 664 | try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); | |
| 665 | try ctx.dir.createDir(io, test_dir_path, .default_dir); | |
| 711 | 666 | |
| 712 | 667 | const base_path = try ctx.transformPath("."); |
| 713 | const base_realpath = try ctx.dir.realpathAlloc(allocator, base_path); | |
| 714 | const expected_file_path = try fs.path.join( | |
| 715 | allocator, | |
| 716 | &.{ base_realpath, "test_file" }, | |
| 717 | ); | |
| 718 | const expected_dir_path = try fs.path.join( | |
| 719 | allocator, | |
| 720 | &.{ base_realpath, "test_dir" }, | |
| 721 | ); | |
| 668 | const base_realpath = try ctx.dir.realPathFileAlloc(io, base_path, arena); | |
| 669 | const expected_file_path = try Dir.path.join(arena, &.{ base_realpath, "test_file" }); | |
| 670 | const expected_dir_path = try Dir.path.join(arena, &.{ base_realpath, "test_dir" }); | |
| 722 | 671 | |
| 723 | 672 | // First, test non-alloc version |
| 724 | 673 | { |
| 725 | const file_path = try ctx.dir.realpath(test_file_path, &buf); | |
| 726 | try testing.expectEqualStrings(expected_file_path, file_path); | |
| 674 | const file_path = buf[0..try ctx.dir.realPathFile(io, test_file_path, &buf)]; | |
| 675 | try expectEqualStrings(expected_file_path, file_path); | |
| 727 | 676 | |
| 728 | const dir_path = try ctx.dir.realpath(test_dir_path, &buf); | |
| 729 | try testing.expectEqualStrings(expected_dir_path, dir_path); | |
| 677 | const dir_path = buf[0..try ctx.dir.realPathFile(io, test_dir_path, &buf)]; | |
| 678 | try expectEqualStrings(expected_dir_path, dir_path); | |
| 730 | 679 | } |
| 731 | 680 | |
| 732 | 681 | // Next, test alloc version |
| 733 | 682 | { |
| 734 | const file_path = try ctx.dir.realpathAlloc(allocator, test_file_path); | |
| 735 | try testing.expectEqualStrings(expected_file_path, file_path); | |
| 683 | const file_path = try ctx.dir.realPathFileAlloc(io, test_file_path, arena); | |
| 684 | try expectEqualStrings(expected_file_path, file_path); | |
| 736 | 685 | |
| 737 | const dir_path = try ctx.dir.realpathAlloc(allocator, test_dir_path); | |
| 738 | try testing.expectEqualStrings(expected_dir_path, dir_path); | |
| 686 | const dir_path = try ctx.dir.realPathFileAlloc(io, test_dir_path, arena); | |
| 687 | try expectEqualStrings(expected_dir_path, dir_path); | |
| 739 | 688 | } |
| 740 | 689 | } |
| 741 | 690 | }.impl); |
| 742 | 691 | } |
| 743 | 692 | |
| 744 | 693 | test "readFileAlloc" { |
| 694 | const io = testing.io; | |
| 695 | ||
| 745 | 696 | var tmp_dir = tmpDir(.{}); |
| 746 | 697 | defer tmp_dir.cleanup(); |
| 747 | 698 | |
| 748 | var file = try tmp_dir.dir.createFile("test_file", .{ .read = true }); | |
| 749 | defer file.close(); | |
| 699 | var file = try tmp_dir.dir.createFile(io, "test_file", .{ .read = true }); | |
| 700 | defer file.close(io); | |
| 750 | 701 | |
| 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 | 703 | defer testing.allocator.free(buf1); |
| 753 | try testing.expectEqualStrings("", buf1); | |
| 704 | try expectEqualStrings("", buf1); | |
| 754 | 705 | |
| 755 | 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); | |
| 757 | 708 | |
| 758 | 709 | { |
| 759 | 710 | // 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 | 712 | defer testing.allocator.free(buf2); |
| 762 | try testing.expectEqualStrings(write_buf, buf2); | |
| 713 | try expectEqualStrings(write_buf, buf2); | |
| 763 | 714 | } |
| 764 | 715 | |
| 765 | 716 | { |
| 766 | 717 | // max_bytes == file_size |
| 767 | try testing.expectError( | |
| 718 | try expectError( | |
| 768 | 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 | } |
| 772 | 723 | |
| 773 | 724 | { |
| 774 | 725 | // 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 | 727 | defer testing.allocator.free(buf2); |
| 777 | try testing.expectEqualStrings(write_buf, buf2); | |
| 728 | try expectEqualStrings(write_buf, buf2); | |
| 778 | 729 | } |
| 779 | 730 | |
| 780 | 731 | // max_bytes < file_size |
| 781 | try testing.expectError( | |
| 732 | try expectError( | |
| 782 | 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 | } |
| 786 | 737 | |
| 787 | 738 | test "Dir.statFile" { |
| 788 | 739 | try testWithAllSupportedPathTypes(struct { |
| 789 | 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"); | |
| 791 | 744 | |
| 792 | try testing.expectError(error.FileNotFound, ctx.dir.statFile(test_file_name)); | |
| 745 | try expectError(error.FileNotFound, ctx.dir.statFile(io, test_file_name, .{})); | |
| 793 | 746 | |
| 794 | try ctx.dir.writeFile(.{ .sub_path = test_file_name, .data = "" }); | |
| 747 | try ctx.dir.writeFile(io, .{ .sub_path = test_file_name, .data = "" }); | |
| 795 | 748 | |
| 796 | const stat = try ctx.dir.statFile(test_file_name); | |
| 797 | try testing.expectEqual(File.Kind.file, stat.kind); | |
| 749 | const stat = try ctx.dir.statFile(io, test_file_name, .{}); | |
| 750 | try expectEqual(.file, stat.kind); | |
| 751 | } | |
| 752 | { | |
| 753 | const test_dir_name = try ctx.transformPath("test_dir"); | |
| 754 | ||
| 755 | try expectError(error.FileNotFound, ctx.dir.statFile(io, test_dir_name, .{})); | |
| 756 | ||
| 757 | try ctx.dir.createDir(io, test_dir_name, .default_dir); | |
| 758 | ||
| 759 | const stat = try ctx.dir.statFile(io, test_dir_name, .{}); | |
| 760 | try expectEqual(.directory, stat.kind); | |
| 761 | } | |
| 798 | 762 | } |
| 799 | 763 | }.impl); |
| 800 | 764 | } |
| ... | ... | @@ -802,12 +766,13 @@ test "Dir.statFile" { |
| 802 | 766 | test "statFile on dangling symlink" { |
| 803 | 767 | try testWithAllSupportedPathTypes(struct { |
| 804 | 768 | fn impl(ctx: *TestContext) !void { |
| 769 | const io = ctx.io; | |
| 805 | 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"; | |
| 807 | 772 | |
| 808 | try setupSymlink(ctx.dir, symlink_target, symlink_name, .{}); | |
| 773 | try setupSymlink(io, ctx.dir, symlink_target, symlink_name, .{}); | |
| 809 | 774 | |
| 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 | 777 | }.impl); |
| 813 | 778 | } |
| ... | ... | @@ -815,25 +780,27 @@ test "statFile on dangling symlink" { |
| 815 | 780 | test "directory operations on files" { |
| 816 | 781 | try testWithAllSupportedPathTypes(struct { |
| 817 | 782 | fn impl(ctx: *TestContext) !void { |
| 783 | const io = ctx.io; | |
| 784 | ||
| 818 | 785 | const test_file_name = try ctx.transformPath("test_file"); |
| 819 | 786 | |
| 820 | var file = try ctx.dir.createFile(test_file_name, .{ .read = true }); | |
| 821 | file.close(); | |
| 787 | var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true }); | |
| 788 | file.close(io); | |
| 822 | 789 | |
| 823 | try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name)); | |
| 824 | try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{})); | |
| 825 | try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name)); | |
| 790 | try expectError(error.PathAlreadyExists, ctx.dir.createDir(io, test_file_name, .default_dir)); | |
| 791 | try expectError(error.NotDir, ctx.dir.openDir(io, test_file_name, .{})); | |
| 792 | try expectError(error.NotDir, ctx.dir.deleteDir(io, test_file_name)); | |
| 826 | 793 | |
| 827 | 794 | if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { |
| 828 | try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name)); | |
| 829 | try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name)); | |
| 795 | try expectError(error.PathAlreadyExists, Dir.createDirAbsolute(io, test_file_name, .default_dir)); | |
| 796 | try expectError(error.NotDir, Dir.deleteDirAbsolute(io, test_file_name)); | |
| 830 | 797 | } |
| 831 | 798 | |
| 832 | 799 | // ensure the file still exists and is a file as a sanity check |
| 833 | file = try ctx.dir.openFile(test_file_name, .{}); | |
| 834 | const stat = try file.stat(); | |
| 835 | try testing.expectEqual(File.Kind.file, stat.kind); | |
| 836 | file.close(); | |
| 800 | file = try ctx.dir.openFile(io, test_file_name, .{}); | |
| 801 | const stat = try file.stat(io); | |
| 802 | try expectEqual(File.Kind.file, stat.kind); | |
| 803 | file.close(io); | |
| 837 | 804 | } |
| 838 | 805 | }.impl); |
| 839 | 806 | } |
| ... | ... | @@ -842,81 +809,91 @@ test "file operations on directories" { |
| 842 | 809 | // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759 |
| 843 | 810 | if (native_os == .freebsd) return error.SkipZigTest; |
| 844 | 811 | |
| 812 | const io = testing.io; | |
| 813 | ||
| 845 | 814 | try testWithAllSupportedPathTypes(struct { |
| 846 | 815 | fn impl(ctx: *TestContext) !void { |
| 847 | 816 | const test_dir_name = try ctx.transformPath("test_dir"); |
| 848 | 817 | |
| 849 | try ctx.dir.makeDir(test_dir_name); | |
| 818 | try ctx.dir.createDir(io, test_dir_name, .default_dir); | |
| 850 | 819 | |
| 851 | try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{})); | |
| 852 | try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name)); | |
| 820 | try expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{})); | |
| 821 | try expectError(error.IsDir, ctx.dir.deleteFile(io, test_dir_name)); | |
| 853 | 822 | switch (native_os) { |
| 854 | 823 | .dragonfly, .netbsd => { |
| 855 | 824 | // 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 | 826 | testing.allocator.free(buf); |
| 858 | 827 | }, |
| 859 | 828 | .wasi => { |
| 860 | 829 | // WASI return EBADF, which gets mapped to NotOpenForReading. |
| 861 | 830 | // 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 | 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 | } |
| 868 | 837 | |
| 869 | 838 | if (native_os == .wasi and builtin.link_libc) { |
| 870 | 839 | // wasmtime unexpectedly succeeds here, see https://github.com/ziglang/zig/issues/20747 |
| 871 | const handle = try ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }); | |
| 872 | handle.close(); | |
| 840 | const handle = try ctx.dir.openFile(io, test_dir_name, .{ .mode = .read_write }); | |
| 841 | handle.close(io); | |
| 873 | 842 | } else { |
| 874 | 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/5732 | |
| 876 | try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write })); | |
| 844 | try expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .mode = .read_write })); | |
| 877 | 845 | } |
| 878 | 846 | |
| 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 | 853 | if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { |
| 880 | try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{})); | |
| 881 | try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name)); | |
| 854 | try expectError(error.IsDir, Dir.createFileAbsolute(io, test_dir_name, .{})); | |
| 855 | try expectError(error.IsDir, Dir.deleteFileAbsolute(io, test_dir_name)); | |
| 882 | 856 | } |
| 883 | 857 | |
| 884 | 858 | // ensure the directory still exists as a sanity check |
| 885 | var dir = try ctx.dir.openDir(test_dir_name, .{}); | |
| 886 | dir.close(); | |
| 859 | var dir = try ctx.dir.openDir(io, test_dir_name, .{}); | |
| 860 | dir.close(io); | |
| 887 | 861 | } |
| 888 | 862 | }.impl); |
| 889 | 863 | } |
| 890 | 864 | |
| 891 | test "makeOpenPath parent dirs do not exist" { | |
| 865 | test "createDirPathOpen parent dirs do not exist" { | |
| 866 | const io = testing.io; | |
| 867 | ||
| 892 | 868 | var tmp_dir = tmpDir(.{}); |
| 893 | 869 | defer tmp_dir.cleanup(); |
| 894 | 870 | |
| 895 | var dir = try tmp_dir.dir.makeOpenPath("root_dir/parent_dir/some_dir", .{}); | |
| 896 | dir.close(); | |
| 871 | var dir = try tmp_dir.dir.createDirPathOpen(io, "root_dir/parent_dir/some_dir", .{}); | |
| 872 | dir.close(io); | |
| 897 | 873 | |
| 898 | 874 | // double check that the full directory structure was created |
| 899 | var dir_verification = try tmp_dir.dir.openDir("root_dir/parent_dir/some_dir", .{}); | |
| 900 | dir_verification.close(); | |
| 875 | var dir_verification = try tmp_dir.dir.openDir(io, "root_dir/parent_dir/some_dir", .{}); | |
| 876 | dir_verification.close(io); | |
| 901 | 877 | } |
| 902 | 878 | |
| 903 | 879 | test "deleteDir" { |
| 904 | 880 | try testWithAllSupportedPathTypes(struct { |
| 905 | 881 | fn impl(ctx: *TestContext) !void { |
| 882 | const io = ctx.io; | |
| 906 | 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"); | |
| 908 | 885 | |
| 909 | 886 | // 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)); | |
| 911 | 888 | |
| 912 | 889 | // deleting a non-empty directory |
| 913 | try ctx.dir.makeDir(test_dir_path); | |
| 914 | try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" }); | |
| 915 | try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path)); | |
| 890 | try ctx.dir.createDir(io, test_dir_path, .default_dir); | |
| 891 | try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); | |
| 892 | try expectError(error.DirNotEmpty, ctx.dir.deleteDir(io, test_dir_path)); | |
| 916 | 893 | |
| 917 | 894 | // deleting an empty directory |
| 918 | try ctx.dir.deleteFile(test_file_path); | |
| 919 | try ctx.dir.deleteDir(test_dir_path); | |
| 895 | try ctx.dir.deleteFile(io, test_file_path); | |
| 896 | try ctx.dir.deleteDir(io, test_dir_path); | |
| 920 | 897 | } |
| 921 | 898 | }.impl); |
| 922 | 899 | } |
| ... | ... | @@ -924,6 +901,7 @@ test "deleteDir" { |
| 924 | 901 | test "Dir.rename files" { |
| 925 | 902 | try testWithAllSupportedPathTypes(struct { |
| 926 | 903 | fn impl(ctx: *TestContext) !void { |
| 904 | const io = ctx.io; | |
| 927 | 905 | // Rename on Windows can hit intermittent AccessDenied errors |
| 928 | 906 | // when certain conditions are true about the host system. |
| 929 | 907 | // For now, skip this test when the path type is UNC to avoid them. |
| ... | ... | @@ -933,32 +911,32 @@ test "Dir.rename files" { |
| 933 | 911 | const missing_file_path = try ctx.transformPath("missing_file_name"); |
| 934 | 912 | const something_else_path = try ctx.transformPath("something_else"); |
| 935 | 913 | |
| 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)); | |
| 937 | 915 | |
| 938 | 916 | // Renaming files |
| 939 | 917 | const test_file_name = try ctx.transformPath("test_file"); |
| 940 | 918 | const renamed_test_file_name = try ctx.transformPath("test_file_renamed"); |
| 941 | var file = try ctx.dir.createFile(test_file_name, .{ .read = true }); | |
| 942 | file.close(); | |
| 943 | try ctx.dir.rename(test_file_name, renamed_test_file_name); | |
| 919 | var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true }); | |
| 920 | file.close(io); | |
| 921 | try ctx.dir.rename(test_file_name, ctx.dir, renamed_test_file_name, io); | |
| 944 | 922 | |
| 945 | 923 | // Ensure the file was renamed |
| 946 | try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{})); | |
| 947 | file = try ctx.dir.openFile(renamed_test_file_name, .{}); | |
| 948 | file.close(); | |
| 924 | try expectError(error.FileNotFound, ctx.dir.openFile(io, test_file_name, .{})); | |
| 925 | file = try ctx.dir.openFile(io, renamed_test_file_name, .{}); | |
| 926 | file.close(io); | |
| 949 | 927 | |
| 950 | 928 | // 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); | |
| 952 | 930 | |
| 953 | 931 | // Rename to existing file succeeds |
| 954 | 932 | const existing_file_path = try ctx.transformPath("existing_file"); |
| 955 | var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true }); | |
| 956 | existing_file.close(); | |
| 957 | try ctx.dir.rename(renamed_test_file_name, existing_file_path); | |
| 933 | var existing_file = try ctx.dir.createFile(io, existing_file_path, .{ .read = true }); | |
| 934 | existing_file.close(io); | |
| 935 | try ctx.dir.rename(renamed_test_file_name, ctx.dir, existing_file_path, io); | |
| 958 | 936 | |
| 959 | try testing.expectError(error.FileNotFound, ctx.dir.openFile(renamed_test_file_name, .{})); | |
| 960 | file = try ctx.dir.openFile(existing_file_path, .{}); | |
| 961 | file.close(); | |
| 937 | try expectError(error.FileNotFound, ctx.dir.openFile(io, renamed_test_file_name, .{})); | |
| 938 | file = try ctx.dir.openFile(io, existing_file_path, .{}); | |
| 939 | file.close(io); | |
| 962 | 940 | } |
| 963 | 941 | }.impl); |
| 964 | 942 | } |
| ... | ... | @@ -966,6 +944,8 @@ test "Dir.rename files" { |
| 966 | 944 | test "Dir.rename directories" { |
| 967 | 945 | try testWithAllSupportedPathTypes(struct { |
| 968 | 946 | fn impl(ctx: *TestContext) !void { |
| 947 | const io = ctx.io; | |
| 948 | ||
| 969 | 949 | // Rename on Windows can hit intermittent AccessDenied errors |
| 970 | 950 | // when certain conditions are true about the host system. |
| 971 | 951 | // For now, skip this test when the path type is UNC to avoid them. |
| ... | ... | @@ -976,27 +956,27 @@ test "Dir.rename directories" { |
| 976 | 956 | const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed"); |
| 977 | 957 | |
| 978 | 958 | // Renaming directories |
| 979 | try ctx.dir.makeDir(test_dir_path); | |
| 980 | try ctx.dir.rename(test_dir_path, test_dir_renamed_path); | |
| 959 | try ctx.dir.createDir(io, test_dir_path, .default_dir); | |
| 960 | try ctx.dir.rename(test_dir_path, ctx.dir, test_dir_renamed_path, io); | |
| 981 | 961 | |
| 982 | 962 | // Ensure the directory was renamed |
| 983 | try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{})); | |
| 984 | var dir = try ctx.dir.openDir(test_dir_renamed_path, .{}); | |
| 963 | try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{})); | |
| 964 | var dir = try ctx.dir.openDir(io, test_dir_renamed_path, .{}); | |
| 985 | 965 | |
| 986 | 966 | // Put a file in the directory |
| 987 | var file = try dir.createFile("test_file", .{ .read = true }); | |
| 988 | file.close(); | |
| 989 | dir.close(); | |
| 967 | var file = try dir.createFile(io, "test_file", .{ .read = true }); | |
| 968 | file.close(io); | |
| 969 | dir.close(io); | |
| 990 | 970 | |
| 991 | 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); | |
| 993 | 973 | |
| 994 | 974 | // Ensure the directory was renamed and the file still exists in it |
| 995 | try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{})); | |
| 996 | dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{}); | |
| 997 | file = try dir.openFile("test_file", .{}); | |
| 998 | file.close(); | |
| 999 | dir.close(); | |
| 975 | try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_renamed_path, .{})); | |
| 976 | dir = try ctx.dir.openDir(io, test_dir_renamed_again_path, .{}); | |
| 977 | file = try dir.openFile(io, "test_file", .{}); | |
| 978 | file.close(io); | |
| 979 | dir.close(io); | |
| 1000 | 980 | } |
| 1001 | 981 | }.impl); |
| 1002 | 982 | } |
| ... | ... | @@ -1007,17 +987,19 @@ test "Dir.rename directory onto empty dir" { |
| 1007 | 987 | |
| 1008 | 988 | try testWithAllSupportedPathTypes(struct { |
| 1009 | 989 | fn impl(ctx: *TestContext) !void { |
| 990 | const io = ctx.io; | |
| 991 | ||
| 1010 | 992 | const test_dir_path = try ctx.transformPath("test_dir"); |
| 1011 | 993 | const target_dir_path = try ctx.transformPath("target_dir_path"); |
| 1012 | 994 | |
| 1013 | try ctx.dir.makeDir(test_dir_path); | |
| 1014 | try ctx.dir.makeDir(target_dir_path); | |
| 1015 | try ctx.dir.rename(test_dir_path, target_dir_path); | |
| 995 | try ctx.dir.createDir(io, test_dir_path, .default_dir); | |
| 996 | try ctx.dir.createDir(io, target_dir_path, .default_dir); | |
| 997 | try ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io); | |
| 1016 | 998 | |
| 1017 | 999 | // Ensure the directory was renamed |
| 1018 | try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{})); | |
| 1019 | var dir = try ctx.dir.openDir(target_dir_path, .{}); | |
| 1020 | dir.close(); | |
| 1000 | try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{})); | |
| 1001 | var dir = try ctx.dir.openDir(io, target_dir_path, .{}); | |
| 1002 | dir.close(io); | |
| 1021 | 1003 | } |
| 1022 | 1004 | }.impl); |
| 1023 | 1005 | } |
| ... | ... | @@ -1028,22 +1010,23 @@ test "Dir.rename directory onto non-empty dir" { |
| 1028 | 1010 | |
| 1029 | 1011 | try testWithAllSupportedPathTypes(struct { |
| 1030 | 1012 | fn impl(ctx: *TestContext) !void { |
| 1013 | const io = ctx.io; | |
| 1031 | 1014 | const test_dir_path = try ctx.transformPath("test_dir"); |
| 1032 | 1015 | const target_dir_path = try ctx.transformPath("target_dir_path"); |
| 1033 | 1016 | |
| 1034 | try ctx.dir.makeDir(test_dir_path); | |
| 1017 | try ctx.dir.createDir(io, test_dir_path, .default_dir); | |
| 1035 | 1018 | |
| 1036 | var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{}); | |
| 1037 | var file = try target_dir.createFile("test_file", .{ .read = true }); | |
| 1038 | file.close(); | |
| 1039 | target_dir.close(); | |
| 1019 | var target_dir = try ctx.dir.createDirPathOpen(io, target_dir_path, .{}); | |
| 1020 | var file = try target_dir.createFile(io, "test_file", .{ .read = true }); | |
| 1021 | file.close(io); | |
| 1022 | target_dir.close(io); | |
| 1040 | 1023 | |
| 1041 | 1024 | // 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)); | |
| 1043 | 1026 | |
| 1044 | 1027 | // Ensure the directory was not renamed |
| 1045 | var dir = try ctx.dir.openDir(test_dir_path, .{}); | |
| 1046 | dir.close(); | |
| 1028 | var dir = try ctx.dir.openDir(io, test_dir_path, .{}); | |
| 1029 | dir.close(io); | |
| 1047 | 1030 | } |
| 1048 | 1031 | }.impl); |
| 1049 | 1032 | } |
| ... | ... | @@ -1054,19 +1037,22 @@ test "Dir.rename file <-> dir" { |
| 1054 | 1037 | |
| 1055 | 1038 | try testWithAllSupportedPathTypes(struct { |
| 1056 | 1039 | fn impl(ctx: *TestContext) !void { |
| 1040 | const io = ctx.io; | |
| 1057 | 1041 | const test_file_path = try ctx.transformPath("test_file"); |
| 1058 | 1042 | const test_dir_path = try ctx.transformPath("test_dir"); |
| 1059 | 1043 | |
| 1060 | var file = try ctx.dir.createFile(test_file_path, .{ .read = true }); | |
| 1061 | file.close(); | |
| 1062 | try ctx.dir.makeDir(test_dir_path); | |
| 1063 | try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path)); | |
| 1064 | try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path)); | |
| 1044 | var file = try ctx.dir.createFile(io, test_file_path, .{ .read = true }); | |
| 1045 | file.close(io); | |
| 1046 | try ctx.dir.createDir(io, test_dir_path, .default_dir); | |
| 1047 | try expectError(error.IsDir, ctx.dir.rename(test_file_path, ctx.dir, test_dir_path, io)); | |
| 1048 | try expectError(error.NotDir, ctx.dir.rename(test_dir_path, ctx.dir, test_file_path, io)); | |
| 1065 | 1049 | } |
| 1066 | 1050 | }.impl); |
| 1067 | 1051 | } |
| 1068 | 1052 | |
| 1069 | 1053 | test "rename" { |
| 1054 | const io = testing.io; | |
| 1055 | ||
| 1070 | 1056 | var tmp_dir1 = tmpDir(.{}); |
| 1071 | 1057 | defer tmp_dir1.cleanup(); |
| 1072 | 1058 | |
| ... | ... | @@ -1076,20 +1062,22 @@ test "rename" { |
| 1076 | 1062 | // Renaming files |
| 1077 | 1063 | const test_file_name = "test_file"; |
| 1078 | 1064 | const renamed_test_file_name = "test_file_renamed"; |
| 1079 | var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true }); | |
| 1080 | file.close(); | |
| 1081 | try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name); | |
| 1065 | var file = try tmp_dir1.dir.createFile(io, test_file_name, .{ .read = true }); | |
| 1066 | file.close(io); | |
| 1067 | try Dir.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name, io); | |
| 1082 | 1068 | |
| 1083 | 1069 | // ensure the file was renamed |
| 1084 | try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{})); | |
| 1085 | file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{}); | |
| 1086 | file.close(); | |
| 1070 | try expectError(error.FileNotFound, tmp_dir1.dir.openFile(io, test_file_name, .{})); | |
| 1071 | file = try tmp_dir2.dir.openFile(io, renamed_test_file_name, .{}); | |
| 1072 | file.close(io); | |
| 1087 | 1073 | } |
| 1088 | 1074 | |
| 1089 | 1075 | test "renameAbsolute" { |
| 1090 | 1076 | if (native_os == .wasi) return error.SkipZigTest; |
| 1091 | 1077 | if (native_os == .openbsd) return error.SkipZigTest; |
| 1092 | 1078 | |
| 1079 | const io = testing.io; | |
| 1080 | ||
| 1093 | 1081 | var tmp_dir = tmpDir(.{}); |
| 1094 | 1082 | defer tmp_dir.cleanup(); |
| 1095 | 1083 | |
| ... | ... | @@ -1098,289 +1086,359 @@ test "renameAbsolute" { |
| 1098 | 1086 | defer arena.deinit(); |
| 1099 | 1087 | const allocator = arena.allocator(); |
| 1100 | 1088 | |
| 1101 | const base_path = try tmp_dir.dir.realpathAlloc(allocator, "."); | |
| 1089 | const base_path = try tmp_dir.dir.realPathFileAlloc(io, ".", allocator); | |
| 1102 | 1090 | |
| 1103 | try testing.expectError(error.FileNotFound, fs.renameAbsolute( | |
| 1104 | try fs.path.join(allocator, &.{ base_path, "missing_file_name" }), | |
| 1105 | try fs.path.join(allocator, &.{ base_path, "something_else" }), | |
| 1091 | try expectError(error.FileNotFound, Dir.renameAbsolute( | |
| 1092 | try Dir.path.join(allocator, &.{ base_path, "missing_file_name" }), | |
| 1093 | try Dir.path.join(allocator, &.{ base_path, "something_else" }), | |
| 1094 | io, | |
| 1106 | 1095 | )); |
| 1107 | 1096 | |
| 1108 | 1097 | // Renaming files |
| 1109 | 1098 | const test_file_name = "test_file"; |
| 1110 | 1099 | const renamed_test_file_name = "test_file_renamed"; |
| 1111 | var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); | |
| 1112 | file.close(); | |
| 1113 | try fs.renameAbsolute( | |
| 1114 | try fs.path.join(allocator, &.{ base_path, test_file_name }), | |
| 1115 | try fs.path.join(allocator, &.{ base_path, renamed_test_file_name }), | |
| 1100 | var file = try tmp_dir.dir.createFile(io, test_file_name, .{ .read = true }); | |
| 1101 | file.close(io); | |
| 1102 | try Dir.renameAbsolute( | |
| 1103 | try Dir.path.join(allocator, &.{ base_path, test_file_name }), | |
| 1104 | try Dir.path.join(allocator, &.{ base_path, renamed_test_file_name }), | |
| 1105 | io, | |
| 1116 | 1106 | ); |
| 1117 | 1107 | |
| 1118 | 1108 | // ensure the file was renamed |
| 1119 | try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); | |
| 1120 | file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); | |
| 1121 | const stat = try file.stat(); | |
| 1122 | try testing.expectEqual(File.Kind.file, stat.kind); | |
| 1123 | file.close(); | |
| 1109 | try expectError(error.FileNotFound, tmp_dir.dir.openFile(io, test_file_name, .{})); | |
| 1110 | file = try tmp_dir.dir.openFile(io, renamed_test_file_name, .{}); | |
| 1111 | const stat = try file.stat(io); | |
| 1112 | try expectEqual(File.Kind.file, stat.kind); | |
| 1113 | file.close(io); | |
| 1124 | 1114 | |
| 1125 | 1115 | // Renaming directories |
| 1126 | 1116 | const test_dir_name = "test_dir"; |
| 1127 | 1117 | const renamed_test_dir_name = "test_dir_renamed"; |
| 1128 | try tmp_dir.dir.makeDir(test_dir_name); | |
| 1129 | try fs.renameAbsolute( | |
| 1130 | try fs.path.join(allocator, &.{ base_path, test_dir_name }), | |
| 1131 | try fs.path.join(allocator, &.{ base_path, renamed_test_dir_name }), | |
| 1118 | try tmp_dir.dir.createDir(io, test_dir_name, .default_dir); | |
| 1119 | try Dir.renameAbsolute( | |
| 1120 | try Dir.path.join(allocator, &.{ base_path, test_dir_name }), | |
| 1121 | try Dir.path.join(allocator, &.{ base_path, renamed_test_dir_name }), | |
| 1122 | io, | |
| 1132 | 1123 | ); |
| 1133 | 1124 | |
| 1134 | 1125 | // ensure the directory was renamed |
| 1135 | try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{})); | |
| 1136 | var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{}); | |
| 1137 | dir.close(); | |
| 1126 | try expectError(error.FileNotFound, tmp_dir.dir.openDir(io, test_dir_name, .{})); | |
| 1127 | var dir = try tmp_dir.dir.openDir(io, renamed_test_dir_name, .{}); | |
| 1128 | dir.close(io); | |
| 1138 | 1129 | } |
| 1139 | 1130 | |
| 1140 | test "openSelfExe" { | |
| 1131 | test "openExecutable" { | |
| 1141 | 1132 | if (native_os == .wasi) return error.SkipZigTest; |
| 1142 | 1133 | |
| 1143 | const self_exe_file = try std.fs.openSelfExe(.{}); | |
| 1144 | self_exe_file.close(); | |
| 1134 | const io = testing.io; | |
| 1135 | ||
| 1136 | const self_exe_file = try std.process.openExecutable(io, .{}); | |
| 1137 | self_exe_file.close(io); | |
| 1145 | 1138 | } |
| 1146 | 1139 | |
| 1147 | test "selfExePath" { | |
| 1140 | test "executablePath" { | |
| 1148 | 1141 | if (native_os == .wasi) return error.SkipZigTest; |
| 1149 | 1142 | |
| 1150 | var buf: [fs.max_path_bytes]u8 = undefined; | |
| 1151 | const buf_self_exe_path = try std.fs.selfExePath(&buf); | |
| 1152 | const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator); | |
| 1143 | const io = testing.io; | |
| 1144 | var buf: [Dir.max_path_bytes]u8 = undefined; | |
| 1145 | const len = try std.process.executablePath(io, &buf); | |
| 1146 | const buf_self_exe_path = buf[0..len]; | |
| 1147 | const alloc_self_exe_path = try std.process.executablePathAlloc(io, testing.allocator); | |
| 1153 | 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 | } |
| 1156 | 1151 | |
| 1157 | 1152 | test "deleteTree does not follow symlinks" { |
| 1153 | const io = testing.io; | |
| 1154 | ||
| 1158 | 1155 | var tmp = tmpDir(.{}); |
| 1159 | 1156 | defer tmp.cleanup(); |
| 1160 | 1157 | |
| 1161 | try tmp.dir.makePath("b"); | |
| 1158 | try tmp.dir.createDirPath(io, "b"); | |
| 1162 | 1159 | { |
| 1163 | var a = try tmp.dir.makeOpenPath("a", .{}); | |
| 1164 | defer a.close(); | |
| 1160 | var a = try tmp.dir.createDirPathOpen(io, "a", .{}); | |
| 1161 | defer a.close(io); | |
| 1165 | 1162 | |
| 1166 | try setupSymlink(a, "../b", "b", .{ .is_directory = true }); | |
| 1163 | try setupSymlink(io, a, "../b", "b", .{ .is_directory = true }); | |
| 1167 | 1164 | } |
| 1168 | 1165 | |
| 1169 | try tmp.dir.deleteTree("a"); | |
| 1166 | try tmp.dir.deleteTree(io, "a"); | |
| 1170 | 1167 | |
| 1171 | try testing.expectError(error.FileNotFound, tmp.dir.access("a", .{})); | |
| 1172 | try tmp.dir.access("b", .{}); | |
| 1168 | try expectError(error.FileNotFound, tmp.dir.access(io, "a", .{})); | |
| 1169 | try tmp.dir.access(io, "b", .{}); | |
| 1173 | 1170 | } |
| 1174 | 1171 | |
| 1175 | 1172 | test "deleteTree on a symlink" { |
| 1173 | const io = testing.io; | |
| 1174 | ||
| 1176 | 1175 | var tmp = tmpDir(.{}); |
| 1177 | 1176 | defer tmp.cleanup(); |
| 1178 | 1177 | |
| 1179 | 1178 | // Symlink to a file |
| 1180 | try tmp.dir.writeFile(.{ .sub_path = "file", .data = "" }); | |
| 1181 | try setupSymlink(tmp.dir, "file", "filelink", .{}); | |
| 1179 | try tmp.dir.writeFile(io, .{ .sub_path = "file", .data = "" }); | |
| 1180 | try setupSymlink(io, tmp.dir, "file", "filelink", .{}); | |
| 1182 | 1181 | |
| 1183 | try tmp.dir.deleteTree("filelink"); | |
| 1184 | try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{})); | |
| 1185 | try tmp.dir.access("file", .{}); | |
| 1182 | try tmp.dir.deleteTree(io, "filelink"); | |
| 1183 | try expectError(error.FileNotFound, tmp.dir.access(io, "filelink", .{})); | |
| 1184 | try tmp.dir.access(io, "file", .{}); | |
| 1186 | 1185 | |
| 1187 | 1186 | // Symlink to a directory |
| 1188 | try tmp.dir.makePath("dir"); | |
| 1189 | try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true }); | |
| 1187 | try tmp.dir.createDirPath(io, "dir"); | |
| 1188 | try setupSymlink(io, tmp.dir, "dir", "dirlink", .{ .is_directory = true }); | |
| 1190 | 1189 | |
| 1191 | try tmp.dir.deleteTree("dirlink"); | |
| 1192 | try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{})); | |
| 1193 | try tmp.dir.access("dir", .{}); | |
| 1190 | try tmp.dir.deleteTree(io, "dirlink"); | |
| 1191 | try expectError(error.FileNotFound, tmp.dir.access(io, "dirlink", .{})); | |
| 1192 | try tmp.dir.access(io, "dir", .{}); | |
| 1194 | 1193 | } |
| 1195 | 1194 | |
| 1196 | test "makePath, put some files in it, deleteTree" { | |
| 1195 | test "createDirPath, put some files in it, deleteTree" { | |
| 1197 | 1196 | try testWithAllSupportedPathTypes(struct { |
| 1198 | 1197 | fn impl(ctx: *TestContext) !void { |
| 1198 | const io = ctx.io; | |
| 1199 | 1199 | const allocator = ctx.arena.allocator(); |
| 1200 | 1200 | const dir_path = try ctx.transformPath("os_test_tmp"); |
| 1201 | 1201 | |
| 1202 | try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); | |
| 1203 | try ctx.dir.writeFile(.{ | |
| 1204 | .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), | |
| 1202 | try ctx.dir.createDirPath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); | |
| 1203 | try ctx.dir.writeFile(io, .{ | |
| 1204 | .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), | |
| 1205 | 1205 | .data = "nonsense", |
| 1206 | 1206 | }); |
| 1207 | try ctx.dir.writeFile(.{ | |
| 1208 | .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), | |
| 1207 | try ctx.dir.writeFile(io, .{ | |
| 1208 | .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), | |
| 1209 | 1209 | .data = "blah", |
| 1210 | 1210 | }); |
| 1211 | 1211 | |
| 1212 | try ctx.dir.deleteTree(dir_path); | |
| 1213 | try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{})); | |
| 1212 | try ctx.dir.deleteTree(io, dir_path); | |
| 1213 | try expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{})); | |
| 1214 | 1214 | } |
| 1215 | 1215 | }.impl); |
| 1216 | 1216 | } |
| 1217 | 1217 | |
| 1218 | test "makePath, put some files in it, deleteTreeMinStackSize" { | |
| 1218 | test "createDirPath, put some files in it, deleteTreeMinStackSize" { | |
| 1219 | 1219 | try testWithAllSupportedPathTypes(struct { |
| 1220 | 1220 | fn impl(ctx: *TestContext) !void { |
| 1221 | const io = ctx.io; | |
| 1221 | 1222 | const allocator = ctx.arena.allocator(); |
| 1222 | 1223 | const dir_path = try ctx.transformPath("os_test_tmp"); |
| 1223 | 1224 | |
| 1224 | try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); | |
| 1225 | try ctx.dir.writeFile(.{ | |
| 1226 | .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), | |
| 1225 | try ctx.dir.createDirPath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); | |
| 1226 | try ctx.dir.writeFile(io, .{ | |
| 1227 | .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), | |
| 1227 | 1228 | .data = "nonsense", |
| 1228 | 1229 | }); |
| 1229 | try ctx.dir.writeFile(.{ | |
| 1230 | .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), | |
| 1230 | try ctx.dir.writeFile(io, .{ | |
| 1231 | .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), | |
| 1231 | 1232 | .data = "blah", |
| 1232 | 1233 | }); |
| 1233 | 1234 | |
| 1234 | try ctx.dir.deleteTreeMinStackSize(dir_path); | |
| 1235 | try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{})); | |
| 1235 | try ctx.dir.deleteTreeMinStackSize(io, dir_path); | |
| 1236 | try expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{})); | |
| 1236 | 1237 | } |
| 1237 | 1238 | }.impl); |
| 1238 | 1239 | } |
| 1239 | 1240 | |
| 1240 | test "makePath in a directory that no longer exists" { | |
| 1241 | test "createDirPath in a directory that no longer exists" { | |
| 1241 | 1242 | if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir |
| 1242 | 1243 | |
| 1244 | const io = testing.io; | |
| 1245 | ||
| 1243 | 1246 | var tmp = tmpDir(.{}); |
| 1244 | 1247 | defer tmp.cleanup(); |
| 1245 | try tmp.parent_dir.deleteTree(&tmp.sub_path); | |
| 1248 | try tmp.parent_dir.deleteTree(io, &tmp.sub_path); | |
| 1246 | 1249 | |
| 1247 | try testing.expectError(error.FileNotFound, tmp.dir.makePath("sub-path")); | |
| 1250 | try expectError(error.FileNotFound, tmp.dir.createDirPath(io, "sub-path")); | |
| 1248 | 1251 | } |
| 1249 | 1252 | |
| 1250 | test "makePath but sub_path contains pre-existing file" { | |
| 1253 | test "createDirPath but sub_path contains pre-existing file" { | |
| 1254 | const io = testing.io; | |
| 1255 | ||
| 1251 | 1256 | var tmp = tmpDir(.{}); |
| 1252 | 1257 | defer tmp.cleanup(); |
| 1253 | 1258 | |
| 1254 | try tmp.dir.makeDir("foo"); | |
| 1255 | try tmp.dir.writeFile(.{ .sub_path = "foo/bar", .data = "" }); | |
| 1259 | try tmp.dir.createDir(io, "foo", .default_dir); | |
| 1260 | try tmp.dir.writeFile(io, .{ .sub_path = "foo/bar", .data = "" }); | |
| 1256 | 1261 | |
| 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 | } |
| 1259 | 1264 | |
| 1260 | fn expectDir(dir: Dir, path: []const u8) !void { | |
| 1261 | var d = try dir.openDir(path, .{}); | |
| 1262 | d.close(); | |
| 1265 | fn expectDir(io: Io, dir: Dir, path: []const u8) !void { | |
| 1266 | var d = try dir.openDir(io, path, .{}); | |
| 1267 | d.close(io); | |
| 1263 | 1268 | } |
| 1264 | 1269 | |
| 1265 | 1270 | test "makepath existing directories" { |
| 1271 | const io = testing.io; | |
| 1272 | ||
| 1266 | 1273 | var tmp = tmpDir(.{}); |
| 1267 | 1274 | defer tmp.cleanup(); |
| 1268 | 1275 | |
| 1269 | try tmp.dir.makeDir("A"); | |
| 1270 | var tmpA = try tmp.dir.openDir("A", .{}); | |
| 1271 | defer tmpA.close(); | |
| 1272 | try tmpA.makeDir("B"); | |
| 1276 | try tmp.dir.createDir(io, "A", .default_dir); | |
| 1277 | var tmpA = try tmp.dir.openDir(io, "A", .{}); | |
| 1278 | defer tmpA.close(io); | |
| 1279 | try tmpA.createDir(io, "B", .default_dir); | |
| 1273 | 1280 | |
| 1274 | const testPath = "A" ++ fs.path.sep_str ++ "B" ++ fs.path.sep_str ++ "C"; | |
| 1275 | try tmp.dir.makePath(testPath); | |
| 1281 | const testPath = "A" ++ Dir.path.sep_str ++ "B" ++ Dir.path.sep_str ++ "C"; | |
| 1282 | try tmp.dir.createDirPath(io, testPath); | |
| 1276 | 1283 | |
| 1277 | try expectDir(tmp.dir, testPath); | |
| 1284 | try expectDir(io, tmp.dir, testPath); | |
| 1278 | 1285 | } |
| 1279 | 1286 | |
| 1280 | 1287 | test "makepath through existing valid symlink" { |
| 1288 | const io = testing.io; | |
| 1289 | ||
| 1281 | 1290 | var tmp = tmpDir(.{}); |
| 1282 | 1291 | defer tmp.cleanup(); |
| 1283 | 1292 | |
| 1284 | try tmp.dir.makeDir("realfolder"); | |
| 1285 | try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{}); | |
| 1293 | try tmp.dir.createDir(io, "realfolder", .default_dir); | |
| 1294 | try setupSymlink(io, tmp.dir, "." ++ Dir.path.sep_str ++ "realfolder", "working-symlink", .{}); | |
| 1286 | 1295 | |
| 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"); | |
| 1288 | 1297 | |
| 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 | } |
| 1291 | 1300 | |
| 1292 | 1301 | test "makepath relative walks" { |
| 1302 | const io = testing.io; | |
| 1303 | ||
| 1293 | 1304 | var tmp = tmpDir(.{}); |
| 1294 | 1305 | defer tmp.cleanup(); |
| 1295 | 1306 | |
| 1296 | const relPath = try fs.path.join(testing.allocator, &.{ | |
| 1307 | const relPath = try Dir.path.join(testing.allocator, &.{ | |
| 1297 | 1308 | "first", "..", "second", "..", "third", "..", "first", "A", "..", "B", "..", "C", |
| 1298 | 1309 | }); |
| 1299 | 1310 | defer testing.allocator.free(relPath); |
| 1300 | 1311 | |
| 1301 | try tmp.dir.makePath(relPath); | |
| 1312 | try tmp.dir.createDirPath(io, relPath); | |
| 1302 | 1313 | |
| 1303 | 1314 | // How .. is handled is different on Windows than non-Windows |
| 1304 | 1315 | switch (native_os) { |
| 1305 | 1316 | .windows => { |
| 1306 | 1317 | // On Windows, .. is resolved before passing the path to NtCreateFile, |
| 1307 | 1318 | // meaning everything except `first/C` drops out. |
| 1308 | try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C"); | |
| 1309 | try testing.expectError(error.FileNotFound, tmp.dir.access("second", .{})); | |
| 1310 | try testing.expectError(error.FileNotFound, tmp.dir.access("third", .{})); | |
| 1319 | try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "C"); | |
| 1320 | try expectError(error.FileNotFound, tmp.dir.access(io, "second", .{})); | |
| 1321 | try expectError(error.FileNotFound, tmp.dir.access(io, "third", .{})); | |
| 1311 | 1322 | }, |
| 1312 | 1323 | else => { |
| 1313 | try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "A"); | |
| 1314 | try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "B"); | |
| 1315 | try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C"); | |
| 1316 | try expectDir(tmp.dir, "second"); | |
| 1317 | try expectDir(tmp.dir, "third"); | |
| 1324 | try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "A"); | |
| 1325 | try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "B"); | |
| 1326 | try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "C"); | |
| 1327 | try expectDir(io, tmp.dir, "second"); | |
| 1328 | try expectDir(io, tmp.dir, "third"); | |
| 1318 | 1329 | }, |
| 1319 | 1330 | } |
| 1320 | 1331 | } |
| 1321 | 1332 | |
| 1322 | 1333 | test "makepath ignores '.'" { |
| 1334 | const io = testing.io; | |
| 1335 | ||
| 1323 | 1336 | var tmp = tmpDir(.{}); |
| 1324 | 1337 | defer tmp.cleanup(); |
| 1325 | 1338 | |
| 1326 | 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 | 1341 | "first", ".", "second", ".", "third", |
| 1329 | 1342 | }); |
| 1330 | 1343 | defer testing.allocator.free(dotPath); |
| 1331 | 1344 | |
| 1332 | 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 | 1347 | "first", "second", "third", |
| 1335 | 1348 | }); |
| 1336 | 1349 | defer testing.allocator.free(expectedPath); |
| 1337 | 1350 | |
| 1338 | try tmp.dir.makePath(dotPath); | |
| 1351 | try tmp.dir.createDirPath(io, dotPath); | |
| 1339 | 1352 | |
| 1340 | try expectDir(tmp.dir, expectedPath); | |
| 1353 | try expectDir(io, tmp.dir, expectedPath); | |
| 1341 | 1354 | } |
| 1342 | 1355 | |
| 1343 | fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void { | |
| 1344 | // setup, create a dir and a nested file both with maxed filenames, and walk the dir | |
| 1356 | fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8, maxed_dirname: []const u8) !void { | |
| 1357 | // create a file, a dir, and a nested file all with maxed filenames | |
| 1345 | 1358 | { |
| 1346 | var maxed_dir = try iterable_dir.makeOpenPath(maxed_filename, .{}); | |
| 1347 | defer maxed_dir.close(); | |
| 1359 | try iterable_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" }); | |
| 1348 | 1360 | |
| 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); | |
| 1350 | 1363 | |
| 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 | 1391 | var walker = try iterable_dir.walk(testing.allocator); |
| 1352 | 1392 | defer walker.deinit(); |
| 1353 | 1393 | |
| 1354 | var count: usize = 0; | |
| 1355 | while (try walker.next()) |entry| { | |
| 1356 | try testing.expectEqualStrings(maxed_filename, entry.basename); | |
| 1357 | count += 1; | |
| 1394 | var file_count: usize = 0; | |
| 1395 | var dir_count: usize = 0; | |
| 1396 | while (try walker.next(io)) |entry| { | |
| 1397 | switch (entry.kind) { | |
| 1398 | .file => { | |
| 1399 | try expectEqualStrings(maxed_filename, entry.basename); | |
| 1400 | file_count += 1; | |
| 1401 | }, | |
| 1402 | .directory => { | |
| 1403 | try expectEqualStrings(maxed_dirname, entry.basename); | |
| 1404 | dir_count += 1; | |
| 1405 | }, | |
| 1406 | else => return error.TestFailed, | |
| 1407 | } | |
| 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 | } |
| 1361 | 1412 | |
| 1362 | 1413 | // ensure that we can delete the tree |
| 1363 | try iterable_dir.deleteTree(maxed_filename); | |
| 1414 | try iterable_dir.deleteTree(io, maxed_filename); | |
| 1364 | 1415 | } |
| 1365 | 1416 | |
| 1366 | 1417 | test "max file name component lengths" { |
| 1418 | const io = testing.io; | |
| 1419 | ||
| 1367 | 1420 | var tmp = tmpDir(.{ .iterate = true }); |
| 1368 | 1421 | defer tmp.cleanup(); |
| 1369 | 1422 | |
| 1370 | 1423 | if (native_os == .windows) { |
| 1371 | 1424 | // U+FFFF is the character with the largest code point that is encoded as a single |
| 1372 | // UTF-16 code unit, so Windows allows for NAME_MAX of them. | |
| 1373 | const maxed_windows_filename = ("\u{FFFF}".*) ** windows.NAME_MAX; | |
| 1374 | try testFilenameLimits(tmp.dir, &maxed_windows_filename); | |
| 1425 | // WTF-16 code unit, so Windows allows for NAME_MAX of them. | |
| 1426 | const maxed_windows_filename1 = ("\u{FFFF}".*) ** windows.NAME_MAX; | |
| 1427 | // This is also a code point that is encoded as one WTF-16 code unit, but | |
| 1428 | // three WTF-8 bytes, so it exercises the limits of both WTF-16 and WTF-8 encodings. | |
| 1429 | const maxed_windows_filename2 = ("€".*) ** windows.NAME_MAX; | |
| 1430 | try testFilenameLimits(io, tmp.dir, &maxed_windows_filename1, &maxed_windows_filename2); | |
| 1375 | 1431 | } else if (native_os == .wasi) { |
| 1376 | 1432 | // On WASI, the maxed filename depends on the host OS, so in order for this test to |
| 1377 | 1433 | // work on any host, we need to use a length that will work for all platforms |
| 1378 | 1434 | // (i.e. the minimum max_name_bytes of all supported platforms). |
| 1379 | const maxed_wasi_filename = [_]u8{'1'} ** 255; | |
| 1380 | try testFilenameLimits(tmp.dir, &maxed_wasi_filename); | |
| 1435 | const maxed_wasi_filename1: [255]u8 = @splat('1'); | |
| 1436 | const maxed_wasi_filename2: [255]u8 = @splat('2'); | |
| 1437 | try testFilenameLimits(io, tmp.dir, &maxed_wasi_filename1, &maxed_wasi_filename2); | |
| 1381 | 1438 | } else { |
| 1382 | const maxed_ascii_filename = [_]u8{'1'} ** std.fs.max_name_bytes; | |
| 1383 | try testFilenameLimits(tmp.dir, &maxed_ascii_filename); | |
| 1439 | const maxed_ascii_filename1: [Dir.max_name_bytes]u8 = @splat('1'); | |
| 1440 | const maxed_ascii_filename2: [Dir.max_name_bytes]u8 = @splat('2'); | |
| 1441 | try testFilenameLimits(io, tmp.dir, &maxed_ascii_filename1, &maxed_ascii_filename2); | |
| 1384 | 1442 | } |
| 1385 | 1443 | } |
| 1386 | 1444 | |
| ... | ... | @@ -1398,21 +1456,21 @@ test "writev, readv" { |
| 1398 | 1456 | var write_vecs: [2][]const u8 = .{ line1, line2 }; |
| 1399 | 1457 | var read_vecs: [2][]u8 = .{ &buf2, &buf1 }; |
| 1400 | 1458 | |
| 1401 | var src_file = try tmp.dir.createFile("test.txt", .{ .read = true }); | |
| 1402 | defer src_file.close(); | |
| 1459 | var src_file = try tmp.dir.createFile(io, "test.txt", .{ .read = true }); | |
| 1460 | defer src_file.close(io); | |
| 1403 | 1461 | |
| 1404 | var writer = src_file.writerStreaming(&.{}); | |
| 1462 | var writer = src_file.writerStreaming(io, &.{}); | |
| 1405 | 1463 | |
| 1406 | 1464 | try writer.interface.writeVecAll(&write_vecs); |
| 1407 | 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)); | |
| 1409 | 1467 | |
| 1410 | var reader = writer.moveToReader(io); | |
| 1468 | var reader = writer.moveToReader(); | |
| 1411 | 1469 | try reader.seekTo(0); |
| 1412 | 1470 | try reader.interface.readVecAll(&read_vecs); |
| 1413 | try testing.expectEqualStrings(&buf1, "line2\n"); | |
| 1414 | try testing.expectEqualStrings(&buf2, "line1\n"); | |
| 1415 | try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1)); | |
| 1471 | try expectEqualStrings(&buf1, "line2\n"); | |
| 1472 | try expectEqualStrings(&buf2, "line1\n"); | |
| 1473 | try expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1)); | |
| 1416 | 1474 | } |
| 1417 | 1475 | |
| 1418 | 1476 | test "pwritev, preadv" { |
| ... | ... | @@ -1428,87 +1486,37 @@ test "pwritev, preadv" { |
| 1428 | 1486 | var buf2: [line2.len]u8 = undefined; |
| 1429 | 1487 | var read_vecs: [2][]u8 = .{ &buf2, &buf1 }; |
| 1430 | 1488 | |
| 1431 | var src_file = try tmp.dir.createFile("test.txt", .{ .read = true }); | |
| 1432 | defer src_file.close(); | |
| 1489 | var src_file = try tmp.dir.createFile(io, "test.txt", .{ .read = true }); | |
| 1490 | defer src_file.close(io); | |
| 1433 | 1491 | |
| 1434 | var writer = src_file.writer(&.{}); | |
| 1492 | var writer = src_file.writer(io, &.{}); | |
| 1435 | 1493 | |
| 1436 | 1494 | try writer.seekTo(16); |
| 1437 | 1495 | try writer.interface.writeVecAll(&lines); |
| 1438 | 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)); | |
| 1440 | 1498 | |
| 1441 | var reader = writer.moveToReader(io); | |
| 1499 | var reader = writer.moveToReader(); | |
| 1442 | 1500 | try reader.seekTo(16); |
| 1443 | 1501 | try reader.interface.readVecAll(&read_vecs); |
| 1444 | try testing.expectEqualStrings(&buf1, "line2\n"); | |
| 1445 | try testing.expectEqualStrings(&buf2, "line1\n"); | |
| 1446 | try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1)); | |
| 1447 | } | |
| 1448 | ||
| 1449 | test "setEndPos" { | |
| 1450 | // https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission) | |
| 1451 | if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; | |
| 1452 | if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23806 | |
| 1453 | ||
| 1454 | const io = testing.io; | |
| 1455 | ||
| 1456 | var tmp = tmpDir(.{}); | |
| 1457 | defer tmp.cleanup(); | |
| 1458 | ||
| 1459 | const file_name = "afile.txt"; | |
| 1460 | try tmp.dir.writeFile(.{ .sub_path = file_name, .data = "ninebytes" }); | |
| 1461 | const f = try tmp.dir.openFile(file_name, .{ .mode = .read_write }); | |
| 1462 | defer f.close(); | |
| 1463 | ||
| 1464 | const initial_size = try f.getEndPos(); | |
| 1465 | var buffer: [32]u8 = undefined; | |
| 1466 | var reader = f.reader(io, &.{}); | |
| 1467 | ||
| 1468 | { | |
| 1469 | try f.setEndPos(initial_size); | |
| 1470 | try testing.expectEqual(initial_size, try f.getEndPos()); | |
| 1471 | try reader.seekTo(0); | |
| 1472 | try testing.expectEqual(initial_size, try reader.interface.readSliceShort(&buffer)); | |
| 1473 | try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]); | |
| 1474 | } | |
| 1475 | ||
| 1476 | { | |
| 1477 | const larger = initial_size + 4; | |
| 1478 | try f.setEndPos(larger); | |
| 1479 | try testing.expectEqual(larger, try f.getEndPos()); | |
| 1480 | try reader.seekTo(0); | |
| 1481 | try testing.expectEqual(larger, try reader.interface.readSliceShort(&buffer)); | |
| 1482 | try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]); | |
| 1483 | } | |
| 1484 | ||
| 1485 | { | |
| 1486 | const smaller = initial_size - 5; | |
| 1487 | try f.setEndPos(smaller); | |
| 1488 | try testing.expectEqual(smaller, try f.getEndPos()); | |
| 1489 | try reader.seekTo(0); | |
| 1490 | try testing.expectEqual(smaller, try reader.interface.readSliceShort(&buffer)); | |
| 1491 | try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]); | |
| 1492 | } | |
| 1493 | ||
| 1494 | try f.setEndPos(0); | |
| 1495 | try testing.expectEqual(0, try f.getEndPos()); | |
| 1496 | try reader.seekTo(0); | |
| 1497 | try testing.expectEqual(0, try reader.interface.readSliceShort(&buffer)); | |
| 1502 | try expectEqualStrings(&buf1, "line2\n"); | |
| 1503 | try expectEqualStrings(&buf2, "line1\n"); | |
| 1504 | try expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1)); | |
| 1498 | 1505 | } |
| 1499 | 1506 | |
| 1500 | 1507 | test "access file" { |
| 1501 | 1508 | try testWithAllSupportedPathTypes(struct { |
| 1502 | 1509 | fn impl(ctx: *TestContext) !void { |
| 1510 | const io = ctx.io; | |
| 1503 | 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"); | |
| 1505 | 1513 | |
| 1506 | try ctx.dir.makePath(dir_path); | |
| 1507 | try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{})); | |
| 1514 | try ctx.dir.createDirPath(io, dir_path); | |
| 1515 | try expectError(error.FileNotFound, ctx.dir.access(io, file_path, .{})); | |
| 1508 | 1516 | |
| 1509 | try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "" }); | |
| 1510 | try ctx.dir.access(file_path, .{}); | |
| 1511 | try ctx.dir.deleteTree(dir_path); | |
| 1517 | try ctx.dir.writeFile(io, .{ .sub_path = file_path, .data = "" }); | |
| 1518 | try ctx.dir.access(io, file_path, .{}); | |
| 1519 | try ctx.dir.deleteTree(io, dir_path); | |
| 1512 | 1520 | } |
| 1513 | 1521 | }.impl); |
| 1514 | 1522 | } |
| ... | ... | @@ -1519,24 +1527,24 @@ test "sendfile" { |
| 1519 | 1527 | var tmp = tmpDir(.{}); |
| 1520 | 1528 | defer tmp.cleanup(); |
| 1521 | 1529 | |
| 1522 | try tmp.dir.makePath("os_test_tmp"); | |
| 1530 | try tmp.dir.createDirPath(io, "os_test_tmp"); | |
| 1523 | 1531 | |
| 1524 | var dir = try tmp.dir.openDir("os_test_tmp", .{}); | |
| 1525 | defer dir.close(); | |
| 1532 | var dir = try tmp.dir.openDir(io, "os_test_tmp", .{}); | |
| 1533 | defer dir.close(io); | |
| 1526 | 1534 | |
| 1527 | 1535 | const line1 = "line1\n"; |
| 1528 | 1536 | const line2 = "second line\n"; |
| 1529 | 1537 | var vecs = [_][]const u8{ line1, line2 }; |
| 1530 | 1538 | |
| 1531 | var src_file = try dir.createFile("sendfile1.txt", .{ .read = true }); | |
| 1532 | defer src_file.close(); | |
| 1539 | var src_file = try dir.createFile(io, "sendfile1.txt", .{ .read = true }); | |
| 1540 | defer src_file.close(io); | |
| 1533 | 1541 | { |
| 1534 | var fw = src_file.writer(&.{}); | |
| 1542 | var fw = src_file.writer(io, &.{}); | |
| 1535 | 1543 | try fw.interface.writeVecAll(&vecs); |
| 1536 | 1544 | } |
| 1537 | 1545 | |
| 1538 | var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true }); | |
| 1539 | defer dest_file.close(); | |
| 1546 | var dest_file = try dir.createFile(io, "sendfile2.txt", .{ .read = true }); | |
| 1547 | defer dest_file.close(io); | |
| 1540 | 1548 | |
| 1541 | 1549 | const header1 = "header1\n"; |
| 1542 | 1550 | const header2 = "second header\n"; |
| ... | ... | @@ -1548,16 +1556,16 @@ test "sendfile" { |
| 1548 | 1556 | var written_buf: [100]u8 = undefined; |
| 1549 | 1557 | var file_reader = src_file.reader(io, &.{}); |
| 1550 | 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 | 1560 | try file_writer.interface.writeVecAll(&headers); |
| 1553 | 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 | 1563 | try file_writer.interface.writeVecAll(&trailers); |
| 1556 | 1564 | try file_writer.interface.flush(); |
| 1557 | var fr = file_writer.moveToReader(io); | |
| 1565 | var fr = file_writer.moveToReader(); | |
| 1558 | 1566 | try fr.seekTo(0); |
| 1559 | 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 | } |
| 1562 | 1570 | |
| 1563 | 1571 | test "sendfile with buffered data" { |
| ... | ... | @@ -1566,18 +1574,18 @@ test "sendfile with buffered data" { |
| 1566 | 1574 | var tmp = tmpDir(.{}); |
| 1567 | 1575 | defer tmp.cleanup(); |
| 1568 | 1576 | |
| 1569 | try tmp.dir.makePath("os_test_tmp"); | |
| 1577 | try tmp.dir.createDirPath(io, "os_test_tmp"); | |
| 1570 | 1578 | |
| 1571 | var dir = try tmp.dir.openDir("os_test_tmp", .{}); | |
| 1572 | defer dir.close(); | |
| 1579 | var dir = try tmp.dir.openDir(io, "os_test_tmp", .{}); | |
| 1580 | defer dir.close(io); | |
| 1573 | 1581 | |
| 1574 | var src_file = try dir.createFile("sendfile1.txt", .{ .read = true }); | |
| 1575 | defer src_file.close(); | |
| 1582 | var src_file = try dir.createFile(io, "sendfile1.txt", .{ .read = true }); | |
| 1583 | defer src_file.close(io); | |
| 1576 | 1584 | |
| 1577 | try src_file.writeAll("AAAABBBB"); | |
| 1585 | try src_file.writeStreamingAll(io, "AAAABBBB"); | |
| 1578 | 1586 | |
| 1579 | var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true }); | |
| 1580 | defer dest_file.close(); | |
| 1587 | var dest_file = try dir.createFile(io, "sendfile2.txt", .{ .read = true }); | |
| 1588 | defer dest_file.close(io); | |
| 1581 | 1589 | |
| 1582 | 1590 | var src_buffer: [32]u8 = undefined; |
| 1583 | 1591 | var file_reader = src_file.reader(io, &src_buffer); |
| ... | ... | @@ -1586,52 +1594,54 @@ test "sendfile with buffered data" { |
| 1586 | 1594 | try file_reader.interface.fill(8); |
| 1587 | 1595 | |
| 1588 | 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); | |
| 1590 | 1598 | |
| 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))); | |
| 1592 | 1600 | |
| 1593 | 1601 | var written_buf: [8]u8 = undefined; |
| 1594 | var fr = file_writer.moveToReader(io); | |
| 1602 | var fr = file_writer.moveToReader(); | |
| 1595 | 1603 | try fr.seekTo(0); |
| 1596 | 1604 | const amt = try fr.interface.readSliceShort(&written_buf); |
| 1597 | 1605 | |
| 1598 | try std.testing.expectEqual(4, amt); | |
| 1599 | try std.testing.expectEqualSlices(u8, "AAAA", written_buf[0..amt]); | |
| 1606 | try expectEqual(4, amt); | |
| 1607 | try expectEqualSlices(u8, "AAAA", written_buf[0..amt]); | |
| 1600 | 1608 | } |
| 1601 | 1609 | |
| 1602 | 1610 | test "copyFile" { |
| 1603 | 1611 | try testWithAllSupportedPathTypes(struct { |
| 1604 | 1612 | fn impl(ctx: *TestContext) !void { |
| 1613 | const io = ctx.io; | |
| 1605 | 1614 | const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP"; |
| 1606 | 1615 | const src_file = try ctx.transformPath("tmp_test_copy_file.txt"); |
| 1607 | 1616 | const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt"); |
| 1608 | 1617 | const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt"); |
| 1609 | 1618 | |
| 1610 | try ctx.dir.writeFile(.{ .sub_path = src_file, .data = data }); | |
| 1611 | defer ctx.dir.deleteFile(src_file) catch {}; | |
| 1619 | try ctx.dir.writeFile(io, .{ .sub_path = src_file, .data = data }); | |
| 1620 | defer ctx.dir.deleteFile(io, src_file) catch {}; | |
| 1612 | 1621 | |
| 1613 | try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{}); | |
| 1614 | defer ctx.dir.deleteFile(dest_file) catch {}; | |
| 1622 | try ctx.dir.copyFile(src_file, ctx.dir, dest_file, io, .{}); | |
| 1623 | defer ctx.dir.deleteFile(io, dest_file) catch {}; | |
| 1615 | 1624 | |
| 1616 | try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode }); | |
| 1617 | defer ctx.dir.deleteFile(dest_file2) catch {}; | |
| 1625 | try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, io, .{}); | |
| 1626 | defer ctx.dir.deleteFile(io, dest_file2) catch {}; | |
| 1618 | 1627 | |
| 1619 | try expectFileContents(ctx.dir, dest_file, data); | |
| 1620 | try expectFileContents(ctx.dir, dest_file2, data); | |
| 1628 | try expectFileContents(io, ctx.dir, dest_file, data); | |
| 1629 | try expectFileContents(io, ctx.dir, dest_file2, data); | |
| 1621 | 1630 | } |
| 1622 | 1631 | }.impl); |
| 1623 | 1632 | } |
| 1624 | 1633 | |
| 1625 | fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void { | |
| 1626 | const contents = try dir.readFileAlloc(file_path, testing.allocator, .limited(1000)); | |
| 1634 | fn expectFileContents(io: Io, dir: Dir, file_path: []const u8, data: []const u8) !void { | |
| 1635 | const contents = try dir.readFileAlloc(io, file_path, testing.allocator, .limited(1000)); | |
| 1627 | 1636 | defer testing.allocator.free(contents); |
| 1628 | 1637 | |
| 1629 | try testing.expectEqualSlices(u8, data, contents); | |
| 1638 | try expectEqualSlices(u8, data, contents); | |
| 1630 | 1639 | } |
| 1631 | 1640 | |
| 1632 | 1641 | test "AtomicFile" { |
| 1633 | 1642 | try testWithAllSupportedPathTypes(struct { |
| 1634 | 1643 | fn impl(ctx: *TestContext) !void { |
| 1644 | const io = ctx.io; | |
| 1635 | 1645 | const allocator = ctx.arena.allocator(); |
| 1636 | 1646 | const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt"); |
| 1637 | 1647 | const test_content = |
| ... | ... | @@ -1641,15 +1651,15 @@ test "AtomicFile" { |
| 1641 | 1651 | |
| 1642 | 1652 | { |
| 1643 | 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 | 1655 | defer af.deinit(); |
| 1646 | 1656 | try af.file_writer.interface.writeAll(test_content); |
| 1647 | 1657 | try af.finish(); |
| 1648 | 1658 | } |
| 1649 | const content = try ctx.dir.readFileAlloc(test_out_file, allocator, .limited(9999)); | |
| 1650 | try testing.expectEqualStrings(test_content, content); | |
| 1659 | const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999)); | |
| 1660 | try expectEqualStrings(test_content, content); | |
| 1651 | 1661 | |
| 1652 | try ctx.dir.deleteFile(test_out_file); | |
| 1662 | try ctx.dir.deleteFile(io, test_out_file); | |
| 1653 | 1663 | } |
| 1654 | 1664 | }.impl); |
| 1655 | 1665 | } |
| ... | ... | @@ -1659,13 +1669,14 @@ test "open file with exclusive nonblocking lock twice" { |
| 1659 | 1669 | |
| 1660 | 1670 | try testWithAllSupportedPathTypes(struct { |
| 1661 | 1671 | fn impl(ctx: *TestContext) !void { |
| 1672 | const io = ctx.io; | |
| 1662 | 1673 | const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); |
| 1663 | 1674 | |
| 1664 | const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); | |
| 1665 | defer file1.close(); | |
| 1675 | const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true }); | |
| 1676 | defer file1.close(io); | |
| 1666 | 1677 | |
| 1667 | const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); | |
| 1668 | try testing.expectError(error.WouldBlock, file2); | |
| 1678 | const file2 = ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true }); | |
| 1679 | try expectError(error.WouldBlock, file2); | |
| 1669 | 1680 | } |
| 1670 | 1681 | }.impl); |
| 1671 | 1682 | } |
| ... | ... | @@ -1675,13 +1686,14 @@ test "open file with shared and exclusive nonblocking lock" { |
| 1675 | 1686 | |
| 1676 | 1687 | try testWithAllSupportedPathTypes(struct { |
| 1677 | 1688 | fn impl(ctx: *TestContext) !void { |
| 1689 | const io = ctx.io; | |
| 1678 | 1690 | const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); |
| 1679 | 1691 | |
| 1680 | const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true }); | |
| 1681 | defer file1.close(); | |
| 1692 | const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .shared, .lock_nonblocking = true }); | |
| 1693 | defer file1.close(io); | |
| 1682 | 1694 | |
| 1683 | const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); | |
| 1684 | try testing.expectError(error.WouldBlock, file2); | |
| 1695 | const file2 = ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true }); | |
| 1696 | try expectError(error.WouldBlock, file2); | |
| 1685 | 1697 | } |
| 1686 | 1698 | }.impl); |
| 1687 | 1699 | } |
| ... | ... | @@ -1691,13 +1703,14 @@ test "open file with exclusive and shared nonblocking lock" { |
| 1691 | 1703 | |
| 1692 | 1704 | try testWithAllSupportedPathTypes(struct { |
| 1693 | 1705 | fn impl(ctx: *TestContext) !void { |
| 1706 | const io = ctx.io; | |
| 1694 | 1707 | const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); |
| 1695 | 1708 | |
| 1696 | const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); | |
| 1697 | defer file1.close(); | |
| 1709 | const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true }); | |
| 1710 | defer file1.close(io); | |
| 1698 | 1711 | |
| 1699 | const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true }); | |
| 1700 | try testing.expectError(error.WouldBlock, file2); | |
| 1712 | const file2 = ctx.dir.createFile(io, filename, .{ .lock = .shared, .lock_nonblocking = true }); | |
| 1713 | try expectError(error.WouldBlock, file2); | |
| 1701 | 1714 | } |
| 1702 | 1715 | }.impl); |
| 1703 | 1716 | } |
| ... | ... | @@ -1707,39 +1720,35 @@ test "open file with exclusive lock twice, make sure second lock waits" { |
| 1707 | 1720 | |
| 1708 | 1721 | try testWithAllSupportedPathTypes(struct { |
| 1709 | 1722 | fn impl(ctx: *TestContext) !void { |
| 1723 | const io = ctx.io; | |
| 1710 | 1724 | const filename = try ctx.transformPath("file_lock_test.txt"); |
| 1711 | 1725 | |
| 1712 | const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive }); | |
| 1713 | errdefer file.close(); | |
| 1726 | const file = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive }); | |
| 1727 | errdefer file.close(io); | |
| 1714 | 1728 | |
| 1715 | 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 | 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 }); | |
| 1719 | 1733 | |
| 1720 | 1734 | locked.set(); |
| 1721 | file1.close(); | |
| 1735 | file1.close(inner_ctx.io); | |
| 1722 | 1736 | } |
| 1723 | 1737 | }; |
| 1724 | 1738 | |
| 1725 | 1739 | var started: std.Thread.ResetEvent = .unset; |
| 1726 | 1740 | var locked: std.Thread.ResetEvent = .unset; |
| 1727 | 1741 | |
| 1728 | const t = try std.Thread.spawn(.{}, S.checkFn, .{ | |
| 1729 | &ctx.dir, | |
| 1730 | filename, | |
| 1731 | &started, | |
| 1732 | &locked, | |
| 1733 | }); | |
| 1742 | const t = try std.Thread.spawn(.{}, S.checkFn, .{ ctx, filename, &started, &locked }); | |
| 1734 | 1743 | defer t.join(); |
| 1735 | 1744 | |
| 1736 | 1745 | // Wait for the spawned thread to start trying to acquire the exclusive file lock. |
| 1737 | 1746 | // Then wait a bit to make sure that can't acquire it since we currently hold the file lock. |
| 1738 | 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)); | |
| 1740 | 1749 | |
| 1741 | 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 | 1752 | locked.wait(); |
| 1744 | 1753 | } |
| 1745 | 1754 | }.impl); |
| ... | ... | @@ -1748,11 +1757,13 @@ test "open file with exclusive lock twice, make sure second lock waits" { |
| 1748 | 1757 | test "open file with exclusive nonblocking lock twice (absolute paths)" { |
| 1749 | 1758 | if (native_os == .wasi) return error.SkipZigTest; |
| 1750 | 1759 | |
| 1760 | const io = testing.io; | |
| 1761 | ||
| 1751 | 1762 | var random_bytes: [12]u8 = undefined; |
| 1752 | 1763 | std.crypto.random.bytes(&random_bytes); |
| 1753 | 1764 | |
| 1754 | var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined; | |
| 1755 | _ = fs.base64_encoder.encode(&random_b64, &random_bytes); | |
| 1765 | var random_b64: [std.fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined; | |
| 1766 | _ = std.fs.base64_encoder.encode(&random_b64, &random_bytes); | |
| 1756 | 1767 | |
| 1757 | 1768 | const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt"; |
| 1758 | 1769 | |
| ... | ... | @@ -1761,47 +1772,50 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { |
| 1761 | 1772 | const cwd = try std.process.getCwdAlloc(gpa); |
| 1762 | 1773 | defer gpa.free(cwd); |
| 1763 | 1774 | |
| 1764 | const filename = try fs.path.resolve(gpa, &.{ cwd, sub_path }); | |
| 1775 | const filename = try Dir.path.resolve(gpa, &.{ cwd, sub_path }); | |
| 1765 | 1776 | defer gpa.free(filename); |
| 1766 | 1777 | |
| 1767 | defer fs.deleteFileAbsolute(filename) catch {}; // createFileAbsolute can leave files on failures | |
| 1768 | const file1 = try fs.createFileAbsolute(filename, .{ | |
| 1778 | defer Dir.deleteFileAbsolute(io, filename) catch {}; // createFileAbsolute can leave files on failures | |
| 1779 | const file1 = try Dir.createFileAbsolute(io, filename, .{ | |
| 1769 | 1780 | .lock = .exclusive, |
| 1770 | 1781 | .lock_nonblocking = true, |
| 1771 | 1782 | }); |
| 1772 | 1783 | |
| 1773 | const file2 = fs.createFileAbsolute(filename, .{ | |
| 1784 | const file2 = Dir.createFileAbsolute(io, filename, .{ | |
| 1774 | 1785 | .lock = .exclusive, |
| 1775 | 1786 | .lock_nonblocking = true, |
| 1776 | 1787 | }); |
| 1777 | file1.close(); | |
| 1778 | try testing.expectError(error.WouldBlock, file2); | |
| 1788 | file1.close(io); | |
| 1789 | try expectError(error.WouldBlock, file2); | |
| 1779 | 1790 | } |
| 1780 | 1791 | |
| 1781 | 1792 | test "read from locked file" { |
| 1782 | 1793 | try testWithAllSupportedPathTypes(struct { |
| 1783 | 1794 | fn impl(ctx: *TestContext) !void { |
| 1795 | const io = ctx.io; | |
| 1784 | 1796 | const filename = try ctx.transformPath("read_lock_file_test.txt"); |
| 1785 | 1797 | |
| 1786 | 1798 | { |
| 1787 | const f = try ctx.dir.createFile(filename, .{ .read = true }); | |
| 1788 | defer f.close(); | |
| 1799 | const f = try ctx.dir.createFile(io, filename, .{ .read = true }); | |
| 1800 | defer f.close(io); | |
| 1789 | 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 | 1806 | .read = true, |
| 1795 | 1807 | .lock = .exclusive, |
| 1796 | 1808 | }); |
| 1797 | defer f.close(); | |
| 1798 | const f2 = try ctx.dir.openFile(filename, .{}); | |
| 1799 | defer f2.close(); | |
| 1809 | defer f.close(io); | |
| 1810 | const f2 = try ctx.dir.openFile(io, filename, .{}); | |
| 1811 | defer f2.close(io); | |
| 1812 | // On POSIX locks may be ignored, however on Windows they cause | |
| 1813 | // LockViolation. | |
| 1800 | 1814 | var buffer: [1]u8 = undefined; |
| 1801 | 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 | 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 | 1823 | } |
| 1810 | 1824 | |
| 1811 | 1825 | test "walker" { |
| 1826 | const io = testing.io; | |
| 1827 | ||
| 1812 | 1828 | var tmp = tmpDir(.{ .iterate = true }); |
| 1813 | 1829 | defer tmp.cleanup(); |
| 1814 | 1830 | |
| ... | ... | @@ -1819,9 +1835,9 @@ test "walker" { |
| 1819 | 1835 | .{ "dir2", 1 }, |
| 1820 | 1836 | .{ "dir3", 1 }, |
| 1821 | 1837 | .{ "dir4", 1 }, |
| 1822 | .{ "dir3" ++ fs.path.sep_str ++ "sub1", 2 }, | |
| 1823 | .{ "dir3" ++ fs.path.sep_str ++ "sub2", 2 }, | |
| 1824 | .{ "dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1", 3 }, | |
| 1838 | .{ "dir3" ++ Dir.path.sep_str ++ "sub1", 2 }, | |
| 1839 | .{ "dir3" ++ Dir.path.sep_str ++ "sub2", 2 }, | |
| 1840 | .{ "dir3" ++ Dir.path.sep_str ++ "sub2" ++ Dir.path.sep_str ++ "subsub1", 3 }, | |
| 1825 | 1841 | }); |
| 1826 | 1842 | |
| 1827 | 1843 | const expected_basenames = std.StaticStringMap(void).initComptime(.{ |
| ... | ... | @@ -1835,35 +1851,37 @@ test "walker" { |
| 1835 | 1851 | }); |
| 1836 | 1852 | |
| 1837 | 1853 | for (expected_paths.keys()) |key| { |
| 1838 | try tmp.dir.makePath(key); | |
| 1854 | try tmp.dir.createDirPath(io, key); | |
| 1839 | 1855 | } |
| 1840 | 1856 | |
| 1841 | 1857 | var walker = try tmp.dir.walk(testing.allocator); |
| 1842 | 1858 | defer walker.deinit(); |
| 1843 | 1859 | |
| 1844 | 1860 | var num_walked: usize = 0; |
| 1845 | while (try walker.next()) |entry| { | |
| 1846 | testing.expect(expected_basenames.has(entry.basename)) catch |err| { | |
| 1861 | while (try walker.next(io)) |entry| { | |
| 1862 | expect(expected_basenames.has(entry.basename)) catch |err| { | |
| 1847 | 1863 | std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); |
| 1848 | 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 | 1867 | std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); |
| 1852 | 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 | 1871 | std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); |
| 1856 | 1872 | return err; |
| 1857 | 1873 | }; |
| 1858 | 1874 | // make sure that the entry.dir is the containing dir |
| 1859 | var entry_dir = try entry.dir.openDir(entry.basename, .{}); | |
| 1860 | defer entry_dir.close(); | |
| 1875 | var entry_dir = try entry.dir.openDir(io, entry.basename, .{}); | |
| 1876 | defer entry_dir.close(io); | |
| 1861 | 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 | } |
| 1865 | 1881 | |
| 1866 | 1882 | test "selective walker, skip entries that start with ." { |
| 1883 | const io = testing.io; | |
| 1884 | ||
| 1867 | 1885 | var tmp = tmpDir(.{ .iterate = true }); |
| 1868 | 1886 | defer tmp.cleanup(); |
| 1869 | 1887 | |
| ... | ... | @@ -1878,11 +1896,11 @@ test "selective walker, skip entries that start with ." { |
| 1878 | 1896 | |
| 1879 | 1897 | const expected_paths = std.StaticStringMap(usize).initComptime(.{ |
| 1880 | 1898 | .{ "dir1", 1 }, |
| 1881 | .{ "dir1" ++ fs.path.sep_str ++ "foo", 2 }, | |
| 1899 | .{ "dir1" ++ Dir.path.sep_str ++ "foo", 2 }, | |
| 1882 | 1900 | .{ "a", 1 }, |
| 1883 | .{ "a" ++ fs.path.sep_str ++ "b", 2 }, | |
| 1884 | .{ "a" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c", 3 }, | |
| 1885 | .{ "a" ++ fs.path.sep_str ++ "baz", 2 }, | |
| 1901 | .{ "a" ++ Dir.path.sep_str ++ "b", 2 }, | |
| 1902 | .{ "a" ++ Dir.path.sep_str ++ "b" ++ Dir.path.sep_str ++ "c", 3 }, | |
| 1903 | .{ "a" ++ Dir.path.sep_str ++ "baz", 2 }, | |
| 1886 | 1904 | }); |
| 1887 | 1905 | |
| 1888 | 1906 | const expected_basenames = std.StaticStringMap(void).initComptime(.{ |
| ... | ... | @@ -1895,41 +1913,43 @@ test "selective walker, skip entries that start with ." { |
| 1895 | 1913 | }); |
| 1896 | 1914 | |
| 1897 | 1915 | for (paths_to_create) |path| { |
| 1898 | try tmp.dir.makePath(path); | |
| 1916 | try tmp.dir.createDirPath(io, path); | |
| 1899 | 1917 | } |
| 1900 | 1918 | |
| 1901 | 1919 | var walker = try tmp.dir.walkSelectively(testing.allocator); |
| 1902 | 1920 | defer walker.deinit(); |
| 1903 | 1921 | |
| 1904 | 1922 | var num_walked: usize = 0; |
| 1905 | while (try walker.next()) |entry| { | |
| 1923 | while (try walker.next(io)) |entry| { | |
| 1906 | 1924 | if (entry.basename[0] == '.') continue; |
| 1907 | 1925 | if (entry.kind == .directory) { |
| 1908 | try walker.enter(entry); | |
| 1926 | try walker.enter(io, entry); | |
| 1909 | 1927 | } |
| 1910 | 1928 | |
| 1911 | testing.expect(expected_basenames.has(entry.basename)) catch |err| { | |
| 1929 | expect(expected_basenames.has(entry.basename)) catch |err| { | |
| 1912 | 1930 | std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); |
| 1913 | 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 | 1934 | std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); |
| 1917 | 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 | 1938 | std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); |
| 1921 | 1939 | return err; |
| 1922 | 1940 | }; |
| 1923 | 1941 | |
| 1924 | 1942 | // make sure that the entry.dir is the containing dir |
| 1925 | var entry_dir = try entry.dir.openDir(entry.basename, .{}); | |
| 1926 | defer entry_dir.close(); | |
| 1943 | var entry_dir = try entry.dir.openDir(io, entry.basename, .{}); | |
| 1944 | defer entry_dir.close(io); | |
| 1927 | 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 | } |
| 1931 | 1949 | |
| 1932 | 1950 | test "walker without fully iterating" { |
| 1951 | const io = testing.io; | |
| 1952 | ||
| 1933 | 1953 | var tmp = tmpDir(.{ .iterate = true }); |
| 1934 | 1954 | defer tmp.cleanup(); |
| 1935 | 1955 | |
| ... | ... | @@ -1939,18 +1959,18 @@ test "walker without fully iterating" { |
| 1939 | 1959 | // Create 2 directories inside the tmp directory, but then only iterate once before breaking. |
| 1940 | 1960 | // This ensures that walker doesn't try to close the initial directory when not fully iterating. |
| 1941 | 1961 | |
| 1942 | try tmp.dir.makePath("a"); | |
| 1943 | try tmp.dir.makePath("b"); | |
| 1962 | try tmp.dir.createDirPath(io, "a"); | |
| 1963 | try tmp.dir.createDirPath(io, "b"); | |
| 1944 | 1964 | |
| 1945 | 1965 | var num_walked: usize = 0; |
| 1946 | while (try walker.next()) |_| { | |
| 1966 | while (try walker.next(io)) |_| { | |
| 1947 | 1967 | num_walked += 1; |
| 1948 | 1968 | break; |
| 1949 | 1969 | } |
| 1950 | try testing.expectEqual(@as(usize, 1), num_walked); | |
| 1970 | try expectEqual(@as(usize, 1), num_walked); | |
| 1951 | 1971 | } |
| 1952 | 1972 | |
| 1953 | test "'.' and '..' in fs.Dir functions" { | |
| 1973 | test "'.' and '..' in Dir functions" { | |
| 1954 | 1974 | if (native_os == .windows and builtin.cpu.arch == .aarch64) { |
| 1955 | 1975 | // https://github.com/ziglang/zig/issues/17134 |
| 1956 | 1976 | return error.SkipZigTest; |
| ... | ... | @@ -1965,27 +1985,27 @@ test "'.' and '..' in fs.Dir functions" { |
| 1965 | 1985 | const rename_path = try ctx.transformPath("./subdir/../rename"); |
| 1966 | 1986 | const update_path = try ctx.transformPath("./subdir/../update"); |
| 1967 | 1987 | |
| 1968 | try ctx.dir.makeDir(subdir_path); | |
| 1969 | try ctx.dir.access(subdir_path, .{}); | |
| 1970 | var created_subdir = try ctx.dir.openDir(subdir_path, .{}); | |
| 1971 | created_subdir.close(); | |
| 1988 | try ctx.dir.createDir(io, subdir_path, .default_dir); | |
| 1989 | try ctx.dir.access(io, subdir_path, .{}); | |
| 1990 | var created_subdir = try ctx.dir.openDir(io, subdir_path, .{}); | |
| 1991 | created_subdir.close(io); | |
| 1972 | 1992 | |
| 1973 | const created_file = try ctx.dir.createFile(file_path, .{}); | |
| 1974 | created_file.close(); | |
| 1975 | try ctx.dir.access(file_path, .{}); | |
| 1993 | const created_file = try ctx.dir.createFile(io, file_path, .{}); | |
| 1994 | created_file.close(io); | |
| 1995 | try ctx.dir.access(io, file_path, .{}); | |
| 1976 | 1996 | |
| 1977 | try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{}); | |
| 1978 | try ctx.dir.rename(copy_path, rename_path); | |
| 1979 | const renamed_file = try ctx.dir.openFile(rename_path, .{}); | |
| 1980 | renamed_file.close(); | |
| 1981 | try ctx.dir.deleteFile(rename_path); | |
| 1997 | try ctx.dir.copyFile(file_path, ctx.dir, copy_path, io, .{}); | |
| 1998 | try ctx.dir.rename(copy_path, ctx.dir, rename_path, io); | |
| 1999 | const renamed_file = try ctx.dir.openFile(io, rename_path, .{}); | |
| 2000 | renamed_file.close(io); | |
| 2001 | try ctx.dir.deleteFile(io, rename_path); | |
| 1982 | 2002 | |
| 1983 | try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" }); | |
| 1984 | var dir = ctx.dir.adaptToNewApi(); | |
| 2003 | try ctx.dir.writeFile(io, .{ .sub_path = update_path, .data = "something" }); | |
| 2004 | var dir = ctx.dir; | |
| 1985 | 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); | |
| 1987 | 2007 | |
| 1988 | try ctx.dir.deleteDir(subdir_path); | |
| 2008 | try ctx.dir.deleteDir(io, subdir_path); | |
| 1989 | 2009 | } |
| 1990 | 2010 | }.impl); |
| 1991 | 2011 | } |
| ... | ... | @@ -1994,6 +2014,8 @@ test "'.' and '..' in absolute functions" { |
| 1994 | 2014 | if (native_os == .wasi) return error.SkipZigTest; |
| 1995 | 2015 | if (native_os == .openbsd) return error.SkipZigTest; |
| 1996 | 2016 | |
| 2017 | const io = testing.io; | |
| 2018 | ||
| 1997 | 2019 | var tmp = tmpDir(.{}); |
| 1998 | 2020 | defer tmp.cleanup(); |
| 1999 | 2021 | |
| ... | ... | @@ -2001,83 +2023,71 @@ test "'.' and '..' in absolute functions" { |
| 2001 | 2023 | defer arena.deinit(); |
| 2002 | 2024 | const allocator = arena.allocator(); |
| 2003 | 2025 | |
| 2004 | const base_path = try tmp.dir.realpathAlloc(allocator, "."); | |
| 2026 | const base_path = try tmp.dir.realPathFileAlloc(io, ".", allocator); | |
| 2005 | 2027 | |
| 2006 | const subdir_path = try fs.path.join(allocator, &.{ base_path, "./subdir" }); | |
| 2007 | try fs.makeDirAbsolute(subdir_path); | |
| 2008 | try fs.accessAbsolute(subdir_path, .{}); | |
| 2009 | var created_subdir = try fs.openDirAbsolute(subdir_path, .{}); | |
| 2010 | created_subdir.close(); | |
| 2028 | const subdir_path = try Dir.path.join(allocator, &.{ base_path, "./subdir" }); | |
| 2029 | try Dir.createDirAbsolute(io, subdir_path, .default_dir); | |
| 2030 | try Dir.accessAbsolute(io, subdir_path, .{}); | |
| 2031 | var created_subdir = try Dir.openDirAbsolute(io, subdir_path, .{}); | |
| 2032 | created_subdir.close(io); | |
| 2011 | 2033 | |
| 2012 | const created_file_path = try fs.path.join(allocator, &.{ subdir_path, "../file" }); | |
| 2013 | const created_file = try fs.createFileAbsolute(created_file_path, .{}); | |
| 2014 | created_file.close(); | |
| 2015 | try fs.accessAbsolute(created_file_path, .{}); | |
| 2034 | const created_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../file" }); | |
| 2035 | const created_file = try Dir.createFileAbsolute(io, created_file_path, .{}); | |
| 2036 | created_file.close(io); | |
| 2037 | try Dir.accessAbsolute(io, created_file_path, .{}); | |
| 2016 | 2038 | |
| 2017 | const copied_file_path = try fs.path.join(allocator, &.{ subdir_path, "../copy" }); | |
| 2018 | try fs.copyFileAbsolute(created_file_path, copied_file_path, .{}); | |
| 2019 | const renamed_file_path = try fs.path.join(allocator, &.{ subdir_path, "../rename" }); | |
| 2020 | try fs.renameAbsolute(copied_file_path, renamed_file_path); | |
| 2021 | const renamed_file = try fs.openFileAbsolute(renamed_file_path, .{}); | |
| 2022 | renamed_file.close(); | |
| 2023 | try fs.deleteFileAbsolute(renamed_file_path); | |
| 2039 | const copied_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../copy" }); | |
| 2040 | try Dir.copyFileAbsolute(created_file_path, copied_file_path, io, .{}); | |
| 2041 | const renamed_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../rename" }); | |
| 2042 | try Dir.renameAbsolute(copied_file_path, renamed_file_path, io); | |
| 2043 | const renamed_file = try Dir.openFileAbsolute(io, renamed_file_path, .{}); | |
| 2044 | renamed_file.close(io); | |
| 2045 | try Dir.deleteFileAbsolute(io, renamed_file_path); | |
| 2024 | 2046 | |
| 2025 | try fs.deleteDirAbsolute(subdir_path); | |
| 2047 | try Dir.deleteDirAbsolute(io, subdir_path); | |
| 2026 | 2048 | } |
| 2027 | 2049 | |
| 2028 | 2050 | test "chmod" { |
| 2029 | if (native_os == .windows or native_os == .wasi) | |
| 2030 | return error.SkipZigTest; | |
| 2051 | if (native_os == .windows or native_os == .wasi) return; | |
| 2052 | ||
| 2053 | const io = testing.io; | |
| 2031 | 2054 | |
| 2032 | 2055 | var tmp = tmpDir(.{}); |
| 2033 | 2056 | defer tmp.cleanup(); |
| 2034 | 2057 | |
| 2035 | const file = try tmp.dir.createFile("test_file", .{ .mode = 0o600 }); | |
| 2036 | defer file.close(); | |
| 2037 | try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat()).mode & 0o7777); | |
| 2058 | const file = try tmp.dir.createFile(io, "test_file", .{ .permissions = .fromMode(0o600) }); | |
| 2059 | defer file.close(io); | |
| 2060 | try expectEqual(0o600, (try file.stat(io)).permissions.toMode() & 0o7777); | |
| 2038 | 2061 | |
| 2039 | try file.chmod(0o644); | |
| 2040 | try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777); | |
| 2062 | try file.setPermissions(io, .fromMode(0o644)); | |
| 2063 | try expectEqual(0o644, (try file.stat(io)).permissions.toMode() & 0o7777); | |
| 2041 | 2064 | |
| 2042 | try tmp.dir.makeDir("test_dir"); | |
| 2043 | var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true }); | |
| 2044 | defer dir.close(); | |
| 2065 | try tmp.dir.createDir(io, "test_dir", .default_dir); | |
| 2066 | var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); | |
| 2067 | defer dir.close(io); | |
| 2045 | 2068 | |
| 2046 | try dir.chmod(0o700); | |
| 2047 | try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat()).mode & 0o7777); | |
| 2069 | try dir.setPermissions(io, .fromMode(0o700)); | |
| 2070 | try expectEqual(0o700, (try dir.stat(io)).permissions.toMode() & 0o7777); | |
| 2048 | 2071 | } |
| 2049 | 2072 | |
| 2050 | test "chown" { | |
| 2073 | test "change ownership" { | |
| 2051 | 2074 | if (native_os == .windows or native_os == .wasi) |
| 2052 | 2075 | return error.SkipZigTest; |
| 2053 | 2076 | |
| 2077 | const io = testing.io; | |
| 2078 | ||
| 2054 | 2079 | var tmp = tmpDir(.{}); |
| 2055 | 2080 | defer tmp.cleanup(); |
| 2056 | 2081 | |
| 2057 | const file = try tmp.dir.createFile("test_file", .{}); | |
| 2058 | defer file.close(); | |
| 2059 | try file.chown(null, null); | |
| 2082 | const file = try tmp.dir.createFile(io, "test_file", .{}); | |
| 2083 | defer file.close(io); | |
| 2084 | try file.setOwner(io, null, null); | |
| 2060 | 2085 | |
| 2061 | try tmp.dir.makeDir("test_dir"); | |
| 2086 | try tmp.dir.createDir(io, "test_dir", .default_dir); | |
| 2062 | 2087 | |
| 2063 | var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true }); | |
| 2064 | defer dir.close(); | |
| 2065 | try dir.chown(null, null); | |
| 2066 | } | |
| 2067 | ||
| 2068 | test "delete a setAsCwd directory on Windows" { | |
| 2069 | if (native_os != .windows) return error.SkipZigTest; | |
| 2070 | ||
| 2071 | var tmp = tmpDir(.{}); | |
| 2072 | // Set tmp dir as current working directory. | |
| 2073 | try tmp.dir.setAsCwd(); | |
| 2074 | tmp.dir.close(); | |
| 2075 | try testing.expectError(error.FileBusy, tmp.parent_dir.deleteTree(&tmp.sub_path)); | |
| 2076 | // Now set the parent dir as the current working dir for clean up. | |
| 2077 | try tmp.parent_dir.setAsCwd(); | |
| 2078 | try tmp.parent_dir.deleteTree(&tmp.sub_path); | |
| 2079 | // Close the parent "tmp" so we don't leak the HANDLE. | |
| 2080 | tmp.parent_dir.close(); | |
| 2088 | var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); | |
| 2089 | defer dir.close(io); | |
| 2090 | try dir.setOwner(io, null, null); | |
| 2081 | 2091 | } |
| 2082 | 2092 | |
| 2083 | 2093 | test "invalid UTF-8/WTF-8 paths" { |
| ... | ... | @@ -2093,71 +2103,65 @@ test "invalid UTF-8/WTF-8 paths" { |
| 2093 | 2103 | // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte |
| 2094 | 2104 | const invalid_path = try ctx.transformPath("\xFF"); |
| 2095 | 2105 | |
| 2096 | try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{})); | |
| 2106 | try expectError(expected_err, ctx.dir.openFile(io, invalid_path, .{})); | |
| 2097 | 2107 | |
| 2098 | try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{})); | |
| 2108 | try expectError(expected_err, ctx.dir.createFile(io, invalid_path, .{})); | |
| 2099 | 2109 | |
| 2100 | try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path)); | |
| 2110 | try expectError(expected_err, ctx.dir.createDir(io, invalid_path, .default_dir)); | |
| 2101 | 2111 | |
| 2102 | try testing.expectError(expected_err, ctx.dir.makePath(invalid_path)); | |
| 2103 | try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{})); | |
| 2112 | try expectError(expected_err, ctx.dir.createDirPath(io, invalid_path)); | |
| 2113 | try expectError(expected_err, ctx.dir.createDirPathOpen(io, invalid_path, .{})); | |
| 2104 | 2114 | |
| 2105 | try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{})); | |
| 2115 | try expectError(expected_err, ctx.dir.openDir(io, invalid_path, .{})); | |
| 2106 | 2116 | |
| 2107 | try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path)); | |
| 2117 | try expectError(expected_err, ctx.dir.deleteFile(io, invalid_path)); | |
| 2108 | 2118 | |
| 2109 | try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path)); | |
| 2119 | try expectError(expected_err, ctx.dir.deleteDir(io, invalid_path)); | |
| 2110 | 2120 | |
| 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)); | |
| 2112 | 2122 | |
| 2113 | try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{})); | |
| 2114 | if (native_os == .wasi) { | |
| 2115 | try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{})); | |
| 2116 | } | |
| 2123 | try expectError(expected_err, ctx.dir.symLink(io, invalid_path, invalid_path, .{})); | |
| 2117 | 2124 | |
| 2118 | try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{})); | |
| 2119 | if (native_os == .wasi) { | |
| 2120 | try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{})); | |
| 2121 | } | |
| 2125 | try expectError(expected_err, ctx.dir.readLink(io, invalid_path, &[_]u8{})); | |
| 2122 | 2126 | |
| 2123 | try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{})); | |
| 2124 | try testing.expectError(expected_err, ctx.dir.readFileAlloc(invalid_path, testing.allocator, .limited(0))); | |
| 2127 | try expectError(expected_err, ctx.dir.readFile(io, invalid_path, &[_]u8{})); | |
| 2128 | try expectError(expected_err, ctx.dir.readFileAlloc(io, invalid_path, testing.allocator, .limited(0))); | |
| 2125 | 2129 | |
| 2126 | try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path)); | |
| 2127 | try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path)); | |
| 2130 | try expectError(expected_err, ctx.dir.deleteTree(io, invalid_path)); | |
| 2131 | try expectError(expected_err, ctx.dir.deleteTreeMinStackSize(io, invalid_path)); | |
| 2128 | 2132 | |
| 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 = "" })); | |
| 2130 | 2134 | |
| 2131 | try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{})); | |
| 2135 | try expectError(expected_err, ctx.dir.access(io, invalid_path, .{})); | |
| 2132 | 2136 | |
| 2133 | var dir = ctx.dir.adaptToNewApi(); | |
| 2134 | try testing.expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{})); | |
| 2135 | try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{})); | |
| 2137 | var dir = ctx.dir; | |
| 2138 | try expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{})); | |
| 2139 | try expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, io, .{})); | |
| 2136 | 2140 | |
| 2137 | try testing.expectError(expected_err, ctx.dir.statFile(invalid_path)); | |
| 2141 | try expectError(expected_err, ctx.dir.statFile(io, invalid_path, .{})); | |
| 2138 | 2142 | |
| 2139 | 2143 | if (native_os != .wasi) { |
| 2140 | try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{})); | |
| 2141 | try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path)); | |
| 2144 | try expectError(expected_err, ctx.dir.realPathFile(io, invalid_path, &[_]u8{})); | |
| 2145 | try expectError(expected_err, ctx.dir.realPathFileAlloc(io, invalid_path, testing.allocator)); | |
| 2142 | 2146 | } |
| 2143 | 2147 | |
| 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)); | |
| 2145 | 2149 | |
| 2146 | 2150 | if (native_os != .wasi and ctx.path_type != .relative) { |
| 2147 | try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{})); | |
| 2148 | try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path)); | |
| 2149 | try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path)); | |
| 2150 | try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path)); | |
| 2151 | try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{})); | |
| 2152 | try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{})); | |
| 2153 | try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{})); | |
| 2154 | try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{})); | |
| 2155 | try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path)); | |
| 2156 | try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path)); | |
| 2157 | var readlink_buf: [fs.max_path_bytes]u8 = undefined; | |
| 2158 | try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf)); | |
| 2159 | try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{})); | |
| 2160 | try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path)); | |
| 2151 | var buf: [Dir.max_path_bytes]u8 = undefined; | |
| 2152 | try expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, io, .{})); | |
| 2153 | try expectError(expected_err, Dir.createDirAbsolute(io, invalid_path, .default_dir)); | |
| 2154 | try expectError(expected_err, Dir.deleteDirAbsolute(io, invalid_path)); | |
| 2155 | try expectError(expected_err, Dir.renameAbsolute(invalid_path, invalid_path, io)); | |
| 2156 | try expectError(expected_err, Dir.openDirAbsolute(io, invalid_path, .{})); | |
| 2157 | try expectError(expected_err, Dir.openFileAbsolute(io, invalid_path, .{})); | |
| 2158 | try expectError(expected_err, Dir.accessAbsolute(io, invalid_path, .{})); | |
| 2159 | try expectError(expected_err, Dir.createFileAbsolute(io, invalid_path, .{})); | |
| 2160 | try expectError(expected_err, Dir.deleteFileAbsolute(io, invalid_path)); | |
| 2161 | try expectError(expected_err, Dir.readLinkAbsolute(io, invalid_path, &buf)); | |
| 2162 | try expectError(expected_err, Dir.symLinkAbsolute(io, invalid_path, invalid_path, .{})); | |
| 2163 | try expectError(expected_err, Dir.realPathFileAbsolute(io, invalid_path, &buf)); | |
| 2164 | try expectError(expected_err, Dir.realPathFileAbsoluteAlloc(io, invalid_path, testing.allocator)); | |
| 2161 | 2165 | } |
| 2162 | 2166 | } |
| 2163 | 2167 | }.impl); |
| ... | ... | @@ -2171,15 +2175,15 @@ test "read file non vectored" { |
| 2171 | 2175 | |
| 2172 | 2176 | const contents = "hello, world!\n"; |
| 2173 | 2177 | |
| 2174 | const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); | |
| 2175 | defer file.close(); | |
| 2178 | const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); | |
| 2179 | defer file.close(io); | |
| 2176 | 2180 | { |
| 2177 | var file_writer: std.fs.File.Writer = .init(file, &.{}); | |
| 2181 | var file_writer: File.Writer = .init(file, io, &.{}); | |
| 2178 | 2182 | try file_writer.interface.writeAll(contents); |
| 2179 | 2183 | try file_writer.interface.flush(); |
| 2180 | 2184 | } |
| 2181 | 2185 | |
| 2182 | var file_reader: std.Io.File.Reader = .initAdapted(file, io, &.{}); | |
| 2186 | var file_reader: std.Io.File.Reader = .init(file, io, &.{}); | |
| 2183 | 2187 | |
| 2184 | 2188 | var write_buffer: [100]u8 = undefined; |
| 2185 | 2189 | var w: std.Io.Writer = .fixed(&write_buffer); |
| ... | ... | @@ -2191,8 +2195,8 @@ test "read file non vectored" { |
| 2191 | 2195 | else => |e| return e, |
| 2192 | 2196 | }; |
| 2193 | 2197 | } |
| 2194 | try testing.expectEqualStrings(contents, w.buffered()); | |
| 2195 | try testing.expectEqual(contents.len, i); | |
| 2198 | try expectEqualStrings(contents, w.buffered()); | |
| 2199 | try expectEqual(contents.len, i); | |
| 2196 | 2200 | } |
| 2197 | 2201 | |
| 2198 | 2202 | test "seek keeping partial buffer" { |
| ... | ... | @@ -2203,18 +2207,18 @@ test "seek keeping partial buffer" { |
| 2203 | 2207 | |
| 2204 | 2208 | const contents = "0123456789"; |
| 2205 | 2209 | |
| 2206 | const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); | |
| 2207 | defer file.close(); | |
| 2210 | const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); | |
| 2211 | defer file.close(io); | |
| 2208 | 2212 | { |
| 2209 | var file_writer: std.fs.File.Writer = .init(file, &.{}); | |
| 2213 | var file_writer: File.Writer = .init(file, io, &.{}); | |
| 2210 | 2214 | try file_writer.interface.writeAll(contents); |
| 2211 | 2215 | try file_writer.interface.flush(); |
| 2212 | 2216 | } |
| 2213 | 2217 | |
| 2214 | 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); | |
| 2216 | 2220 | |
| 2217 | try testing.expectEqual(0, file_reader.logicalPos()); | |
| 2221 | try expectEqual(0, file_reader.logicalPos()); | |
| 2218 | 2222 | |
| 2219 | 2223 | var buf: [4]u8 = undefined; |
| 2220 | 2224 | try file_reader.interface.readSliceAll(&buf); |
| ... | ... | @@ -2224,18 +2228,18 @@ test "seek keeping partial buffer" { |
| 2224 | 2228 | return; |
| 2225 | 2229 | } |
| 2226 | 2230 | |
| 2227 | try testing.expectEqual(4, file_reader.logicalPos()); | |
| 2228 | try testing.expectEqual(7, file_reader.pos); | |
| 2231 | try expectEqual(4, file_reader.logicalPos()); | |
| 2232 | try expectEqual(7, file_reader.pos); | |
| 2229 | 2233 | try file_reader.seekTo(6); |
| 2230 | try testing.expectEqual(6, file_reader.logicalPos()); | |
| 2231 | try testing.expectEqual(7, file_reader.pos); | |
| 2234 | try expectEqual(6, file_reader.logicalPos()); | |
| 2235 | try expectEqual(7, file_reader.pos); | |
| 2232 | 2236 | |
| 2233 | try testing.expectEqualStrings("0123", &buf); | |
| 2237 | try expectEqualStrings("0123", &buf); | |
| 2234 | 2238 | |
| 2235 | 2239 | const n = try file_reader.interface.readSliceShort(&buf); |
| 2236 | try testing.expectEqual(4, n); | |
| 2240 | try expectEqual(4, n); | |
| 2237 | 2241 | |
| 2238 | try testing.expectEqualStrings("6789", &buf); | |
| 2242 | try expectEqualStrings("6789", &buf); | |
| 2239 | 2243 | } |
| 2240 | 2244 | |
| 2241 | 2245 | test "seekBy" { |
| ... | ... | @@ -2244,16 +2248,16 @@ test "seekBy" { |
| 2244 | 2248 | var tmp_dir = testing.tmpDir(.{}); |
| 2245 | 2249 | defer tmp_dir.cleanup(); |
| 2246 | 2250 | |
| 2247 | try tmp_dir.dir.writeFile(.{ .sub_path = "blah.txt", .data = "let's test seekBy" }); | |
| 2248 | const f = try tmp_dir.dir.openFile("blah.txt", .{ .mode = .read_only }); | |
| 2249 | defer f.close(); | |
| 2251 | try tmp_dir.dir.writeFile(io, .{ .sub_path = "blah.txt", .data = "let's test seekBy" }); | |
| 2252 | const f = try tmp_dir.dir.openFile(io, "blah.txt", .{ .mode = .read_only }); | |
| 2253 | defer f.close(io); | |
| 2250 | 2254 | var reader = f.readerStreaming(io, &.{}); |
| 2251 | 2255 | try reader.seekBy(2); |
| 2252 | 2256 | |
| 2253 | 2257 | var buffer: [20]u8 = undefined; |
| 2254 | 2258 | const n = try reader.interface.readSliceShort(&buffer); |
| 2255 | try testing.expectEqual(15, n); | |
| 2256 | try testing.expectEqualStrings("t's test seekBy", buffer[0..15]); | |
| 2259 | try expectEqual(15, n); | |
| 2260 | try expectEqualStrings("t's test seekBy", buffer[0..15]); | |
| 2257 | 2261 | } |
| 2258 | 2262 | |
| 2259 | 2263 | test "seekTo flushes buffered data" { |
| ... | ... | @@ -2264,11 +2268,11 @@ test "seekTo flushes buffered data" { |
| 2264 | 2268 | |
| 2265 | 2269 | const contents = "data"; |
| 2266 | 2270 | |
| 2267 | const file = try tmp.dir.createFile("seek.bin", .{ .read = true }); | |
| 2268 | defer file.close(); | |
| 2271 | const file = try tmp.dir.createFile(io, "seek.bin", .{ .read = true }); | |
| 2272 | defer file.close(io); | |
| 2269 | 2273 | { |
| 2270 | 2274 | var buf: [16]u8 = undefined; |
| 2271 | var file_writer = std.fs.File.writer(file, &buf); | |
| 2275 | var file_writer = file.writer(io, &buf); | |
| 2272 | 2276 | |
| 2273 | 2277 | try file_writer.interface.writeAll(contents); |
| 2274 | 2278 | try file_writer.seekTo(8); |
| ... | ... | @@ -2276,11 +2280,11 @@ test "seekTo flushes buffered data" { |
| 2276 | 2280 | } |
| 2277 | 2281 | |
| 2278 | 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); | |
| 2280 | 2284 | |
| 2281 | 2285 | var buf: [4]u8 = undefined; |
| 2282 | 2286 | try file_reader.interface.readSliceAll(&buf); |
| 2283 | try std.testing.expectEqualStrings(contents, &buf); | |
| 2287 | try expectEqualStrings(contents, &buf); | |
| 2284 | 2288 | } |
| 2285 | 2289 | |
| 2286 | 2290 | test "File.Writer sendfile with buffered contents" { |
| ... | ... | @@ -2290,11 +2294,11 @@ test "File.Writer sendfile with buffered contents" { |
| 2290 | 2294 | defer tmp_dir.cleanup(); |
| 2291 | 2295 | |
| 2292 | 2296 | { |
| 2293 | try tmp_dir.dir.writeFile(.{ .sub_path = "a", .data = "bcd" }); | |
| 2294 | const in = try tmp_dir.dir.openFile("a", .{}); | |
| 2295 | defer in.close(); | |
| 2296 | const out = try tmp_dir.dir.createFile("b", .{}); | |
| 2297 | defer out.close(); | |
| 2297 | try tmp_dir.dir.writeFile(io, .{ .sub_path = "a", .data = "bcd" }); | |
| 2298 | const in = try tmp_dir.dir.openFile(io, "a", .{}); | |
| 2299 | defer in.close(io); | |
| 2300 | const out = try tmp_dir.dir.createFile(io, "b", .{}); | |
| 2301 | defer out.close(io); | |
| 2298 | 2302 | |
| 2299 | 2303 | var in_buf: [2]u8 = undefined; |
| 2300 | 2304 | var in_r = in.reader(io, &in_buf); |
| ... | ... | @@ -2302,16 +2306,332 @@ test "File.Writer sendfile with buffered contents" { |
| 2302 | 2306 | try in_r.interface.fill(2); |
| 2303 | 2307 | |
| 2304 | 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 | 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 | 2312 | try out_w.interface.flush(); |
| 2309 | 2313 | } |
| 2310 | 2314 | |
| 2311 | var check = try tmp_dir.dir.openFile("b", .{}); | |
| 2312 | defer check.close(); | |
| 2315 | var check = try tmp_dir.dir.openFile(io, "b", .{}); | |
| 2316 | defer check.close(io); | |
| 2313 | 2317 | var check_buf: [4]u8 = undefined; |
| 2314 | 2318 | var check_r = check.reader(io, &check_buf); |
| 2315 | try testing.expectEqualStrings("abcd", try check_r.interface.take(4)); | |
| 2316 | try testing.expectError(error.EndOfStream, check_r.interface.takeByte()); | |
| 2319 | try expectEqualStrings("abcd", try check_r.interface.take(4)); | |
| 2320 | try expectError(error.EndOfStream, check_r.interface.takeByte()); | |
| 2321 | } | |
| 2322 | ||
| 2323 | test "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 | ||
| 2333 | fn 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 | ||
| 2340 | test "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 | ||
| 2364 | test "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 | ||
| 2409 | fn 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 | ||
| 2416 | test "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 | ||
| 2428 | test "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 | ||
| 2441 | test "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 | ||
| 2454 | test "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 | ||
| 2467 | test "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 | ||
| 2508 | test "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 | ||
| 2524 | test "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 | ||
| 2566 | test "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 | ||
| 2609 | test "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 | 1 | // zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig |
| 2 | const builtin = @import("builtin"); | |
| 2 | 3 | |
| 3 | 4 | const std = @import("std"); |
| 4 | const builtin = @import("builtin"); | |
| 5 | const Io = std.Io; | |
| 5 | 6 | const time = std.time; |
| 6 | 7 | const Timer = time.Timer; |
| 7 | 8 | const hash = std.hash; |
| ... | ... | @@ -354,7 +355,7 @@ fn mode(comptime x: comptime_int) comptime_int { |
| 354 | 355 | |
| 355 | 356 | pub fn main() !void { |
| 356 | 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 | 359 | const stdout = &stdout_writer.interface; |
| 359 | 360 | |
| 360 | 361 | var buffer: [1024]u8 = undefined; |
lib/std/heap/debug_allocator.zig+26-29| ... | ... | @@ -84,7 +84,7 @@ const builtin = @import("builtin"); |
| 84 | 84 | const StackTrace = std.builtin.StackTrace; |
| 85 | 85 | |
| 86 | 86 | const std = @import("std"); |
| 87 | const log = std.log.scoped(.gpa); | |
| 87 | const log = std.log.scoped(.DebugAllocator); | |
| 88 | 88 | const math = std.math; |
| 89 | 89 | const assert = std.debug.assert; |
| 90 | 90 | const mem = std.mem; |
| ... | ... | @@ -425,7 +425,6 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 425 | 425 | bucket: *BucketHeader, |
| 426 | 426 | size_class_index: usize, |
| 427 | 427 | used_bits_count: usize, |
| 428 | tty_config: std.Io.tty.Config, | |
| 429 | 428 | ) usize { |
| 430 | 429 | const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index)); |
| 431 | 430 | const slot_count = slot_counts[size_class_index]; |
| ... | ... | @@ -445,7 +444,7 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 445 | 444 | addr, |
| 446 | 445 | std.debug.FormatStackTrace{ |
| 447 | 446 | .stack_trace = stack_trace, |
| 448 | .tty_config = tty_config, | |
| 447 | .terminal_mode = std.log.terminalMode(), | |
| 449 | 448 | }, |
| 450 | 449 | }); |
| 451 | 450 | leaks += 1; |
| ... | ... | @@ -460,14 +459,12 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 460 | 459 | pub fn detectLeaks(self: *Self) usize { |
| 461 | 460 | var leaks: usize = 0; |
| 462 | 461 | |
| 463 | const tty_config: std.Io.tty.Config = .detect(.stderr()); | |
| 464 | ||
| 465 | 462 | for (self.buckets, 0..) |init_optional_bucket, size_class_index| { |
| 466 | 463 | var optional_bucket = init_optional_bucket; |
| 467 | 464 | const slot_count = slot_counts[size_class_index]; |
| 468 | 465 | const used_bits_count = usedBitsCount(slot_count); |
| 469 | 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 | 468 | optional_bucket = bucket.prev; |
| 472 | 469 | } |
| 473 | 470 | } |
| ... | ... | @@ -480,7 +477,7 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 480 | 477 | @intFromPtr(large_alloc.bytes.ptr), |
| 481 | 478 | std.debug.FormatStackTrace{ |
| 482 | 479 | .stack_trace = stack_trace, |
| 483 | .tty_config = tty_config, | |
| 480 | .terminal_mode = std.log.terminalMode(), | |
| 484 | 481 | }, |
| 485 | 482 | }); |
| 486 | 483 | leaks += 1; |
| ... | ... | @@ -534,21 +531,21 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 534 | 531 | } |
| 535 | 532 | |
| 536 | 533 | fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void { |
| 534 | @branchHint(.cold); | |
| 537 | 535 | var addr_buf: [stack_n]usize = undefined; |
| 538 | 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 | 537 | log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{ |
| 541 | 538 | std.debug.FormatStackTrace{ |
| 542 | 539 | .stack_trace = alloc_stack_trace, |
| 543 | .tty_config = tty_config, | |
| 540 | .terminal_mode = std.log.terminalMode(), | |
| 544 | 541 | }, |
| 545 | 542 | std.debug.FormatStackTrace{ |
| 546 | 543 | .stack_trace = free_stack_trace, |
| 547 | .tty_config = tty_config, | |
| 544 | .terminal_mode = std.log.terminalMode(), | |
| 548 | 545 | }, |
| 549 | 546 | std.debug.FormatStackTrace{ |
| 550 | 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 | 585 | } |
| 589 | 586 | |
| 590 | 587 | if (config.safety and old_mem.len != entry.value_ptr.bytes.len) { |
| 588 | @branchHint(.cold); | |
| 591 | 589 | var addr_buf: [stack_n]usize = undefined; |
| 592 | 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 | 591 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 595 | 592 | entry.value_ptr.bytes.len, |
| 596 | 593 | old_mem.len, |
| 597 | 594 | std.debug.FormatStackTrace{ |
| 598 | 595 | .stack_trace = entry.value_ptr.getStackTrace(.alloc), |
| 599 | .tty_config = tty_config, | |
| 596 | .terminal_mode = std.log.terminalMode(), | |
| 600 | 597 | }, |
| 601 | 598 | std.debug.FormatStackTrace{ |
| 602 | 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 | 698 | } |
| 702 | 699 | |
| 703 | 700 | if (config.safety and old_mem.len != entry.value_ptr.bytes.len) { |
| 701 | @branchHint(.cold); | |
| 704 | 702 | var addr_buf: [stack_n]usize = undefined; |
| 705 | 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 | 704 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 708 | 705 | entry.value_ptr.bytes.len, |
| 709 | 706 | old_mem.len, |
| 710 | 707 | std.debug.FormatStackTrace{ |
| 711 | 708 | .stack_trace = entry.value_ptr.getStackTrace(.alloc), |
| 712 | .tty_config = tty_config, | |
| 709 | .terminal_mode = std.log.terminalMode(), | |
| 713 | 710 | }, |
| 714 | 711 | std.debug.FormatStackTrace{ |
| 715 | 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 | 932 | var addr_buf: [stack_n]usize = undefined; |
| 936 | 933 | const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); |
| 937 | 934 | if (old_memory.len != requested_size) { |
| 938 | const tty_config: std.Io.tty.Config = .detect(.stderr()); | |
| 935 | @branchHint(.cold); | |
| 939 | 936 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 940 | 937 | requested_size, |
| 941 | 938 | old_memory.len, |
| 942 | 939 | std.debug.FormatStackTrace{ |
| 943 | 940 | .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), |
| 944 | .tty_config = tty_config, | |
| 941 | .terminal_mode = std.log.terminalMode(), | |
| 945 | 942 | }, |
| 946 | 943 | std.debug.FormatStackTrace{ |
| 947 | 944 | .stack_trace = free_stack_trace, |
| 948 | .tty_config = tty_config, | |
| 945 | .terminal_mode = std.log.terminalMode(), | |
| 949 | 946 | }, |
| 950 | 947 | }); |
| 951 | 948 | } |
| 952 | 949 | if (alignment != slot_alignment) { |
| 953 | const tty_config: std.Io.tty.Config = .detect(.stderr()); | |
| 950 | @branchHint(.cold); | |
| 954 | 951 | log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{ |
| 955 | 952 | slot_alignment.toByteUnits(), |
| 956 | 953 | alignment.toByteUnits(), |
| 957 | 954 | std.debug.FormatStackTrace{ |
| 958 | 955 | .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), |
| 959 | .tty_config = tty_config, | |
| 956 | .terminal_mode = std.log.terminalMode(), | |
| 960 | 957 | }, |
| 961 | 958 | std.debug.FormatStackTrace{ |
| 962 | 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 | 1041 | var addr_buf: [stack_n]usize = undefined; |
| 1045 | 1042 | const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); |
| 1046 | 1043 | if (memory.len != requested_size) { |
| 1047 | const tty_config: std.Io.tty.Config = .detect(.stderr()); | |
| 1044 | @branchHint(.cold); | |
| 1048 | 1045 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 1049 | 1046 | requested_size, |
| 1050 | 1047 | memory.len, |
| 1051 | 1048 | std.debug.FormatStackTrace{ |
| 1052 | 1049 | .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), |
| 1053 | .tty_config = tty_config, | |
| 1050 | .terminal_mode = std.log.terminalMode(), | |
| 1054 | 1051 | }, |
| 1055 | 1052 | std.debug.FormatStackTrace{ |
| 1056 | 1053 | .stack_trace = free_stack_trace, |
| 1057 | .tty_config = tty_config, | |
| 1054 | .terminal_mode = std.log.terminalMode(), | |
| 1058 | 1055 | }, |
| 1059 | 1056 | }); |
| 1060 | 1057 | } |
| 1061 | 1058 | if (alignment != slot_alignment) { |
| 1062 | const tty_config: std.Io.tty.Config = .detect(.stderr()); | |
| 1059 | @branchHint(.cold); | |
| 1063 | 1060 | log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{ |
| 1064 | 1061 | slot_alignment.toByteUnits(), |
| 1065 | 1062 | alignment.toByteUnits(), |
| 1066 | 1063 | std.debug.FormatStackTrace{ |
| 1067 | 1064 | .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), |
| 1068 | .tty_config = tty_config, | |
| 1065 | .terminal_mode = std.log.terminalMode(), | |
| 1069 | 1066 | }, |
| 1070 | 1067 | std.debug.FormatStackTrace{ |
| 1071 | 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 | 2 | const std = @import("std.zig"); |
| 3 | 3 | const assert = std.debug.assert; |
| 4 | 4 | const Writer = std.Io.Writer; |
| 5 | const File = std.fs.File; | |
| 5 | const File = std.Io.File; | |
| 6 | 6 | |
| 7 | 7 | pub const Client = @import("http/Client.zig"); |
| 8 | 8 | pub 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 | 1473 | /// |
| 1474 | 1474 | /// This function is threadsafe. |
| 1475 | 1475 | pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connection { |
| 1476 | const io = client.io; | |
| 1477 | ||
| 1476 | 1478 | if (client.connection_pool.findConnection(.{ |
| 1477 | 1479 | .host = path, |
| 1478 | 1480 | .port = 0, |
| ... | ... | @@ -1485,7 +1487,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti |
| 1485 | 1487 | conn.* = .{ .data = undefined }; |
| 1486 | 1488 | |
| 1487 | 1489 | const stream = try Io.net.connectUnixSocket(path); |
| 1488 | errdefer stream.close(); | |
| 1490 | errdefer stream.close(io); | |
| 1489 | 1491 | |
| 1490 | 1492 | conn.data = .{ |
| 1491 | 1493 | .stream = stream, |
lib/std/json/dynamic.zig+3-4| ... | ... | @@ -47,10 +47,9 @@ pub const Value = union(enum) { |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | 49 | pub fn dump(v: Value) void { |
| 50 | const w, _ = std.debug.lockStderrWriter(&.{}); | |
| 51 | defer std.debug.unlockStderrWriter(); | |
| 52 | ||
| 53 | json.Stringify.value(v, .{}, w) catch return; | |
| 50 | const stderr = std.debug.lockStderr(&.{}, null); | |
| 51 | defer std.debug.unlockStderr(); | |
| 52 | json.Stringify.value(v, .{}, &stderr.file_writer.interface) catch return; | |
| 54 | 53 | } |
| 55 | 54 | |
| 56 | 55 | pub fn jsonStringify(value: @This(), jws: anytype) !void { |
lib/std/log.zig+31-15| ... | ... | @@ -15,7 +15,7 @@ |
| 15 | 15 | //! |
| 16 | 16 | //! For an example implementation of the `logFn` function, see `defaultLog`, |
| 17 | 17 | //! 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 | 20 | //! error: this is an error |
| 21 | 21 | //! 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 | 80 | return @intFromEnum(level) <= @intFromEnum(std.options.log_level); |
| 81 | 81 | } |
| 82 | 82 | |
| 83 | pub const terminalMode = std.options.logTerminalMode; | |
| 84 | ||
| 85 | pub fn defaultTerminalMode() std.Io.Terminal.Mode { | |
| 86 | const stderr = std.debug.lockStderr(&.{}).terminal(); | |
| 87 | std.debug.unlockStderr(); | |
| 88 | return stderr.mode; | |
| 89 | } | |
| 90 | ||
| 83 | 91 | /// The default implementation for the log function. Custom log functions may |
| 84 | 92 | /// forward log messages to this function. |
| 85 | 93 | /// |
| ... | ... | @@ -92,25 +100,33 @@ pub fn defaultLog( |
| 92 | 100 | args: anytype, |
| 93 | 101 | ) void { |
| 94 | 102 | var buffer: [64]u8 = undefined; |
| 95 | const stderr, const ttyconf = std.debug.lockStderrWriter(&buffer); | |
| 96 | defer std.debug.unlockStderrWriter(); | |
| 97 | ttyconf.setColor(stderr, switch (level) { | |
| 103 | const stderr = std.debug.lockStderr(&buffer).terminal(); | |
| 104 | defer std.debug.unlockStderr(); | |
| 105 | return defaultLogFileTerminal(level, scope, format, args, stderr) catch {}; | |
| 106 | } | |
| 107 | ||
| 108 | pub 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 | 116 | .err => .red, |
| 99 | 117 | .warn => .yellow, |
| 100 | 118 | .info => .green, |
| 101 | 119 | .debug => .magenta, |
| 102 | 120 | }) catch {}; |
| 103 | ttyconf.setColor(stderr, .bold) catch {}; | |
| 104 | stderr.writeAll(level.asText()) catch return; | |
| 105 | ttyconf.setColor(stderr, .reset) catch {}; | |
| 106 | ttyconf.setColor(stderr, .dim) catch {}; | |
| 107 | ttyconf.setColor(stderr, .bold) catch {}; | |
| 108 | if (scope != .default) { | |
| 109 | stderr.print("({s})", .{@tagName(scope)}) catch return; | |
| 110 | } | |
| 111 | stderr.writeAll(": ") catch return; | |
| 112 | ttyconf.setColor(stderr, .reset) catch {}; | |
| 113 | stderr.print(format ++ "\n", args) catch return; | |
| 121 | t.setColor(.bold) catch {}; | |
| 122 | try t.writer.writeAll(level.asText()); | |
| 123 | t.setColor(.reset) catch {}; | |
| 124 | t.setColor(.dim) catch {}; | |
| 125 | t.setColor(.bold) catch {}; | |
| 126 | if (scope != .default) try t.writer.print("({t})", .{scope}); | |
| 127 | try t.writer.writeAll(": "); | |
| 128 | t.setColor(.reset) catch {}; | |
| 129 | try t.writer.print(format ++ "\n", args); | |
| 114 | 130 | } |
| 115 | 131 | |
| 116 | 132 | /// 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 | 21 | const elf = std.elf; |
| 22 | 22 | const fs = std.fs; |
| 23 | 23 | const dl = @import("dynamic_library.zig"); |
| 24 | const max_path_bytes = std.fs.max_path_bytes; | |
| 25 | 24 | const posix = std.posix; |
| 26 | 25 | const native_os = builtin.os.tag; |
| 27 | 26 | |
| ... | ... | @@ -31,7 +30,6 @@ pub const uefi = @import("os/uefi.zig"); |
| 31 | 30 | pub const wasi = @import("os/wasi.zig"); |
| 32 | 31 | pub const emscripten = @import("os/emscripten.zig"); |
| 33 | 32 | pub const windows = @import("os/windows.zig"); |
| 34 | pub const freebsd = @import("os/freebsd.zig"); | |
| 35 | 33 | |
| 36 | 34 | test { |
| 37 | 35 | _ = linux; |
| ... | ... | @@ -56,135 +54,6 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_o |
| 56 | 54 | else => undefined, |
| 57 | 55 | }; |
| 58 | 56 | |
| 59 | pub 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. | |
| 91 | pub 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 | ||
| 188 | 57 | pub const FstatError = error{ |
| 189 | 58 | SystemResources, |
| 190 | 59 | AccessDenied, |
| ... | ... | @@ -203,3 +72,8 @@ pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t { |
| 203 | 72 | else => |err| return posix.unexpectedErrno(err), |
| 204 | 73 | } |
| 205 | 74 | } |
| 75 | ||
| 76 | pub 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 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const fd_t = std.c.fd_t; | |
| 3 | const off_t = std.c.off_t; | |
| 4 | const unexpectedErrno = std.posix.unexpectedErrno; | |
| 5 | const errno = std.posix.errno; | |
| 6 | const builtin = @import("builtin"); | |
| 7 | ||
| 8 | pub 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 | ||
| 36 | 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 { | |
| 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 | 1420 | if (@hasField(SYS, "chmod")) { |
| 1421 | 1421 | return syscall2(.chmod, @intFromPtr(path), mode); |
| 1422 | 1422 | } else { |
| 1423 | return fchmodat(AT.FDCWD, path, mode, 0); | |
| 1423 | return fchmodat(AT.FDCWD, path, mode); | |
| 1424 | 1424 | } |
| 1425 | 1425 | } |
| 1426 | 1426 | |
| ... | ... | @@ -1432,7 +1432,7 @@ pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize { |
| 1432 | 1432 | } |
| 1433 | 1433 | } |
| 1434 | 1434 | |
| 1435 | pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t, _: u32) usize { | |
| 1435 | pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t) usize { | |
| 1436 | 1436 | return syscall3(.fchmodat, @bitCast(@as(isize, fd)), @intFromPtr(path), mode); |
| 1437 | 1437 | } |
| 1438 | 1438 | |
| ... | ... | @@ -1561,14 +1561,14 @@ pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) usize { |
| 1561 | 1561 | } |
| 1562 | 1562 | } |
| 1563 | 1563 | |
| 1564 | pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: i32) usize { | |
| 1564 | pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: u32) usize { | |
| 1565 | 1565 | return syscall5( |
| 1566 | 1566 | .linkat, |
| 1567 | 1567 | @as(usize, @bitCast(@as(isize, oldfd))), |
| 1568 | 1568 | @intFromPtr(oldpath), |
| 1569 | 1569 | @as(usize, @bitCast(@as(isize, newfd))), |
| 1570 | 1570 | @intFromPtr(newpath), |
| 1571 | @as(usize, @bitCast(@as(isize, flags))), | |
| 1571 | flags, | |
| 1572 | 1572 | ); |
| 1573 | 1573 | } |
| 1574 | 1574 | |
| ... | ... | @@ -6040,7 +6040,7 @@ pub const dirent64 = extern struct { |
| 6040 | 6040 | off: u64, |
| 6041 | 6041 | reclen: u16, |
| 6042 | 6042 | type: u8, |
| 6043 | name: u8, // field address is the address of first byte of name https://github.com/ziglang/zig/issues/173 | |
| 6043 | name: [0]u8, | |
| 6044 | 6044 | }; |
| 6045 | 6045 | |
| 6046 | 6046 | pub const dl_phdr_info = extern struct { |
| ... | ... | @@ -6891,10 +6891,6 @@ pub const utsname = extern struct { |
| 6891 | 6891 | }; |
| 6892 | 6892 | pub const HOST_NAME_MAX = 64; |
| 6893 | 6893 | |
| 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. | |
| 6898 | 6894 | pub const STATX = packed struct(u32) { |
| 6899 | 6895 | /// Want `mode & S.IFMT`. |
| 6900 | 6896 | TYPE: bool = false, |
| ... | ... | @@ -6982,7 +6978,9 @@ pub const statx_timestamp = extern struct { |
| 6982 | 6978 | |
| 6983 | 6979 | /// Renamed to `Statx` to not conflict with the `statx` function. |
| 6984 | 6980 | pub 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 | 6984 | mask: STATX, |
| 6987 | 6985 | /// Block size for filesystem I/O. |
| 6988 | 6986 | blksize: u32, |
| ... | ... | @@ -9872,165 +9870,3 @@ pub const cmsghdr = extern struct { |
| 9872 | 9870 | level: i32, |
| 9873 | 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. | |
| 9878 | pub 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 | 1 | const IoUring = @This(); |
| 2 | const std = @import("std"); | |
| 2 | ||
| 3 | 3 | const builtin = @import("builtin"); |
| 4 | const is_linux = builtin.os.tag == .linux; | |
| 5 | ||
| 6 | const std = @import("../../std.zig"); | |
| 7 | const Io = std.Io; | |
| 8 | const Allocator = std.mem.Allocator; | |
| 4 | 9 | const assert = std.debug.assert; |
| 5 | const mem = std.mem; | |
| 6 | const net = std.Io.net; | |
| 7 | 10 | const posix = std.posix; |
| 8 | 11 | const linux = std.os.linux; |
| 9 | 12 | const testing = std.testing; |
| 10 | const is_linux = builtin.os.tag == .linux; | |
| 11 | 13 | const page_size_min = std.heap.page_size_min; |
| 14 | const createSocketTestHarness = @import("IoUring/test.zig").createSocketTestHarness; | |
| 12 | 15 | |
| 13 | 16 | fd: linux.fd_t = -1, |
| 14 | 17 | sq: SubmissionQueue, |
| ... | ... | @@ -22,7 +25,7 @@ features: u32, |
| 22 | 25 | /// see https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L8027-L8050. |
| 23 | 26 | /// Matches the interface of io_uring_queue_init() in liburing. |
| 24 | 27 | pub 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 | 29 | .flags = flags, |
| 27 | 30 | .sq_thread_idle = 1000, |
| 28 | 31 | }); |
| ... | ... | @@ -1309,7 +1312,7 @@ pub fn unregister_buffers(self: *IoUring) !void { |
| 1309 | 1312 | /// io_uring subsystem of the running kernel. The io_uring_probe contains the |
| 1310 | 1313 | /// list of supported operations. |
| 1311 | 1314 | pub 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 | 1316 | const res = linux.io_uring_register(self.fd, .REGISTER_PROBE, &probe, probe.ops.len); |
| 1314 | 1317 | try handle_register_buf_ring_result(res); |
| 1315 | 1318 | return probe; |
| ... | ... | @@ -1636,7 +1639,7 @@ pub const BufferGroup = struct { |
| 1636 | 1639 | |
| 1637 | 1640 | pub fn init( |
| 1638 | 1641 | ring: *IoUring, |
| 1639 | allocator: mem.Allocator, | |
| 1642 | allocator: Allocator, | |
| 1640 | 1643 | group_id: u16, |
| 1641 | 1644 | buffer_size: u32, |
| 1642 | 1645 | buffers_count: u16, |
| ... | ... | @@ -1670,7 +1673,7 @@ pub const BufferGroup = struct { |
| 1670 | 1673 | }; |
| 1671 | 1674 | } |
| 1672 | 1675 | |
| 1673 | pub fn deinit(self: *BufferGroup, allocator: mem.Allocator) void { | |
| 1676 | pub fn deinit(self: *BufferGroup, allocator: Allocator) void { | |
| 1674 | 1677 | free_buf_ring(self.ring.fd, self.br, self.buffers_count, self.group_id); |
| 1675 | 1678 | allocator.free(self.buffers); |
| 1676 | 1679 | allocator.free(self.heads); |
| ... | ... | @@ -1695,7 +1698,7 @@ pub const BufferGroup = struct { |
| 1695 | 1698 | } |
| 1696 | 1699 | |
| 1697 | 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 | 1702 | const pos = self.buffer_size * buffer_id; |
| 1700 | 1703 | return self.buffers[pos .. pos + self.buffer_size][self.heads[buffer_id]..]; |
| 1701 | 1704 | } |
| ... | ... | @@ -1764,7 +1767,7 @@ fn register_buf_ring( |
| 1764 | 1767 | group_id: u16, |
| 1765 | 1768 | flags: linux.io_uring_buf_reg.Flags, |
| 1766 | 1769 | ) !void { |
| 1767 | var reg = mem.zeroInit(linux.io_uring_buf_reg, .{ | |
| 1770 | var reg = std.mem.zeroInit(linux.io_uring_buf_reg, .{ | |
| 1768 | 1771 | .ring_addr = addr, |
| 1769 | 1772 | .ring_entries = entries, |
| 1770 | 1773 | .bgid = group_id, |
| ... | ... | @@ -1781,7 +1784,7 @@ fn register_buf_ring( |
| 1781 | 1784 | } |
| 1782 | 1785 | |
| 1783 | 1786 | fn 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 | 1788 | .bgid = group_id, |
| 1786 | 1789 | }); |
| 1787 | 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 | 1851 | @atomicStore(u16, &br.tail, tail, .release); |
| 1849 | 1852 | } |
| 1850 | 1853 | |
| 1851 | test "structs/offsets/entries" { | |
| 1852 | if (!is_linux) return error.SkipZigTest; | |
| 1853 | ||
| 1854 | try testing.expectEqual(@as(usize, 120), @sizeOf(linux.io_uring_params)); | |
| 1855 | try testing.expectEqual(@as(usize, 64), @sizeOf(linux.io_uring_sqe)); | |
| 1856 | try testing.expectEqual(@as(usize, 16), @sizeOf(linux.io_uring_cqe)); | |
| 1857 | ||
| 1858 | try testing.expectEqual(0, linux.IORING_OFF_SQ_RING); | |
| 1859 | try testing.expectEqual(0x8000000, linux.IORING_OFF_CQ_RING); | |
| 1860 | try testing.expectEqual(0x10000000, linux.IORING_OFF_SQES); | |
| 1861 | ||
| 1862 | try testing.expectError(error.EntriesZero, IoUring.init(0, 0)); | |
| 1863 | try testing.expectError(error.EntriesNotPowerOfTwo, IoUring.init(3, 0)); | |
| 1864 | } | |
| 1865 | ||
| 1866 | test "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 | ||
| 1933 | test "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 | ||
| 1975 | test "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 | ||
| 2045 | test "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 | ||
| 2092 | test "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 | ||
| 2163 | test "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 | ||
| 2228 | test "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 | ||
| 2282 | test "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 | ||
| 2313 | test "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 | ||
| 2354 | test "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 | ||
| 2446 | test "timeout (after a relative time)" { | |
| 1854 | test BufferGroup { | |
| 2447 | 1855 | if (!is_linux) return error.SkipZigTest; |
| 2448 | 1856 | |
| 2449 | 1857 | const io = testing.io; |
| 1858 | _ = io; | |
| 2450 | 1859 | |
| 2451 | var ring = IoUring.init(1, 0) catch |err| switch (err) { | |
| 2452 | error.SystemOutdated => return error.SkipZigTest, | |
| 2453 | error.PermissionDenied => return error.SkipZigTest, | |
| 2454 | else => return err, | |
| 2455 | }; | |
| 2456 | defer ring.deinit(); | |
| 2457 | ||
| 2458 | const ms = 10; | |
| 2459 | const margin = 5; | |
| 2460 | const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 }; | |
| 2461 | ||
| 2462 | const started = try std.Io.Clock.awake.now(io); | |
| 2463 | const sqe = try ring.timeout(0x55555555, &ts, 0, 0); | |
| 2464 | try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode); | |
| 2465 | try testing.expectEqual(@as(u32, 1), try ring.submit()); | |
| 2466 | const cqe = try ring.copy_cqe(); | |
| 2467 | const stopped = try std.Io.Clock.awake.now(io); | |
| 2468 | ||
| 2469 | try testing.expectEqual(linux.io_uring_cqe{ | |
| 2470 | .user_data = 0x55555555, | |
| 2471 | .res = -@as(i32, @intFromEnum(linux.E.TIME)), | |
| 2472 | .flags = 0, | |
| 2473 | }, cqe); | |
| 2474 | ||
| 2475 | // Tests should not depend on timings: skip test if outside margin. | |
| 2476 | const ms_elapsed = started.durationTo(stopped).toMilliseconds(); | |
| 2477 | if (ms_elapsed > margin) return error.SkipZigTest; | |
| 2478 | } | |
| 2479 | ||
| 2480 | test "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 | ||
| 2513 | test "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 | ||
| 2573 | test "accept/connect/recv/link_timeout" { | |
| 2574 | if (!is_linux) return error.SkipZigTest; | |
| 2575 | ||
| 1860 | // Init IoUring | |
| 2576 | 1861 | var ring = IoUring.init(16, 0) catch |err| switch (err) { |
| 2577 | 1862 | error.SystemOutdated => return error.SkipZigTest, |
| 2578 | 1863 | error.PermissionDenied => return error.SkipZigTest, |
| ... | ... | @@ -2580,2052 +1865,60 @@ test "accept/connect/recv/link_timeout" { |
| 2580 | 1865 | }; |
| 2581 | 1866 | defer ring.deinit(); |
| 2582 | 1867 | |
| 2583 | const socket_test_harness = try createSocketTestHarness(&ring); | |
| 2584 | defer socket_test_harness.close(); | |
| 2585 | ||
| 2586 | var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 }; | |
| 2587 | ||
| 2588 | const sqe_recv = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0); | |
| 2589 | sqe_recv.flags |= linux.IOSQE_IO_LINK; | |
| 2590 | ||
| 2591 | const ts = linux.kernel_timespec{ .sec = 0, .nsec = 1000000 }; | |
| 2592 | _ = try ring.link_timeout(0x22222222, &ts, 0); | |
| 2593 | ||
| 2594 | const nr_wait = try ring.submit(); | |
| 2595 | try testing.expectEqual(@as(u32, 2), nr_wait); | |
| 2596 | ||
| 2597 | var i: usize = 0; | |
| 2598 | while (i < nr_wait) : (i += 1) { | |
| 2599 | const cqe = try ring.copy_cqe(); | |
| 2600 | switch (cqe.user_data) { | |
| 2601 | 0xffffffff => { | |
| 2602 | if (cqe.res != -@as(i32, @intFromEnum(linux.E.INTR)) and | |
| 2603 | cqe.res != -@as(i32, @intFromEnum(linux.E.CANCELED))) | |
| 2604 | { | |
| 2605 | std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res }); | |
| 2606 | try testing.expect(false); | |
| 2607 | } | |
| 2608 | }, | |
| 2609 | 0x22222222 => { | |
| 2610 | if (cqe.res != -@as(i32, @intFromEnum(linux.E.ALREADY)) and | |
| 2611 | cqe.res != -@as(i32, @intFromEnum(linux.E.TIME))) | |
| 2612 | { | |
| 2613 | std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res }); | |
| 2614 | try testing.expect(false); | |
| 2615 | } | |
| 2616 | }, | |
| 2617 | else => @panic("should not happen"), | |
| 2618 | } | |
| 2619 | } | |
| 2620 | } | |
| 2621 | ||
| 2622 | test "fallocate" { | |
| 2623 | if (!is_linux) return error.SkipZigTest; | |
| 2624 | ||
| 2625 | var ring = IoUring.init(1, 0) catch |err| switch (err) { | |
| 2626 | error.SystemOutdated => return error.SkipZigTest, | |
| 2627 | error.PermissionDenied => return error.SkipZigTest, | |
| 1868 | // Init buffer group for ring | |
| 1869 | const group_id: u16 = 1; // buffers group id | |
| 1870 | const buffers_count: u16 = 1; // number of buffers in buffer group | |
| 1871 | const buffer_size: usize = 128; // size of each buffer in group | |
| 1872 | var buf_grp = BufferGroup.init( | |
| 1873 | &ring, | |
| 1874 | testing.allocator, | |
| 1875 | group_id, | |
| 1876 | buffer_size, | |
| 1877 | buffers_count, | |
| 1878 | ) catch |err| switch (err) { | |
| 1879 | // kernel older than 5.19 | |
| 1880 | error.ArgumentsInvalid => return error.SkipZigTest, | |
| 2628 | 1881 | else => return err, |
| 2629 | 1882 | }; |
| 2630 | defer ring.deinit(); | |
| 2631 | ||
| 2632 | var tmp = std.testing.tmpDir(.{}); | |
| 2633 | defer tmp.cleanup(); | |
| 2634 | ||
| 2635 | const path = "test_io_uring_fallocate"; | |
| 2636 | const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); | |
| 2637 | defer file.close(); | |
| 2638 | ||
| 2639 | try testing.expectEqual(@as(u64, 0), (try file.stat()).size); | |
| 1883 | defer buf_grp.deinit(testing.allocator); | |
| 2640 | 1884 | |
| 2641 | const len: u64 = 65536; | |
| 2642 | const sqe = try ring.fallocate(0xaaaaaaaa, file.handle, 0, 0, len); | |
| 2643 | try testing.expectEqual(linux.IORING_OP.FALLOCATE, sqe.opcode); | |
| 2644 | try testing.expectEqual(file.handle, sqe.fd); | |
| 2645 | try testing.expectEqual(@as(u32, 1), try ring.submit()); | |
| 1885 | // Create client/server fds | |
| 1886 | const fds = try createSocketTestHarness(&ring); | |
| 1887 | defer fds.close(); | |
| 1888 | const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe }; | |
| 2646 | 1889 | |
| 2647 | const cqe = try ring.copy_cqe(); | |
| 2648 | switch (cqe.err()) { | |
| 2649 | .SUCCESS => {}, | |
| 2650 | // This kernel's io_uring does not yet implement fallocate(): | |
| 2651 | .INVAL => return error.SkipZigTest, | |
| 2652 | // This kernel does not implement fallocate(): | |
| 2653 | .NOSYS => return error.SkipZigTest, | |
| 2654 | // The filesystem containing the file referred to by fd does not support this operation; | |
| 2655 | // or the mode is not supported by the filesystem containing the file referred to by fd: | |
| 2656 | .OPNOTSUPP => return error.SkipZigTest, | |
| 2657 | else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), | |
| 1890 | // Client sends data | |
| 1891 | { | |
| 1892 | _ = try ring.send(1, fds.client, data[0..], 0); | |
| 1893 | const submitted = try ring.submit(); | |
| 1894 | try testing.expectEqual(1, submitted); | |
| 1895 | const cqe_send = try ring.copy_cqe(); | |
| 1896 | if (cqe_send.err() == .INVAL) return error.SkipZigTest; | |
| 1897 | try testing.expectEqual(linux.io_uring_cqe{ .user_data = 1, .res = data.len, .flags = 0 }, cqe_send); | |
| 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 | ||
| 2668 | test "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); | |
| 2685 | 1899 | |
| 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); | |
| 2687 | 1906 | |
| 2688 | var buf: linux.Statx = undefined; | |
| 2689 | const sqe = try ring.statx( | |
| 2690 | 0xaaaaaaaa, | |
| 2691 | tmp.dir.fd, | |
| 2692 | path, | |
| 2693 | 0, | |
| 2694 | .{ .SIZE = true }, | |
| 2695 | &buf, | |
| 2696 | ); | |
| 2697 | try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode); | |
| 2698 | try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); | |
| 2699 | try testing.expectEqual(@as(u32, 1), try ring.submit()); | |
| 1907 | // ... when we have completion for recv operation | |
| 1908 | const cqe = try ring.copy_cqe(); | |
| 1909 | try testing.expectEqual(2, cqe.user_data); // matches submitted user_data | |
| 1910 | try testing.expect(cqe.res >= 0); // success | |
| 1911 | try testing.expectEqual(posix.E.SUCCESS, cqe.err()); | |
| 1912 | try testing.expectEqual(data.len, @as(usize, @intCast(cqe.res))); // cqe.res holds received data len | |
| 2700 | 1913 | |
| 2701 | const cqe = try ring.copy_cqe(); | |
| 2702 | switch (cqe.err()) { | |
| 2703 | .SUCCESS => {}, | |
| 2704 | // This kernel's io_uring does not yet implement statx(): | |
| 2705 | .INVAL => return error.SkipZigTest, | |
| 2706 | // This kernel does not implement statx(): | |
| 2707 | .NOSYS => return error.SkipZigTest, | |
| 2708 | // The filesystem containing the file referred to by fd does not support this operation; | |
| 2709 | // or the mode is not supported by the filesystem containing the file referred to by fd: | |
| 2710 | .OPNOTSUPP => return error.SkipZigTest, | |
| 2711 | // not supported on older kernels (5.4) | |
| 2712 | .BADF => return error.SkipZigTest, | |
| 2713 | else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), | |
| 1914 | // Get buffer from pool | |
| 1915 | const buf = try buf_grp.get(cqe); | |
| 1916 | try testing.expectEqualSlices(u8, &data, buf); | |
| 1917 | // Release buffer to the kernel when application is done with it | |
| 1918 | try buf_grp.put(cqe); | |
| 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 | ||
| 2725 | test "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 | ||
| 2775 | test "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 | ||
| 2862 | test "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 | ||
| 2929 | test "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 | ||
| 2987 | test "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 | ||
| 3039 | test "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 | ||
| 3083 | test "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 | ||
| 3131 | test "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 | ||
| 3186 | test "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 | ||
| 3318 | test "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 | ||
| 3407 | test "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. | |
| 3574 | const 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 | ||
| 3585 | fn 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 | ||
| 3638 | fn 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 | ||
| 3654 | test "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 | ||
| 3696 | test "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 | ||
| 3752 | test "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 | ||
| 3835 | test "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 | ||
| 3901 | test "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 | ||
| 3924 | test "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 | ||
| 4005 | test "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 | ||
| 4056 | test "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. | |
| 4083 | inline 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 | ||
| 4105 | test 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 | ||
| 4170 | test "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 | ||
| 4259 | test "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. | |
| 4399 | fn 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 | ||
| 4420 | fn 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 | ||
| 4443 | test "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 | ||
| 4490 | test "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 | ||
| 4598 | fn 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 | } |
| 4628 | 1921 | |
| 4629 | fn addrAny(addr: *linux.sockaddr.in) *linux.sockaddr { | |
| 4630 | return @ptrCast(addr); | |
| 1922 | test { | |
| 1923 | if (is_linux) _ = @import("IoUring/test.zig"); | |
| 4631 | 1924 | } |
lib/std/os/linux/IoUring/test.zig created+2691| ... | ... | @@ -0,0 +1,2691 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | ||
| 3 | const std = @import("../../../std.zig"); | |
| 4 | const Io = std.Io; | |
| 5 | const mem = std.mem; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const testing = std.testing; | |
| 8 | const linux = std.os.linux; | |
| 9 | ||
| 10 | const IoUring = std.os.linux.IoUring; | |
| 11 | const BufferGroup = IoUring.BufferGroup; | |
| 12 | ||
| 13 | const posix = std.posix; | |
| 14 | const iovec = posix.iovec; | |
| 15 | const iovec_const = posix.iovec_const; | |
| 16 | ||
| 17 | comptime { | |
| 18 | assert(builtin.os.tag == .linux); | |
| 19 | } | |
| 20 | ||
| 21 | test "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 | ||
| 34 | test "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 | ||
| 99 | test "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 | ||
| 141 | test "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 | ||
| 211 | test "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 | ||
| 258 | test "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 | ||
| 329 | test "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 | ||
| 394 | test "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 | ||
| 446 | test "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 | ||
| 477 | test "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 | ||
| 519 | test "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 | ||
| 609 | test "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 | ||
| 641 | test "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 | ||
| 672 | test "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 | ||
| 730 | test "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 | ||
| 780 | test "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 | ||
| 826 | test "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 | ||
| 883 | test "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 | ||
| 934 | test "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 | ||
| 1019 | test "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 | ||
| 1084 | test "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 | ||
| 1142 | test "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 | ||
| 1194 | test "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 | ||
| 1238 | test "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 | ||
| 1286 | test "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 | ||
| 1341 | test "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 | ||
| 1471 | test "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 | ||
| 1558 | test "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 | ||
| 1725 | test "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 | ||
| 1765 | test "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 | ||
| 1824 | test "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 | ||
| 1907 | test "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 | ||
| 1973 | test "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 | ||
| 1996 | test "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 | ||
| 2077 | test "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 | ||
| 2128 | test "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 | ||
| 2218 | test "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 | ||
| 2358 | test "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 | ||
| 2403 | test "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. | |
| 2512 | fn 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 | ||
| 2533 | fn 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 | ||
| 2556 | fn 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. | |
| 2588 | pub 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 | ||
| 2599 | pub 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 | ||
| 2652 | fn 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. | |
| 2669 | inline 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 | ||
| 2689 | fn 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 | 12 | test "fallocate" { |
| 13 | 13 | if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/30220 |
| 14 | 14 | |
| 15 | const io = std.testing.io; | |
| 16 | ||
| 15 | 17 | var tmp = std.testing.tmpDir(.{}); |
| 16 | 18 | defer tmp.cleanup(); |
| 17 | 19 | |
| 18 | 20 | const path = "test_fallocate"; |
| 19 | const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); | |
| 20 | defer file.close(); | |
| 21 | const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .permissions = .fromMode(0o666) }); | |
| 22 | defer file.close(io); | |
| 21 | 23 | |
| 22 | try expect((try file.stat()).size == 0); | |
| 24 | try expect((try file.stat(io)).size == 0); | |
| 23 | 25 | |
| 24 | 26 | const len: i64 = 65536; |
| 25 | 27 | switch (linux.errno(linux.fallocate(file.handle, 0, 0, len))) { |
| ... | ... | @@ -29,7 +31,7 @@ test "fallocate" { |
| 29 | 31 | else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), |
| 30 | 32 | } |
| 31 | 33 | |
| 32 | try expect((try file.stat()).size == len); | |
| 34 | try expect((try file.stat(io)).size == len); | |
| 33 | 35 | } |
| 34 | 36 | |
| 35 | 37 | test "getpid" { |
| ... | ... | @@ -77,12 +79,14 @@ test "timer" { |
| 77 | 79 | } |
| 78 | 80 | |
| 79 | 81 | test "statx" { |
| 82 | const io = std.testing.io; | |
| 83 | ||
| 80 | 84 | var tmp = std.testing.tmpDir(.{}); |
| 81 | 85 | defer tmp.cleanup(); |
| 82 | 86 | |
| 83 | 87 | const tmp_file_name = "just_a_temporary_file.txt"; |
| 84 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 85 | defer file.close(); | |
| 88 | var file = try tmp.dir.createFile(io, tmp_file_name, .{}); | |
| 89 | defer file.close(io); | |
| 86 | 90 | |
| 87 | 91 | var buf: linux.Statx = undefined; |
| 88 | 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 | 115 | } |
| 112 | 116 | |
| 113 | 117 | test "fadvise" { |
| 118 | const io = std.testing.io; | |
| 119 | ||
| 114 | 120 | var tmp = std.testing.tmpDir(.{}); |
| 115 | 121 | defer tmp.cleanup(); |
| 116 | 122 | |
| 117 | 123 | const tmp_file_name = "temp_posix_fadvise.txt"; |
| 118 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 119 | defer file.close(); | |
| 124 | var file = try tmp.dir.createFile(io, tmp_file_name, .{}); | |
| 125 | defer file.close(io); | |
| 120 | 126 | |
| 121 | 127 | var buf: [2048]u8 = undefined; |
| 122 | try file.writeAll(&buf); | |
| 128 | try file.writeStreamingAll(io, &buf); | |
| 123 | 129 | |
| 124 | 130 | const ret = linux.fadvise(file.handle, 0, 0, linux.POSIX_FADV.SEQUENTIAL); |
| 125 | 131 | try expectEqual(@as(usize, 0), ret); |
| ... | ... | @@ -401,14 +407,6 @@ test "futex2_requeue" { |
| 401 | 407 | try expectEqual(0, rc); |
| 402 | 408 | } |
| 403 | 409 | |
| 404 | test "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 | ||
| 412 | 410 | test { |
| 413 | 411 | _ = linux.IoUring; |
| 414 | 412 | } |
lib/std/os/uefi/protocol/file.zig-9| ... | ... | @@ -163,15 +163,6 @@ pub const File = extern struct { |
| 163 | 163 | } |
| 164 | 164 | } |
| 165 | 165 | |
| 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 | 166 | pub fn setPosition(self: *File, position: u64) SeekError!void { |
| 176 | 167 | switch (self._set_position(self, position)) { |
| 177 | 168 | .success => {}, |
lib/std/os/windows.zig+20-237| ... | ... | @@ -215,6 +215,10 @@ pub const FILE = struct { |
| 215 | 215 | AccessFlags: ACCESS_MASK, |
| 216 | 216 | }; |
| 217 | 217 | |
| 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 | 222 | pub const RENAME_INFORMATION = extern struct { |
| 219 | 223 | Flags: FLAGS, |
| 220 | 224 | RootDirectory: ?HANDLE, |
| ... | ... | @@ -2310,17 +2314,15 @@ pub const OpenFileOptions = struct { |
| 2310 | 2314 | sa: ?*SECURITY_ATTRIBUTES = null, |
| 2311 | 2315 | share_access: FILE.SHARE = .VALID_FLAGS, |
| 2312 | 2316 | creation: FILE.CREATE_DISPOSITION, |
| 2313 | /// If true, tries to open path as a directory. | |
| 2314 | /// Defaults to false. | |
| 2315 | filter: Filter = .file_only, | |
| 2317 | filter: Filter = .non_directory_only, | |
| 2316 | 2318 | /// If false, tries to open path as a reparse point without dereferencing it. |
| 2317 | 2319 | /// Defaults to true. |
| 2318 | 2320 | follow_symlinks: bool = true, |
| 2319 | 2321 | |
| 2320 | 2322 | pub const Filter = enum { |
| 2321 | 2323 | /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory. |
| 2322 | file_only, | |
| 2323 | /// Causes `OpenFile` to return `error.NotDir` if the opened handle would be a file. | |
| 2324 | non_directory_only, | |
| 2325 | /// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory. | |
| 2324 | 2326 | dir_only, |
| 2325 | 2327 | /// `OpenFile` does not discriminate between opening files and directories. |
| 2326 | 2328 | any, |
| ... | ... | @@ -2328,10 +2330,10 @@ pub const OpenFileOptions = struct { |
| 2328 | 2330 | }; |
| 2329 | 2331 | |
| 2330 | 2332 | pub 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 | 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 | 2337 | return error.IsDir; |
| 2336 | 2338 | } |
| 2337 | 2339 | |
| ... | ... | @@ -2366,7 +2368,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 2366 | 2368 | options.creation, |
| 2367 | 2369 | .{ |
| 2368 | 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 | 2372 | .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, |
| 2371 | 2373 | .OPEN_REPARSE_POINT = !options.follow_symlinks, |
| 2372 | 2374 | }, |
| ... | ... | @@ -2828,149 +2830,6 @@ pub fn CloseHandle(hObject: HANDLE) void { |
| 2828 | 2830 | assert(ntdll.NtClose(hObject) == .SUCCESS); |
| 2829 | 2831 | } |
| 2830 | 2832 | |
| 2831 | pub 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. | |
| 2847 | pub 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 | ||
| 2883 | pub 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 | ||
| 2899 | pub 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 | ||
| 2940 | pub const SetCurrentDirectoryError = error{ | |
| 2941 | NameTooLong, | |
| 2942 | FileNotFound, | |
| 2943 | NotDir, | |
| 2944 | AccessDenied, | |
| 2945 | NoDevice, | |
| 2946 | BadPathName, | |
| 2947 | Unexpected, | |
| 2948 | }; | |
| 2949 | ||
| 2950 | pub 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 | ||
| 2974 | 2833 | pub const GetCurrentDirectoryError = error{ |
| 2975 | 2834 | NameTooLong, |
| 2976 | 2835 | Unexpected, |
| ... | ... | @@ -3040,7 +2899,7 @@ pub fn CreateSymbolicLink( |
| 3040 | 2899 | }, |
| 3041 | 2900 | .dir = dir, |
| 3042 | 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 | 2903 | }) catch |err| switch (err) { |
| 3045 | 2904 | error.IsDir => return error.PathAlreadyExists, |
| 3046 | 2905 | error.NotDir => return error.Unexpected, |
| ... | ... | @@ -3584,7 +3443,7 @@ test QueryObjectName { |
| 3584 | 3443 | //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. |
| 3585 | 3444 | var tmp = std.testing.tmpDir(.{}); |
| 3586 | 3445 | defer tmp.cleanup(); |
| 3587 | const handle = tmp.dir.fd; | |
| 3446 | const handle = tmp.dir.handle; | |
| 3588 | 3447 | var out_buffer: [PATH_MAX_WIDE]u16 = undefined; |
| 3589 | 3448 | |
| 3590 | 3449 | const result_path = try QueryObjectName(handle, &out_buffer); |
| ... | ... | @@ -3597,7 +3456,6 @@ test QueryObjectName { |
| 3597 | 3456 | |
| 3598 | 3457 | pub const GetFinalPathNameByHandleError = error{ |
| 3599 | 3458 | AccessDenied, |
| 3600 | BadPathName, | |
| 3601 | 3459 | FileNotFound, |
| 3602 | 3460 | NameTooLong, |
| 3603 | 3461 | /// The volume does not contain a recognized file system. File system |
| ... | ... | @@ -3622,6 +3480,8 @@ pub const GetFinalPathNameByHandleFormat = struct { |
| 3622 | 3480 | /// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`). |
| 3623 | 3481 | /// If DOS volume name format is selected, note that this function does *not* prepend |
| 3624 | 3482 | /// `\\?\` prefix to the resultant path. |
| 3483 | /// | |
| 3484 | /// TODO move this function into std.Io.Threaded and add cancelation checks | |
| 3625 | 3485 | pub fn GetFinalPathNameByHandle( |
| 3626 | 3486 | hFile: HANDLE, |
| 3627 | 3487 | fmt: GetFinalPathNameByHandleFormat, |
| ... | ... | @@ -3701,6 +3561,7 @@ pub fn GetFinalPathNameByHandle( |
| 3701 | 3561 | error.WouldBlock => return error.Unexpected, |
| 3702 | 3562 | error.NetworkNotFound => return error.Unexpected, |
| 3703 | 3563 | error.AntivirusInterference => return error.Unexpected, |
| 3564 | error.BadPathName => return error.Unexpected, | |
| 3704 | 3565 | else => |e| return e, |
| 3705 | 3566 | }; |
| 3706 | 3567 | defer CloseHandle(mgmt_handle); |
| ... | ... | @@ -3746,9 +3607,7 @@ pub fn GetFinalPathNameByHandle( |
| 3746 | 3607 | const total_len = drive_letter.len + file_name_u16.len; |
| 3747 | 3608 | |
| 3748 | 3609 | // Validate that DOS does not contain any spurious nul bytes. |
| 3749 | if (mem.findScalar(u16, out_buffer[0..total_len], 0)) |_| { | |
| 3750 | return error.BadPathName; | |
| 3751 | } | |
| 3610 | assert(mem.findScalar(u16, out_buffer[0..total_len], 0) == null); | |
| 3752 | 3611 | |
| 3753 | 3612 | return out_buffer[0..total_len]; |
| 3754 | 3613 | } else if (mountmgrIsVolumeName(symlink)) { |
| ... | ... | @@ -3798,9 +3657,7 @@ pub fn GetFinalPathNameByHandle( |
| 3798 | 3657 | const total_len = volume_path.len + file_name_u16.len; |
| 3799 | 3658 | |
| 3800 | 3659 | // Validate that DOS does not contain any spurious nul bytes. |
| 3801 | if (mem.findScalar(u16, out_buffer[0..total_len], 0)) |_| { | |
| 3802 | return error.BadPathName; | |
| 3803 | } | |
| 3660 | assert(mem.findScalar(u16, out_buffer[0..total_len], 0) == null); | |
| 3804 | 3661 | |
| 3805 | 3662 | return out_buffer[0..total_len]; |
| 3806 | 3663 | } |
| ... | ... | @@ -3847,7 +3704,7 @@ test GetFinalPathNameByHandle { |
| 3847 | 3704 | //any file will do |
| 3848 | 3705 | var tmp = std.testing.tmpDir(.{}); |
| 3849 | 3706 | defer tmp.cleanup(); |
| 3850 | const handle = tmp.dir.fd; | |
| 3707 | const handle = tmp.dir.handle; | |
| 3851 | 3708 | var buffer: [PATH_MAX_WIDE]u16 = undefined; |
| 3852 | 3709 | |
| 3853 | 3710 | //check with sufficient size |
| ... | ... | @@ -4248,80 +4105,6 @@ pub fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter |
| 4248 | 4105 | assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0); |
| 4249 | 4106 | } |
| 4250 | 4107 | |
| 4251 | pub const SetFileTimeError = error{Unexpected}; | |
| 4252 | ||
| 4253 | pub 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 | ||
| 4267 | pub const LockFileError = error{ | |
| 4268 | SystemResources, | |
| 4269 | WouldBlock, | |
| 4270 | } || UnexpectedError; | |
| 4271 | ||
| 4272 | pub 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 | ||
| 4305 | pub const UnlockFileError = error{ | |
| 4306 | RangeNotLocked, | |
| 4307 | } || UnexpectedError; | |
| 4308 | ||
| 4309 | pub 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 | 4108 | /// This is a workaround for the C backend until zig has the ability to put |
| 4326 | 4109 | /// C code in inline assembly. |
| 4327 | 4110 | extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque; |
| ... | ... | @@ -4713,8 +4496,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE |
| 4713 | 4496 | break :path_to_get path; |
| 4714 | 4497 | } |
| 4715 | 4498 | // We can also skip GetFinalPathNameByHandle if the handle matches |
| 4716 | // the handle returned by fs.cwd() | |
| 4717 | if (dir.? == std.fs.cwd().fd) { | |
| 4499 | // the handle returned by Io.Dir.cwd() | |
| 4500 | if (dir.? == Io.Dir.cwd().handle) { | |
| 4718 | 4501 | break :path_to_get path; |
| 4719 | 4502 | } |
| 4720 | 4503 | // 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 | 12 | const mem = std.mem; |
| 13 | 13 | const coff = std.coff; |
| 14 | 14 | const fs = std.fs; |
| 15 | const File = std.fs.File; | |
| 15 | const File = std.Io.File; | |
| 16 | 16 | const debug = std.debug; |
| 17 | 17 | |
| 18 | 18 | const ArrayList = std.ArrayList; |
lib/std/posix.zig+404-2954| ... | ... | @@ -15,15 +15,16 @@ |
| 15 | 15 | //! deal with the exception. |
| 16 | 16 | |
| 17 | 17 | const builtin = @import("builtin"); |
| 18 | const root = @import("root"); | |
| 18 | const native_os = builtin.os.tag; | |
| 19 | ||
| 19 | 20 | const std = @import("std.zig"); |
| 21 | const Io = std.Io; | |
| 20 | 22 | const mem = std.mem; |
| 21 | 23 | const fs = std.fs; |
| 22 | const max_path_bytes = fs.max_path_bytes; | |
| 24 | const max_path_bytes = std.fs.max_path_bytes; | |
| 23 | 25 | const maxInt = std.math.maxInt; |
| 24 | 26 | const cast = std.math.cast; |
| 25 | 27 | const assert = std.debug.assert; |
| 26 | const native_os = builtin.os.tag; | |
| 27 | 28 | const page_size_min = std.heap.page_size_min; |
| 28 | 29 | |
| 29 | 30 | test { |
| ... | ... | @@ -53,6 +54,7 @@ else switch (native_os) { |
| 53 | 54 | pub const uid_t = void; |
| 54 | 55 | pub const gid_t = void; |
| 55 | 56 | pub const mode_t = u0; |
| 57 | pub const nlink_t = u0; | |
| 56 | 58 | pub const ino_t = void; |
| 57 | 59 | pub const IFNAMESIZE = {}; |
| 58 | 60 | pub const SIG = void; |
| ... | ... | @@ -309,274 +311,6 @@ pub fn close(fd: fd_t) void { |
| 309 | 311 | } |
| 310 | 312 | } |
| 311 | 313 | |
| 312 | pub 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. | |
| 327 | pub 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 | ||
| 351 | pub 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. | |
| 385 | pub 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 | ||
| 402 | fn 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 | ||
| 429 | fn 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 | ||
| 536 | pub 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. | |
| 551 | pub 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 | ||
| 580 | 314 | pub const RebootError = error{ |
| 581 | 315 | PermissionDenied, |
| 582 | 316 | } || UnexpectedError; |
| ... | ... | @@ -698,66 +432,6 @@ fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void { |
| 698 | 432 | } |
| 699 | 433 | } |
| 700 | 434 | |
| 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. | |
| 705 | pub 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 | ||
| 761 | 435 | pub const RaiseError = UnexpectedError; |
| 762 | 436 | |
| 763 | 437 | pub fn raise(sig: SIG) RaiseError!void { |
| ... | ... | @@ -798,33 +472,6 @@ pub fn kill(pid: pid_t, sig: SIG) KillError!void { |
| 798 | 472 | } |
| 799 | 473 | } |
| 800 | 474 | |
| 801 | /// Exits all threads of the program with the specified status code. | |
| 802 | pub 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 | ||
| 828 | 475 | pub const ReadError = std.Io.File.Reader.Error; |
| 829 | 476 | |
| 830 | 477 | /// 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 | 486 | /// The corresponding POSIX limit is `maxInt(isize)`. |
| 840 | 487 | pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 841 | 488 | if (buf.len == 0) return 0; |
| 842 | if (native_os == .windows) { | |
| 843 | return windows.ReadFile(fd, buf, null); | |
| 844 | } | |
| 845 | if (native_os == .wasi and !builtin.link_libc) { | |
| 846 | const iovs = [1]iovec{iovec{ | |
| 847 | .base = buf.ptr, | |
| 848 | .len = buf.len, | |
| 849 | }}; | |
| 850 | ||
| 851 | var nread: usize = undefined; | |
| 852 | switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) { | |
| 853 | .SUCCESS => return nread, | |
| 854 | .INTR => unreachable, | |
| 855 | .INVAL => unreachable, | |
| 856 | .FAULT => unreachable, | |
| 857 | .AGAIN => unreachable, | |
| 858 | .BADF => return error.NotOpenForReading, // Can be a race condition. | |
| 859 | .IO => return error.InputOutput, | |
| 860 | .ISDIR => return error.IsDir, | |
| 861 | .NOBUFS => return error.SystemResources, | |
| 862 | .NOMEM => return error.SystemResources, | |
| 863 | .NOTCONN => return error.SocketUnconnected, | |
| 864 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 865 | .TIMEDOUT => return error.Timeout, | |
| 866 | .NOTCAPABLE => return error.AccessDenied, | |
| 867 | else => |err| return unexpectedErrno(err), | |
| 868 | } | |
| 869 | } | |
| 489 | if (native_os == .windows) @compileError("unsupported OS"); | |
| 490 | if (native_os == .wasi) @compileError("unsupported OS"); | |
| 870 | 491 | |
| 871 | 492 | // Prevents EINVAL. |
| 872 | 493 | const max_count = switch (native_os) { |
| ... | ... | @@ -881,7 +502,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 881 | 502 | .INTR => continue, |
| 882 | 503 | .INVAL => unreachable, |
| 883 | 504 | .FAULT => unreachable, |
| 884 | .SRCH => return error.ProcessNotFound, | |
| 885 | 505 | .AGAIN => return error.WouldBlock, |
| 886 | 506 | .CANCELED => return error.Canceled, |
| 887 | 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 | 517 | } |
| 898 | 518 | } |
| 899 | 519 | |
| 900 | /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. | |
| 520 | pub 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 | 571 | /// For POSIX systems, if `fd` is opened in non blocking mode, the function will |
| 903 | 572 | /// return error.WouldBlock when EAGAIN is received. |
| 904 | 573 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are |
| 905 | 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: | |
| 908 | /// * Windows | |
| 909 | /// On these systems, the read races with concurrent writes to the same file descriptor. | |
| 910 | /// | |
| 911 | /// This function assumes that all vectors, including zero-length vectors, have | |
| 912 | /// a pointer within the address space of the application. | |
| 913 | pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { | |
| 914 | if (native_os == .windows) { | |
| 915 | if (iov.len == 0) return 0; | |
| 916 | const first = iov[0]; | |
| 917 | return read(fd, first.base[0..first.len]); | |
| 918 | } | |
| 919 | if (native_os == .wasi and !builtin.link_libc) { | |
| 920 | var nread: usize = undefined; | |
| 921 | switch (wasi.fd_read(fd, iov.ptr, iov.len, &nread)) { | |
| 922 | .SUCCESS => return nread, | |
| 923 | .INTR => unreachable, | |
| 924 | .INVAL => unreachable, | |
| 925 | .FAULT => unreachable, | |
| 926 | .AGAIN => unreachable, // currently not support in WASI | |
| 927 | .BADF => return error.NotOpenForReading, // can be a race condition | |
| 928 | .IO => return error.InputOutput, | |
| 929 | .ISDIR => return error.IsDir, | |
| 930 | .NOBUFS => return error.SystemResources, | |
| 931 | .NOMEM => return error.SystemResources, | |
| 932 | .NOTCONN => return error.SocketUnconnected, | |
| 933 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 934 | .TIMEDOUT => return error.Timeout, | |
| 935 | .NOTCAPABLE => return error.AccessDenied, | |
| 936 | else => |err| return unexpectedErrno(err), | |
| 937 | } | |
| 938 | } | |
| 576 | /// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000` | |
| 577 | /// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as | |
| 578 | /// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page. | |
| 579 | /// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. | |
| 580 | /// The corresponding POSIX limit is `maxInt(isize)`. | |
| 581 | pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { | |
| 582 | if (bytes.len == 0) return 0; | |
| 583 | if (native_os == .windows) @compileError("unsupported OS"); | |
| 584 | if (native_os == .wasi) @compileError("unsupported OS"); | |
| 939 | 585 | |
| 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 | 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 | 593 | switch (errno(rc)) { |
| 943 | 594 | .SUCCESS => return @intCast(rc), |
| 944 | 595 | .INTR => continue, |
| 945 | .INVAL => unreachable, | |
| 596 | .INVAL => return error.InvalidArgument, | |
| 946 | 597 | .FAULT => unreachable, |
| 947 | .SRCH => return error.ProcessNotFound, | |
| 948 | 598 | .AGAIN => return error.WouldBlock, |
| 949 | .BADF => return error.NotOpenForReading, // can be a race condition | |
| 599 | .BADF => return error.NotOpenForWriting, // can be a race condition. | |
| 600 | .DESTADDRREQ => unreachable, // `connect` was never called. | |
| 601 | .DQUOT => return error.DiskQuota, | |
| 602 | .FBIG => return error.FileTooBig, | |
| 950 | 603 | .IO => return error.InputOutput, |
| 951 | .ISDIR => return error.IsDir, | |
| 952 | .NOBUFS => return error.SystemResources, | |
| 953 | .NOMEM => return error.SystemResources, | |
| 954 | .NOTCONN => return error.SocketUnconnected, | |
| 604 | .NOSPC => return error.NoSpaceLeft, | |
| 605 | .ACCES => return error.AccessDenied, | |
| 606 | .PERM => return error.PermissionDenied, | |
| 607 | .PIPE => return error.BrokenPipe, | |
| 955 | 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 | 612 | else => |err| return unexpectedErrno(err), |
| 958 | 613 | } |
| 959 | 614 | } |
| 960 | 615 | } |
| 961 | 616 | |
| 962 | pub const PReadError = std.Io.File.ReadPositionalError; | |
| 617 | pub const OpenError = std.Io.File.OpenError || error{WouldBlock}; | |
| 963 | 618 | |
| 964 | /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. | |
| 965 | /// | |
| 966 | /// Retries when interrupted by a signal. | |
| 967 | /// | |
| 968 | /// For POSIX systems, if `fd` is opened in non blocking mode, the function will | |
| 969 | /// return error.WouldBlock when EAGAIN is received. | |
| 970 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are | |
| 971 | /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. | |
| 972 | /// | |
| 973 | /// Linux has a limit on how many bytes may be transferred in one `pread` call, which is `0x7ffff000` | |
| 974 | /// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as | |
| 975 | /// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page. | |
| 976 | /// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. | |
| 977 | /// The corresponding POSIX limit is `maxInt(isize)`. | |
| 978 | pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { | |
| 979 | if (buf.len == 0) return 0; | |
| 619 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | |
| 620 | /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | |
| 621 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 622 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 623 | /// See also `openZ`. | |
| 624 | pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t { | |
| 980 | 625 | if (native_os == .windows) { |
| 981 | return windows.ReadFile(fd, buf, offset); | |
| 982 | } | |
| 983 | if (native_os == .wasi and !builtin.link_libc) { | |
| 984 | const iovs = [1]iovec{iovec{ | |
| 985 | .base = buf.ptr, | |
| 986 | .len = buf.len, | |
| 987 | }}; | |
| 988 | ||
| 989 | var nread: usize = undefined; | |
| 990 | switch (wasi.fd_pread(fd, &iovs, iovs.len, offset, &nread)) { | |
| 991 | .SUCCESS => return nread, | |
| 992 | .INTR => unreachable, | |
| 993 | .INVAL => unreachable, | |
| 994 | .FAULT => unreachable, | |
| 995 | .AGAIN => unreachable, | |
| 996 | .BADF => return error.NotOpenForReading, // Can be a race condition. | |
| 997 | .IO => return error.InputOutput, | |
| 998 | .ISDIR => return error.IsDir, | |
| 999 | .NOBUFS => return error.SystemResources, | |
| 1000 | .NOMEM => return error.SystemResources, | |
| 1001 | .NOTCONN => return error.SocketUnconnected, | |
| 1002 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 1003 | .TIMEDOUT => return error.Timeout, | |
| 1004 | .NXIO => return error.Unseekable, | |
| 1005 | .SPIPE => return error.Unseekable, | |
| 1006 | .OVERFLOW => return error.Unseekable, | |
| 1007 | .NOTCAPABLE => return error.AccessDenied, | |
| 1008 | else => |err| return unexpectedErrno(err), | |
| 1009 | } | |
| 626 | @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); | |
| 627 | } else if (native_os == .wasi and !builtin.link_libc) { | |
| 628 | return openat(AT.FDCWD, file_path, flags, perm); | |
| 1010 | 629 | } |
| 630 | const file_path_c = try toPosixPath(file_path); | |
| 631 | return openZ(&file_path_c, flags, perm); | |
| 632 | } | |
| 1011 | 633 | |
| 1012 | // Prevent EINVAL. | |
| 1013 | const max_count = switch (native_os) { | |
| 1014 | .linux => 0x7ffff000, | |
| 1015 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => maxInt(i32), | |
| 1016 | else => maxInt(isize), | |
| 1017 | }; | |
| 634 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | |
| 635 | /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | |
| 636 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 637 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 638 | /// See also `open`. | |
| 639 | pub 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 | } | |
| 1018 | 645 | |
| 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 | 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 | 649 | switch (errno(rc)) { |
| 1023 | 650 | .SUCCESS => return @intCast(rc), |
| 1024 | 651 | .INTR => continue, |
| 1025 | .INVAL => unreachable, | |
| 652 | ||
| 1026 | 653 | .FAULT => unreachable, |
| 1027 | .SRCH => return error.ProcessNotFound, | |
| 1028 | .AGAIN => return error.WouldBlock, | |
| 1029 | .BADF => return error.NotOpenForReading, // Can be a race condition. | |
| 1030 | .IO => return error.InputOutput, | |
| 654 | .INVAL => return error.BadPathName, | |
| 655 | .ACCES => return error.AccessDenied, | |
| 656 | .FBIG => return error.FileTooBig, | |
| 657 | .OVERFLOW => return error.FileTooBig, | |
| 1031 | 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 | 667 | .NOMEM => return error.SystemResources, |
| 1034 | .NOTCONN => return error.SocketUnconnected, | |
| 1035 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 1036 | .TIMEDOUT => return error.Timeout, | |
| 1037 | .NXIO => return error.Unseekable, | |
| 1038 | .SPIPE => return error.Unseekable, | |
| 1039 | .OVERFLOW => return error.Unseekable, | |
| 668 | .NOSPC => return error.NoSpaceLeft, | |
| 669 | .NOTDIR => return error.NotDir, | |
| 670 | .PERM => return error.PermissionDenied, | |
| 671 | .EXIST => return error.PathAlreadyExists, | |
| 672 | .BUSY => return error.DeviceBusy, | |
| 673 | .ILSEQ => return error.BadPathName, | |
| 1040 | 674 | else => |err| return unexpectedErrno(err), |
| 1041 | 675 | } |
| 1042 | 676 | } |
| 1043 | 677 | } |
| 1044 | 678 | |
| 1045 | pub const TruncateError = error{ | |
| 1046 | FileTooBig, | |
| 1047 | InputOutput, | |
| 1048 | FileBusy, | |
| 1049 | AccessDenied, | |
| 1050 | PermissionDenied, | |
| 1051 | NonResizable, | |
| 1052 | } || UnexpectedError; | |
| 1053 | ||
| 1054 | /// Length must be positive when treated as an i64. | |
| 1055 | pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { | |
| 1056 | const signed_len: i64 = @bitCast(length); | |
| 1057 | if (signed_len < 0) return error.FileTooBig; // avoid ambiguous EINVAL errors | |
| 1058 | ||
| 679 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | |
| 680 | /// `file_path` is relative to the open directory handle `dir_fd`. | |
| 681 | /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | |
| 682 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 683 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 684 | /// See also `openatZ`. | |
| 685 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t { | |
| 1059 | 686 | if (native_os == .windows) { |
| 1060 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 1061 | const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{ | |
| 1062 | .EndOfFile = signed_len, | |
| 1063 | }; | |
| 1064 | const rc = windows.ntdll.NtSetInformationFile( | |
| 1065 | fd, | |
| 1066 | &io_status_block, | |
| 1067 | &eof_info, | |
| 1068 | @sizeOf(windows.FILE.END_OF_FILE_INFORMATION), | |
| 1069 | .EndOfFile, | |
| 1070 | ); | |
| 1071 | switch (rc) { | |
| 1072 | .SUCCESS => return, | |
| 1073 | .INVALID_HANDLE => unreachable, // Handle not open for writing | |
| 1074 | .ACCESS_DENIED => return error.AccessDenied, | |
| 1075 | .USER_MAPPED_FILE => return error.AccessDenied, | |
| 1076 | .INVALID_PARAMETER => return error.FileTooBig, | |
| 1077 | else => return windows.unexpectedStatus(rc), | |
| 1078 | } | |
| 687 | @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); | |
| 688 | } else if (native_os == .wasi and !builtin.link_libc) { | |
| 689 | @compileError("use std.Io instead"); | |
| 1079 | 690 | } |
| 1080 | if (native_os == .wasi and !builtin.link_libc) { | |
| 1081 | switch (wasi.fd_filestat_set_size(fd, length)) { | |
| 1082 | .SUCCESS => return, | |
| 1083 | .INTR => unreachable, | |
| 1084 | .FBIG => return error.FileTooBig, | |
| 1085 | .IO => return error.InputOutput, | |
| 1086 | .PERM => return error.PermissionDenied, | |
| 1087 | .TXTBSY => return error.FileBusy, | |
| 1088 | .BADF => unreachable, // Handle not open for writing | |
| 1089 | .INVAL => return error.NonResizable, | |
| 1090 | .NOTCAPABLE => return error.AccessDenied, | |
| 1091 | else => |err| return unexpectedErrno(err), | |
| 1092 | } | |
| 691 | const file_path_c = try toPosixPath(file_path); | |
| 692 | return openatZ(dir_fd, &file_path_c, flags, mode); | |
| 693 | } | |
| 694 | ||
| 695 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | |
| 696 | /// `file_path` is relative to the open directory handle `dir_fd`. | |
| 697 | /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | |
| 698 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 699 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 700 | /// See also `openat`. | |
| 701 | pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t { | |
| 702 | if (native_os == .windows) { | |
| 703 | @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); | |
| 704 | } else if (native_os == .wasi and !builtin.link_libc) { | |
| 705 | return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode); | |
| 1093 | 706 | } |
| 1094 | 707 | |
| 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 | 709 | while (true) { |
| 1097 | switch (errno(ftruncate_sym(fd, signed_len))) { | |
| 1098 | .SUCCESS => return, | |
| 710 | const rc = openat_sym(dir_fd, file_path, flags, mode); | |
| 711 | switch (errno(rc)) { | |
| 712 | .SUCCESS => return @intCast(rc), | |
| 1099 | 713 | .INTR => continue, |
| 714 | ||
| 715 | .FAULT => unreachable, | |
| 716 | .INVAL => return error.BadPathName, | |
| 717 | .BADF => unreachable, | |
| 718 | .ACCES => return error.AccessDenied, | |
| 1100 | 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 | 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 | 737 | .TXTBSY => return error.FileBusy, |
| 1104 | .BADF => unreachable, // Handle not open for writing | |
| 1105 | .INVAL => return error.NonResizable, // This is returned for /dev/null for example. | |
| 738 | .NXIO => return error.NoDevice, | |
| 739 | .ILSEQ => return error.BadPathName, | |
| 1106 | 740 | else => |err| return unexpectedErrno(err), |
| 1107 | 741 | } |
| 1108 | 742 | } |
| 1109 | 743 | } |
| 1110 | 744 | |
| 1111 | /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. | |
| 1112 | /// | |
| 1113 | /// Retries when interrupted by a signal. | |
| 1114 | /// | |
| 1115 | /// For POSIX systems, if `fd` is opened in non blocking mode, the function will | |
| 1116 | /// return error.WouldBlock when EAGAIN is received. | |
| 1117 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are | |
| 1118 | /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. | |
| 1119 | /// | |
| 1120 | /// This operation is non-atomic on the following systems: | |
| 1121 | /// * Darwin | |
| 1122 | /// * Windows | |
| 1123 | /// On these systems, the read races with concurrent writes to the same file descriptor. | |
| 1124 | pub 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, | |
| 745 | pub fn dup(old_fd: fd_t) !fd_t { | |
| 746 | const rc = system.dup(old_fd); | |
| 747 | return switch (errno(rc)) { | |
| 748 | .SUCCESS => return @intCast(rc), | |
| 749 | .MFILE => error.ProcessFdQuotaExceeded, | |
| 750 | .BADF => unreachable, // invalid file descriptor | |
| 751 | else => |err| return unexpectedErrno(err), | |
| 1128 | 752 | }; |
| 1129 | if (have_pread_but_not_preadv) { | |
| 1130 | // We could loop here; but proper usage of `preadv` must handle partial reads anyway. | |
| 1131 | // So we simply read into the first vector only. | |
| 1132 | if (iov.len == 0) return 0; | |
| 1133 | const first = iov[0]; | |
| 1134 | return pread(fd, first.base[0..first.len], offset); | |
| 1135 | } | |
| 1136 | if (native_os == .wasi and !builtin.link_libc) { | |
| 1137 | var nread: usize = undefined; | |
| 1138 | switch (wasi.fd_pread(fd, iov.ptr, iov.len, offset, &nread)) { | |
| 1139 | .SUCCESS => return nread, | |
| 1140 | .INTR => unreachable, | |
| 1141 | .INVAL => unreachable, | |
| 1142 | .FAULT => unreachable, | |
| 1143 | .AGAIN => unreachable, | |
| 1144 | .BADF => return error.NotOpenForReading, // can be a race condition | |
| 1145 | .IO => return error.InputOutput, | |
| 1146 | .ISDIR => return error.IsDir, | |
| 1147 | .NOBUFS => return error.SystemResources, | |
| 1148 | .NOMEM => return error.SystemResources, | |
| 1149 | .NOTCONN => return error.SocketUnconnected, | |
| 1150 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 1151 | .TIMEDOUT => return error.Timeout, | |
| 1152 | .NXIO => return error.Unseekable, | |
| 1153 | .SPIPE => return error.Unseekable, | |
| 1154 | .OVERFLOW => return error.Unseekable, | |
| 1155 | .NOTCAPABLE => return error.AccessDenied, | |
| 1156 | else => |err| return unexpectedErrno(err), | |
| 1157 | } | |
| 1158 | } | |
| 753 | } | |
| 1159 | 754 | |
| 1160 | const preadv_sym = if (lfs64_abi) system.preadv64 else system.preadv; | |
| 755 | pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { | |
| 1161 | 756 | while (true) { |
| 1162 | const rc = preadv_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset)); | |
| 1163 | switch (errno(rc)) { | |
| 1164 | .SUCCESS => return @bitCast(rc), | |
| 1165 | .INTR => continue, | |
| 1166 | .INVAL => unreachable, | |
| 1167 | .FAULT => unreachable, | |
| 1168 | .SRCH => return error.ProcessNotFound, | |
| 1169 | .AGAIN => return error.WouldBlock, | |
| 1170 | .BADF => return error.NotOpenForReading, // can be a race condition | |
| 1171 | .IO => return error.InputOutput, | |
| 1172 | .ISDIR => return error.IsDir, | |
| 1173 | .NOBUFS => return error.SystemResources, | |
| 1174 | .NOMEM => return error.SystemResources, | |
| 1175 | .NOTCONN => return error.SocketUnconnected, | |
| 1176 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 1177 | .TIMEDOUT => return error.Timeout, | |
| 1178 | .NXIO => return error.Unseekable, | |
| 1179 | .SPIPE => return error.Unseekable, | |
| 1180 | .OVERFLOW => return error.Unseekable, | |
| 757 | switch (errno(system.dup2(old_fd, new_fd))) { | |
| 758 | .SUCCESS => return, | |
| 759 | .BUSY, .INTR => continue, | |
| 760 | .MFILE => return error.ProcessFdQuotaExceeded, | |
| 761 | .INVAL => unreachable, // invalid parameters passed to dup2 | |
| 762 | .BADF => unreachable, // invalid file descriptor | |
| 1181 | 763 | else => |err| return unexpectedErrno(err), |
| 1182 | 764 | } |
| 1183 | 765 | } |
| 1184 | 766 | } |
| 1185 | 767 | |
| 1186 | pub const WriteError = error{ | |
| 1187 | DiskQuota, | |
| 1188 | FileTooBig, | |
| 1189 | InputOutput, | |
| 1190 | NoSpaceLeft, | |
| 1191 | DeviceBusy, | |
| 1192 | InvalidArgument, | |
| 1193 | ||
| 1194 | /// File descriptor does not hold the required rights to write to it. | |
| 1195 | AccessDenied, | |
| 1196 | PermissionDenied, | |
| 1197 | BrokenPipe, | |
| 1198 | SystemResources, | |
| 1199 | Canceled, | |
| 1200 | NotOpenForWriting, | |
| 1201 | ||
| 1202 | /// The process cannot access the file because another process has locked | |
| 1203 | /// a portion of the file. Windows-only. | |
| 1204 | LockViolation, | |
| 1205 | ||
| 1206 | /// This error occurs when no global event loop is configured, | |
| 1207 | /// and reading from the file descriptor would block. | |
| 1208 | WouldBlock, | |
| 1209 | ||
| 1210 | /// Connection reset by peer. | |
| 1211 | ConnectionResetByPeer, | |
| 1212 | ||
| 1213 | /// This error occurs in Linux if the process being written to | |
| 1214 | /// no longer exists. | |
| 1215 | ProcessNotFound, | |
| 1216 | /// This error occurs when a device gets disconnected before or mid-flush | |
| 1217 | /// while it's being written to - errno(6): No such device or address. | |
| 1218 | NoDevice, | |
| 1219 | ||
| 1220 | /// The socket type requires that message be sent atomically, and the size of the message | |
| 1221 | /// to be sent made this impossible. The message is not transmitted. | |
| 1222 | MessageOversize, | |
| 1223 | } || UnexpectedError; | |
| 1224 | ||
| 1225 | /// Write to a file descriptor. | |
| 1226 | /// Retries when interrupted by a signal. | |
| 1227 | /// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero. | |
| 1228 | /// | |
| 1229 | /// Note that a successful write() may transfer fewer than count bytes. Such partial writes can | |
| 1230 | /// occur for various reasons; for example, because there was insufficient space on the disk | |
| 1231 | /// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or | |
| 1232 | /// similar was interrupted by a signal handler after it had transferred some, but before it had | |
| 1233 | /// transferred all of the requested bytes. In the event of a partial write, the caller can make | |
| 1234 | /// another write() call to transfer the remaining bytes. The subsequent call will either | |
| 1235 | /// transfer further bytes or may result in an error (e.g., if the disk is now full). | |
| 1236 | /// | |
| 1237 | /// For POSIX systems, if `fd` is opened in non blocking mode, the function will | |
| 1238 | /// return error.WouldBlock when EAGAIN is received. | |
| 1239 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are | |
| 1240 | /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. | |
| 1241 | /// | |
| 1242 | /// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000` | |
| 1243 | /// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as | |
| 1244 | /// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page. | |
| 1245 | /// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. | |
| 1246 | /// The corresponding POSIX limit is `maxInt(isize)`. | |
| 1247 | pub 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. | |
| 1331 | pub 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 | ||
| 1384 | pub 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)`. | |
| 1408 | pub 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. | |
| 1497 | pub 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 | ||
| 1561 | pub 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`. | |
| 1568 | pub 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`. | |
| 1583 | pub 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`. | |
| 1628 | pub 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`. | |
| 1644 | pub 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 | ||
| 1688 | pub 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 | ||
| 1698 | pub 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 | ||
| 1711 | pub fn getpid() pid_t { | |
| 1712 | return system.getpid(); | |
| 1713 | } | |
| 1714 | ||
| 1715 | pub fn getppid() pid_t { | |
| 1716 | return system.getppid(); | |
| 1717 | } | |
| 1718 | ||
| 1719 | pub 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. | |
| 1735 | pub 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 | ||
| 1773 | pub 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. | |
| 1781 | pub 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`. | |
| 1833 | pub 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`. | |
| 1843 | pub 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`. | |
| 1885 | pub 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 | ||
| 1896 | pub const GetCwdError = error{ | |
| 1897 | NameTooLong, | |
| 1898 | CurrentWorkingDirectoryUnlinked, | |
| 1899 | } || UnexpectedError; | |
| 1900 | ||
| 1901 | /// The result is a slice of out_buffer, indexed from 0. | |
| 1902 | pub 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 | ||
| 1929 | pub 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. | |
| 1958 | pub 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`. | |
| 1971 | pub 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`. | |
| 2007 | pub 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`. | |
| 2020 | pub 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`. | |
| 2046 | pub 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 | ||
| 2073 | pub 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. | |
| 2092 | pub 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. | |
| 2120 | pub 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 | ||
| 2132 | pub 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. | |
| 2136 | pub 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. | |
| 2171 | pub 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 | ||
| 2219 | pub 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`. | |
| 2251 | pub 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. | |
| 2267 | pub 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. | |
| 2295 | pub 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 | }; | |
| 768 | pub fn getpid() pid_t { | |
| 769 | return system.getpid(); | |
| 2300 | 770 | } |
| 2301 | 771 | |
| 2302 | pub const UnlinkatError = UnlinkError || error{ | |
| 2303 | /// When passing `AT.REMOVEDIR`, this error occurs when the named directory is not empty. | |
| 2304 | DirNotEmpty, | |
| 2305 | }; | |
| 2306 | ||
| 2307 | /// Delete a file name and possibly the file it refers to, based on an open directory handle. | |
| 2308 | /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | |
| 2309 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 2310 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 2311 | /// Asserts that the path parameter has no null bytes. | |
| 2312 | pub 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 | } | |
| 772 | pub fn getppid() pid_t { | |
| 773 | return system.getppid(); | |
| 2322 | 774 | } |
| 2323 | 775 | |
| 2324 | /// WASI-only. Same as `unlinkat` but targeting WASI. | |
| 2325 | /// See also `unlinkat`. | |
| 2326 | pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { | |
| 2327 | const remove_dir = (flags & AT.REMOVEDIR) != 0; | |
| 2328 | const res = if (remove_dir) | |
| 2329 | wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len) | |
| 2330 | else | |
| 2331 | wasi.path_unlink_file(dirfd, file_path.ptr, file_path.len); | |
| 2332 | switch (res) { | |
| 2333 | .SUCCESS => return, | |
| 2334 | .ACCES => return error.AccessDenied, | |
| 2335 | .PERM => return error.PermissionDenied, | |
| 2336 | .BUSY => return error.FileBusy, | |
| 776 | pub const ExecveError = error{ | |
| 777 | SystemResources, | |
| 778 | AccessDenied, | |
| 779 | PermissionDenied, | |
| 780 | InvalidExe, | |
| 781 | FileSystem, | |
| 782 | IsDir, | |
| 783 | FileNotFound, | |
| 784 | NotDir, | |
| 785 | FileBusy, | |
| 786 | ProcessFdQuotaExceeded, | |
| 787 | SystemFdQuotaExceeded, | |
| 788 | NameTooLong, | |
| 789 | } || UnexpectedError; | |
| 790 | ||
| 791 | /// This function ignores PATH environment variable. See `execvpeZ` for that. | |
| 792 | pub 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 | 799 | .FAULT => unreachable, |
| 2338 | .IO => return error.FileSystem, | |
| 2339 | .ISDIR => return error.IsDir, | |
| 2340 | .LOOP => return error.SymLinkLoop, | |
| 800 | .@"2BIG" => return error.SystemResources, | |
| 801 | .MFILE => return error.ProcessFdQuotaExceeded, | |
| 2341 | 802 | .NAMETOOLONG => return error.NameTooLong, |
| 2342 | .NOENT => return error.FileNotFound, | |
| 2343 | .NOTDIR => return error.NotDir, | |
| 803 | .NFILE => return error.SystemFdQuotaExceeded, | |
| 2344 | 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. | |
| 2358 | pub 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 | 805 | .ACCES => return error.AccessDenied, |
| 2368 | 806 | .PERM => return error.PermissionDenied, |
| 2369 | .BUSY => return error.FileBusy, | |
| 2370 | .FAULT => unreachable, | |
| 807 | .INVAL => return error.InvalidExe, | |
| 808 | .NOEXEC => return error.InvalidExe, | |
| 2371 | 809 | .IO => return error.FileSystem, |
| 810 | .LOOP => return error.FileSystem, | |
| 2372 | 811 | .ISDIR => return error.IsDir, |
| 2373 | .LOOP => return error.SymLinkLoop, | |
| 2374 | .NAMETOOLONG => return error.NameTooLong, | |
| 2375 | 812 | .NOENT => return error.FileNotFound, |
| 2376 | 813 | .NOTDIR => return error.NotDir, |
| 2377 | .NOMEM => return error.SystemResources, | |
| 2378 | .ROFS => return error.ReadOnlyFileSystem, | |
| 2379 | .EXIST => return error.DirNotEmpty, | |
| 2380 | .NOTEMPTY => return error.DirNotEmpty, | |
| 2381 | .ILSEQ => return error.BadPathName, | |
| 2382 | ||
| 2383 | .INVAL => unreachable, // invalid flags, or pathname has . as last component | |
| 2384 | .BADF => unreachable, // always a race condition | |
| 2385 | ||
| 2386 | else => |err| return unexpectedErrno(err), | |
| 814 | .TXTBSY => return error.FileBusy, | |
| 815 | else => |err| switch (native_os) { | |
| 816 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (err) { | |
| 817 | .BADEXEC => return error.InvalidExe, | |
| 818 | .BADARCH => return error.InvalidExe, | |
| 819 | else => return unexpectedErrno(err), | |
| 820 | }, | |
| 821 | .linux => switch (err) { | |
| 822 | .LIBBAD => return error.InvalidExe, | |
| 823 | else => return unexpectedErrno(err), | |
| 824 | }, | |
| 825 | else => return unexpectedErrno(err), | |
| 826 | }, | |
| 2387 | 827 | } |
| 2388 | 828 | } |
| 2389 | 829 | |
| 2390 | /// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only. | |
| 2391 | pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void { | |
| 2392 | const remove_dir = (flags & AT.REMOVEDIR) != 0; | |
| 2393 | return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); | |
| 2394 | } | |
| 830 | pub const Arg0Expand = enum { | |
| 831 | expand, | |
| 832 | no_expand, | |
| 833 | }; | |
| 2395 | 834 | |
| 2396 | pub const RenameError = error{ | |
| 2397 | /// In WASI, this error may occur when the file descriptor does | |
| 2398 | /// not hold the required rights to rename a resource by path relative to it. | |
| 2399 | /// | |
| 2400 | /// On Windows, this error may be returned instead of PathAlreadyExists when | |
| 2401 | /// renaming a directory over an existing directory. | |
| 2402 | AccessDenied, | |
| 2403 | PermissionDenied, | |
| 2404 | FileBusy, | |
| 2405 | DiskQuota, | |
| 2406 | IsDir, | |
| 2407 | SymLinkLoop, | |
| 2408 | LinkQuotaExceeded, | |
| 2409 | NameTooLong, | |
| 2410 | FileNotFound, | |
| 2411 | NotDir, | |
| 2412 | SystemResources, | |
| 2413 | NoSpaceLeft, | |
| 2414 | PathAlreadyExists, | |
| 2415 | ReadOnlyFileSystem, | |
| 2416 | RenameAcrossMountPoints, | |
| 2417 | /// WASI: file paths must be valid UTF-8. | |
| 2418 | /// Windows: file paths provided by the user must be valid WTF-8. | |
| 2419 | /// https://wtf-8.codeberg.page/ | |
| 2420 | BadPathName, | |
| 2421 | NoDevice, | |
| 2422 | SharingViolation, | |
| 2423 | PipeBusy, | |
| 2424 | /// On Windows, `\\server` or `\\server\share` was not found. | |
| 2425 | NetworkNotFound, | |
| 2426 | /// On Windows, antivirus software is enabled by default. It can be | |
| 2427 | /// disabled, but Windows Update sometimes ignores the user's preference | |
| 2428 | /// and re-enables it. When enabled, antivirus software on Windows | |
| 2429 | /// intercepts file system operations and makes them significantly slower | |
| 2430 | /// in addition to possibly failing with this error code. | |
| 2431 | AntivirusInterference, | |
| 2432 | } || UnexpectedError; | |
| 835 | /// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, | |
| 836 | /// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. | |
| 837 | /// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in. | |
| 838 | pub fn execvpeZ_expandArg0( | |
| 839 | comptime arg0_expand: Arg0Expand, | |
| 840 | file: [*:0]const u8, | |
| 841 | child_argv: switch (arg0_expand) { | |
| 842 | .expand => [*:null]?[*:0]const u8, | |
| 843 | .no_expand => [*:null]const ?[*:0]const u8, | |
| 844 | }, | |
| 845 | envp: [*:null]const ?[*:0]const u8, | |
| 846 | ) ExecveError { | |
| 847 | const file_slice = mem.sliceTo(file, 0); | |
| 848 | if (mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); | |
| 2433 | 849 | |
| 2434 | /// Change the name or location of a file. | |
| 2435 | /// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | |
| 2436 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 2437 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 2438 | pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { | |
| 2439 | if (native_os == .wasi and !builtin.link_libc) { | |
| 2440 | return renameat(AT.FDCWD, old_path, AT.FDCWD, new_path); | |
| 2441 | } else if (native_os == .windows) { | |
| 2442 | const old_path_w = try windows.sliceToPrefixedFileW(null, old_path); | |
| 2443 | const new_path_w = try windows.sliceToPrefixedFileW(null, new_path); | |
| 2444 | return renameW(old_path_w.span().ptr, new_path_w.span().ptr); | |
| 2445 | } else { | |
| 2446 | const old_path_c = try toPosixPath(old_path); | |
| 2447 | const new_path_c = try toPosixPath(new_path); | |
| 2448 | return renameZ(&old_path_c, &new_path_c); | |
| 850 | const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin"; | |
| 851 | // Use of PATH_MAX here is valid as the path_buf will be passed | |
| 852 | // directly to the operating system in execveZ. | |
| 853 | var path_buf: [PATH_MAX]u8 = undefined; | |
| 854 | var it = mem.tokenizeScalar(u8, PATH, ':'); | |
| 855 | var seen_eacces = false; | |
| 856 | var err: ExecveError = error.FileNotFound; | |
| 857 | ||
| 858 | // In case of expanding arg0 we must put it back if we return with an error. | |
| 859 | const prev_arg0 = child_argv[0]; | |
| 860 | defer switch (arg0_expand) { | |
| 861 | .expand => child_argv[0] = prev_arg0, | |
| 862 | .no_expand => {}, | |
| 863 | }; | |
| 864 | ||
| 865 | while (it.next()) |search_path| { | |
| 866 | const path_len = search_path.len + file_slice.len + 1; | |
| 867 | if (path_buf.len < path_len + 1) return error.NameTooLong; | |
| 868 | @memcpy(path_buf[0..search_path.len], search_path); | |
| 869 | path_buf[search_path.len] = '/'; | |
| 870 | @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); | |
| 871 | path_buf[path_len] = 0; | |
| 872 | const full_path = path_buf[0..path_len :0].ptr; | |
| 873 | switch (arg0_expand) { | |
| 874 | .expand => child_argv[0] = full_path, | |
| 875 | .no_expand => {}, | |
| 876 | } | |
| 877 | err = execveZ(full_path, child_argv, envp); | |
| 878 | switch (err) { | |
| 879 | error.AccessDenied => seen_eacces = true, | |
| 880 | error.FileNotFound, error.NotDir => {}, | |
| 881 | else => |e| return e, | |
| 882 | } | |
| 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`. | |
| 890 | pub 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 | } |
| 2451 | 897 | |
| 2452 | /// Same as `rename` except the parameters are null-terminated. | |
| 2453 | pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void { | |
| 898 | /// Get an environment variable. | |
| 899 | /// See also `getenvZ`. | |
| 900 | pub fn getenv(key: []const u8) ?[:0]const u8 { | |
| 2454 | 901 | if (native_os == .windows) { |
| 2455 | const old_path_w = try windows.cStrToPrefixedFileW(null, old_path); | |
| 2456 | const new_path_w = try windows.cStrToPrefixedFileW(null, new_path); | |
| 2457 | return renameW(old_path_w.span().ptr, new_path_w.span().ptr); | |
| 2458 | } else if (native_os == .wasi and !builtin.link_libc) { | |
| 2459 | return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0)); | |
| 902 | @compileError("std.posix.getenv is unavailable for Windows because environment strings are in WTF-16 format. See std.process.getEnvVarOwned for a cross-platform API or std.process.getenvW for a Windows-specific API."); | |
| 2460 | 903 | } |
| 2461 | switch (errno(system.rename(old_path, new_path))) { | |
| 2462 | .SUCCESS => return, | |
| 2463 | .ACCES => return error.AccessDenied, | |
| 2464 | .PERM => return error.PermissionDenied, | |
| 2465 | .BUSY => return error.FileBusy, | |
| 2466 | .DQUOT => return error.DiskQuota, | |
| 2467 | .FAULT => unreachable, | |
| 2468 | .INVAL => unreachable, | |
| 2469 | .ISDIR => return error.IsDir, | |
| 2470 | .LOOP => return error.SymLinkLoop, | |
| 2471 | .MLINK => return error.LinkQuotaExceeded, | |
| 2472 | .NAMETOOLONG => return error.NameTooLong, | |
| 2473 | .NOENT => return error.FileNotFound, | |
| 2474 | .NOTDIR => return error.NotDir, | |
| 2475 | .NOMEM => return error.SystemResources, | |
| 2476 | .NOSPC => return error.NoSpaceLeft, | |
| 2477 | .EXIST => return error.PathAlreadyExists, | |
| 2478 | .NOTEMPTY => return error.PathAlreadyExists, | |
| 2479 | .ROFS => return error.ReadOnlyFileSystem, | |
| 2480 | .XDEV => return error.RenameAcrossMountPoints, | |
| 2481 | .ILSEQ => return error.BadPathName, | |
| 2482 | else => |err| return unexpectedErrno(err), | |
| 904 | if (mem.findScalar(u8, key, '=') != null) { | |
| 905 | return null; | |
| 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; | |
| 2485 | 916 | |
| 2486 | /// Same as `rename` except the parameters are null-terminated and WTF16LE encoded. | |
| 2487 | /// Assumes target is Windows. | |
| 2488 | pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void { | |
| 2489 | const cwd_handle = std.fs.cwd().fd; | |
| 2490 | return windows.RenameFile(cwd_handle, mem.span(old_path), cwd_handle, mem.span(new_path), true); | |
| 2491 | } | |
| 917 | return mem.sliceTo(line + line_i + 1, 0); | |
| 918 | } | |
| 919 | return null; | |
| 920 | } | |
| 921 | if (native_os == .wasi) { | |
| 922 | @compileError("std.posix.getenv is unavailable for WASI. See std.process.getEnvMap or std.process.getEnvVarOwned for a cross-platform API."); | |
| 923 | } | |
| 924 | // The simplified start logic doesn't populate environ. | |
| 925 | if (std.start.simplified_logic) return null; | |
| 926 | // TODO see https://github.com/ziglang/zig/issues/4524 | |
| 927 | for (std.os.environ) |ptr| { | |
| 928 | var line_i: usize = 0; | |
| 929 | while (ptr[line_i] != 0) : (line_i += 1) { | |
| 930 | if (line_i == key.len) break; | |
| 931 | if (ptr[line_i] != key[line_i]) break; | |
| 932 | } | |
| 933 | if ((line_i != key.len) or (ptr[line_i] != '=')) continue; | |
| 2492 | 934 | |
| 2493 | /// Change the name or location of a file based on an open directory handle. | |
| 2494 | /// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | |
| 2495 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 2496 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 2497 | pub fn renameat( | |
| 2498 | old_dir_fd: fd_t, | |
| 2499 | old_path: []const u8, | |
| 2500 | new_dir_fd: fd_t, | |
| 2501 | new_path: []const u8, | |
| 2502 | ) RenameError!void { | |
| 2503 | if (native_os == .windows) { | |
| 2504 | const old_path_w = try windows.sliceToPrefixedFileW(old_dir_fd, old_path); | |
| 2505 | const new_path_w = try windows.sliceToPrefixedFileW(new_dir_fd, new_path); | |
| 2506 | return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); | |
| 2507 | } else if (native_os == .wasi and !builtin.link_libc) { | |
| 2508 | const old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path }; | |
| 2509 | const new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path }; | |
| 2510 | return renameatWasi(old, new); | |
| 2511 | } else { | |
| 2512 | const old_path_c = try toPosixPath(old_path); | |
| 2513 | const new_path_c = try toPosixPath(new_path); | |
| 2514 | return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c); | |
| 935 | return mem.sliceTo(ptr + line_i + 1, 0); | |
| 2515 | 936 | } |
| 937 | return null; | |
| 2516 | 938 | } |
| 2517 | 939 | |
| 2518 | /// WASI-only. Same as `renameat` expect targeting WASI. | |
| 2519 | /// See also `renameat`. | |
| 2520 | fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void { | |
| 2521 | switch (wasi.path_rename(old.dir_fd, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) { | |
| 2522 | .SUCCESS => return, | |
| 2523 | .ACCES => return error.AccessDenied, | |
| 2524 | .PERM => return error.PermissionDenied, | |
| 2525 | .BUSY => return error.FileBusy, | |
| 2526 | .DQUOT => return error.DiskQuota, | |
| 2527 | .FAULT => unreachable, | |
| 2528 | .INVAL => unreachable, | |
| 2529 | .ISDIR => return error.IsDir, | |
| 2530 | .LOOP => return error.SymLinkLoop, | |
| 2531 | .MLINK => return error.LinkQuotaExceeded, | |
| 2532 | .NAMETOOLONG => return error.NameTooLong, | |
| 2533 | .NOENT => return error.FileNotFound, | |
| 2534 | .NOTDIR => return error.NotDir, | |
| 2535 | .NOMEM => return error.SystemResources, | |
| 2536 | .NOSPC => return error.NoSpaceLeft, | |
| 2537 | .EXIST => return error.PathAlreadyExists, | |
| 2538 | .NOTEMPTY => return error.PathAlreadyExists, | |
| 2539 | .ROFS => return error.ReadOnlyFileSystem, | |
| 2540 | .XDEV => return error.RenameAcrossMountPoints, | |
| 2541 | .NOTCAPABLE => return error.AccessDenied, | |
| 2542 | .ILSEQ => return error.BadPathName, | |
| 2543 | else => |err| return unexpectedErrno(err), | |
| 940 | /// Get an environment variable with a null-terminated name. | |
| 941 | /// See also `getenv`. | |
| 942 | pub fn getenvZ(key: [*:0]const u8) ?[:0]const u8 { | |
| 943 | if (builtin.link_libc) { | |
| 944 | const value = system.getenv(key) orelse return null; | |
| 945 | return mem.sliceTo(value, 0); | |
| 946 | } | |
| 947 | if (native_os == .windows) { | |
| 948 | @compileError("std.posix.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.process.getenvW for Windows-specific API."); | |
| 2544 | 949 | } |
| 950 | return getenv(mem.sliceTo(key, 0)); | |
| 2545 | 951 | } |
| 2546 | 952 | |
| 2547 | /// An fd-relative file path | |
| 2548 | /// | |
| 2549 | /// This is currently only used for WASI-specific functionality, but the concept | |
| 2550 | /// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions. | |
| 2551 | const RelativePathWasi = struct { | |
| 2552 | /// Handle to directory | |
| 2553 | dir_fd: fd_t, | |
| 2554 | /// Path to resource within `dir_fd`. | |
| 2555 | relative_path: []const u8, | |
| 2556 | }; | |
| 953 | pub const GetCwdError = error{ | |
| 954 | NameTooLong, | |
| 955 | CurrentWorkingDirectoryUnlinked, | |
| 956 | } || UnexpectedError; | |
| 2557 | 957 | |
| 2558 | /// Same as `renameat` except the parameters are null-terminated. | |
| 2559 | pub fn renameatZ( | |
| 2560 | old_dir_fd: fd_t, | |
| 2561 | old_path: [*:0]const u8, | |
| 2562 | new_dir_fd: fd_t, | |
| 2563 | new_path: [*:0]const u8, | |
| 2564 | ) RenameError!void { | |
| 958 | /// The result is a slice of out_buffer, indexed from 0. | |
| 959 | pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { | |
| 2565 | 960 | if (native_os == .windows) { |
| 2566 | const old_path_w = try windows.cStrToPrefixedFileW(old_dir_fd, old_path); | |
| 2567 | const new_path_w = try windows.cStrToPrefixedFileW(new_dir_fd, new_path); | |
| 2568 | return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); | |
| 961 | return windows.GetCurrentDirectory(out_buffer); | |
| 2569 | 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 | } |
| 2572 | 969 | |
| 2573 | switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) { | |
| 2574 | .SUCCESS => return, | |
| 2575 | .ACCES => return error.AccessDenied, | |
| 2576 | .PERM => return error.PermissionDenied, | |
| 2577 | .BUSY => return error.FileBusy, | |
| 2578 | .DQUOT => return error.DiskQuota, | |
| 970 | const err: E = if (builtin.link_libc) err: { | |
| 971 | const c_err = if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*; | |
| 972 | break :err @enumFromInt(c_err); | |
| 973 | } else err: { | |
| 974 | break :err errno(system.getcwd(out_buffer.ptr, out_buffer.len)); | |
| 975 | }; | |
| 976 | switch (err) { | |
| 977 | .SUCCESS => return mem.sliceTo(out_buffer, 0), | |
| 2579 | 978 | .FAULT => unreachable, |
| 2580 | 979 | .INVAL => unreachable, |
| 2581 | .ISDIR => return error.IsDir, | |
| 2582 | .LOOP => return error.SymLinkLoop, | |
| 2583 | .MLINK => return error.LinkQuotaExceeded, | |
| 2584 | .NAMETOOLONG => return error.NameTooLong, | |
| 2585 | .NOENT => return error.FileNotFound, | |
| 2586 | .NOTDIR => return error.NotDir, | |
| 2587 | .NOMEM => return error.SystemResources, | |
| 2588 | .NOSPC => return error.NoSpaceLeft, | |
| 2589 | .EXIST => return error.PathAlreadyExists, | |
| 2590 | .NOTEMPTY => return error.PathAlreadyExists, | |
| 2591 | .ROFS => return error.ReadOnlyFileSystem, | |
| 2592 | .XDEV => return error.RenameAcrossMountPoints, | |
| 2593 | .ILSEQ => return error.BadPathName, | |
| 2594 | else => |err| return unexpectedErrno(err), | |
| 980 | .NOENT => return error.CurrentWorkingDirectoryUnlinked, | |
| 981 | .RANGE => return error.NameTooLong, | |
| 982 | else => return unexpectedErrno(err), | |
| 2595 | 983 | } |
| 2596 | 984 | } |
| 2597 | 985 | |
| 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. | |
| 2600 | pub 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 | 986 | /// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| 2611 | 987 | /// On WASI, `sub_dir_path` should be encoded as valid UTF-8. |
| 2612 | 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 | 1027 | } |
| 2652 | 1028 | } |
| 2653 | 1029 | |
| 2654 | pub const MakeDirError = std.Io.Dir.MakeError; | |
| 1030 | pub const MakeDirError = std.Io.Dir.CreateDirError; | |
| 2655 | 1031 | |
| 2656 | 1032 | /// Create a directory. |
| 2657 | 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 | 1081 | pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void { |
| 2706 | 1082 | _ = mode; |
| 2707 | 1083 | const sub_dir_handle = windows.OpenFile(dir_path_w, .{ |
| 2708 | .dir = fs.cwd().fd, | |
| 1084 | .dir = Io.Dir.cwd().handle, | |
| 2709 | 1085 | .access_mask = .{ |
| 2710 | 1086 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 2711 | 1087 | .GENERIC = .{ .READ = true }, |
| ... | ... | @@ -2723,84 +1099,6 @@ pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void { |
| 2723 | 1099 | windows.CloseHandle(sub_dir_handle); |
| 2724 | 1100 | } |
| 2725 | 1101 | |
| 2726 | pub 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. | |
| 2749 | pub 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. | |
| 2769 | pub 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. | |
| 2797 | pub 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 | ||
| 2804 | 1102 | pub const ChangeCurDirError = error{ |
| 2805 | 1103 | AccessDenied, |
| 2806 | 1104 | FileSystem, |
| ... | ... | @@ -2821,11 +1119,9 @@ pub const ChangeCurDirError = error{ |
| 2821 | 1119 | /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. |
| 2822 | 1120 | pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { |
| 2823 | 1121 | if (native_os == .wasi and !builtin.link_libc) { |
| 2824 | @compileError("WASI does not support os.chdir"); | |
| 1122 | @compileError("unsupported OS"); | |
| 2825 | 1123 | } else if (native_os == .windows) { |
| 2826 | var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; | |
| 2827 | const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path); | |
| 2828 | return chdirW(wtf16_dir_path[0..len]); | |
| 1124 | @compileError("unsupported OS"); | |
| 2829 | 1125 | } else { |
| 2830 | 1126 | const dir_path_c = try toPosixPath(dir_path); |
| 2831 | 1127 | return chdirZ(&dir_path_c); |
| ... | ... | @@ -2838,12 +1134,9 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { |
| 2838 | 1134 | /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. |
| 2839 | 1135 | pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { |
| 2840 | 1136 | if (native_os == .windows) { |
| 2841 | const dir_path_span = mem.span(dir_path); | |
| 2842 | var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; | |
| 2843 | const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path_span); | |
| 2844 | return chdirW(wtf16_dir_path[0..len]); | |
| 1137 | @compileError("unsupported OS"); | |
| 2845 | 1138 | } else if (native_os == .wasi and !builtin.link_libc) { |
| 2846 | return chdir(mem.span(dir_path)); | |
| 1139 | @compileError("unsupported OS"); | |
| 2847 | 1140 | } |
| 2848 | 1141 | switch (errno(system.chdir(dir_path))) { |
| 2849 | 1142 | .SUCCESS => return, |
| ... | ... | @@ -2860,14 +1153,6 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { |
| 2860 | 1153 | } |
| 2861 | 1154 | } |
| 2862 | 1155 | |
| 2863 | /// Windows-only. Same as `chdir` except the parameter is WTF16 LE encoded. | |
| 2864 | pub 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 | ||
| 2871 | 1156 | pub const FchdirError = error{ |
| 2872 | 1157 | AccessDenied, |
| 2873 | 1158 | NotDir, |
| ... | ... | @@ -2889,194 +1174,6 @@ pub fn fchdir(dirfd: fd_t) FchdirError!void { |
| 2889 | 1174 | } |
| 2890 | 1175 | } |
| 2891 | 1176 | |
| 2892 | pub 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. | |
| 2929 | pub 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`. | |
| 2954 | pub 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. | |
| 2959 | pub 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`. | |
| 2998 | pub 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`. | |
| 3018 | pub 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`. | |
| 3044 | pub 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`. | |
| 3050 | pub 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 | ||
| 3080 | 1177 | pub const SetEidError = error{ |
| 3081 | 1178 | InvalidUserId, |
| 3082 | 1179 | PermissionDenied, |
| ... | ... | @@ -3176,47 +1273,6 @@ pub fn getegid() gid_t { |
| 3176 | 1273 | return system.getegid(); |
| 3177 | 1274 | } |
| 3178 | 1275 | |
| 3179 | /// Test whether a file descriptor refers to a terminal. | |
| 3180 | pub 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 | ||
| 3220 | 1276 | pub const SocketError = error{ |
| 3221 | 1277 | /// Permission to create a socket of the specified type and/or |
| 3222 | 1278 | /// pro‐tocol is denied. |
| ... | ... | @@ -3406,7 +1462,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi |
| 3406 | 1462 | |
| 3407 | 1463 | pub const ListenError = error{ |
| 3408 | 1464 | FileDescriptorNotASocket, |
| 3409 | OperationNotSupported, | |
| 1465 | OperationUnsupported, | |
| 3410 | 1466 | } || std.Io.net.IpAddress.ListenError || std.Io.net.UnixAddress.ListenError; |
| 3411 | 1467 | |
| 3412 | 1468 | 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 | 1475 | .ADDRINUSE => return error.AddressInUse, |
| 3420 | 1476 | .BADF => unreachable, |
| 3421 | 1477 | .NOTSOCK => return error.FileDescriptorNotASocket, |
| 3422 | .OPNOTSUPP => return error.OperationNotSupported, | |
| 1478 | .OPNOTSUPP => return error.OperationUnsupported, | |
| 3423 | 1479 | else => |err| return unexpectedErrno(err), |
| 3424 | 1480 | } |
| 3425 | 1481 | } |
| ... | ... | @@ -4081,7 +2137,7 @@ pub const FanotifyMarkError = error{ |
| 4081 | 2137 | SystemResources, |
| 4082 | 2138 | UserMarkQuotaExceeded, |
| 4083 | 2139 | NotDir, |
| 4084 | OperationNotSupported, | |
| 2140 | OperationUnsupported, | |
| 4085 | 2141 | PermissionDenied, |
| 4086 | 2142 | NotSameFileSystem, |
| 4087 | 2143 | NameTooLong, |
| ... | ... | @@ -4121,7 +2177,7 @@ pub fn fanotify_markZ( |
| 4121 | 2177 | .NOMEM => return error.SystemResources, |
| 4122 | 2178 | .NOSPC => return error.UserMarkQuotaExceeded, |
| 4123 | 2179 | .NOTDIR => return error.NotDir, |
| 4124 | .OPNOTSUPP => return error.OperationNotSupported, | |
| 2180 | .OPNOTSUPP => return error.OperationUnsupported, | |
| 4125 | 2181 | .PERM => return error.PermissionDenied, |
| 4126 | 2182 | .XDEV => return error.NotSameFileSystem, |
| 4127 | 2183 | else => |err| return unexpectedErrno(err), |
| ... | ... | @@ -4370,75 +2426,14 @@ pub const MSyncError = error{ |
| 4370 | 2426 | UnmappedMemory, |
| 4371 | 2427 | PermissionDenied, |
| 4372 | 2428 | } || UnexpectedError; |
| 4373 | ||
| 4374 | pub fn msync(memory: []align(page_size_min) u8, flags: i32) MSyncError!void { | |
| 4375 | switch (errno(system.msync(memory.ptr, memory.len, flags))) { | |
| 4376 | .SUCCESS => return, | |
| 4377 | .PERM => return error.PermissionDenied, | |
| 4378 | .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory | |
| 4379 | .INVAL => unreachable, // Invalid parameters. | |
| 4380 | else => unreachable, | |
| 4381 | } | |
| 4382 | } | |
| 4383 | ||
| 4384 | pub 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. | |
| 4409 | pub 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. | |
| 4420 | pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { | |
| 4421 | if (native_os == .windows) { | |
| 4422 | @compileError("use std.Io instead"); | |
| 4423 | } else if (native_os == .wasi and !builtin.link_libc) { | |
| 4424 | return access(mem.sliceTo(path, 0), mode); | |
| 4425 | } | |
| 4426 | switch (errno(system.access(path, mode))) { | |
| 4427 | .SUCCESS => return, | |
| 4428 | .ACCES => return error.AccessDenied, | |
| 4429 | .PERM => return error.PermissionDenied, | |
| 4430 | .ROFS => return error.ReadOnlyFileSystem, | |
| 4431 | .LOOP => return error.SymLinkLoop, | |
| 4432 | .TXTBSY => return error.FileBusy, | |
| 4433 | .NOTDIR => return error.FileNotFound, | |
| 4434 | .NOENT => return error.FileNotFound, | |
| 4435 | .NAMETOOLONG => return error.NameTooLong, | |
| 4436 | .INVAL => unreachable, | |
| 4437 | .FAULT => unreachable, | |
| 4438 | .IO => return error.InputOutput, | |
| 4439 | .NOMEM => return error.SystemResources, | |
| 4440 | .ILSEQ => return error.BadPathName, | |
| 4441 | else => |err| return unexpectedErrno(err), | |
| 2429 | ||
| 2430 | pub fn msync(memory: []align(page_size_min) u8, flags: i32) MSyncError!void { | |
| 2431 | switch (errno(system.msync(memory.ptr, memory.len, flags))) { | |
| 2432 | .SUCCESS => return, | |
| 2433 | .PERM => return error.PermissionDenied, | |
| 2434 | .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory | |
| 2435 | .INVAL => unreachable, // Invalid parameters. | |
| 2436 | else => unreachable, | |
| 4442 | 2437 | } |
| 4443 | 2438 | } |
| 4444 | 2439 | |
| ... | ... | @@ -4586,177 +2581,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { |
| 4586 | 2581 | } |
| 4587 | 2582 | } |
| 4588 | 2583 | |
| 4589 | pub const SeekError = std.Io.File.SeekError; | |
| 4590 | ||
| 4591 | pub 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. | |
| 4634 | pub 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. | |
| 4676 | pub 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. | |
| 4718 | pub 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 | ||
| 4760 | 2584 | pub const FcntlError = error{ |
| 4761 | 2585 | PermissionDenied, |
| 4762 | 2586 | FileBusy, |
| ... | ... | @@ -4786,185 +2610,6 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { |
| 4786 | 2610 | } |
| 4787 | 2611 | } |
| 4788 | 2612 | |
| 4789 | pub 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. | |
| 4801 | pub 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 | ||
| 4817 | pub 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. | |
| 4880 | pub 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. | |
| 4898 | pub 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. | |
| 4955 | pub 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. | |
| 4964 | pub 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 | 2613 | /// Spurious wakeups are possible and no precision of timing is guaranteed. |
| 4969 | 2614 | pub fn nanosleep(seconds: u64, nanoseconds: u64) void { |
| 4970 | 2615 | var req = timespec{ |
| ... | ... | @@ -5049,16 +2694,16 @@ pub fn dl_iterate_phdr( |
| 5049 | 2694 | |
| 5050 | 2695 | // Last return value from the callback function. |
| 5051 | 2696 | while (it.next()) |entry| { |
| 5052 | const phdrs: []elf.ElfN.Phdr = if (entry.l_addr != 0) phdrs: { | |
| 5053 | const ehdr: *elf.ElfN.Ehdr = @ptrFromInt(entry.l_addr); | |
| 2697 | const phdrs: []elf.ElfN.Phdr = if (entry.addr != 0) phdrs: { | |
| 2698 | const ehdr: *elf.ElfN.Ehdr = @ptrFromInt(entry.addr); | |
| 5054 | 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 | 2701 | break :phdrs phdrs[0..ehdr.phnum]; |
| 5057 | 2702 | } else getSelfPhdrs(); |
| 5058 | 2703 | |
| 5059 | 2704 | var info: dl_phdr_info = .{ |
| 5060 | .addr = entry.l_addr, | |
| 5061 | .name = entry.l_name, | |
| 2705 | .addr = entry.addr, | |
| 2706 | .name = entry.name, | |
| 5062 | 2707 | .phdr = phdrs.ptr, |
| 5063 | 2708 | .phnum = @intCast(phdrs.len), |
| 5064 | 2709 | }; |
| ... | ... | @@ -5229,74 +2874,6 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?* |
| 5229 | 2874 | } |
| 5230 | 2875 | } |
| 5231 | 2876 | |
| 5232 | pub 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 | ||
| 5256 | pub 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 | ||
| 5300 | 2877 | pub const GetHostNameError = error{PermissionDenied} || UnexpectedError; |
| 5301 | 2878 | |
| 5302 | 2879 | pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 { |
| ... | ... | @@ -5612,98 +3189,6 @@ pub fn send( |
| 5612 | 3189 | }; |
| 5613 | 3190 | } |
| 5614 | 3191 | |
| 5615 | pub 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)`. | |
| 5657 | pub 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 | ||
| 5707 | 3192 | pub const PollError = error{ |
| 5708 | 3193 | /// The network subsystem has failed. |
| 5709 | 3194 | NetworkDown, |
| ... | ... | @@ -5916,7 +3401,7 @@ pub const SetSockOptError = error{ |
| 5916 | 3401 | /// Setting the socket option requires more elevated permissions. |
| 5917 | 3402 | PermissionDenied, |
| 5918 | 3403 | |
| 5919 | OperationNotSupported, | |
| 3404 | OperationUnsupported, | |
| 5920 | 3405 | NetworkDown, |
| 5921 | 3406 | FileDescriptorNotASocket, |
| 5922 | 3407 | SocketNotBound, |
| ... | ... | @@ -5952,7 +3437,7 @@ pub fn setsockopt(fd: socket_t, level: i32, optname: u32, opt: []const u8) SetSo |
| 5952 | 3437 | .NOBUFS => return error.SystemResources, |
| 5953 | 3438 | .PERM => return error.PermissionDenied, |
| 5954 | 3439 | .NODEV => return error.NoDevice, |
| 5955 | .OPNOTSUPP => return error.OperationNotSupported, | |
| 3440 | .OPNOTSUPP => return error.OperationUnsupported, | |
| 5956 | 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 | 3603 | } |
| 6119 | 3604 | } |
| 6120 | 3605 | |
| 6121 | pub const SyncError = error{ | |
| 6122 | InputOutput, | |
| 6123 | NoSpaceLeft, | |
| 6124 | DiskQuota, | |
| 6125 | AccessDenied, | |
| 6126 | } || UnexpectedError; | |
| 3606 | pub const SyncError = std.Io.File.SyncError; | |
| 6127 | 3607 | |
| 6128 | 3608 | /// Write all pending file contents and metadata modifications to all filesystems. |
| 6129 | 3609 | pub fn sync() void { |
| ... | ... | @@ -6143,38 +3623,8 @@ pub fn syncfs(fd: fd_t) SyncError!void { |
| 6143 | 3623 | } |
| 6144 | 3624 | } |
| 6145 | 3625 | |
| 6146 | /// Write all pending file contents and metadata modifications for the specified file descriptor to the underlying filesystem. | |
| 6147 | pub 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 | 3626 | /// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata. |
| 6171 | 3627 | pub 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 | 3628 | const rc = system.fdatasync(fd); |
| 6179 | 3629 | switch (errno(rc)) { |
| 6180 | 3630 | .SUCCESS => return, |
| ... | ... | @@ -6197,7 +3647,7 @@ pub const PrctlError = error{ |
| 6197 | 3647 | /// or PR_MPX_DISABLE_MANAGEMENT |
| 6198 | 3648 | UnsupportedFeature, |
| 6199 | 3649 | /// Can only occur with PR_SET_FP_MODE |
| 6200 | OperationNotSupported, | |
| 3650 | OperationUnsupported, | |
| 6201 | 3651 | PermissionDenied, |
| 6202 | 3652 | } || UnexpectedError; |
| 6203 | 3653 | |
| ... | ... | @@ -6221,7 +3671,7 @@ pub fn prctl(option: PR, args: anytype) PrctlError!u31 { |
| 6221 | 3671 | .FAULT => return error.InvalidAddress, |
| 6222 | 3672 | .INVAL => unreachable, |
| 6223 | 3673 | .NODEV, .NXIO => return error.UnsupportedFeature, |
| 6224 | .OPNOTSUPP => return error.OperationNotSupported, | |
| 3674 | .OPNOTSUPP => return error.OperationUnsupported, | |
| 6225 | 3675 | .PERM, .BUSY => return error.PermissionDenied, |
| 6226 | 3676 | .RANGE => unreachable, |
| 6227 | 3677 | else => |err| return unexpectedErrno(err), |
| ... | ... | @@ -6480,7 +3930,7 @@ pub const PtraceError = error{ |
| 6480 | 3930 | DeviceBusy, |
| 6481 | 3931 | InputOutput, |
| 6482 | 3932 | NameTooLong, |
| 6483 | OperationNotSupported, | |
| 3933 | OperationUnsupported, | |
| 6484 | 3934 | OutOfMemory, |
| 6485 | 3935 | ProcessNotFound, |
| 6486 | 3936 | PermissionDenied, |
| ... | ... | @@ -6582,7 +4032,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo |
| 6582 | 4032 | .INVAL => unreachable, |
| 6583 | 4033 | .PERM => error.PermissionDenied, |
| 6584 | 4034 | .BUSY => error.DeviceBusy, |
| 6585 | .NOTSUP => error.OperationNotSupported, | |
| 4035 | .NOTSUP => error.OperationUnsupported, | |
| 6586 | 4036 | else => |err| return unexpectedErrno(err), |
| 6587 | 4037 | }, |
| 6588 | 4038 | |
| ... | ... | @@ -6593,7 +4043,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo |
| 6593 | 4043 | pub const NameToFileHandleAtError = error{ |
| 6594 | 4044 | FileNotFound, |
| 6595 | 4045 | NotDir, |
| 6596 | OperationNotSupported, | |
| 4046 | OperationUnsupported, | |
| 6597 | 4047 | NameTooLong, |
| 6598 | 4048 | Unexpected, |
| 6599 | 4049 | }; |
| ... | ... | @@ -6622,7 +4072,7 @@ pub fn name_to_handle_atZ( |
| 6622 | 4072 | .INVAL => unreachable, // bad flags, or handle_bytes too big |
| 6623 | 4073 | .NOENT => return error.FileNotFound, |
| 6624 | 4074 | .NOTDIR => return error.NotDir, |
| 6625 | .OPNOTSUPP => return error.OperationNotSupported, | |
| 4075 | .OPNOTSUPP => return error.OperationUnsupported, | |
| 6626 | 4076 | .OVERFLOW => return error.NameTooLong, |
| 6627 | 4077 | else => |err| return unexpectedErrno(err), |
| 6628 | 4078 | } |
lib/std/posix/test.zig+84-554| ... | ... | @@ -1,34 +1,24 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const native_os = builtin.target.os.tag; | |
| 3 | const AtomicRmwOp = std.builtin.AtomicRmwOp; | |
| 4 | const AtomicOrder = std.builtin.AtomicOrder; | |
| 5 | ||
| 1 | 6 | const std = @import("../std.zig"); |
| 7 | const Io = std.Io; | |
| 8 | const Dir = std.Io.Dir; | |
| 2 | 9 | const posix = std.posix; |
| 3 | const testing = std.testing; | |
| 4 | const expect = testing.expect; | |
| 5 | const expectEqual = testing.expectEqual; | |
| 6 | const expectError = testing.expectError; | |
| 7 | const fs = std.fs; | |
| 8 | 10 | const mem = std.mem; |
| 9 | 11 | const elf = std.elf; |
| 10 | 12 | const linux = std.os.linux; |
| 13 | const AT = std.posix.AT; | |
| 11 | 14 | |
| 12 | const a = std.testing.allocator; | |
| 13 | ||
| 14 | const builtin = @import("builtin"); | |
| 15 | const AtomicRmwOp = std.builtin.AtomicRmwOp; | |
| 16 | const AtomicOrder = std.builtin.AtomicOrder; | |
| 17 | const native_os = builtin.target.os.tag; | |
| 15 | const testing = std.testing; | |
| 16 | const expect = std.testing.expect; | |
| 17 | const expectEqual = std.testing.expectEqual; | |
| 18 | const expectEqualSlices = std.testing.expectEqualSlices; | |
| 19 | const expectEqualStrings = std.testing.expectEqualStrings; | |
| 20 | const expectError = std.testing.expectError; | |
| 18 | 21 | const tmpDir = std.testing.tmpDir; |
| 19 | const 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 | |
| 25 | test "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 | } | |
| 32 | 22 | |
| 33 | 23 | test "check WASI CWD" { |
| 34 | 24 | if (native_os == .wasi) { |
| ... | ... | @@ -43,206 +33,6 @@ test "check WASI CWD" { |
| 43 | 33 | } |
| 44 | 34 | } |
| 45 | 35 | |
| 46 | test "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 | ||
| 114 | test "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 | ||
| 122 | fn 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 | ||
| 128 | fn 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 | ||
| 145 | test "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 | ||
| 184 | test "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 | ||
| 217 | test "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 | ||
| 246 | 36 | test "getrandom" { |
| 247 | 37 | var buf_a: [50]u8 = undefined; |
| 248 | 38 | var buf_b: [50]u8 = undefined; |
| ... | ... | @@ -273,7 +63,7 @@ test "sigaltstack" { |
| 273 | 63 | // Setting a stack size less than MINSIGSTKSZ returns ENOMEM |
| 274 | 64 | st.flags = 0; |
| 275 | 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 | } |
| 278 | 68 | |
| 279 | 69 | // If the type is not available use void to avoid erroring out when `iter_fn` is |
| ... | ... | @@ -345,7 +135,7 @@ test "pipe" { |
| 345 | 135 | try expect((try posix.write(fds[1], "hello")) == 5); |
| 346 | 136 | var buf: [16]u8 = undefined; |
| 347 | 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 | 139 | posix.close(fds[1]); |
| 350 | 140 | posix.close(fds[0]); |
| 351 | 141 | } |
| ... | ... | @@ -356,6 +146,8 @@ test "argsAlloc" { |
| 356 | 146 | } |
| 357 | 147 | |
| 358 | 148 | test "memfd_create" { |
| 149 | const io = testing.io; | |
| 150 | ||
| 359 | 151 | // memfd_create is only supported by linux and freebsd. |
| 360 | 152 | switch (native_os) { |
| 361 | 153 | .linux => {}, |
| ... | ... | @@ -366,21 +158,22 @@ test "memfd_create" { |
| 366 | 158 | else => return error.SkipZigTest, |
| 367 | 159 | } |
| 368 | 160 | |
| 369 | const fd = try posix.memfd_create("test", 0); | |
| 370 | defer posix.close(fd); | |
| 371 | try expect((try posix.write(fd, "test")) == 4); | |
| 372 | try posix.lseek_SET(fd, 0); | |
| 161 | const file: Io.File = .{ .handle = try posix.memfd_create("test", 0) }; | |
| 162 | defer file.close(io); | |
| 163 | try file.writePositionalAll(io, "test", 0); | |
| 373 | 164 | |
| 374 | 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 | 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 | } |
| 379 | 170 | |
| 380 | 171 | test "mmap" { |
| 381 | 172 | if (native_os == .windows or native_os == .wasi) |
| 382 | 173 | return error.SkipZigTest; |
| 383 | 174 | |
| 175 | const io = testing.io; | |
| 176 | ||
| 384 | 177 | var tmp = tmpDir(.{}); |
| 385 | 178 | defer tmp.cleanup(); |
| 386 | 179 | |
| ... | ... | @@ -396,14 +189,14 @@ test "mmap" { |
| 396 | 189 | ); |
| 397 | 190 | defer posix.munmap(data); |
| 398 | 191 | |
| 399 | try testing.expectEqual(@as(usize, 1234), data.len); | |
| 192 | try expectEqual(@as(usize, 1234), data.len); | |
| 400 | 193 | |
| 401 | 194 | // 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)); | |
| 403 | 196 | |
| 404 | 197 | // Make sure the memory is writeable as requested |
| 405 | 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 | } |
| 408 | 201 | |
| 409 | 202 | const test_out_file = "os_tmp_test"; |
| ... | ... | @@ -412,10 +205,10 @@ test "mmap" { |
| 412 | 205 | |
| 413 | 206 | // Create a file used for testing mmap() calls with a file descriptor |
| 414 | 207 | { |
| 415 | const file = try tmp.dir.createFile(test_out_file, .{}); | |
| 416 | defer file.close(); | |
| 208 | const file = try tmp.dir.createFile(io, test_out_file, .{}); | |
| 209 | defer file.close(io); | |
| 417 | 210 | |
| 418 | var stream = file.writer(&.{}); | |
| 211 | var stream = file.writer(io, &.{}); | |
| 419 | 212 | |
| 420 | 213 | var i: usize = 0; |
| 421 | 214 | while (i < alloc_size / @sizeOf(u32)) : (i += 1) { |
| ... | ... | @@ -425,8 +218,8 @@ test "mmap" { |
| 425 | 218 | |
| 426 | 219 | // Map the whole file |
| 427 | 220 | { |
| 428 | const file = try tmp.dir.openFile(test_out_file, .{}); | |
| 429 | defer file.close(); | |
| 221 | const file = try tmp.dir.openFile(io, test_out_file, .{}); | |
| 222 | defer file.close(io); | |
| 430 | 223 | |
| 431 | 224 | const data = try posix.mmap( |
| 432 | 225 | null, |
| ... | ... | @@ -442,7 +235,7 @@ test "mmap" { |
| 442 | 235 | |
| 443 | 236 | var i: usize = 0; |
| 444 | 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 | } |
| 448 | 241 | |
| ... | ... | @@ -450,8 +243,8 @@ test "mmap" { |
| 450 | 243 | |
| 451 | 244 | // Map the upper half of the file |
| 452 | 245 | { |
| 453 | const file = try tmp.dir.openFile(test_out_file, .{}); | |
| 454 | defer file.close(); | |
| 246 | const file = try tmp.dir.openFile(io, test_out_file, .{}); | |
| 247 | defer file.close(io); | |
| 455 | 248 | |
| 456 | 249 | const data = try posix.mmap( |
| 457 | 250 | null, |
| ... | ... | @@ -467,7 +260,7 @@ test "mmap" { |
| 467 | 260 | |
| 468 | 261 | var i: usize = alloc_size / 2 / @sizeOf(u32); |
| 469 | 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 | 269 | if (native_os == .windows or native_os == .wasi) |
| 477 | 270 | return error.SkipZigTest; |
| 478 | 271 | |
| 272 | const io = testing.io; | |
| 273 | ||
| 479 | 274 | var tmp = tmpDir(.{}); |
| 480 | 275 | defer tmp.cleanup(); |
| 481 | 276 | |
| 482 | 277 | const test_out_file = "os_tmp_test"; |
| 483 | 278 | |
| 484 | const file = try tmp.dir.createFile(test_out_file, .{}); | |
| 485 | defer file.close(); | |
| 279 | const file = try tmp.dir.createFile(io, test_out_file, .{}); | |
| 280 | defer file.close(io); | |
| 486 | 281 | |
| 487 | 282 | // Note: The test assumes createFile opens the file with CLOEXEC |
| 488 | 283 | { |
| ... | ... | @@ -522,18 +317,20 @@ test "sync" { |
| 522 | 317 | |
| 523 | 318 | test "fsync" { |
| 524 | 319 | switch (native_os) { |
| 525 | .linux, .windows, .illumos => {}, | |
| 320 | .linux, .illumos => {}, | |
| 526 | 321 | else => return error.SkipZigTest, |
| 527 | 322 | } |
| 528 | 323 | |
| 324 | const io = testing.io; | |
| 325 | ||
| 529 | 326 | var tmp = tmpDir(.{}); |
| 530 | 327 | defer tmp.cleanup(); |
| 531 | 328 | |
| 532 | 329 | const test_out_file = "os_tmp_test"; |
| 533 | const file = try tmp.dir.createFile(test_out_file, .{}); | |
| 534 | defer file.close(); | |
| 330 | const file = try tmp.dir.createFile(io, test_out_file, .{}); | |
| 331 | defer file.close(io); | |
| 535 | 332 | |
| 536 | try posix.fsync(file.handle); | |
| 333 | try file.sync(io); | |
| 537 | 334 | try posix.fdatasync(file.handle); |
| 538 | 335 | } |
| 539 | 336 | |
| ... | ... | @@ -571,9 +368,9 @@ test "sigrtmin/max" { |
| 571 | 368 | return error.SkipZigTest; |
| 572 | 369 | } |
| 573 | 370 | |
| 574 | try std.testing.expect(posix.sigrtmin() >= 32); | |
| 575 | try std.testing.expect(posix.sigrtmin() >= posix.system.sigrtmin()); | |
| 576 | try std.testing.expect(posix.sigrtmin() < posix.system.sigrtmax()); | |
| 371 | try expect(posix.sigrtmin() >= 32); | |
| 372 | try expect(posix.sigrtmin() >= posix.system.sigrtmin()); | |
| 373 | try expect(posix.sigrtmin() < posix.system.sigrtmax()); | |
| 577 | 374 | } |
| 578 | 375 | |
| 579 | 376 | test "sigset empty/full" { |
| ... | ... | @@ -646,27 +443,29 @@ test "dup & dup2" { |
| 646 | 443 | else => return error.SkipZigTest, |
| 647 | 444 | } |
| 648 | 445 | |
| 446 | const io = testing.io; | |
| 447 | ||
| 649 | 448 | var tmp = tmpDir(.{}); |
| 650 | 449 | defer tmp.cleanup(); |
| 651 | 450 | |
| 652 | 451 | { |
| 653 | var file = try tmp.dir.createFile("os_dup_test", .{}); | |
| 654 | defer file.close(); | |
| 452 | var file = try tmp.dir.createFile(io, "os_dup_test", .{}); | |
| 453 | defer file.close(io); | |
| 655 | 454 | |
| 656 | var duped = std.fs.File{ .handle = try posix.dup(file.handle) }; | |
| 657 | defer duped.close(); | |
| 658 | try duped.writeAll("dup"); | |
| 455 | var duped = Io.File{ .handle = try posix.dup(file.handle) }; | |
| 456 | defer duped.close(io); | |
| 457 | try duped.writeStreamingAll(io, "dup"); | |
| 659 | 458 | |
| 660 | 459 | // Tests aren't run in parallel so using the next fd shouldn't be an issue. |
| 661 | 460 | const new_fd = duped.handle + 1; |
| 662 | 461 | try posix.dup2(file.handle, new_fd); |
| 663 | var dup2ed = std.fs.File{ .handle = new_fd }; | |
| 664 | defer dup2ed.close(); | |
| 665 | try dup2ed.writeAll("dup2"); | |
| 462 | var dup2ed = Io.File{ .handle = new_fd }; | |
| 463 | defer dup2ed.close(io); | |
| 464 | try dup2ed.writeStreamingAll(io, "dup2"); | |
| 666 | 465 | } |
| 667 | 466 | |
| 668 | 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 | } |
| 671 | 470 | |
| 672 | 471 | test "getpid" { |
| ... | ... | @@ -684,208 +483,78 @@ test "getppid" { |
| 684 | 483 | try expect(posix.getppid() >= 0); |
| 685 | 484 | } |
| 686 | 485 | |
| 687 | test "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 | ||
| 701 | test "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 | ||
| 766 | 486 | test "rename smoke test" { |
| 767 | 487 | if (native_os == .wasi) return error.SkipZigTest; |
| 768 | 488 | if (native_os == .windows) return error.SkipZigTest; |
| 769 | 489 | if (native_os == .openbsd) return error.SkipZigTest; |
| 770 | 490 | |
| 491 | const io = testing.io; | |
| 492 | const gpa = testing.allocator; | |
| 493 | ||
| 771 | 494 | var tmp = tmpDir(.{}); |
| 772 | 495 | defer tmp.cleanup(); |
| 773 | 496 | |
| 774 | const base_path = try tmp.dir.realpathAlloc(a, "."); | |
| 775 | defer a.free(base_path); | |
| 497 | const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa); | |
| 498 | defer gpa.free(base_path); | |
| 776 | 499 | |
| 777 | 500 | const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; |
| 778 | 501 | |
| 779 | 502 | { |
| 780 | 503 | // Create some file using `open`. |
| 781 | const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); | |
| 782 | defer a.free(file_path); | |
| 504 | const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); | |
| 505 | defer gpa.free(file_path); | |
| 783 | 506 | const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); |
| 784 | 507 | posix.close(fd); |
| 785 | 508 | |
| 786 | 509 | // Rename the file |
| 787 | const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_file" }); | |
| 788 | defer a.free(new_file_path); | |
| 789 | try posix.rename(file_path, new_file_path); | |
| 510 | const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" }); | |
| 511 | defer gpa.free(new_file_path); | |
| 512 | try Io.Dir.renameAbsolute(file_path, new_file_path, io); | |
| 790 | 513 | } |
| 791 | 514 | |
| 792 | 515 | { |
| 793 | 516 | // Try opening renamed file |
| 794 | const file_path = try fs.path.join(a, &.{ base_path, "some_other_file" }); | |
| 795 | defer a.free(file_path); | |
| 517 | const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" }); | |
| 518 | defer gpa.free(file_path); | |
| 796 | 519 | const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode); |
| 797 | 520 | posix.close(fd); |
| 798 | 521 | } |
| 799 | 522 | |
| 800 | 523 | { |
| 801 | 524 | // Try opening original file - should fail with error.FileNotFound |
| 802 | const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); | |
| 803 | defer a.free(file_path); | |
| 525 | const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); | |
| 526 | defer gpa.free(file_path); | |
| 804 | 527 | try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode)); |
| 805 | 528 | } |
| 806 | 529 | |
| 807 | 530 | { |
| 808 | 531 | // Create some directory |
| 809 | const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); | |
| 810 | defer a.free(file_path); | |
| 532 | const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); | |
| 533 | defer gpa.free(file_path); | |
| 811 | 534 | try posix.mkdir(file_path, mode); |
| 812 | 535 | |
| 813 | 536 | // Rename the directory |
| 814 | const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" }); | |
| 815 | defer a.free(new_file_path); | |
| 816 | try posix.rename(file_path, new_file_path); | |
| 537 | const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" }); | |
| 538 | defer gpa.free(new_file_path); | |
| 539 | try Io.Dir.renameAbsolute(file_path, new_file_path, io); | |
| 817 | 540 | } |
| 818 | 541 | |
| 819 | 542 | { |
| 820 | 543 | // Try opening renamed directory |
| 821 | const file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" }); | |
| 822 | defer a.free(file_path); | |
| 544 | const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" }); | |
| 545 | defer gpa.free(file_path); | |
| 823 | 546 | const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); |
| 824 | 547 | posix.close(fd); |
| 825 | 548 | } |
| 826 | 549 | |
| 827 | 550 | { |
| 828 | 551 | // Try opening original directory - should fail with error.FileNotFound |
| 829 | const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); | |
| 830 | defer a.free(file_path); | |
| 552 | const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); | |
| 553 | defer gpa.free(file_path); | |
| 831 | 554 | try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode)); |
| 832 | 555 | } |
| 833 | 556 | } |
| 834 | 557 | |
| 835 | test "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 | ||
| 889 | 558 | test "timerfd" { |
| 890 | 559 | if (native_os != .linux) return error.SkipZigTest; |
| 891 | 560 | |
| ... | ... | @@ -903,142 +572,3 @@ test "timerfd" { |
| 903 | 572 | const expect_disarmed_timer: linux.itimerspec = .{ .it_interval = .{ .sec = 0, .nsec = 0 }, .it_value = .{ .sec = 0, .nsec = 0 } }; |
| 904 | 573 | try expectEqual(expect_disarmed_timer, git); |
| 905 | 574 | } |
| 906 | ||
| 907 | test "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 | ||
| 917 | test "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 | ||
| 931 | test "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 | ||
| 945 | test "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 | ||
| 959 | fn 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 | ||
| 978 | fn 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 | ||
| 983 | test "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 | ||
| 1020 | const 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 | const std = @import("std.zig"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 2 | const native_os = builtin.os.tag; | |
| 3 | ||
| 4 | const std = @import("std.zig"); | |
| 5 | const Io = std.Io; | |
| 6 | const File = std.Io.File; | |
| 3 | 7 | const fs = std.fs; |
| 4 | 8 | const mem = std.mem; |
| 5 | 9 | const math = std.math; |
| 6 | const Allocator = mem.Allocator; | |
| 10 | const Allocator = std.mem.Allocator; | |
| 7 | 11 | const assert = std.debug.assert; |
| 8 | 12 | const testing = std.testing; |
| 9 | const native_os = builtin.os.tag; | |
| 10 | 13 | const posix = std.posix; |
| 11 | 14 | const windows = std.os.windows; |
| 12 | 15 | const unicode = std.unicode; |
| 16 | const max_path_bytes = std.fs.max_path_bytes; | |
| 13 | 17 | |
| 14 | 18 | pub const Child = @import("process/Child.zig"); |
| 15 | pub const abort = posix.abort; | |
| 16 | pub const exit = posix.exit; | |
| 17 | 19 | pub const changeCurDir = posix.chdir; |
| 18 | 20 | pub const changeCurDirZ = posix.chdirZ; |
| 19 | 21 | |
| 20 | 22 | pub const GetCwdError = posix.GetCwdError; |
| 21 | 23 | |
| 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. | |
| 29 | pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; | |
| 30 | ||
| 22 | 31 | /// The result is a slice of `out_buffer`, from index `0`. |
| 23 | 32 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| 24 | 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 | 44 | pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 { |
| 36 | 45 | // The use of max_path_bytes here is just a heuristic: most paths will fit |
| 37 | 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 | 48 | var heap_buf: ?[]u8 = null; |
| 40 | 49 | defer if (heap_buf) |buf| allocator.free(buf); |
| 41 | 50 | |
| ... | ... | @@ -437,25 +446,25 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError |
| 437 | 446 | } |
| 438 | 447 | |
| 439 | 448 | /// On Windows, `key` must be valid WTF-8. |
| 440 | pub fn hasEnvVarConstant(comptime key: []const u8) bool { | |
| 449 | pub inline fn hasEnvVarConstant(comptime key: []const u8) bool { | |
| 441 | 450 | if (native_os == .windows) { |
| 442 | 451 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); |
| 443 | 452 | return getenvW(key_w) != null; |
| 444 | 453 | } else if (native_os == .wasi and !builtin.link_libc) { |
| 445 | @compileError("hasEnvVarConstant is not supported for WASI without libc"); | |
| 454 | return false; | |
| 446 | 455 | } else { |
| 447 | 456 | return posix.getenv(key) != null; |
| 448 | 457 | } |
| 449 | 458 | } |
| 450 | 459 | |
| 451 | 460 | /// On Windows, `key` must be valid WTF-8. |
| 452 | pub fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { | |
| 461 | pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { | |
| 453 | 462 | if (native_os == .windows) { |
| 454 | 463 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); |
| 455 | 464 | const value = getenvW(key_w) orelse return false; |
| 456 | 465 | return value.len != 0; |
| 457 | 466 | } else if (native_os == .wasi and !builtin.link_libc) { |
| 458 | @compileError("hasNonEmptyEnvVarConstant is not supported for WASI without libc"); | |
| 467 | return false; | |
| 459 | 468 | } else { |
| 460 | 469 | const value = posix.getenv(key) orelse return false; |
| 461 | 470 | return value.len != 0; |
| ... | ... | @@ -1571,9 +1580,9 @@ pub fn getUserInfo(name: []const u8) !UserInfo { |
| 1571 | 1580 | |
| 1572 | 1581 | /// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else |
| 1573 | 1582 | /// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`. |
| 1574 | pub fn posixGetUserInfo(name: []const u8) !UserInfo { | |
| 1575 | const file = try std.fs.openFileAbsolute("/etc/passwd", .{}); | |
| 1576 | defer file.close(); | |
| 1583 | pub fn posixGetUserInfo(io: Io, name: []const u8) !UserInfo { | |
| 1584 | const file = try Io.Dir.openFileAbsolute(io, "/etc/passwd", .{}); | |
| 1585 | defer file.close(io); | |
| 1577 | 1586 | var buffer: [4096]u8 = undefined; |
| 1578 | 1587 | var file_reader = file.reader(&buffer); |
| 1579 | 1588 | return posixGetUserInfoPasswdStream(name, &file_reader.interface) catch |err| switch (err) { |
| ... | ... | @@ -1839,21 +1848,19 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 { |
| 1839 | 1848 | } |
| 1840 | 1849 | } |
| 1841 | 1850 | |
| 1842 | /// Indicate that we are now terminating with a successful exit code. | |
| 1843 | /// In debug builds, this is a no-op, so that the calling code's | |
| 1844 | /// cleanup mechanisms are tested and so that external tools that | |
| 1845 | /// check for resource leaks can be accurate. In release builds, this | |
| 1846 | /// calls exit(0), and does not return. | |
| 1847 | pub fn cleanExit() void { | |
| 1848 | if (builtin.mode == .Debug) { | |
| 1849 | return; | |
| 1850 | } else { | |
| 1851 | std.debug.lockStdErr(); | |
| 1852 | exit(0); | |
| 1853 | } | |
| 1851 | /// Indicate intent to terminate with a successful exit code. | |
| 1852 | /// | |
| 1853 | /// In debug builds, this is a no-op, so that the calling code's cleanup | |
| 1854 | /// mechanisms are tested and so that external tools checking for resource | |
| 1855 | /// leaks can be accurate. In release builds, this calls `exit` with code zero, | |
| 1856 | /// and does not return. | |
| 1857 | pub fn cleanExit(io: Io) void { | |
| 1858 | if (builtin.mode == .Debug) return; | |
| 1859 | _ = io.lockStderr(&.{}, .no_color) catch {}; | |
| 1860 | exit(0); | |
| 1854 | 1861 | } |
| 1855 | 1862 | |
| 1856 | /// Raise the open file descriptor limit. | |
| 1863 | /// Request ability to have more open file descriptors simultaneously. | |
| 1857 | 1864 | /// |
| 1858 | 1865 | /// On some systems, this raises the limit before seeing ProcessFdQuotaExceeded |
| 1859 | 1866 | /// errors. On other systems, this does nothing. |
| ... | ... | @@ -2110,3 +2117,215 @@ pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn { |
| 2110 | 2117 | std.log.err(format, format_arguments); |
| 2111 | 2118 | exit(1); |
| 2112 | 2119 | } |
| 2120 | ||
| 2121 | pub 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 | ||
| 2159 | pub const ExecutablePathAllocError = ExecutablePathBaseError || Allocator.Error; | |
| 2160 | ||
| 2161 | pub 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 | ||
| 2170 | pub 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 | |
| 2188 | pub 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. | |
| 2198 | pub 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. | |
| 2206 | pub 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 | ||
| 2215 | pub const OpenExecutableError = File.OpenError || ExecutablePathError || File.LockError; | |
| 2216 | ||
| 2217 | pub 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. | |
| 2227 | pub 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. | |
| 2284 | pub 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 | ||
| 2308 | pub 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. | |
| 2329 | pub 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 | const ChildProcess = @This(); | |
| 1 | const Child = @This(); | |
| 2 | 2 | |
| 3 | 3 | const builtin = @import("builtin"); |
| 4 | 4 | const native_os = builtin.os.tag; |
| 5 | 5 | |
| 6 | 6 | const std = @import("../std.zig"); |
| 7 | const Io = std.Io; | |
| 7 | 8 | const unicode = std.unicode; |
| 8 | 9 | const fs = std.fs; |
| 9 | 10 | const process = std.process; |
| 10 | const File = std.fs.File; | |
| 11 | const File = std.Io.File; | |
| 11 | 12 | const windows = std.os.windows; |
| 12 | 13 | const linux = std.os.linux; |
| 13 | 14 | const posix = std.posix; |
| ... | ... | @@ -30,7 +31,7 @@ pub const Id = switch (native_os) { |
| 30 | 31 | id: Id, |
| 31 | 32 | thread_handle: if (native_os == .windows) windows.HANDLE else void, |
| 32 | 33 | |
| 33 | allocator: mem.Allocator, | |
| 34 | allocator: Allocator, | |
| 34 | 35 | |
| 35 | 36 | /// The writing end of the child process's standard input pipe. |
| 36 | 37 | /// Usage requires `stdin_behavior == StdIo.Pipe`. |
| ... | ... | @@ -76,7 +77,7 @@ cwd: ?[]const u8, |
| 76 | 77 | /// Set to change the current working directory when spawning the child process. |
| 77 | 78 | /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190 |
| 78 | 79 | /// Once that is done, `cwd` will be deprecated in favor of this field. |
| 79 | cwd_dir: ?fs.Dir = null, | |
| 80 | cwd_dir: ?Io.Dir = null, | |
| 80 | 81 | |
| 81 | 82 | err_pipe: if (native_os == .windows) void else ?posix.fd_t, |
| 82 | 83 | |
| ... | ... | @@ -228,7 +229,7 @@ pub const StdIo = enum { |
| 228 | 229 | }; |
| 229 | 230 | |
| 230 | 231 | /// First argument in argv is the executable. |
| 231 | pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess { | |
| 232 | pub fn init(argv: []const []const u8, allocator: Allocator) Child { | |
| 232 | 233 | return .{ |
| 233 | 234 | .allocator = allocator, |
| 234 | 235 | .argv = argv, |
| ... | ... | @@ -251,7 +252,7 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess { |
| 251 | 252 | }; |
| 252 | 253 | } |
| 253 | 254 | |
| 254 | pub fn setUserName(self: *ChildProcess, name: []const u8) !void { | |
| 255 | pub fn setUserName(self: *Child, name: []const u8) !void { | |
| 255 | 256 | const user_info = try process.getUserInfo(name); |
| 256 | 257 | self.uid = user_info.uid; |
| 257 | 258 | self.gid = user_info.gid; |
| ... | ... | @@ -259,35 +260,35 @@ pub fn setUserName(self: *ChildProcess, name: []const u8) !void { |
| 259 | 260 | |
| 260 | 261 | /// On success must call `kill` or `wait`. |
| 261 | 262 | /// After spawning the `id` is available. |
| 262 | pub fn spawn(self: *ChildProcess) SpawnError!void { | |
| 263 | pub fn spawn(self: *Child, io: Io) SpawnError!void { | |
| 263 | 264 | if (!process.can_spawn) { |
| 264 | 265 | @compileError("the target operating system cannot spawn processes"); |
| 265 | 266 | } |
| 266 | 267 | |
| 267 | 268 | if (native_os == .windows) { |
| 268 | return self.spawnWindows(); | |
| 269 | return self.spawnWindows(io); | |
| 269 | 270 | } else { |
| 270 | return self.spawnPosix(); | |
| 271 | return self.spawnPosix(io); | |
| 271 | 272 | } |
| 272 | 273 | } |
| 273 | 274 | |
| 274 | pub fn spawnAndWait(self: *ChildProcess) SpawnError!Term { | |
| 275 | try self.spawn(); | |
| 276 | return self.wait(); | |
| 275 | pub fn spawnAndWait(child: *Child, io: Io) SpawnError!Term { | |
| 276 | try child.spawn(io); | |
| 277 | return child.wait(io); | |
| 277 | 278 | } |
| 278 | 279 | |
| 279 | 280 | /// Forcibly terminates child process and then cleans up all resources. |
| 280 | pub fn kill(self: *ChildProcess) !Term { | |
| 281 | pub fn kill(self: *Child, io: Io) !Term { | |
| 281 | 282 | if (native_os == .windows) { |
| 282 | return self.killWindows(1); | |
| 283 | return self.killWindows(io, 1); | |
| 283 | 284 | } else { |
| 284 | return self.killPosix(); | |
| 285 | return self.killPosix(io); | |
| 285 | 286 | } |
| 286 | 287 | } |
| 287 | 288 | |
| 288 | pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term { | |
| 289 | pub fn killWindows(self: *Child, io: Io, exit_code: windows.UINT) !Term { | |
| 289 | 290 | if (self.term) |term| { |
| 290 | self.cleanupStreams(); | |
| 291 | self.cleanupStreams(io); | |
| 291 | 292 | return term; |
| 292 | 293 | } |
| 293 | 294 | |
| ... | ... | @@ -303,20 +304,20 @@ pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term { |
| 303 | 304 | }, |
| 304 | 305 | else => return err, |
| 305 | 306 | }; |
| 306 | try self.waitUnwrappedWindows(); | |
| 307 | try self.waitUnwrappedWindows(io); | |
| 307 | 308 | return self.term.?; |
| 308 | 309 | } |
| 309 | 310 | |
| 310 | pub fn killPosix(self: *ChildProcess) !Term { | |
| 311 | pub fn killPosix(self: *Child, io: Io) !Term { | |
| 311 | 312 | if (self.term) |term| { |
| 312 | self.cleanupStreams(); | |
| 313 | self.cleanupStreams(io); | |
| 313 | 314 | return term; |
| 314 | 315 | } |
| 315 | 316 | posix.kill(self.id, posix.SIG.TERM) catch |err| switch (err) { |
| 316 | 317 | error.ProcessNotFound => return error.AlreadyTerminated, |
| 317 | 318 | else => return err, |
| 318 | 319 | }; |
| 319 | self.waitUnwrappedPosix(); | |
| 320 | self.waitUnwrappedPosix(io); | |
| 320 | 321 | return self.term.?; |
| 321 | 322 | } |
| 322 | 323 | |
| ... | ... | @@ -324,7 +325,7 @@ pub const WaitError = SpawnError || std.os.windows.GetProcessMemoryInfoError; |
| 324 | 325 | |
| 325 | 326 | /// On some targets, `spawn` may not report all spawn errors, such as `error.InvalidExe`. |
| 326 | 327 | /// This function will block until any spawn errors can be reported, and return them. |
| 327 | pub fn waitForSpawn(self: *ChildProcess) SpawnError!void { | |
| 328 | pub fn waitForSpawn(self: *Child) SpawnError!void { | |
| 328 | 329 | if (native_os == .windows) return; // `spawn` reports everything |
| 329 | 330 | if (self.term) |term| { |
| 330 | 331 | _ = term catch |spawn_err| return spawn_err; |
| ... | ... | @@ -354,15 +355,15 @@ pub fn waitForSpawn(self: *ChildProcess) SpawnError!void { |
| 354 | 355 | } |
| 355 | 356 | |
| 356 | 357 | /// Blocks until child process terminates and then cleans up all resources. |
| 357 | pub fn wait(self: *ChildProcess) WaitError!Term { | |
| 358 | pub fn wait(self: *Child, io: Io) WaitError!Term { | |
| 358 | 359 | try self.waitForSpawn(); // report spawn errors |
| 359 | 360 | if (self.term) |term| { |
| 360 | self.cleanupStreams(); | |
| 361 | self.cleanupStreams(io); | |
| 361 | 362 | return term; |
| 362 | 363 | } |
| 363 | 364 | switch (native_os) { |
| 364 | .windows => try self.waitUnwrappedWindows(), | |
| 365 | else => self.waitUnwrappedPosix(), | |
| 365 | .windows => try self.waitUnwrappedWindows(io), | |
| 366 | else => self.waitUnwrappedPosix(io), | |
| 366 | 367 | } |
| 367 | 368 | self.id = undefined; |
| 368 | 369 | return self.term.?; |
| ... | ... | @@ -380,7 +381,7 @@ pub const RunResult = struct { |
| 380 | 381 | /// |
| 381 | 382 | /// The process must be started with stdout_behavior and stderr_behavior == .Pipe |
| 382 | 383 | pub fn collectOutput( |
| 383 | child: ChildProcess, | |
| 384 | child: Child, | |
| 384 | 385 | /// Used for `stdout` and `stderr`. |
| 385 | 386 | allocator: Allocator, |
| 386 | 387 | stdout: *ArrayList(u8), |
| ... | ... | @@ -434,11 +435,10 @@ pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix |
| 434 | 435 | |
| 435 | 436 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. |
| 436 | 437 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. |
| 437 | pub fn run(args: struct { | |
| 438 | allocator: mem.Allocator, | |
| 438 | pub fn run(allocator: Allocator, io: Io, args: struct { | |
| 439 | 439 | argv: []const []const u8, |
| 440 | 440 | cwd: ?[]const u8 = null, |
| 441 | cwd_dir: ?fs.Dir = null, | |
| 441 | cwd_dir: ?Io.Dir = null, | |
| 442 | 442 | /// Required if unable to access the current env map (e.g. building a |
| 443 | 443 | /// library on some platforms). |
| 444 | 444 | env_map: ?*const EnvMap = null, |
| ... | ... | @@ -446,7 +446,7 @@ pub fn run(args: struct { |
| 446 | 446 | expand_arg0: Arg0Expand = .no_expand, |
| 447 | 447 | progress_node: std.Progress.Node = std.Progress.Node.none, |
| 448 | 448 | }) RunError!RunResult { |
| 449 | var child = ChildProcess.init(args.argv, args.allocator); | |
| 449 | var child = Child.init(args.argv, allocator); | |
| 450 | 450 | child.stdin_behavior = .Ignore; |
| 451 | 451 | child.stdout_behavior = .Pipe; |
| 452 | 452 | child.stderr_behavior = .Pipe; |
| ... | ... | @@ -457,24 +457,24 @@ pub fn run(args: struct { |
| 457 | 457 | child.progress_node = args.progress_node; |
| 458 | 458 | |
| 459 | 459 | var stdout: ArrayList(u8) = .empty; |
| 460 | defer stdout.deinit(args.allocator); | |
| 460 | defer stdout.deinit(allocator); | |
| 461 | 461 | var stderr: ArrayList(u8) = .empty; |
| 462 | defer stderr.deinit(args.allocator); | |
| 462 | defer stderr.deinit(allocator); | |
| 463 | 463 | |
| 464 | try child.spawn(); | |
| 464 | try child.spawn(io); | |
| 465 | 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); | |
| 469 | 469 | |
| 470 | 470 | return .{ |
| 471 | .stdout = try stdout.toOwnedSlice(args.allocator), | |
| 472 | .stderr = try stderr.toOwnedSlice(args.allocator), | |
| 473 | .term = try child.wait(), | |
| 471 | .stdout = try stdout.toOwnedSlice(allocator), | |
| 472 | .stderr = try stderr.toOwnedSlice(allocator), | |
| 473 | .term = try child.wait(io), | |
| 474 | 474 | }; |
| 475 | 475 | } |
| 476 | 476 | |
| 477 | fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void { | |
| 477 | fn waitUnwrappedWindows(self: *Child, io: Io) WaitError!void { | |
| 478 | 478 | const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false); |
| 479 | 479 | |
| 480 | 480 | self.term = @as(SpawnError!Term, x: { |
| ... | ... | @@ -492,11 +492,11 @@ fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void { |
| 492 | 492 | |
| 493 | 493 | posix.close(self.id); |
| 494 | 494 | posix.close(self.thread_handle); |
| 495 | self.cleanupStreams(); | |
| 495 | self.cleanupStreams(io); | |
| 496 | 496 | return result; |
| 497 | 497 | } |
| 498 | 498 | |
| 499 | fn waitUnwrappedPosix(self: *ChildProcess) void { | |
| 499 | fn waitUnwrappedPosix(self: *Child, io: Io) void { | |
| 500 | 500 | const res: posix.WaitPidResult = res: { |
| 501 | 501 | if (self.request_resource_usage_statistics) { |
| 502 | 502 | switch (native_os) { |
| ... | ... | @@ -527,25 +527,25 @@ fn waitUnwrappedPosix(self: *ChildProcess) void { |
| 527 | 527 | break :res posix.waitpid(self.id, 0); |
| 528 | 528 | }; |
| 529 | 529 | const status = res.status; |
| 530 | self.cleanupStreams(); | |
| 530 | self.cleanupStreams(io); | |
| 531 | 531 | self.handleWaitResult(status); |
| 532 | 532 | } |
| 533 | 533 | |
| 534 | fn handleWaitResult(self: *ChildProcess, status: u32) void { | |
| 534 | fn handleWaitResult(self: *Child, status: u32) void { | |
| 535 | 535 | self.term = statusToTerm(status); |
| 536 | 536 | } |
| 537 | 537 | |
| 538 | fn cleanupStreams(self: *ChildProcess) void { | |
| 538 | fn cleanupStreams(self: *Child, io: Io) void { | |
| 539 | 539 | if (self.stdin) |*stdin| { |
| 540 | stdin.close(); | |
| 540 | stdin.close(io); | |
| 541 | 541 | self.stdin = null; |
| 542 | 542 | } |
| 543 | 543 | if (self.stdout) |*stdout| { |
| 544 | stdout.close(); | |
| 544 | stdout.close(io); | |
| 545 | 545 | self.stdout = null; |
| 546 | 546 | } |
| 547 | 547 | if (self.stderr) |*stderr| { |
| 548 | stderr.close(); | |
| 548 | stderr.close(io); | |
| 549 | 549 | self.stderr = null; |
| 550 | 550 | } |
| 551 | 551 | } |
| ... | ... | @@ -561,7 +561,7 @@ fn statusToTerm(status: u32) Term { |
| 561 | 561 | Term{ .Unknown = status }; |
| 562 | 562 | } |
| 563 | 563 | |
| 564 | fn spawnPosix(self: *ChildProcess) SpawnError!void { | |
| 564 | fn spawnPosix(self: *Child, io: Io) SpawnError!void { | |
| 565 | 565 | // The child process does need to access (one end of) these pipes. However, |
| 566 | 566 | // we must initially set CLOEXEC to avoid a race condition. If another thread |
| 567 | 567 | // 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 | 596 | error.NoSpaceLeft => unreachable, |
| 597 | 597 | error.FileTooBig => unreachable, |
| 598 | 598 | error.DeviceBusy => unreachable, |
| 599 | error.FileLocksNotSupported => unreachable, | |
| 599 | error.FileLocksUnsupported => unreachable, | |
| 600 | 600 | error.BadPathName => unreachable, // Windows-only |
| 601 | 601 | error.WouldBlock => unreachable, |
| 602 | 602 | error.NetworkNotFound => unreachable, // Windows-only |
| ... | ... | @@ -659,7 +659,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { |
| 659 | 659 | })).ptr; |
| 660 | 660 | } else { |
| 661 | 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 | }; |
| 665 | 665 | |
| ... | ... | @@ -671,41 +671,41 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { |
| 671 | 671 | const pid_result = try posix.fork(); |
| 672 | 672 | if (pid_result == 0) { |
| 673 | 673 | // we are the child |
| 674 | setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); | |
| 675 | setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); | |
| 676 | setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); | |
| 674 | setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err); | |
| 675 | setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err); | |
| 676 | setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err); | |
| 677 | 677 | |
| 678 | 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 | 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 | } |
| 683 | 683 | |
| 684 | 684 | // Must happen after fchdir above, the cwd file descriptor might be |
| 685 | 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); | |
| 687 | 687 | |
| 688 | 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 | } |
| 691 | 691 | |
| 692 | 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 | } |
| 695 | 695 | |
| 696 | 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 | } |
| 699 | 699 | |
| 700 | 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 | } |
| 703 | 703 | |
| 704 | 704 | const err = switch (self.expand_arg0) { |
| 705 | 705 | .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), |
| 706 | 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 | } |
| 710 | 710 | |
| 711 | 711 | // we are the parent |
| ... | ... | @@ -750,7 +750,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { |
| 750 | 750 | self.progress_node.setIpcFd(prog_pipe[0]); |
| 751 | 751 | } |
| 752 | 752 | |
| 753 | fn spawnWindows(self: *ChildProcess) SpawnError!void { | |
| 753 | fn spawnWindows(self: *Child, io: Io) SpawnError!void { | |
| 754 | 754 | var saAttr = windows.SECURITY_ATTRIBUTES{ |
| 755 | 755 | .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), |
| 756 | 756 | .bInheritHandle = windows.TRUE, |
| ... | ... | @@ -880,7 +880,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void { |
| 880 | 880 | const app_name_wtf8 = self.argv[0]; |
| 881 | 881 | const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8); |
| 882 | 882 | |
| 883 | // the cwd set in ChildProcess is in effect when choosing the executable path | |
| 883 | // the cwd set in Child is in effect when choosing the executable path | |
| 884 | 884 | // to match posix semantics |
| 885 | 885 | var cwd_path_w_needs_free = false; |
| 886 | 886 | const cwd_path_w = x: { |
| ... | ... | @@ -953,7 +953,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void { |
| 953 | 953 | try dir_buf.appendSlice(self.allocator, app_dir); |
| 954 | 954 | } |
| 955 | 955 | |
| 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 | 957 | const original_err = switch (no_path_err) { |
| 958 | 958 | // argv[0] contains unsupported characters that will never resolve to a valid exe. |
| 959 | 959 | error.InvalidArg0 => return error.FileNotFound, |
| ... | ... | @@ -965,7 +965,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void { |
| 965 | 965 | // If the app name had path separators, that disallows PATH searching, |
| 966 | 966 | // and there's no need to search the PATH if the app name is absolute. |
| 967 | 967 | // We still search the path if the cwd is absolute because of the |
| 968 | // "cwd set in ChildProcess is in effect when choosing the executable path | |
| 968 | // "cwd set in Child is in effect when choosing the executable path | |
| 969 | 969 | // to match posix semantics" behavior--we don't want to skip searching |
| 970 | 970 | // the PATH just because we were trying to set the cwd of the child process. |
| 971 | 971 | if (app_dirname_w != null or app_name_is_absolute) { |
| ... | ... | @@ -977,7 +977,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void { |
| 977 | 977 | dir_buf.clearRetainingCapacity(); |
| 978 | 978 | try dir_buf.appendSlice(self.allocator, search_path); |
| 979 | 979 | |
| 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 | 981 | break :run; |
| 982 | 982 | } else |err| switch (err) { |
| 983 | 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 | 1039 | |
| 1040 | 1040 | // Child of fork calls this to report an error to the fork parent. |
| 1041 | 1041 | // Then the child exits. |
| 1042 | fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { | |
| 1043 | writeIntFd(fd, @as(ErrInt, @intFromError(err))) catch {}; | |
| 1042 | fn forkChildErrReport(io: Io, fd: i32, err: Child.SpawnError) noreturn { | |
| 1043 | writeIntFd(io, fd, @as(ErrInt, @intFromError(err))) catch {}; | |
| 1044 | 1044 | // If we're linking libc, some naughty applications may have registered atexit handlers |
| 1045 | 1045 | // which we really do not want to run in the fork child. I caught LLVM doing this and |
| 1046 | 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 | 1049 | // The _exit(2) function does nothing but make the exit syscall, unlike exit(3) |
| 1050 | 1050 | std.c._exit(1); |
| 1051 | 1051 | } |
| 1052 | posix.exit(1); | |
| 1052 | posix.system.exit(1); | |
| 1053 | 1053 | } |
| 1054 | 1054 | |
| 1055 | fn writeIntFd(fd: i32, value: ErrInt) !void { | |
| 1055 | fn writeIntFd(io: Io, fd: i32, value: ErrInt) !void { | |
| 1056 | 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 | 1058 | fw.interface.writeInt(u64, value, .little) catch unreachable; |
| 1059 | 1059 | fw.interface.flush() catch return error.SystemResources; |
| 1060 | 1060 | } |
| ... | ... | @@ -1078,7 +1078,8 @@ const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8); |
| 1078 | 1078 | /// Note: `app_buf` should not contain any leading path separators. |
| 1079 | 1079 | /// Note: If the dir is the cwd, dir_buf should be empty (len = 0). |
| 1080 | 1080 | fn windowsCreateProcessPathExt( |
| 1081 | allocator: mem.Allocator, | |
| 1081 | allocator: Allocator, | |
| 1082 | io: Io, | |
| 1082 | 1083 | dir_buf: *ArrayList(u16), |
| 1083 | 1084 | app_buf: *ArrayList(u16), |
| 1084 | 1085 | pathext: [:0]const u16, |
| ... | ... | @@ -1122,16 +1123,14 @@ fn windowsCreateProcessPathExt( |
| 1122 | 1123 | // Under those conditions, here we will have access to lower level directory |
| 1123 | 1124 | // opening function knowing which implementation we are in. Here, we imitate |
| 1124 | 1125 | // that scenario. |
| 1125 | var threaded: std.Io.Threaded = .init_single_threaded; | |
| 1126 | const io = threaded.ioBasic(); | |
| 1127 | ||
| 1128 | 1126 | var dir = dir: { |
| 1129 | 1127 | // needs to be null-terminated |
| 1130 | 1128 | try dir_buf.append(allocator, 0); |
| 1131 | 1129 | defer dir_buf.shrinkRetainingCapacity(dir_path_len); |
| 1132 | 1130 | const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; |
| 1133 | 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 | 1134 | .iterate = true, |
| 1136 | 1135 | }) catch return error.FileNotFound; |
| 1137 | 1136 | }; |
| ... | ... | @@ -1525,9 +1524,9 @@ const WindowsCommandLineCache = struct { |
| 1525 | 1524 | script_cmd_line: ?[:0]u16 = null, |
| 1526 | 1525 | cmd_exe_path: ?[:0]u16 = null, |
| 1527 | 1526 | argv: []const []const u8, |
| 1528 | allocator: mem.Allocator, | |
| 1527 | allocator: Allocator, | |
| 1529 | 1528 | |
| 1530 | fn init(allocator: mem.Allocator, argv: []const []const u8) WindowsCommandLineCache { | |
| 1529 | fn init(allocator: Allocator, argv: []const []const u8) WindowsCommandLineCache { | |
| 1531 | 1530 | return .{ |
| 1532 | 1531 | .allocator = allocator, |
| 1533 | 1532 | .argv = argv, |
| ... | ... | @@ -1571,7 +1570,7 @@ const WindowsCommandLineCache = struct { |
| 1571 | 1570 | |
| 1572 | 1571 | /// Returns the absolute path of `cmd.exe` within the Windows system directory. |
| 1573 | 1572 | /// The caller owns the returned slice. |
| 1574 | fn windowsCmdExePath(allocator: mem.Allocator) error{ OutOfMemory, Unexpected }![:0]u16 { | |
| 1573 | fn windowsCmdExePath(allocator: Allocator) error{ OutOfMemory, Unexpected }![:0]u16 { | |
| 1575 | 1574 | var buf = try ArrayList(u16).initCapacity(allocator, 128); |
| 1576 | 1575 | errdefer buf.deinit(allocator); |
| 1577 | 1576 | while (true) { |
| ... | ... | @@ -1608,7 +1607,7 @@ const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 }; |
| 1608 | 1607 | /// |
| 1609 | 1608 | /// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead. |
| 1610 | 1609 | fn argvToCommandLineWindows( |
| 1611 | allocator: mem.Allocator, | |
| 1610 | allocator: Allocator, | |
| 1612 | 1611 | argv: []const []const u8, |
| 1613 | 1612 | ) ArgvToCommandLineError![:0]u16 { |
| 1614 | 1613 | var buf = std.array_list.Managed(u8).init(allocator); |
| ... | ... | @@ -1784,7 +1783,7 @@ const ArgvToScriptCommandLineError = error{ |
| 1784 | 1783 | /// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise. |
| 1785 | 1784 | /// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem. |
| 1786 | 1785 | fn argvToScriptCommandLineWindows( |
| 1787 | allocator: mem.Allocator, | |
| 1786 | allocator: Allocator, | |
| 1788 | 1787 | /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD. |
| 1789 | 1788 | /// The script must have been verified to exist at this path before calling this function. |
| 1790 | 1789 | script_path: []const u16, |
lib/std/start.zig+13-3| ... | ... | @@ -110,7 +110,7 @@ fn main2() callconv(.c) c_int { |
| 110 | 110 | } |
| 111 | 111 | |
| 112 | 112 | fn _start2() callconv(.withStackAlign(.c, 1)) noreturn { |
| 113 | std.posix.exit(callMain()); | |
| 113 | std.process.exit(callMain()); | |
| 114 | 114 | } |
| 115 | 115 | |
| 116 | 116 | fn spirvMain2() callconv(.kernel) void { |
| ... | ... | @@ -118,7 +118,7 @@ fn spirvMain2() callconv(.kernel) void { |
| 118 | 118 | } |
| 119 | 119 | |
| 120 | 120 | fn wWinMainCRTStartup2() callconv(.c) noreturn { |
| 121 | std.posix.exit(callMain()); | |
| 121 | std.process.exit(callMain()); | |
| 122 | 122 | } |
| 123 | 123 | |
| 124 | 124 | //////////////////////////////////////////////////////////////////////////////// |
| ... | ... | @@ -627,7 +627,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn { |
| 627 | 627 | for (slice) |func| func(); |
| 628 | 628 | } |
| 629 | 629 | |
| 630 | std.posix.exit(callMainWithArgs(argc, argv, envp)); | |
| 630 | std.process.exit(callMainWithArgs(argc, argv, envp)); | |
| 631 | 631 | } |
| 632 | 632 | |
| 633 | 633 | fn expandStackSize(phdrs: []elf.Phdr) void { |
| ... | ... | @@ -669,6 +669,11 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 { |
| 669 | 669 | std.os.argv = argv[0..argc]; |
| 670 | 670 | std.os.environ = envp; |
| 671 | 671 | |
| 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 | 677 | std.debug.maybeEnableSegfaultHandler(); |
| 673 | 678 | |
| 674 | 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 | 696 | |
| 692 | 697 | fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { |
| 693 | 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 | 704 | return callMain(); |
| 695 | 705 | } |
| 696 | 706 |
lib/std/std.zig+23-3| ... | ... | @@ -108,14 +108,14 @@ pub const start = @import("start.zig"); |
| 108 | 108 | |
| 109 | 109 | const root = @import("root"); |
| 110 | 110 | |
| 111 | /// Stdlib-wide options that can be overridden by the root file. | |
| 111 | /// Compile-time known settings overridable by the root source file. | |
| 112 | 112 | pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options else .{}; |
| 113 | 113 | |
| 114 | 114 | pub const Options = struct { |
| 115 | 115 | enable_segfault_handler: bool = debug.default_enable_segfault_handler, |
| 116 | 116 | |
| 117 | /// Function used to implement `std.fs.cwd` for WASI. | |
| 118 | wasiCwd: fn () os.wasi.fd_t = fs.defaultWasiCwd, | |
| 117 | /// Function used to implement `std.Io.Dir.cwd` for WASI. | |
| 118 | wasiCwd: fn () os.wasi.fd_t = os.defaultWasiCwd, | |
| 119 | 119 | |
| 120 | 120 | /// The current log level. |
| 121 | 121 | log_level: log.Level = log.default_level, |
| ... | ... | @@ -129,6 +129,8 @@ pub const Options = struct { |
| 129 | 129 | args: anytype, |
| 130 | 130 | ) void = log.defaultLog, |
| 131 | 131 | |
| 132 | logTerminalMode: fn () Io.Terminal.Mode = log.defaultTerminalMode, | |
| 133 | ||
| 132 | 134 | /// Overrides `std.heap.page_size_min`. |
| 133 | 135 | page_size_min: ?usize = null, |
| 134 | 136 | /// Overrides `std.heap.page_size_max`. |
| ... | ... | @@ -173,6 +175,24 @@ pub const Options = struct { |
| 173 | 175 | /// If this is `false`, then captured stack traces will always be empty, and attempts to write |
| 174 | 176 | /// stack traces will just print an error to the relevant `Io.Writer` and return. |
| 175 | 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 | }; |
| 177 | 197 | |
| 178 | 198 | // 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 | 16 | //! pax reference: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13 |
| 17 | 17 | |
| 18 | 18 | const std = @import("std"); |
| 19 | const Io = std.Io; | |
| 19 | 20 | const assert = std.debug.assert; |
| 20 | 21 | const testing = std.testing; |
| 21 | 22 | |
| ... | ... | @@ -302,7 +303,7 @@ pub const FileKind = enum { |
| 302 | 303 | |
| 303 | 304 | /// Iterator over entries in the tar file represented by reader. |
| 304 | 305 | pub const Iterator = struct { |
| 305 | reader: *std.Io.Reader, | |
| 306 | reader: *Io.Reader, | |
| 306 | 307 | diagnostics: ?*Diagnostics = null, |
| 307 | 308 | |
| 308 | 309 | // buffers for heeader and file attributes |
| ... | ... | @@ -328,7 +329,7 @@ pub const Iterator = struct { |
| 328 | 329 | |
| 329 | 330 | /// Iterates over files in tar archive. |
| 330 | 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 | 333 | return .{ |
| 333 | 334 | .reader = reader, |
| 334 | 335 | .diagnostics = options.diagnostics, |
| ... | ... | @@ -473,7 +474,7 @@ pub const Iterator = struct { |
| 473 | 474 | return null; |
| 474 | 475 | } |
| 475 | 476 | |
| 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 | 478 | try it.reader.streamExact64(w, file.size); |
| 478 | 479 | it.unread_file_bytes = 0; |
| 479 | 480 | } |
| ... | ... | @@ -499,14 +500,14 @@ const pax_max_size_attr_len = 64; |
| 499 | 500 | |
| 500 | 501 | pub const PaxIterator = struct { |
| 501 | 502 | size: usize, // cumulative size of all pax attributes |
| 502 | reader: *std.Io.Reader, | |
| 503 | reader: *Io.Reader, | |
| 503 | 504 | |
| 504 | 505 | const Self = @This(); |
| 505 | 506 | |
| 506 | 507 | const Attribute = struct { |
| 507 | 508 | kind: PaxAttributeKind, |
| 508 | 509 | len: usize, // length of the attribute value |
| 509 | reader: *std.Io.Reader, // reader positioned at value start | |
| 510 | reader: *Io.Reader, // reader positioned at value start | |
| 510 | 511 | |
| 511 | 512 | // Copies pax attribute value into destination buffer. |
| 512 | 513 | // Must be called with destination buffer of size at least Attribute.len. |
| ... | ... | @@ -573,13 +574,13 @@ pub const PaxIterator = struct { |
| 573 | 574 | } |
| 574 | 575 | |
| 575 | 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 | 578 | if (try reader.takeByte() != '\n') return error.PaxInvalidAttributeEnd; |
| 578 | 579 | } |
| 579 | 580 | }; |
| 580 | 581 | |
| 581 | 582 | /// Saves tar file content to the file systems. |
| 582 | pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOptions) !void { | |
| 583 | pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOptions) !void { | |
| 583 | 584 | var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; |
| 584 | 585 | var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; |
| 585 | 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 | 606 | switch (file.kind) { |
| 606 | 607 | .directory => { |
| 607 | 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 | 612 | .file => { |
| 612 | if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| { | |
| 613 | defer fs_file.close(); | |
| 614 | var file_writer = fs_file.writer(&file_contents_buffer); | |
| 613 | if (createDirAndFile(io, dir, file_name, filePermissions(file.mode, options))) |fs_file| { | |
| 614 | defer fs_file.close(io); | |
| 615 | var file_writer = fs_file.writer(io, &file_contents_buffer); | |
| 615 | 616 | try it.streamRemaining(file, &file_writer.interface); |
| 616 | 617 | try file_writer.interface.flush(); |
| 617 | 618 | } else |err| { |
| ... | ... | @@ -624,7 +625,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp |
| 624 | 625 | }, |
| 625 | 626 | .sym_link => { |
| 626 | 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 | 629 | const d = options.diagnostics orelse return error.UnableToCreateSymLink; |
| 629 | 630 | try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{ |
| 630 | 631 | .code = err, |
| ... | ... | @@ -637,12 +638,12 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp |
| 637 | 638 | } |
| 638 | 639 | } |
| 639 | 640 | |
| 640 | fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mode) !std.fs.File { | |
| 641 | const fs_file = dir.createFile(file_name, .{ .exclusive = true, .mode = mode }) catch |err| { | |
| 641 | fn createDirAndFile(io: Io, dir: Io.Dir, file_name: []const u8, permissions: Io.File.Permissions) !Io.File { | |
| 642 | const fs_file = dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions }) catch |err| { | |
| 642 | 643 | if (err == error.FileNotFound) { |
| 643 | 644 | if (std.fs.path.dirname(file_name)) |dir_name| { |
| 644 | try dir.makePath(dir_name); | |
| 645 | return try dir.createFile(file_name, .{ .exclusive = true, .mode = mode }); | |
| 645 | try dir.createDirPath(io, dir_name); | |
| 646 | return try dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions }); | |
| 646 | 647 | } |
| 647 | 648 | } |
| 648 | 649 | return err; |
| ... | ... | @@ -651,12 +652,12 @@ fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mo |
| 651 | 652 | } |
| 652 | 653 | |
| 653 | 654 | // Creates a symbolic link at path `file_name` which points to `link_name`. |
| 654 | fn createDirAndSymlink(dir: std.fs.Dir, link_name: []const u8, file_name: []const u8) !void { | |
| 655 | dir.symLink(link_name, file_name, .{}) catch |err| { | |
| 655 | fn createDirAndSymlink(io: Io, dir: Io.Dir, link_name: []const u8, file_name: []const u8) !void { | |
| 656 | dir.symLink(io, link_name, file_name, .{}) catch |err| { | |
| 656 | 657 | if (err == error.FileNotFound) { |
| 657 | 658 | if (std.fs.path.dirname(file_name)) |dir_name| { |
| 658 | try dir.makePath(dir_name); | |
| 659 | return try dir.symLink(link_name, file_name, .{}); | |
| 659 | try dir.createDirPath(io, dir_name); | |
| 660 | return try dir.symLink(io, link_name, file_name, .{}); | |
| 660 | 661 | } |
| 661 | 662 | } |
| 662 | 663 | return err; |
| ... | ... | @@ -783,7 +784,7 @@ test PaxIterator { |
| 783 | 784 | var buffer: [1024]u8 = undefined; |
| 784 | 785 | |
| 785 | 786 | outer: for (cases) |case| { |
| 786 | var reader: std.Io.Reader = .fixed(case.data); | |
| 787 | var reader: Io.Reader = .fixed(case.data); | |
| 787 | 788 | var it: PaxIterator = .{ |
| 788 | 789 | .size = case.data.len, |
| 789 | 790 | .reader = &reader, |
| ... | ... | @@ -874,25 +875,27 @@ test "header parse mode" { |
| 874 | 875 | } |
| 875 | 876 | |
| 876 | 877 | test "create file and symlink" { |
| 878 | const io = testing.io; | |
| 879 | ||
| 877 | 880 | var root = testing.tmpDir(.{}); |
| 878 | 881 | defer root.cleanup(); |
| 879 | 882 | |
| 880 | var file = try createDirAndFile(root.dir, "file1", default_mode); | |
| 881 | file.close(); | |
| 882 | file = try createDirAndFile(root.dir, "a/b/c/file2", default_mode); | |
| 883 | file.close(); | |
| 883 | var file = try createDirAndFile(io, root.dir, "file1", .default_file); | |
| 884 | file.close(io); | |
| 885 | file = try createDirAndFile(io, root.dir, "a/b/c/file2", .default_file); | |
| 886 | file.close(io); | |
| 884 | 887 | |
| 885 | createDirAndSymlink(root.dir, "a/b/c/file2", "symlink1") catch |err| { | |
| 888 | createDirAndSymlink(io, root.dir, "a/b/c/file2", "symlink1") catch |err| { | |
| 886 | 889 | // On Windows when developer mode is not enabled |
| 887 | 890 | if (err == error.AccessDenied) return error.SkipZigTest; |
| 888 | 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"); | |
| 891 | 894 | |
| 892 | 895 | // Danglink symlnik, file created later |
| 893 | try createDirAndSymlink(root.dir, "../../../g/h/i/file4", "j/k/l/symlink3"); | |
| 894 | file = try createDirAndFile(root.dir, "g/h/i/file4", default_mode); | |
| 895 | file.close(); | |
| 896 | try createDirAndSymlink(io, root.dir, "../../../g/h/i/file4", "j/k/l/symlink3"); | |
| 897 | file = try createDirAndFile(io, root.dir, "g/h/i/file4", .default_file); | |
| 898 | file.close(io); | |
| 896 | 899 | } |
| 897 | 900 | |
| 898 | 901 | test Iterator { |
| ... | ... | @@ -916,7 +919,7 @@ test Iterator { |
| 916 | 919 | // example/empty/ |
| 917 | 920 | |
| 918 | 921 | const data = @embedFile("tar/testdata/example.tar"); |
| 919 | var reader: std.Io.Reader = .fixed(data); | |
| 922 | var reader: Io.Reader = .fixed(data); | |
| 920 | 923 | |
| 921 | 924 | // User provided buffers to the iterator |
| 922 | 925 | var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; |
| ... | ... | @@ -942,7 +945,7 @@ test Iterator { |
| 942 | 945 | .file => { |
| 943 | 946 | try testing.expectEqualStrings("example/a/file", file.name); |
| 944 | 947 | var buf: [16]u8 = undefined; |
| 945 | var w: std.Io.Writer = .fixed(&buf); | |
| 948 | var w: Io.Writer = .fixed(&buf); | |
| 946 | 949 | try it.streamRemaining(file, &w); |
| 947 | 950 | try testing.expectEqualStrings("content\n", w.buffered()); |
| 948 | 951 | }, |
| ... | ... | @@ -955,6 +958,7 @@ test Iterator { |
| 955 | 958 | } |
| 956 | 959 | |
| 957 | 960 | test pipeToFileSystem { |
| 961 | const io = testing.io; | |
| 958 | 962 | // Example tar file is created from this tree structure: |
| 959 | 963 | // $ tree example |
| 960 | 964 | // example |
| ... | ... | @@ -975,14 +979,14 @@ test pipeToFileSystem { |
| 975 | 979 | // example/empty/ |
| 976 | 980 | |
| 977 | 981 | const data = @embedFile("tar/testdata/example.tar"); |
| 978 | var reader: std.Io.Reader = .fixed(data); | |
| 982 | var reader: Io.Reader = .fixed(data); | |
| 979 | 983 | |
| 980 | 984 | var tmp = testing.tmpDir(.{ .follow_symlinks = false }); |
| 981 | 985 | defer tmp.cleanup(); |
| 982 | 986 | const dir = tmp.dir; |
| 983 | 987 | |
| 984 | 988 | // Save tar from reader to the file system `dir` |
| 985 | pipeToFileSystem(dir, &reader, .{ | |
| 989 | pipeToFileSystem(io, dir, &reader, .{ | |
| 986 | 990 | .mode_mode = .ignore, |
| 987 | 991 | .strip_components = 1, |
| 988 | 992 | .exclude_empty_directories = true, |
| ... | ... | @@ -992,21 +996,22 @@ test pipeToFileSystem { |
| 992 | 996 | return err; |
| 993 | 997 | }; |
| 994 | 998 | |
| 995 | try testing.expectError(error.FileNotFound, dir.statFile("empty")); | |
| 996 | try testing.expect((try dir.statFile("a/file")).kind == .file); | |
| 997 | try testing.expect((try dir.statFile("b/symlink")).kind == .file); // statFile follows symlink | |
| 999 | try testing.expectError(error.FileNotFound, dir.statFile(io, "empty", .{})); | |
| 1000 | try testing.expect((try dir.statFile(io, "a/file", .{})).kind == .file); | |
| 1001 | try testing.expect((try dir.statFile(io, "b/symlink", .{})).kind == .file); // statFile follows symlink | |
| 998 | 1002 | |
| 999 | 1003 | var buf: [32]u8 = undefined; |
| 1000 | 1004 | try testing.expectEqualSlices( |
| 1001 | 1005 | u8, |
| 1002 | 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 | } |
| 1006 | 1010 | |
| 1007 | 1011 | test "pipeToFileSystem root_dir" { |
| 1012 | const io = testing.io; | |
| 1008 | 1013 | const data = @embedFile("tar/testdata/example.tar"); |
| 1009 | var reader: std.Io.Reader = .fixed(data); | |
| 1014 | var reader: Io.Reader = .fixed(data); | |
| 1010 | 1015 | |
| 1011 | 1016 | // with strip_components = 1 |
| 1012 | 1017 | { |
| ... | ... | @@ -1015,7 +1020,7 @@ test "pipeToFileSystem root_dir" { |
| 1015 | 1020 | var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; |
| 1016 | 1021 | defer diagnostics.deinit(); |
| 1017 | 1022 | |
| 1018 | pipeToFileSystem(tmp.dir, &reader, .{ | |
| 1023 | pipeToFileSystem(io, tmp.dir, &reader, .{ | |
| 1019 | 1024 | .strip_components = 1, |
| 1020 | 1025 | .diagnostics = &diagnostics, |
| 1021 | 1026 | }) catch |err| { |
| ... | ... | @@ -1037,7 +1042,7 @@ test "pipeToFileSystem root_dir" { |
| 1037 | 1042 | var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; |
| 1038 | 1043 | defer diagnostics.deinit(); |
| 1039 | 1044 | |
| 1040 | pipeToFileSystem(tmp.dir, &reader, .{ | |
| 1045 | pipeToFileSystem(io, tmp.dir, &reader, .{ | |
| 1041 | 1046 | .strip_components = 0, |
| 1042 | 1047 | .diagnostics = &diagnostics, |
| 1043 | 1048 | }) catch |err| { |
| ... | ... | @@ -1053,43 +1058,46 @@ test "pipeToFileSystem root_dir" { |
| 1053 | 1058 | } |
| 1054 | 1059 | |
| 1055 | 1060 | test "findRoot with single file archive" { |
| 1061 | const io = testing.io; | |
| 1056 | 1062 | const data = @embedFile("tar/testdata/22752.tar"); |
| 1057 | var reader: std.Io.Reader = .fixed(data); | |
| 1063 | var reader: Io.Reader = .fixed(data); | |
| 1058 | 1064 | |
| 1059 | 1065 | var tmp = testing.tmpDir(.{}); |
| 1060 | 1066 | defer tmp.cleanup(); |
| 1061 | 1067 | |
| 1062 | 1068 | var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; |
| 1063 | 1069 | defer diagnostics.deinit(); |
| 1064 | try pipeToFileSystem(tmp.dir, &reader, .{ .diagnostics = &diagnostics }); | |
| 1070 | try pipeToFileSystem(io, tmp.dir, &reader, .{ .diagnostics = &diagnostics }); | |
| 1065 | 1071 | |
| 1066 | 1072 | try testing.expectEqualStrings("", diagnostics.root_dir); |
| 1067 | 1073 | } |
| 1068 | 1074 | |
| 1069 | 1075 | test "findRoot without explicit root dir" { |
| 1076 | const io = testing.io; | |
| 1070 | 1077 | const data = @embedFile("tar/testdata/19820.tar"); |
| 1071 | var reader: std.Io.Reader = .fixed(data); | |
| 1078 | var reader: Io.Reader = .fixed(data); | |
| 1072 | 1079 | |
| 1073 | 1080 | var tmp = testing.tmpDir(.{}); |
| 1074 | 1081 | defer tmp.cleanup(); |
| 1075 | 1082 | |
| 1076 | 1083 | var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; |
| 1077 | 1084 | defer diagnostics.deinit(); |
| 1078 | try pipeToFileSystem(tmp.dir, &reader, .{ .diagnostics = &diagnostics }); | |
| 1085 | try pipeToFileSystem(io, tmp.dir, &reader, .{ .diagnostics = &diagnostics }); | |
| 1079 | 1086 | |
| 1080 | 1087 | try testing.expectEqualStrings("root", diagnostics.root_dir); |
| 1081 | 1088 | } |
| 1082 | 1089 | |
| 1083 | 1090 | test "pipeToFileSystem strip_components" { |
| 1091 | const io = testing.io; | |
| 1084 | 1092 | const data = @embedFile("tar/testdata/example.tar"); |
| 1085 | var reader: std.Io.Reader = .fixed(data); | |
| 1093 | var reader: Io.Reader = .fixed(data); | |
| 1086 | 1094 | |
| 1087 | 1095 | var tmp = testing.tmpDir(.{ .follow_symlinks = false }); |
| 1088 | 1096 | defer tmp.cleanup(); |
| 1089 | 1097 | var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; |
| 1090 | 1098 | defer diagnostics.deinit(); |
| 1091 | 1099 | |
| 1092 | pipeToFileSystem(tmp.dir, &reader, .{ | |
| 1100 | pipeToFileSystem(io, tmp.dir, &reader, .{ | |
| 1093 | 1101 | .strip_components = 3, |
| 1094 | 1102 | .diagnostics = &diagnostics, |
| 1095 | 1103 | }) catch |err| { |
| ... | ... | @@ -1110,45 +1118,36 @@ fn normalizePath(bytes: []u8) []u8 { |
| 1110 | 1118 | return bytes; |
| 1111 | 1119 | } |
| 1112 | 1120 | |
| 1113 | const default_mode = std.fs.File.default_mode; | |
| 1114 | ||
| 1115 | 1121 | // File system mode based on tar header mode and mode_mode options. |
| 1116 | fn fileMode(mode: u32, options: PipeOptions) std.fs.File.Mode { | |
| 1117 | if (!std.fs.has_executable_bit or options.mode_mode == .ignore) | |
| 1118 | return default_mode; | |
| 1119 | ||
| 1120 | const S = std.posix.S; | |
| 1121 | ||
| 1122 | // The mode from the tar file is inspected for the owner executable bit. | |
| 1123 | if (mode & S.IXUSR == 0) | |
| 1124 | return default_mode; | |
| 1125 | ||
| 1126 | // This bit is copied to the group and other executable bits. | |
| 1127 | // Other bits of the mode are left as the default when creating files. | |
| 1128 | return default_mode | S.IXUSR | S.IXGRP | S.IXOTH; | |
| 1122 | fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { | |
| 1123 | return if (!Io.File.Permissions.has_executable_bit or options.mode_mode == .ignore or (mode & 0o100) == 0) | |
| 1124 | .default_file | |
| 1125 | else | |
| 1126 | .executable_file; | |
| 1129 | 1127 | } |
| 1130 | 1128 | |
| 1131 | test fileMode { | |
| 1132 | if (!std.fs.has_executable_bit) return error.SkipZigTest; | |
| 1133 | try testing.expectEqual(default_mode, fileMode(0o744, PipeOptions{ .mode_mode = .ignore })); | |
| 1134 | try testing.expectEqual(0o777, fileMode(0o744, PipeOptions{})); | |
| 1135 | try testing.expectEqual(0o666, fileMode(0o644, PipeOptions{})); | |
| 1136 | try testing.expectEqual(0o666, fileMode(0o655, PipeOptions{})); | |
| 1129 | test filePermissions { | |
| 1130 | if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; | |
| 1131 | try testing.expectEqual(.default_file, filePermissions(0o744, .{ .mode_mode = .ignore })); | |
| 1132 | try testing.expectEqual(.executable_file, filePermissions(0o744, .{})); | |
| 1133 | try testing.expectEqual(.default_file, filePermissions(0o644, .{})); | |
| 1134 | try testing.expectEqual(.default_file, filePermissions(0o655, .{})); | |
| 1137 | 1135 | } |
| 1138 | 1136 | |
| 1139 | 1137 | test "executable bit" { |
| 1140 | if (!std.fs.has_executable_bit) return error.SkipZigTest; | |
| 1138 | if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; | |
| 1141 | 1139 | |
| 1140 | const io = testing.io; | |
| 1142 | 1141 | const S = std.posix.S; |
| 1143 | 1142 | const data = @embedFile("tar/testdata/example.tar"); |
| 1144 | 1143 | |
| 1145 | 1144 | for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| { |
| 1146 | var reader: std.Io.Reader = .fixed(data); | |
| 1145 | var reader: Io.Reader = .fixed(data); | |
| 1147 | 1146 | |
| 1148 | 1147 | var tmp = testing.tmpDir(.{ .follow_symlinks = false }); |
| 1149 | 1148 | //defer tmp.cleanup(); |
| 1150 | 1149 | |
| 1151 | pipeToFileSystem(tmp.dir, &reader, .{ | |
| 1150 | pipeToFileSystem(io, tmp.dir, &reader, .{ | |
| 1152 | 1151 | .strip_components = 1, |
| 1153 | 1152 | .exclude_empty_directories = true, |
| 1154 | 1153 | .mode_mode = opt, |
| ... | ... | @@ -1158,19 +1157,21 @@ test "executable bit" { |
| 1158 | 1157 | return err; |
| 1159 | 1158 | }; |
| 1160 | 1159 | |
| 1161 | const fs = try tmp.dir.statFile("a/file"); | |
| 1160 | const fs = try tmp.dir.statFile(io, "a/file", .{}); | |
| 1162 | 1161 | try testing.expect(fs.kind == .file); |
| 1163 | 1162 | |
| 1163 | const mode = fs.permissions.toMode(); | |
| 1164 | ||
| 1164 | 1165 | if (opt == .executable_bit_only) { |
| 1165 | 1166 | // Executable bit is set for user, group and others |
| 1166 | try testing.expect(fs.mode & S.IXUSR > 0); | |
| 1167 | try testing.expect(fs.mode & S.IXGRP > 0); | |
| 1168 | try testing.expect(fs.mode & S.IXOTH > 0); | |
| 1167 | try testing.expect(mode & S.IXUSR > 0); | |
| 1168 | try testing.expect(mode & S.IXGRP > 0); | |
| 1169 | try testing.expect(mode & S.IXOTH > 0); | |
| 1169 | 1170 | } |
| 1170 | 1171 | if (opt == .ignore) { |
| 1171 | try testing.expect(fs.mode & S.IXUSR == 0); | |
| 1172 | try testing.expect(fs.mode & S.IXGRP == 0); | |
| 1173 | try testing.expect(fs.mode & S.IXOTH == 0); | |
| 1172 | try testing.expect(mode & S.IXUSR == 0); | |
| 1173 | try testing.expect(mode & S.IXGRP == 0); | |
| 1174 | try testing.expect(mode & S.IXOTH == 0); | |
| 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 | 424 | } |
| 425 | 425 | |
| 426 | 426 | test "should not overwrite existing file" { |
| 427 | const io = testing.io; | |
| 427 | 428 | // Starting from this folder structure: |
| 428 | 429 | // $ tree root |
| 429 | 430 | // root |
| ... | ... | @@ -469,17 +470,18 @@ test "should not overwrite existing file" { |
| 469 | 470 | defer root.cleanup(); |
| 470 | 471 | try testing.expectError( |
| 471 | 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 | ); |
| 474 | 475 | |
| 475 | 476 | // Unpack with strip_components = 0 should pass |
| 476 | 477 | r = .fixed(data); |
| 477 | 478 | var root2 = std.testing.tmpDir(.{}); |
| 478 | 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 | } |
| 481 | 482 | |
| 482 | 483 | test "case sensitivity" { |
| 484 | const io = testing.io; | |
| 483 | 485 | // Mimicking issue #18089, this tar contains, same file name in two case |
| 484 | 486 | // sensitive name version. Should fail on case insensitive file systems. |
| 485 | 487 | // |
| ... | ... | @@ -495,13 +497,13 @@ test "case sensitivity" { |
| 495 | 497 | var root = std.testing.tmpDir(.{}); |
| 496 | 498 | defer root.cleanup(); |
| 497 | 499 | |
| 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 | 501 | // on case insensitive fs we fail on overwrite existing file |
| 500 | 502 | try testing.expectEqual(error.PathAlreadyExists, err); |
| 501 | 503 | return; |
| 502 | 504 | }; |
| 503 | 505 | |
| 504 | 506 | // on case sensitive os both files are created |
| 505 | try testing.expect((try root.dir.statFile("alacritty/darkermatrix.yml")).kind == .file); | |
| 506 | try testing.expect((try root.dir.statFile("alacritty/Darkermatrix.yml")).kind == .file); | |
| 507 | try testing.expect((try root.dir.statFile(io, "alacritty/darkermatrix.yml", .{})).kind == .file); | |
| 508 | try testing.expect((try root.dir.statFile(io, "alacritty/Darkermatrix.yml", .{})).kind == .file); | |
| 507 | 509 | } |
lib/std/testing.zig+64-56| ... | ... | @@ -1,5 +1,7 @@ |
| 1 | const std = @import("std.zig"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 2 | ||
| 3 | const std = @import("std.zig"); | |
| 4 | const Io = std.Io; | |
| 3 | 5 | const assert = std.debug.assert; |
| 4 | 6 | const math = std.math; |
| 5 | 7 | |
| ... | ... | @@ -28,8 +30,8 @@ pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{ |
| 28 | 30 | break :b .init; |
| 29 | 31 | }; |
| 30 | 32 | |
| 31 | pub var io_instance: std.Io.Threaded = undefined; | |
| 32 | pub const io = io_instance.io(); | |
| 33 | pub var io_instance: Io.Threaded = undefined; | |
| 34 | pub const io = if (builtin.is_test) io_instance.io() else @compileError("not testing"); | |
| 33 | 35 | |
| 34 | 36 | /// TODO https://github.com/ziglang/zig/issues/5738 |
| 35 | 37 | pub var log_level = std.log.Level.warn; |
| ... | ... | @@ -352,11 +354,10 @@ test expectApproxEqRel { |
| 352 | 354 | } |
| 353 | 355 | } |
| 354 | 356 | |
| 355 | /// This function is intended to be used only in tests. When the two slices are not | |
| 356 | /// equal, prints diagnostics to stderr to show exactly how they are not equal (with | |
| 357 | /// the differences highlighted in red), then returns a test failure error. | |
| 358 | /// The colorized output is optional and controlled by the return of `std.Io.tty.Config.detect`. | |
| 359 | /// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead. | |
| 357 | /// This function is intended to be used only in tests. When the two slices are | |
| 358 | /// not equal, prints diagnostics to stderr to show exactly how they are not | |
| 359 | /// equal (with the differences highlighted in red), then returns a test | |
| 360 | /// failure error. | |
| 360 | 361 | pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void { |
| 361 | 362 | const diff_index: usize = diff_index: { |
| 362 | 363 | const shortest = @min(expected.len, actual.len); |
| ... | ... | @@ -367,9 +368,11 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const |
| 367 | 368 | break :diff_index if (expected.len == actual.len) return else shortest; |
| 368 | 369 | }; |
| 369 | 370 | if (!backend_can_print) return error.TestExpectedEqual; |
| 370 | const stderr_w, const ttyconf = std.debug.lockStderrWriter(&.{}); | |
| 371 | defer std.debug.unlockStderrWriter(); | |
| 372 | failEqualSlices(T, expected, actual, diff_index, stderr_w, ttyconf) catch {}; | |
| 371 | // Intentionally using the debug Io instance rather than the testing Io instance. | |
| 372 | const stderr = std.debug.lockStderr(&.{}); | |
| 373 | defer std.debug.unlockStderr(); | |
| 374 | const w = &stderr.file_writer.interface; | |
| 375 | failEqualSlices(T, expected, actual, diff_index, w, stderr.terminal_mode) catch {}; | |
| 373 | 376 | return error.TestExpectedEqual; |
| 374 | 377 | } |
| 375 | 378 | |
| ... | ... | @@ -378,8 +381,8 @@ fn failEqualSlices( |
| 378 | 381 | expected: []const T, |
| 379 | 382 | actual: []const T, |
| 380 | 383 | diff_index: usize, |
| 381 | w: *std.Io.Writer, | |
| 382 | ttyconf: std.Io.tty.Config, | |
| 384 | w: *Io.Writer, | |
| 385 | terminal_mode: Io.Terminal.Mode, | |
| 383 | 386 | ) !void { |
| 384 | 387 | try w.print("slices differ. first difference occurs at index {d} (0x{X})\n", .{ diff_index, diff_index }); |
| 385 | 388 | |
| ... | ... | @@ -402,12 +405,12 @@ fn failEqualSlices( |
| 402 | 405 | var differ = if (T == u8) BytesDiffer{ |
| 403 | 406 | .expected = expected_window, |
| 404 | 407 | .actual = actual_window, |
| 405 | .ttyconf = ttyconf, | |
| 408 | .terminal_mode = terminal_mode, | |
| 406 | 409 | } else SliceDiffer(T){ |
| 407 | 410 | .start_index = window_start, |
| 408 | 411 | .expected = expected_window, |
| 409 | 412 | .actual = actual_window, |
| 410 | .ttyconf = ttyconf, | |
| 413 | .terminal_mode = terminal_mode, | |
| 411 | 414 | }; |
| 412 | 415 | |
| 413 | 416 | // 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 | 467 | start_index: usize, |
| 465 | 468 | expected: []const T, |
| 466 | 469 | actual: []const T, |
| 467 | ttyconf: std.Io.tty.Config, | |
| 470 | terminal_mode: Io.Terminal.Mode, | |
| 468 | 471 | |
| 469 | 472 | const Self = @This(); |
| 470 | 473 | |
| 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 | 476 | for (self.expected, 0..) |value, i| { |
| 473 | 477 | const full_index = self.start_index + i; |
| 474 | 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 | 480 | if (@typeInfo(T) == .pointer) { |
| 477 | 481 | try writer.print("[{}]{*}: {any}\n", .{ full_index, value, value }); |
| 478 | 482 | } else { |
| 479 | 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 | 491 | const BytesDiffer = struct { |
| 488 | 492 | expected: []const u8, |
| 489 | 493 | actual: []const u8, |
| 490 | ttyconf: std.Io.tty.Config, | |
| 494 | terminal_mode: Io.Terminal.Mode, | |
| 491 | 495 | |
| 492 | pub fn write(self: BytesDiffer, writer: *std.Io.Writer) !void { | |
| 496 | pub fn write(self: BytesDiffer, writer: *Io.Writer) !void { | |
| 493 | 497 | var expected_iterator = std.mem.window(u8, self.expected, 16, 16); |
| 494 | 498 | var row: usize = 0; |
| 495 | 499 | while (expected_iterator.next()) |chunk| { |
| ... | ... | @@ -514,7 +518,7 @@ const BytesDiffer = struct { |
| 514 | 518 | try self.writeDiff(writer, "{c}", .{byte}, diff); |
| 515 | 519 | } else { |
| 516 | 520 | // 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 | 522 | try self.writeDiff(writer, ".", .{}, diff); |
| 519 | 523 | continue; |
| 520 | 524 | } |
| ... | ... | @@ -535,10 +539,14 @@ const BytesDiffer = struct { |
| 535 | 539 | } |
| 536 | 540 | } |
| 537 | 541 | |
| 538 | fn writeDiff(self: BytesDiffer, writer: *std.Io.Writer, comptime fmt: []const u8, args: anytype, diff: bool) !void { | |
| 539 | if (diff) try self.ttyconf.setColor(writer, .red); | |
| 542 | fn terminal(self: *const BytesDiffer, writer: *Io.Writer) Io.Terminal { | |
| 543 | return .{ .writer = writer, .mode = self.terminal_mode }; | |
| 544 | } | |
| 545 | ||
| 546 | fn writeDiff(self: BytesDiffer, writer: *Io.Writer, comptime fmt: []const u8, args: anytype, diff: bool) !void { | |
| 547 | if (diff) try self.terminal(writer).setColor(.red); | |
| 540 | 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 | }; |
| 544 | 552 | |
| ... | ... | @@ -605,34 +613,35 @@ pub fn expect(ok: bool) !void { |
| 605 | 613 | } |
| 606 | 614 | |
| 607 | 615 | pub const TmpDir = struct { |
| 608 | dir: std.fs.Dir, | |
| 609 | parent_dir: std.fs.Dir, | |
| 616 | dir: Io.Dir, | |
| 617 | parent_dir: Io.Dir, | |
| 610 | 618 | sub_path: [sub_path_len]u8, |
| 611 | 619 | |
| 612 | 620 | const random_bytes_count = 12; |
| 613 | 621 | const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); |
| 614 | 622 | |
| 615 | 623 | pub fn cleanup(self: *TmpDir) void { |
| 616 | self.dir.close(); | |
| 617 | self.parent_dir.deleteTree(&self.sub_path) catch {}; | |
| 618 | self.parent_dir.close(); | |
| 624 | self.dir.close(io); | |
| 625 | self.parent_dir.deleteTree(io, &self.sub_path) catch {}; | |
| 626 | self.parent_dir.close(io); | |
| 619 | 627 | self.* = undefined; |
| 620 | 628 | } |
| 621 | 629 | }; |
| 622 | 630 | |
| 623 | pub fn tmpDir(opts: std.fs.Dir.OpenOptions) TmpDir { | |
| 631 | pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir { | |
| 632 | comptime assert(builtin.is_test); | |
| 624 | 633 | var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; |
| 625 | 634 | std.crypto.random.bytes(&random_bytes); |
| 626 | 635 | var sub_path: [TmpDir.sub_path_len]u8 = undefined; |
| 627 | 636 | _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); |
| 628 | 637 | |
| 629 | const cwd = std.fs.cwd(); | |
| 630 | var cache_dir = cwd.makeOpenPath(".zig-cache", .{}) catch | |
| 638 | const cwd = Io.Dir.cwd(); | |
| 639 | var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch | |
| 631 | 640 | @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); |
| 632 | defer cache_dir.close(); | |
| 633 | const parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch | |
| 641 | defer cache_dir.close(io); | |
| 642 | const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch | |
| 634 | 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) catch | |
| 644 | const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch | |
| 636 | 645 | @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); |
| 637 | 646 | |
| 638 | 647 | return .{ |
| ... | ... | @@ -929,7 +938,7 @@ test "expectEqualDeep primitive type" { |
| 929 | 938 | a, |
| 930 | 939 | b, |
| 931 | 940 | |
| 932 | pub fn format(self: @This(), writer: *std.Io.Writer) !void { | |
| 941 | pub fn format(self: @This(), writer: *Io.Writer) !void { | |
| 933 | 942 | try writer.writeAll(@tagName(self)); |
| 934 | 943 | } |
| 935 | 944 | }; |
| ... | ... | @@ -1146,9 +1155,10 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime |
| 1146 | 1155 | break :x failing_allocator_inst.alloc_index; |
| 1147 | 1156 | }; |
| 1148 | 1157 | |
| 1149 | var fail_index: usize = 0; | |
| 1150 | while (fail_index < needed_alloc_count) : (fail_index += 1) { | |
| 1151 | var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ .fail_index = fail_index }); | |
| 1158 | for (0..needed_alloc_count) |fail_index| { | |
| 1159 | var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ | |
| 1160 | .fail_index = fail_index, | |
| 1161 | }); | |
| 1152 | 1162 | args.@"0" = failing_allocator_inst.allocator(); |
| 1153 | 1163 | |
| 1154 | 1164 | if (@call(.auto, test_fn, args)) |_| { |
| ... | ... | @@ -1160,7 +1170,6 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime |
| 1160 | 1170 | } else |err| switch (err) { |
| 1161 | 1171 | error.OutOfMemory => { |
| 1162 | 1172 | if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) { |
| 1163 | const tty_config: std.Io.tty.Config = .detect(.stderr()); | |
| 1164 | 1173 | print( |
| 1165 | 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 | 1181 | failing_allocator_inst.deallocations, |
| 1173 | 1182 | std.debug.FormatStackTrace{ |
| 1174 | 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 | 1228 | return @import("root").fuzz(context, testOne, options); |
| 1221 | 1229 | } |
| 1222 | 1230 | |
| 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`. | |
| 1224 | 1232 | pub const Reader = struct { |
| 1225 | 1233 | calls: []const Call, |
| 1226 | interface: std.Io.Reader, | |
| 1234 | interface: Io.Reader, | |
| 1227 | 1235 | next_call_index: usize, |
| 1228 | 1236 | next_offset: usize, |
| 1229 | 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, | |
| 1231 | 1239 | |
| 1232 | 1240 | pub const Call = struct { |
| 1233 | 1241 | buffer: []const u8, |
| ... | ... | @@ -1247,7 +1255,7 @@ pub const Reader = struct { |
| 1247 | 1255 | }; |
| 1248 | 1256 | } |
| 1249 | 1257 | |
| 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 | 1259 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); |
| 1252 | 1260 | if (r.calls.len - r.next_call_index == 0) return error.EndOfStream; |
| 1253 | 1261 | const call = r.calls[r.next_call_index]; |
| ... | ... | @@ -1262,13 +1270,13 @@ pub const Reader = struct { |
| 1262 | 1270 | } |
| 1263 | 1271 | }; |
| 1264 | 1272 | |
| 1265 | /// A `std.Io.Reader` that gets its data from another `std.Io.Reader`, and always | |
| 1273 | /// A `Io.Reader` that gets its data from another `Io.Reader`, and always | |
| 1266 | 1274 | /// writes to its own buffer (and returns 0) during `stream` and `readVec`. |
| 1267 | 1275 | pub const ReaderIndirect = struct { |
| 1268 | in: *std.Io.Reader, | |
| 1269 | interface: std.Io.Reader, | |
| 1276 | in: *Io.Reader, | |
| 1277 | interface: Io.Reader, | |
| 1270 | 1278 | |
| 1271 | pub fn init(in: *std.Io.Reader, buffer: []u8) ReaderIndirect { | |
| 1279 | pub fn init(in: *Io.Reader, buffer: []u8) ReaderIndirect { | |
| 1272 | 1280 | return .{ |
| 1273 | 1281 | .in = in, |
| 1274 | 1282 | .interface = .{ |
| ... | ... | @@ -1283,17 +1291,17 @@ pub const ReaderIndirect = struct { |
| 1283 | 1291 | }; |
| 1284 | 1292 | } |
| 1285 | 1293 | |
| 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 | 1295 | try streamInner(r); |
| 1288 | 1296 | return 0; |
| 1289 | 1297 | } |
| 1290 | 1298 | |
| 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 | 1300 | try streamInner(r); |
| 1293 | 1301 | return 0; |
| 1294 | 1302 | } |
| 1295 | 1303 | |
| 1296 | fn streamInner(r: *std.Io.Reader) std.Io.Reader.Error!void { | |
| 1304 | fn streamInner(r: *Io.Reader) Io.Reader.Error!void { | |
| 1297 | 1305 | const r_indirect: *ReaderIndirect = @alignCast(@fieldParentPtr("interface", r)); |
| 1298 | 1306 | |
| 1299 | 1307 | // If there's no room remaining in the buffer at all, make room. |
| ... | ... | @@ -1301,12 +1309,12 @@ pub const ReaderIndirect = struct { |
| 1301 | 1309 | try r.rebase(r.buffer.len); |
| 1302 | 1310 | } |
| 1303 | 1311 | |
| 1304 | var writer: std.Io.Writer = .{ | |
| 1312 | var writer: Io.Writer = .{ | |
| 1305 | 1313 | .buffer = r.buffer, |
| 1306 | 1314 | .end = r.end, |
| 1307 | 1315 | .vtable = &.{ |
| 1308 | .drain = std.Io.Writer.unreachableDrain, | |
| 1309 | .rebase = std.Io.Writer.unreachableRebase, | |
| 1316 | .drain = Io.Writer.unreachableDrain, | |
| 1317 | .rebase = Io.Writer.unreachableRebase, | |
| 1310 | 1318 | }, |
| 1311 | 1319 | }; |
| 1312 | 1320 | defer r.end = writer.end; |
lib/std/unicode/throughput_test.zig+3-3| ... | ... | @@ -1,8 +1,8 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const time = std.time; |
| 3 | 4 | const unicode = std.unicode; |
| 4 | ||
| 5 | const Timer = time.Timer; | |
| 5 | const Timer = std.time.Timer; | |
| 6 | 6 | |
| 7 | 7 | const N = 1_000_000; |
| 8 | 8 | |
| ... | ... | @@ -41,7 +41,7 @@ fn benchmarkCodepointCount(buf: []const u8) !ResultCount { |
| 41 | 41 | pub fn main() !void { |
| 42 | 42 | // Size of buffer is about size of printed message. |
| 43 | 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 | 45 | const stdout = &stdout_writer.interface; |
| 46 | 46 | |
| 47 | 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 | 46 | pub const SrcHash = [16]u8; |
| 47 | 47 | |
| 48 | 48 | pub const Color = enum { |
| 49 | /// Determine whether stderr is a terminal or not automatically. | |
| 49 | /// Auto-detect whether stream supports terminal colors. | |
| 50 | 50 | auto, |
| 51 | /// Assume stderr is not a terminal. | |
| 51 | /// Force-enable colors. | |
| 52 | 52 | off, |
| 53 | /// Assume stderr is a terminal. | |
| 53 | /// Suppress colors. | |
| 54 | 54 | on, |
| 55 | 55 | |
| 56 | pub fn getTtyConf(color: Color, detected: Io.tty.Config) Io.tty.Config { | |
| 56 | pub fn terminalMode(color: Color) ?Io.Terminal.Mode { | |
| 57 | 57 | return switch (color) { |
| 58 | .auto => detected, | |
| 59 | .on => .escape_codes, | |
| 60 | .off => .no_color, | |
| 61 | }; | |
| 62 | } | |
| 63 | pub fn detectTtyConf(color: Color) Io.tty.Config { | |
| 64 | return switch (color) { | |
| 65 | .auto => .detect(.stderr()), | |
| 58 | .auto => null, | |
| 66 | 59 | .on => .escape_codes, |
| 67 | 60 | .off => .no_color, |
| 68 | 61 | }; |
| ... | ... | @@ -639,7 +632,7 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![ |
| 639 | 632 | return buffer.toOwnedSliceSentinel(gpa, 0); |
| 640 | 633 | } |
| 641 | 634 | |
| 642 | pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void { | |
| 635 | pub fn printAstErrorsToStderr(gpa: Allocator, io: Io, tree: Ast, path: []const u8, color: Color) !void { | |
| 643 | 636 | var wip_errors: std.zig.ErrorBundle.Wip = undefined; |
| 644 | 637 | try wip_errors.init(gpa); |
| 645 | 638 | defer wip_errors.deinit(); |
| ... | ... | @@ -648,7 +641,7 @@ pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color |
| 648 | 641 | |
| 649 | 642 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 650 | 643 | defer error_bundle.deinit(gpa); |
| 651 | error_bundle.renderToStdErr(.{}, color); | |
| 644 | return error_bundle.renderToStderr(io, .{}, color); | |
| 652 | 645 | } |
| 653 | 646 | |
| 654 | 647 | pub fn putAstErrorsIntoBundle( |
lib/std/zig/ErrorBundle.zig+44-34| ... | ... | @@ -162,45 +162,57 @@ pub const RenderOptions = struct { |
| 162 | 162 | include_log_text: bool = true, |
| 163 | 163 | }; |
| 164 | 164 | |
| 165 | pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions, color: std.zig.Color) void { | |
| 165 | pub const RenderToStderrError = Io.Cancelable || Io.File.Writer.Error; | |
| 166 | ||
| 167 | pub fn renderToStderr(eb: ErrorBundle, io: Io, options: RenderOptions, color: std.zig.Color) RenderToStderrError!void { | |
| 166 | 168 | var buffer: [256]u8 = undefined; |
| 167 | const w, const ttyconf = std.debug.lockStderrWriter(&buffer); | |
| 168 | defer std.debug.unlockStderrWriter(); | |
| 169 | renderToWriter(eb, options, w, color.getTtyConf(ttyconf)) catch return; | |
| 169 | const stderr = try io.lockStderr(&buffer, color.terminalMode()); | |
| 170 | defer io.unlockStderr(); | |
| 171 | renderToTerminal(eb, options, stderr.terminal()) catch |err| switch (err) { | |
| 172 | error.WriteFailed => return stderr.file_writer.err.?, | |
| 173 | else => |e| return e, | |
| 174 | }; | |
| 175 | } | |
| 176 | ||
| 177 | pub 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 | } |
| 171 | 183 | |
| 172 | pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, w: *Writer, ttyconf: Io.tty.Config) (Writer.Error || std.posix.UnexpectedError)!void { | |
| 184 | pub fn renderToTerminal(eb: ErrorBundle, options: RenderOptions, t: Io.Terminal) Io.Terminal.SetColorError!void { | |
| 173 | 185 | if (eb.extra.len == 0) return; |
| 174 | 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 | } |
| 177 | 189 | |
| 178 | 190 | if (options.include_log_text) { |
| 179 | 191 | const log_text = eb.getCompileLogOutput(); |
| 180 | 192 | if (log_text.len != 0) { |
| 181 | try w.writeAll("\nCompile Log Output:\n"); | |
| 182 | try w.writeAll(log_text); | |
| 193 | try t.writer.writeAll("\nCompile Log Output:\n"); | |
| 194 | try t.writer.writeAll(log_text); | |
| 183 | 195 | } |
| 184 | 196 | } |
| 185 | 197 | } |
| 186 | 198 | |
| 187 | fn renderErrorMessageToWriter( | |
| 199 | fn renderErrorMessage( | |
| 188 | 200 | eb: ErrorBundle, |
| 189 | 201 | options: RenderOptions, |
| 190 | 202 | err_msg_index: MessageIndex, |
| 191 | w: *Writer, | |
| 192 | ttyconf: Io.tty.Config, | |
| 203 | t: Io.Terminal, | |
| 193 | 204 | kind: []const u8, |
| 194 | color: Io.tty.Color, | |
| 205 | color: Io.Terminal.Color, | |
| 195 | 206 | indent: usize, |
| 196 | ) (Writer.Error || std.posix.UnexpectedError)!void { | |
| 207 | ) Io.Terminal.SetColorError!void { | |
| 208 | const w = t.writer; | |
| 197 | 209 | const err_msg = eb.getErrorMessage(err_msg_index); |
| 198 | 210 | if (err_msg.src_loc != .none) { |
| 199 | 211 | const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc)); |
| 200 | 212 | var prefix: Writer.Discarding = .init(&.{}); |
| 201 | 213 | try w.splatByteAll(' ', indent); |
| 202 | 214 | prefix.count += indent; |
| 203 | try ttyconf.setColor(w, .bold); | |
| 215 | try t.setColor(.bold); | |
| 204 | 216 | try w.print("{s}:{d}:{d}: ", .{ |
| 205 | 217 | eb.nullTerminatedString(src.data.src_path), |
| 206 | 218 | src.data.line + 1, |
| ... | ... | @@ -211,7 +223,7 @@ fn renderErrorMessageToWriter( |
| 211 | 223 | src.data.line + 1, |
| 212 | 224 | src.data.column + 1, |
| 213 | 225 | }); |
| 214 | try ttyconf.setColor(w, color); | |
| 226 | try t.setColor(color); | |
| 215 | 227 | try w.writeAll(kind); |
| 216 | 228 | prefix.count += kind.len; |
| 217 | 229 | try w.writeAll(": "); |
| ... | ... | @@ -219,17 +231,17 @@ fn renderErrorMessageToWriter( |
| 219 | 231 | // This is the length of the part before the error message: |
| 220 | 232 | // e.g. "file.zig:4:5: error: " |
| 221 | 233 | const prefix_len: usize = @intCast(prefix.count); |
| 222 | try ttyconf.setColor(w, .reset); | |
| 223 | try ttyconf.setColor(w, .bold); | |
| 234 | try t.setColor(.reset); | |
| 235 | try t.setColor(.bold); | |
| 224 | 236 | if (err_msg.count == 1) { |
| 225 | 237 | try writeMsg(eb, err_msg, w, prefix_len); |
| 226 | 238 | try w.writeByte('\n'); |
| 227 | 239 | } else { |
| 228 | 240 | try writeMsg(eb, err_msg, w, prefix_len); |
| 229 | try ttyconf.setColor(w, .dim); | |
| 241 | try t.setColor(.dim); | |
| 230 | 242 | try w.print(" ({d} times)\n", .{err_msg.count}); |
| 231 | 243 | } |
| 232 | try ttyconf.setColor(w, .reset); | |
| 244 | try t.setColor(.reset); | |
| 233 | 245 | if (src.data.source_line != 0 and options.include_source_line) { |
| 234 | 246 | const line = eb.nullTerminatedString(src.data.source_line); |
| 235 | 247 | for (line) |b| switch (b) { |
| ... | ... | @@ -242,19 +254,19 @@ fn renderErrorMessageToWriter( |
| 242 | 254 | // -1 since span.main includes the caret |
| 243 | 255 | const after_caret = src.data.span_end -| src.data.span_main -| 1; |
| 244 | 256 | try w.splatByteAll(' ', src.data.column - before_caret); |
| 245 | try ttyconf.setColor(w, .green); | |
| 257 | try t.setColor(.green); | |
| 246 | 258 | try w.splatByteAll('~', before_caret); |
| 247 | 259 | try w.writeByte('^'); |
| 248 | 260 | try w.splatByteAll('~', after_caret); |
| 249 | 261 | try w.writeByte('\n'); |
| 250 | try ttyconf.setColor(w, .reset); | |
| 262 | try t.setColor(.reset); | |
| 251 | 263 | } |
| 252 | 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 | 267 | if (src.data.reference_trace_len > 0 and options.include_reference_trace) { |
| 256 | try ttyconf.setColor(w, .reset); | |
| 257 | try ttyconf.setColor(w, .dim); | |
| 268 | try t.setColor(.reset); | |
| 269 | try t.setColor(.dim); | |
| 258 | 270 | try w.print("referenced by:\n", .{}); |
| 259 | 271 | var ref_index = src.end; |
| 260 | 272 | for (0..src.data.reference_trace_len) |_| { |
| ... | ... | @@ -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 | 298 | } else { |
| 287 | try ttyconf.setColor(w, color); | |
| 299 | try t.setColor(color); | |
| 288 | 300 | try w.splatByteAll(' ', indent); |
| 289 | 301 | try w.writeAll(kind); |
| 290 | 302 | try w.writeAll(": "); |
| 291 | try ttyconf.setColor(w, .reset); | |
| 303 | try t.setColor(.reset); | |
| 292 | 304 | const msg = eb.nullTerminatedString(err_msg.msg); |
| 293 | 305 | if (err_msg.count == 1) { |
| 294 | 306 | try w.print("{s}\n", .{msg}); |
| 295 | 307 | } else { |
| 296 | 308 | try w.print("{s}", .{msg}); |
| 297 | try ttyconf.setColor(w, .dim); | |
| 309 | try t.setColor(.dim); | |
| 298 | 310 | try w.print(" ({d} times)\n", .{err_msg.count}); |
| 299 | 311 | } |
| 300 | try ttyconf.setColor(w, .reset); | |
| 312 | try t.setColor(.reset); | |
| 301 | 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 | 818 | }; |
| 807 | 819 | defer bundle.deinit(std.testing.allocator); |
| 808 | 820 | |
| 809 | const ttyconf: Io.tty.Config = .no_color; | |
| 810 | ||
| 811 | 821 | var bundle_buf: Writer.Allocating = .init(std.testing.allocator); |
| 812 | 822 | const bundle_bw = &bundle_buf.interface; |
| 813 | 823 | defer bundle_buf.deinit(); |
| 814 | try bundle.renderToWriter(.{ .ttyconf = ttyconf }, bundle_bw); | |
| 824 | try bundle.renderToWriter(bundle_bw); | |
| 815 | 825 | |
| 816 | 826 | var copy = copy: { |
| 817 | 827 | var wip: ErrorBundle.Wip = undefined; |
| ... | ... | @@ -827,7 +837,7 @@ pub const Wip = struct { |
| 827 | 837 | var copy_buf: Writer.Allocating = .init(std.testing.allocator); |
| 828 | 838 | const copy_bw = &copy_buf.interface; |
| 829 | 839 | defer copy_buf.deinit(); |
| 830 | try copy.renderToWriter(.{ .ttyconf = ttyconf }, copy_bw); | |
| 840 | try copy.renderToWriter(copy_bw); | |
| 831 | 841 | |
| 832 | 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 | const LibCDirs = @This(); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | ||
| 4 | const std = @import("../std.zig"); | |
| 5 | const Io = std.Io; | |
| 6 | const LibCInstallation = std.zig.LibCInstallation; | |
| 7 | const Allocator = std.mem.Allocator; | |
| 8 | ||
| 1 | 9 | libc_include_dir_list: []const []const u8, |
| 2 | 10 | libc_installation: ?*const LibCInstallation, |
| 3 | 11 | libc_framework_dir_list: []const []const u8, |
| ... | ... | @@ -14,6 +22,7 @@ pub const DarwinSdkLayout = enum { |
| 14 | 22 | |
| 15 | 23 | pub fn detect( |
| 16 | 24 | arena: Allocator, |
| 25 | io: Io, | |
| 17 | 26 | zig_lib_dir: []const u8, |
| 18 | 27 | target: *const std.Target, |
| 19 | 28 | is_native_abi: bool, |
| ... | ... | @@ -38,7 +47,7 @@ pub fn detect( |
| 38 | 47 | // using the system libc installation. |
| 39 | 48 | if (is_native_abi and !target.isMinGW()) { |
| 40 | 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 | 51 | error.CCompilerExitCode, |
| 43 | 52 | error.CCompilerCrashed, |
| 44 | 53 | error.CCompilerCannotFindHeaders, |
| ... | ... | @@ -75,7 +84,7 @@ pub fn detect( |
| 75 | 84 | |
| 76 | 85 | if (use_system_abi) { |
| 77 | 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 | 88 | return detectFromInstallation(arena, target, libc); |
| 80 | 89 | } |
| 81 | 90 | |
| ... | ... | @@ -265,9 +274,3 @@ fn libCGenericName(target: *const std.Target) [:0]const u8 { |
| 265 | 274 | => unreachable, |
| 266 | 275 | } |
| 267 | 276 | } |
| 268 | ||
| 269 | const LibCDirs = @This(); | |
| 270 | const builtin = @import("builtin"); | |
| 271 | const std = @import("../std.zig"); | |
| 272 | const LibCInstallation = std.zig.LibCInstallation; | |
| 273 | const Allocator = std.mem.Allocator; |
lib/std/zig/LibCInstallation.zig+94-111| ... | ... | @@ -1,4 +1,18 @@ |
| 1 | 1 | //! See the render function implementation for documentation of the fields. |
| 2 | const LibCInstallation = @This(); | |
| 3 | ||
| 4 | const builtin = @import("builtin"); | |
| 5 | const is_darwin = builtin.target.os.tag.isDarwin(); | |
| 6 | const is_windows = builtin.target.os.tag == .windows; | |
| 7 | const is_haiku = builtin.target.os.tag == .haiku; | |
| 8 | ||
| 9 | const std = @import("std"); | |
| 10 | const Io = std.Io; | |
| 11 | const Target = std.Target; | |
| 12 | const fs = std.fs; | |
| 13 | const Allocator = std.mem.Allocator; | |
| 14 | const Path = std.Build.Cache.Path; | |
| 15 | const log = std.log.scoped(.libc_installation); | |
| 2 | 16 | |
| 3 | 17 | include_dir: ?[]const u8 = null, |
| 4 | 18 | sys_include_dir: ?[]const u8 = null, |
| ... | ... | @@ -23,11 +37,7 @@ pub const FindError = error{ |
| 23 | 37 | ZigIsTheCCompiler, |
| 24 | 38 | }; |
| 25 | 39 | |
| 26 | pub fn parse( | |
| 27 | allocator: Allocator, | |
| 28 | libc_file: []const u8, | |
| 29 | target: *const std.Target, | |
| 30 | ) !LibCInstallation { | |
| 40 | pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const std.Target) !LibCInstallation { | |
| 31 | 41 | var self: LibCInstallation = .{}; |
| 32 | 42 | |
| 33 | 43 | const fields = std.meta.fields(LibCInstallation); |
| ... | ... | @@ -43,7 +53,7 @@ pub fn parse( |
| 43 | 53 | } |
| 44 | 54 | } |
| 45 | 55 | |
| 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 | 57 | defer allocator.free(contents); |
| 48 | 58 | |
| 49 | 59 | var it = std.mem.tokenizeScalar(u8, contents, '\n'); |
| ... | ... | @@ -156,7 +166,6 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void { |
| 156 | 166 | } |
| 157 | 167 | |
| 158 | 168 | pub const FindNativeOptions = struct { |
| 159 | allocator: Allocator, | |
| 160 | 169 | target: *const std.Target, |
| 161 | 170 | |
| 162 | 171 | /// If enabled, will print human-friendly errors to stderr. |
| ... | ... | @@ -164,50 +173,50 @@ pub const FindNativeOptions = struct { |
| 164 | 173 | }; |
| 165 | 174 | |
| 166 | 175 | /// Finds the default, native libc. |
| 167 | pub fn findNative(args: FindNativeOptions) FindError!LibCInstallation { | |
| 176 | pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!LibCInstallation { | |
| 168 | 177 | var self: LibCInstallation = .{}; |
| 169 | 178 | |
| 170 | 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 | 181 | return error.DarwinSdkNotFound; |
| 173 | const sdk = std.zig.system.darwin.getSdk(args.allocator, args.target) orelse | |
| 182 | const sdk = std.zig.system.darwin.getSdk(gpa, io, args.target) orelse | |
| 174 | 183 | return error.DarwinSdkNotFound; |
| 175 | defer args.allocator.free(sdk); | |
| 184 | defer gpa.free(sdk); | |
| 176 | 185 | |
| 177 | self.include_dir = try fs.path.join(args.allocator, &.{ | |
| 186 | self.include_dir = try fs.path.join(gpa, &.{ | |
| 178 | 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 | 190 | sdk, "usr/include", |
| 182 | 191 | }); |
| 183 | 192 | return self; |
| 184 | 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 | 195 | error.NotFound => return error.WindowsSdkNotFound, |
| 187 | 196 | error.PathTooLong => return error.WindowsSdkNotFound, |
| 188 | 197 | error.OutOfMemory => return error.OutOfMemory, |
| 189 | 198 | }; |
| 190 | defer sdk.free(args.allocator); | |
| 199 | defer sdk.free(gpa); | |
| 191 | 200 | |
| 192 | try self.findNativeMsvcIncludeDir(args, sdk); | |
| 193 | try self.findNativeMsvcLibDir(args, sdk); | |
| 194 | try self.findNativeKernel32LibDir(args, sdk); | |
| 195 | try self.findNativeIncludeDirWindows(args, sdk); | |
| 196 | try self.findNativeCrtDirWindows(args, sdk); | |
| 201 | try self.findNativeMsvcIncludeDir(gpa, io, sdk); | |
| 202 | try self.findNativeMsvcLibDir(gpa, sdk); | |
| 203 | try self.findNativeKernel32LibDir(gpa, io, args, sdk); | |
| 204 | try self.findNativeIncludeDirWindows(gpa, io, sdk); | |
| 205 | try self.findNativeCrtDirWindows(gpa, io, args.target, sdk); | |
| 197 | 206 | } else if (is_haiku) { |
| 198 | try self.findNativeIncludeDirPosix(args); | |
| 199 | try self.findNativeGccDirHaiku(args); | |
| 200 | self.crt_dir = try args.allocator.dupeZ(u8, "/system/develop/lib"); | |
| 207 | try self.findNativeIncludeDirPosix(gpa, io, args); | |
| 208 | try self.findNativeGccDirHaiku(gpa, io, args); | |
| 209 | self.crt_dir = try gpa.dupeZ(u8, "/system/develop/lib"); | |
| 201 | 210 | } else if (builtin.target.os.tag == .illumos) { |
| 202 | 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"); | |
| 204 | self.sys_include_dir = try args.allocator.dupeZ(u8, "/usr/include"); | |
| 205 | self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib/64"); | |
| 212 | self.include_dir = try gpa.dupeZ(u8, "/usr/include"); | |
| 213 | self.sys_include_dir = try gpa.dupeZ(u8, "/usr/include"); | |
| 214 | self.crt_dir = try gpa.dupeZ(u8, "/usr/lib/64"); | |
| 206 | 215 | } else if (std.process.can_spawn) { |
| 207 | try self.findNativeIncludeDirPosix(args); | |
| 216 | try self.findNativeIncludeDirPosix(gpa, io, args); | |
| 208 | 217 | switch (builtin.target.os.tag) { |
| 209 | .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib"), | |
| 210 | .linux => try self.findNativeCrtDirPosix(args), | |
| 218 | .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try gpa.dupeZ(u8, "/usr/lib"), | |
| 219 | .linux => try self.findNativeCrtDirPosix(gpa, io, args), | |
| 211 | 220 | else => {}, |
| 212 | 221 | } |
| 213 | 222 | } else { |
| ... | ... | @@ -227,11 +236,9 @@ pub fn deinit(self: *LibCInstallation, allocator: Allocator) void { |
| 227 | 236 | self.* = undefined; |
| 228 | 237 | } |
| 229 | 238 | |
| 230 | fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void { | |
| 231 | const allocator = args.allocator; | |
| 232 | ||
| 239 | fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void { | |
| 233 | 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 | 242 | error.Unexpected => unreachable, // WASI-only |
| 236 | 243 | else => |e| return e, |
| 237 | 244 | }; |
| ... | ... | @@ -250,7 +257,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F |
| 250 | 257 | |
| 251 | 258 | const dev_null = if (is_windows) "nul" else "/dev/null"; |
| 252 | 259 | |
| 253 | var argv = std.array_list.Managed([]const u8).init(allocator); | |
| 260 | var argv = std.array_list.Managed([]const u8).init(gpa); | |
| 254 | 261 | defer argv.deinit(); |
| 255 | 262 | |
| 256 | 263 | try appendCcExe(&argv, skip_cc_env_var); |
| ... | ... | @@ -261,8 +268,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F |
| 261 | 268 | dev_null, |
| 262 | 269 | }); |
| 263 | 270 | |
| 264 | const run_res = std.process.Child.run(.{ | |
| 265 | .allocator = allocator, | |
| 271 | const run_res = std.process.Child.run(gpa, io, .{ | |
| 266 | 272 | .argv = argv.items, |
| 267 | 273 | .max_output_bytes = 1024 * 1024, |
| 268 | 274 | .env_map = &env_map, |
| ... | ... | @@ -279,8 +285,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F |
| 279 | 285 | }, |
| 280 | 286 | }; |
| 281 | 287 | defer { |
| 282 | allocator.free(run_res.stdout); | |
| 283 | allocator.free(run_res.stderr); | |
| 288 | gpa.free(run_res.stdout); | |
| 289 | gpa.free(run_res.stderr); | |
| 284 | 290 | } |
| 285 | 291 | switch (run_res.term) { |
| 286 | 292 | .Exited => |code| if (code != 0) { |
| ... | ... | @@ -294,7 +300,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F |
| 294 | 300 | } |
| 295 | 301 | |
| 296 | 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 | 304 | defer search_paths.deinit(); |
| 299 | 305 | while (it.next()) |line| { |
| 300 | 306 | if (line.len != 0 and line[0] == ' ') { |
| ... | ... | @@ -318,7 +324,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F |
| 318 | 324 | // search in reverse order |
| 319 | 325 | const search_path_untrimmed = search_paths.items[search_paths.items.len - path_i - 1]; |
| 320 | 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 | 328 | error.FileNotFound, |
| 323 | 329 | error.NotDir, |
| 324 | 330 | error.NoDevice, |
| ... | ... | @@ -326,11 +332,11 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F |
| 326 | 332 | |
| 327 | 333 | else => return error.FileSystem, |
| 328 | 334 | }; |
| 329 | defer search_dir.close(); | |
| 335 | defer search_dir.close(io); | |
| 330 | 336 | |
| 331 | 337 | if (self.include_dir == null) { |
| 332 | if (search_dir.access(include_dir_example_file, .{})) |_| { | |
| 333 | self.include_dir = try allocator.dupeZ(u8, search_path); | |
| 338 | if (search_dir.access(io, include_dir_example_file, .{})) |_| { | |
| 339 | self.include_dir = try gpa.dupeZ(u8, search_path); | |
| 334 | 340 | } else |err| switch (err) { |
| 335 | 341 | error.FileNotFound => {}, |
| 336 | 342 | else => return error.FileSystem, |
| ... | ... | @@ -338,8 +344,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F |
| 338 | 344 | } |
| 339 | 345 | |
| 340 | 346 | if (self.sys_include_dir == null) { |
| 341 | if (search_dir.access(sys_include_dir_example_file, .{})) |_| { | |
| 342 | self.sys_include_dir = try allocator.dupeZ(u8, search_path); | |
| 347 | if (search_dir.access(io, sys_include_dir_example_file, .{})) |_| { | |
| 348 | self.sys_include_dir = try gpa.dupeZ(u8, search_path); | |
| 343 | 349 | } else |err| switch (err) { |
| 344 | 350 | error.FileNotFound => {}, |
| 345 | 351 | else => return error.FileSystem, |
| ... | ... | @@ -357,22 +363,21 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F |
| 357 | 363 | |
| 358 | 364 | fn findNativeIncludeDirWindows( |
| 359 | 365 | self: *LibCInstallation, |
| 360 | args: FindNativeOptions, | |
| 366 | gpa: Allocator, | |
| 367 | io: Io, | |
| 361 | 368 | sdk: std.zig.WindowsSdk, |
| 362 | 369 | ) FindError!void { |
| 363 | const allocator = args.allocator; | |
| 364 | ||
| 365 | 370 | var install_buf: [2]std.zig.WindowsSdk.Installation = undefined; |
| 366 | 371 | const installs = fillInstallations(&install_buf, sdk); |
| 367 | 372 | |
| 368 | var result_buf = std.array_list.Managed(u8).init(allocator); | |
| 373 | var result_buf = std.array_list.Managed(u8).init(gpa); | |
| 369 | 374 | defer result_buf.deinit(); |
| 370 | 375 | |
| 371 | 376 | for (installs) |install| { |
| 372 | 377 | result_buf.shrinkAndFree(0); |
| 373 | 378 | try result_buf.print("{s}\\Include\\{s}\\ucrt", .{ install.path, install.version }); |
| 374 | 379 | |
| 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 | 381 | error.FileNotFound, |
| 377 | 382 | error.NotDir, |
| 378 | 383 | error.NoDevice, |
| ... | ... | @@ -380,9 +385,9 @@ fn findNativeIncludeDirWindows( |
| 380 | 385 | |
| 381 | 386 | else => return error.FileSystem, |
| 382 | 387 | }; |
| 383 | defer dir.close(); | |
| 388 | defer dir.close(io); | |
| 384 | 389 | |
| 385 | dir.access("stdlib.h", .{}) catch |err| switch (err) { | |
| 390 | dir.access(io, "stdlib.h", .{}) catch |err| switch (err) { | |
| 386 | 391 | error.FileNotFound => continue, |
| 387 | 392 | else => return error.FileSystem, |
| 388 | 393 | }; |
| ... | ... | @@ -396,18 +401,18 @@ fn findNativeIncludeDirWindows( |
| 396 | 401 | |
| 397 | 402 | fn findNativeCrtDirWindows( |
| 398 | 403 | self: *LibCInstallation, |
| 399 | args: FindNativeOptions, | |
| 404 | gpa: Allocator, | |
| 405 | io: Io, | |
| 406 | target: *const std.Target, | |
| 400 | 407 | sdk: std.zig.WindowsSdk, |
| 401 | 408 | ) FindError!void { |
| 402 | const allocator = args.allocator; | |
| 403 | ||
| 404 | 409 | var install_buf: [2]std.zig.WindowsSdk.Installation = undefined; |
| 405 | 410 | const installs = fillInstallations(&install_buf, sdk); |
| 406 | 411 | |
| 407 | var result_buf = std.array_list.Managed(u8).init(allocator); | |
| 412 | var result_buf = std.array_list.Managed(u8).init(gpa); | |
| 408 | 413 | defer result_buf.deinit(); |
| 409 | 414 | |
| 410 | const arch_sub_dir = switch (args.target.cpu.arch) { | |
| 415 | const arch_sub_dir = switch (target.cpu.arch) { | |
| 411 | 416 | .x86 => "x86", |
| 412 | 417 | .x86_64 => "x64", |
| 413 | 418 | .arm, .armeb => "arm", |
| ... | ... | @@ -419,7 +424,7 @@ fn findNativeCrtDirWindows( |
| 419 | 424 | result_buf.shrinkAndFree(0); |
| 420 | 425 | try result_buf.print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ install.path, install.version, arch_sub_dir }); |
| 421 | 426 | |
| 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 | 428 | error.FileNotFound, |
| 424 | 429 | error.NotDir, |
| 425 | 430 | error.NoDevice, |
| ... | ... | @@ -427,9 +432,9 @@ fn findNativeCrtDirWindows( |
| 427 | 432 | |
| 428 | 433 | else => return error.FileSystem, |
| 429 | 434 | }; |
| 430 | defer dir.close(); | |
| 435 | defer dir.close(io); | |
| 431 | 436 | |
| 432 | dir.access("ucrt.lib", .{}) catch |err| switch (err) { | |
| 437 | dir.access(io, "ucrt.lib", .{}) catch |err| switch (err) { | |
| 433 | 438 | error.FileNotFound => continue, |
| 434 | 439 | else => return error.FileSystem, |
| 435 | 440 | }; |
| ... | ... | @@ -440,9 +445,8 @@ fn findNativeCrtDirWindows( |
| 440 | 445 | return error.LibCRuntimeNotFound; |
| 441 | 446 | } |
| 442 | 447 | |
| 443 | fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void { | |
| 444 | self.crt_dir = try ccPrintFileName(.{ | |
| 445 | .allocator = args.allocator, | |
| 448 | fn findNativeCrtDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void { | |
| 449 | self.crt_dir = try ccPrintFileName(gpa, io, .{ | |
| 446 | 450 | .search_basename = switch (args.target.os.tag) { |
| 447 | 451 | .linux => if (args.target.abi.isAndroid()) "crtbegin_dynamic.o" else "crt1.o", |
| 448 | 452 | else => "crt1.o", |
| ... | ... | @@ -452,9 +456,8 @@ fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindE |
| 452 | 456 | }); |
| 453 | 457 | } |
| 454 | 458 | |
| 455 | fn findNativeGccDirHaiku(self: *LibCInstallation, args: FindNativeOptions) FindError!void { | |
| 456 | self.gcc_dir = try ccPrintFileName(.{ | |
| 457 | .allocator = args.allocator, | |
| 459 | fn findNativeGccDirHaiku(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void { | |
| 460 | self.gcc_dir = try ccPrintFileName(gpa, io, .{ | |
| 458 | 461 | .search_basename = "crtbeginS.o", |
| 459 | 462 | .want_dirname = .only_dir, |
| 460 | 463 | .verbose = args.verbose, |
| ... | ... | @@ -463,15 +466,15 @@ fn findNativeGccDirHaiku(self: *LibCInstallation, args: FindNativeOptions) FindE |
| 463 | 466 | |
| 464 | 467 | fn findNativeKernel32LibDir( |
| 465 | 468 | self: *LibCInstallation, |
| 469 | gpa: Allocator, | |
| 470 | io: Io, | |
| 466 | 471 | args: FindNativeOptions, |
| 467 | 472 | sdk: std.zig.WindowsSdk, |
| 468 | 473 | ) FindError!void { |
| 469 | const allocator = args.allocator; | |
| 470 | ||
| 471 | 474 | var install_buf: [2]std.zig.WindowsSdk.Installation = undefined; |
| 472 | 475 | const installs = fillInstallations(&install_buf, sdk); |
| 473 | 476 | |
| 474 | var result_buf = std.array_list.Managed(u8).init(allocator); | |
| 477 | var result_buf = std.array_list.Managed(u8).init(gpa); | |
| 475 | 478 | defer result_buf.deinit(); |
| 476 | 479 | |
| 477 | 480 | const arch_sub_dir = switch (args.target.cpu.arch) { |
| ... | ... | @@ -486,7 +489,7 @@ fn findNativeKernel32LibDir( |
| 486 | 489 | result_buf.shrinkAndFree(0); |
| 487 | 490 | try result_buf.print("{s}\\Lib\\{s}\\um\\{s}", .{ install.path, install.version, arch_sub_dir }); |
| 488 | 491 | |
| 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 | 493 | error.FileNotFound, |
| 491 | 494 | error.NotDir, |
| 492 | 495 | error.NoDevice, |
| ... | ... | @@ -494,9 +497,9 @@ fn findNativeKernel32LibDir( |
| 494 | 497 | |
| 495 | 498 | else => return error.FileSystem, |
| 496 | 499 | }; |
| 497 | defer dir.close(); | |
| 500 | defer dir.close(io); | |
| 498 | 501 | |
| 499 | dir.access("kernel32.lib", .{}) catch |err| switch (err) { | |
| 502 | dir.access(io, "kernel32.lib", .{}) catch |err| switch (err) { | |
| 500 | 503 | error.FileNotFound => continue, |
| 501 | 504 | else => return error.FileSystem, |
| 502 | 505 | }; |
| ... | ... | @@ -509,19 +512,18 @@ fn findNativeKernel32LibDir( |
| 509 | 512 | |
| 510 | 513 | fn findNativeMsvcIncludeDir( |
| 511 | 514 | self: *LibCInstallation, |
| 512 | args: FindNativeOptions, | |
| 515 | gpa: Allocator, | |
| 516 | io: Io, | |
| 513 | 517 | sdk: std.zig.WindowsSdk, |
| 514 | 518 | ) FindError!void { |
| 515 | const allocator = args.allocator; | |
| 516 | ||
| 517 | 519 | const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCStdLibHeaderNotFound; |
| 518 | 520 | const up1 = fs.path.dirname(msvc_lib_dir) orelse return error.LibCStdLibHeaderNotFound; |
| 519 | 521 | const up2 = fs.path.dirname(up1) orelse return error.LibCStdLibHeaderNotFound; |
| 520 | 522 | |
| 521 | const dir_path = try fs.path.join(allocator, &[_][]const u8{ up2, "include" }); | |
| 522 | errdefer allocator.free(dir_path); | |
| 523 | const dir_path = try fs.path.join(gpa, &[_][]const u8{ up2, "include" }); | |
| 524 | errdefer gpa.free(dir_path); | |
| 523 | 525 | |
| 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 | 527 | error.FileNotFound, |
| 526 | 528 | error.NotDir, |
| 527 | 529 | error.NoDevice, |
| ... | ... | @@ -529,9 +531,9 @@ fn findNativeMsvcIncludeDir( |
| 529 | 531 | |
| 530 | 532 | else => return error.FileSystem, |
| 531 | 533 | }; |
| 532 | defer dir.close(); | |
| 534 | defer dir.close(io); | |
| 533 | 535 | |
| 534 | dir.access("vcruntime.h", .{}) catch |err| switch (err) { | |
| 536 | dir.access(io, "vcruntime.h", .{}) catch |err| switch (err) { | |
| 535 | 537 | error.FileNotFound => return error.LibCStdLibHeaderNotFound, |
| 536 | 538 | else => return error.FileSystem, |
| 537 | 539 | }; |
| ... | ... | @@ -541,27 +543,23 @@ fn findNativeMsvcIncludeDir( |
| 541 | 543 | |
| 542 | 544 | fn findNativeMsvcLibDir( |
| 543 | 545 | self: *LibCInstallation, |
| 544 | args: FindNativeOptions, | |
| 546 | gpa: Allocator, | |
| 545 | 547 | sdk: std.zig.WindowsSdk, |
| 546 | 548 | ) FindError!void { |
| 547 | const allocator = args.allocator; | |
| 548 | 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 | } |
| 551 | 552 | |
| 552 | 553 | pub const CCPrintFileNameOptions = struct { |
| 553 | allocator: Allocator, | |
| 554 | 554 | search_basename: []const u8, |
| 555 | 555 | want_dirname: enum { full_path, only_dir }, |
| 556 | 556 | verbose: bool = false, |
| 557 | 557 | }; |
| 558 | 558 | |
| 559 | 559 | /// caller owns returned memory |
| 560 | fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { | |
| 561 | const allocator = args.allocator; | |
| 562 | ||
| 560 | fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 { | |
| 563 | 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 | 563 | error.Unexpected => unreachable, // WASI-only |
| 566 | 564 | else => |e| return e, |
| 567 | 565 | }; |
| ... | ... | @@ -578,17 +576,16 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { |
| 578 | 576 | break :blk false; |
| 579 | 577 | }; |
| 580 | 578 | |
| 581 | var argv = std.array_list.Managed([]const u8).init(allocator); | |
| 579 | var argv = std.array_list.Managed([]const u8).init(gpa); | |
| 582 | 580 | defer argv.deinit(); |
| 583 | 581 | |
| 584 | const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={s}", .{args.search_basename}); | |
| 585 | defer allocator.free(arg1); | |
| 582 | const arg1 = try std.fmt.allocPrint(gpa, "-print-file-name={s}", .{args.search_basename}); | |
| 583 | defer gpa.free(arg1); | |
| 586 | 584 | |
| 587 | 585 | try appendCcExe(&argv, skip_cc_env_var); |
| 588 | 586 | try argv.append(arg1); |
| 589 | 587 | |
| 590 | const run_res = std.process.Child.run(.{ | |
| 591 | .allocator = allocator, | |
| 588 | const run_res = std.process.Child.run(gpa, io, .{ | |
| 592 | 589 | .argv = argv.items, |
| 593 | 590 | .max_output_bytes = 1024 * 1024, |
| 594 | 591 | .env_map = &env_map, |
| ... | ... | @@ -602,8 +599,8 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { |
| 602 | 599 | else => return error.UnableToSpawnCCompiler, |
| 603 | 600 | }; |
| 604 | 601 | defer { |
| 605 | allocator.free(run_res.stdout); | |
| 606 | allocator.free(run_res.stderr); | |
| 602 | gpa.free(run_res.stdout); | |
| 603 | gpa.free(run_res.stderr); | |
| 607 | 604 | } |
| 608 | 605 | switch (run_res.term) { |
| 609 | 606 | .Exited => |code| if (code != 0) { |
| ... | ... | @@ -622,10 +619,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { |
| 622 | 619 | // So we detect failure by checking if the output matches exactly the input. |
| 623 | 620 | if (std.mem.eql(u8, line, args.search_basename)) return error.LibCRuntimeNotFound; |
| 624 | 621 | switch (args.want_dirname) { |
| 625 | .full_path => return allocator.dupeZ(u8, line), | |
| 622 | .full_path => return gpa.dupeZ(u8, line), | |
| 626 | 623 | .only_dir => { |
| 627 | 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 | 1012 | }, |
| 1016 | 1013 | } |
| 1017 | 1014 | } |
| 1018 | ||
| 1019 | const LibCInstallation = @This(); | |
| 1020 | const std = @import("std"); | |
| 1021 | const builtin = @import("builtin"); | |
| 1022 | const Target = std.Target; | |
| 1023 | const fs = std.fs; | |
| 1024 | const Allocator = std.mem.Allocator; | |
| 1025 | const Path = std.Build.Cache.Path; | |
| 1026 | ||
| 1027 | const is_darwin = builtin.target.os.tag.isDarwin(); | |
| 1028 | const is_windows = builtin.target.os.tag == .windows; | |
| 1029 | const is_haiku = builtin.target.os.tag == .haiku; | |
| 1030 | ||
| 1031 | const log = std.log.scoped(.libc_installation); |
lib/std/zig/WindowsSdk.zig+158-147| ... | ... | @@ -1,7 +1,11 @@ |
| 1 | 1 | const WindowsSdk = @This(); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | ||
| 3 | 4 | const std = @import("std"); |
| 5 | const Io = std.Io; | |
| 6 | const Dir = std.Io.Dir; | |
| 4 | 7 | const Writer = std.Io.Writer; |
| 8 | const Allocator = std.mem.Allocator; | |
| 5 | 9 | |
| 6 | 10 | windows10sdk: ?Installation, |
| 7 | 11 | windows81sdk: ?Installation, |
| ... | ... | @@ -19,8 +23,8 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len |
| 19 | 23 | |
| 20 | 24 | /// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory. |
| 21 | 25 | /// Caller owns the result's fields. |
| 22 | /// After finishing work, call `free(allocator)`. | |
| 23 | pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { | |
| 26 | /// Returns memory allocated by `gpa` | |
| 27 | pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { | |
| 24 | 28 | if (builtin.os.tag != .windows) return error.NotFound; |
| 25 | 29 | |
| 26 | 30 | //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 | 33 | }; |
| 30 | 34 | defer roots_key.closeKey(); |
| 31 | 35 | |
| 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 | 37 | error.InstallationNotFound => null, |
| 34 | 38 | error.PathTooLong => null, |
| 35 | 39 | error.VersionTooLong => null, |
| 36 | 40 | error.OutOfMemory => return error.OutOfMemory, |
| 37 | 41 | }; |
| 38 | errdefer if (windows10sdk) |*w| w.free(allocator); | |
| 42 | errdefer if (windows10sdk) |*w| w.free(gpa); | |
| 39 | 43 | |
| 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 | 45 | error.InstallationNotFound => null, |
| 42 | 46 | error.PathTooLong => null, |
| 43 | 47 | error.VersionTooLong => null, |
| 44 | 48 | error.OutOfMemory => return error.OutOfMemory, |
| 45 | 49 | }; |
| 46 | errdefer if (windows81sdk) |*w| w.free(allocator); | |
| 50 | errdefer if (windows81sdk) |*w| w.free(gpa); | |
| 47 | 51 | |
| 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 | 53 | error.MsvcLibDirNotFound => null, |
| 50 | 54 | error.OutOfMemory => return error.OutOfMemory, |
| 51 | 55 | }; |
| 52 | errdefer allocator.free(msvc_lib_dir); | |
| 56 | errdefer gpa.free(msvc_lib_dir); | |
| 53 | 57 | |
| 54 | 58 | return .{ |
| 55 | 59 | .windows10sdk = windows10sdk, |
| ... | ... | @@ -58,15 +62,15 @@ pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutO |
| 58 | 62 | }; |
| 59 | 63 | } |
| 60 | 64 | |
| 61 | pub fn free(sdk: WindowsSdk, allocator: std.mem.Allocator) void { | |
| 65 | pub fn free(sdk: WindowsSdk, gpa: Allocator) void { | |
| 62 | 66 | if (sdk.windows10sdk) |*w10sdk| { |
| 63 | w10sdk.free(allocator); | |
| 67 | w10sdk.free(gpa); | |
| 64 | 68 | } |
| 65 | 69 | if (sdk.windows81sdk) |*w81sdk| { |
| 66 | w81sdk.free(allocator); | |
| 70 | w81sdk.free(gpa); | |
| 67 | 71 | } |
| 68 | 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 | } |
| 72 | 76 | |
| ... | ... | @@ -74,8 +78,9 @@ pub fn free(sdk: WindowsSdk, allocator: std.mem.Allocator) void { |
| 74 | 78 | /// and a version. Returns slice of version strings sorted in descending order. |
| 75 | 79 | /// Caller owns result. |
| 76 | 80 | fn iterateAndFilterByVersion( |
| 77 | iterator: *std.fs.Dir.Iterator, | |
| 78 | allocator: std.mem.Allocator, | |
| 81 | iterator: *Dir.Iterator, | |
| 82 | gpa: Allocator, | |
| 83 | io: Io, | |
| 79 | 84 | prefix: []const u8, |
| 80 | 85 | ) error{OutOfMemory}![][]const u8 { |
| 81 | 86 | const Version = struct { |
| ... | ... | @@ -92,15 +97,15 @@ fn iterateAndFilterByVersion( |
| 92 | 97 | std.mem.order(u8, lhs.build, rhs.build); |
| 93 | 98 | } |
| 94 | 99 | }; |
| 95 | var versions = std.array_list.Managed(Version).init(allocator); | |
| 96 | var dirs = std.array_list.Managed([]const u8).init(allocator); | |
| 100 | var versions = std.array_list.Managed(Version).init(gpa); | |
| 101 | var dirs = std.array_list.Managed([]const u8).init(gpa); | |
| 97 | 102 | defer { |
| 98 | 103 | versions.deinit(); |
| 99 | for (dirs.items) |filtered_dir| allocator.free(filtered_dir); | |
| 104 | for (dirs.items) |filtered_dir| gpa.free(filtered_dir); | |
| 100 | 105 | dirs.deinit(); |
| 101 | 106 | } |
| 102 | 107 | |
| 103 | iterate: while (iterator.next() catch null) |entry| { | |
| 108 | iterate: while (iterator.next(io) catch null) |entry| { | |
| 104 | 109 | if (entry.kind != .directory) continue; |
| 105 | 110 | if (!std.mem.startsWith(u8, entry.name, prefix)) continue; |
| 106 | 111 | |
| ... | ... | @@ -116,8 +121,8 @@ fn iterateAndFilterByVersion( |
| 116 | 121 | num.* = Version.parseNum(num_it.next() orelse break) orelse continue :iterate |
| 117 | 122 | else if (num_it.next()) |_| continue; |
| 118 | 123 | |
| 119 | const name = try allocator.dupe(u8, suffix); | |
| 120 | errdefer allocator.free(name); | |
| 124 | const name = try gpa.dupe(u8, suffix); | |
| 125 | errdefer gpa.free(name); | |
| 121 | 126 | if (underscore) |pos| version.build = name[pos + 1 ..]; |
| 122 | 127 | |
| 123 | 128 | try versions.append(version); |
| ... | ... | @@ -174,7 +179,7 @@ const RegistryWtf8 = struct { |
| 174 | 179 | |
| 175 | 180 | /// Get string from registry. |
| 176 | 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 | 183 | const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: { |
| 179 | 184 | var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; |
| 180 | 185 | const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable; |
| ... | ... | @@ -190,11 +195,11 @@ const RegistryWtf8 = struct { |
| 190 | 195 | }; |
| 191 | 196 | |
| 192 | 197 | const registry_wtf16le: RegistryWtf16Le = .{ .key = reg.key }; |
| 193 | const value_wtf16le = try registry_wtf16le.getString(allocator, subkey_wtf16le, value_name_wtf16le); | |
| 194 | defer allocator.free(value_wtf16le); | |
| 198 | const value_wtf16le = try registry_wtf16le.getString(gpa, subkey_wtf16le, value_name_wtf16le); | |
| 199 | defer gpa.free(value_wtf16le); | |
| 195 | 200 | |
| 196 | const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_wtf16le); | |
| 197 | errdefer allocator.free(value_wtf8); | |
| 201 | const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, value_wtf16le); | |
| 202 | errdefer gpa.free(value_wtf8); | |
| 198 | 203 | |
| 199 | 204 | return value_wtf8; |
| 200 | 205 | } |
| ... | ... | @@ -282,7 +287,7 @@ const RegistryWtf16Le = struct { |
| 282 | 287 | } |
| 283 | 288 | |
| 284 | 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 | 291 | var actual_type: windows.ULONG = undefined; |
| 287 | 292 | |
| 288 | 293 | // Calculating length to allocate |
| ... | ... | @@ -311,8 +316,8 @@ const RegistryWtf16Le = struct { |
| 311 | 316 | else => return error.NotAString, |
| 312 | 317 | } |
| 313 | 318 | |
| 314 | const value_wtf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable); | |
| 315 | errdefer allocator.free(value_wtf16le_buf); | |
| 319 | const value_wtf16le_buf: []u16 = try gpa.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable); | |
| 320 | errdefer gpa.free(value_wtf16le_buf); | |
| 316 | 321 | |
| 317 | 322 | return_code_int = windows.advapi32.RegGetValueW( |
| 318 | 323 | reg.key, |
| ... | ... | @@ -346,7 +351,7 @@ const RegistryWtf16Le = struct { |
| 346 | 351 | break :value_wtf16le std.mem.span(value_wtf16le_overestimated); |
| 347 | 352 | }; |
| 348 | 353 | |
| 349 | _ = allocator.resize(value_wtf16le_buf, value_wtf16le.len); | |
| 354 | _ = gpa.resize(value_wtf16le_buf, value_wtf16le.len); | |
| 350 | 355 | return value_wtf16le; |
| 351 | 356 | } |
| 352 | 357 | |
| ... | ... | @@ -414,88 +419,89 @@ pub const Installation = struct { |
| 414 | 419 | |
| 415 | 420 | /// Find path and version of Windows SDK. |
| 416 | 421 | /// Caller owns the result's fields. |
| 417 | /// After finishing work, call `free(allocator)`. | |
| 418 | 422 | fn find( |
| 419 | allocator: std.mem.Allocator, | |
| 423 | gpa: Allocator, | |
| 424 | io: Io, | |
| 420 | 425 | roots_key: RegistryWtf8, |
| 421 | 426 | roots_subkey: []const u8, |
| 422 | 427 | prefix: []const u8, |
| 423 | 428 | version_key_name: []const u8, |
| 424 | 429 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { |
| 425 | 430 | roots: { |
| 426 | const installation = findFromRoot(allocator, roots_key, roots_subkey, prefix) catch | |
| 431 | const installation = findFromRoot(gpa, io, roots_key, roots_subkey, prefix) catch | |
| 427 | 432 | break :roots; |
| 428 | 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 | 438 | if (installation.isValidVersion()) return installation; |
| 434 | installation.free(allocator); | |
| 439 | installation.free(gpa); | |
| 435 | 440 | } |
| 436 | 441 | return error.InstallationNotFound; |
| 437 | 442 | } |
| 438 | 443 | |
| 439 | 444 | fn findFromRoot( |
| 440 | allocator: std.mem.Allocator, | |
| 445 | gpa: Allocator, | |
| 446 | io: Io, | |
| 441 | 447 | roots_key: RegistryWtf8, |
| 442 | 448 | roots_subkey: []const u8, |
| 443 | 449 | prefix: []const u8, |
| 444 | 450 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { |
| 445 | 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 | 453 | error.NotAString => return error.InstallationNotFound, |
| 448 | 454 | error.ValueNameNotFound => return error.InstallationNotFound, |
| 449 | 455 | error.StringNotFound => return error.InstallationNotFound, |
| 450 | 456 | |
| 451 | 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)) { | |
| 454 | allocator.free(path_maybe_with_trailing_slash); | |
| 459 | if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) { | |
| 460 | gpa.free(path_maybe_with_trailing_slash); | |
| 455 | 461 | return error.PathTooLong; |
| 456 | 462 | } |
| 457 | 463 | |
| 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 | 465 | errdefer path.deinit(); |
| 460 | 466 | |
| 461 | 467 | // String might contain trailing slash, so trim it here |
| 462 | 468 | if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop(); |
| 463 | 469 | break :path try path.toOwnedSlice(); |
| 464 | 470 | }; |
| 465 | errdefer allocator.free(path); | |
| 471 | errdefer gpa.free(path); | |
| 466 | 472 | |
| 467 | 473 | const version = version: { |
| 468 | var buf: [std.fs.max_path_bytes]u8 = undefined; | |
| 474 | var buf: [Dir.max_path_bytes]u8 = undefined; | |
| 469 | 475 | const sdk_lib_dir_path = std.fmt.bufPrint(buf[0..], "{s}\\Lib\\", .{path}) catch |err| switch (err) { |
| 470 | 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; | |
| 473 | 479 | |
| 474 | 480 | // 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 | 482 | .iterate = true, |
| 477 | 483 | }) catch |err| switch (err) { |
| 478 | 484 | error.NameTooLong => return error.PathTooLong, |
| 479 | 485 | else => return error.InstallationNotFound, |
| 480 | 486 | }; |
| 481 | defer sdk_lib_dir.close(); | |
| 487 | defer sdk_lib_dir.close(io); | |
| 482 | 488 | |
| 483 | 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 | 491 | if (versions.len == 0) return error.InstallationNotFound; |
| 486 | 492 | defer { |
| 487 | for (versions[1..]) |version| allocator.free(version); | |
| 488 | allocator.free(versions); | |
| 493 | for (versions[1..]) |version| gpa.free(version); | |
| 494 | gpa.free(versions); | |
| 489 | 495 | } |
| 490 | 496 | break :version versions[0]; |
| 491 | 497 | }; |
| 492 | errdefer allocator.free(version); | |
| 498 | errdefer gpa.free(version); | |
| 493 | 499 | |
| 494 | 500 | return .{ .path = path, .version = version }; |
| 495 | 501 | } |
| 496 | 502 | |
| 497 | 503 | fn findFromInstallationFolder( |
| 498 | allocator: std.mem.Allocator, | |
| 504 | gpa: Allocator, | |
| 499 | 505 | version_key_name: []const u8, |
| 500 | 506 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { |
| 501 | 507 | var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined; |
| ... | ... | @@ -514,7 +520,7 @@ pub const Installation = struct { |
| 514 | 520 | defer key.closeKey(); |
| 515 | 521 | |
| 516 | 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 | 524 | error.NotAString => return error.InstallationNotFound, |
| 519 | 525 | error.ValueNameNotFound => return error.InstallationNotFound, |
| 520 | 526 | error.StringNotFound => return error.InstallationNotFound, |
| ... | ... | @@ -522,12 +528,12 @@ pub const Installation = struct { |
| 522 | 528 | error.OutOfMemory => return error.OutOfMemory, |
| 523 | 529 | }; |
| 524 | 530 | |
| 525 | if (path_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) { | |
| 526 | allocator.free(path_maybe_with_trailing_slash); | |
| 531 | if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) { | |
| 532 | gpa.free(path_maybe_with_trailing_slash); | |
| 527 | 533 | return error.PathTooLong; |
| 528 | 534 | } |
| 529 | 535 | |
| 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 | 537 | errdefer path.deinit(); |
| 532 | 538 | |
| 533 | 539 | // String might contain trailing slash, so trim it here |
| ... | ... | @@ -536,12 +542,12 @@ pub const Installation = struct { |
| 536 | 542 | const path_without_trailing_slash = try path.toOwnedSlice(); |
| 537 | 543 | break :path path_without_trailing_slash; |
| 538 | 544 | }; |
| 539 | errdefer allocator.free(path); | |
| 545 | errdefer gpa.free(path); | |
| 540 | 546 | |
| 541 | 547 | const version: []const u8 = version: { |
| 542 | 548 | |
| 543 | 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 | 551 | error.NotAString => return error.InstallationNotFound, |
| 546 | 552 | error.ValueNameNotFound => return error.InstallationNotFound, |
| 547 | 553 | error.StringNotFound => return error.InstallationNotFound, |
| ... | ... | @@ -549,11 +555,11 @@ pub const Installation = struct { |
| 549 | 555 | error.OutOfMemory => return error.OutOfMemory, |
| 550 | 556 | }; |
| 551 | 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 | 559 | return error.VersionTooLong; |
| 554 | 560 | } |
| 555 | 561 | |
| 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 | 563 | errdefer version.deinit(); |
| 558 | 564 | |
| 559 | 565 | try version.appendSlice(".0"); |
| ... | ... | @@ -561,14 +567,14 @@ pub const Installation = struct { |
| 561 | 567 | const version_with_0 = try version.toOwnedSlice(); |
| 562 | 568 | break :version version_with_0; |
| 563 | 569 | }; |
| 564 | errdefer allocator.free(version); | |
| 570 | errdefer gpa.free(version); | |
| 565 | 571 | |
| 566 | 572 | return .{ .path = path, .version = version }; |
| 567 | 573 | } |
| 568 | 574 | |
| 569 | 575 | /// Check whether this version is enumerated in registry. |
| 570 | 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 | 578 | const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ |
| 573 | 579 | windows_kits_reg_key, |
| 574 | 580 | installation.version, |
| ... | ... | @@ -597,21 +603,21 @@ pub const Installation = struct { |
| 597 | 603 | return (reg_value == 1); |
| 598 | 604 | } |
| 599 | 605 | |
| 600 | fn free(install: Installation, allocator: std.mem.Allocator) void { | |
| 601 | allocator.free(install.path); | |
| 602 | allocator.free(install.version); | |
| 606 | fn free(install: Installation, gpa: Allocator) void { | |
| 607 | gpa.free(install.path); | |
| 608 | gpa.free(install.version); | |
| 603 | 609 | } |
| 604 | 610 | }; |
| 605 | 611 | |
| 606 | 612 | const 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 | 614 | const vs_setup_key_path = "SOFTWARE\\Microsoft\\VisualStudio\\Setup"; |
| 609 | 615 | const vs_setup_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, vs_setup_key_path, .{}) catch |err| switch (err) { |
| 610 | 616 | error.KeyNotFound => return error.PathNotFound, |
| 611 | 617 | }; |
| 612 | 618 | defer vs_setup_key.closeKey(); |
| 613 | 619 | |
| 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 | 621 | error.NotAString, |
| 616 | 622 | error.ValueNameNotFound, |
| 617 | 623 | error.StringNotFound, |
| ... | ... | @@ -619,24 +625,24 @@ const MsvcLibDir = struct { |
| 619 | 625 | |
| 620 | 626 | error.OutOfMemory => return error.OutOfMemory, |
| 621 | 627 | }; |
| 622 | defer allocator.free(packages_path); | |
| 628 | defer gpa.free(packages_path); | |
| 623 | 629 | |
| 624 | if (!std.fs.path.isAbsolute(packages_path)) return error.PathNotFound; | |
| 630 | if (!Dir.path.isAbsolute(packages_path)) return error.PathNotFound; | |
| 625 | 631 | |
| 626 | const instances_path = try std.fs.path.join(allocator, &.{ packages_path, "_Instances" }); | |
| 627 | defer allocator.free(instances_path); | |
| 632 | const instances_path = try Dir.path.join(gpa, &.{ packages_path, "_Instances" }); | |
| 633 | defer gpa.free(instances_path); | |
| 628 | 634 | |
| 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 | } |
| 631 | 637 | |
| 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 | 639 | const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}"; |
| 634 | 640 | const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid, .{}) catch |err| switch (err) { |
| 635 | 641 | error.KeyNotFound => return error.PathNotFound, |
| 636 | 642 | }; |
| 637 | 643 | defer setup_config_key.closeKey(); |
| 638 | 644 | |
| 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 | 646 | error.NotAString, |
| 641 | 647 | error.ValueNameNotFound, |
| 642 | 648 | error.StringNotFound, |
| ... | ... | @@ -644,11 +650,11 @@ const MsvcLibDir = struct { |
| 644 | 650 | |
| 645 | 651 | error.OutOfMemory => return error.OutOfMemory, |
| 646 | 652 | }; |
| 647 | defer allocator.free(dll_path); | |
| 653 | defer gpa.free(dll_path); | |
| 648 | 654 | |
| 649 | if (!std.fs.path.isAbsolute(dll_path)) return error.PathNotFound; | |
| 655 | if (!Dir.path.isAbsolute(dll_path)) return error.PathNotFound; | |
| 650 | 656 | |
| 651 | var path_it = std.fs.path.componentIterator(dll_path); | |
| 657 | var path_it = Dir.path.componentIterator(dll_path); | |
| 652 | 658 | // the .dll filename |
| 653 | 659 | _ = path_it.last(); |
| 654 | 660 | const root_path = while (path_it.previous()) |dir_component| { |
| ... | ... | @@ -659,17 +665,17 @@ const MsvcLibDir = struct { |
| 659 | 665 | return error.PathNotFound; |
| 660 | 666 | }; |
| 661 | 667 | |
| 662 | const instances_path = try std.fs.path.join(allocator, &.{ root_path, "Packages", "_Instances" }); | |
| 663 | defer allocator.free(instances_path); | |
| 668 | const instances_path = try Dir.path.join(gpa, &.{ root_path, "Packages", "_Instances" }); | |
| 669 | defer gpa.free(instances_path); | |
| 664 | 670 | |
| 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 | } |
| 667 | 673 | |
| 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 | 675 | // First, try getting the packages cache path from the registry. |
| 670 | 676 | // This only seems to exist when the path is different from the default. |
| 671 | 677 | method1: { |
| 672 | return findInstancesDirViaSetup(allocator) catch |err| switch (err) { | |
| 678 | return findInstancesDirViaSetup(gpa, io) catch |err| switch (err) { | |
| 673 | 679 | error.OutOfMemory => |e| return e, |
| 674 | 680 | error.PathNotFound => break :method1, |
| 675 | 681 | }; |
| ... | ... | @@ -677,7 +683,7 @@ const MsvcLibDir = struct { |
| 677 | 683 | // Otherwise, try to get the path from the .dll that would have been |
| 678 | 684 | // loaded via COM for SetupConfiguration. |
| 679 | 685 | method2: { |
| 680 | return findInstancesDirViaCLSID(allocator) catch |err| switch (err) { | |
| 686 | return findInstancesDirViaCLSID(gpa, io) catch |err| switch (err) { | |
| 681 | 687 | error.OutOfMemory => |e| return e, |
| 682 | 688 | error.PathNotFound => break :method2, |
| 683 | 689 | }; |
| ... | ... | @@ -685,19 +691,19 @@ const MsvcLibDir = struct { |
| 685 | 691 | // If that can't be found, fall back to manually appending |
| 686 | 692 | // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA% |
| 687 | 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 | 695 | error.OutOfMemory => |e| return e, |
| 690 | 696 | error.InvalidWtf8 => unreachable, |
| 691 | 697 | error.EnvironmentVariableNotFound => break :method3, |
| 692 | 698 | }; |
| 693 | defer allocator.free(program_data); | |
| 699 | defer gpa.free(program_data); | |
| 694 | 700 | |
| 695 | if (!std.fs.path.isAbsolute(program_data)) break :method3; | |
| 701 | if (!Dir.path.isAbsolute(program_data)) break :method3; | |
| 696 | 702 | |
| 697 | const instances_path = try std.fs.path.join(allocator, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" }); | |
| 698 | defer allocator.free(instances_path); | |
| 703 | const instances_path = try Dir.path.join(gpa, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" }); | |
| 704 | defer gpa.free(instances_path); | |
| 699 | 705 | |
| 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 | 708 | return error.PathNotFound; |
| 703 | 709 | } |
| ... | ... | @@ -748,33 +754,33 @@ const MsvcLibDir = struct { |
| 748 | 754 | /// |
| 749 | 755 | /// The logic in this function is intended to match what ISetupConfiguration does |
| 750 | 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 | 758 | // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances` |
| 753 | 759 | // This will contain directories with names of instance IDs like 80a758ca, |
| 754 | 760 | // which will contain `state.json` files that have the version and |
| 755 | 761 | // installation directory. |
| 756 | var instances_dir = try findInstancesDir(allocator); | |
| 757 | defer instances_dir.close(); | |
| 762 | var instances_dir = try findInstancesDir(gpa, io); | |
| 763 | defer instances_dir.close(io); | |
| 758 | 764 | |
| 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 | 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); | |
| 762 | 768 | |
| 763 | 769 | var latest_version: u64 = 0; |
| 764 | 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 | 772 | if (entry.kind != .directory) continue; |
| 767 | 773 | |
| 768 | 774 | var writer: Writer = .fixed(&state_subpath_buf); |
| 769 | 775 | |
| 770 | 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 | 778 | writer.writeAll("state.json") catch unreachable; |
| 773 | 779 | |
| 774 | const json_contents = instances_dir.readFileAlloc(writer.buffered(), allocator, .limited(std.math.maxInt(usize))) catch continue; | |
| 775 | defer allocator.free(json_contents); | |
| 780 | const json_contents = instances_dir.readFileAlloc(io, writer.buffered(), gpa, .limited(std.math.maxInt(usize))) catch continue; | |
| 781 | defer gpa.free(json_contents); | |
| 776 | 782 | |
| 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 | 784 | defer parsed.deinit(); |
| 779 | 785 | |
| 780 | 786 | if (parsed.value != .object) continue; |
| ... | ... | @@ -791,35 +797,40 @@ const MsvcLibDir = struct { |
| 791 | 797 | const installation_path = parsed.value.object.get("installationPath") orelse continue; |
| 792 | 798 | if (installation_path != .string) continue; |
| 793 | 799 | |
| 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 | 801 | error.OutOfMemory => |e| return e, |
| 796 | 802 | error.PathNotFound => continue, |
| 797 | 803 | }; |
| 798 | defer allocator.free(lib_dir_path); | |
| 804 | defer gpa.free(lib_dir_path); | |
| 799 | 805 | |
| 800 | 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 | 808 | latest_version = parsed_version; |
| 803 | 809 | } |
| 804 | 810 | |
| 805 | 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 | } |
| 808 | 814 | |
| 809 | fn libDirFromInstallationPath(allocator: std.mem.Allocator, installation_path: []const u8, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { | |
| 810 | var lib_dir_buf = try std.array_list.Managed(u8).initCapacity(allocator, installation_path.len + 64); | |
| 815 | fn libDirFromInstallationPath( | |
| 816 | gpa: Allocator, | |
| 817 | io: Io, | |
| 818 | installation_path: []const u8, | |
| 819 | arch: std.Target.Cpu.Arch, | |
| 820 | ) error{ OutOfMemory, PathNotFound }![]const u8 { | |
| 821 | var lib_dir_buf = try std.array_list.Managed(u8).initCapacity(gpa, installation_path.len + 64); | |
| 811 | 822 | errdefer lib_dir_buf.deinit(); |
| 812 | 823 | |
| 813 | 824 | lib_dir_buf.appendSliceAssumeCapacity(installation_path); |
| 814 | 825 | |
| 815 | if (!std.fs.path.isSep(lib_dir_buf.getLast())) { | |
| 826 | if (!Dir.path.isSep(lib_dir_buf.getLast())) { | |
| 816 | 827 | try lib_dir_buf.append('\\'); |
| 817 | 828 | } |
| 818 | 829 | const installation_path_with_trailing_sep_len = lib_dir_buf.items.len; |
| 819 | 830 | |
| 820 | 831 | try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); |
| 821 | 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 | 834 | return error.PathNotFound; |
| 824 | 835 | }; |
| 825 | 836 | var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n"); |
| ... | ... | @@ -837,7 +848,7 @@ const MsvcLibDir = struct { |
| 837 | 848 | else => unreachable, |
| 838 | 849 | }); |
| 839 | 850 | |
| 840 | if (!verifyLibDir(lib_dir_buf.items)) { | |
| 851 | if (!verifyLibDir(io, lib_dir_buf.items)) { | |
| 841 | 852 | return error.PathNotFound; |
| 842 | 853 | } |
| 843 | 854 | |
| ... | ... | @@ -845,64 +856,64 @@ const MsvcLibDir = struct { |
| 845 | 856 | } |
| 846 | 857 | |
| 847 | 858 | // 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 { | |
| 849 | 860 | |
| 850 | 861 | // %localappdata%\Microsoft\VisualStudio\ |
| 851 | 862 | // %appdata%\Local\Microsoft\VisualStudio\ |
| 852 | const visualstudio_folder_path = std.fs.getAppDataDir(allocator, "Microsoft\\VisualStudio\\") catch return error.PathNotFound; | |
| 853 | defer allocator.free(visualstudio_folder_path); | |
| 863 | const visualstudio_folder_path = std.fs.getAppDataDir(gpa, "Microsoft\\VisualStudio\\") catch return error.PathNotFound; | |
| 864 | defer gpa.free(visualstudio_folder_path); | |
| 854 | 865 | |
| 855 | 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 | 868 | // enumerate folders that contain `privateregistry.bin`, looking for all versions |
| 858 | 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 | 871 | .iterate = true, |
| 861 | 872 | }) catch return error.PathNotFound; |
| 862 | defer visualstudio_folder.close(); | |
| 873 | defer visualstudio_folder.close(io); | |
| 863 | 874 | |
| 864 | 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 | 878 | defer { |
| 868 | for (vs_versions) |vs_version| allocator.free(vs_version); | |
| 869 | allocator.free(vs_versions); | |
| 879 | for (vs_versions) |vs_version| gpa.free(vs_version); | |
| 880 | gpa.free(vs_versions); | |
| 870 | 881 | } |
| 871 | 882 | var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined; |
| 872 | 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; | |
| 874 | defer allocator.free(privateregistry_absolute_path); | |
| 875 | if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue; | |
| 884 | const privateregistry_absolute_path = Dir.path.join(gpa, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue; | |
| 885 | defer gpa.free(privateregistry_absolute_path); | |
| 886 | if (!Dir.path.isAbsolute(privateregistry_absolute_path)) continue; | |
| 876 | 887 | |
| 877 | 888 | const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue; |
| 878 | 889 | defer visualstudio_registry.closeKey(); |
| 879 | 890 | |
| 880 | 891 | const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable; |
| 881 | 892 | |
| 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 | 894 | error.OutOfMemory => return error.OutOfMemory, |
| 884 | 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 length | |
| 887 | allocator.free(source_directories_value); | |
| 897 | if (source_directories_value.len > (Dir.max_path_bytes * 30)) { // note(bratishkaerik): guessing from the fact that on my computer it has 15 paths and at least some of them are not of max length | |
| 898 | gpa.free(source_directories_value); | |
| 888 | 899 | continue; |
| 889 | 900 | } |
| 890 | 901 | |
| 891 | 902 | break :source_directories source_directories_value; |
| 892 | 903 | } else return error.PathNotFound; |
| 893 | defer allocator.free(source_directories); | |
| 904 | defer gpa.free(source_directories); | |
| 894 | 905 | |
| 895 | 906 | var source_directories_split = std.mem.splitScalar(u8, source_directories, ';'); |
| 896 | 907 | |
| 897 | 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()); | |
| 899 | 910 | |
| 900 | if (msvc_include_dir_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(msvc_include_dir_maybe_with_trailing_slash)) { | |
| 901 | allocator.free(msvc_include_dir_maybe_with_trailing_slash); | |
| 911 | if (msvc_include_dir_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(msvc_include_dir_maybe_with_trailing_slash)) { | |
| 912 | gpa.free(msvc_include_dir_maybe_with_trailing_slash); | |
| 902 | 913 | return error.PathNotFound; |
| 903 | 914 | } |
| 904 | 915 | |
| 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 | 917 | errdefer msvc_dir.deinit(); |
| 907 | 918 | |
| 908 | 919 | // String might contain trailing slash, so trim it here |
| ... | ... | @@ -924,19 +935,19 @@ const MsvcLibDir = struct { |
| 924 | 935 | const msvc_dir_with_arch = try msvc_dir.toOwnedSlice(); |
| 925 | 936 | break :msvc_dir msvc_dir_with_arch; |
| 926 | 937 | }; |
| 927 | errdefer allocator.free(msvc_dir); | |
| 938 | errdefer gpa.free(msvc_dir); | |
| 928 | 939 | |
| 929 | if (!verifyLibDir(msvc_dir)) { | |
| 940 | if (!verifyLibDir(io, msvc_dir)) { | |
| 930 | 941 | return error.PathNotFound; |
| 931 | 942 | } |
| 932 | 943 | |
| 933 | 944 | return msvc_dir; |
| 934 | 945 | } |
| 935 | 946 | |
| 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 | 948 | var base_path: std.array_list.Managed(u8) = base_path: { |
| 938 | 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 | 951 | error.OutOfMemory => return error.OutOfMemory, |
| 941 | 952 | else => break :try_env, |
| 942 | 953 | }; |
| ... | ... | @@ -944,8 +955,8 @@ const MsvcLibDir = struct { |
| 944 | 955 | |
| 945 | 956 | if (env_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| { |
| 946 | 957 | if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env; |
| 947 | if (!std.fs.path.isAbsolute(VS140COMNTOOLS)) break :try_env; | |
| 948 | var list = std.array_list.Managed(u8).init(allocator); | |
| 958 | if (!Dir.path.isAbsolute(VS140COMNTOOLS)) break :try_env; | |
| 959 | var list = std.array_list.Managed(u8).init(gpa); | |
| 949 | 960 | errdefer list.deinit(); |
| 950 | 961 | |
| 951 | 962 | try list.appendSlice(VS140COMNTOOLS); // C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools |
| ... | ... | @@ -959,17 +970,17 @@ const MsvcLibDir = struct { |
| 959 | 970 | const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", .{ .wow64_32 = true }) catch return error.PathNotFound; |
| 960 | 971 | defer vs7_key.closeKey(); |
| 961 | 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 | 974 | error.OutOfMemory => return error.OutOfMemory, |
| 964 | 975 | else => break :try_vs7_key, |
| 965 | 976 | }; |
| 966 | 977 | |
| 967 | if (path_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) { | |
| 968 | allocator.free(path_maybe_with_trailing_slash); | |
| 978 | if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) { | |
| 979 | gpa.free(path_maybe_with_trailing_slash); | |
| 969 | 980 | break :try_vs7_key; |
| 970 | 981 | } |
| 971 | 982 | |
| 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 | 984 | errdefer path.deinit(); |
| 974 | 985 | |
| 975 | 986 | // String might contain trailing slash, so trim it here |
| ... | ... | @@ -989,7 +1000,7 @@ const MsvcLibDir = struct { |
| 989 | 1000 | else => unreachable, |
| 990 | 1001 | }); |
| 991 | 1002 | |
| 992 | if (!verifyLibDir(base_path.items)) { | |
| 1003 | if (!verifyLibDir(io, base_path.items)) { | |
| 993 | 1004 | return error.PathNotFound; |
| 994 | 1005 | } |
| 995 | 1006 | |
| ... | ... | @@ -997,13 +1008,13 @@ const MsvcLibDir = struct { |
| 997 | 1008 | return full_path; |
| 998 | 1009 | } |
| 999 | 1010 | |
| 1000 | fn verifyLibDir(lib_dir_path: []const u8) bool { | |
| 1001 | std.debug.assert(std.fs.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*` | |
| 1011 | fn verifyLibDir(io: Io, lib_dir_path: []const u8) bool { | |
| 1012 | std.debug.assert(Dir.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*` | |
| 1002 | 1013 | |
| 1003 | var dir = std.fs.openDirAbsolute(lib_dir_path, .{}) catch return false; | |
| 1004 | defer dir.close(); | |
| 1014 | var dir = Dir.openDirAbsolute(io, lib_dir_path, .{}) catch return false; | |
| 1015 | defer dir.close(io); | |
| 1005 | 1016 | |
| 1006 | const stat = dir.statFile("vcruntime.lib") catch return false; | |
| 1017 | const stat = dir.statFile(io, "vcruntime.lib", .{}) catch return false; | |
| 1007 | 1018 | if (stat.kind != .file) |
| 1008 | 1019 | return false; |
| 1009 | 1020 | |
| ... | ... | @@ -1012,18 +1023,18 @@ const MsvcLibDir = struct { |
| 1012 | 1023 | |
| 1013 | 1024 | /// Find path to MSVC's `lib/` directory. |
| 1014 | 1025 | /// Caller owns the result. |
| 1015 | pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { | |
| 1016 | const full_path = MsvcLibDir.findViaCOM(allocator, arch) catch |err1| switch (err1) { | |
| 1026 | pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { | |
| 1027 | const full_path = MsvcLibDir.findViaCOM(gpa, io, arch) catch |err1| switch (err1) { | |
| 1017 | 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 | 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 | 1032 | error.OutOfMemory => return error.OutOfMemory, |
| 1022 | 1033 | error.PathNotFound => return error.MsvcLibDirNotFound, |
| 1023 | 1034 | }, |
| 1024 | 1035 | }, |
| 1025 | 1036 | }; |
| 1026 | errdefer allocator.free(full_path); | |
| 1037 | errdefer gpa.free(full_path); | |
| 1027 | 1038 | |
| 1028 | 1039 | return full_path; |
| 1029 | 1040 | } |
lib/std/zig/Zir.zig+4-4| ... | ... | @@ -11,9 +11,11 @@ |
| 11 | 11 | //! * In the future, possibly inline assembly, which needs to get parsed and |
| 12 | 12 | //! handled by the codegen backend, and errors reported there. However for now, |
| 13 | 13 | //! inline assembly is not an exception. |
| 14 | const Zir = @This(); | |
| 15 | const builtin = @import("builtin"); | |
| 14 | 16 | |
| 15 | 17 | const std = @import("std"); |
| 16 | const builtin = @import("builtin"); | |
| 18 | const Io = std.Io; | |
| 17 | 19 | const mem = std.mem; |
| 18 | 20 | const Allocator = std.mem.Allocator; |
| 19 | 21 | const assert = std.debug.assert; |
| ... | ... | @@ -21,8 +23,6 @@ const BigIntConst = std.math.big.int.Const; |
| 21 | 23 | const BigIntMutable = std.math.big.int.Mutable; |
| 22 | 24 | const Ast = std.zig.Ast; |
| 23 | 25 | |
| 24 | const Zir = @This(); | |
| 25 | ||
| 26 | 26 | instructions: std.MultiArrayList(Inst).Slice, |
| 27 | 27 | /// In order to store references to strings in fewer bytes, we copy all |
| 28 | 28 | /// string bytes into here. String bytes can be null. It is up to whomever |
| ... | ... | @@ -45,7 +45,7 @@ pub const Header = extern struct { |
| 45 | 45 | /// it's essentially free to have a zero field here and makes the warning go away, |
| 46 | 46 | /// making it more likely that following Valgrind warnings will be taken seriously. |
| 47 | 47 | unused: u32 = 0, |
| 48 | stat_inode: std.fs.File.INode, | |
| 48 | stat_inode: Io.File.INode, | |
| 49 | 49 | stat_size: u64, |
| 50 | 50 | stat_mtime: i128, |
| 51 | 51 | }; |
lib/std/zig/Zoir.zig+8-7| ... | ... | @@ -1,6 +1,13 @@ |
| 1 | 1 | //! Zig Object Intermediate Representation. |
| 2 | 2 | //! Simplified AST for the ZON (Zig Object Notation) format. |
| 3 | 3 | //! `ZonGen` converts `Ast` to `Zoir`. |
| 4 | const Zoir = @This(); | |
| 5 | ||
| 6 | const std = @import("std"); | |
| 7 | const Io = std.Io; | |
| 8 | const assert = std.debug.assert; | |
| 9 | const Allocator = std.mem.Allocator; | |
| 10 | const Ast = std.zig.Ast; | |
| 4 | 11 | |
| 5 | 12 | nodes: std.MultiArrayList(Node.Repr).Slice, |
| 6 | 13 | extra: []u32, |
| ... | ... | @@ -25,7 +32,7 @@ pub const Header = extern struct { |
| 25 | 32 | /// making it more likely that following Valgrind warnings will be taken seriously. |
| 26 | 33 | unused: u64 = 0, |
| 27 | 34 | |
| 28 | stat_inode: std.fs.File.INode, | |
| 35 | stat_inode: Io.File.INode, | |
| 29 | 36 | stat_size: u64, |
| 30 | 37 | stat_mtime: i128, |
| 31 | 38 | |
| ... | ... | @@ -254,9 +261,3 @@ pub const CompileError = extern struct { |
| 254 | 261 | assert(std.meta.hasUniqueRepresentation(Note)); |
| 255 | 262 | } |
| 256 | 263 | }; |
| 257 | ||
| 258 | const std = @import("std"); | |
| 259 | const assert = std.debug.assert; | |
| 260 | const Allocator = std.mem.Allocator; | |
| 261 | const Ast = std.zig.Ast; | |
| 262 | const Zoir = @This(); |
lib/std/zig/llvm/Builder.zig+16-13| ... | ... | @@ -1,14 +1,17 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const Builder = @This(); | |
| 3 | ||
| 1 | 4 | const std = @import("../../std.zig"); |
| 5 | const Io = std.Io; | |
| 2 | 6 | const Allocator = std.mem.Allocator; |
| 3 | 7 | const assert = std.debug.assert; |
| 4 | const bitcode_writer = @import("bitcode_writer.zig"); | |
| 5 | const Builder = @This(); | |
| 6 | const builtin = @import("builtin"); | |
| 7 | 8 | const DW = std.dwarf; |
| 8 | const ir = @import("ir.zig"); | |
| 9 | 9 | const log = std.log.scoped(.llvm); |
| 10 | 10 | const Writer = std.Io.Writer; |
| 11 | 11 | |
| 12 | const bitcode_writer = @import("bitcode_writer.zig"); | |
| 13 | const ir = @import("ir.zig"); | |
| 14 | ||
| 12 | 15 | gpa: Allocator, |
| 13 | 16 | strip: bool, |
| 14 | 17 | |
| ... | ... | @@ -9573,21 +9576,21 @@ pub fn asmValue( |
| 9573 | 9576 | return (try self.asmConst(ty, info, assembly, constraints)).toValue(); |
| 9574 | 9577 | } |
| 9575 | 9578 | |
| 9576 | pub fn dump(b: *Builder) void { | |
| 9579 | pub fn dump(b: *Builder, io: Io) void { | |
| 9577 | 9580 | var buffer: [4000]u8 = undefined; |
| 9578 | const stderr: std.fs.File = .stderr(); | |
| 9579 | b.printToFile(stderr, &buffer) catch {}; | |
| 9581 | const stderr: Io.File = .stderr(); | |
| 9582 | b.printToFile(io, stderr, &buffer) catch {}; | |
| 9580 | 9583 | } |
| 9581 | 9584 | |
| 9582 | pub fn printToFilePath(b: *Builder, dir: std.fs.Dir, path: []const u8) !void { | |
| 9585 | pub fn printToFilePath(b: *Builder, io: Io, dir: Io.Dir, path: []const u8) !void { | |
| 9583 | 9586 | var buffer: [4000]u8 = undefined; |
| 9584 | const file = try dir.createFile(path, .{}); | |
| 9585 | defer file.close(); | |
| 9586 | try b.printToFile(file, &buffer); | |
| 9587 | const file = try dir.createFile(io, path, .{}); | |
| 9588 | defer file.close(io); | |
| 9589 | try b.printToFile(io, file, &buffer); | |
| 9587 | 9590 | } |
| 9588 | 9591 | |
| 9589 | pub fn printToFile(b: *Builder, file: std.fs.File, buffer: []u8) !void { | |
| 9590 | var fw = file.writer(buffer); | |
| 9592 | pub fn printToFile(b: *Builder, io: Io, file: Io.File, buffer: []u8) !void { | |
| 9593 | var fw = file.writer(io, buffer); | |
| 9591 | 9594 | try print(b, &fw.interface); |
| 9592 | 9595 | try fw.interface.flush(); |
| 9593 | 9596 | } |
lib/std/zig/parser_test.zig+27-18| ... | ... | @@ -1,7 +1,6 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const mem = std.mem; | |
| 3 | const print = std.debug.print; | |
| 4 | const maxInt = std.math.maxInt; | |
| 2 | const Io = std.Io; | |
| 3 | const Allocator = std.mem.Allocator; | |
| 5 | 4 | |
| 6 | 5 | test "zig fmt: remove extra whitespace at start and end of file with comment between" { |
| 7 | 6 | try testTransform( |
| ... | ... | @@ -4539,7 +4538,7 @@ test "zig fmt: Only indent multiline string literals in function calls" { |
| 4539 | 4538 | test "zig fmt: Don't add extra newline after if" { |
| 4540 | 4539 | try testCanonical( |
| 4541 | 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 | 4542 | \\ return; |
| 4544 | 4543 | \\ } |
| 4545 | 4544 | \\} |
| ... | ... | @@ -6332,54 +6331,64 @@ test "ampersand" { |
| 6332 | 6331 | |
| 6333 | 6332 | var fixed_buffer_mem: [100 * 1024]u8 = undefined; |
| 6334 | 6333 | |
| 6335 | fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 { | |
| 6334 | fn testParse(io: Io, source: [:0]const u8, allocator: Allocator, anything_changed: *bool) ![]u8 { | |
| 6336 | 6335 | var buffer: [64]u8 = undefined; |
| 6337 | const stderr, _ = std.debug.lockStderrWriter(&buffer); | |
| 6338 | defer std.debug.unlockStderrWriter(); | |
| 6336 | const stderr = try io.lockStderr(&buffer, null); | |
| 6337 | defer io.unlockStderr(); | |
| 6338 | const writer = &stderr.file_writer.interface; | |
| 6339 | 6339 | |
| 6340 | 6340 | var tree = try std.zig.Ast.parse(allocator, source, .zig); |
| 6341 | 6341 | defer tree.deinit(allocator); |
| 6342 | 6342 | |
| 6343 | 6343 | for (tree.errors) |parse_error| { |
| 6344 | 6344 | const loc = tree.tokenLocation(0, parse_error.token); |
| 6345 | try stderr.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); | |
| 6346 | try tree.renderError(parse_error, stderr); | |
| 6347 | try stderr.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); | |
| 6345 | try writer.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); | |
| 6346 | try tree.renderError(parse_error, writer); | |
| 6347 | try writer.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); | |
| 6348 | 6348 | { |
| 6349 | 6349 | var i: usize = 0; |
| 6350 | 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 | 6357 | if (tree.errors.len != 0) { |
| 6358 | 6358 | return error.ParseError; |
| 6359 | 6359 | } |
| 6360 | 6360 | |
| 6361 | 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 | 6363 | return formatted; |
| 6364 | 6364 | } |
| 6365 | fn testTransformImpl(allocator: mem.Allocator, fba: *std.heap.FixedBufferAllocator, source: [:0]const u8, expected_source: []const u8) !void { | |
| 6365 | fn 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 | 6372 | // reset the fixed buffer allocator each run so that it can be re-used for each |
| 6367 | 6373 | // iteration of the failing index |
| 6368 | 6374 | fba.reset(); |
| 6369 | 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 | 6377 | try std.testing.expectEqualStrings(expected_source, result_source); |
| 6372 | 6378 | const changes_expected = source.ptr != expected_source.ptr; |
| 6373 | 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 | 6381 | return error.TestFailed; |
| 6376 | 6382 | } |
| 6377 | 6383 | try std.testing.expect(anything_changed == changes_expected); |
| 6378 | 6384 | allocator.free(result_source); |
| 6379 | 6385 | } |
| 6380 | 6386 | fn testTransform(source: [:0]const u8, expected_source: []const u8) !void { |
| 6387 | const io = std.testing.io; | |
| 6381 | 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 | } |
| 6384 | 6393 | fn testCanonical(source: [:0]const u8) !void { |
| 6385 | 6394 | return testTransform(source, source); |
lib/std/zig/perf_test.zig+2-1| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const mem = std.mem; |
| 3 | 4 | const Tokenizer = std.zig.Tokenizer; |
| 4 | 5 | const fmtIntSizeBin = std.fmt.fmtIntSizeBin; |
| ... | ... | @@ -22,7 +23,7 @@ pub fn main() !void { |
| 22 | 23 | const bytes_per_sec = @as(u64, @intFromFloat(@floor(bytes_per_sec_float))); |
| 23 | 24 | |
| 24 | 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 | 27 | const stdout = &stdout_writer.interface; |
| 27 | 28 | try stdout.print("parsing speed: {Bi:.2}/s, {Bi:.2} used \n", .{ bytes_per_sec, memory_used }); |
| 28 | 29 | try stdout.flush(); |
lib/std/zig/system.zig+23-21| ... | ... | @@ -1,11 +1,12 @@ |
| 1 | 1 | const builtin = @import("builtin"); |
| 2 | const native_endian = builtin.cpu.arch.endian(); | |
| 3 | ||
| 2 | 4 | const std = @import("../std.zig"); |
| 3 | 5 | const mem = std.mem; |
| 4 | 6 | const elf = std.elf; |
| 5 | 7 | const fs = std.fs; |
| 6 | 8 | const assert = std.debug.assert; |
| 7 | 9 | const Target = std.Target; |
| 8 | const native_endian = builtin.cpu.arch.endian(); | |
| 9 | 10 | const posix = std.posix; |
| 10 | 11 | const Io = std.Io; |
| 11 | 12 | |
| ... | ... | @@ -39,6 +40,7 @@ pub const GetExternalExecutorOptions = struct { |
| 39 | 40 | /// Return whether or not the given host is capable of running executables of |
| 40 | 41 | /// the other target. |
| 41 | 42 | pub fn getExternalExecutor( |
| 43 | io: Io, | |
| 42 | 44 | host: *const std.Target, |
| 43 | 45 | candidate: *const std.Target, |
| 44 | 46 | options: GetExternalExecutorOptions, |
| ... | ... | @@ -69,7 +71,7 @@ pub fn getExternalExecutor( |
| 69 | 71 | if (os_match and cpu_ok) native: { |
| 70 | 72 | if (options.link_libc) { |
| 71 | 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 | 75 | bad_result = .{ .bad_dl = candidate_dl }; |
| 74 | 76 | break :native; |
| 75 | 77 | }; |
| ... | ... | @@ -209,7 +211,6 @@ pub const DetectError = error{ |
| 209 | 211 | DeviceBusy, |
| 210 | 212 | OSVersionDetectionFail, |
| 211 | 213 | Unexpected, |
| 212 | ProcessNotFound, | |
| 213 | 214 | } || Io.Cancelable; |
| 214 | 215 | |
| 215 | 216 | /// 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 | 248 | os.version_range.windows.min = detected_version; |
| 248 | 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 | 252 | .freebsd, .netbsd, .dragonfly => { |
| 252 | 253 | const key = switch (builtin.target.os.tag) { |
| 253 | 254 | .freebsd => "kern.osreldate", |
| ... | ... | @@ -322,7 +323,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { |
| 322 | 323 | error.Unexpected => return error.OSVersionDetectionFail, |
| 323 | 324 | }; |
| 324 | 325 | |
| 325 | if (Target.Query.parseVersion(buf[0..len :0])) |ver| { | |
| 326 | if (Target.Query.parseVersion(buf[0 .. len - 1 :0])) |ver| { | |
| 326 | 327 | assert(ver.build == null); |
| 327 | 328 | assert(ver.pre == null); |
| 328 | 329 | os.version_range.semver.min = ver; |
| ... | ... | @@ -422,7 +423,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { |
| 422 | 423 | error.SocketUnconnected => return error.Unexpected, |
| 423 | 424 | |
| 424 | 425 | error.AccessDenied, |
| 425 | error.ProcessNotFound, | |
| 426 | 426 | error.SymLinkLoop, |
| 427 | 427 | error.ProcessFdQuotaExceeded, |
| 428 | 428 | error.SystemFdQuotaExceeded, |
| ... | ... | @@ -553,7 +553,6 @@ pub const AbiAndDynamicLinkerFromFileError = error{ |
| 553 | 553 | SystemResources, |
| 554 | 554 | ProcessFdQuotaExceeded, |
| 555 | 555 | SystemFdQuotaExceeded, |
| 556 | ProcessNotFound, | |
| 557 | 556 | IsDir, |
| 558 | 557 | WouldBlock, |
| 559 | 558 | InputOutput, |
| ... | ... | @@ -693,8 +692,10 @@ fn abiAndDynamicLinkerFromFile( |
| 693 | 692 | |
| 694 | 693 | // So far, no luck. Next we try to see if the information is |
| 695 | 694 | // present in the symlink data for the dynamic linker path. |
| 696 | var link_buf: [posix.PATH_MAX]u8 = undefined; | |
| 697 | const link_name = posix.readlink(dl_path, &link_buf) catch |err| switch (err) { | |
| 695 | var link_buffer: [posix.PATH_MAX]u8 = undefined; | |
| 696 | const link_name = if (Io.Dir.readLinkAbsolute(io, dl_path, &link_buffer)) |n| | |
| 697 | link_buffer[0..n] | |
| 698 | else |err| switch (err) { | |
| 698 | 699 | error.NameTooLong => unreachable, |
| 699 | 700 | error.BadPathName => unreachable, // Windows only |
| 700 | 701 | error.UnsupportedReparsePointType => unreachable, // Windows only |
| ... | ... | @@ -711,6 +712,7 @@ fn abiAndDynamicLinkerFromFile( |
| 711 | 712 | error.SystemResources, |
| 712 | 713 | error.FileSystem, |
| 713 | 714 | error.SymLinkLoop, |
| 715 | error.Canceled, | |
| 714 | 716 | error.Unexpected, |
| 715 | 717 | => |e| return e, |
| 716 | 718 | }; |
| ... | ... | @@ -786,7 +788,9 @@ test glibcVerFromLinkName { |
| 786 | 788 | } |
| 787 | 789 | |
| 788 | 790 | fn 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 | 794 | error.NameTooLong => return error.Unexpected, |
| 791 | 795 | error.BadPathName => return error.Unexpected, |
| 792 | 796 | error.DeviceBusy => return error.Unexpected, |
| ... | ... | @@ -805,7 +809,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { |
| 805 | 809 | error.Unexpected => |e| return e, |
| 806 | 810 | error.Canceled => |e| return e, |
| 807 | 811 | }; |
| 808 | defer dir.close(); | |
| 812 | defer dir.close(io); | |
| 809 | 813 | |
| 810 | 814 | // Now we have a candidate for the path to libc shared object. In |
| 811 | 815 | // 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 | 819 | // .dynstr section, and finding the max version number of symbols |
| 816 | 820 | // that start with "GLIBC_2.". |
| 817 | 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 | 823 | error.NameTooLong => return error.Unexpected, |
| 820 | 824 | error.BadPathName => return error.Unexpected, |
| 821 | 825 | error.PipeBusy => return error.Unexpected, // Windows-only |
| 822 | 826 | error.SharingViolation => return error.Unexpected, // Windows-only |
| 823 | 827 | error.NetworkNotFound => return error.Unexpected, // Windows-only |
| 824 | 828 | 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 | 830 | error.NoSpaceLeft => return error.Unexpected, // read-only |
| 827 | 831 | error.PathAlreadyExists => return error.Unexpected, // read-only |
| 828 | 832 | error.DeviceBusy => return error.Unexpected, // read-only |
| ... | ... | @@ -837,7 +841,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { |
| 837 | 841 | error.NotDir => return error.GLibCNotFound, |
| 838 | 842 | error.IsDir => return error.GLibCNotFound, |
| 839 | 843 | |
| 840 | error.ProcessNotFound => |e| return e, | |
| 841 | 844 | error.ProcessFdQuotaExceeded => |e| return e, |
| 842 | 845 | error.SystemFdQuotaExceeded => |e| return e, |
| 843 | 846 | error.SystemResources => |e| return e, |
| ... | ... | @@ -845,11 +848,11 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { |
| 845 | 848 | error.Unexpected => |e| return e, |
| 846 | 849 | error.Canceled => |e| return e, |
| 847 | 850 | }; |
| 848 | defer file.close(); | |
| 851 | defer file.close(io); | |
| 849 | 852 | |
| 850 | 853 | // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system. |
| 851 | 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); | |
| 853 | 856 | |
| 854 | 857 | return glibcVerFromSoFile(&file_reader) catch |err| switch (err) { |
| 855 | 858 | error.InvalidElfMagic, |
| ... | ... | @@ -1024,14 +1027,14 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ |
| 1024 | 1027 | }; |
| 1025 | 1028 | |
| 1026 | 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 | 1031 | error.NoSpaceLeft => return error.Unexpected, |
| 1029 | 1032 | error.NameTooLong => return error.Unexpected, |
| 1030 | 1033 | error.PathAlreadyExists => return error.Unexpected, |
| 1031 | 1034 | error.SharingViolation => return error.Unexpected, |
| 1032 | 1035 | error.BadPathName => return error.Unexpected, |
| 1033 | 1036 | error.PipeBusy => return error.Unexpected, |
| 1034 | error.FileLocksNotSupported => return error.Unexpected, | |
| 1037 | error.FileLocksUnsupported => return error.Unexpected, | |
| 1035 | 1038 | error.FileBusy => return error.Unexpected, // opened without write permissions |
| 1036 | 1039 | error.AntivirusInterference => return error.Unexpected, // Windows-only error |
| 1037 | 1040 | |
| ... | ... | @@ -1049,9 +1052,9 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ |
| 1049 | 1052 | else => |e| return e, |
| 1050 | 1053 | }; |
| 1051 | 1054 | var is_elf_file = false; |
| 1052 | defer if (!is_elf_file) file.close(); | |
| 1055 | defer if (!is_elf_file) file.close(io); | |
| 1053 | 1056 | |
| 1054 | file_reader = .initAdapted(file, io, &file_reader_buffer); | |
| 1057 | file_reader = .init(file, io, &file_reader_buffer); | |
| 1055 | 1058 | file_name = undefined; // it aliases file_reader_buffer |
| 1056 | 1059 | |
| 1057 | 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 | 1104 | error.SymLinkLoop, |
| 1102 | 1105 | error.ProcessFdQuotaExceeded, |
| 1103 | 1106 | error.SystemFdQuotaExceeded, |
| 1104 | error.ProcessNotFound, | |
| 1105 | 1107 | error.Canceled, |
| 1106 | 1108 | => |e| return e, |
| 1107 | 1109 |
lib/std/zig/system/NativePaths.zig+7-6| ... | ... | @@ -1,11 +1,12 @@ |
| 1 | const std = @import("../../std.zig"); | |
| 1 | const NativePaths = @This(); | |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | ||
| 4 | const std = @import("../../std.zig"); | |
| 5 | const Io = std.Io; | |
| 3 | 6 | const Allocator = std.mem.Allocator; |
| 4 | 7 | const process = std.process; |
| 5 | 8 | const mem = std.mem; |
| 6 | 9 | |
| 7 | const NativePaths = @This(); | |
| 8 | ||
| 9 | 10 | arena: Allocator, |
| 10 | 11 | include_dirs: std.ArrayList([]const u8) = .empty, |
| 11 | 12 | lib_dirs: std.ArrayList([]const u8) = .empty, |
| ... | ... | @@ -13,7 +14,7 @@ framework_dirs: std.ArrayList([]const u8) = .empty, |
| 13 | 14 | rpaths: std.ArrayList([]const u8) = .empty, |
| 14 | 15 | warnings: std.ArrayList([]const u8) = .empty, |
| 15 | 16 | |
| 16 | pub fn detect(arena: Allocator, native_target: *const std.Target) !NativePaths { | |
| 17 | pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !NativePaths { | |
| 17 | 18 | var self: NativePaths = .{ .arena = arena }; |
| 18 | 19 | var is_nix = false; |
| 19 | 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 | 116 | |
| 116 | 117 | // TODO: consider also adding macports paths |
| 117 | 118 | if (builtin.target.os.tag.isDarwin()) { |
| 118 | if (std.zig.system.darwin.isSdkInstalled(arena)) sdk: { | |
| 119 | const sdk = std.zig.system.darwin.getSdk(arena, native_target) orelse break :sdk; | |
| 119 | if (std.zig.system.darwin.isSdkInstalled(arena, io)) sdk: { | |
| 120 | const sdk = std.zig.system.darwin.getSdk(arena, io, native_target) orelse break :sdk; | |
| 120 | 121 | try self.addLibDir(try std.fs.path.join(arena, &.{ sdk, "usr/lib" })); |
| 121 | 122 | try self.addFrameworkDir(try std.fs.path.join(arena, &.{ sdk, "System/Library/Frameworks" })); |
| 122 | 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 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const mem = std.mem; |
| 3 | const Allocator = mem.Allocator; | |
| 4 | const Allocator = std.mem.Allocator; | |
| 4 | 5 | const Target = std.Target; |
| 5 | 6 | const Version = std.SemanticVersion; |
| 6 | 7 | |
| 7 | 8 | pub const macos = @import("darwin/macos.zig"); |
| 8 | 9 | |
| 9 | 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 | 13 | /// Therefore, we resort to invoking `xcode-select --print-path` and checking |
| 12 | 14 | /// if the status is nonzero. |
| 15 | /// | |
| 13 | 16 | /// stderr from xcode-select is ignored. |
| 17 | /// | |
| 14 | 18 | /// If error.OutOfMemory occurs in Allocator, this function returns null. |
| 15 | pub fn isSdkInstalled(allocator: Allocator) bool { | |
| 16 | const result = std.process.Child.run(.{ | |
| 17 | .allocator = allocator, | |
| 19 | pub fn isSdkInstalled(gpa: Allocator, io: Io) bool { | |
| 20 | const result = std.process.Child.run(gpa, io, .{ | |
| 18 | 21 | .argv = &.{ "xcode-select", "--print-path" }, |
| 19 | 22 | }) catch return false; |
| 20 | ||
| 21 | 23 | defer { |
| 22 | allocator.free(result.stderr); | |
| 23 | allocator.free(result.stdout); | |
| 24 | gpa.free(result.stderr); | |
| 25 | gpa.free(result.stdout); | |
| 24 | 26 | } |
| 25 | ||
| 26 | 27 | return switch (result.term) { |
| 27 | 28 | .Exited => |code| if (code == 0) result.stdout.len > 0 else false, |
| 28 | 29 | else => false, |
| ... | ... | @@ -34,7 +35,7 @@ pub fn isSdkInstalled(allocator: Allocator) bool { |
| 34 | 35 | /// Caller owns the memory. |
| 35 | 36 | /// stderr from xcrun is ignored. |
| 36 | 37 | /// If error.OutOfMemory occurs in Allocator, this function returns null. |
| 37 | pub fn getSdk(allocator: Allocator, target: *const Target) ?[]const u8 { | |
| 38 | pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 { | |
| 38 | 39 | const is_simulator_abi = target.abi == .simulator; |
| 39 | 40 | const sdk = switch (target.os.tag) { |
| 40 | 41 | .driverkit => "driverkit", |
| ... | ... | @@ -46,16 +47,16 @@ pub fn getSdk(allocator: Allocator, target: *const Target) ?[]const u8 { |
| 46 | 47 | else => return null, |
| 47 | 48 | }; |
| 48 | 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 | 51 | defer { |
| 51 | allocator.free(result.stderr); | |
| 52 | allocator.free(result.stdout); | |
| 52 | gpa.free(result.stderr); | |
| 53 | gpa.free(result.stdout); | |
| 53 | 54 | } |
| 54 | 55 | switch (result.term) { |
| 55 | 56 | .Exited => |code| if (code != 0) return null, |
| 56 | 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 | } |
| 60 | 61 | |
| 61 | 62 | test { |
lib/std/zig/system/darwin/macos.zig+5-4| ... | ... | @@ -1,14 +1,15 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 3 | 5 | const assert = std.debug.assert; |
| 4 | 6 | const mem = std.mem; |
| 5 | 7 | const testing = std.testing; |
| 6 | ||
| 7 | 8 | const Target = std.Target; |
| 8 | 9 | |
| 9 | 10 | /// Detect macOS version. |
| 10 | 11 | /// `target_os` is not modified in case of error. |
| 11 | pub fn detect(target_os: *Target.Os) !void { | |
| 12 | pub fn detect(io: Io, target_os: *Target.Os) !void { | |
| 12 | 13 | // Drop use of osproductversion sysctl because: |
| 13 | 14 | // 1. only available 10.13.4 High Sierra and later |
| 14 | 15 | // 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 | 55 | // approx. 4 times historical file size |
| 55 | 56 | var buf: [2048]u8 = undefined; |
| 56 | 57 | |
| 57 | if (std.fs.cwd().readFile(path, &buf)) |bytes| { | |
| 58 | if (Io.Dir.cwd().readFile(io, path, &buf)) |bytes| { | |
| 58 | 59 | if (parseSystemVersion(bytes)) |ver| { |
| 59 | 60 | // never return non-canonical `10.(16+)` |
| 60 | 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 | 444 | } |
| 445 | 445 | |
| 446 | 446 | pub 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 | 448 | else => return null, |
| 449 | 449 | }; |
| 450 | defer file.close(); | |
| 450 | defer file.close(io); | |
| 451 | 451 | |
| 452 | 452 | var buffer: [4096]u8 = undefined; // "flags" lines can get pretty long. |
| 453 | 453 | var file_reader = file.reader(io, &buffer); |
lib/std/zip.zig+16-12| ... | ... | @@ -4,9 +4,11 @@ |
| 4 | 4 | //! Note that this file uses the abbreviation "cd" for "central directory" |
| 5 | 5 | |
| 6 | 6 | const builtin = @import("builtin"); |
| 7 | const std = @import("std"); | |
| 8 | const File = std.fs.File; | |
| 9 | 7 | const is_le = builtin.target.cpu.arch.endian() == .little; |
| 8 | ||
| 9 | const std = @import("std"); | |
| 10 | const Io = std.Io; | |
| 11 | const File = std.Io.File; | |
| 10 | 12 | const Writer = std.Io.Writer; |
| 11 | 13 | const Reader = std.Io.Reader; |
| 12 | 14 | const flate = std.compress.flate; |
| ... | ... | @@ -115,7 +117,7 @@ pub const EndRecord = extern struct { |
| 115 | 117 | return record; |
| 116 | 118 | } |
| 117 | 119 | |
| 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 | 121 | ZipNoEndRecord, |
| 120 | 122 | EndOfStream, |
| 121 | 123 | ReadFailed, |
| ... | ... | @@ -460,8 +462,10 @@ pub const Iterator = struct { |
| 460 | 462 | stream: *File.Reader, |
| 461 | 463 | options: ExtractOptions, |
| 462 | 464 | filename_buf: []u8, |
| 463 | dest: std.fs.Dir, | |
| 465 | dest: Io.Dir, | |
| 464 | 466 | ) !void { |
| 467 | const io = stream.io; | |
| 468 | ||
| 465 | 469 | if (filename_buf.len < self.filename_len) |
| 466 | 470 | return error.ZipInsufficientBuffer; |
| 467 | 471 | switch (self.compression_method) { |
| ... | ... | @@ -550,23 +554,23 @@ pub const Iterator = struct { |
| 550 | 554 | if (filename[filename.len - 1] == '/') { |
| 551 | 555 | if (self.uncompressed_size != 0) |
| 552 | 556 | return error.ZipBadDirectorySize; |
| 553 | try dest.makePath(filename[0 .. filename.len - 1]); | |
| 557 | try dest.createDirPath(io, filename[0 .. filename.len - 1]); | |
| 554 | 558 | return; |
| 555 | 559 | } |
| 556 | 560 | |
| 557 | 561 | const out_file = blk: { |
| 558 | 562 | if (std.fs.path.dirname(filename)) |dirname| { |
| 559 | var parent_dir = try dest.makeOpenPath(dirname, .{}); | |
| 560 | defer parent_dir.close(); | |
| 563 | var parent_dir = try dest.createDirPathOpen(io, dirname, .{}); | |
| 564 | defer parent_dir.close(io); | |
| 561 | 565 | |
| 562 | 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 | 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 | 574 | const local_data_file_offset: u64 = |
| 571 | 575 | @as(u64, self.file_offset) + |
| 572 | 576 | @as(u64, @sizeOf(LocalFileHeader)) + |
| ... | ... | @@ -647,7 +651,7 @@ pub const ExtractOptions = struct { |
| 647 | 651 | }; |
| 648 | 652 | |
| 649 | 653 | /// Extract the zipped files to the given `dest` directory. |
| 650 | pub fn extract(dest: std.fs.Dir, fr: *File.Reader, options: ExtractOptions) !void { | |
| 654 | pub fn extract(dest: Io.Dir, fr: *File.Reader, options: ExtractOptions) !void { | |
| 651 | 655 | if (options.verify_checksums) @panic("TODO unimplemented"); |
| 652 | 656 | |
| 653 | 657 | var iter = try Iterator.init(fr); |
src/Air/print.zig+18-10| ... | ... | @@ -9,7 +9,7 @@ const Type = @import("../Type.zig"); |
| 9 | 9 | const Air = @import("../Air.zig"); |
| 10 | 10 | const InternPool = @import("../InternPool.zig"); |
| 11 | 11 | |
| 12 | pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air.Liveness) void { | |
| 12 | pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air.Liveness) !void { | |
| 13 | 13 | comptime assert(build_options.enable_debug_extensions); |
| 14 | 14 | const instruction_bytes = air.instructions.len * |
| 15 | 15 | // 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 | 24 | liveness_special_bytes + tomb_bytes; |
| 25 | 25 | |
| 26 | 26 | // zig fmt: off |
| 27 | stream.print( | |
| 27 | try stream.print( | |
| 28 | 28 | \\# Total AIR+Liveness bytes: {Bi} |
| 29 | 29 | \\# AIR Instructions: {d} ({Bi}) |
| 30 | 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 | 39 | tomb_bytes, |
| 40 | 40 | if (liveness) |l| l.extra.len else 0, liveness_extra_bytes, |
| 41 | 41 | if (liveness) |l| l.special.count() else 0, liveness_special_bytes, |
| 42 | }) catch return; | |
| 42 | }); | |
| 43 | 43 | // zig fmt: on |
| 44 | 44 | |
| 45 | 45 | var writer: Writer = .{ |
| ... | ... | @@ -50,7 +50,7 @@ pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air |
| 50 | 50 | .indent = 2, |
| 51 | 51 | .skip_body = false, |
| 52 | 52 | }; |
| 53 | writer.writeBody(stream, air.getMainBody()) catch return; | |
| 53 | try writer.writeBody(stream, air.getMainBody()); | |
| 54 | 54 | } |
| 55 | 55 | |
| 56 | 56 | pub fn writeInst( |
| ... | ... | @@ -73,15 +73,23 @@ pub fn writeInst( |
| 73 | 73 | } |
| 74 | 74 | |
| 75 | 75 | pub fn dump(air: Air, pt: Zcu.PerThread, liveness: ?Air.Liveness) void { |
| 76 | const stderr_bw, _ = std.debug.lockStderrWriter(&.{}); | |
| 77 | defer std.debug.unlockStderrWriter(); | |
| 78 | air.write(stderr_bw, pt, liveness); | |
| 76 | const comp = pt.zcu.comp; | |
| 77 | const io = comp.io; | |
| 78 | var buffer: [512]u8 = undefined; | |
| 79 | const stderr = try io.lockStderr(&buffer, null); | |
| 80 | defer io.unlockStderr(); | |
| 81 | const w = &stderr.file_writer.interface; | |
| 82 | air.write(w, pt, liveness); | |
| 79 | 83 | } |
| 80 | 84 | |
| 81 | 85 | pub fn dumpInst(air: Air, inst: Air.Inst.Index, pt: Zcu.PerThread, liveness: ?Air.Liveness) void { |
| 82 | const stderr_bw, _ = std.debug.lockStderrWriter(&.{}); | |
| 83 | defer std.debug.unlockStderrWriter(); | |
| 84 | air.writeInst(stderr_bw, inst, pt, liveness); | |
| 86 | const comp = pt.zcu.comp; | |
| 87 | const io = comp.io; | |
| 88 | var buffer: [512]u8 = undefined; | |
| 89 | const stderr = try io.lockStderr(&buffer, null); | |
| 90 | defer io.unlockStderr(); | |
| 91 | const w = &stderr.file_writer.interface; | |
| 92 | air.writeInst(w, inst, pt, liveness); | |
| 85 | 93 | } |
| 86 | 94 | |
| 87 | 95 | const Writer = struct { |
src/Builtin.zig+3-2| ... | ... | @@ -313,8 +313,9 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void { |
| 313 | 313 | assert(file.source != null); |
| 314 | 314 | |
| 315 | 315 | const root_dir, const sub_path = file.path.openInfo(comp.dirs); |
| 316 | const io = comp.io; | |
| 316 | 317 | |
| 317 | if (root_dir.statFile(sub_path)) |stat| { | |
| 318 | if (root_dir.statFile(io, sub_path, .{})) |stat| { | |
| 318 | 319 | if (stat.size != file.source.?.len) { |
| 319 | 320 | std.log.warn( |
| 320 | 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 | 343 | } |
| 343 | 344 | |
| 344 | 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 | 347 | defer af.deinit(); |
| 347 | 348 | try af.file_writer.interface.writeAll(file.source.?); |
| 348 | 349 | af.finish() catch |err| switch (err) { |
src/Compilation.zig+230-206| ... | ... | @@ -446,11 +446,11 @@ pub const Path = struct { |
| 446 | 446 | } |
| 447 | 447 | |
| 448 | 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 | 450 | const dir = switch (p.root) { |
| 451 | 451 | .none => { |
| 452 | 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 | 455 | .zig_lib => dirs.zig_lib.handle, |
| 456 | 456 | .global_cache => dirs.global_cache.handle, |
| ... | ... | @@ -721,13 +721,13 @@ pub const Directories = struct { |
| 721 | 721 | /// This may be the same as `global_cache`. |
| 722 | 722 | local_cache: Cache.Directory, |
| 723 | 723 | |
| 724 | pub fn deinit(dirs: *Directories) void { | |
| 724 | pub fn deinit(dirs: *Directories, io: Io) void { | |
| 725 | 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; | |
| 727 | 727 | |
| 728 | dirs.global_cache.handle.close(); | |
| 729 | if (close_local) dirs.local_cache.handle.close(); | |
| 730 | dirs.zig_lib.handle.close(); | |
| 728 | dirs.global_cache.handle.close(io); | |
| 729 | if (close_local) dirs.local_cache.handle.close(io); | |
| 730 | dirs.zig_lib.handle.close(io); | |
| 731 | 731 | } |
| 732 | 732 | |
| 733 | 733 | /// Returns a `Directories` where `local_cache` is replaced with `global_cache`, intended for |
| ... | ... | @@ -745,6 +745,7 @@ pub const Directories = struct { |
| 745 | 745 | /// Uses `std.process.fatal` on error conditions. |
| 746 | 746 | pub fn init( |
| 747 | 747 | arena: Allocator, |
| 748 | io: Io, | |
| 748 | 749 | override_zig_lib: ?[]const u8, |
| 749 | 750 | override_global_cache: ?[]const u8, |
| 750 | 751 | local_cache_strat: union(enum) { |
| ... | ... | @@ -768,30 +769,30 @@ pub const Directories = struct { |
| 768 | 769 | }; |
| 769 | 770 | |
| 770 | 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 | 773 | if (wasi) break :d openWasiPreopen(wasi_preopens, "/lib"); |
| 773 | break :d introspect.findZigLibDirFromSelfExe(arena, cwd, self_exe_path) catch |err| { | |
| 774 | fatal("unable to find zig installation directory '{s}': {s}", .{ self_exe_path, @errorName(err) }); | |
| 774 | break :d introspect.findZigLibDirFromSelfExe(arena, io, cwd, self_exe_path) catch |err| { | |
| 775 | fatal("unable to find zig installation directory '{s}': {t}", .{ self_exe_path, err }); | |
| 775 | 776 | }; |
| 776 | 777 | }; |
| 777 | 778 | |
| 778 | 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 | 781 | if (wasi) break :d openWasiPreopen(wasi_preopens, "/cache"); |
| 781 | 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 | }; |
| 786 | 787 | |
| 787 | 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 | 790 | .search => d: { |
| 790 | const maybe_path = introspect.resolveSuitableLocalCacheDir(arena, cwd) catch |err| { | |
| 791 | fatal("unable to resolve zig cache directory: {s}", .{@errorName(err)}); | |
| 791 | const maybe_path = introspect.resolveSuitableLocalCacheDir(arena, io, cwd) catch |err| { | |
| 792 | fatal("unable to resolve zig cache directory: {t}", .{err}); | |
| 792 | 793 | }; |
| 793 | 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 | 797 | .global => global_cache, |
| 797 | 798 | }; |
| ... | ... | @@ -814,18 +815,24 @@ pub const Directories = struct { |
| 814 | 815 | return .{ |
| 815 | 816 | .path = if (std.mem.eql(u8, name, ".")) null else name, |
| 816 | 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 | 829 | const path = introspect.resolvePath(arena, cwd, &.{unresolved_path}) catch |err| { |
| 823 | 830 | fatal("unable to resolve {s} directory: {s}", .{ @tagName(thing), @errorName(err) }); |
| 824 | 831 | }; |
| 825 | 832 | const nonempty_path = if (path.len == 0) "." else path; |
| 826 | 833 | const handle_or_err = switch (thing) { |
| 827 | .@"zig lib" => fs.cwd().openDir(nonempty_path, .{}), | |
| 828 | .@"global cache", .@"local cache" => fs.cwd().makeOpenPath(nonempty_path, .{}), | |
| 834 | .@"zig lib" => Io.Dir.cwd().openDir(io, nonempty_path, .{}), | |
| 835 | .@"global cache", .@"local cache" => Io.Dir.cwd().createDirPathOpen(io, nonempty_path, .{}), | |
| 829 | 836 | }; |
| 830 | 837 | return .{ |
| 831 | 838 | .path = if (path.len == 0) null else path, |
| ... | ... | @@ -912,8 +919,8 @@ pub const CrtFile = struct { |
| 912 | 919 | lock: Cache.Lock, |
| 913 | 920 | full_object_path: Cache.Path, |
| 914 | 921 | |
| 915 | pub fn deinit(self: *CrtFile, gpa: Allocator) void { | |
| 916 | self.lock.release(); | |
| 922 | pub fn deinit(self: *CrtFile, gpa: Allocator, io: Io) void { | |
| 923 | self.lock.release(io); | |
| 917 | 924 | gpa.free(self.full_object_path.sub_path); |
| 918 | 925 | self.* = undefined; |
| 919 | 926 | } |
| ... | ... | @@ -1104,8 +1111,8 @@ pub const CObject = struct { |
| 1104 | 1111 | const source_line = source_line: { |
| 1105 | 1112 | if (diag.src_loc.offset == 0 or diag.src_loc.column == 0) break :source_line 0; |
| 1106 | 1113 | |
| 1107 | const file = fs.cwd().openFile(file_name, .{}) catch break :source_line 0; | |
| 1108 | defer file.close(); | |
| 1114 | const file = Io.Dir.cwd().openFile(io, file_name, .{}) catch break :source_line 0; | |
| 1115 | defer file.close(io); | |
| 1109 | 1116 | var buffer: [1024]u8 = undefined; |
| 1110 | 1117 | var file_reader = file.reader(io, &buffer); |
| 1111 | 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 | 1186 | }; |
| 1180 | 1187 | |
| 1181 | 1188 | var buffer: [1024]u8 = undefined; |
| 1182 | const file = try fs.cwd().openFile(path, .{}); | |
| 1183 | defer file.close(); | |
| 1189 | const file = try Io.Dir.cwd().openFile(io, path, .{}); | |
| 1190 | defer file.close(io); | |
| 1184 | 1191 | var file_reader = file.reader(io, &buffer); |
| 1185 | 1192 | var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = &file_reader.interface }); |
| 1186 | 1193 | defer bc.deinit(); |
| ... | ... | @@ -1310,7 +1317,7 @@ pub const CObject = struct { |
| 1310 | 1317 | }; |
| 1311 | 1318 | |
| 1312 | 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 | 1321 | switch (self.status) { |
| 1315 | 1322 | .new => return false, |
| 1316 | 1323 | .failure, .failure_retryable => { |
| ... | ... | @@ -1319,15 +1326,15 @@ pub const CObject = struct { |
| 1319 | 1326 | }, |
| 1320 | 1327 | .success => |*success| { |
| 1321 | 1328 | gpa.free(success.object_path.sub_path); |
| 1322 | success.lock.release(); | |
| 1329 | success.lock.release(io); | |
| 1323 | 1330 | self.status = .new; |
| 1324 | 1331 | return false; |
| 1325 | 1332 | }, |
| 1326 | 1333 | } |
| 1327 | 1334 | } |
| 1328 | 1335 | |
| 1329 | pub fn destroy(self: *CObject, gpa: Allocator) void { | |
| 1330 | _ = self.clearStatus(gpa); | |
| 1336 | pub fn destroy(self: *CObject, gpa: Allocator, io: Io) void { | |
| 1337 | _ = self.clearStatus(gpa, io); | |
| 1331 | 1338 | gpa.destroy(self); |
| 1332 | 1339 | } |
| 1333 | 1340 | }; |
| ... | ... | @@ -1357,7 +1364,7 @@ pub const Win32Resource = struct { |
| 1357 | 1364 | }, |
| 1358 | 1365 | |
| 1359 | 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 | 1368 | switch (self.status) { |
| 1362 | 1369 | .new => return false, |
| 1363 | 1370 | .failure, .failure_retryable => { |
| ... | ... | @@ -1366,15 +1373,15 @@ pub const Win32Resource = struct { |
| 1366 | 1373 | }, |
| 1367 | 1374 | .success => |*success| { |
| 1368 | 1375 | gpa.free(success.res_path); |
| 1369 | success.lock.release(); | |
| 1376 | success.lock.release(io); | |
| 1370 | 1377 | self.status = .new; |
| 1371 | 1378 | return false; |
| 1372 | 1379 | }, |
| 1373 | 1380 | } |
| 1374 | 1381 | } |
| 1375 | 1382 | |
| 1376 | pub fn destroy(self: *Win32Resource, gpa: Allocator) void { | |
| 1377 | _ = self.clearStatus(gpa); | |
| 1383 | pub fn destroy(self: *Win32Resource, gpa: Allocator, io: Io) void { | |
| 1384 | _ = self.clearStatus(gpa, io); | |
| 1378 | 1385 | gpa.destroy(self); |
| 1379 | 1386 | } |
| 1380 | 1387 | }; |
| ... | ... | @@ -1603,9 +1610,9 @@ const CacheUse = union(CacheMode) { |
| 1603 | 1610 | /// Prevents other processes from clobbering files in the output directory. |
| 1604 | 1611 | lock: ?Cache.Lock, |
| 1605 | 1612 | |
| 1606 | fn releaseLock(whole: *Whole) void { | |
| 1613 | fn releaseLock(whole: *Whole, io: Io) void { | |
| 1607 | 1614 | if (whole.lock) |*lock| { |
| 1608 | lock.release(); | |
| 1615 | lock.release(io); | |
| 1609 | 1616 | whole.lock = null; |
| 1610 | 1617 | } |
| 1611 | 1618 | } |
| ... | ... | @@ -1617,17 +1624,17 @@ const CacheUse = union(CacheMode) { |
| 1617 | 1624 | } |
| 1618 | 1625 | }; |
| 1619 | 1626 | |
| 1620 | fn deinit(cu: CacheUse) void { | |
| 1627 | fn deinit(cu: CacheUse, io: Io) void { | |
| 1621 | 1628 | switch (cu) { |
| 1622 | 1629 | .none => |none| { |
| 1623 | 1630 | assert(none.tmp_artifact_directory == null); |
| 1624 | 1631 | }, |
| 1625 | 1632 | .incremental => |incremental| { |
| 1626 | incremental.artifact_directory.handle.close(); | |
| 1633 | incremental.artifact_directory.handle.close(io); | |
| 1627 | 1634 | }, |
| 1628 | 1635 | .whole => |whole| { |
| 1629 | 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 | 1879 | pub const CreateCachePath = struct { |
| 1873 | 1880 | which: enum { local, global }, |
| 1874 | 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 | 1884 | pub fn format(diag: CreateDiagnostic, w: *Writer) Writer.Error!void { |
| 1878 | 1885 | switch (diag) { |
| ... | ... | @@ -1896,13 +1903,17 @@ pub const CreateDiagnostic = union(enum) { |
| 1896 | 1903 | return error.CreateFail; |
| 1897 | 1904 | } |
| 1898 | 1905 | }; |
| 1899 | pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, options: CreateOptions) error{ | |
| 1906 | ||
| 1907 | pub const CreateError = error{ | |
| 1900 | 1908 | OutOfMemory, |
| 1909 | Canceled, | |
| 1901 | 1910 | Unexpected, |
| 1902 | 1911 | CurrentWorkingDirectoryUnlinked, |
| 1903 | 1912 | /// An error has been stored to `diag`. |
| 1904 | 1913 | CreateFail, |
| 1905 | }!*Compilation { | |
| 1914 | }; | |
| 1915 | ||
| 1916 | pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, options: CreateOptions) CreateError!*Compilation { | |
| 1906 | 1917 | const output_mode = options.config.output_mode; |
| 1907 | 1918 | const is_dyn_lib = switch (output_mode) { |
| 1908 | 1919 | .Obj, .Exe => false, |
| ... | ... | @@ -1950,6 +1961,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, |
| 1950 | 1961 | |
| 1951 | 1962 | const libc_dirs = std.zig.LibCDirs.detect( |
| 1952 | 1963 | arena, |
| 1964 | io, | |
| 1953 | 1965 | options.dirs.zig_lib.path.?, |
| 1954 | 1966 | target, |
| 1955 | 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 | 2092 | } |
| 2081 | 2093 | |
| 2082 | 2094 | if (options.verbose_llvm_cpu_features) { |
| 2083 | if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: { | |
| 2084 | const stderr_w, _ = std.debug.lockStderrWriter(&.{}); | |
| 2085 | defer std.debug.unlockStderrWriter(); | |
| 2086 | stderr_w.print("compilation: {s}\n", .{options.root_name}) catch break :print; | |
| 2087 | stderr_w.print(" target: {s}\n", .{try target.zigTriple(arena)}) catch break :print; | |
| 2088 | stderr_w.print(" cpu: {s}\n", .{target.cpu.model.name}) catch break :print; | |
| 2089 | stderr_w.print(" features: {s}\n", .{cf}) catch {}; | |
| 2095 | if (options.root_mod.resolved_target.llvm_cpu_features) |cf| { | |
| 2096 | const stderr = try io.lockStderr(&.{}, null); | |
| 2097 | defer io.unlockStderr(); | |
| 2098 | const w = &stderr.file_writer.interface; | |
| 2099 | printVerboseLlvmCpuFeatures(w, arena, options.root_name, target, cf) catch |err| switch (err) { | |
| 2100 | error.WriteFailed => switch (stderr.file_writer.err.?) { | |
| 2101 | error.Canceled => |e| return e, | |
| 2102 | else => {}, | |
| 2103 | }, | |
| 2104 | error.OutOfMemory => |e| return e, | |
| 2105 | }; | |
| 2090 | 2106 | } |
| 2091 | 2107 | } |
| 2092 | 2108 | |
| ... | ... | @@ -2104,16 +2120,16 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, |
| 2104 | 2120 | cache.* = .{ |
| 2105 | 2121 | .gpa = gpa, |
| 2106 | 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 | 2124 | return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } }); |
| 2109 | 2125 | }, |
| 2110 | 2126 | }; |
| 2111 | 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 | 2129 | cache.addPrefix(options.dirs.zig_lib); |
| 2114 | 2130 | cache.addPrefix(options.dirs.local_cache); |
| 2115 | 2131 | cache.addPrefix(options.dirs.global_cache); |
| 2116 | errdefer cache.manifest_dir.close(); | |
| 2132 | errdefer cache.manifest_dir.close(io); | |
| 2117 | 2133 | |
| 2118 | 2134 | // This is shared hasher state common to zig source and all C source files. |
| 2119 | 2135 | cache.hash.addBytes(build_options.version); |
| ... | ... | @@ -2154,18 +2170,18 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, |
| 2154 | 2170 | // to redundantly happen for each AstGen operation. |
| 2155 | 2171 | const zir_sub_dir = "z"; |
| 2156 | 2172 | |
| 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 | 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 | 2177 | const local_zir_cache: Cache.Directory = .{ |
| 2162 | 2178 | .handle = local_zir_dir, |
| 2163 | 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 | 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 | 2185 | const global_zir_cache: Cache.Directory = .{ |
| 2170 | 2186 | .handle = global_zir_dir, |
| 2171 | 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 | 2449 | const digest = hash.final(); |
| 2434 | 2450 | |
| 2435 | 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 | 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 | 2456 | const artifact_directory: Cache.Directory = .{ |
| 2441 | 2457 | .handle = artifact_dir, |
| 2442 | 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 | 2703 | return comp; |
| 2688 | 2704 | } |
| 2689 | 2705 | |
| 2706 | fn 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 | ||
| 2690 | 2719 | pub fn destroy(comp: *Compilation) void { |
| 2691 | 2720 | const gpa = comp.gpa; |
| 2721 | const io = comp.io; | |
| 2692 | 2722 | |
| 2693 | 2723 | if (comp.bin_file) |lf| lf.destroy(); |
| 2694 | 2724 | if (comp.zcu) |zcu| zcu.deinit(); |
| 2695 | comp.cache_use.deinit(); | |
| 2725 | comp.cache_use.deinit(io); | |
| 2696 | 2726 | |
| 2697 | 2727 | for (&comp.work_queues) |*work_queue| work_queue.deinit(gpa); |
| 2698 | 2728 | comp.c_object_work_queue.deinit(gpa); |
| ... | ... | @@ -2705,36 +2735,36 @@ pub fn destroy(comp: *Compilation) void { |
| 2705 | 2735 | var it = comp.crt_files.iterator(); |
| 2706 | 2736 | while (it.next()) |entry| { |
| 2707 | 2737 | gpa.free(entry.key_ptr.*); |
| 2708 | entry.value_ptr.deinit(gpa); | |
| 2738 | entry.value_ptr.deinit(gpa, io); | |
| 2709 | 2739 | } |
| 2710 | 2740 | comp.crt_files.deinit(gpa); |
| 2711 | 2741 | } |
| 2712 | if (comp.libcxx_static_lib) |*crt_file| crt_file.deinit(gpa); | |
| 2713 | if (comp.libcxxabi_static_lib) |*crt_file| crt_file.deinit(gpa); | |
| 2714 | if (comp.libunwind_static_lib) |*crt_file| crt_file.deinit(gpa); | |
| 2715 | if (comp.tsan_lib) |*crt_file| crt_file.deinit(gpa); | |
| 2716 | if (comp.ubsan_rt_lib) |*crt_file| crt_file.deinit(gpa); | |
| 2717 | if (comp.ubsan_rt_obj) |*crt_file| crt_file.deinit(gpa); | |
| 2718 | if (comp.zigc_static_lib) |*crt_file| crt_file.deinit(gpa); | |
| 2719 | if (comp.compiler_rt_lib) |*crt_file| crt_file.deinit(gpa); | |
| 2720 | if (comp.compiler_rt_obj) |*crt_file| crt_file.deinit(gpa); | |
| 2721 | if (comp.compiler_rt_dyn_lib) |*crt_file| crt_file.deinit(gpa); | |
| 2722 | if (comp.fuzzer_lib) |*crt_file| crt_file.deinit(gpa); | |
| 2742 | if (comp.libcxx_static_lib) |*crt_file| crt_file.deinit(gpa, io); | |
| 2743 | if (comp.libcxxabi_static_lib) |*crt_file| crt_file.deinit(gpa, io); | |
| 2744 | if (comp.libunwind_static_lib) |*crt_file| crt_file.deinit(gpa, io); | |
| 2745 | if (comp.tsan_lib) |*crt_file| crt_file.deinit(gpa, io); | |
| 2746 | if (comp.ubsan_rt_lib) |*crt_file| crt_file.deinit(gpa, io); | |
| 2747 | if (comp.ubsan_rt_obj) |*crt_file| crt_file.deinit(gpa, io); | |
| 2748 | if (comp.zigc_static_lib) |*crt_file| crt_file.deinit(gpa, io); | |
| 2749 | if (comp.compiler_rt_lib) |*crt_file| crt_file.deinit(gpa, io); | |
| 2750 | if (comp.compiler_rt_obj) |*crt_file| crt_file.deinit(gpa, io); | |
| 2751 | if (comp.compiler_rt_dyn_lib) |*crt_file| crt_file.deinit(gpa, io); | |
| 2752 | if (comp.fuzzer_lib) |*crt_file| crt_file.deinit(gpa, io); | |
| 2723 | 2753 | |
| 2724 | 2754 | if (comp.glibc_so_files) |*glibc_file| { |
| 2725 | glibc_file.deinit(gpa); | |
| 2755 | glibc_file.deinit(gpa, io); | |
| 2726 | 2756 | } |
| 2727 | 2757 | |
| 2728 | 2758 | if (comp.freebsd_so_files) |*freebsd_file| { |
| 2729 | freebsd_file.deinit(gpa); | |
| 2759 | freebsd_file.deinit(gpa, io); | |
| 2730 | 2760 | } |
| 2731 | 2761 | |
| 2732 | 2762 | if (comp.netbsd_so_files) |*netbsd_file| { |
| 2733 | netbsd_file.deinit(gpa); | |
| 2763 | netbsd_file.deinit(gpa, io); | |
| 2734 | 2764 | } |
| 2735 | 2765 | |
| 2736 | 2766 | for (comp.c_object_table.keys()) |key| { |
| 2737 | key.destroy(gpa); | |
| 2767 | key.destroy(gpa, io); | |
| 2738 | 2768 | } |
| 2739 | 2769 | comp.c_object_table.deinit(gpa); |
| 2740 | 2770 | |
| ... | ... | @@ -2744,7 +2774,7 @@ pub fn destroy(comp: *Compilation) void { |
| 2744 | 2774 | comp.failed_c_objects.deinit(gpa); |
| 2745 | 2775 | |
| 2746 | 2776 | for (comp.win32_resource_table.keys()) |key| { |
| 2747 | key.destroy(gpa); | |
| 2777 | key.destroy(gpa, io); | |
| 2748 | 2778 | } |
| 2749 | 2779 | comp.win32_resource_table.deinit(gpa); |
| 2750 | 2780 | |
| ... | ... | @@ -2760,7 +2790,7 @@ pub fn destroy(comp: *Compilation) void { |
| 2760 | 2790 | |
| 2761 | 2791 | comp.clearMiscFailures(); |
| 2762 | 2792 | |
| 2763 | comp.cache_parent.manifest_dir.close(); | |
| 2793 | comp.cache_parent.manifest_dir.close(io); | |
| 2764 | 2794 | } |
| 2765 | 2795 | |
| 2766 | 2796 | pub fn clearMiscFailures(comp: *Compilation) void { |
| ... | ... | @@ -2791,10 +2821,12 @@ pub fn hotCodeSwap( |
| 2791 | 2821 | } |
| 2792 | 2822 | |
| 2793 | 2823 | fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { |
| 2824 | const io = comp.io; | |
| 2825 | ||
| 2794 | 2826 | switch (comp.cache_use) { |
| 2795 | 2827 | .none => |none| { |
| 2796 | 2828 | if (none.tmp_artifact_directory) |*tmp_dir| { |
| 2797 | tmp_dir.handle.close(); | |
| 2829 | tmp_dir.handle.close(io); | |
| 2798 | 2830 | none.tmp_artifact_directory = null; |
| 2799 | 2831 | if (dev.env == .bootstrap) { |
| 2800 | 2832 | // 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 | 2845 | return; |
| 2814 | 2846 | } |
| 2815 | 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| { | |
| 2817 | log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{ | |
| 2818 | comp.dirs.local_cache.path orelse ".", | |
| 2819 | fs.path.sep, | |
| 2820 | tmp_dir_sub_path, | |
| 2821 | @errorName(err), | |
| 2848 | comp.dirs.local_cache.handle.deleteTree(io, tmp_dir_sub_path) catch |err| { | |
| 2849 | log.warn("failed to delete temporary directory '{s}{c}{s}': {t}", .{ | |
| 2850 | comp.dirs.local_cache.path orelse ".", fs.path.sep, tmp_dir_sub_path, err, | |
| 2822 | 2851 | }); |
| 2823 | 2852 | }; |
| 2824 | 2853 | } |
| ... | ... | @@ -2834,15 +2863,12 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { |
| 2834 | 2863 | comp.bin_file = null; |
| 2835 | 2864 | } |
| 2836 | 2865 | if (whole.tmp_artifact_directory) |*tmp_dir| { |
| 2837 | tmp_dir.handle.close(); | |
| 2866 | tmp_dir.handle.close(io); | |
| 2838 | 2867 | whole.tmp_artifact_directory = null; |
| 2839 | 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| { | |
| 2841 | log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{ | |
| 2842 | comp.dirs.local_cache.path orelse ".", | |
| 2843 | fs.path.sep, | |
| 2844 | tmp_dir_sub_path, | |
| 2845 | @errorName(err), | |
| 2869 | comp.dirs.local_cache.handle.deleteTree(io, tmp_dir_sub_path) catch |err| { | |
| 2870 | log.warn("failed to delete temporary directory '{s}{c}{s}': {t}", .{ | |
| 2871 | comp.dirs.local_cache.path orelse ".", fs.path.sep, tmp_dir_sub_path, err, | |
| 2846 | 2872 | }); |
| 2847 | 2873 | }; |
| 2848 | 2874 | } |
| ... | ... | @@ -2891,7 +2917,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE |
| 2891 | 2917 | tmp_dir_rand_int = std.crypto.random.int(u64); |
| 2892 | 2918 | const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); |
| 2893 | 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 | 2921 | return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err }); |
| 2896 | 2922 | }; |
| 2897 | 2923 | break :d .{ .path = path, .handle = handle }; |
| ... | ... | @@ -2901,7 +2927,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE |
| 2901 | 2927 | .whole => |whole| { |
| 2902 | 2928 | assert(comp.bin_file == null); |
| 2903 | 2929 | // We are about to obtain this lock, so here we give other processes a chance first. |
| 2904 | whole.releaseLock(); | |
| 2930 | whole.releaseLock(io); | |
| 2905 | 2931 | |
| 2906 | 2932 | man = comp.cache_parent.obtain(); |
| 2907 | 2933 | whole.cache_manifest = &man; |
| ... | ... | @@ -2972,7 +2998,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE |
| 2972 | 2998 | tmp_dir_rand_int = std.crypto.random.int(u64); |
| 2973 | 2999 | const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); |
| 2974 | 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 | 3002 | return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err }); |
| 2977 | 3003 | }; |
| 2978 | 3004 | break :d .{ .path = path, .handle = handle }; |
| ... | ... | @@ -3087,17 +3113,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE |
| 3087 | 3113 | } |
| 3088 | 3114 | |
| 3089 | 3115 | if (build_options.enable_debug_extensions and comp.verbose_intern_pool) { |
| 3090 | std.debug.print("intern pool stats for '{s}':\n", .{ | |
| 3091 | comp.root_name, | |
| 3092 | }); | |
| 3116 | std.debug.print("intern pool stats for '{s}':\n", .{comp.root_name}); | |
| 3093 | 3117 | zcu.intern_pool.dump(); |
| 3094 | 3118 | } |
| 3095 | 3119 | |
| 3096 | 3120 | if (build_options.enable_debug_extensions and comp.verbose_generic_instances) { |
| 3097 | std.debug.print("generic instances for '{s}:0x{x}':\n", .{ | |
| 3098 | comp.root_name, | |
| 3099 | @intFromPtr(zcu), | |
| 3100 | }); | |
| 3121 | std.debug.print("generic instances for '{s}:0x{x}':\n", .{ comp.root_name, @intFromPtr(zcu) }); | |
| 3101 | 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 | 3173 | // the file handle and re-open it in the follow up call to |
| 3153 | 3174 | // `makeWritable`. |
| 3154 | 3175 | if (lf.file) |f| { |
| 3155 | f.close(); | |
| 3176 | f.close(io); | |
| 3156 | 3177 | lf.file = null; |
| 3157 | 3178 | |
| 3158 | 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 | 3186 | |
| 3166 | 3187 | // Rename the temporary directory into place. |
| 3167 | 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 | 3190 | whole.tmp_artifact_directory = null; |
| 3170 | 3191 | const s = fs.path.sep_str; |
| 3171 | 3192 | const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); |
| 3172 | 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 | 3195 | return comp.setMiscFailure( |
| 3175 | 3196 | .rename_results, |
| 3176 | 3197 | "failed to rename compilation results ('{f}{s}') into local cache ('{f}{s}'): {t}", |
| ... | ... | @@ -3300,11 +3321,8 @@ pub fn resolveEmitPathFlush( |
| 3300 | 3321 | }, |
| 3301 | 3322 | } |
| 3302 | 3323 | } |
| 3303 | fn flush( | |
| 3304 | comp: *Compilation, | |
| 3305 | arena: Allocator, | |
| 3306 | tid: Zcu.PerThread.Id, | |
| 3307 | ) Allocator.Error!void { | |
| 3324 | ||
| 3325 | fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancelable || Allocator.Error)!void { | |
| 3308 | 3326 | const io = comp.io; |
| 3309 | 3327 | if (comp.zcu) |zcu| { |
| 3310 | 3328 | if (zcu.llvm_object) |llvm_object| { |
| ... | ... | @@ -3370,7 +3388,7 @@ fn flush( |
| 3370 | 3388 | // This is needed before reading the error flags. |
| 3371 | 3389 | lf.flush(arena, tid, comp.link_prog_node) catch |err| switch (err) { |
| 3372 | 3390 | error.LinkFailure => {}, // Already reported. |
| 3373 | error.OutOfMemory => return error.OutOfMemory, | |
| 3391 | error.OutOfMemory, error.Canceled => |e| return e, | |
| 3374 | 3392 | }; |
| 3375 | 3393 | } |
| 3376 | 3394 | if (comp.zcu) |zcu| { |
| ... | ... | @@ -3389,17 +3407,19 @@ fn flush( |
| 3389 | 3407 | /// implementation at the bottom of this function. |
| 3390 | 3408 | /// This function is only called when CacheMode is `whole`. |
| 3391 | 3409 | fn renameTmpIntoCache( |
| 3410 | io: Io, | |
| 3392 | 3411 | cache_directory: Cache.Directory, |
| 3393 | 3412 | tmp_dir_sub_path: []const u8, |
| 3394 | 3413 | o_sub_path: []const u8, |
| 3395 | 3414 | ) !void { |
| 3396 | 3415 | var seen_eaccess = false; |
| 3397 | 3416 | while (true) { |
| 3398 | fs.rename( | |
| 3417 | Io.Dir.rename( | |
| 3399 | 3418 | cache_directory.handle, |
| 3400 | 3419 | tmp_dir_sub_path, |
| 3401 | 3420 | cache_directory.handle, |
| 3402 | 3421 | o_sub_path, |
| 3422 | io, | |
| 3403 | 3423 | ) catch |err| switch (err) { |
| 3404 | 3424 | // On Windows, rename fails with `AccessDenied` rather than `PathAlreadyExists`. |
| 3405 | 3425 | // See https://github.com/ziglang/zig/issues/8362 |
| ... | ... | @@ -3407,17 +3427,17 @@ fn renameTmpIntoCache( |
| 3407 | 3427 | .windows => { |
| 3408 | 3428 | if (seen_eaccess) return error.AccessDenied; |
| 3409 | 3429 | seen_eaccess = true; |
| 3410 | try cache_directory.handle.deleteTree(o_sub_path); | |
| 3430 | try cache_directory.handle.deleteTree(io, o_sub_path); | |
| 3411 | 3431 | continue; |
| 3412 | 3432 | }, |
| 3413 | 3433 | else => return error.AccessDenied, |
| 3414 | 3434 | }, |
| 3415 | 3435 | error.PathAlreadyExists => { |
| 3416 | try cache_directory.handle.deleteTree(o_sub_path); | |
| 3436 | try cache_directory.handle.deleteTree(io, o_sub_path); | |
| 3417 | 3437 | continue; |
| 3418 | 3438 | }, |
| 3419 | 3439 | error.FileNotFound => { |
| 3420 | try cache_directory.handle.makePath("o"); | |
| 3440 | try cache_directory.handle.createDirPath(io, "o"); | |
| 3421 | 3441 | continue; |
| 3422 | 3442 | }, |
| 3423 | 3443 | else => |e| return e, |
| ... | ... | @@ -3592,6 +3612,7 @@ fn emitFromCObject( |
| 3592 | 3612 | new_ext: []const u8, |
| 3593 | 3613 | unresolved_emit_path: []const u8, |
| 3594 | 3614 | ) Allocator.Error!void { |
| 3615 | const io = comp.io; | |
| 3595 | 3616 | // The dirname and stem (i.e. everything but the extension), of the sub path of the C object. |
| 3596 | 3617 | // We'll append `new_ext` to it to get the path to the right thing (asm, LLVM IR, etc). |
| 3597 | 3618 | const c_obj_dir_and_stem: []const u8 = p: { |
| ... | ... | @@ -3601,23 +3622,18 @@ fn emitFromCObject( |
| 3601 | 3622 | }; |
| 3602 | 3623 | const src_path: Cache.Path = .{ |
| 3603 | 3624 | .root_dir = c_obj_path.root_dir, |
| 3604 | .sub_path = try std.fmt.allocPrint(arena, "{s}{s}", .{ | |
| 3605 | c_obj_dir_and_stem, | |
| 3606 | new_ext, | |
| 3607 | }), | |
| 3625 | .sub_path = try std.fmt.allocPrint(arena, "{s}{s}", .{ c_obj_dir_and_stem, new_ext }), | |
| 3608 | 3626 | }; |
| 3609 | 3627 | const emit_path = comp.resolveEmitPath(unresolved_emit_path); |
| 3610 | 3628 | |
| 3611 | src_path.root_dir.handle.copyFile( | |
| 3629 | Io.Dir.copyFile( | |
| 3630 | src_path.root_dir.handle, | |
| 3612 | 3631 | src_path.sub_path, |
| 3613 | 3632 | emit_path.root_dir.handle, |
| 3614 | 3633 | emit_path.sub_path, |
| 3634 | io, | |
| 3615 | 3635 | .{}, |
| 3616 | ) catch |err| log.err("unable to copy '{f}' to '{f}': {s}", .{ | |
| 3617 | src_path, | |
| 3618 | emit_path, | |
| 3619 | @errorName(err), | |
| 3620 | }); | |
| 3636 | ) catch |err| log.err("unable to copy '{f}' to '{f}': {t}", .{ src_path, emit_path, err }); | |
| 3621 | 3637 | } |
| 3622 | 3638 | |
| 3623 | 3639 | /// Having the file open for writing is problematic as far as executing the |
| ... | ... | @@ -3673,6 +3689,7 @@ pub fn saveState(comp: *Compilation) !void { |
| 3673 | 3689 | const lf = comp.bin_file orelse return; |
| 3674 | 3690 | |
| 3675 | 3691 | const gpa = comp.gpa; |
| 3692 | const io = comp.io; | |
| 3676 | 3693 | |
| 3677 | 3694 | var bufs = std.array_list.Managed([]const u8).init(gpa); |
| 3678 | 3695 | defer bufs.deinit(); |
| ... | ... | @@ -3893,7 +3910,7 @@ pub fn saveState(comp: *Compilation) !void { |
| 3893 | 3910 | // Using an atomic file prevents a crash or power failure from corrupting |
| 3894 | 3911 | // the previous incremental compilation state. |
| 3895 | 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 | 3914 | defer af.deinit(); |
| 3898 | 3915 | try af.file_writer.interface.writeVecAll(bufs.items); |
| 3899 | 3916 | try af.finish(); |
| ... | ... | @@ -4251,12 +4268,13 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { |
| 4251 | 4268 | // However, we haven't reported any such error. |
| 4252 | 4269 | // This is a compiler bug. |
| 4253 | 4270 | print_ctx: { |
| 4254 | var stderr_w, _ = std.debug.lockStderrWriter(&.{}); | |
| 4255 | defer std.debug.unlockStderrWriter(); | |
| 4256 | stderr_w.writeAll("referenced transitive analysis errors, but none actually emitted\n") catch break :print_ctx; | |
| 4257 | stderr_w.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}) catch break :print_ctx; | |
| 4271 | const stderr = std.debug.lockStderr(&.{}).terminal(); | |
| 4272 | defer std.debug.unlockStderr(); | |
| 4273 | const w = stderr.writer; | |
| 4274 | w.writeAll("referenced transitive analysis errors, but none actually emitted\n") catch break :print_ctx; | |
| 4275 | w.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}) catch break :print_ctx; | |
| 4258 | 4276 | while (ref) |r| { |
| 4259 | stderr_w.print("referenced by: {f}{s}\n", .{ | |
| 4277 | w.print("referenced by: {f}{s}\n", .{ | |
| 4260 | 4278 | zcu.fmtAnalUnit(r.referencer), |
| 4261 | 4279 | if (zcu.transitive_failed_analysis.contains(r.referencer)) " [transitive failure]" else "", |
| 4262 | 4280 | }) catch break :print_ctx; |
| ... | ... | @@ -5038,7 +5056,9 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node |
| 5038 | 5056 | } |
| 5039 | 5057 | |
| 5040 | 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 | } |
| 5043 | 5063 | |
| 5044 | 5064 | const JobError = Allocator.Error || Io.Cancelable; |
| ... | ... | @@ -5211,13 +5231,10 @@ fn processOneJob( |
| 5211 | 5231 | } |
| 5212 | 5232 | } |
| 5213 | 5233 | |
| 5214 | fn createDepFile( | |
| 5215 | comp: *Compilation, | |
| 5216 | depfile: []const u8, | |
| 5217 | binfile: Cache.Path, | |
| 5218 | ) anyerror!void { | |
| 5234 | fn createDepFile(comp: *Compilation, depfile: []const u8, binfile: Cache.Path) anyerror!void { | |
| 5235 | const io = comp.io; | |
| 5219 | 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 | 5238 | defer af.deinit(); |
| 5222 | 5239 | |
| 5223 | 5240 | comp.writeDepFile(binfile, &af.file_writer.interface) catch return af.file_writer.err.?; |
| ... | ... | @@ -5258,39 +5275,35 @@ fn workerDocsCopy(comp: *Compilation) void { |
| 5258 | 5275 | |
| 5259 | 5276 | fn docsCopyFallible(comp: *Compilation) anyerror!void { |
| 5260 | 5277 | const zcu = comp.zcu orelse return comp.lockAndSetMiscFailure(.docs_copy, "no Zig code to document", .{}); |
| 5278 | const io = comp.io; | |
| 5261 | 5279 | |
| 5262 | 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 | 5282 | return comp.lockAndSetMiscFailure( |
| 5265 | 5283 | .docs_copy, |
| 5266 | 5284 | "unable to create output directory '{f}': {s}", |
| 5267 | 5285 | .{ docs_path, @errorName(err) }, |
| 5268 | 5286 | ); |
| 5269 | 5287 | }; |
| 5270 | defer out_dir.close(); | |
| 5288 | defer out_dir.close(io); | |
| 5271 | 5289 | |
| 5272 | 5290 | for (&[_][]const u8{ "docs/main.js", "docs/index.html" }) |sub_path| { |
| 5273 | 5291 | const basename = fs.path.basename(sub_path); |
| 5274 | comp.dirs.zig_lib.handle.copyFile(sub_path, out_dir, basename, .{}) catch |err| { | |
| 5275 | comp.lockAndSetMiscFailure(.docs_copy, "unable to copy {s}: {s}", .{ | |
| 5276 | sub_path, | |
| 5277 | @errorName(err), | |
| 5278 | }); | |
| 5279 | return; | |
| 5280 | }; | |
| 5292 | comp.dirs.zig_lib.handle.copyFile(sub_path, out_dir, basename, io, .{}) catch |err| | |
| 5293 | return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy {s}: {t}", .{ sub_path, err }); | |
| 5281 | 5294 | } |
| 5282 | 5295 | |
| 5283 | var tar_file = out_dir.createFile("sources.tar", .{}) catch |err| { | |
| 5296 | var tar_file = out_dir.createFile(io, "sources.tar", .{}) catch |err| { | |
| 5284 | 5297 | return comp.lockAndSetMiscFailure( |
| 5285 | 5298 | .docs_copy, |
| 5286 | 5299 | "unable to create '{f}/sources.tar': {s}", |
| 5287 | 5300 | .{ docs_path, @errorName(err) }, |
| 5288 | 5301 | ); |
| 5289 | 5302 | }; |
| 5290 | defer tar_file.close(); | |
| 5303 | defer tar_file.close(io); | |
| 5291 | 5304 | |
| 5292 | 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); | |
| 5294 | 5307 | |
| 5295 | 5308 | var seen_table: std.AutoArrayHashMapUnmanaged(*Package.Module, []const u8) = .empty; |
| 5296 | 5309 | defer seen_table.deinit(comp.gpa); |
| ... | ... | @@ -5321,17 +5334,17 @@ fn docsCopyModule( |
| 5321 | 5334 | comp: *Compilation, |
| 5322 | 5335 | module: *Package.Module, |
| 5323 | 5336 | name: []const u8, |
| 5324 | tar_file_writer: *fs.File.Writer, | |
| 5337 | tar_file_writer: *Io.File.Writer, | |
| 5325 | 5338 | ) !void { |
| 5326 | 5339 | const io = comp.io; |
| 5327 | 5340 | const root = module.root; |
| 5328 | 5341 | var mod_dir = d: { |
| 5329 | 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 | 5344 | } catch |err| { |
| 5332 | 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); | |
| 5335 | 5348 | |
| 5336 | 5349 | var walker = try mod_dir.walk(comp.gpa); |
| 5337 | 5350 | defer walker.deinit(); |
| ... | ... | @@ -5341,7 +5354,7 @@ fn docsCopyModule( |
| 5341 | 5354 | |
| 5342 | 5355 | var buffer: [1024]u8 = undefined; |
| 5343 | 5356 | |
| 5344 | while (try walker.next()) |entry| { | |
| 5357 | while (try walker.next(io)) |entry| { | |
| 5345 | 5358 | switch (entry.kind) { |
| 5346 | 5359 | .file => { |
| 5347 | 5360 | if (!std.mem.endsWith(u8, entry.basename, ".zig")) continue; |
| ... | ... | @@ -5350,14 +5363,14 @@ fn docsCopyModule( |
| 5350 | 5363 | }, |
| 5351 | 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 | 5367 | return comp.lockAndSetMiscFailure(.docs_copy, "unable to open {f}{s}: {t}", .{ |
| 5355 | 5368 | root.fmt(comp), entry.path, err, |
| 5356 | 5369 | }); |
| 5357 | 5370 | }; |
| 5358 | defer file.close(); | |
| 5359 | const stat = try file.stat(); | |
| 5360 | var file_reader: fs.File.Reader = .initSize(file.adaptToNewApi(), io, &buffer, stat.size); | |
| 5371 | defer file.close(io); | |
| 5372 | const stat = try file.stat(io); | |
| 5373 | var file_reader: Io.File.Reader = .initSize(file, io, &buffer, stat.size); | |
| 5361 | 5374 | |
| 5362 | 5375 | archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime) catch |err| { |
| 5363 | 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 | 5509 | try comp.updateSubCompilation(sub_compilation, .docs_wasm, prog_node); |
| 5497 | 5510 | |
| 5498 | 5511 | var crt_file = try sub_compilation.toCrtFile(); |
| 5499 | defer crt_file.deinit(gpa); | |
| 5512 | defer crt_file.deinit(gpa, io); | |
| 5500 | 5513 | |
| 5501 | 5514 | const docs_bin_file = crt_file.full_object_path; |
| 5502 | 5515 | assert(docs_bin_file.sub_path.len > 0); // emitted binary is not a directory |
| 5503 | 5516 | |
| 5504 | 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 | 5519 | comp.lockAndSetMiscFailure( |
| 5507 | 5520 | .docs_copy, |
| 5508 | 5521 | "unable to create output directory '{f}': {t}", |
| ... | ... | @@ -5510,12 +5523,14 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU |
| 5510 | 5523 | ); |
| 5511 | 5524 | return error.AlreadyReported; |
| 5512 | 5525 | }; |
| 5513 | defer out_dir.close(); | |
| 5526 | defer out_dir.close(io); | |
| 5514 | 5527 | |
| 5515 | crt_file.full_object_path.root_dir.handle.copyFile( | |
| 5528 | Io.Dir.copyFile( | |
| 5529 | crt_file.full_object_path.root_dir.handle, | |
| 5516 | 5530 | crt_file.full_object_path.sub_path, |
| 5517 | 5531 | out_dir, |
| 5518 | 5532 | "main.wasm", |
| 5533 | io, | |
| 5519 | 5534 | .{}, |
| 5520 | 5535 | ) catch |err| { |
| 5521 | 5536 | comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{f}' to '{f}': {t}", .{ |
| ... | ... | @@ -5692,8 +5707,8 @@ pub fn translateC( |
| 5692 | 5707 | const tmp_basename = std.fmt.hex(std.crypto.random.int(u64)); |
| 5693 | 5708 | const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename; |
| 5694 | 5709 | const cache_dir = comp.dirs.local_cache.handle; |
| 5695 | var cache_tmp_dir = try cache_dir.makeOpenPath(tmp_sub_path, .{}); | |
| 5696 | defer cache_tmp_dir.close(); | |
| 5710 | var cache_tmp_dir = try cache_dir.createDirPathOpen(io, tmp_sub_path, .{}); | |
| 5711 | defer cache_tmp_dir.close(io); | |
| 5697 | 5712 | |
| 5698 | 5713 | const translated_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, translated_basename }); |
| 5699 | 5714 | const source_path = switch (source) { |
| ... | ... | @@ -5702,7 +5717,7 @@ pub fn translateC( |
| 5702 | 5717 | const out_h_sub_path = tmp_sub_path ++ fs.path.sep_str ++ cimport_basename; |
| 5703 | 5718 | const out_h_path = try comp.dirs.local_cache.join(arena, &.{out_h_sub_path}); |
| 5704 | 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 | 5721 | break :path out_h_path; |
| 5707 | 5722 | }, |
| 5708 | 5723 | .path => |p| p, |
| ... | ... | @@ -5749,7 +5764,7 @@ pub fn translateC( |
| 5749 | 5764 | try argv.appendSlice(comp.global_cc_argv); |
| 5750 | 5765 | try argv.appendSlice(owner_mod.cc_argv); |
| 5751 | 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 | } |
| 5754 | 5769 | |
| 5755 | 5770 | var stdout: []u8 = undefined; |
| ... | ... | @@ -5775,7 +5790,7 @@ pub fn translateC( |
| 5775 | 5790 | } |
| 5776 | 5791 | |
| 5777 | 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 | 5794 | log.warn("failed to delete '{s}': {t}", .{ dep_file_path, err }); |
| 5780 | 5795 | }; |
| 5781 | 5796 | } |
| ... | ... | @@ -5805,7 +5820,7 @@ pub fn translateC( |
| 5805 | 5820 | const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest; |
| 5806 | 5821 | |
| 5807 | 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); | |
| 5809 | 5824 | |
| 5810 | 5825 | return .{ |
| 5811 | 5826 | .digest = bin_digest, |
| ... | ... | @@ -6144,7 +6159,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr |
| 6144 | 6159 | const gpa = comp.gpa; |
| 6145 | 6160 | const io = comp.io; |
| 6146 | 6161 | |
| 6147 | if (c_object.clearStatus(gpa)) { | |
| 6162 | if (c_object.clearStatus(gpa, io)) { | |
| 6148 | 6163 | // There was previous failure. |
| 6149 | 6164 | comp.mutex.lockUncancelable(io); |
| 6150 | 6165 | defer comp.mutex.unlock(io); |
| ... | ... | @@ -6257,7 +6272,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr |
| 6257 | 6272 | } |
| 6258 | 6273 | |
| 6259 | 6274 | if (comp.verbose_cc) { |
| 6260 | dump_argv(argv.items); | |
| 6275 | try dumpArgv(io, argv.items); | |
| 6261 | 6276 | } |
| 6262 | 6277 | |
| 6263 | 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 | 6282 | // We can't know the digest until we do the C compiler invocation, |
| 6268 | 6283 | // so we need a temporary filename. |
| 6269 | 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", .{}); | |
| 6271 | defer zig_cache_tmp_dir.close(); | |
| 6285 | var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{}); | |
| 6286 | defer zig_cache_tmp_dir.close(io); | |
| 6272 | 6287 | |
| 6273 | 6288 | const out_diag_path = if (comp.clang_passthrough_mode or !ext.clangSupportsDiagnostics()) |
| 6274 | 6289 | null |
| ... | ... | @@ -6303,15 +6318,15 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr |
| 6303 | 6318 | } |
| 6304 | 6319 | |
| 6305 | 6320 | if (comp.verbose_cc) { |
| 6306 | dump_argv(argv.items); | |
| 6321 | try dumpArgv(io, argv.items); | |
| 6307 | 6322 | } |
| 6308 | 6323 | |
| 6309 | 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 | 6326 | error.FileNotFound => {}, // the file wasn't created due to an error we reported |
| 6312 | 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 | 6330 | error.FileNotFound => {}, // the file wasn't created due to an error we reported |
| 6316 | 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 | 6337 | child.stdout_behavior = .Inherit; |
| 6323 | 6338 | child.stderr_behavior = .Inherit; |
| 6324 | 6339 | |
| 6325 | const term = child.spawnAndWait() catch |err| { | |
| 6340 | const term = child.spawnAndWait(io) catch |err| { | |
| 6326 | 6341 | return comp.failCObj(c_object, "failed to spawn zig clang (passthrough mode) {s}: {s}", .{ argv.items[0], @errorName(err) }); |
| 6327 | 6342 | }; |
| 6328 | 6343 | switch (term) { |
| ... | ... | @@ -6340,12 +6355,12 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr |
| 6340 | 6355 | child.stdout_behavior = .Ignore; |
| 6341 | 6356 | child.stderr_behavior = .Pipe; |
| 6342 | 6357 | |
| 6343 | try child.spawn(); | |
| 6358 | try child.spawn(io); | |
| 6344 | 6359 | |
| 6345 | 6360 | var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); |
| 6346 | 6361 | const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); |
| 6347 | 6362 | |
| 6348 | const term = child.wait() catch |err| { | |
| 6363 | const term = child.wait(io) catch |err| { | |
| 6349 | 6364 | return comp.failCObj(c_object, "failed to spawn zig clang {s}: {s}", .{ argv.items[0], @errorName(err) }); |
| 6350 | 6365 | }; |
| 6351 | 6366 | |
| ... | ... | @@ -6387,7 +6402,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr |
| 6387 | 6402 | |
| 6388 | 6403 | if (comp.file_system_inputs != null) { |
| 6389 | 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 | 6406 | defer gpa.free(dep_file_contents); |
| 6392 | 6407 | |
| 6393 | 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 | 6447 | // Rename into place. |
| 6433 | 6448 | const digest = man.final(); |
| 6434 | 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, .{}); | |
| 6436 | defer o_dir.close(); | |
| 6450 | var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{}); | |
| 6451 | defer o_dir.close(io); | |
| 6437 | 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 | 6454 | break :blk digest; |
| 6440 | 6455 | }; |
| 6441 | 6456 | |
| ... | ... | @@ -6477,8 +6492,6 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 6477 | 6492 | const tracy_trace = trace(@src()); |
| 6478 | 6493 | defer tracy_trace.end(); |
| 6479 | 6494 | |
| 6480 | const io = comp.io; | |
| 6481 | ||
| 6482 | 6495 | const src_path = switch (win32_resource.src) { |
| 6483 | 6496 | .rc => |rc_src| rc_src.src_path, |
| 6484 | 6497 | .manifest => |src_path| src_path, |
| ... | ... | @@ -6487,11 +6500,13 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 6487 | 6500 | |
| 6488 | 6501 | log.debug("updating win32 resource: {s}", .{src_path}); |
| 6489 | 6502 | |
| 6503 | const io = comp.io; | |
| 6504 | ||
| 6490 | 6505 | var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa); |
| 6491 | 6506 | defer arena_allocator.deinit(); |
| 6492 | 6507 | const arena = arena_allocator.allocator(); |
| 6493 | 6508 | |
| 6494 | if (win32_resource.clearStatus(comp.gpa)) { | |
| 6509 | if (win32_resource.clearStatus(comp.gpa, io)) { | |
| 6495 | 6510 | // There was previous failure. |
| 6496 | 6511 | comp.mutex.lockUncancelable(io); |
| 6497 | 6512 | defer comp.mutex.unlock(io); |
| ... | ... | @@ -6521,8 +6536,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 6521 | 6536 | const digest = man.final(); |
| 6522 | 6537 | |
| 6523 | 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, .{}); | |
| 6525 | defer o_dir.close(); | |
| 6539 | var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{}); | |
| 6540 | defer o_dir.close(io); | |
| 6526 | 6541 | |
| 6527 | 6542 | const in_rc_path = try comp.dirs.local_cache.join(comp.gpa, &.{ |
| 6528 | 6543 | o_sub_path, rc_basename, |
| ... | ... | @@ -6559,7 +6574,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 6559 | 6574 | resource_id, resource_type, fmtRcEscape(src_path), |
| 6560 | 6575 | }); |
| 6561 | 6576 | |
| 6562 | try o_dir.writeFile(.{ .sub_path = rc_basename, .data = input }); | |
| 6577 | try o_dir.writeFile(io, .{ .sub_path = rc_basename, .data = input }); | |
| 6563 | 6578 | |
| 6564 | 6579 | var argv = std.array_list.Managed([]const u8).init(comp.gpa); |
| 6565 | 6580 | defer argv.deinit(); |
| ... | ... | @@ -6609,8 +6624,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 6609 | 6624 | const rc_basename_noext = src_basename[0 .. src_basename.len - fs.path.extension(src_basename).len]; |
| 6610 | 6625 | |
| 6611 | 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", .{}); | |
| 6613 | defer zig_cache_tmp_dir.close(); | |
| 6627 | var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{}); | |
| 6628 | defer zig_cache_tmp_dir.close(io); | |
| 6614 | 6629 | |
| 6615 | 6630 | const res_filename = try std.fmt.allocPrint(arena, "{s}.res", .{rc_basename_noext}); |
| 6616 | 6631 | |
| ... | ... | @@ -6652,7 +6667,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 6652 | 6667 | // Read depfile and update cache manifest |
| 6653 | 6668 | { |
| 6654 | 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 | 6671 | defer arena.free(dep_file_contents); |
| 6657 | 6672 | |
| 6658 | 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 | 6695 | // Rename into place. |
| 6681 | 6696 | const digest = man.final(); |
| 6682 | 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, .{}); | |
| 6684 | defer o_dir.close(); | |
| 6698 | var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{}); | |
| 6699 | defer o_dir.close(io); | |
| 6685 | 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 | 6702 | break :blk digest; |
| 6688 | 6703 | }; |
| 6689 | 6704 | |
| ... | ... | @@ -6716,6 +6731,7 @@ fn spawnZigRc( |
| 6716 | 6731 | argv: []const []const u8, |
| 6717 | 6732 | child_progress_node: std.Progress.Node, |
| 6718 | 6733 | ) !void { |
| 6734 | const io = comp.io; | |
| 6719 | 6735 | var node_name: std.ArrayList(u8) = .empty; |
| 6720 | 6736 | defer node_name.deinit(arena); |
| 6721 | 6737 | |
| ... | ... | @@ -6725,8 +6741,8 @@ fn spawnZigRc( |
| 6725 | 6741 | child.stderr_behavior = .Pipe; |
| 6726 | 6742 | child.progress_node = child_progress_node; |
| 6727 | 6743 | |
| 6728 | child.spawn() catch |err| { | |
| 6729 | return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {s}", .{ argv[0], @errorName(err) }); | |
| 6744 | child.spawn(io) catch |err| { | |
| 6745 | return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {t}", .{ argv[0], err }); | |
| 6730 | 6746 | }; |
| 6731 | 6747 | |
| 6732 | 6748 | var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{ |
| ... | ... | @@ -6758,7 +6774,7 @@ fn spawnZigRc( |
| 6758 | 6774 | // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace) |
| 6759 | 6775 | const stderr = poller.reader(.stderr); |
| 6760 | 6776 | |
| 6761 | const term = child.wait() catch |err| { | |
| 6777 | const term = child.wait(io) catch |err| { | |
| 6762 | 6778 | return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv[0], @errorName(err) }); |
| 6763 | 6779 | }; |
| 6764 | 6780 | |
| ... | ... | @@ -7765,17 +7781,25 @@ pub fn lockAndSetMiscFailure( |
| 7765 | 7781 | return setMiscFailure(comp, tag, format, args); |
| 7766 | 7782 | } |
| 7767 | 7783 | |
| 7768 | pub fn dump_argv(argv: []const []const u8) void { | |
| 7784 | pub fn dumpArgv(io: Io, argv: []const []const u8) Io.Cancelable!void { | |
| 7769 | 7785 | var buffer: [64]u8 = undefined; |
| 7770 | const stderr, _ = std.debug.lockStderrWriter(&buffer); | |
| 7771 | defer std.debug.unlockStderrWriter(); | |
| 7772 | nosuspend { | |
| 7773 | for (argv, 0..) |arg, i| { | |
| 7774 | if (i != 0) stderr.writeByte(' ') catch return; | |
| 7775 | stderr.writeAll(arg) catch return; | |
| 7776 | } | |
| 7777 | stderr.writeByte('\n') catch return; | |
| 7786 | const stderr = try io.lockStderr(&buffer, null); | |
| 7787 | defer io.unlockStderr(); | |
| 7788 | const w = &stderr.file_writer.interface; | |
| 7789 | return dumpArgvWriter(w, argv) catch |err| switch (err) { | |
| 7790 | error.WriteFailed => switch (stderr.file_writer.err.?) { | |
| 7791 | error.Canceled => return error.Canceled, | |
| 7792 | else => return, | |
| 7793 | }, | |
| 7794 | }; | |
| 7795 | } | |
| 7796 | ||
| 7797 | fn 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 | } |
| 7780 | 7804 | |
| 7781 | 7805 | pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend { |
src/InternPool.zig+30-30| ... | ... | @@ -1,20 +1,21 @@ |
| 1 | 1 | //! All interned objects have both a value and a type. |
| 2 | 2 | //! This data structure is self-contained. |
| 3 | const InternPool = @This(); | |
| 3 | 4 | |
| 4 | 5 | const builtin = @import("builtin"); |
| 6 | ||
| 5 | 7 | const std = @import("std"); |
| 8 | const Io = std.Io; | |
| 6 | 9 | const Allocator = std.mem.Allocator; |
| 7 | 10 | const assert = std.debug.assert; |
| 8 | 11 | const BigIntConst = std.math.big.int.Const; |
| 9 | 12 | const BigIntMutable = std.math.big.int.Mutable; |
| 10 | 13 | const Cache = std.Build.Cache; |
| 11 | const Io = std.Io; | |
| 12 | 14 | const Limb = std.math.big.Limb; |
| 13 | 15 | const Hash = std.hash.Wyhash; |
| 16 | const Zir = std.zig.Zir; | |
| 14 | 17 | |
| 15 | const InternPool = @This(); | |
| 16 | 18 | const Zcu = @import("Zcu.zig"); |
| 17 | const Zir = std.zig.Zir; | |
| 18 | 19 | |
| 19 | 20 | /// One item per thread, indexed by `tid`, which is dense and unique per thread. |
| 20 | 21 | locals: []Local, |
| ... | ... | @@ -11166,11 +11167,15 @@ pub fn mutateVarInit(ip: *InternPool, io: Io, index: Index, init_index: Index) v |
| 11166 | 11167 | } |
| 11167 | 11168 | |
| 11168 | 11169 | pub fn dump(ip: *const InternPool) void { |
| 11169 | dumpStatsFallible(ip, std.heap.page_allocator) catch return; | |
| 11170 | dumpAllFallible(ip) catch return; | |
| 11170 | var buffer: [4096]u8 = undefined; | |
| 11171 | const stderr = std.debug.lockStderr(&buffer); | |
| 11172 | defer std.debug.unlockStderr(); | |
| 11173 | const w = &stderr.file_writer.interface; | |
| 11174 | dumpStatsFallible(ip, w, std.heap.page_allocator) catch return; | |
| 11175 | dumpAllFallible(ip, w) catch return; | |
| 11171 | 11176 | } |
| 11172 | 11177 | |
| 11173 | fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void { | |
| 11178 | fn dumpStatsFallible(ip: *const InternPool, w: *Io.Writer, arena: Allocator) !void { | |
| 11174 | 11179 | var items_len: usize = 0; |
| 11175 | 11180 | var extra_len: usize = 0; |
| 11176 | 11181 | var limbs_len: usize = 0; |
| ... | ... | @@ -11423,18 +11428,13 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void { |
| 11423 | 11428 | }; |
| 11424 | 11429 | counts.sort(SortContext{ .map = &counts }); |
| 11425 | 11430 | const len = @min(50, counts.count()); |
| 11426 | std.debug.print(" top 50 tags:\n", .{}); | |
| 11431 | try w.print(" top 50 tags:\n", .{}); | |
| 11427 | 11432 | for (counts.keys()[0..len], counts.values()[0..len]) |tag, stats| { |
| 11428 | std.debug.print(" {s}: {d} occurrences, {d} total bytes\n", .{ | |
| 11429 | @tagName(tag), stats.count, stats.bytes, | |
| 11430 | }); | |
| 11433 | try w.print(" {t}: {d} occurrences, {d} total bytes\n", .{ tag, stats.count, stats.bytes }); | |
| 11431 | 11434 | } |
| 11432 | 11435 | } |
| 11433 | 11436 | |
| 11434 | fn dumpAllFallible(ip: *const InternPool) anyerror!void { | |
| 11435 | var buffer: [4096]u8 = undefined; | |
| 11436 | const stderr_bw, _ = std.debug.lockStderrWriter(&buffer); | |
| 11437 | defer std.debug.unlockStderrWriter(); | |
| 11437 | fn dumpAllFallible(ip: *const InternPool, w: *Io.Writer) anyerror!void { | |
| 11438 | 11438 | for (ip.locals, 0..) |*local, tid| { |
| 11439 | 11439 | const items = local.shared.items.view(); |
| 11440 | 11440 | for ( |
| ... | ... | @@ -11443,12 +11443,12 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void { |
| 11443 | 11443 | 0.., |
| 11444 | 11444 | ) |tag, data, index| { |
| 11445 | 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 | 11447 | switch (tag) { |
| 11448 | 11448 | .removed => {}, |
| 11449 | 11449 | |
| 11450 | .simple_type => try stderr_bw.print("{s}", .{@tagName(@as(SimpleType, @enumFromInt(@intFromEnum(i))))}), | |
| 11451 | .simple_value => try stderr_bw.print("{s}", .{@tagName(@as(SimpleValue, @enumFromInt(@intFromEnum(i))))}), | |
| 11450 | .simple_type => try w.print("{s}", .{@tagName(@as(SimpleType, @enumFromInt(@intFromEnum(i))))}), | |
| 11451 | .simple_value => try w.print("{s}", .{@tagName(@as(SimpleValue, @enumFromInt(@intFromEnum(i))))}), | |
| 11452 | 11452 | |
| 11453 | 11453 | .type_int_signed, |
| 11454 | 11454 | .type_int_unsigned, |
| ... | ... | @@ -11521,23 +11521,27 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void { |
| 11521 | 11521 | .func_coerced, |
| 11522 | 11522 | .union_value, |
| 11523 | 11523 | .memoized_call, |
| 11524 | => try stderr_bw.print("{d}", .{data}), | |
| 11524 | => try w.print("{d}", .{data}), | |
| 11525 | 11525 | |
| 11526 | 11526 | .opt_null, |
| 11527 | 11527 | .type_slice, |
| 11528 | 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 | } |
| 11535 | 11535 | |
| 11536 | 11536 | pub 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 | } |
| 11539 | 11543 | |
| 11540 | pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator) anyerror!void { | |
| 11544 | pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator, w: *Io.Writer) !void { | |
| 11541 | 11545 | var arena_allocator = std.heap.ArenaAllocator.init(allocator); |
| 11542 | 11546 | defer arena_allocator.deinit(); |
| 11543 | 11547 | const arena = arena_allocator.allocator(); |
| ... | ... | @@ -11564,10 +11568,6 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator) |
| 11564 | 11568 | } |
| 11565 | 11569 | } |
| 11566 | 11570 | |
| 11567 | var buffer: [4096]u8 = undefined; | |
| 11568 | const stderr_bw, _ = std.debug.lockStderrWriter(&buffer); | |
| 11569 | defer std.debug.unlockStderrWriter(); | |
| 11570 | ||
| 11571 | 11571 | const SortContext = struct { |
| 11572 | 11572 | values: []std.ArrayList(Index), |
| 11573 | 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 | 11579 | var it = instances.iterator(); |
| 11580 | 11580 | while (it.next()) |entry| { |
| 11581 | 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 | 11583 | for (entry.value_ptr.items) |index| { |
| 11584 | 11584 | const unwrapped_index = index.unwrap(ip); |
| 11585 | 11585 | const func = ip.extraFuncInstance(unwrapped_index.tid, unwrapped_index.getExtra(ip), unwrapped_index.getData(ip)); |
| 11586 | 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 | 11588 | for (func.comptime_args.get(ip)) |arg| { |
| 11589 | 11589 | if (arg != .none) { |
| 11590 | 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 | 383 | }, |
| 384 | 384 | .remote => |remote| remote, |
| 385 | 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 | 387 | var resource: Resource = .{ .dir = dir }; |
| 388 | 388 | return f.runResource(path_or_url, &resource, null); |
| 389 | 389 | } else |dir_err| { |
| 390 | 390 | var server_header_buffer: [init_resource_buffer_size]u8 = undefined; |
| 391 | 391 | |
| 392 | 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 | 394 | var resource: Resource = .{ .file = file.reader(io, &server_header_buffer) }; |
| 395 | 395 | return f.runResource(path_or_url, &resource, null); |
| 396 | 396 | } else |err| break :e err; |
| ... | ... | @@ -418,7 +418,7 @@ pub fn run(f: *Fetch) RunError!void { |
| 418 | 418 | const prefixed_pkg_sub_path = prefixed_pkg_sub_path_buffer[0 .. 2 + hash_slice.len]; |
| 419 | 419 | const prefix_len: usize = if (f.job_queue.read_only) "p/".len else 0; |
| 420 | 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 | 422 | assert(f.lazy_status != .unavailable); |
| 423 | 423 | f.package_root = .{ |
| 424 | 424 | .root_dir = cache_root, |
| ... | ... | @@ -500,12 +500,12 @@ fn runResource( |
| 500 | 500 | var tmp_directory: Cache.Directory = .{ |
| 501 | 501 | .path = tmp_directory_path, |
| 502 | 502 | .handle = handle: { |
| 503 | const dir = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{ | |
| 504 | .iterate = true, | |
| 503 | const dir = cache_root.handle.createDirPathOpen(io, tmp_dir_sub_path, .{ | |
| 504 | .open_options = .{ .iterate = true }, | |
| 505 | 505 | }) catch |err| { |
| 506 | 506 | try eb.addRootErrorMessage(.{ |
| 507 | .msg = try eb.printString("unable to create temporary directory '{s}': {s}", .{ | |
| 508 | tmp_directory_path, @errorName(err), | |
| 507 | .msg = try eb.printString("unable to create temporary directory '{s}': {t}", .{ | |
| 508 | tmp_directory_path, err, | |
| 509 | 509 | }), |
| 510 | 510 | }); |
| 511 | 511 | return error.FetchFailed; |
| ... | ... | @@ -513,7 +513,7 @@ fn runResource( |
| 513 | 513 | break :handle dir; |
| 514 | 514 | }, |
| 515 | 515 | }; |
| 516 | defer tmp_directory.handle.close(); | |
| 516 | defer tmp_directory.handle.close(io); | |
| 517 | 517 | |
| 518 | 518 | // Fetch and unpack a resource into a temporary directory. |
| 519 | 519 | var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory); |
| ... | ... | @@ -523,9 +523,9 @@ fn runResource( |
| 523 | 523 | // Apply btrfs workaround if needed. Reopen tmp_directory. |
| 524 | 524 | if (native_os == .linux and f.job_queue.work_around_btrfs_bug) { |
| 525 | 525 | // https://github.com/ziglang/zig/issues/17095 |
| 526 | pkg_path.root_dir.handle.close(); | |
| 527 | pkg_path.root_dir.handle = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{ | |
| 528 | .iterate = true, | |
| 526 | pkg_path.root_dir.handle.close(io); | |
| 527 | pkg_path.root_dir.handle = cache_root.handle.createDirPathOpen(io, tmp_dir_sub_path, .{ | |
| 528 | .open_options = .{ .iterate = true }, | |
| 529 | 529 | }) catch @panic("btrfs workaround failed"); |
| 530 | 530 | } |
| 531 | 531 | |
| ... | ... | @@ -567,7 +567,7 @@ fn runResource( |
| 567 | 567 | .root_dir = cache_root, |
| 568 | 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 | 571 | const src = try cache_root.join(arena, &.{tmp_dir_sub_path}); |
| 572 | 572 | const dest = try cache_root.join(arena, &.{f.package_root.sub_path}); |
| 573 | 573 | try eb.addRootErrorMessage(.{ .msg = try eb.printString( |
| ... | ... | @@ -578,7 +578,7 @@ fn runResource( |
| 578 | 578 | }; |
| 579 | 579 | // Remove temporary directory root if not already renamed to global cache. |
| 580 | 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 | } |
| 583 | 583 | |
| 584 | 584 | // Validate the computed hash against the expected hash. If invalid, this |
| ... | ... | @@ -637,8 +637,9 @@ pub fn computedPackageHash(f: *const Fetch) Package.Hash { |
| 637 | 637 | /// `computeHash` gets a free check for the existence of `build.zig`, but when |
| 638 | 638 | /// not computing a hash, we need to do a syscall to check for it. |
| 639 | 639 | fn checkBuildFileExistence(f: *Fetch) RunError!void { |
| 640 | const io = f.job_queue.io; | |
| 640 | 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 | 643 | f.has_build_zig = true; |
| 643 | 644 | } else |err| switch (err) { |
| 644 | 645 | error.FileNotFound => {}, |
| ... | ... | @@ -655,9 +656,11 @@ fn checkBuildFileExistence(f: *Fetch) RunError!void { |
| 655 | 656 | |
| 656 | 657 | /// This function populates `f.manifest` or leaves it `null`. |
| 657 | 658 | fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { |
| 659 | const io = f.job_queue.io; | |
| 658 | 660 | const eb = &f.error_bundle; |
| 659 | 661 | const arena = f.arena.allocator(); |
| 660 | 662 | const manifest_bytes = pkg_root.root_dir.handle.readFileAllocOptions( |
| 663 | io, | |
| 661 | 664 | try fs.path.join(arena, &.{ pkg_root.sub_path, Manifest.basename }), |
| 662 | 665 | arena, |
| 663 | 666 | .limited(Manifest.max_bytes), |
| ... | ... | @@ -882,10 +885,10 @@ fn fail(f: *Fetch, msg_tok: std.zig.Ast.TokenIndex, msg_str: u32) RunError { |
| 882 | 885 | } |
| 883 | 886 | |
| 884 | 887 | const Resource = union(enum) { |
| 885 | file: fs.File.Reader, | |
| 888 | file: Io.File.Reader, | |
| 886 | 889 | http_request: HttpRequest, |
| 887 | 890 | git: Git, |
| 888 | dir: fs.Dir, | |
| 891 | dir: Io.Dir, | |
| 889 | 892 | |
| 890 | 893 | const Git = struct { |
| 891 | 894 | session: git.Session, |
| ... | ... | @@ -908,7 +911,7 @@ const Resource = union(enum) { |
| 908 | 911 | .git => |*git_resource| { |
| 909 | 912 | git_resource.fetch_stream.deinit(); |
| 910 | 913 | }, |
| 911 | .dir => |*dir| dir.close(), | |
| 914 | .dir => |*dir| dir.close(io), | |
| 912 | 915 | } |
| 913 | 916 | resource.* = undefined; |
| 914 | 917 | } |
| ... | ... | @@ -995,7 +998,7 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u |
| 995 | 998 | |
| 996 | 999 | if (ascii.eqlIgnoreCase(uri.scheme, "file")) { |
| 997 | 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 | 1002 | return f.fail(f.location_tok, try eb.printString("unable to open '{f}{s}': {t}", .{ |
| 1000 | 1003 | f.parent_package_root, path, err, |
| 1001 | 1004 | })); |
| ... | ... | @@ -1247,13 +1250,14 @@ fn unpackResource( |
| 1247 | 1250 | } |
| 1248 | 1251 | } |
| 1249 | 1252 | |
| 1250 | fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) RunError!UnpackResult { | |
| 1253 | fn unpackTarball(f: *Fetch, out_dir: Io.Dir, reader: *Io.Reader) RunError!UnpackResult { | |
| 1251 | 1254 | const eb = &f.error_bundle; |
| 1252 | 1255 | const arena = f.arena.allocator(); |
| 1256 | const io = f.job_queue.io; | |
| 1253 | 1257 | |
| 1254 | 1258 | var diagnostics: std.tar.Diagnostics = .{ .allocator = arena }; |
| 1255 | 1259 | |
| 1256 | std.tar.pipeToFileSystem(out_dir, reader, .{ | |
| 1260 | std.tar.pipeToFileSystem(io, out_dir, reader, .{ | |
| 1257 | 1261 | .diagnostics = &diagnostics, |
| 1258 | 1262 | .strip_components = 0, |
| 1259 | 1263 | .mode_mode = .ignore, |
| ... | ... | @@ -1280,7 +1284,7 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) RunError!Unpack |
| 1280 | 1284 | |
| 1281 | 1285 | fn unzip( |
| 1282 | 1286 | f: *Fetch, |
| 1283 | out_dir: fs.Dir, | |
| 1287 | out_dir: Io.Dir, | |
| 1284 | 1288 | reader: *Io.Reader, |
| 1285 | 1289 | ) error{ ReadFailed, OutOfMemory, Canceled, FetchFailed }!UnpackResult { |
| 1286 | 1290 | // We write the entire contents to a file first because zip files |
| ... | ... | @@ -1302,7 +1306,7 @@ fn unzip( |
| 1302 | 1306 | const random_integer = std.crypto.random.int(u64); |
| 1303 | 1307 | zip_path[prefix.len..][0..random_len].* = std.fmt.hex(random_integer); |
| 1304 | 1308 | |
| 1305 | break cache_root.handle.createFile(&zip_path, .{ | |
| 1309 | break cache_root.handle.createFile(io, &zip_path, .{ | |
| 1306 | 1310 | .exclusive = true, |
| 1307 | 1311 | .read = true, |
| 1308 | 1312 | }) catch |err| switch (err) { |
| ... | ... | @@ -1314,10 +1318,10 @@ fn unzip( |
| 1314 | 1318 | ), |
| 1315 | 1319 | }; |
| 1316 | 1320 | }; |
| 1317 | defer zip_file.close(); | |
| 1321 | defer zip_file.close(io); | |
| 1318 | 1322 | var zip_file_buffer: [4096]u8 = undefined; |
| 1319 | 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); | |
| 1321 | 1325 | |
| 1322 | 1326 | _ = reader.streamRemaining(&zip_file_writer.interface) catch |err| switch (err) { |
| 1323 | 1327 | error.ReadFailed => return error.ReadFailed, |
| ... | ... | @@ -1330,7 +1334,7 @@ fn unzip( |
| 1330 | 1334 | f.location_tok, |
| 1331 | 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 | }; |
| 1335 | 1339 | |
| 1336 | 1340 | var diagnostics: std.zip.Diagnostics = .{ .allocator = f.arena.allocator() }; |
| ... | ... | @@ -1343,13 +1347,13 @@ fn unzip( |
| 1343 | 1347 | .diagnostics = &diagnostics, |
| 1344 | 1348 | }) catch |err| return f.fail(f.location_tok, try eb.printString("zip extract failed: {t}", .{err})); |
| 1345 | 1349 | |
| 1346 | cache_root.handle.deleteFile(&zip_path) catch |err| | |
| 1350 | cache_root.handle.deleteFile(io, &zip_path) catch |err| | |
| 1347 | 1351 | return f.fail(f.location_tok, try eb.printString("delete temporary zip failed: {t}", .{err})); |
| 1348 | 1352 | |
| 1349 | 1353 | return .{ .root_dir = diagnostics.root_dir }; |
| 1350 | 1354 | } |
| 1351 | 1355 | |
| 1352 | fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!UnpackResult { | |
| 1356 | fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!UnpackResult { | |
| 1353 | 1357 | const io = f.job_queue.io; |
| 1354 | 1358 | const arena = f.arena.allocator(); |
| 1355 | 1359 | // 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 | 1366 | // we do not attempt to replicate the exact structure of a real .git |
| 1363 | 1367 | // directory, since that isn't relevant for fetching a package. |
| 1364 | 1368 | { |
| 1365 | var pack_dir = try out_dir.makeOpenPath(".git", .{}); | |
| 1366 | defer pack_dir.close(); | |
| 1367 | var pack_file = try pack_dir.createFile("pkg.pack", .{ .read = true }); | |
| 1368 | defer pack_file.close(); | |
| 1369 | var pack_dir = try out_dir.createDirPathOpen(io, ".git", .{}); | |
| 1370 | defer pack_dir.close(io); | |
| 1371 | var pack_file = try pack_dir.createFile(io, "pkg.pack", .{ .read = true }); | |
| 1372 | defer pack_file.close(io); | |
| 1369 | 1373 | var pack_file_buffer: [4096]u8 = undefined; |
| 1370 | 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 | 1376 | const fetch_reader = &resource.fetch_stream.reader; |
| 1373 | 1377 | _ = try fetch_reader.streamRemaining(&pack_file_writer.interface); |
| 1374 | 1378 | try pack_file_writer.interface.flush(); |
| 1375 | break :b pack_file_writer.moveToReader(io); | |
| 1379 | break :b pack_file_writer.moveToReader(); | |
| 1376 | 1380 | }; |
| 1377 | 1381 | |
| 1378 | var index_file = try pack_dir.createFile("pkg.idx", .{ .read = true }); | |
| 1379 | defer index_file.close(); | |
| 1382 | var index_file = try pack_dir.createFile(io, "pkg.idx", .{ .read = true }); | |
| 1383 | defer index_file.close(io); | |
| 1380 | 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 | 1387 | const index_prog_node = f.prog_node.start("Index pack", 0); |
| 1384 | 1388 | defer index_prog_node.end(); |
| ... | ... | @@ -1393,7 +1397,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U |
| 1393 | 1397 | try repository.init(gpa, object_format, &pack_file_reader, &index_file_reader); |
| 1394 | 1398 | defer repository.deinit(); |
| 1395 | 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); | |
| 1397 | 1401 | |
| 1398 | 1402 | if (diagnostics.errors.items.len > 0) { |
| 1399 | 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 | 1411 | } |
| 1408 | 1412 | } |
| 1409 | 1413 | |
| 1410 | try out_dir.deleteTree(".git"); | |
| 1414 | try out_dir.deleteTree(io, ".git"); | |
| 1411 | 1415 | return res; |
| 1412 | 1416 | } |
| 1413 | 1417 | |
| 1414 | fn recursiveDirectoryCopy(f: *Fetch, dir: fs.Dir, tmp_dir: fs.Dir) anyerror!void { | |
| 1418 | fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void { | |
| 1415 | 1419 | const gpa = f.arena.child_allocator; |
| 1420 | const io = f.job_queue.io; | |
| 1416 | 1421 | // Recursive directory copy. |
| 1417 | 1422 | var it = try dir.walk(gpa); |
| 1418 | 1423 | defer it.deinit(); |
| 1419 | while (try it.next()) |entry| { | |
| 1424 | while (try it.next(io)) |entry| { | |
| 1420 | 1425 | switch (entry.kind) { |
| 1421 | 1426 | .directory => {}, // omit empty directories |
| 1422 | 1427 | .file => { |
| 1423 | dir.copyFile( | |
| 1424 | entry.path, | |
| 1425 | tmp_dir, | |
| 1426 | entry.path, | |
| 1427 | .{}, | |
| 1428 | ) catch |err| switch (err) { | |
| 1428 | dir.copyFile(entry.path, tmp_dir, entry.path, io, .{}) catch |err| switch (err) { | |
| 1429 | 1429 | error.FileNotFound => { |
| 1430 | if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname); | |
| 1431 | try dir.copyFile(entry.path, tmp_dir, entry.path, .{}); | |
| 1430 | if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.createDirPath(io, dirname); | |
| 1431 | try dir.copyFile(entry.path, tmp_dir, entry.path, io, .{}); | |
| 1432 | 1432 | }, |
| 1433 | 1433 | else => |e| return e, |
| 1434 | 1434 | }; |
| 1435 | 1435 | }, |
| 1436 | 1436 | .sym_link => { |
| 1437 | 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 | 1439 | // TODO: if this would create a symlink to outside |
| 1440 | 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 | 1442 | error.FileNotFound => { |
| 1443 | if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname); | |
| 1444 | try tmp_dir.symLink(link_name, entry.path, .{}); | |
| 1443 | if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.createDirPath(io, dirname); | |
| 1444 | try tmp_dir.symLink(io, link_name, entry.path, .{}); | |
| 1445 | 1445 | }, |
| 1446 | 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 | 1451 | } |
| 1452 | 1452 | } |
| 1453 | 1453 | |
| 1454 | pub fn renameTmpIntoCache(cache_dir: fs.Dir, tmp_dir_sub_path: []const u8, dest_dir_sub_path: []const u8) !void { | |
| 1454 | pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u8, dest_dir_sub_path: []const u8) !void { | |
| 1455 | 1455 | assert(dest_dir_sub_path[1] == fs.path.sep); |
| 1456 | 1456 | var handled_missing_dir = false; |
| 1457 | 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 | 1459 | error.FileNotFound => { |
| 1460 | 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 | 1462 | error.PathAlreadyExists => handled_missing_dir = true, |
| 1463 | 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 | 1466 | }, |
| 1467 | 1467 | error.PathAlreadyExists, error.AccessDenied => { |
| 1468 | 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 | 1470 | // Garbage files leftover in zig-cache/tmp/ is, as they say |
| 1471 | 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 | 1519 | var group: Io.Group = .init; |
| 1520 | 1520 | defer group.wait(io); |
| 1521 | 1521 | |
| 1522 | while (walker.next() catch |err| { | |
| 1522 | while (walker.next(io) catch |err| { | |
| 1523 | 1523 | try eb.addRootErrorMessage(.{ .msg = try eb.printString( |
| 1524 | 1524 | "unable to walk temporary directory '{f}': {s}", |
| 1525 | 1525 | .{ pkg_path, @errorName(err) }, |
| ... | ... | @@ -1542,7 +1542,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute |
| 1542 | 1542 | .fs_path = fs_path, |
| 1543 | 1543 | .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 | 1546 | try deleted_files.append(deleted_file); |
| 1547 | 1547 | continue; |
| 1548 | 1548 | } |
| ... | ... | @@ -1570,7 +1570,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute |
| 1570 | 1570 | .failure = undefined, // to be populated by the worker |
| 1571 | 1571 | .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 | 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 | 1588 | var i: usize = 0; |
| 1589 | 1589 | while (i < sus_dirs.count()) : (i += 1) { |
| 1590 | 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 | 1592 | error.DirNotEmpty => continue, |
| 1593 | 1593 | error.FileNotFound => continue, |
| 1594 | 1594 | else => |e| { |
| ... | ... | @@ -1638,7 +1638,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute |
| 1638 | 1638 | assert(!f.job_queue.recursive); |
| 1639 | 1639 | // Print something to stdout that can be text diffed to figure out why |
| 1640 | 1640 | // the package hash is different. |
| 1641 | dumpHashInfo(all_files.items) catch |err| { | |
| 1641 | dumpHashInfo(io, all_files.items) catch |err| { | |
| 1642 | 1642 | std.debug.print("unable to write to stdout: {s}\n", .{@errorName(err)}); |
| 1643 | 1643 | std.process.exit(1); |
| 1644 | 1644 | }; |
| ... | ... | @@ -1650,9 +1650,9 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute |
| 1650 | 1650 | }; |
| 1651 | 1651 | } |
| 1652 | 1652 | |
| 1653 | fn dumpHashInfo(all_files: []const *const HashedFile) !void { | |
| 1653 | fn dumpHashInfo(io: Io, all_files: []const *const HashedFile) !void { | |
| 1654 | 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 | 1656 | const w = &stdout_writer.interface; |
| 1657 | 1657 | for (all_files) |hashed_file| { |
| 1658 | 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 | 1660 | try w.flush(); |
| 1661 | 1661 | } |
| 1662 | 1662 | |
| 1663 | fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile) void { | |
| 1664 | hashed_file.failure = hashFileFallible(dir, hashed_file); | |
| 1663 | fn workerHashFile(io: Io, dir: Io.Dir, hashed_file: *HashedFile) void { | |
| 1664 | hashed_file.failure = hashFileFallible(io, dir, hashed_file); | |
| 1665 | 1665 | } |
| 1666 | 1666 | |
| 1667 | fn workerDeleteFile(dir: fs.Dir, deleted_file: *DeletedFile) void { | |
| 1668 | deleted_file.failure = deleteFileFallible(dir, deleted_file); | |
| 1667 | fn workerDeleteFile(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) void { | |
| 1668 | deleted_file.failure = deleteFileFallible(io, dir, deleted_file); | |
| 1669 | 1669 | } |
| 1670 | 1670 | |
| 1671 | fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void { | |
| 1671 | fn hashFileFallible(io: Io, dir: Io.Dir, hashed_file: *HashedFile) HashedFile.Error!void { | |
| 1672 | 1672 | var buf: [8000]u8 = undefined; |
| 1673 | 1673 | var hasher = Package.Hash.Algo.init(.{}); |
| 1674 | 1674 | hasher.update(hashed_file.normalized_path); |
| ... | ... | @@ -1676,24 +1676,24 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void |
| 1676 | 1676 | |
| 1677 | 1677 | switch (hashed_file.kind) { |
| 1678 | 1678 | .file => { |
| 1679 | var file = try dir.openFile(hashed_file.fs_path, .{}); | |
| 1680 | defer file.close(); | |
| 1679 | var file = try dir.openFile(io, hashed_file.fs_path, .{}); | |
| 1680 | defer file.close(io); | |
| 1681 | 1681 | // Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463 |
| 1682 | 1682 | hasher.update(&.{ 0, 0 }); |
| 1683 | 1683 | var file_header: FileHeader = .{}; |
| 1684 | 1684 | while (true) { |
| 1685 | const bytes_read = try file.read(&buf); | |
| 1685 | const bytes_read = try file.readPositional(io, &.{&buf}, file_size); | |
| 1686 | 1686 | if (bytes_read == 0) break; |
| 1687 | 1687 | file_size += bytes_read; |
| 1688 | 1688 | hasher.update(buf[0..bytes_read]); |
| 1689 | 1689 | file_header.update(buf[0..bytes_read]); |
| 1690 | 1690 | } |
| 1691 | 1691 | if (file_header.isExecutable()) { |
| 1692 | try setExecutable(file); | |
| 1692 | try setExecutable(io, file); | |
| 1693 | 1693 | } |
| 1694 | 1694 | }, |
| 1695 | 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 | 1697 | if (fs.path.sep != canonical_sep) { |
| 1698 | 1698 | // Package hashes are intended to be consistent across |
| 1699 | 1699 | // platforms which means we must normalize path separators |
| ... | ... | @@ -1707,16 +1707,13 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void |
| 1707 | 1707 | hashed_file.size = file_size; |
| 1708 | 1708 | } |
| 1709 | 1709 | |
| 1710 | fn deleteFileFallible(dir: fs.Dir, deleted_file: *DeletedFile) DeletedFile.Error!void { | |
| 1711 | try dir.deleteFile(deleted_file.fs_path); | |
| 1710 | fn deleteFileFallible(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) DeletedFile.Error!void { | |
| 1711 | try dir.deleteFile(io, deleted_file.fs_path); | |
| 1712 | 1712 | } |
| 1713 | 1713 | |
| 1714 | fn setExecutable(file: fs.File) !void { | |
| 1715 | if (!std.fs.has_executable_bit) return; | |
| 1716 | ||
| 1717 | const S = std.posix.S; | |
| 1718 | const mode = fs.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH; | |
| 1719 | try file.chmod(mode); | |
| 1714 | fn setExecutable(io: Io, file: Io.File) !void { | |
| 1715 | if (!Io.File.Permissions.has_executable_bit) return; | |
| 1716 | try file.setPermissions(io, .executable_file); | |
| 1720 | 1717 | } |
| 1721 | 1718 | |
| 1722 | 1719 | const DeletedFile = struct { |
| ... | ... | @@ -1724,8 +1721,8 @@ const DeletedFile = struct { |
| 1724 | 1721 | failure: Error!void, |
| 1725 | 1722 | |
| 1726 | 1723 | const Error = |
| 1727 | fs.Dir.DeleteFileError || | |
| 1728 | fs.Dir.DeleteDirError; | |
| 1724 | Io.Dir.DeleteFileError || | |
| 1725 | Io.Dir.DeleteDirError; | |
| 1729 | 1726 | }; |
| 1730 | 1727 | |
| 1731 | 1728 | const HashedFile = struct { |
| ... | ... | @@ -1737,11 +1734,11 @@ const HashedFile = struct { |
| 1737 | 1734 | size: u64, |
| 1738 | 1735 | |
| 1739 | 1736 | const Error = |
| 1740 | fs.File.OpenError || | |
| 1741 | fs.File.ReadError || | |
| 1742 | fs.File.StatError || | |
| 1743 | fs.File.ChmodError || | |
| 1744 | fs.Dir.ReadLinkError; | |
| 1737 | Io.File.OpenError || | |
| 1738 | Io.File.ReadPositionalError || | |
| 1739 | Io.File.StatError || | |
| 1740 | Io.File.SetPermissionsError || | |
| 1741 | Io.Dir.ReadLinkError; | |
| 1745 | 1742 | |
| 1746 | 1743 | const Kind = enum { file, link }; |
| 1747 | 1744 | |
| ... | ... | @@ -2043,7 +2040,7 @@ const UnpackResult = struct { |
| 2043 | 2040 | defer errors.deinit(gpa); |
| 2044 | 2041 | var aw: Io.Writer.Allocating = .init(gpa); |
| 2045 | 2042 | defer aw.deinit(); |
| 2046 | try errors.renderToWriter(.{}, &aw.writer, .no_color); | |
| 2043 | try errors.renderToWriter(.{}, &aw.writer); | |
| 2047 | 2044 | try std.testing.expectEqualStrings( |
| 2048 | 2045 | \\error: unable to unpack |
| 2049 | 2046 | \\ note: unable to create symlink from 'dir2/file2' to 'filename': SymlinkError |
| ... | ... | @@ -2074,7 +2071,7 @@ test "tarball with duplicate paths" { |
| 2074 | 2071 | defer tmp.cleanup(); |
| 2075 | 2072 | |
| 2076 | 2073 | const tarball_name = "duplicate_paths.tar.gz"; |
| 2077 | try saveEmbedFile(tarball_name, tmp.dir); | |
| 2074 | try saveEmbedFile(io, tarball_name, tmp.dir); | |
| 2078 | 2075 | const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); |
| 2079 | 2076 | defer gpa.free(tarball_path); |
| 2080 | 2077 | |
| ... | ... | @@ -2107,7 +2104,7 @@ test "tarball with excluded duplicate paths" { |
| 2107 | 2104 | defer tmp.cleanup(); |
| 2108 | 2105 | |
| 2109 | 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 | 2108 | const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); |
| 2112 | 2109 | defer gpa.free(tarball_path); |
| 2113 | 2110 | |
| ... | ... | @@ -2153,7 +2150,7 @@ test "tarball without root folder" { |
| 2153 | 2150 | defer tmp.cleanup(); |
| 2154 | 2151 | |
| 2155 | 2152 | const tarball_name = "no_root.tar.gz"; |
| 2156 | try saveEmbedFile(tarball_name, tmp.dir); | |
| 2153 | try saveEmbedFile(io, tarball_name, tmp.dir); | |
| 2157 | 2154 | const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); |
| 2158 | 2155 | defer gpa.free(tarball_path); |
| 2159 | 2156 | |
| ... | ... | @@ -2178,7 +2175,7 @@ test "tarball without root folder" { |
| 2178 | 2175 | } |
| 2179 | 2176 | |
| 2180 | 2177 | test "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 | 2179 | const gpa = std.testing.allocator; |
| 2183 | 2180 | const io = std.testing.io; |
| 2184 | 2181 | |
| ... | ... | @@ -2186,7 +2183,7 @@ test "set executable bit based on file content" { |
| 2186 | 2183 | defer tmp.cleanup(); |
| 2187 | 2184 | |
| 2188 | 2185 | const tarball_name = "executables.tar.gz"; |
| 2189 | try saveEmbedFile(tarball_name, tmp.dir); | |
| 2186 | try saveEmbedFile(io, tarball_name, tmp.dir); | |
| 2190 | 2187 | const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); |
| 2191 | 2188 | defer gpa.free(tarball_path); |
| 2192 | 2189 | |
| ... | ... | @@ -2210,16 +2207,16 @@ test "set executable bit based on file content" { |
| 2210 | 2207 | ); |
| 2211 | 2208 | |
| 2212 | 2209 | var out = try fb.packageDir(); |
| 2213 | defer out.close(); | |
| 2210 | defer out.close(io); | |
| 2214 | 2211 | const S = std.posix.S; |
| 2215 | 2212 | // expect executable bit not set |
| 2216 | try std.testing.expect((try out.statFile("file1")).mode & S.IXUSR == 0); | |
| 2217 | try std.testing.expect((try out.statFile("script_without_shebang")).mode & S.IXUSR == 0); | |
| 2213 | try std.testing.expect((try out.statFile(io, "file1", .{})).permissions.toMode() & S.IXUSR == 0); | |
| 2214 | try std.testing.expect((try out.statFile(io, "script_without_shebang", .{})).permissions.toMode() & S.IXUSR == 0); | |
| 2218 | 2215 | // expect executable bit set |
| 2219 | try std.testing.expect((try out.statFile("hello")).mode & S.IXUSR != 0); | |
| 2220 | try std.testing.expect((try out.statFile("script")).mode & S.IXUSR != 0); | |
| 2221 | try std.testing.expect((try out.statFile("script_with_shebang_without_exec_bit")).mode & S.IXUSR != 0); | |
| 2222 | try std.testing.expect((try out.statFile("hello_ln")).mode & S.IXUSR != 0); | |
| 2216 | try std.testing.expect((try out.statFile(io, "hello", .{})).permissions.toMode() & S.IXUSR != 0); | |
| 2217 | try std.testing.expect((try out.statFile(io, "script", .{})).permissions.toMode() & S.IXUSR != 0); | |
| 2218 | try std.testing.expect((try out.statFile(io, "script_with_shebang_without_exec_bit", .{})).permissions.toMode() & S.IXUSR != 0); | |
| 2219 | try std.testing.expect((try out.statFile(io, "hello_ln", .{})).permissions.toMode() & S.IXUSR != 0); | |
| 2223 | 2220 | |
| 2224 | 2221 | // |
| 2225 | 2222 | // $ 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 | 2228 | // -rwxrwxr-x 1 17 Apr script_with_shebang_without_exec_bit |
| 2232 | 2229 | } |
| 2233 | 2230 | |
| 2234 | fn saveEmbedFile(comptime tarball_name: []const u8, dir: fs.Dir) !void { | |
| 2231 | fn saveEmbedFile(io: Io, comptime tarball_name: []const u8, dir: Io.Dir) !void { | |
| 2235 | 2232 | //const tarball_name = "duplicate_paths_excluded.tar.gz"; |
| 2236 | 2233 | const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name); |
| 2237 | var tmp_file = try dir.createFile(tarball_name, .{}); | |
| 2238 | defer tmp_file.close(); | |
| 2239 | try tmp_file.writeAll(tarball_content); | |
| 2234 | var tmp_file = try dir.createFile(io, tarball_name, .{}); | |
| 2235 | defer tmp_file.close(io); | |
| 2236 | try tmp_file.writeStreamingAll(io, tarball_content); | |
| 2240 | 2237 | } |
| 2241 | 2238 | |
| 2242 | 2239 | // Builds Fetch with required dependencies, clears dependencies on deinit(). |
| ... | ... | @@ -2250,10 +2247,10 @@ const TestFetchBuilder = struct { |
| 2250 | 2247 | self: *TestFetchBuilder, |
| 2251 | 2248 | allocator: std.mem.Allocator, |
| 2252 | 2249 | io: Io, |
| 2253 | cache_parent_dir: std.fs.Dir, | |
| 2250 | cache_parent_dir: std.Io.Dir, | |
| 2254 | 2251 | path_or_url: []const u8, |
| 2255 | 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", .{}); | |
| 2257 | 2254 | |
| 2258 | 2255 | self.http_client = .{ .allocator = allocator, .io = io }; |
| 2259 | 2256 | self.global_cache_directory = .{ .handle = cache_dir, .path = null }; |
| ... | ... | @@ -2301,35 +2298,40 @@ const TestFetchBuilder = struct { |
| 2301 | 2298 | } |
| 2302 | 2299 | |
| 2303 | 2300 | fn deinit(self: *TestFetchBuilder) void { |
| 2301 | const io = self.job_queue.io; | |
| 2304 | 2302 | self.fetch.deinit(); |
| 2305 | 2303 | self.job_queue.deinit(); |
| 2306 | 2304 | self.fetch.prog_node.end(); |
| 2307 | self.global_cache_directory.handle.close(); | |
| 2305 | self.global_cache_directory.handle.close(io); | |
| 2308 | 2306 | self.http_client.deinit(); |
| 2309 | 2307 | } |
| 2310 | 2308 | |
| 2311 | fn packageDir(self: *TestFetchBuilder) !fs.Dir { | |
| 2309 | fn packageDir(self: *TestFetchBuilder) !Io.Dir { | |
| 2310 | const io = self.job_queue.io; | |
| 2312 | 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 | } |
| 2315 | 2314 | |
| 2316 | 2315 | // Test helper, asserts thet package dir constains expected_files. |
| 2317 | 2316 | // expected_files must be sorted. |
| 2318 | 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 | 2321 | var package_dir = try self.packageDir(); |
| 2320 | defer package_dir.close(); | |
| 2322 | defer package_dir.close(io); | |
| 2321 | 2323 | |
| 2322 | 2324 | var actual_files: std.ArrayList([]u8) = .empty; |
| 2323 | defer actual_files.deinit(std.testing.allocator); | |
| 2324 | defer for (actual_files.items) |file| std.testing.allocator.free(file); | |
| 2325 | var walker = try package_dir.walk(std.testing.allocator); | |
| 2325 | defer actual_files.deinit(gpa); | |
| 2326 | defer for (actual_files.items) |file| gpa.free(file); | |
| 2327 | var walker = try package_dir.walk(gpa); | |
| 2326 | 2328 | defer walker.deinit(); |
| 2327 | while (try walker.next()) |entry| { | |
| 2329 | while (try walker.next(io)) |entry| { | |
| 2328 | 2330 | if (entry.kind != .file) continue; |
| 2329 | const path = try std.testing.allocator.dupe(u8, entry.path); | |
| 2330 | errdefer std.testing.allocator.free(path); | |
| 2331 | const path = try gpa.dupe(u8, entry.path); | |
| 2332 | errdefer gpa.free(path); | |
| 2331 | 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 | 2336 | std.mem.sortUnstable([]u8, actual_files.items, {}, struct { |
| 2335 | 2337 | fn lessThan(_: void, a: []u8, b: []u8) bool { |
| ... | ... | @@ -2346,17 +2348,19 @@ const TestFetchBuilder = struct { |
| 2346 | 2348 | |
| 2347 | 2349 | // Test helper, asserts that fetch has failed with `msg` error message. |
| 2348 | 2350 | fn expectFetchErrors(self: *TestFetchBuilder, notes_len: usize, msg: []const u8) !void { |
| 2351 | const gpa = std.testing.allocator; | |
| 2352 | ||
| 2349 | 2353 | var errors = try self.fetch.error_bundle.toOwnedBundle(""); |
| 2350 | defer errors.deinit(std.testing.allocator); | |
| 2354 | defer errors.deinit(gpa); | |
| 2351 | 2355 | |
| 2352 | 2356 | const em = errors.getErrorMessage(errors.getMessages()[0]); |
| 2353 | 2357 | try std.testing.expectEqual(1, em.count); |
| 2354 | 2358 | if (notes_len > 0) { |
| 2355 | 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 | 2362 | defer aw.deinit(); |
| 2359 | try errors.renderToWriter(.{}, &aw.writer, .no_color); | |
| 2363 | try errors.renderToWriter(.{}, &aw.writer); | |
| 2360 | 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 | 198 | repo: *Repository, |
| 199 | 199 | allocator: Allocator, |
| 200 | 200 | format: Oid.Format, |
| 201 | pack_file: *std.fs.File.Reader, | |
| 202 | index_file: *std.fs.File.Reader, | |
| 201 | pack_file: *Io.File.Reader, | |
| 202 | index_file: *Io.File.Reader, | |
| 203 | 203 | ) !void { |
| 204 | 204 | repo.* = .{ .odb = undefined }; |
| 205 | 205 | try repo.odb.init(allocator, format, pack_file, index_file); |
| ... | ... | @@ -213,7 +213,8 @@ pub const Repository = struct { |
| 213 | 213 | /// Checks out the repository at `commit_oid` to `worktree`. |
| 214 | 214 | pub fn checkout( |
| 215 | 215 | repository: *Repository, |
| 216 | worktree: std.fs.Dir, | |
| 216 | io: Io, | |
| 217 | worktree: Io.Dir, | |
| 217 | 218 | commit_oid: Oid, |
| 218 | 219 | diagnostics: *Diagnostics, |
| 219 | 220 | ) !void { |
| ... | ... | @@ -223,13 +224,14 @@ pub const Repository = struct { |
| 223 | 224 | if (commit_object.type != .commit) return error.NotACommit; |
| 224 | 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 | } |
| 228 | 229 | |
| 229 | 230 | /// Checks out the tree at `tree_oid` to `worktree`. |
| 230 | 231 | fn checkoutTree( |
| 231 | 232 | repository: *Repository, |
| 232 | dir: std.fs.Dir, | |
| 233 | io: Io, | |
| 234 | dir: Io.Dir, | |
| 233 | 235 | tree_oid: Oid, |
| 234 | 236 | current_path: []const u8, |
| 235 | 237 | diagnostics: *Diagnostics, |
| ... | ... | @@ -251,18 +253,18 @@ pub const Repository = struct { |
| 251 | 253 | while (try tree_iter.next()) |entry| { |
| 252 | 254 | switch (entry.type) { |
| 253 | 255 | .directory => { |
| 254 | try dir.makeDir(entry.name); | |
| 255 | var subdir = try dir.openDir(entry.name, .{}); | |
| 256 | defer subdir.close(); | |
| 256 | try dir.createDir(io, entry.name, .default_dir); | |
| 257 | var subdir = try dir.openDir(io, entry.name, .{}); | |
| 258 | defer subdir.close(io); | |
| 257 | 259 | const sub_path = try std.fs.path.join(repository.odb.allocator, &.{ current_path, entry.name }); |
| 258 | 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 | 263 | .file => { |
| 262 | 264 | try repository.odb.seekOid(entry.oid); |
| 263 | 265 | const file_object = try repository.odb.readObject(); |
| 264 | 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 | 268 | const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name }); |
| 267 | 269 | errdefer diagnostics.allocator.free(file_name); |
| 268 | 270 | try diagnostics.errors.append(diagnostics.allocator, .{ .unable_to_create_file = .{ |
| ... | ... | @@ -271,15 +273,15 @@ pub const Repository = struct { |
| 271 | 273 | } }); |
| 272 | 274 | continue; |
| 273 | 275 | }; |
| 274 | defer file.close(); | |
| 275 | try file.writeAll(file_object.data); | |
| 276 | defer file.close(io); | |
| 277 | try file.writePositionalAll(io, file_object.data, 0); | |
| 276 | 278 | }, |
| 277 | 279 | .symlink => { |
| 278 | 280 | try repository.odb.seekOid(entry.oid); |
| 279 | 281 | const symlink_object = try repository.odb.readObject(); |
| 280 | 282 | if (symlink_object.type != .blob) return error.InvalidFile; |
| 281 | 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 | 285 | const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name }); |
| 284 | 286 | errdefer diagnostics.allocator.free(file_name); |
| 285 | 287 | const link_name_dup = try diagnostics.allocator.dupe(u8, link_name); |
| ... | ... | @@ -294,7 +296,7 @@ pub const Repository = struct { |
| 294 | 296 | .gitlink => { |
| 295 | 297 | // Consistent with git archive behavior, create the directory but |
| 296 | 298 | // 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 | 372 | /// [pack-format](https://git-scm.com/docs/pack-format). |
| 371 | 373 | const Odb = struct { |
| 372 | 374 | format: Oid.Format, |
| 373 | pack_file: *std.fs.File.Reader, | |
| 375 | pack_file: *Io.File.Reader, | |
| 374 | 376 | index_header: IndexHeader, |
| 375 | index_file: *std.fs.File.Reader, | |
| 377 | index_file: *Io.File.Reader, | |
| 376 | 378 | cache: ObjectCache = .{}, |
| 377 | 379 | allocator: Allocator, |
| 378 | 380 | |
| ... | ... | @@ -381,8 +383,8 @@ const Odb = struct { |
| 381 | 383 | odb: *Odb, |
| 382 | 384 | allocator: Allocator, |
| 383 | 385 | format: Oid.Format, |
| 384 | pack_file: *std.fs.File.Reader, | |
| 385 | index_file: *std.fs.File.Reader, | |
| 386 | pack_file: *Io.File.Reader, | |
| 387 | index_file: *Io.File.Reader, | |
| 386 | 388 | ) !void { |
| 387 | 389 | try pack_file.seekTo(0); |
| 388 | 390 | try index_file.seekTo(0); |
| ... | ... | @@ -1270,8 +1272,8 @@ const IndexEntry = struct { |
| 1270 | 1272 | pub fn indexPack( |
| 1271 | 1273 | allocator: Allocator, |
| 1272 | 1274 | format: Oid.Format, |
| 1273 | pack: *std.fs.File.Reader, | |
| 1274 | index_writer: *std.fs.File.Writer, | |
| 1275 | pack: *Io.File.Reader, | |
| 1276 | index_writer: *Io.File.Writer, | |
| 1275 | 1277 | ) !void { |
| 1276 | 1278 | try pack.seekTo(0); |
| 1277 | 1279 | |
| ... | ... | @@ -1370,7 +1372,7 @@ pub fn indexPack( |
| 1370 | 1372 | fn indexPackFirstPass( |
| 1371 | 1373 | allocator: Allocator, |
| 1372 | 1374 | format: Oid.Format, |
| 1373 | pack: *std.fs.File.Reader, | |
| 1375 | pack: *Io.File.Reader, | |
| 1374 | 1376 | index_entries: *std.AutoHashMapUnmanaged(Oid, IndexEntry), |
| 1375 | 1377 | pending_deltas: *std.ArrayList(IndexEntry), |
| 1376 | 1378 | ) !Oid { |
| ... | ... | @@ -1423,7 +1425,7 @@ fn indexPackFirstPass( |
| 1423 | 1425 | fn indexPackHashDelta( |
| 1424 | 1426 | allocator: Allocator, |
| 1425 | 1427 | format: Oid.Format, |
| 1426 | pack: *std.fs.File.Reader, | |
| 1428 | pack: *Io.File.Reader, | |
| 1427 | 1429 | delta: IndexEntry, |
| 1428 | 1430 | index_entries: std.AutoHashMapUnmanaged(Oid, IndexEntry), |
| 1429 | 1431 | cache: *ObjectCache, |
| ... | ... | @@ -1475,7 +1477,7 @@ fn indexPackHashDelta( |
| 1475 | 1477 | fn resolveDeltaChain( |
| 1476 | 1478 | allocator: Allocator, |
| 1477 | 1479 | format: Oid.Format, |
| 1478 | pack: *std.fs.File.Reader, | |
| 1480 | pack: *Io.File.Reader, | |
| 1479 | 1481 | base_object: Object, |
| 1480 | 1482 | delta_offsets: []const u64, |
| 1481 | 1483 | cache: *ObjectCache, |
| ... | ... | @@ -1582,17 +1584,17 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u |
| 1582 | 1584 | |
| 1583 | 1585 | var git_dir = testing.tmpDir(.{}); |
| 1584 | 1586 | defer git_dir.cleanup(); |
| 1585 | var pack_file = try git_dir.dir.createFile("testrepo.pack", .{ .read = true }); | |
| 1586 | defer pack_file.close(); | |
| 1587 | try pack_file.writeAll(testrepo_pack); | |
| 1587 | var pack_file = try git_dir.dir.createFile(io, "testrepo.pack", .{ .read = true }); | |
| 1588 | defer pack_file.close(io); | |
| 1589 | try pack_file.writeStreamingAll(io, testrepo_pack); | |
| 1588 | 1590 | |
| 1589 | 1591 | var pack_file_buffer: [2000]u8 = undefined; |
| 1590 | 1592 | var pack_file_reader = pack_file.reader(io, &pack_file_buffer); |
| 1591 | 1593 | |
| 1592 | var index_file = try git_dir.dir.createFile("testrepo.idx", .{ .read = true }); | |
| 1593 | defer index_file.close(); | |
| 1594 | var index_file = try git_dir.dir.createFile(io, "testrepo.idx", .{ .read = true }); | |
| 1595 | defer index_file.close(io); | |
| 1594 | 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 | 1598 | try indexPack(testing.allocator, format, &pack_file_reader, &index_file_writer); |
| 1597 | 1599 | |
| 1598 | 1600 | // 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 | 1602 | const max_file_size = 8192; |
| 1601 | 1603 | |
| 1602 | 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 | 1606 | defer testing.allocator.free(index_file_data); |
| 1605 | 1607 | // testrepo.idx is generated by Git. The index created by this file should |
| 1606 | 1608 | // 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 | 1623 | |
| 1622 | 1624 | var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; |
| 1623 | 1625 | defer diagnostics.deinit(); |
| 1624 | try repository.checkout(worktree.dir, commit_id, &diagnostics); | |
| 1626 | try repository.checkout(io, worktree.dir, commit_id, &diagnostics); | |
| 1625 | 1627 | try testing.expect(diagnostics.errors.items.len == 0); |
| 1626 | 1628 | |
| 1627 | 1629 | const expected_files: []const []const u8 = &.{ |
| ... | ... | @@ -1646,7 +1648,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u |
| 1646 | 1648 | defer for (actual_files.items) |file| testing.allocator.free(file); |
| 1647 | 1649 | var walker = try worktree.dir.walk(testing.allocator); |
| 1648 | 1650 | defer walker.deinit(); |
| 1649 | while (try walker.next()) |entry| { | |
| 1651 | while (try walker.next(io)) |entry| { | |
| 1650 | 1652 | if (entry.kind != .file) continue; |
| 1651 | 1653 | const path = try testing.allocator.dupe(u8, entry.path); |
| 1652 | 1654 | errdefer testing.allocator.free(path); |
| ... | ... | @@ -1676,7 +1678,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u |
| 1676 | 1678 | \\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 | 1682 | defer testing.allocator.free(actual_file_contents); |
| 1681 | 1683 | try testing.expectEqualStrings(expected_file_contents, actual_file_contents); |
| 1682 | 1684 | } |
| ... | ... | @@ -1700,7 +1702,7 @@ test "SHA-256 packfile indexing and checkout" { |
| 1700 | 1702 | pub fn main() !void { |
| 1701 | 1703 | const allocator = std.heap.smp_allocator; |
| 1702 | 1704 | |
| 1703 | var threaded: Io.Threaded = .init(allocator); | |
| 1705 | var threaded: Io.Threaded = .init(allocator, .{}); | |
| 1704 | 1706 | defer threaded.deinit(); |
| 1705 | 1707 | const io = threaded.io(); |
| 1706 | 1708 | |
| ... | ... | @@ -1712,23 +1714,23 @@ pub fn main() !void { |
| 1712 | 1714 | |
| 1713 | 1715 | const format = std.meta.stringToEnum(Oid.Format, args[1]) orelse return error.InvalidFormat; |
| 1714 | 1716 | |
| 1715 | var pack_file = try std.fs.cwd().openFile(args[2], .{}); | |
| 1716 | defer pack_file.close(); | |
| 1717 | var pack_file = try Io.Dir.cwd().openFile(io, args[2], .{}); | |
| 1718 | defer pack_file.close(io); | |
| 1717 | 1719 | var pack_file_buffer: [4096]u8 = undefined; |
| 1718 | 1720 | var pack_file_reader = pack_file.reader(io, &pack_file_buffer); |
| 1719 | 1721 | |
| 1720 | 1722 | const commit = try Oid.parse(format, args[3]); |
| 1721 | var worktree = try std.fs.cwd().makeOpenPath(args[4], .{}); | |
| 1722 | defer worktree.close(); | |
| 1723 | var worktree = try Io.Dir.cwd().createDirPathOpen(io, args[4], .{}); | |
| 1724 | defer worktree.close(io); | |
| 1723 | 1725 | |
| 1724 | var git_dir = try worktree.makeOpenPath(".git", .{}); | |
| 1725 | defer git_dir.close(); | |
| 1726 | var git_dir = try worktree.createDirPathOpen(io, ".git", .{}); | |
| 1727 | defer git_dir.close(io); | |
| 1726 | 1728 | |
| 1727 | 1729 | std.debug.print("Starting index...\n", .{}); |
| 1728 | var index_file = try git_dir.createFile("idx", .{ .read = true }); | |
| 1729 | defer index_file.close(); | |
| 1730 | var index_file = try git_dir.createFile(io, "idx", .{ .read = true }); | |
| 1731 | defer index_file.close(io); | |
| 1730 | 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 | 1734 | try indexPack(allocator, format, &pack_file_reader, &index_file_writer); |
| 1733 | 1735 | |
| 1734 | 1736 | std.debug.print("Starting checkout...\n", .{}); |
| ... | ... | @@ -1738,7 +1740,7 @@ pub fn main() !void { |
| 1738 | 1740 | defer repository.deinit(); |
| 1739 | 1741 | var diagnostics: Diagnostics = .{ .allocator = allocator }; |
| 1740 | 1742 | defer diagnostics.deinit(); |
| 1741 | try repository.checkout(worktree, commit, &diagnostics); | |
| 1743 | try repository.checkout(io, worktree, commit, &diagnostics); | |
| 1742 | 1744 | |
| 1743 | 1745 | for (diagnostics.errors.items) |err| { |
| 1744 | 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 | 2668 | |
| 2669 | 2669 | pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg) error{ AnalysisFail, OutOfMemory } { |
| 2670 | 2670 | @branchHint(.cold); |
| 2671 | const gpa = sema.gpa; | |
| 2672 | 2671 | const zcu = sema.pt.zcu; |
| 2672 | const comp = zcu.comp; | |
| 2673 | const gpa = comp.gpa; | |
| 2674 | const io = comp.io; | |
| 2673 | 2675 | |
| 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 | 2677 | var wip_errors: std.zig.ErrorBundle.Wip = undefined; |
| 2676 | 2678 | wip_errors.init(gpa) catch @panic("out of memory"); |
| 2677 | 2679 | Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory"); |
| 2678 | 2680 | std.debug.print("compile error during Sema:\n", .{}); |
| 2679 | 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 | 2683 | std.debug.panicExtra(@returnAddress(), "unexpected compile error occurred", .{}); |
| 2682 | 2684 | } |
| 2683 | 2685 |
src/Zcu.zig+20-17| ... | ... | @@ -1076,11 +1076,11 @@ pub const File = struct { |
| 1076 | 1076 | |
| 1077 | 1077 | var f = f: { |
| 1078 | 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); | |
| 1082 | 1082 | |
| 1083 | const stat = f.stat() catch |err| switch (err) { | |
| 1083 | const stat = f.stat(io) catch |err| switch (err) { | |
| 1084 | 1084 | error.Streaming => { |
| 1085 | 1085 | // Since `file.stat` is populated, this was previously a file stream; since it is |
| 1086 | 1086 | // now not a file stream, it must have changed. |
| ... | ... | @@ -1200,7 +1200,7 @@ pub const EmbedFile = struct { |
| 1200 | 1200 | /// `.none` means the file was not loaded, so `stat` is undefined. |
| 1201 | 1201 | val: InternPool.Index, |
| 1202 | 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 | 1204 | stat: Cache.File.Stat, |
| 1205 | 1205 | |
| 1206 | 1206 | pub const Index = enum(u32) { |
| ... | ... | @@ -2813,8 +2813,8 @@ pub fn init(zcu: *Zcu, gpa: Allocator, io: Io, thread_count: usize) !void { |
| 2813 | 2813 | |
| 2814 | 2814 | pub fn deinit(zcu: *Zcu) void { |
| 2815 | 2815 | const comp = zcu.comp; |
| 2816 | const gpa = comp.gpa; | |
| 2817 | 2816 | const io = comp.io; |
| 2817 | const gpa = zcu.gpa; | |
| 2818 | 2818 | { |
| 2819 | 2819 | const pt: Zcu.PerThread = .activate(zcu, .main); |
| 2820 | 2820 | defer pt.deactivate(); |
| ... | ... | @@ -2835,8 +2835,8 @@ pub fn deinit(zcu: *Zcu) void { |
| 2835 | 2835 | } |
| 2836 | 2836 | zcu.embed_table.deinit(gpa); |
| 2837 | 2837 | |
| 2838 | zcu.local_zir_cache.handle.close(); | |
| 2839 | zcu.global_zir_cache.handle.close(); | |
| 2838 | zcu.local_zir_cache.handle.close(io); | |
| 2839 | zcu.global_zir_cache.handle.close(io); | |
| 2840 | 2840 | |
| 2841 | 2841 | for (zcu.failed_analysis.values()) |value| value.destroy(gpa); |
| 2842 | 2842 | for (zcu.failed_codegen.values()) |value| value.destroy(gpa); |
| ... | ... | @@ -2900,7 +2900,7 @@ pub fn deinit(zcu: *Zcu) void { |
| 2900 | 2900 | |
| 2901 | 2901 | if (zcu.resolved_references) |*r| r.deinit(gpa); |
| 2902 | 2902 | |
| 2903 | if (zcu.comp.debugIncremental()) { | |
| 2903 | if (comp.debugIncremental()) { | |
| 2904 | 2904 | zcu.incremental_debug_state.deinit(gpa); |
| 2905 | 2905 | } |
| 2906 | 2906 | } |
| ... | ... | @@ -2927,7 +2927,7 @@ comptime { |
| 2927 | 2927 | } |
| 2928 | 2928 | } |
| 2929 | 2929 | |
| 2930 | pub fn loadZirCache(gpa: Allocator, io: Io, cache_file: std.fs.File) !Zir { | |
| 2930 | pub fn loadZirCache(gpa: Allocator, io: Io, cache_file: Io.File) !Zir { | |
| 2931 | 2931 | var buffer: [2000]u8 = undefined; |
| 2932 | 2932 | var file_reader = cache_file.reader(io, &buffer); |
| 2933 | 2933 | return result: { |
| ... | ... | @@ -2986,7 +2986,12 @@ pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_br: *Io.Reader |
| 2986 | 2986 | return zir; |
| 2987 | 2987 | } |
| 2988 | 2988 | |
| 2989 | pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.Stat, zir: Zir) (std.fs.File.WriteError || Allocator.Error)!void { | |
| 2989 | pub 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 | 2995 | const safety_buffer = if (data_has_safety_tag) |
| 2991 | 2996 | try gpa.alloc([8]u8, zir.instructions.len) |
| 2992 | 2997 | else |
| ... | ... | @@ -3020,13 +3025,12 @@ pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.S |
| 3020 | 3025 | zir.string_bytes, |
| 3021 | 3026 | @ptrCast(zir.extra), |
| 3022 | 3027 | }; |
| 3023 | var cache_fw = cache_file.writer(&.{}); | |
| 3024 | cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) { | |
| 3025 | error.WriteFailed => return cache_fw.err.?, | |
| 3028 | cache_file_writer.interface.writeVecAll(&vecs) catch |err| switch (err) { | |
| 3029 | error.WriteFailed => return cache_file_writer.err.?, | |
| 3026 | 3030 | }; |
| 3027 | 3031 | } |
| 3028 | 3032 | |
| 3029 | pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir) std.fs.File.WriteError!void { | |
| 3033 | pub fn saveZoirCache(cache_file_writer: *Io.File.Writer, stat: Io.File.Stat, zoir: Zoir) Io.File.Writer.Error!void { | |
| 3030 | 3034 | const header: Zoir.Header = .{ |
| 3031 | 3035 | .nodes_len = @intCast(zoir.nodes.len), |
| 3032 | 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 | 3054 | @ptrCast(zoir.compile_errors), |
| 3051 | 3055 | @ptrCast(zoir.error_notes), |
| 3052 | 3056 | }; |
| 3053 | var cache_fw = cache_file.writer(&.{}); | |
| 3054 | cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) { | |
| 3055 | error.WriteFailed => return cache_fw.err.?, | |
| 3057 | cache_file_writer.interface.writeVecAll(&vecs) catch |err| switch (err) { | |
| 3058 | error.WriteFailed => return cache_file_writer.err.?, | |
| 3056 | 3059 | }; |
| 3057 | 3060 | } |
| 3058 | 3061 |
src/Zcu/PerThread.zig+54-33| ... | ... | @@ -94,11 +94,11 @@ pub fn updateFile( |
| 94 | 94 | // In any case we need to examine the stat of the file to determine the course of action. |
| 95 | 95 | var source_file = f: { |
| 96 | 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); | |
| 100 | 100 | |
| 101 | const stat = try source_file.stat(); | |
| 101 | const stat = try source_file.stat(io); | |
| 102 | 102 | |
| 103 | 103 | const want_local_cache = switch (file.path.root) { |
| 104 | 104 | .none, .local_cache => true, |
| ... | ... | @@ -118,7 +118,7 @@ pub fn updateFile( |
| 118 | 118 | const zir_dir = cache_directory.handle; |
| 119 | 119 | |
| 120 | 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 | 122 | .never_loaded, .retryable_failure => lock: { |
| 123 | 123 | // First, load the cached ZIR code, if any. |
| 124 | 124 | log.debug("AstGen checking cache: {f} (local={}, digest={s})", .{ |
| ... | ... | @@ -170,7 +170,7 @@ pub fn updateFile( |
| 170 | 170 | // version. Likewise if we're working on AstGen and another process asks for |
| 171 | 171 | // the cached file, they'll get it. |
| 172 | 172 | const cache_file = while (true) { |
| 173 | break zir_dir.createFile(&hex_digest, .{ | |
| 173 | break zir_dir.createFile(io, &hex_digest, .{ | |
| 174 | 174 | .read = true, |
| 175 | 175 | .truncate = false, |
| 176 | 176 | .lock = lock, |
| ... | ... | @@ -196,7 +196,7 @@ pub fn updateFile( |
| 196 | 196 | cache_directory, |
| 197 | 197 | }); |
| 198 | 198 | } |
| 199 | break zir_dir.createFile(&hex_digest, .{ | |
| 199 | break zir_dir.createFile(io, &hex_digest, .{ | |
| 200 | 200 | .read = true, |
| 201 | 201 | .truncate = false, |
| 202 | 202 | .lock = lock, |
| ... | ... | @@ -215,7 +215,7 @@ pub fn updateFile( |
| 215 | 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); | |
| 219 | 219 | |
| 220 | 220 | // Under `--time-report`, ignore cache hits; do the work anyway for those juicy numbers. |
| 221 | 221 | const ignore_hit = comp.time_report != null; |
| ... | ... | @@ -238,18 +238,13 @@ pub fn updateFile( |
| 238 | 238 | if (builtin.os.tag == .wasi or lock == .exclusive) break true; |
| 239 | 239 | // Otherwise, unlock to give someone a chance to get the exclusive lock |
| 240 | 240 | // and then upgrade to an exclusive lock. |
| 241 | cache_file.unlock(); | |
| 241 | cache_file.unlock(io); | |
| 242 | 242 | lock = .exclusive; |
| 243 | try cache_file.lock(lock); | |
| 243 | try cache_file.lock(io, lock); | |
| 244 | 244 | }; |
| 245 | 245 | |
| 246 | 246 | if (need_update) { |
| 247 | // The cache is definitely stale so delete the contents to avoid an underwrite later. | |
| 248 | cache_file.setEndPos(0) catch |err| switch (err) { | |
| 249 | error.FileTooBig => unreachable, // 0 is not too big | |
| 250 | else => |e| return e, | |
| 251 | }; | |
| 252 | try cache_file.seekTo(0); | |
| 247 | var cache_file_writer: Io.File.Writer = .init(cache_file, io, &.{}); | |
| 253 | 248 | |
| 254 | 249 | if (stat.size > std.math.maxInt(u32)) |
| 255 | 250 | return error.FileTooBig; |
| ... | ... | @@ -278,22 +273,28 @@ pub fn updateFile( |
| 278 | 273 | switch (file.getMode()) { |
| 279 | 274 | .zig => { |
| 280 | 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 | 277 | error.OutOfMemory => |e| return e, |
| 283 | else => log.warn("unable to write cached ZIR code for {f} to {f}{s}: {s}", .{ | |
| 284 | file.path.fmt(comp), cache_directory, &hex_digest, @errorName(err), | |
| 278 | else => log.warn("unable to write cached ZIR code for {f} to {f}{s}: {t}", .{ | |
| 279 | file.path.fmt(comp), cache_directory, &hex_digest, err, | |
| 285 | 280 | }), |
| 286 | 281 | }; |
| 287 | 282 | }, |
| 288 | 283 | .zon => { |
| 289 | 284 | file.zoir = try ZonGen.generate(gpa, file.tree.?, .{}); |
| 290 | Zcu.saveZoirCache(cache_file, stat, file.zoir.?) catch |err| { | |
| 291 | log.warn("unable to write cached ZOIR code for {f} to {f}{s}: {s}", .{ | |
| 292 | file.path.fmt(comp), cache_directory, &hex_digest, @errorName(err), | |
| 285 | Zcu.saveZoirCache(&cache_file_writer, stat, file.zoir.?) catch |err| { | |
| 286 | log.warn("unable to write cached ZOIR code for {f} to {f}{s}: {t}", .{ | |
| 287 | file.path.fmt(comp), cache_directory, &hex_digest, err, | |
| 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 | 298 | if (timer.finish()) |ns_astgen| { |
| 298 | 299 | comp.mutex.lockUncancelable(io); |
| 299 | 300 | defer comp.mutex.unlock(io); |
| ... | ... | @@ -346,8 +347,8 @@ pub fn updateFile( |
| 346 | 347 | |
| 347 | 348 | fn loadZirZoirCache( |
| 348 | 349 | zcu: *Zcu, |
| 349 | cache_file: std.fs.File, | |
| 350 | stat: std.fs.File.Stat, | |
| 350 | cache_file: Io.File, | |
| 351 | stat: Io.File.Stat, | |
| 351 | 352 | file: *Zcu.File, |
| 352 | 353 | comptime mode: Ast.Mode, |
| 353 | 354 | ) !enum { success, invalid, truncated, stale } { |
| ... | ... | @@ -2466,11 +2467,11 @@ fn updateEmbedFileInner( |
| 2466 | 2467 | |
| 2467 | 2468 | var file = f: { |
| 2468 | 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); | |
| 2472 | 2473 | |
| 2473 | const stat: Cache.File.Stat = .fromFs(try file.stat()); | |
| 2474 | const stat: Cache.File.Stat = .fromFs(try file.stat(io)); | |
| 2474 | 2475 | |
| 2475 | 2476 | if (ef.val != .none) { |
| 2476 | 2477 | const old_stat = ef.stat; |
| ... | ... | @@ -4524,12 +4525,14 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Ru |
| 4524 | 4525 | .stage2_llvm, |
| 4525 | 4526 | => {}, |
| 4526 | 4527 | }, |
| 4528 | error.Canceled => |e| return e, | |
| 4527 | 4529 | } |
| 4528 | 4530 | return error.AlreadyReported; |
| 4529 | 4531 | }; |
| 4530 | 4532 | } |
| 4531 | 4533 | fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) error{ |
| 4532 | 4534 | OutOfMemory, |
| 4535 | Canceled, | |
| 4533 | 4536 | CodegenFail, |
| 4534 | 4537 | NoLinkFile, |
| 4535 | 4538 | BackendDoesNotProduceMir, |
| ... | ... | @@ -4555,12 +4558,16 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e |
| 4555 | 4558 | null; |
| 4556 | 4559 | defer if (liveness) |*l| l.deinit(gpa); |
| 4557 | 4560 | |
| 4558 | if (build_options.enable_debug_extensions and comp.verbose_air) { | |
| 4559 | const stderr, _ = std.debug.lockStderrWriter(&.{}); | |
| 4560 | defer std.debug.unlockStderrWriter(); | |
| 4561 | stderr.print("# Begin Function AIR: {f}:\n", .{fqn.fmt(ip)}) catch {}; | |
| 4562 | air.write(stderr, pt, liveness); | |
| 4563 | stderr.print("# End Function AIR: {f}\n\n", .{fqn.fmt(ip)}) catch {}; | |
| 4561 | if (build_options.enable_debug_extensions and comp.verbose_air) p: { | |
| 4562 | const io = comp.io; | |
| 4563 | const stderr = try io.lockStderr(&.{}, null); | |
| 4564 | defer io.unlockStderr(); | |
| 4565 | printVerboseAir(pt, liveness, fqn, air, &stderr.file_writer.interface) catch |err| switch (err) { | |
| 4566 | error.WriteFailed => switch (stderr.file_writer.err.?) { | |
| 4567 | error.Canceled => |e| return e, | |
| 4568 | else => break :p, | |
| 4569 | }, | |
| 4570 | }; | |
| 4564 | 4571 | } |
| 4565 | 4572 | |
| 4566 | 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 | 4582 | |
| 4576 | 4583 | verify.verify() catch |err| switch (err) { |
| 4577 | 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 | } |
| 4581 | 4588 | |
| ... | ... | @@ -4611,3 +4618,17 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e |
| 4611 | 4618 | => return zcu.codegenFail(nav, "unable to codegen: {s}", .{@errorName(err)}), |
| 4612 | 4619 | }; |
| 4613 | 4620 | } |
| 4621 | ||
| 4622 | fn 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 | 11273 | return @enumFromInt(isel.values.items.len); |
| 11274 | 11274 | } |
| 11275 | 11275 | pub 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 | 11276 | const zcu = isel.pt.zcu; |
| 11281 | 11277 | const gpa = zcu.gpa; |
| 11282 | 11278 | const ip = &zcu.intern_pool; |
| 11283 | 11279 | const nav = ip.getNav(isel.nav_index); |
| 11284 | 11280 | |
| 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 | 11287 | var reverse_live_values: std.AutoArrayHashMapUnmanaged(Value.Index, std.ArrayList(Air.Inst.Index)) = .empty; |
| 11286 | 11288 | defer { |
| 11287 | 11289 | for (reverse_live_values.values()) |*list| list.deinit(gpa); |
src/codegen/llvm.zig+21-17| ... | ... | @@ -1,19 +1,22 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 3 | 5 | const assert = std.debug.assert; |
| 4 | 6 | const Allocator = std.mem.Allocator; |
| 5 | 7 | const log = std.log.scoped(.codegen); |
| 6 | 8 | const math = std.math; |
| 7 | 9 | const DW = std.dwarf; |
| 8 | ||
| 9 | 10 | const Builder = std.zig.llvm.Builder; |
| 11 | ||
| 12 | const build_options = @import("build_options"); | |
| 10 | 13 | const llvm = if (build_options.have_llvm) |
| 11 | 14 | @import("llvm/bindings.zig") |
| 12 | 15 | else |
| 13 | 16 | @compileError("LLVM unavailable"); |
| 17 | ||
| 14 | 18 | const link = @import("../link.zig"); |
| 15 | 19 | const Compilation = @import("../Compilation.zig"); |
| 16 | const build_options = @import("build_options"); | |
| 17 | 20 | const Zcu = @import("../Zcu.zig"); |
| 18 | 21 | const InternPool = @import("../InternPool.zig"); |
| 19 | 22 | const Package = @import("../Package.zig"); |
| ... | ... | @@ -799,6 +802,7 @@ pub const Object = struct { |
| 799 | 802 | pub fn emit(o: *Object, pt: Zcu.PerThread, options: EmitOptions) error{ LinkFailure, OutOfMemory }!void { |
| 800 | 803 | const zcu = pt.zcu; |
| 801 | 804 | const comp = zcu.comp; |
| 805 | const io = comp.io; | |
| 802 | 806 | const diags = &comp.link_diags; |
| 803 | 807 | |
| 804 | 808 | { |
| ... | ... | @@ -961,10 +965,10 @@ pub const Object = struct { |
| 961 | 965 | const context, const module = emit: { |
| 962 | 966 | if (options.pre_ir_path) |path| { |
| 963 | 967 | if (std.mem.eql(u8, path, "-")) { |
| 964 | o.builder.dump(); | |
| 968 | o.builder.dump(io); | |
| 965 | 969 | } else { |
| 966 | o.builder.printToFilePath(std.fs.cwd(), path) catch |err| { | |
| 967 | log.err("failed printing LLVM module to \"{s}\": {s}", .{ path, @errorName(err) }); | |
| 970 | o.builder.printToFilePath(io, Io.Dir.cwd(), path) catch |err| { | |
| 971 | log.err("failed printing LLVM module to \"{s}\": {t}", .{ path, err }); | |
| 968 | 972 | }; |
| 969 | 973 | } |
| 970 | 974 | } |
| ... | ... | @@ -977,26 +981,26 @@ pub const Object = struct { |
| 977 | 981 | o.builder.clearAndFree(); |
| 978 | 982 | |
| 979 | 983 | if (options.pre_bc_path) |path| { |
| 980 | var file = std.fs.cwd().createFile(path, .{}) catch |err| | |
| 981 | return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) }); | |
| 982 | defer file.close(); | |
| 984 | var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| | |
| 985 | return diags.fail("failed to create '{s}': {t}", .{ path, err }); | |
| 986 | defer file.close(io); | |
| 983 | 987 | |
| 984 | 988 | const ptr: [*]const u8 = @ptrCast(bitcode.ptr); |
| 985 | file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err| | |
| 986 | return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) }); | |
| 989 | file.writeStreamingAll(io, ptr[0..(bitcode.len * 4)]) catch |err| | |
| 990 | return diags.fail("failed to write to '{s}': {t}", .{ path, err }); | |
| 987 | 991 | } |
| 988 | 992 | |
| 989 | 993 | if (options.asm_path == null and options.bin_path == null and |
| 990 | 994 | options.post_ir_path == null and options.post_bc_path == null) return; |
| 991 | 995 | |
| 992 | 996 | if (options.post_bc_path) |path| { |
| 993 | var file = std.fs.cwd().createFile(path, .{}) catch |err| | |
| 994 | return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) }); | |
| 995 | defer file.close(); | |
| 997 | var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| | |
| 998 | return diags.fail("failed to create '{s}': {t}", .{ path, err }); | |
| 999 | defer file.close(io); | |
| 996 | 1000 | |
| 997 | 1001 | const ptr: [*]const u8 = @ptrCast(bitcode.ptr); |
| 998 | file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err| | |
| 999 | return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) }); | |
| 1002 | file.writeStreamingAll(io, ptr[0..(bitcode.len * 4)]) catch |err| | |
| 1003 | return diags.fail("failed to write to '{s}': {t}", .{ path, err }); | |
| 1000 | 1004 | } |
| 1001 | 1005 | |
| 1002 | 1006 | if (!build_options.have_llvm or !comp.config.use_lib_llvm) { |
| ... | ... | @@ -2710,7 +2714,7 @@ pub const Object = struct { |
| 2710 | 2714 | } |
| 2711 | 2715 | |
| 2712 | 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 | 2718 | defer aw.deinit(); |
| 2715 | 2719 | ty.print(&aw.writer, pt, null) catch |err| switch (err) { |
| 2716 | 2720 | error.WriteFailed => return error.OutOfMemory, |
src/crash_report.zig+7-6| ... | ... | @@ -95,19 +95,20 @@ fn dumpCrashContext() Io.Writer.Error!void { |
| 95 | 95 | |
| 96 | 96 | // TODO: this does mean that a different thread could grab the stderr mutex between the context |
| 97 | 97 | // and the actual panic printing, which would be quite confusing. |
| 98 | const stderr, _ = std.debug.lockStderrWriter(&.{}); | |
| 99 | defer std.debug.unlockStderrWriter(); | |
| 98 | const stderr = std.debug.lockStderr(&.{}); | |
| 99 | defer std.debug.unlockStderr(); | |
| 100 | const w = &stderr.file_writer.interface; | |
| 100 | 101 | |
| 101 | try stderr.writeAll("Compiler crash context:\n"); | |
| 102 | try w.writeAll("Compiler crash context:\n"); | |
| 102 | 103 | |
| 103 | 104 | if (CodegenFunc.current) |*cg| { |
| 104 | 105 | const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav; |
| 105 | 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 | 108 | } else if (AnalyzeBody.current) |anal| { |
| 108 | try dumpCrashContextSema(anal, stderr, &S.crash_heap); | |
| 109 | try dumpCrashContextSema(anal, w, &S.crash_heap); | |
| 109 | 110 | } else { |
| 110 | try stderr.writeAll("(no context)\n\n"); | |
| 111 | try w.writeAll("(no context)\n\n"); | |
| 111 | 112 | } |
| 112 | 113 | } |
| 113 | 114 | fn 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 | 37 | arena: Allocator, |
| 38 | 38 | io: Io, |
| 39 | 39 | out_buffer: std.Io.Writer.Allocating, |
| 40 | stdout_writer: *fs.File.Writer, | |
| 40 | stdout_writer: *Io.File.Writer, | |
| 41 | 41 | |
| 42 | const SeenMap = std.AutoHashMap(fs.File.INode, void); | |
| 42 | const SeenMap = std.AutoHashMap(Io.File.INode, void); | |
| 43 | 43 | }; |
| 44 | 44 | |
| 45 | 45 | pub 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 | 59 | const arg = args[i]; |
| 60 | 60 | if (mem.startsWith(u8, arg, "-")) { |
| 61 | 61 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 62 | try fs.File.stdout().writeAll(usage_fmt); | |
| 63 | return process.cleanExit(); | |
| 62 | try Io.File.stdout().writeStreamingAll(io, usage_fmt); | |
| 63 | return process.cleanExit(io); | |
| 64 | 64 | } else if (mem.eql(u8, arg, "--color")) { |
| 65 | 65 | if (i + 1 >= args.len) { |
| 66 | 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 | 99 | fatal("cannot use --stdin with positional arguments", .{}); |
| 100 | 100 | } |
| 101 | 101 | |
| 102 | const stdin: fs.File = .stdin(); | |
| 102 | const stdin: Io.File = .stdin(); | |
| 103 | 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 | 105 | const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| { |
| 106 | 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 | 124 | try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>"); |
| 125 | 125 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 126 | 126 | defer error_bundle.deinit(gpa); |
| 127 | error_bundle.renderToStdErr(.{}, color); | |
| 127 | error_bundle.renderToStderr(io, .{}, color) catch {}; | |
| 128 | 128 | process.exit(2); |
| 129 | 129 | } |
| 130 | 130 | } else { |
| ... | ... | @@ -138,12 +138,12 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! |
| 138 | 138 | try wip_errors.addZoirErrorMessages(zoir, tree, source_code, "<stdin>"); |
| 139 | 139 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 140 | 140 | defer error_bundle.deinit(gpa); |
| 141 | error_bundle.renderToStdErr(.{}, color); | |
| 141 | error_bundle.renderToStderr(io, .{}, color) catch {}; | |
| 142 | 142 | process.exit(2); |
| 143 | 143 | } |
| 144 | 144 | } |
| 145 | 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 | 147 | process.exit(2); |
| 148 | 148 | } |
| 149 | 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 | 154 | process.exit(code); |
| 155 | 155 | } |
| 156 | 156 | |
| 157 | return fs.File.stdout().writeAll(formatted); | |
| 157 | return Io.File.stdout().writeStreamingAll(io, formatted); | |
| 158 | 158 | } |
| 159 | 159 | |
| 160 | 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 | 162 | } |
| 163 | 163 | |
| 164 | 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); | |
| 166 | 166 | |
| 167 | 167 | var fmt: Fmt = .{ |
| 168 | 168 | .gpa = gpa, |
| ... | ... | @@ -182,13 +182,13 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! |
| 182 | 182 | // Mark any excluded files/directories as already seen, |
| 183 | 183 | // so that they are skipped later during actual processing |
| 184 | 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 | 186 | error.FileNotFound => continue, |
| 187 | 187 | // On Windows, statFile does not work for directories |
| 188 | 188 | error.IsDir => dir: { |
| 189 | var dir = try fs.cwd().openDir(file_path, .{}); | |
| 190 | defer dir.close(); | |
| 191 | break :dir try dir.stat(); | |
| 189 | var dir = try Io.Dir.cwd().openDir(io, file_path, .{}); | |
| 190 | defer dir.close(io); | |
| 191 | break :dir try dir.stat(io); | |
| 192 | 192 | }, |
| 193 | 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 | 196 | } |
| 197 | 197 | |
| 198 | 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 | 201 | try fmt.stdout_writer.interface.flush(); |
| 202 | 202 | if (fmt.any_error) { |
| ... | ... | @@ -204,7 +204,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! |
| 204 | 204 | } |
| 205 | 205 | } |
| 206 | 206 | |
| 207 | fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) !void { | |
| 207 | fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: Io.Dir, sub_path: []const u8) !void { | |
| 208 | 208 | fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) { |
| 209 | 209 | error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path), |
| 210 | 210 | else => { |
| ... | ... | @@ -219,17 +219,19 @@ fn fmtPathDir( |
| 219 | 219 | fmt: *Fmt, |
| 220 | 220 | file_path: []const u8, |
| 221 | 221 | check_mode: bool, |
| 222 | parent_dir: fs.Dir, | |
| 222 | parent_dir: Io.Dir, | |
| 223 | 223 | parent_sub_path: []const u8, |
| 224 | 224 | ) !void { |
| 225 | var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true }); | |
| 226 | defer dir.close(); | |
| 225 | const io = fmt.io; | |
| 226 | ||
| 227 | var dir = try parent_dir.openDir(io, parent_sub_path, .{ .iterate = true }); | |
| 228 | defer dir.close(io); | |
| 227 | 229 | |
| 228 | const stat = try dir.stat(); | |
| 230 | const stat = try dir.stat(io); | |
| 229 | 231 | if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; |
| 230 | 232 | |
| 231 | 233 | var dir_it = dir.iterate(); |
| 232 | while (try dir_it.next()) |entry| { | |
| 234 | while (try dir_it.next(io)) |entry| { | |
| 233 | 235 | const is_dir = entry.kind == .directory; |
| 234 | 236 | |
| 235 | 237 | if (mem.startsWith(u8, entry.name, ".")) continue; |
| ... | ... | @@ -242,7 +244,7 @@ fn fmtPathDir( |
| 242 | 244 | try fmtPathDir(fmt, full_path, check_mode, dir, entry.name); |
| 243 | 245 | } else { |
| 244 | 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 | 248 | fmt.any_error = true; |
| 247 | 249 | return; |
| 248 | 250 | }; |
| ... | ... | @@ -255,22 +257,22 @@ fn fmtPathFile( |
| 255 | 257 | fmt: *Fmt, |
| 256 | 258 | file_path: []const u8, |
| 257 | 259 | check_mode: bool, |
| 258 | dir: fs.Dir, | |
| 260 | dir: Io.Dir, | |
| 259 | 261 | sub_path: []const u8, |
| 260 | 262 | ) !void { |
| 261 | 263 | const io = fmt.io; |
| 262 | 264 | |
| 263 | const source_file = try dir.openFile(sub_path, .{}); | |
| 265 | const source_file = try dir.openFile(io, sub_path, .{}); | |
| 264 | 266 | var file_closed = false; |
| 265 | errdefer if (!file_closed) source_file.close(); | |
| 267 | errdefer if (!file_closed) source_file.close(io); | |
| 266 | 268 | |
| 267 | const stat = try source_file.stat(); | |
| 269 | const stat = try source_file.stat(io); | |
| 268 | 270 | |
| 269 | 271 | if (stat.kind == .directory) |
| 270 | 272 | return error.IsDir; |
| 271 | 273 | |
| 272 | 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 | 276 | file_reader.size = stat.size; |
| 275 | 277 | |
| 276 | 278 | const gpa = fmt.gpa; |
| ... | ... | @@ -280,7 +282,7 @@ fn fmtPathFile( |
| 280 | 282 | }; |
| 281 | 283 | defer gpa.free(source_code); |
| 282 | 284 | |
| 283 | source_file.close(); | |
| 285 | source_file.close(io); | |
| 284 | 286 | file_closed = true; |
| 285 | 287 | |
| 286 | 288 | // Add to set after no longer possible to get error.IsDir. |
| ... | ... | @@ -296,7 +298,7 @@ fn fmtPathFile( |
| 296 | 298 | defer tree.deinit(gpa); |
| 297 | 299 | |
| 298 | 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 | 302 | fmt.any_error = true; |
| 301 | 303 | return; |
| 302 | 304 | } |
| ... | ... | @@ -317,7 +319,7 @@ fn fmtPathFile( |
| 317 | 319 | try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path); |
| 318 | 320 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 319 | 321 | defer error_bundle.deinit(gpa); |
| 320 | error_bundle.renderToStdErr(.{}, fmt.color); | |
| 322 | try error_bundle.renderToStderr(io, .{}, fmt.color); | |
| 321 | 323 | fmt.any_error = true; |
| 322 | 324 | } |
| 323 | 325 | }, |
| ... | ... | @@ -332,7 +334,7 @@ fn fmtPathFile( |
| 332 | 334 | try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path); |
| 333 | 335 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 334 | 336 | defer error_bundle.deinit(gpa); |
| 335 | error_bundle.renderToStdErr(.{}, fmt.color); | |
| 337 | try error_bundle.renderToStderr(io, .{}, fmt.color); | |
| 336 | 338 | fmt.any_error = true; |
| 337 | 339 | } |
| 338 | 340 | }, |
| ... | ... | @@ -353,7 +355,7 @@ fn fmtPathFile( |
| 353 | 355 | try fmt.stdout_writer.interface.print("{s}\n", .{file_path}); |
| 354 | 356 | fmt.any_error = true; |
| 355 | 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 | 359 | defer af.deinit(); |
| 358 | 360 | |
| 359 | 361 | try af.file_writer.interface.writeAll(fmt.out_buffer.written()); |
| ... | ... | @@ -368,7 +370,7 @@ pub fn main() !void { |
| 368 | 370 | var arena_instance = std.heap.ArenaAllocator.init(gpa); |
| 369 | 371 | const arena = arena_instance.allocator(); |
| 370 | 372 | const args = try process.argsAlloc(arena); |
| 371 | var threaded: std.Io.Threaded = .init(gpa); | |
| 373 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 372 | 374 | defer threaded.deinit(); |
| 373 | 375 | const io = threaded.io(); |
| 374 | 376 | return run(gpa, arena, io, args[1..]); |
src/introspect.zig+50-48| ... | ... | @@ -1,53 +1,55 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 2 | const build_options = @import("build_options"); | |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | const Io = std.Io; | |
| 6 | const Dir = std.Io.Dir; | |
| 3 | 7 | const mem = std.mem; |
| 4 | const Allocator = mem.Allocator; | |
| 5 | const os = std.os; | |
| 6 | const fs = std.fs; | |
| 8 | const Allocator = std.mem.Allocator; | |
| 7 | 9 | const Cache = std.Build.Cache; |
| 10 | ||
| 8 | 11 | const Compilation = @import("Compilation.zig"); |
| 9 | 12 | const Package = @import("Package.zig"); |
| 10 | const build_options = @import("build_options"); | |
| 11 | 13 | |
| 12 | 14 | /// Returns the sub_path that worked, or `null` if none did. |
| 13 | 15 | /// The path of the returned Directory is relative to `base`. |
| 14 | 16 | /// The handle of the returned Directory is open. |
| 15 | fn testZigInstallPrefix(base_dir: fs.Dir) ?Cache.Directory { | |
| 16 | const test_index_file = "std" ++ fs.path.sep_str ++ "std.zig"; | |
| 17 | fn testZigInstallPrefix(io: Io, base_dir: Io.Dir) ?Cache.Directory { | |
| 18 | const test_index_file = "std" ++ Dir.path.sep_str ++ "std.zig"; | |
| 17 | 19 | |
| 18 | 20 | zig_dir: { |
| 19 | 21 | // Try lib/zig/std/std.zig |
| 20 | const lib_zig = "lib" ++ fs.path.sep_str ++ "zig"; | |
| 21 | var test_zig_dir = base_dir.openDir(lib_zig, .{}) catch break :zig_dir; | |
| 22 | const file = test_zig_dir.openFile(test_index_file, .{}) catch { | |
| 23 | test_zig_dir.close(); | |
| 22 | const lib_zig = "lib" ++ Dir.path.sep_str ++ "zig"; | |
| 23 | var test_zig_dir = base_dir.openDir(io, lib_zig, .{}) catch break :zig_dir; | |
| 24 | const file = test_zig_dir.openFile(io, test_index_file, .{}) catch { | |
| 25 | test_zig_dir.close(io); | |
| 24 | 26 | break :zig_dir; |
| 25 | 27 | }; |
| 26 | file.close(); | |
| 28 | file.close(io); | |
| 27 | 29 | return .{ .handle = test_zig_dir, .path = lib_zig }; |
| 28 | 30 | } |
| 29 | 31 | |
| 30 | 32 | // Try lib/std/std.zig |
| 31 | var test_zig_dir = base_dir.openDir("lib", .{}) catch return null; | |
| 32 | const file = test_zig_dir.openFile(test_index_file, .{}) catch { | |
| 33 | test_zig_dir.close(); | |
| 33 | var test_zig_dir = base_dir.openDir(io, "lib", .{}) catch return null; | |
| 34 | const file = test_zig_dir.openFile(io, test_index_file, .{}) catch { | |
| 35 | test_zig_dir.close(io); | |
| 34 | 36 | return null; |
| 35 | 37 | }; |
| 36 | file.close(); | |
| 38 | file.close(io); | |
| 37 | 39 | return .{ .handle = test_zig_dir, .path = "lib" }; |
| 38 | 40 | } |
| 39 | 41 | |
| 40 | 42 | /// Both the directory handle and the path are newly allocated resources which the caller now owns. |
| 41 | pub fn findZigLibDir(gpa: Allocator) !Cache.Directory { | |
| 43 | pub fn findZigLibDir(gpa: Allocator, io: Io) !Cache.Directory { | |
| 42 | 44 | const cwd_path = try getResolvedCwd(gpa); |
| 43 | 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 | 47 | defer gpa.free(self_exe_path); |
| 46 | 48 | |
| 47 | return findZigLibDirFromSelfExe(gpa, cwd_path, self_exe_path); | |
| 49 | return findZigLibDirFromSelfExe(gpa, io, cwd_path, self_exe_path); | |
| 48 | 50 | } |
| 49 | 51 | |
| 50 | /// Like `std.process.getCwdAlloc`, but also resolves the path with `std.fs.path.resolve`. This | |
| 52 | /// Like `std.process.getCwdAlloc`, but also resolves the path with `Dir.path.resolve`. This | |
| 51 | 53 | /// means the path has no repeated separators, no "." or ".." components, and no trailing separator. |
| 52 | 54 | /// On WASI, "" is returned instead of ".". |
| 53 | 55 | pub fn getResolvedCwd(gpa: Allocator) error{ |
| ... | ... | @@ -65,27 +67,28 @@ pub fn getResolvedCwd(gpa: Allocator) error{ |
| 65 | 67 | } |
| 66 | 68 | const cwd = try std.process.getCwdAlloc(gpa); |
| 67 | 69 | defer gpa.free(cwd); |
| 68 | const resolved = try fs.path.resolve(gpa, &.{cwd}); | |
| 69 | std.debug.assert(fs.path.isAbsolute(resolved)); | |
| 70 | const resolved = try Dir.path.resolve(gpa, &.{cwd}); | |
| 71 | std.debug.assert(Dir.path.isAbsolute(resolved)); | |
| 70 | 72 | return resolved; |
| 71 | 73 | } |
| 72 | 74 | |
| 73 | 75 | /// Both the directory handle and the path are newly allocated resources which the caller now owns. |
| 74 | 76 | pub fn findZigLibDirFromSelfExe( |
| 75 | 77 | allocator: Allocator, |
| 78 | io: Io, | |
| 76 | 79 | /// The return value of `getResolvedCwd`. |
| 77 | 80 | /// Passed as an argument to avoid pointlessly repeating the call. |
| 78 | 81 | cwd_path: []const u8, |
| 79 | 82 | self_exe_path: []const u8, |
| 80 | 83 | ) error{ OutOfMemory, FileNotFound }!Cache.Directory { |
| 81 | const cwd = fs.cwd(); | |
| 84 | const cwd = Io.Dir.cwd(); | |
| 82 | 85 | var cur_path: []const u8 = self_exe_path; |
| 83 | while (fs.path.dirname(cur_path)) |dirname| : (cur_path = dirname) { | |
| 84 | var base_dir = cwd.openDir(dirname, .{}) catch continue; | |
| 85 | defer base_dir.close(); | |
| 86 | while (Dir.path.dirname(cur_path)) |dirname| : (cur_path = dirname) { | |
| 87 | var base_dir = cwd.openDir(io, dirname, .{}) catch continue; | |
| 88 | defer base_dir.close(io); | |
| 86 | 89 | |
| 87 | const sub_directory = testZigInstallPrefix(base_dir) orelse continue; | |
| 88 | const p = try fs.path.join(allocator, &.{ dirname, sub_directory.path.? }); | |
| 90 | const sub_directory = testZigInstallPrefix(io, base_dir) orelse continue; | |
| 91 | const p = try Dir.path.join(allocator, &.{ dirname, sub_directory.path.? }); | |
| 89 | 92 | defer allocator.free(p); |
| 90 | 93 | |
| 91 | 94 | const resolved = try resolvePath(allocator, cwd_path, &.{p}); |
| ... | ... | @@ -109,18 +112,18 @@ pub fn resolveGlobalCacheDir(allocator: Allocator) ![]u8 { |
| 109 | 112 | if (builtin.os.tag != .windows) { |
| 110 | 113 | if (std.zig.EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| { |
| 111 | 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 | 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 | } |
| 119 | 122 | |
| 120 | return fs.getAppDataDir(allocator, appname); | |
| 123 | return std.fs.getAppDataDir(allocator, appname); | |
| 121 | 124 | } |
| 122 | 125 | |
| 123 | /// Similar to `fs.path.resolve`, but converts to a cwd-relative path, or, if that would | |
| 126 | /// Similar to `Dir.path.resolve`, but converts to a cwd-relative path, or, if that would | |
| 124 | 127 | /// start with a relative up-dir (".."), an absolute path based on the cwd. Also, the cwd |
| 125 | 128 | /// returns the empty string ("") instead of ".". |
| 126 | 129 | pub fn resolvePath( |
| ... | ... | @@ -132,7 +135,7 @@ pub fn resolvePath( |
| 132 | 135 | ) Allocator.Error![]u8 { |
| 133 | 136 | if (builtin.target.os.tag == .wasi) { |
| 134 | 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 | 139 | if (mem.eql(u8, res, ".")) { |
| 137 | 140 | gpa.free(res); |
| 138 | 141 | return ""; |
| ... | ... | @@ -142,16 +145,16 @@ pub fn resolvePath( |
| 142 | 145 | |
| 143 | 146 | // Heuristic for a fast path: if no component is absolute and ".." never appears, we just need to resolve `paths`. |
| 144 | 147 | for (paths) |p| { |
| 145 | if (fs.path.isAbsolute(p)) break; // absolute path | |
| 148 | if (Dir.path.isAbsolute(p)) break; // absolute path | |
| 146 | 149 | if (mem.indexOf(u8, p, "..") != null) break; // may contain up-dir |
| 147 | 150 | } else { |
| 148 | 151 | // no absolute path, no "..". |
| 149 | const res = try fs.path.resolve(gpa, paths); | |
| 152 | const res = try Dir.path.resolve(gpa, paths); | |
| 150 | 153 | if (mem.eql(u8, res, ".")) { |
| 151 | 154 | gpa.free(res); |
| 152 | 155 | return ""; |
| 153 | 156 | } |
| 154 | std.debug.assert(!fs.path.isAbsolute(res)); | |
| 157 | std.debug.assert(!Dir.path.isAbsolute(res)); | |
| 155 | 158 | std.debug.assert(!isUpDir(res)); |
| 156 | 159 | return res; |
| 157 | 160 | } |
| ... | ... | @@ -160,19 +163,19 @@ pub fn resolvePath( |
| 160 | 163 | // Optimization: `paths` often has just one element. |
| 161 | 164 | const path_resolved = switch (paths.len) { |
| 162 | 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 | 167 | else => r: { |
| 165 | 168 | const all_paths = try gpa.alloc([]const u8, paths.len + 1); |
| 166 | 169 | defer gpa.free(all_paths); |
| 167 | 170 | all_paths[0] = cwd_resolved; |
| 168 | 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 | 175 | errdefer gpa.free(path_resolved); |
| 173 | 176 | |
| 174 | std.debug.assert(fs.path.isAbsolute(path_resolved)); | |
| 175 | std.debug.assert(fs.path.isAbsolute(cwd_resolved)); | |
| 177 | std.debug.assert(Dir.path.isAbsolute(path_resolved)); | |
| 178 | std.debug.assert(Dir.path.isAbsolute(cwd_resolved)); | |
| 176 | 179 | |
| 177 | 180 | if (!std.mem.startsWith(u8, path_resolved, cwd_resolved)) return path_resolved; // not in cwd |
| 178 | 181 | if (path_resolved.len == cwd_resolved.len) { |
| ... | ... | @@ -180,7 +183,7 @@ pub fn resolvePath( |
| 180 | 183 | gpa.free(path_resolved); |
| 181 | 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) | |
| 184 | 187 | |
| 185 | 188 | // in cwd; extract sub path |
| 186 | 189 | const sub_path = try gpa.dupe(u8, path_resolved[cwd_resolved.len + 1 ..]); |
| ... | ... | @@ -188,9 +191,8 @@ pub fn resolvePath( |
| 188 | 191 | return sub_path; |
| 189 | 192 | } |
| 190 | 193 | |
| 191 | /// TODO move this to std.fs.path | |
| 192 | 194 | pub 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 | } |
| 195 | 197 | |
| 196 | 198 | pub const default_local_zig_cache_basename = ".zig-cache"; |
| ... | ... | @@ -198,15 +200,15 @@ pub const default_local_zig_cache_basename = ".zig-cache"; |
| 198 | 200 | /// Searches upwards from `cwd` for a directory containing a `build.zig` file. |
| 199 | 201 | /// If such a directory is found, returns the path to it joined to the `.zig_cache` name. |
| 200 | 202 | /// Otherwise, returns `null`, indicating no suitable local cache location. |
| 201 | pub fn resolveSuitableLocalCacheDir(arena: Allocator, cwd: []const u8) Allocator.Error!?[]u8 { | |
| 203 | pub fn resolveSuitableLocalCacheDir(arena: Allocator, io: Io, cwd: []const u8) Allocator.Error!?[]u8 { | |
| 202 | 204 | var cur_dir = cwd; |
| 203 | 205 | while (true) { |
| 204 | const joined = try fs.path.join(arena, &.{ cur_dir, Package.build_zig_basename }); | |
| 205 | if (fs.cwd().access(joined, .{})) |_| { | |
| 206 | return try fs.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename }); | |
| 206 | const joined = try Dir.path.join(arena, &.{ cur_dir, Package.build_zig_basename }); | |
| 207 | if (Io.Dir.cwd().access(io, joined, .{})) |_| { | |
| 208 | return try Dir.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename }); | |
| 207 | 209 | } else |err| switch (err) { |
| 208 | 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 | 212 | continue; |
| 211 | 213 | }, |
| 212 | 214 | else => return null, |
src/libs/freebsd.zig+14-14| ... | ... | @@ -1,9 +1,9 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const Allocator = std.mem.Allocator; |
| 3 | 4 | const mem = std.mem; |
| 4 | 5 | const log = std.log; |
| 5 | const fs = std.fs; | |
| 6 | const path = fs.path; | |
| 6 | const path = std.Io.Dir.path; | |
| 7 | 7 | const assert = std.debug.assert; |
| 8 | 8 | const Version = std.SemanticVersion; |
| 9 | 9 | const Path = std.Build.Cache.Path; |
| ... | ... | @@ -401,8 +401,8 @@ pub const BuiltSharedObjects = struct { |
| 401 | 401 | lock: Cache.Lock, |
| 402 | 402 | dir_path: Path, |
| 403 | 403 | |
| 404 | pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void { | |
| 405 | self.lock.release(); | |
| 404 | pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void { | |
| 405 | self.lock.release(io); | |
| 406 | 406 | gpa.free(self.dir_path.sub_path); |
| 407 | 407 | self.* = undefined; |
| 408 | 408 | } |
| ... | ... | @@ -444,12 +444,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 444 | 444 | var cache: Cache = .{ |
| 445 | 445 | .gpa = gpa, |
| 446 | 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 | 450 | cache.addPrefix(comp.dirs.zig_lib); |
| 451 | 451 | cache.addPrefix(comp.dirs.global_cache); |
| 452 | defer cache.manifest_dir.close(); | |
| 452 | defer cache.manifest_dir.close(io); | |
| 453 | 453 | |
| 454 | 454 | var man = cache.obtain(); |
| 455 | 455 | defer man.deinit(); |
| ... | ... | @@ -468,7 +468,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 468 | 468 | .lock = man.toOwnedLock(), |
| 469 | 469 | .dir_path = .{ |
| 470 | 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 | 477 | const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); |
| 478 | 478 | |
| 479 | 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 | 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); | |
| 484 | 484 | |
| 485 | 485 | const abilists_contents = man.files.keys()[abilists_index].contents.?; |
| 486 | 486 | const metadata = try loadMetaData(gpa, abilists_contents); |
| ... | ... | @@ -520,7 +520,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 520 | 520 | for (metadata.all_versions[0 .. target_ver_index + 1]) |ver| { |
| 521 | 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 | 524 | map_contents.deinit(); |
| 525 | 525 | } |
| 526 | 526 | |
| ... | ... | @@ -974,7 +974,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 974 | 974 | |
| 975 | 975 | var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "stdthreads", etc. |
| 976 | 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 | 978 | try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node); |
| 979 | 979 | } |
| 980 | 980 | |
| ... | ... | @@ -986,7 +986,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 986 | 986 | .lock = man.toOwnedLock(), |
| 987 | 987 | .dir_path = .{ |
| 988 | 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 | 1014 | const so_path: Path = .{ |
| 1015 | 1015 | .root_dir = so_files.dir_path.root_dir, |
| 1016 | 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 | 1018 | }) catch return comp.setAllocFailure(), |
| 1019 | 1019 | }; |
| 1020 | 1020 | task_buffer[task_buffer_i] = .{ .load_dso = so_path }; |
src/libs/glibc.zig+16-16| ... | ... | @@ -1,9 +1,9 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const Allocator = std.mem.Allocator; |
| 3 | 4 | const mem = std.mem; |
| 4 | 5 | const log = std.log; |
| 5 | const fs = std.fs; | |
| 6 | const path = fs.path; | |
| 6 | const path = std.Io.Dir.path; | |
| 7 | 7 | const assert = std.debug.assert; |
| 8 | 8 | const Version = std.SemanticVersion; |
| 9 | 9 | const Path = std.Build.Cache.Path; |
| ... | ... | @@ -640,8 +640,8 @@ pub const BuiltSharedObjects = struct { |
| 640 | 640 | lock: Cache.Lock, |
| 641 | 641 | dir_path: Path, |
| 642 | 642 | |
| 643 | pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void { | |
| 644 | self.lock.release(); | |
| 643 | pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void { | |
| 644 | self.lock.release(io); | |
| 645 | 645 | gpa.free(self.dir_path.sub_path); |
| 646 | 646 | self.* = undefined; |
| 647 | 647 | } |
| ... | ... | @@ -679,12 +679,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 679 | 679 | var cache: Cache = .{ |
| 680 | 680 | .gpa = gpa, |
| 681 | 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 | 685 | cache.addPrefix(comp.dirs.zig_lib); |
| 686 | 686 | cache.addPrefix(comp.dirs.global_cache); |
| 687 | defer cache.manifest_dir.close(); | |
| 687 | defer cache.manifest_dir.close(io); | |
| 688 | 688 | |
| 689 | 689 | var man = cache.obtain(); |
| 690 | 690 | defer man.deinit(); |
| ... | ... | @@ -703,7 +703,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 703 | 703 | .lock = man.toOwnedLock(), |
| 704 | 704 | .dir_path = .{ |
| 705 | 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 | 712 | const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); |
| 713 | 713 | |
| 714 | 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 | 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); | |
| 719 | 719 | |
| 720 | 720 | const abilists_contents = man.files.keys()[abilists_index].contents.?; |
| 721 | 721 | const metadata = try loadMetaData(gpa, abilists_contents); |
| ... | ... | @@ -759,7 +759,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 759 | 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 | 763 | map_contents.deinit(); // The most recent allocation of an arena can be freed :) |
| 764 | 764 | } |
| 765 | 765 | |
| ... | ... | @@ -775,7 +775,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 775 | 775 | try stubs_asm.appendSlice(".text\n"); |
| 776 | 776 | |
| 777 | 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 | 779 | var opt_symbol_name: ?[]const u8 = null; |
| 780 | 780 | var versions_buffer: [32]u8 = undefined; |
| 781 | 781 | var versions_len: usize = undefined; |
| ... | ... | @@ -796,7 +796,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 796 | 796 | // twice, which causes a "duplicate symbol" assembler error. |
| 797 | 797 | var versions_written = std.AutoArrayHashMap(Version, void).init(arena); |
| 798 | 798 | |
| 799 | var inc_reader: std.Io.Reader = .fixed(metadata.inclusions); | |
| 799 | var inc_reader: Io.Reader = .fixed(metadata.inclusions); | |
| 800 | 800 | |
| 801 | 801 | const fn_inclusions_len = try inc_reader.takeInt(u16, .little); |
| 802 | 802 | |
| ... | ... | @@ -1118,7 +1118,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 1118 | 1118 | |
| 1119 | 1119 | var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc. |
| 1120 | 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 | 1122 | try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node); |
| 1123 | 1123 | } |
| 1124 | 1124 | |
| ... | ... | @@ -1130,7 +1130,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 1130 | 1130 | .lock = man.toOwnedLock(), |
| 1131 | 1131 | .dir_path = .{ |
| 1132 | 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 | 1156 | const so_path: Path = .{ |
| 1157 | 1157 | .root_dir = so_files.dir_path.root_dir, |
| 1158 | 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 | 1160 | }) catch return comp.setAllocFailure(), |
| 1161 | 1161 | }; |
| 1162 | 1162 | task_buffer[task_buffer_i] = .{ .load_dso = so_path }; |
src/libs/mingw.zig+39-26| ... | ... | @@ -1,7 +1,8 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const Allocator = std.mem.Allocator; |
| 3 | 4 | const mem = std.mem; |
| 4 | const path = std.fs.path; | |
| 5 | const path = std.Io.Dir.path; | |
| 5 | 6 | const assert = std.debug.assert; |
| 6 | 7 | const log = std.log.scoped(.mingw); |
| 7 | 8 | |
| ... | ... | @@ -241,7 +242,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { |
| 241 | 242 | defer arena_allocator.deinit(); |
| 242 | 243 | const arena = arena_allocator.allocator(); |
| 243 | 244 | |
| 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 | 246 | error.FileNotFound => { |
| 246 | 247 | log.debug("no {s}.def file available to make a DLL import {s}.lib", .{ lib_name, lib_name }); |
| 247 | 248 | // 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 | 258 | var cache: Cache = .{ |
| 258 | 259 | .gpa = gpa, |
| 259 | 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 | 264 | cache.addPrefix(comp.dirs.zig_lib); |
| 264 | 265 | cache.addPrefix(comp.dirs.global_cache); |
| 265 | defer cache.manifest_dir.close(); | |
| 266 | defer cache.manifest_dir.close(io); | |
| 266 | 267 | |
| 267 | 268 | cache.hash.addBytes(build_options.version); |
| 268 | 269 | cache.hash.addOptionalBytes(comp.dirs.zig_lib.path); |
| ... | ... | @@ -296,26 +297,32 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { |
| 296 | 297 | |
| 297 | 298 | const digest = man.final(); |
| 298 | 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 | defer o_dir.close(); | |
| 300 | var o_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}); | |
| 301 | defer o_dir.close(io); | |
| 301 | 302 | |
| 302 | 303 | const aro = @import("aro"); |
| 303 | 304 | var diagnostics: aro.Diagnostics = .{ |
| 304 | 305 | .output = .{ .to_list = .{ .arena = .init(gpa) } }, |
| 305 | 306 | }; |
| 306 | 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 | 309 | defer aro_comp.deinit(); |
| 309 | 310 | |
| 310 | 311 | aro_comp.target = .fromZigTarget(target.*); |
| 311 | 312 | |
| 312 | 313 | const include_dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "mingw", "def-include" }); |
| 313 | 314 | |
| 314 | if (comp.verbose_cc) print: { | |
| 315 | var stderr, _ = std.debug.lockStderrWriter(&.{}); | |
| 316 | defer std.debug.unlockStderrWriter(); | |
| 317 | nosuspend stderr.print("def file: {s}\n", .{def_file_path}) catch break :print; | |
| 318 | nosuspend stderr.print("include dir: {s}\n", .{include_dir}) catch break :print; | |
| 315 | if (comp.verbose_cc) { | |
| 316 | var buffer: [256]u8 = undefined; | |
| 317 | const stderr = try io.lockStderr(&buffer, null); | |
| 318 | defer io.unlockStderr(); | |
| 319 | const w = &stderr.file_writer.interface; | |
| 320 | w.print("def file: {s}\n", .{def_file_path}) catch |err| switch (err) { | |
| 321 | error.WriteFailed => return stderr.file_writer.err.?, | |
| 322 | }; | |
| 323 | w.print("include dir: {s}\n", .{include_dir}) catch |err| switch (err) { | |
| 324 | error.WriteFailed => return stderr.file_writer.err.?, | |
| 325 | }; | |
| 319 | 326 | } |
| 320 | 327 | |
| 321 | 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 | 339 | |
| 333 | 340 | if (aro_comp.diagnostics.output.to_list.messages.items.len != 0) { |
| 334 | 341 | var buffer: [64]u8 = undefined; |
| 335 | const w, const ttyconf = std.debug.lockStderrWriter(&buffer); | |
| 336 | defer std.debug.unlockStderrWriter(); | |
| 342 | const stderr = try io.lockStderr(&buffer, null); | |
| 343 | defer io.unlockStderr(); | |
| 337 | 344 | for (aro_comp.diagnostics.output.to_list.messages.items) |msg| { |
| 338 | 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 | 350 | return error.AroPreprocessorFailed; |
| 341 | 351 | } |
| 342 | 352 | } |
| 343 | 353 | } |
| 344 | 354 | |
| 345 | 355 | const members = members: { |
| 346 | var aw: std.Io.Writer.Allocating = .init(gpa); | |
| 356 | var aw: Io.Writer.Allocating = .init(gpa); | |
| 347 | 357 | errdefer aw.deinit(); |
| 348 | 358 | try pp.prettyPrintTokens(&aw.writer, .result_only); |
| 349 | 359 | |
| ... | ... | @@ -356,8 +366,9 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { |
| 356 | 366 | error.OutOfMemory => |e| return e, |
| 357 | 367 | error.ParseError => { |
| 358 | 368 | var buffer: [64]u8 = undefined; |
| 359 | const w, _ = std.debug.lockStderrWriter(&buffer); | |
| 360 | defer std.debug.unlockStderrWriter(); | |
| 369 | const stderr = try io.lockStderr(&buffer, null); | |
| 370 | defer io.unlockStderr(); | |
| 371 | const w = &stderr.file_writer.interface; | |
| 361 | 372 | try w.writeAll("error: "); |
| 362 | 373 | try def_diagnostics.writeMsg(w, input); |
| 363 | 374 | try w.writeByte('\n'); |
| ... | ... | @@ -376,10 +387,10 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { |
| 376 | 387 | errdefer gpa.free(lib_final_path); |
| 377 | 388 | |
| 378 | 389 | { |
| 379 | const lib_final_file = try o_dir.createFile(final_lib_basename, .{ .truncate = true }); | |
| 380 | defer lib_final_file.close(); | |
| 390 | const lib_final_file = try o_dir.createFile(io, final_lib_basename, .{ .truncate = true }); | |
| 391 | defer lib_final_file.close(io); | |
| 381 | 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 | 394 | try implib.writeCoffArchive(gpa, &file_writer.interface, members); |
| 384 | 395 | try file_writer.interface.flush(); |
| 385 | 396 | } |
| ... | ... | @@ -401,11 +412,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { |
| 401 | 412 | |
| 402 | 413 | pub fn libExists( |
| 403 | 414 | allocator: Allocator, |
| 415 | io: Io, | |
| 404 | 416 | target: *const std.Target, |
| 405 | 417 | zig_lib_directory: Cache.Directory, |
| 406 | 418 | lib_name: []const u8, |
| 407 | 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 | 421 | error.FileNotFound => return false, |
| 410 | 422 | else => |e| return e, |
| 411 | 423 | }; |
| ... | ... | @@ -417,6 +429,7 @@ pub fn libExists( |
| 417 | 429 | /// see if a .def file exists. |
| 418 | 430 | fn findDef( |
| 419 | 431 | allocator: Allocator, |
| 432 | io: Io, | |
| 420 | 433 | target: *const std.Target, |
| 421 | 434 | zig_lib_directory: Cache.Directory, |
| 422 | 435 | lib_name: []const u8, |
| ... | ... | @@ -442,7 +455,7 @@ fn findDef( |
| 442 | 455 | } else { |
| 443 | 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 | 459 | return override_path.toOwnedSlice(); |
| 447 | 460 | } else |err| switch (err) { |
| 448 | 461 | error.FileNotFound => {}, |
| ... | ... | @@ -459,7 +472,7 @@ fn findDef( |
| 459 | 472 | } else { |
| 460 | 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 | 476 | return override_path.toOwnedSlice(); |
| 464 | 477 | } else |err| switch (err) { |
| 465 | 478 | error.FileNotFound => {}, |
| ... | ... | @@ -476,7 +489,7 @@ fn findDef( |
| 476 | 489 | } else { |
| 477 | 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 | 493 | return override_path.toOwnedSlice(); |
| 481 | 494 | } else |err| switch (err) { |
| 482 | 495 | error.FileNotFound => {}, |
src/libs/mingw/def.zig+22-10| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | |
| 3 | 4 | pub const ModuleDefinitionType = enum { |
| 4 | 5 | mingw, |
| ... | ... | @@ -663,7 +664,9 @@ test parse { |
| 663 | 664 | \\ |
| 664 | 665 | ; |
| 665 | 666 | |
| 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 | 671 | .name = "foo", |
| 669 | 672 | .mangled_symbol_name = null, |
| ... | ... | @@ -743,7 +746,7 @@ test parse { |
| 743 | 746 | }, |
| 744 | 747 | }); |
| 745 | 748 | |
| 746 | try testParse(.I386, source, "foo.dll", &[_]ModuleDefinition.Export{ | |
| 749 | try testParse(io, .I386, source, "foo.dll", &[_]ModuleDefinition.Export{ | |
| 747 | 750 | .{ |
| 748 | 751 | .name = "_foo", |
| 749 | 752 | .mangled_symbol_name = null, |
| ... | ... | @@ -823,7 +826,7 @@ test parse { |
| 823 | 826 | }, |
| 824 | 827 | }); |
| 825 | 828 | |
| 826 | try testParse(.ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{ | |
| 829 | try testParse(io, .ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{ | |
| 827 | 830 | .{ |
| 828 | 831 | .name = "foo", |
| 829 | 832 | .mangled_symbol_name = null, |
| ... | ... | @@ -903,7 +906,7 @@ test parse { |
| 903 | 906 | }, |
| 904 | 907 | }); |
| 905 | 908 | |
| 906 | try testParse(.ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{ | |
| 909 | try testParse(io, .ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{ | |
| 907 | 910 | .{ |
| 908 | 911 | .name = "foo", |
| 909 | 912 | .mangled_symbol_name = null, |
| ... | ... | @@ -997,7 +1000,9 @@ test "ntdll" { |
| 997 | 1000 | \\RtlActivateActivationContextUnsafeFast@0 |
| 998 | 1001 | ; |
| 999 | 1002 | |
| 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 | 1007 | .name = "RtlDispatchAPC@12", |
| 1003 | 1008 | .mangled_symbol_name = null, |
| ... | ... | @@ -1023,15 +1028,22 @@ test "ntdll" { |
| 1023 | 1028 | }); |
| 1024 | 1029 | } |
| 1025 | 1030 | |
| 1026 | fn testParse(machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8, expected_module_name: []const u8, expected_exports: []const ModuleDefinition.Export) !void { | |
| 1031 | fn 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 | 1038 | var diagnostics: Diagnostics = undefined; |
| 1028 | 1039 | const module = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) { |
| 1029 | 1040 | error.OutOfMemory => |e| return e, |
| 1030 | 1041 | error.ParseError => { |
| 1031 | const stderr, _ = std.debug.lockStderrWriter(&.{}); | |
| 1032 | defer std.debug.unlockStderrWriter(); | |
| 1033 | try diagnostics.writeMsg(stderr, source); | |
| 1034 | try stderr.writeByte('\n'); | |
| 1042 | const stderr = try io.lockStderr(&.{}, null); | |
| 1043 | defer io.unlockStderr(); | |
| 1044 | const w = &stderr.file_writer.interface; | |
| 1045 | try diagnostics.writeMsg(w, source); | |
| 1046 | try w.writeByte('\n'); | |
| 1035 | 1047 | return err; |
| 1036 | 1048 | }, |
| 1037 | 1049 | }; |
src/libs/netbsd.zig+13-13| ... | ... | @@ -1,9 +1,9 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const Allocator = std.mem.Allocator; |
| 3 | 4 | const mem = std.mem; |
| 4 | 5 | const log = std.log; |
| 5 | const fs = std.fs; | |
| 6 | const path = fs.path; | |
| 6 | const path = std.Io.Dir.path; | |
| 7 | 7 | const assert = std.debug.assert; |
| 8 | 8 | const Version = std.SemanticVersion; |
| 9 | 9 | const Path = std.Build.Cache.Path; |
| ... | ... | @@ -346,8 +346,8 @@ pub const BuiltSharedObjects = struct { |
| 346 | 346 | lock: Cache.Lock, |
| 347 | 347 | dir_path: Path, |
| 348 | 348 | |
| 349 | pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void { | |
| 350 | self.lock.release(); | |
| 349 | pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void { | |
| 350 | self.lock.release(io); | |
| 351 | 351 | gpa.free(self.dir_path.sub_path); |
| 352 | 352 | self.* = undefined; |
| 353 | 353 | } |
| ... | ... | @@ -385,12 +385,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 385 | 385 | var cache: Cache = .{ |
| 386 | 386 | .gpa = gpa, |
| 387 | 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 | 391 | cache.addPrefix(comp.dirs.zig_lib); |
| 392 | 392 | cache.addPrefix(comp.dirs.global_cache); |
| 393 | defer cache.manifest_dir.close(); | |
| 393 | defer cache.manifest_dir.close(io); | |
| 394 | 394 | |
| 395 | 395 | var man = cache.obtain(); |
| 396 | 396 | defer man.deinit(); |
| ... | ... | @@ -409,7 +409,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 409 | 409 | .lock = man.toOwnedLock(), |
| 410 | 410 | .dir_path = .{ |
| 411 | 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 | 418 | const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); |
| 419 | 419 | |
| 420 | 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 | 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); | |
| 425 | 425 | |
| 426 | 426 | const abilists_contents = man.files.keys()[abilists_index].contents.?; |
| 427 | 427 | const metadata = try loadMetaData(gpa, abilists_contents); |
| ... | ... | @@ -628,7 +628,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 628 | 628 | |
| 629 | 629 | var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc. |
| 630 | 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 | 632 | try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node); |
| 633 | 633 | } |
| 634 | 634 | |
| ... | ... | @@ -640,7 +640,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 640 | 640 | .lock = man.toOwnedLock(), |
| 641 | 641 | .dir_path = .{ |
| 642 | 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 | 661 | const so_path: Path = .{ |
| 662 | 662 | .root_dir = so_files.dir_path.root_dir, |
| 663 | 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 | 665 | }) catch return comp.setAllocFailure(), |
| 666 | 666 | }; |
| 667 | 667 | task_buffer[task_buffer_i] = .{ .load_dso = so_path }; |
src/link.zig+138-86| ... | ... | @@ -393,7 +393,7 @@ pub const File = struct { |
| 393 | 393 | comp: *Compilation, |
| 394 | 394 | emit: Path, |
| 395 | 395 | |
| 396 | file: ?fs.File, | |
| 396 | file: ?Io.File, | |
| 397 | 397 | /// When using the LLVM backend, the emitted object is written to a file with this name. This |
| 398 | 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 | 620 | emit.sub_path, std.crypto.random.int(u32), |
| 621 | 621 | }); |
| 622 | 622 | defer gpa.free(tmp_sub_path); |
| 623 | try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, .{}); | |
| 624 | try emit.root_dir.handle.rename(tmp_sub_path, emit.sub_path); | |
| 623 | try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, io, .{}); | |
| 624 | try emit.root_dir.handle.rename(tmp_sub_path, emit.root_dir.handle, emit.sub_path, io); | |
| 625 | 625 | switch (builtin.os.tag) { |
| 626 | 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 | 629 | .maccatalyst, .macos => { |
| 630 | 630 | const macho_file = base.cast(.macho).?; |
| 631 | 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 | 635 | .windows => unreachable, |
| ... | ... | @@ -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 | 642 | .elf2, .coff2 => if (base.file == null) { |
| 643 | 643 | const mf = if (base.cast(.elf2)) |elf| |
| ... | ... | @@ -646,10 +646,10 @@ pub const File = struct { |
| 646 | 646 | &coff.mf |
| 647 | 647 | else |
| 648 | 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 | 650 | .mode = .read_write, |
| 651 | 651 | }); |
| 652 | base.file = .adaptFromNewApi(mf.file); | |
| 652 | base.file = mf.file; | |
| 653 | 653 | try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1])); |
| 654 | 654 | }, |
| 655 | 655 | .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), |
| ... | ... | @@ -687,7 +687,7 @@ pub const File = struct { |
| 687 | 687 | .lld => assert(base.file == null), |
| 688 | 688 | .elf => if (base.file) |f| { |
| 689 | 689 | dev.check(.elf_linker); |
| 690 | f.close(); | |
| 690 | f.close(io); | |
| 691 | 691 | base.file = null; |
| 692 | 692 | |
| 693 | 693 | if (base.child_pid) |pid| { |
| ... | ... | @@ -701,7 +701,7 @@ pub const File = struct { |
| 701 | 701 | }, |
| 702 | 702 | .macho, .wasm => if (base.file) |f| { |
| 703 | 703 | dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker }); |
| 704 | f.close(); | |
| 704 | f.close(io); | |
| 705 | 705 | base.file = null; |
| 706 | 706 | |
| 707 | 707 | if (base.child_pid) |pid| { |
| ... | ... | @@ -852,10 +852,12 @@ pub const File = struct { |
| 852 | 852 | } |
| 853 | 853 | } |
| 854 | 854 | |
| 855 | pub fn releaseLock(self: *File) void { | |
| 856 | if (self.lock) |*lock| { | |
| 857 | lock.release(); | |
| 858 | self.lock = null; | |
| 855 | pub fn releaseLock(base: *File) void { | |
| 856 | const comp = base.comp; | |
| 857 | const io = comp.io; | |
| 858 | if (base.lock) |*lock| { | |
| 859 | lock.release(io); | |
| 860 | base.lock = null; | |
| 859 | 861 | } |
| 860 | 862 | } |
| 861 | 863 | |
| ... | ... | @@ -866,8 +868,9 @@ pub const File = struct { |
| 866 | 868 | } |
| 867 | 869 | |
| 868 | 870 | pub fn destroy(base: *File) void { |
| 871 | const io = base.comp.io; | |
| 869 | 872 | base.releaseLock(); |
| 870 | if (base.file) |f| f.close(); | |
| 873 | if (base.file) |f| f.close(io); | |
| 871 | 874 | switch (base.tag) { |
| 872 | 875 | .plan9 => unreachable, |
| 873 | 876 | inline else => |tag| { |
| ... | ... | @@ -897,16 +900,16 @@ pub const File = struct { |
| 897 | 900 | } |
| 898 | 901 | } |
| 899 | 902 | |
| 900 | pub const FlushError = error{ | |
| 903 | pub const FlushError = Io.Cancelable || Allocator.Error || error{ | |
| 901 | 904 | /// Indicates an error will be present in `Compilation.link_diags`. |
| 902 | 905 | LinkFailure, |
| 903 | OutOfMemory, | |
| 904 | 906 | }; |
| 905 | 907 | |
| 906 | 908 | /// Commit pending changes and write headers. Takes into account final output mode. |
| 907 | 909 | /// `arena` has the lifetime of the call to `Compilation.update`. |
| 908 | 910 | pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void { |
| 909 | 911 | const comp = base.comp; |
| 912 | const io = comp.io; | |
| 910 | 913 | if (comp.clang_preprocessor_mode == .yes or comp.clang_preprocessor_mode == .pch) { |
| 911 | 914 | dev.check(.clang_command); |
| 912 | 915 | const emit = base.emit; |
| ... | ... | @@ -917,12 +920,19 @@ pub const File = struct { |
| 917 | 920 | assert(comp.c_object_table.count() == 1); |
| 918 | 921 | const the_key = comp.c_object_table.keys()[0]; |
| 919 | 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 | 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 | 933 | std.fmt.alt(@as(Path, cached_pp_file_path), .formatEscapeChar), |
| 924 | 934 | std.fmt.alt(@as(Path, emit), .formatEscapeChar), |
| 925 | @errorName(err), | |
| 935 | err, | |
| 926 | 936 | }); |
| 927 | 937 | }; |
| 928 | 938 | return; |
| ... | ... | @@ -1060,9 +1070,10 @@ pub const File = struct { |
| 1060 | 1070 | /// Opens a path as an object file and parses it into the linker. |
| 1061 | 1071 | fn openLoadObject(base: *File, path: Path) anyerror!void { |
| 1062 | 1072 | if (base.tag == .lld) return; |
| 1073 | const io = base.comp.io; | |
| 1063 | 1074 | const diags = &base.comp.link_diags; |
| 1064 | const input = try openObjectInput(diags, path); | |
| 1065 | errdefer input.object.file.close(); | |
| 1075 | const input = try openObjectInput(io, diags, path); | |
| 1076 | errdefer input.object.file.close(io); | |
| 1066 | 1077 | try loadInput(base, input); |
| 1067 | 1078 | } |
| 1068 | 1079 | |
| ... | ... | @@ -1070,21 +1081,22 @@ pub const File = struct { |
| 1070 | 1081 | /// If `query` is non-null, allows GNU ld scripts. |
| 1071 | 1082 | fn openLoadArchive(base: *File, path: Path, opt_query: ?UnresolvedInput.Query) anyerror!void { |
| 1072 | 1083 | if (base.tag == .lld) return; |
| 1084 | const io = base.comp.io; | |
| 1073 | 1085 | if (opt_query) |query| { |
| 1074 | const archive = try openObject(path, query.must_link, query.hidden); | |
| 1075 | errdefer archive.file.close(); | |
| 1086 | const archive = try openObject(io, path, query.must_link, query.hidden); | |
| 1087 | errdefer archive.file.close(io); | |
| 1076 | 1088 | loadInput(base, .{ .archive = archive }) catch |err| switch (err) { |
| 1077 | 1089 | error.BadMagic, error.UnexpectedEndOfFile => { |
| 1078 | 1090 | if (base.tag != .elf and base.tag != .elf2) return err; |
| 1079 | 1091 | try loadGnuLdScript(base, path, query, archive.file); |
| 1080 | archive.file.close(); | |
| 1092 | archive.file.close(io); | |
| 1081 | 1093 | return; |
| 1082 | 1094 | }, |
| 1083 | 1095 | else => return err, |
| 1084 | 1096 | }; |
| 1085 | 1097 | } else { |
| 1086 | const archive = try openObject(path, false, false); | |
| 1087 | errdefer archive.file.close(); | |
| 1098 | const archive = try openObject(io, path, false, false); | |
| 1099 | errdefer archive.file.close(io); | |
| 1088 | 1100 | try loadInput(base, .{ .archive = archive }); |
| 1089 | 1101 | } |
| 1090 | 1102 | } |
| ... | ... | @@ -1093,29 +1105,30 @@ pub const File = struct { |
| 1093 | 1105 | /// Handles GNU ld scripts. |
| 1094 | 1106 | fn openLoadDso(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void { |
| 1095 | 1107 | if (base.tag == .lld) return; |
| 1096 | const dso = try openDso(path, query.needed, query.weak, query.reexport); | |
| 1097 | errdefer dso.file.close(); | |
| 1108 | const io = base.comp.io; | |
| 1109 | const dso = try openDso(io, path, query.needed, query.weak, query.reexport); | |
| 1110 | errdefer dso.file.close(io); | |
| 1098 | 1111 | loadInput(base, .{ .dso = dso }) catch |err| switch (err) { |
| 1099 | 1112 | error.BadMagic, error.UnexpectedEndOfFile => { |
| 1100 | 1113 | if (base.tag != .elf and base.tag != .elf2) return err; |
| 1101 | 1114 | try loadGnuLdScript(base, path, query, dso.file); |
| 1102 | dso.file.close(); | |
| 1115 | dso.file.close(io); | |
| 1103 | 1116 | return; |
| 1104 | 1117 | }, |
| 1105 | 1118 | else => return err, |
| 1106 | 1119 | }; |
| 1107 | 1120 | } |
| 1108 | 1121 | |
| 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 | 1123 | const comp = base.comp; |
| 1124 | const io = comp.io; | |
| 1111 | 1125 | const diags = &comp.link_diags; |
| 1112 | 1126 | const gpa = comp.gpa; |
| 1113 | const io = comp.io; | |
| 1114 | const stat = try file.stat(); | |
| 1127 | const stat = try file.stat(io); | |
| 1115 | 1128 | const size = std.math.cast(u32, stat.size) orelse return error.FileTooBig; |
| 1116 | 1129 | const buf = try gpa.alloc(u8, size); |
| 1117 | 1130 | defer gpa.free(buf); |
| 1118 | const n = try file.preadAll(buf, 0); | |
| 1131 | const n = try file.readPositionalAll(io, buf, 0); | |
| 1119 | 1132 | if (buf.len != n) return error.UnexpectedEndOfFile; |
| 1120 | 1133 | var ld_script = try LdScript.parse(gpa, diags, path, buf); |
| 1121 | 1134 | defer ld_script.deinit(gpa); |
| ... | ... | @@ -1180,6 +1193,32 @@ pub const File = struct { |
| 1180 | 1193 | } |
| 1181 | 1194 | } |
| 1182 | 1195 | |
| 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 | 1222 | pub const Tag = enum { |
| 1184 | 1223 | coff2, |
| 1185 | 1224 | elf, |
| ... | ... | @@ -1231,22 +1270,26 @@ pub const File = struct { |
| 1231 | 1270 | ty: InternPool.Index, |
| 1232 | 1271 | }; |
| 1233 | 1272 | |
| 1234 | pub fn determineMode( | |
| 1273 | pub fn determinePermissions( | |
| 1235 | 1274 | output_mode: std.builtin.OutputMode, |
| 1236 | 1275 | link_mode: std.builtin.LinkMode, |
| 1237 | ) fs.File.Mode { | |
| 1276 | ) Io.File.Permissions { | |
| 1238 | 1277 | // On common systems with a 0o022 umask, 0o777 will still result in a file created |
| 1239 | 1278 | // with 0o755 permissions, but it works appropriately if the system is configured |
| 1240 | 1279 | // more leniently. As another data point, C's fopen seems to open files with the |
| 1241 | 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 | 1286 | switch (output_mode) { |
| 1244 | 1287 | .Lib => return switch (link_mode) { |
| 1245 | 1288 | .dynamic => executable_mode, |
| 1246 | .static => fs.File.default_mode, | |
| 1289 | .static => .default_file, | |
| 1247 | 1290 | }, |
| 1248 | 1291 | .Exe => return executable_mode, |
| 1249 | .Obj => return fs.File.default_mode, | |
| 1292 | .Obj => return .default_file, | |
| 1250 | 1293 | } |
| 1251 | 1294 | } |
| 1252 | 1295 | |
| ... | ... | @@ -1656,19 +1699,19 @@ pub const Input = union(enum) { |
| 1656 | 1699 | |
| 1657 | 1700 | pub const Object = struct { |
| 1658 | 1701 | path: Path, |
| 1659 | file: fs.File, | |
| 1702 | file: Io.File, | |
| 1660 | 1703 | must_link: bool, |
| 1661 | 1704 | hidden: bool, |
| 1662 | 1705 | }; |
| 1663 | 1706 | |
| 1664 | 1707 | pub const Res = struct { |
| 1665 | 1708 | path: Path, |
| 1666 | file: fs.File, | |
| 1709 | file: Io.File, | |
| 1667 | 1710 | }; |
| 1668 | 1711 | |
| 1669 | 1712 | pub const Dso = struct { |
| 1670 | 1713 | path: Path, |
| 1671 | file: fs.File, | |
| 1714 | file: Io.File, | |
| 1672 | 1715 | needed: bool, |
| 1673 | 1716 | weak: bool, |
| 1674 | 1717 | reexport: bool, |
| ... | ... | @@ -1690,7 +1733,7 @@ pub const Input = union(enum) { |
| 1690 | 1733 | } |
| 1691 | 1734 | |
| 1692 | 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 | 1737 | return switch (input) { |
| 1695 | 1738 | .object, .archive => |obj| .{ obj.path, obj.file }, |
| 1696 | 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 | 1778 | pub fn resolveInputs( |
| 1736 | 1779 | gpa: Allocator, |
| 1737 | 1780 | arena: Allocator, |
| 1781 | io: Io, | |
| 1738 | 1782 | target: *const std.Target, |
| 1739 | 1783 | /// This function mutates this array but does not take ownership. |
| 1740 | 1784 | /// Allocated with `gpa`. |
| ... | ... | @@ -1784,6 +1828,7 @@ pub fn resolveInputs( |
| 1784 | 1828 | for (lib_directories) |lib_directory| switch (try resolveLibInput( |
| 1785 | 1829 | gpa, |
| 1786 | 1830 | arena, |
| 1831 | io, | |
| 1787 | 1832 | unresolved_inputs, |
| 1788 | 1833 | resolved_inputs, |
| 1789 | 1834 | &checked_paths, |
| ... | ... | @@ -1810,6 +1855,7 @@ pub fn resolveInputs( |
| 1810 | 1855 | for (lib_directories) |lib_directory| switch (try resolveLibInput( |
| 1811 | 1856 | gpa, |
| 1812 | 1857 | arena, |
| 1858 | io, | |
| 1813 | 1859 | unresolved_inputs, |
| 1814 | 1860 | resolved_inputs, |
| 1815 | 1861 | &checked_paths, |
| ... | ... | @@ -1837,6 +1883,7 @@ pub fn resolveInputs( |
| 1837 | 1883 | switch (try resolveLibInput( |
| 1838 | 1884 | gpa, |
| 1839 | 1885 | arena, |
| 1886 | io, | |
| 1840 | 1887 | unresolved_inputs, |
| 1841 | 1888 | resolved_inputs, |
| 1842 | 1889 | &checked_paths, |
| ... | ... | @@ -1855,6 +1902,7 @@ pub fn resolveInputs( |
| 1855 | 1902 | switch (try resolveLibInput( |
| 1856 | 1903 | gpa, |
| 1857 | 1904 | arena, |
| 1905 | io, | |
| 1858 | 1906 | unresolved_inputs, |
| 1859 | 1907 | resolved_inputs, |
| 1860 | 1908 | &checked_paths, |
| ... | ... | @@ -1886,6 +1934,7 @@ pub fn resolveInputs( |
| 1886 | 1934 | if (try resolvePathInput( |
| 1887 | 1935 | gpa, |
| 1888 | 1936 | arena, |
| 1937 | io, | |
| 1889 | 1938 | unresolved_inputs, |
| 1890 | 1939 | resolved_inputs, |
| 1891 | 1940 | &ld_script_bytes, |
| ... | ... | @@ -1903,6 +1952,7 @@ pub fn resolveInputs( |
| 1903 | 1952 | switch ((try resolvePathInput( |
| 1904 | 1953 | gpa, |
| 1905 | 1954 | arena, |
| 1955 | io, | |
| 1906 | 1956 | unresolved_inputs, |
| 1907 | 1957 | resolved_inputs, |
| 1908 | 1958 | &ld_script_bytes, |
| ... | ... | @@ -1930,6 +1980,7 @@ pub fn resolveInputs( |
| 1930 | 1980 | if (try resolvePathInput( |
| 1931 | 1981 | gpa, |
| 1932 | 1982 | arena, |
| 1983 | io, | |
| 1933 | 1984 | unresolved_inputs, |
| 1934 | 1985 | resolved_inputs, |
| 1935 | 1986 | &ld_script_bytes, |
| ... | ... | @@ -1969,6 +2020,7 @@ const fatal = std.process.fatal; |
| 1969 | 2020 | fn resolveLibInput( |
| 1970 | 2021 | gpa: Allocator, |
| 1971 | 2022 | arena: Allocator, |
| 2023 | io: Io, | |
| 1972 | 2024 | /// Allocated via `gpa`. |
| 1973 | 2025 | unresolved_inputs: *std.ArrayList(UnresolvedInput), |
| 1974 | 2026 | /// Allocated via `gpa`. |
| ... | ... | @@ -1994,11 +2046,11 @@ fn resolveLibInput( |
| 1994 | 2046 | .sub_path = try std.fmt.allocPrint(arena, "lib{s}.tbd", .{lib_name}), |
| 1995 | 2047 | }; |
| 1996 | 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 | 2050 | error.FileNotFound => break :tbd, |
| 1999 | 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 | 2054 | return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query); |
| 2003 | 2055 | } |
| 2004 | 2056 | |
| ... | ... | @@ -2013,7 +2065,7 @@ fn resolveLibInput( |
| 2013 | 2065 | }), |
| 2014 | 2066 | }; |
| 2015 | 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 | 2069 | .path = test_path, |
| 2018 | 2070 | .query = name_query.query, |
| 2019 | 2071 | }, link_mode, color)) { |
| ... | ... | @@ -2030,13 +2082,13 @@ fn resolveLibInput( |
| 2030 | 2082 | .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}), |
| 2031 | 2083 | }; |
| 2032 | 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 | 2086 | error.FileNotFound => break :so, |
| 2035 | 2087 | else => |e| fatal("unable to search for so library '{f}': {s}", .{ |
| 2036 | 2088 | test_path, @errorName(e), |
| 2037 | 2089 | }), |
| 2038 | 2090 | }; |
| 2039 | errdefer file.close(); | |
| 2091 | errdefer file.close(io); | |
| 2040 | 2092 | return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query); |
| 2041 | 2093 | } |
| 2042 | 2094 | |
| ... | ... | @@ -2048,11 +2100,11 @@ fn resolveLibInput( |
| 2048 | 2100 | .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}), |
| 2049 | 2101 | }; |
| 2050 | 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 | 2104 | error.FileNotFound => break :mingw, |
| 2053 | 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 | 2108 | return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query); |
| 2057 | 2109 | } |
| 2058 | 2110 | |
| ... | ... | @@ -2062,7 +2114,7 @@ fn resolveLibInput( |
| 2062 | 2114 | fn finishResolveLibInput( |
| 2063 | 2115 | resolved_inputs: *std.ArrayList(Input), |
| 2064 | 2116 | path: Path, |
| 2065 | file: std.fs.File, | |
| 2117 | file: Io.File, | |
| 2066 | 2118 | link_mode: std.builtin.LinkMode, |
| 2067 | 2119 | query: UnresolvedInput.Query, |
| 2068 | 2120 | ) ResolveLibInputResult { |
| ... | ... | @@ -2087,6 +2139,7 @@ fn finishResolveLibInput( |
| 2087 | 2139 | fn resolvePathInput( |
| 2088 | 2140 | gpa: Allocator, |
| 2089 | 2141 | arena: Allocator, |
| 2142 | io: Io, | |
| 2090 | 2143 | /// Allocated with `gpa`. |
| 2091 | 2144 | unresolved_inputs: *std.ArrayList(UnresolvedInput), |
| 2092 | 2145 | /// Allocated with `gpa`. |
| ... | ... | @@ -2098,12 +2151,12 @@ fn resolvePathInput( |
| 2098 | 2151 | color: std.zig.Color, |
| 2099 | 2152 | ) Allocator.Error!?ResolveLibInputResult { |
| 2100 | 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), | |
| 2102 | .shared_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color), | |
| 2154 | .static_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color), | |
| 2155 | .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color), | |
| 2103 | 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 | 2158 | fatal("failed to open object {f}: {s}", .{ pq.path, @errorName(err) }); |
| 2106 | errdefer file.close(); | |
| 2159 | errdefer file.close(io); | |
| 2107 | 2160 | try resolved_inputs.append(gpa, .{ .object = .{ |
| 2108 | 2161 | .path = pq.path, |
| 2109 | 2162 | .file = file, |
| ... | ... | @@ -2113,9 +2166,9 @@ fn resolvePathInput( |
| 2113 | 2166 | return null; |
| 2114 | 2167 | }, |
| 2115 | 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 | 2170 | fatal("failed to open windows resource {f}: {s}", .{ pq.path, @errorName(err) }); |
| 2118 | errdefer file.close(); | |
| 2171 | errdefer file.close(io); | |
| 2119 | 2172 | try resolved_inputs.append(gpa, .{ .res = .{ |
| 2120 | 2173 | .path = pq.path, |
| 2121 | 2174 | .file = file, |
| ... | ... | @@ -2129,6 +2182,7 @@ fn resolvePathInput( |
| 2129 | 2182 | fn resolvePathInputLib( |
| 2130 | 2183 | gpa: Allocator, |
| 2131 | 2184 | arena: Allocator, |
| 2185 | io: Io, | |
| 2132 | 2186 | /// Allocated with `gpa`. |
| 2133 | 2187 | unresolved_inputs: *std.ArrayList(UnresolvedInput), |
| 2134 | 2188 | /// Allocated with `gpa`. |
| ... | ... | @@ -2149,30 +2203,29 @@ fn resolvePathInputLib( |
| 2149 | 2203 | .static_library, .shared_library => true, |
| 2150 | 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 | 2207 | error.FileNotFound => return .no_match, |
| 2154 | else => |e| fatal("unable to search for {s} library '{f}': {s}", .{ | |
| 2155 | @tagName(link_mode), std.fmt.alt(test_path, .formatEscapeChar), @errorName(e), | |
| 2208 | else => |e| fatal("unable to search for {t} library '{f}': {t}", .{ | |
| 2209 | link_mode, std.fmt.alt(test_path, .formatEscapeChar), e, | |
| 2156 | 2210 | }), |
| 2157 | 2211 | }; |
| 2158 | errdefer file.close(); | |
| 2212 | errdefer file.close(io); | |
| 2159 | 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}", .{ | |
| 2161 | std.fmt.alt(test_path, .formatEscapeChar), @errorName(err), | |
| 2162 | }); | |
| 2214 | const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err| | |
| 2215 | fatal("failed to read '{f}': {t}", .{ std.fmt.alt(test_path, .formatEscapeChar), err }); | |
| 2163 | 2216 | const buf = ld_script_bytes.items[0..n]; |
| 2164 | 2217 | if (mem.startsWith(u8, buf, std.elf.MAGIC) or mem.startsWith(u8, buf, std.elf.ARMAG)) { |
| 2165 | 2218 | // Appears to be an ELF or archive file. |
| 2166 | 2219 | return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query); |
| 2167 | 2220 | } |
| 2168 | const stat = file.stat() catch |err| | |
| 2169 | fatal("failed to stat {f}: {s}", .{ test_path, @errorName(err) }); | |
| 2221 | const stat = file.stat(io) catch |err| | |
| 2222 | fatal("failed to stat {f}: {t}", .{ test_path, err }); | |
| 2170 | 2223 | const size = std.math.cast(u32, stat.size) orelse |
| 2171 | 2224 | fatal("{f}: linker script too big", .{test_path}); |
| 2172 | 2225 | try ld_script_bytes.resize(gpa, size); |
| 2173 | 2226 | const buf2 = ld_script_bytes.items[n..]; |
| 2174 | const n2 = file.preadAll(buf2, n) catch |err| | |
| 2175 | fatal("failed to read {f}: {s}", .{ test_path, @errorName(err) }); | |
| 2227 | const n2 = file.readPositionalAll(io, buf2, n) catch |err| | |
| 2228 | fatal("failed to read {f}: {t}", .{ test_path, err }); | |
| 2176 | 2229 | if (n2 != buf2.len) fatal("failed to read {f}: unexpected end of file", .{test_path}); |
| 2177 | 2230 | |
| 2178 | 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 | 2245 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 2193 | 2246 | defer error_bundle.deinit(gpa); |
| 2194 | 2247 | |
| 2195 | error_bundle.renderToStdErr(.{}, color); | |
| 2196 | ||
| 2248 | error_bundle.renderToStderr(io, .{}, color) catch {}; | |
| 2197 | 2249 | std.process.exit(1); |
| 2198 | 2250 | } |
| 2199 | 2251 | |
| 2200 | 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 | 2254 | defer ld_script.deinit(gpa); |
| 2203 | 2255 | |
| 2204 | 2256 | try unresolved_inputs.ensureUnusedCapacity(gpa, ld_script.args.len); |
| ... | ... | @@ -2223,23 +2275,23 @@ fn resolvePathInputLib( |
| 2223 | 2275 | } }); |
| 2224 | 2276 | } |
| 2225 | 2277 | } |
| 2226 | file.close(); | |
| 2278 | file.close(io); | |
| 2227 | 2279 | return .ok; |
| 2228 | 2280 | } |
| 2229 | 2281 | |
| 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 | 2283 | error.FileNotFound => return .no_match, |
| 2232 | 2284 | else => |e| fatal("unable to search for {s} library {f}: {s}", .{ |
| 2233 | 2285 | @tagName(link_mode), test_path, @errorName(e), |
| 2234 | 2286 | }), |
| 2235 | 2287 | }; |
| 2236 | errdefer file.close(); | |
| 2288 | errdefer file.close(io); | |
| 2237 | 2289 | return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query); |
| 2238 | 2290 | } |
| 2239 | 2291 | |
| 2240 | pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object { | |
| 2241 | var file = try path.root_dir.handle.openFile(path.sub_path, .{}); | |
| 2242 | errdefer file.close(); | |
| 2292 | pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object { | |
| 2293 | var file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); | |
| 2294 | errdefer file.close(io); | |
| 2243 | 2295 | return .{ |
| 2244 | 2296 | .path = path, |
| 2245 | 2297 | .file = file, |
| ... | ... | @@ -2248,9 +2300,9 @@ pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object { |
| 2248 | 2300 | }; |
| 2249 | 2301 | } |
| 2250 | 2302 | |
| 2251 | pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso { | |
| 2252 | var file = try path.root_dir.handle.openFile(path.sub_path, .{}); | |
| 2253 | errdefer file.close(); | |
| 2303 | pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso { | |
| 2304 | var file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); | |
| 2305 | errdefer file.close(io); | |
| 2254 | 2306 | return .{ |
| 2255 | 2307 | .path = path, |
| 2256 | 2308 | .file = file, |
| ... | ... | @@ -2260,20 +2312,20 @@ pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso |
| 2260 | 2312 | }; |
| 2261 | 2313 | } |
| 2262 | 2314 | |
| 2263 | pub fn openObjectInput(diags: *Diags, path: Path) error{LinkFailure}!Input { | |
| 2264 | return .{ .object = openObject(path, false, false) catch |err| { | |
| 2315 | pub fn openObjectInput(io: Io, diags: *Diags, path: Path) error{LinkFailure}!Input { | |
| 2316 | return .{ .object = openObject(io, path, false, false) catch |err| { | |
| 2265 | 2317 | return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) }); |
| 2266 | 2318 | } }; |
| 2267 | 2319 | } |
| 2268 | 2320 | |
| 2269 | pub fn openArchiveInput(diags: *Diags, path: Path, must_link: bool, hidden: bool) error{LinkFailure}!Input { | |
| 2270 | return .{ .archive = openObject(path, must_link, hidden) catch |err| { | |
| 2321 | pub fn openArchiveInput(io: Io, diags: *Diags, path: Path, must_link: bool, hidden: bool) error{LinkFailure}!Input { | |
| 2322 | return .{ .archive = openObject(io, path, must_link, hidden) catch |err| { | |
| 2271 | 2323 | return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) }); |
| 2272 | 2324 | } }; |
| 2273 | 2325 | } |
| 2274 | 2326 | |
| 2275 | pub fn openDsoInput(diags: *Diags, path: Path, needed: bool, weak: bool, reexport: bool) error{LinkFailure}!Input { | |
| 2276 | return .{ .dso = openDso(path, needed, weak, reexport) catch |err| { | |
| 2327 | pub fn openDsoInput(io: Io, diags: *Diags, path: Path, needed: bool, weak: bool, reexport: bool) error{LinkFailure}!Input { | |
| 2328 | return .{ .dso = openDso(io, path, needed, weak, reexport) catch |err| { | |
| 2277 | 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 | 124 | emit: Path, |
| 125 | 125 | options: link.File.OpenOptions, |
| 126 | 126 | ) !*C { |
| 127 | const io = comp.io; | |
| 127 | 128 | const target = &comp.root_mod.resolved_target.result; |
| 128 | 129 | assert(target.ofmt == .c); |
| 129 | 130 | const optimize_mode = comp.root_mod.optimize_mode; |
| ... | ... | @@ -135,11 +136,11 @@ pub fn createEmpty( |
| 135 | 136 | assert(!use_lld); |
| 136 | 137 | assert(!use_llvm); |
| 137 | 138 | |
| 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 | 140 | // Truncation is done on `flush`. |
| 140 | 141 | .truncate = false, |
| 141 | 142 | }); |
| 142 | errdefer file.close(); | |
| 143 | errdefer file.close(io); | |
| 143 | 144 | |
| 144 | 145 | const c_file = try arena.create(C); |
| 145 | 146 | |
| ... | ... | @@ -370,6 +371,7 @@ pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.P |
| 370 | 371 | const comp = self.base.comp; |
| 371 | 372 | const diags = &comp.link_diags; |
| 372 | 373 | const gpa = comp.gpa; |
| 374 | const io = comp.io; | |
| 373 | 375 | const zcu = self.base.comp.zcu.?; |
| 374 | 376 | const ip = &zcu.intern_pool; |
| 375 | 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 | 509 | }, self.getString(av_block.code)); |
| 508 | 510 | |
| 509 | 511 | const file = self.base.file.?; |
| 510 | file.setEndPos(f.file_size) catch |err| return diags.fail("failed to allocate file: {s}", .{@errorName(err)}); | |
| 511 | var fw = file.writer(&.{}); | |
| 512 | file.setLength(io, f.file_size) catch |err| return diags.fail("failed to allocate file: {t}", .{err}); | |
| 513 | var fw = file.writer(io, &.{}); | |
| 512 | 514 | var w = &fw.interface; |
| 513 | 515 | w.writeVecAll(f.all_buffers.items) catch |err| switch (err) { |
| 514 | 516 | error.WriteFailed => return diags.fail("failed to write to '{f}': {s}", .{ |
| ... | ... | @@ -763,6 +765,7 @@ pub fn flushEmitH(zcu: *Zcu) !void { |
| 763 | 765 | if (true) return; // emit-h is regressed |
| 764 | 766 | |
| 765 | 767 | const emit_h = zcu.emit_h orelse return; |
| 768 | const io = zcu.comp.io; | |
| 766 | 769 | |
| 767 | 770 | // We collect a list of buffers to write, and write them all at once with pwritev 😎 |
| 768 | 771 | const num_buffers = emit_h.decl_table.count() + 1; |
| ... | ... | @@ -790,14 +793,14 @@ pub fn flushEmitH(zcu: *Zcu) !void { |
| 790 | 793 | } |
| 791 | 794 | |
| 792 | 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 | 797 | // We set the end position explicitly below; by not truncating the file, we possibly |
| 795 | 798 | // make it easier on the file system by doing 1 reallocation instead of two. |
| 796 | 799 | .truncate = false, |
| 797 | 800 | }); |
| 798 | defer file.close(); | |
| 801 | defer file.close(io); | |
| 799 | 802 | |
| 800 | try file.setEndPos(file_size); | |
| 803 | try file.setLength(io, file_size); | |
| 801 | 804 | try file.pwritevAll(all_buffers.items, 0); |
| 802 | 805 | } |
| 803 | 806 |
src/link/Coff.zig+42-32| ... | ... | @@ -1,3 +1,23 @@ |
| 1 | const Coff = @This(); | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | const native_endian = builtin.cpu.arch.endian(); | |
| 5 | ||
| 6 | const std = @import("std"); | |
| 7 | const Io = std.Io; | |
| 8 | const assert = std.debug.assert; | |
| 9 | const log = std.log.scoped(.link); | |
| 10 | ||
| 11 | const codegen = @import("../codegen.zig"); | |
| 12 | const Compilation = @import("../Compilation.zig"); | |
| 13 | const InternPool = @import("../InternPool.zig"); | |
| 14 | const link = @import("../link.zig"); | |
| 15 | const MappedFile = @import("MappedFile.zig"); | |
| 16 | const target_util = @import("../target.zig"); | |
| 17 | const Type = @import("../Type.zig"); | |
| 18 | const Value = @import("../Value.zig"); | |
| 19 | const Zcu = @import("../Zcu.zig"); | |
| 20 | ||
| 1 | 21 | base: link.File, |
| 2 | 22 | mf: MappedFile, |
| 3 | 23 | nodes: std.MultiArrayList(Node), |
| ... | ... | @@ -631,12 +651,14 @@ fn create( |
| 631 | 651 | else => return error.UnsupportedCOFFArchitecture, |
| 632 | 652 | }; |
| 633 | 653 | |
| 654 | const io = comp.io; | |
| 655 | ||
| 634 | 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 | 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 | 662 | coff.* = .{ |
| 641 | 663 | .base = .{ |
| 642 | 664 | .tag = .coff2, |
| ... | ... | @@ -644,14 +666,14 @@ fn create( |
| 644 | 666 | .comp = comp, |
| 645 | 667 | .emit = path, |
| 646 | 668 | |
| 647 | .file = .adaptFromNewApi(file), | |
| 669 | .file = file, | |
| 648 | 670 | .gc_sections = false, |
| 649 | 671 | .print_gc_sections = false, |
| 650 | 672 | .build_id = .none, |
| 651 | 673 | .allow_shlib_undefined = false, |
| 652 | 674 | .stack_size = 0, |
| 653 | 675 | }, |
| 654 | .mf = try .init(file, comp.gpa), | |
| 676 | .mf = try .init(file, comp.gpa, io), | |
| 655 | 677 | .nodes = .empty, |
| 656 | 678 | .import_table = .{ |
| 657 | 679 | .ni = .none, |
| ... | ... | @@ -1727,22 +1749,20 @@ pub fn flush( |
| 1727 | 1749 | const comp = coff.base.comp; |
| 1728 | 1750 | if (comp.compiler_rt_dyn_lib) |crt_file| { |
| 1729 | 1751 | const gpa = comp.gpa; |
| 1752 | const io = comp.io; | |
| 1730 | 1753 | const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{ |
| 1731 | 1754 | std.fs.path.dirname(coff.base.emit.sub_path) orelse "", |
| 1732 | 1755 | std.fs.path.basename(crt_file.full_object_path.sub_path), |
| 1733 | 1756 | }); |
| 1734 | 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 | 1760 | crt_file.full_object_path.sub_path, |
| 1737 | 1761 | coff.base.emit.root_dir.handle, |
| 1738 | 1762 | compiler_rt_sub_path, |
| 1763 | io, | |
| 1739 | 1764 | .{}, |
| 1740 | ) catch |err| switch (err) { | |
| 1741 | else => |e| return comp.link_diags.fail("Copy '{s}' failed: {s}", .{ | |
| 1742 | compiler_rt_sub_path, | |
| 1743 | @errorName(e), | |
| 1744 | }), | |
| 1745 | }; | |
| 1765 | ) catch |err| return comp.link_diags.fail("copy '{s}' failed: {t}", .{ compiler_rt_sub_path, err }); | |
| 1746 | 1766 | } |
| 1747 | 1767 | } |
| 1748 | 1768 | |
| ... | ... | @@ -2358,10 +2378,16 @@ pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTe |
| 2358 | 2378 | _ = name; |
| 2359 | 2379 | } |
| 2360 | 2380 | |
| 2361 | pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) void { | |
| 2362 | const w, _ = std.debug.lockStderrWriter(&.{}); | |
| 2363 | defer std.debug.unlockStderrWriter(); | |
| 2364 | coff.printNode(tid, w, .root, 0) catch {}; | |
| 2381 | pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) Io.Cancelable!void { | |
| 2382 | const comp = coff.base.comp; | |
| 2383 | const io = comp.io; | |
| 2384 | var buffer: [512]u8 = undefined; | |
| 2385 | const stderr = try io.lockStderr(&buffer, null); | |
| 2386 | defer io.unlockStderr(); | |
| 2387 | const w = &stderr.file_writer.interface; | |
| 2388 | coff.printNode(tid, w, .root, 0) catch |err| switch (err) { | |
| 2389 | error.WriteFailed => return stderr.err.?, | |
| 2390 | }; | |
| 2365 | 2391 | } |
| 2366 | 2392 | |
| 2367 | 2393 | pub fn printNode( |
| ... | ... | @@ -2459,19 +2485,3 @@ pub fn printNode( |
| 2459 | 2485 | } |
| 2460 | 2486 | } |
| 2461 | 2487 | } |
| 2462 | ||
| 2463 | const assert = std.debug.assert; | |
| 2464 | const builtin = @import("builtin"); | |
| 2465 | const codegen = @import("../codegen.zig"); | |
| 2466 | const Compilation = @import("../Compilation.zig"); | |
| 2467 | const Coff = @This(); | |
| 2468 | const InternPool = @import("../InternPool.zig"); | |
| 2469 | const link = @import("../link.zig"); | |
| 2470 | const log = std.log.scoped(.link); | |
| 2471 | const MappedFile = @import("MappedFile.zig"); | |
| 2472 | const native_endian = builtin.cpu.arch.endian(); | |
| 2473 | const std = @import("std"); | |
| 2474 | const target_util = @import("../target.zig"); | |
| 2475 | const Type = @import("../Type.zig"); | |
| 2476 | const Value = @import("../Value.zig"); | |
| 2477 | const Zcu = @import("../Zcu.zig"); |
src/link/Dwarf.zig+62-45| ... | ... | @@ -1,3 +1,24 @@ |
| 1 | const Dwarf = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Allocator = std.mem.Allocator; | |
| 6 | const DW = std.dwarf; | |
| 7 | const Zir = std.zig.Zir; | |
| 8 | const assert = std.debug.assert; | |
| 9 | const log = std.log.scoped(.dwarf); | |
| 10 | const Writer = std.Io.Writer; | |
| 11 | ||
| 12 | const InternPool = @import("../InternPool.zig"); | |
| 13 | const Module = @import("../Package.zig").Module; | |
| 14 | const Type = @import("../Type.zig"); | |
| 15 | const Value = @import("../Value.zig"); | |
| 16 | const Zcu = @import("../Zcu.zig"); | |
| 17 | const codegen = @import("../codegen.zig"); | |
| 18 | const dev = @import("../dev.zig"); | |
| 19 | const link = @import("../link.zig"); | |
| 20 | const target_info = @import("../target.zig"); | |
| 21 | ||
| 1 | 22 | gpa: Allocator, |
| 2 | 23 | bin_file: *link.File, |
| 3 | 24 | format: DW.Format, |
| ... | ... | @@ -27,18 +48,18 @@ pub const UpdateError = error{ |
| 27 | 48 | EndOfStream, |
| 28 | 49 | Underflow, |
| 29 | 50 | UnexpectedEndOfFile, |
| 51 | NonResizable, | |
| 30 | 52 | } || |
| 31 | 53 | codegen.GenerateSymbolError || |
| 32 | std.fs.File.OpenError || | |
| 33 | std.fs.File.SetEndPosError || | |
| 34 | std.fs.File.CopyRangeError || | |
| 35 | std.fs.File.PReadError || | |
| 36 | std.fs.File.PWriteError; | |
| 54 | Io.File.OpenError || | |
| 55 | Io.File.LengthError || | |
| 56 | Io.File.ReadPositionalError || | |
| 57 | Io.File.WritePositionalError; | |
| 37 | 58 | |
| 38 | 59 | pub const FlushError = UpdateError; |
| 39 | 60 | |
| 40 | 61 | pub const RelocError = |
| 41 | std.fs.File.PWriteError; | |
| 62 | Io.File.PWriteError; | |
| 42 | 63 | |
| 43 | 64 | pub const AddressSize = enum(u8) { |
| 44 | 65 | @"32" = 4, |
| ... | ... | @@ -135,11 +156,14 @@ const DebugInfo = struct { |
| 135 | 156 | |
| 136 | 157 | fn declAbbrevCode(debug_info: *DebugInfo, unit: Unit.Index, entry: Entry.Index) !AbbrevCode { |
| 137 | 158 | const dwarf: *Dwarf = @fieldParentPtr("debug_info", debug_info); |
| 159 | const comp = dwarf.bin_file.comp; | |
| 160 | const io = comp.io; | |
| 138 | 161 | const unit_ptr = debug_info.section.getUnit(unit); |
| 139 | 162 | const entry_ptr = unit_ptr.getEntry(entry); |
| 140 | 163 | if (entry_ptr.len < AbbrevCode.decl_bytes) return .null; |
| 141 | 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 | 167 | &abbrev_code_buf, |
| 144 | 168 | debug_info.section.off(dwarf) + unit_ptr.off + unit_ptr.header_len + entry_ptr.off, |
| 145 | 169 | ) != abbrev_code_buf.len) return error.InputOutput; |
| ... | ... | @@ -619,13 +643,10 @@ const Unit = struct { |
| 619 | 643 | |
| 620 | 644 | fn move(unit: *Unit, sec: *Section, dwarf: *Dwarf, new_off: u32) UpdateError!void { |
| 621 | 645 | if (unit.off == new_off) return; |
| 622 | const n = try dwarf.getFile().?.copyRangeAll( | |
| 623 | sec.off(dwarf) + unit.off, | |
| 624 | dwarf.getFile().?, | |
| 625 | sec.off(dwarf) + new_off, | |
| 626 | unit.len, | |
| 627 | ); | |
| 628 | if (n != unit.len) return error.InputOutput; | |
| 646 | const comp = dwarf.bin_file.comp; | |
| 647 | const io = comp.io; | |
| 648 | const file = dwarf.getFile().?; | |
| 649 | try link.File.copyRangeAll2(io, file, file, sec.off(dwarf) + unit.off, sec.off(dwarf) + new_off, unit.len); | |
| 629 | 650 | unit.off = new_off; |
| 630 | 651 | } |
| 631 | 652 | |
| ... | ... | @@ -655,10 +676,14 @@ const Unit = struct { |
| 655 | 676 | |
| 656 | 677 | fn replaceHeader(unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void { |
| 657 | 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 | } |
| 660 | 683 | |
| 661 | 684 | fn writeTrailer(unit: *Unit, sec: *Section, dwarf: *Dwarf) UpdateError!void { |
| 685 | const comp = dwarf.bin_file.comp; | |
| 686 | const io = comp.io; | |
| 662 | 687 | const start = unit.off + unit.header_len + if (unit.last.unwrap()) |last_entry| end: { |
| 663 | 688 | const last_entry_ptr = unit.getEntry(last_entry); |
| 664 | 689 | break :end last_entry_ptr.off + last_entry_ptr.len; |
| ... | ... | @@ -688,7 +713,7 @@ const Unit = struct { |
| 688 | 713 | assert(fw.end == extended_op_bytes + op_len_bytes); |
| 689 | 714 | fw.writeByte(DW.LNE.padding) catch unreachable; |
| 690 | 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 | 718 | var trailer_aw: Writer.Allocating = try .initCapacity(dwarf.gpa, len); |
| 694 | 719 | defer trailer_aw.deinit(); |
| ... | ... | @@ -748,7 +773,7 @@ const Unit = struct { |
| 748 | 773 | assert(tw.end == unit.trailer_len); |
| 749 | 774 | tw.splatByteAll(fill_byte, len - unit.trailer_len) catch unreachable; |
| 750 | 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 | } |
| 753 | 778 | |
| 754 | 779 | fn resolveRelocs(unit: *Unit, sec: *Section, dwarf: *Dwarf) RelocError!void { |
| ... | ... | @@ -834,6 +859,8 @@ const Entry = struct { |
| 834 | 859 | dwarf: *Dwarf, |
| 835 | 860 | ) (UpdateError || Writer.Error)!void { |
| 836 | 861 | assert(entry.len > 0); |
| 862 | const comp = dwarf.bin_file.comp; | |
| 863 | const io = comp.io; | |
| 837 | 864 | const start = entry.off + entry.len; |
| 838 | 865 | if (sec == &dwarf.debug_frame.section) { |
| 839 | 866 | const len = if (entry.next.unwrap()) |next_entry| |
| ... | ... | @@ -843,11 +870,11 @@ const Entry = struct { |
| 843 | 870 | var unit_len_buf: [8]u8 = undefined; |
| 844 | 871 | const unit_len_bytes = unit_len_buf[0..dwarf.sectionOffsetBytes()]; |
| 845 | 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 | 874 | const buf = try dwarf.gpa.alloc(u8, len - entry.len); |
| 848 | 875 | defer dwarf.gpa.free(buf); |
| 849 | 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 | 878 | return; |
| 852 | 879 | } |
| 853 | 880 | const len = unit.getEntry(entry.next.unwrap() orelse return).off - start; |
| ... | ... | @@ -906,7 +933,7 @@ const Entry = struct { |
| 906 | 933 | }, |
| 907 | 934 | } else assert(!sec.pad_entries_to_ideal and len == 0); |
| 908 | 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 | } |
| 911 | 938 | |
| 912 | 939 | fn resize( |
| ... | ... | @@ -949,11 +976,13 @@ const Entry = struct { |
| 949 | 976 | |
| 950 | 977 | fn replace(entry_ptr: *Entry, unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void { |
| 951 | 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 | 982 | if (false) { |
| 954 | 983 | const buf = try dwarf.gpa.alloc(u8, sec.len); |
| 955 | 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 | 986 | log.info("Section{{ .first = {}, .last = {}, .off = 0x{x}, .len = 0x{x} }}", .{ |
| 958 | 987 | @intFromEnum(sec.first), |
| 959 | 988 | @intFromEnum(sec.last), |
| ... | ... | @@ -4682,6 +4711,8 @@ fn updateContainerTypeWriterError( |
| 4682 | 4711 | } |
| 4683 | 4712 | |
| 4684 | 4713 | pub 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 | 4716 | const ip = &zcu.intern_pool; |
| 4686 | 4717 | |
| 4687 | 4718 | const inst_info = zir_index.resolveFull(ip).?; |
| ... | ... | @@ -4701,7 +4732,7 @@ pub fn updateLineNumber(dwarf: *Dwarf, zcu: *Zcu, zir_index: InternPool.TrackedI |
| 4701 | 4732 | |
| 4702 | 4733 | const unit = dwarf.debug_info.section.getUnit(dwarf.getUnitIfExists(file.mod.?) orelse return); |
| 4703 | 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 | } |
| 4706 | 4737 | |
| 4707 | 4738 | pub 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 | 4769 | fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Error)!void { |
| 4739 | 4770 | const zcu = pt.zcu; |
| 4740 | 4771 | const ip = &zcu.intern_pool; |
| 4772 | const comp = dwarf.bin_file.comp; | |
| 4773 | const io = comp.io; | |
| 4741 | 4774 | |
| 4742 | 4775 | { |
| 4743 | 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 | 4990 | if (dwarf.debug_str.section.dirty) { |
| 4958 | 4991 | const contents = dwarf.debug_str.contents.items; |
| 4959 | 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 | 4994 | dwarf.debug_str.section.dirty = false; |
| 4962 | 4995 | } |
| 4963 | 4996 | if (dwarf.debug_line.section.dirty) { |
| ... | ... | @@ -5069,7 +5102,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro |
| 5069 | 5102 | if (dwarf.debug_line_str.section.dirty) { |
| 5070 | 5103 | const contents = dwarf.debug_line_str.contents.items; |
| 5071 | 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 | 5106 | dwarf.debug_line_str.section.dirty = false; |
| 5074 | 5107 | } |
| 5075 | 5108 | if (dwarf.debug_loclists.section.dirty) { |
| ... | ... | @@ -6350,7 +6383,7 @@ const AbbrevCode = enum { |
| 6350 | 6383 | }); |
| 6351 | 6384 | }; |
| 6352 | 6385 | |
| 6353 | fn getFile(dwarf: *Dwarf) ?std.fs.File { | |
| 6386 | fn getFile(dwarf: *Dwarf) ?Io.File { | |
| 6354 | 6387 | if (dwarf.bin_file.cast(.macho)) |macho_file| if (macho_file.d_sym) |*d_sym| return d_sym.file; |
| 6355 | 6388 | return dwarf.bin_file.file; |
| 6356 | 6389 | } |
| ... | ... | @@ -6391,9 +6424,11 @@ fn writeInt(dwarf: *Dwarf, buf: []u8, int: u64) void { |
| 6391 | 6424 | } |
| 6392 | 6425 | |
| 6393 | 6426 | fn resolveReloc(dwarf: *Dwarf, source: u64, target: u64, size: u32) RelocError!void { |
| 6427 | const comp = dwarf.bin_file.comp; | |
| 6428 | const io = comp.io; | |
| 6394 | 6429 | var buf: [8]u8 = undefined; |
| 6395 | 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 | } |
| 6398 | 6433 | |
| 6399 | 6434 | fn unitLengthBytes(dwarf: *Dwarf) u32 { |
| ... | ... | @@ -6429,21 +6464,3 @@ const force_incremental = false; |
| 6429 | 6464 | inline fn incremental(dwarf: Dwarf) bool { |
| 6430 | 6465 | return force_incremental or dwarf.bin_file.comp.config.incremental; |
| 6431 | 6466 | } |
| 6432 | ||
| 6433 | const Allocator = std.mem.Allocator; | |
| 6434 | const DW = std.dwarf; | |
| 6435 | const Dwarf = @This(); | |
| 6436 | const InternPool = @import("../InternPool.zig"); | |
| 6437 | const Module = @import("../Package.zig").Module; | |
| 6438 | const Type = @import("../Type.zig"); | |
| 6439 | const Value = @import("../Value.zig"); | |
| 6440 | const Zcu = @import("../Zcu.zig"); | |
| 6441 | const Zir = std.zig.Zir; | |
| 6442 | const assert = std.debug.assert; | |
| 6443 | const codegen = @import("../codegen.zig"); | |
| 6444 | const dev = @import("../dev.zig"); | |
| 6445 | const link = @import("../link.zig"); | |
| 6446 | const log = std.log.scoped(.dwarf); | |
| 6447 | const std = @import("std"); | |
| 6448 | const target_info = @import("../target.zig"); | |
| 6449 | const Writer = std.Io.Writer; |
src/link/Elf.zig+56-57| ... | ... | @@ -313,12 +313,14 @@ pub fn createEmpty( |
| 313 | 313 | const is_obj = output_mode == .Obj; |
| 314 | 314 | const is_obj_or_ar = is_obj or (output_mode == .Lib and link_mode == .static); |
| 315 | 315 | |
| 316 | const io = comp.io; | |
| 317 | ||
| 316 | 318 | // What path should this ELF linker code output to? |
| 317 | 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 | 321 | .truncate = true, |
| 320 | 322 | .read = true, |
| 321 | .mode = link.File.determineMode(output_mode, link_mode), | |
| 323 | .permissions = link.File.determinePermissions(output_mode, link_mode), | |
| 322 | 324 | }); |
| 323 | 325 | |
| 324 | 326 | const gpa = comp.gpa; |
| ... | ... | @@ -406,10 +408,12 @@ pub fn open( |
| 406 | 408 | } |
| 407 | 409 | |
| 408 | 410 | pub 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; | |
| 410 | 414 | |
| 411 | 415 | for (self.file_handles.items) |fh| { |
| 412 | fh.close(); | |
| 416 | fh.close(io); | |
| 413 | 417 | } |
| 414 | 418 | self.file_handles.deinit(gpa); |
| 415 | 419 | |
| ... | ... | @@ -483,6 +487,8 @@ pub fn getUavVAddr(self: *Elf, uav: InternPool.Index, reloc_info: link.File.Relo |
| 483 | 487 | |
| 484 | 488 | /// Returns end pos of collision, if any. |
| 485 | 489 | fn detectAllocCollision(self: *Elf, start: u64, size: u64) !?u64 { |
| 490 | const comp = self.base.comp; | |
| 491 | const io = comp.io; | |
| 486 | 492 | const small_ptr = self.ptr_width == .p32; |
| 487 | 493 | const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); |
| 488 | 494 | if (start < ehdr_size) |
| ... | ... | @@ -522,7 +528,7 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) !?u64 { |
| 522 | 528 | } |
| 523 | 529 | } |
| 524 | 530 | |
| 525 | if (at_end) try self.base.file.?.setEndPos(end); | |
| 531 | if (at_end) try self.base.file.?.setLength(io, end); | |
| 526 | 532 | return null; |
| 527 | 533 | } |
| 528 | 534 | |
| ... | ... | @@ -552,6 +558,8 @@ pub fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u64) !u64 { |
| 552 | 558 | } |
| 553 | 559 | |
| 554 | 560 | pub 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 | 563 | const shdr = &self.sections.items(.shdr)[shdr_index]; |
| 556 | 564 | |
| 557 | 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 | 582 | new_offset, |
| 575 | 583 | }); |
| 576 | 584 | |
| 577 | const amt = try self.base.file.?.copyRangeAll( | |
| 578 | shdr.sh_offset, | |
| 579 | self.base.file.?, | |
| 580 | new_offset, | |
| 581 | existing_size, | |
| 582 | ); | |
| 583 | // TODO figure out what to about this error condition - how to communicate it up. | |
| 584 | if (amt != existing_size) return error.InputOutput; | |
| 585 | try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size); | |
| 585 | 586 | |
| 586 | 587 | shdr.sh_offset = new_offset; |
| 587 | 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 | } |
| 591 | 592 | |
| ... | ... | @@ -737,8 +738,8 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { |
| 737 | 738 | .res => unreachable, |
| 738 | 739 | .dso_exact => @panic("TODO"), |
| 739 | 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 | .dso => |dso| try parseDso(gpa, diags, dso, &self.shared_objects, &self.files, target), | |
| 741 | .archive => |obj| try parseArchive(gpa, io, diags, &self.file_handles, &self.files, target, debug_fmt_strip, default_sym_version, &self.objects, obj, is_static_lib), | |
| 742 | .dso => |dso| try parseDso(gpa, io, diags, dso, &self.shared_objects, &self.files, target), | |
| 742 | 743 | } |
| 743 | 744 | } |
| 744 | 745 | |
| ... | ... | @@ -747,9 +748,10 @@ pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std |
| 747 | 748 | defer tracy.end(); |
| 748 | 749 | |
| 749 | 750 | const comp = self.base.comp; |
| 751 | const io = comp.io; | |
| 750 | 752 | const diags = &comp.link_diags; |
| 751 | 753 | |
| 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); | |
| 753 | 755 | |
| 754 | 756 | const sub_prog_node = prog_node.start("ELF Flush", 0); |
| 755 | 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 | 759 | return flushInner(self, arena, tid) catch |err| switch (err) { |
| 758 | 760 | error.OutOfMemory => return error.OutOfMemory, |
| 759 | 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 | } |
| 763 | 765 | |
| ... | ... | @@ -1047,9 +1049,11 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void { |
| 1047 | 1049 | } |
| 1048 | 1050 | |
| 1049 | 1051 | pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void { |
| 1050 | const diags = &self.base.comp.link_diags; | |
| 1051 | const obj = link.openObject(path, false, false) catch |err| { | |
| 1052 | switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) { | |
| 1052 | const comp = self.base.comp; | |
| 1053 | const io = comp.io; | |
| 1054 | const diags = &comp.link_diags; | |
| 1055 | const obj = link.openObject(io, path, false, false) catch |err| { | |
| 1056 | switch (diags.failParse(path, "failed to open object: {t}", .{err})) { | |
| 1053 | 1057 | error.LinkFailure => return, |
| 1054 | 1058 | } |
| 1055 | 1059 | }; |
| ... | ... | @@ -1057,10 +1061,11 @@ pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void { |
| 1057 | 1061 | } |
| 1058 | 1062 | |
| 1059 | 1063 | fn 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 | 1066 | self.parseObject(obj) catch |err| switch (err) { |
| 1062 | 1067 | 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 | } |
| 1066 | 1071 | |
| ... | ... | @@ -1068,10 +1073,12 @@ fn parseObject(self: *Elf, obj: link.Input.Object) !void { |
| 1068 | 1073 | const tracy = trace(@src()); |
| 1069 | 1074 | defer tracy.end(); |
| 1070 | 1075 | |
| 1071 | const gpa = self.base.comp.gpa; | |
| 1072 | const diags = &self.base.comp.link_diags; | |
| 1073 | const target = &self.base.comp.root_mod.resolved_target.result; | |
| 1074 | const debug_fmt_strip = self.base.comp.config.debug_format == .strip; | |
| 1076 | const comp = self.base.comp; | |
| 1077 | const io = comp.io; | |
| 1078 | const gpa = comp.gpa; | |
| 1079 | const diags = &comp.link_diags; | |
| 1080 | const target = &comp.root_mod.resolved_target.result; | |
| 1081 | const debug_fmt_strip = comp.config.debug_format == .strip; | |
| 1075 | 1082 | const default_sym_version = self.default_sym_version; |
| 1076 | 1083 | const file_handles = &self.file_handles; |
| 1077 | 1084 | |
| ... | ... | @@ -1090,14 +1097,15 @@ fn parseObject(self: *Elf, obj: link.Input.Object) !void { |
| 1090 | 1097 | try self.objects.append(gpa, index); |
| 1091 | 1098 | |
| 1092 | 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 | 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 | } |
| 1098 | 1105 | |
| 1099 | 1106 | fn parseArchive( |
| 1100 | 1107 | gpa: Allocator, |
| 1108 | io: Io, | |
| 1101 | 1109 | diags: *Diags, |
| 1102 | 1110 | file_handles: *std.ArrayList(File.Handle), |
| 1103 | 1111 | files: *std.MultiArrayList(File.Entry), |
| ... | ... | @@ -1112,7 +1120,7 @@ fn parseArchive( |
| 1112 | 1120 | defer tracy.end(); |
| 1113 | 1121 | |
| 1114 | 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 | 1124 | defer archive.deinit(gpa); |
| 1117 | 1125 | |
| 1118 | 1126 | const init_alive = if (is_static_lib) true else obj.must_link; |
| ... | ... | @@ -1123,15 +1131,16 @@ fn parseArchive( |
| 1123 | 1131 | const object = &files.items(.data)[index].object; |
| 1124 | 1132 | object.index = index; |
| 1125 | 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 | 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 | 1137 | try objects.append(gpa, index); |
| 1130 | 1138 | } |
| 1131 | 1139 | } |
| 1132 | 1140 | |
| 1133 | 1141 | fn parseDso( |
| 1134 | 1142 | gpa: Allocator, |
| 1143 | io: Io, | |
| 1135 | 1144 | diags: *Diags, |
| 1136 | 1145 | dso: link.Input.Dso, |
| 1137 | 1146 | shared_objects: *std.StringArrayHashMapUnmanaged(File.Index), |
| ... | ... | @@ -1143,8 +1152,8 @@ fn parseDso( |
| 1143 | 1152 | |
| 1144 | 1153 | const handle = dso.file; |
| 1145 | 1154 | |
| 1146 | const stat = Stat.fromFs(try handle.stat()); | |
| 1147 | var header = try SharedObject.parseHeader(gpa, diags, dso.path, handle, stat, target); | |
| 1155 | const stat = Stat.fromFs(try handle.stat(io)); | |
| 1156 | var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target); | |
| 1148 | 1157 | defer header.deinit(gpa); |
| 1149 | 1158 | |
| 1150 | 1159 | const soname = header.soname() orelse dso.path.basename(); |
| ... | ... | @@ -1158,7 +1167,7 @@ fn parseDso( |
| 1158 | 1167 | |
| 1159 | 1168 | gop.value_ptr.* = index; |
| 1160 | 1169 | |
| 1161 | var parsed = try SharedObject.parse(gpa, &header, handle); | |
| 1170 | var parsed = try SharedObject.parse(gpa, io, &header, handle); | |
| 1162 | 1171 | errdefer parsed.deinit(gpa); |
| 1163 | 1172 | |
| 1164 | 1173 | const duped_path: Path = .{ |
| ... | ... | @@ -2888,13 +2897,7 @@ pub fn allocateAllocSections(self: *Elf) !void { |
| 2888 | 2897 | if (shdr.sh_offset > 0) { |
| 2889 | 2898 | // Get size actually commited to the output file. |
| 2890 | 2899 | const existing_size = self.sectionSize(shndx); |
| 2891 | const amt = try self.base.file.?.copyRangeAll( | |
| 2892 | shdr.sh_offset, | |
| 2893 | self.base.file.?, | |
| 2894 | new_offset, | |
| 2895 | existing_size, | |
| 2896 | ); | |
| 2897 | if (amt != existing_size) return error.InputOutput; | |
| 2900 | try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size); | |
| 2898 | 2901 | } |
| 2899 | 2902 | |
| 2900 | 2903 | shdr.sh_offset = new_offset; |
| ... | ... | @@ -2930,13 +2933,7 @@ pub fn allocateNonAllocSections(self: *Elf) !void { |
| 2930 | 2933 | |
| 2931 | 2934 | if (shdr.sh_offset > 0) { |
| 2932 | 2935 | const existing_size = self.sectionSize(@intCast(shndx)); |
| 2933 | const amt = try self.base.file.?.copyRangeAll( | |
| 2934 | shdr.sh_offset, | |
| 2935 | self.base.file.?, | |
| 2936 | new_offset, | |
| 2937 | existing_size, | |
| 2938 | ); | |
| 2939 | if (amt != existing_size) return error.InputOutput; | |
| 2936 | try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size); | |
| 2940 | 2937 | } |
| 2941 | 2938 | |
| 2942 | 2939 | shdr.sh_offset = new_offset; |
| ... | ... | @@ -3649,7 +3646,7 @@ fn fileLookup(files: std.MultiArrayList(File.Entry), index: File.Index, zig_obje |
| 3649 | 3646 | pub fn addFileHandle( |
| 3650 | 3647 | gpa: Allocator, |
| 3651 | 3648 | file_handles: *std.ArrayList(File.Handle), |
| 3652 | handle: fs.File, | |
| 3649 | handle: Io.File, | |
| 3653 | 3650 | ) Allocator.Error!File.HandleIndex { |
| 3654 | 3651 | try file_handles.append(gpa, handle); |
| 3655 | 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 | 4063 | } |
| 4067 | 4064 | |
| 4068 | 4065 | /// Caller owns the memory. |
| 4069 | pub fn preadAllAlloc(allocator: Allocator, handle: fs.File, offset: u64, size: u64) ![]u8 { | |
| 4066 | pub fn preadAllAlloc(allocator: Allocator, io: Io, io_file: Io.File, offset: u64, size: u64) ![]u8 { | |
| 4070 | 4067 | const buffer = try allocator.alloc(u8, math.cast(usize, size) orelse return error.Overflow); |
| 4071 | 4068 | errdefer allocator.free(buffer); |
| 4072 | const amt = try handle.preadAll(buffer, offset); | |
| 4069 | const amt = try io_file.readPositionalAll(io, buffer, offset); | |
| 4073 | 4070 | if (amt != size) return error.InputOutput; |
| 4074 | 4071 | return buffer; |
| 4075 | 4072 | } |
| ... | ... | @@ -4435,16 +4432,17 @@ pub fn stringTableLookup(strtab: []const u8, off: u32) [:0]const u8 { |
| 4435 | 4432 | |
| 4436 | 4433 | pub fn pwriteAll(elf_file: *Elf, bytes: []const u8, offset: u64) error{LinkFailure}!void { |
| 4437 | 4434 | const comp = elf_file.base.comp; |
| 4435 | const io = comp.io; | |
| 4438 | 4436 | const diags = &comp.link_diags; |
| 4439 | elf_file.base.file.?.pwriteAll(bytes, offset) catch |err| { | |
| 4440 | return diags.fail("failed to write: {s}", .{@errorName(err)}); | |
| 4441 | }; | |
| 4437 | elf_file.base.file.?.writePositionalAll(io, bytes, offset) catch |err| | |
| 4438 | return diags.fail("failed to write: {t}", .{err}); | |
| 4442 | 4439 | } |
| 4443 | 4440 | |
| 4444 | pub fn setEndPos(elf_file: *Elf, length: u64) error{LinkFailure}!void { | |
| 4441 | pub fn setLength(elf_file: *Elf, length: u64) error{LinkFailure}!void { | |
| 4445 | 4442 | const comp = elf_file.base.comp; |
| 4443 | const io = comp.i; | |
| 4446 | 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 | 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 | 4456 | } |
| 4459 | 4457 | |
| 4460 | 4458 | const std = @import("std"); |
| 4459 | const Io = std.Io; | |
| 4461 | 4460 | const build_options = @import("build_options"); |
| 4462 | 4461 | const builtin = @import("builtin"); |
| 4463 | 4462 | const assert = std.debug.assert; |
src/link/Elf/Archive.zig+28-25| ... | ... | @@ -1,3 +1,21 @@ |
| 1 | const Archive = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const assert = std.debug.assert; | |
| 6 | const elf = std.elf; | |
| 7 | const fs = std.fs; | |
| 8 | const log = std.log.scoped(.link); | |
| 9 | const mem = std.mem; | |
| 10 | const Path = std.Build.Cache.Path; | |
| 11 | const Allocator = std.mem.Allocator; | |
| 12 | ||
| 13 | const Diags = @import("../../link.zig").Diags; | |
| 14 | const Elf = @import("../Elf.zig"); | |
| 15 | const File = @import("file.zig").File; | |
| 16 | const Object = @import("Object.zig"); | |
| 17 | const StringTable = @import("../StringTable.zig"); | |
| 18 | ||
| 1 | 19 | objects: []const Object, |
| 2 | 20 | /// '\n'-delimited |
| 3 | 21 | strtab: []const u8, |
| ... | ... | @@ -10,22 +28,23 @@ pub fn deinit(a: *Archive, gpa: Allocator) void { |
| 10 | 28 | |
| 11 | 29 | pub fn parse( |
| 12 | 30 | gpa: Allocator, |
| 31 | io: Io, | |
| 13 | 32 | diags: *Diags, |
| 14 | 33 | file_handles: *const std.ArrayList(File.Handle), |
| 15 | 34 | path: Path, |
| 16 | 35 | handle_index: File.HandleIndex, |
| 17 | 36 | ) !Archive { |
| 18 | const handle = file_handles.items[handle_index]; | |
| 37 | const file = file_handles.items[handle_index]; | |
| 19 | 38 | var pos: usize = 0; |
| 20 | 39 | { |
| 21 | 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 | 42 | if (n != magic_buffer.len) return error.BadMagic; |
| 24 | 43 | if (!mem.eql(u8, &magic_buffer, elf.ARMAG)) return error.BadMagic; |
| 25 | 44 | pos += magic_buffer.len; |
| 26 | 45 | } |
| 27 | 46 | |
| 28 | const size = (try handle.stat()).size; | |
| 47 | const size = (try file.stat(io)).size; | |
| 29 | 48 | |
| 30 | 49 | var objects: std.ArrayList(Object) = .empty; |
| 31 | 50 | defer objects.deinit(gpa); |
| ... | ... | @@ -36,7 +55,7 @@ pub fn parse( |
| 36 | 55 | while (pos < size) { |
| 37 | 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 | 59 | if (n != @sizeOf(elf.ar_hdr)) return error.UnexpectedEndOfFile; |
| 41 | 60 | } |
| 42 | 61 | pos += @sizeOf(elf.ar_hdr); |
| ... | ... | @@ -53,7 +72,7 @@ pub fn parse( |
| 53 | 72 | if (hdr.isSymtab() or hdr.isSymtab64()) continue; |
| 54 | 73 | if (hdr.isStrtab()) { |
| 55 | 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 | 76 | if (amt != obj_size) return error.InputOutput; |
| 58 | 77 | continue; |
| 59 | 78 | } |
| ... | ... | @@ -120,7 +139,7 @@ pub fn setArHdr(opts: struct { |
| 120 | 139 | @memset(mem.asBytes(&hdr), 0x20); |
| 121 | 140 | |
| 122 | 141 | { |
| 123 | var writer: std.Io.Writer = .fixed(&hdr.ar_name); | |
| 142 | var writer: Io.Writer = .fixed(&hdr.ar_name); | |
| 124 | 143 | switch (opts.name) { |
| 125 | 144 | .symtab => writer.print("{s}", .{elf.SYM64NAME}) catch unreachable, |
| 126 | 145 | .strtab => writer.print("//", .{}) catch unreachable, |
| ... | ... | @@ -133,7 +152,7 @@ pub fn setArHdr(opts: struct { |
| 133 | 152 | hdr.ar_gid[0] = '0'; |
| 134 | 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 | 156 | writer.print("{d}", .{opts.size}) catch unreachable; |
| 138 | 157 | } |
| 139 | 158 | hdr.ar_fmag = elf.ARFMAG.*; |
| ... | ... | @@ -206,7 +225,7 @@ pub const ArSymtab = struct { |
| 206 | 225 | ar: ArSymtab, |
| 207 | 226 | elf_file: *Elf, |
| 208 | 227 | |
| 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 | 229 | const ar = f.ar; |
| 211 | 230 | const elf_file = f.elf_file; |
| 212 | 231 | for (ar.symtab.items, 0..) |entry, i| { |
| ... | ... | @@ -261,7 +280,7 @@ pub const ArStrtab = struct { |
| 261 | 280 | try writer.writeAll(ar.buffer.items); |
| 262 | 281 | } |
| 263 | 282 | |
| 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 | 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 | 296 | /// Total size of the contributing object (excludes ar_hdr). |
| 278 | 297 | size: u64 = 0, |
| 279 | 298 | }; |
| 280 | ||
| 281 | const std = @import("std"); | |
| 282 | const assert = std.debug.assert; | |
| 283 | const elf = std.elf; | |
| 284 | const fs = std.fs; | |
| 285 | const log = std.log.scoped(.link); | |
| 286 | const mem = std.mem; | |
| 287 | const Path = std.Build.Cache.Path; | |
| 288 | const Allocator = std.mem.Allocator; | |
| 289 | ||
| 290 | const Diags = @import("../../link.zig").Diags; | |
| 291 | const Archive = @This(); | |
| 292 | const Elf = @import("../Elf.zig"); | |
| 293 | const File = @import("file.zig").File; | |
| 294 | const Object = @import("Object.zig"); | |
| 295 | const 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 | 90 | } |
| 91 | 91 | |
| 92 | 92 | pub 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 | 96 | const osec = elf_file.sections.items(.shdr)[list.output_section_index]; |
| 95 | 97 | assert(osec.sh_type != elf.SHT_NOBITS); |
| 96 | 98 | assert(!list.dirty); |
| ... | ... | @@ -121,12 +123,14 @@ pub fn write(list: AtomList, buffer: *std.Io.Writer.Allocating, undefs: anytype, |
| 121 | 123 | try atom_ptr.resolveRelocsAlloc(elf_file, out_code); |
| 122 | 124 | } |
| 123 | 125 | |
| 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 | 127 | buffer.clearRetainingCapacity(); |
| 126 | 128 | } |
| 127 | 129 | |
| 128 | 130 | pub 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 | 134 | const osec = elf_file.sections.items(.shdr)[list.output_section_index]; |
| 131 | 135 | assert(osec.sh_type != elf.SHT_NOBITS); |
| 132 | 136 | |
| ... | ... | @@ -152,7 +156,7 @@ pub fn writeRelocatable(list: AtomList, buffer: *std.array_list.Managed(u8), elf |
| 152 | 156 | @memcpy(out_code, code); |
| 153 | 157 | } |
| 154 | 158 | |
| 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 | 160 | buffer.clearRetainingCapacity(); |
| 157 | 161 | } |
| 158 | 162 |
src/link/Elf/Object.zig+61-51| ... | ... | @@ -1,3 +1,30 @@ |
| 1 | const Object = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const assert = std.debug.assert; | |
| 6 | const eh_frame = @import("eh_frame.zig"); | |
| 7 | const elf = std.elf; | |
| 8 | const fs = std.fs; | |
| 9 | const log = std.log.scoped(.link); | |
| 10 | const math = std.math; | |
| 11 | const mem = std.mem; | |
| 12 | const Path = std.Build.Cache.Path; | |
| 13 | const Allocator = std.mem.Allocator; | |
| 14 | ||
| 15 | const Diags = @import("../../link.zig").Diags; | |
| 16 | const Archive = @import("Archive.zig"); | |
| 17 | const Atom = @import("Atom.zig"); | |
| 18 | const AtomList = @import("AtomList.zig"); | |
| 19 | const Cie = eh_frame.Cie; | |
| 20 | const Elf = @import("../Elf.zig"); | |
| 21 | const Fde = eh_frame.Fde; | |
| 22 | const File = @import("file.zig").File; | |
| 23 | const Merge = @import("Merge.zig"); | |
| 24 | const Symbol = @import("Symbol.zig"); | |
| 25 | const Alignment = Atom.Alignment; | |
| 26 | const riscv = @import("../riscv.zig"); | |
| 27 | ||
| 1 | 28 | archive: ?InArchive = null, |
| 2 | 29 | /// Archive files cannot contain subdirectories, so only the basename is needed |
| 3 | 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 | 92 | pub fn parse( |
| 66 | 93 | self: *Object, |
| 67 | 94 | gpa: Allocator, |
| 95 | io: Io, | |
| 68 | 96 | diags: *Diags, |
| 69 | 97 | /// For error reporting purposes only. |
| 70 | 98 | path: Path, |
| 71 | handle: fs.File, | |
| 99 | handle: Io.File, | |
| 72 | 100 | target: *const std.Target, |
| 73 | 101 | debug_fmt_strip: bool, |
| 74 | 102 | default_sym_version: elf.Versym, |
| ... | ... | @@ -78,7 +106,7 @@ pub fn parse( |
| 78 | 106 | // Allocate atom index 0 to null atom |
| 79 | 107 | try self.atoms.append(gpa, .{ .extra_index = try self.addAtomExtra(gpa, .{}) }); |
| 80 | 108 | |
| 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 | 110 | try self.initSymbols(gpa, default_sym_version); |
| 83 | 111 | |
| 84 | 112 | for (self.shdrs.items, 0..) |shdr, i| { |
| ... | ... | @@ -87,7 +115,7 @@ pub fn parse( |
| 87 | 115 | if ((target.cpu.arch == .x86_64 and shdr.sh_type == elf.SHT_X86_64_UNWIND) or |
| 88 | 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 | 123 | pub fn parseCommon( |
| 96 | 124 | self: *Object, |
| 97 | 125 | gpa: Allocator, |
| 126 | io: Io, | |
| 98 | 127 | diags: *Diags, |
| 99 | 128 | path: Path, |
| 100 | handle: fs.File, | |
| 129 | handle: Io.File, | |
| 101 | 130 | target: *const std.Target, |
| 102 | 131 | ) !void { |
| 103 | 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; | |
| 105 | 134 | |
| 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 | 136 | defer gpa.free(header_buffer); |
| 108 | 137 | self.header = @as(*align(1) const elf.Elf64_Ehdr, @ptrCast(header_buffer)).*; |
| 109 | 138 | if (!mem.eql(u8, self.header.?.e_ident[0..4], elf.MAGIC)) { |
| ... | ... | @@ -127,7 +156,7 @@ pub fn parseCommon( |
| 127 | 156 | return diags.failParse(path, "corrupt header: section header table extends past the end of file", .{}); |
| 128 | 157 | } |
| 129 | 158 | |
| 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 | 160 | defer gpa.free(shdrs_buffer); |
| 132 | 161 | const shdrs = @as([*]align(1) const elf.Elf64_Shdr, @ptrCast(shdrs_buffer.ptr))[0..shnum]; |
| 133 | 162 | try self.shdrs.appendUnalignedSlice(gpa, shdrs); |
| ... | ... | @@ -140,7 +169,7 @@ pub fn parseCommon( |
| 140 | 169 | } |
| 141 | 170 | } |
| 142 | 171 | |
| 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 | 173 | defer gpa.free(shstrtab); |
| 145 | 174 | for (self.shdrs.items) |shdr| { |
| 146 | 175 | if (shdr.sh_name >= shstrtab.len) { |
| ... | ... | @@ -158,7 +187,7 @@ pub fn parseCommon( |
| 158 | 187 | const shdr = self.shdrs.items[index]; |
| 159 | 188 | self.first_global = shdr.sh_info; |
| 160 | 189 | |
| 161 | const raw_symtab = try self.preadShdrContentsAlloc(gpa, handle, index); | |
| 190 | const raw_symtab = try self.preadShdrContentsAlloc(gpa, io, handle, index); | |
| 162 | 191 | defer gpa.free(raw_symtab); |
| 163 | 192 | const nsyms = math.divExact(usize, raw_symtab.len, @sizeOf(elf.Elf64_Sym)) catch { |
| 164 | 193 | return diags.failParse(path, "symbol table not evenly divisible", .{}); |
| ... | ... | @@ -166,7 +195,7 @@ pub fn parseCommon( |
| 166 | 195 | const symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(raw_symtab.ptr))[0..nsyms]; |
| 167 | 196 | |
| 168 | 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 | 199 | defer gpa.free(strtab); |
| 171 | 200 | try self.strtab.appendSlice(gpa, strtab); |
| 172 | 201 | |
| ... | ... | @@ -262,9 +291,10 @@ pub fn validateEFlags( |
| 262 | 291 | fn initAtoms( |
| 263 | 292 | self: *Object, |
| 264 | 293 | gpa: Allocator, |
| 294 | io: Io, | |
| 265 | 295 | diags: *Diags, |
| 266 | 296 | path: Path, |
| 267 | handle: fs.File, | |
| 297 | handle: Io.File, | |
| 268 | 298 | debug_fmt_strip: bool, |
| 269 | 299 | target: *const std.Target, |
| 270 | 300 | ) !void { |
| ... | ... | @@ -297,7 +327,7 @@ fn initAtoms( |
| 297 | 327 | }; |
| 298 | 328 | |
| 299 | 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 | 331 | defer gpa.free(group_raw_data); |
| 302 | 332 | const group_nmembers = math.divExact(usize, group_raw_data.len, @sizeOf(u32)) catch { |
| 303 | 333 | return diags.failParse(path, "corrupt section group: not evenly divisible ", .{}); |
| ... | ... | @@ -338,7 +368,7 @@ fn initAtoms( |
| 338 | 368 | const shndx: u32 = @intCast(i); |
| 339 | 369 | if (self.skipShdr(shndx, debug_fmt_strip)) continue; |
| 340 | 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 | 372 | defer gpa.free(data); |
| 343 | 373 | const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*; |
| 344 | 374 | break :blk .{ chdr.ch_size, Alignment.fromNonzeroByteUnits(chdr.ch_addralign) }; |
| ... | ... | @@ -359,7 +389,7 @@ fn initAtoms( |
| 359 | 389 | elf.SHT_REL, elf.SHT_RELA => { |
| 360 | 390 | const atom_index = self.atoms_indexes.items[shdr.sh_info]; |
| 361 | 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 | 393 | defer gpa.free(relocs); |
| 364 | 394 | atom_ptr.relocs_section_index = @intCast(i); |
| 365 | 395 | const rel_index: u32 = @intCast(self.relocs.items.len); |
| ... | ... | @@ -421,7 +451,8 @@ fn initSymbols( |
| 421 | 451 | fn parseEhFrame( |
| 422 | 452 | self: *Object, |
| 423 | 453 | gpa: Allocator, |
| 424 | handle: fs.File, | |
| 454 | io: Io, | |
| 455 | handle: Io.File, | |
| 425 | 456 | shndx: u32, |
| 426 | 457 | target: *const std.Target, |
| 427 | 458 | ) !void { |
| ... | ... | @@ -430,12 +461,12 @@ fn parseEhFrame( |
| 430 | 461 | else => {}, |
| 431 | 462 | } else null; |
| 432 | 463 | |
| 433 | const raw = try self.preadShdrContentsAlloc(gpa, handle, shndx); | |
| 464 | const raw = try self.preadShdrContentsAlloc(gpa, io, handle, shndx); | |
| 434 | 465 | defer gpa.free(raw); |
| 435 | 466 | const data_start: u32 = @intCast(self.eh_frame_data.items.len); |
| 436 | 467 | try self.eh_frame_data.appendSlice(gpa, raw); |
| 437 | 468 | const relocs = if (relocs_shndx) |index| |
| 438 | try self.preadRelocsAlloc(gpa, handle, index) | |
| 469 | try self.preadRelocsAlloc(gpa, io, handle, index) | |
| 439 | 470 | else |
| 440 | 471 | &[0]elf.Elf64_Rela{}; |
| 441 | 472 | defer gpa.free(relocs); |
| ... | ... | @@ -1095,13 +1126,18 @@ pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, elf_file: *Elf |
| 1095 | 1126 | } |
| 1096 | 1127 | |
| 1097 | 1128 | pub fn updateArSize(self: *Object, elf_file: *Elf) !void { |
| 1129 | const comp = elf_file.base.comp; | |
| 1130 | const io = comp.io; | |
| 1098 | 1131 | self.output_ar_state.size = if (self.archive) |ar| ar.size else size: { |
| 1099 | 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 | } |
| 1103 | 1136 | |
| 1104 | 1137 | pub 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 | 1141 | const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow; |
| 1106 | 1142 | const offset: u64 = if (self.archive) |ar| ar.offset else 0; |
| 1107 | 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 | 1150 | }); |
| 1115 | 1151 | try writer.writeAll(mem.asBytes(&hdr)); |
| 1116 | 1152 | const handle = elf_file.fileHandle(self.file_handle); |
| 1117 | const gpa = elf_file.base.comp.gpa; | |
| 1118 | 1153 | const data = try gpa.alloc(u8, size); |
| 1119 | 1154 | defer gpa.free(data); |
| 1120 | const amt = try handle.preadAll(data, offset); | |
| 1155 | const amt = try handle.readPositionalAll(io, data, offset); | |
| 1121 | 1156 | if (amt != size) return error.InputOutput; |
| 1122 | 1157 | try writer.writeAll(data); |
| 1123 | 1158 | } |
| ... | ... | @@ -1190,11 +1225,12 @@ pub fn writeSymtab(self: *Object, elf_file: *Elf) void { |
| 1190 | 1225 | /// Caller owns the memory. |
| 1191 | 1226 | pub fn codeDecompressAlloc(self: *Object, elf_file: *Elf, atom_index: Atom.Index) ![]u8 { |
| 1192 | 1227 | const comp = elf_file.base.comp; |
| 1228 | const io = comp.io; | |
| 1193 | 1229 | const gpa = comp.gpa; |
| 1194 | 1230 | const atom_ptr = self.atom(atom_index).?; |
| 1195 | 1231 | const shdr = atom_ptr.inputShdr(elf_file); |
| 1196 | 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 | 1234 | defer if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) gpa.free(data); |
| 1199 | 1235 | |
| 1200 | 1236 | if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) { |
| ... | ... | @@ -1310,18 +1346,18 @@ fn addString(self: *Object, gpa: Allocator, str: []const u8) !u32 { |
| 1310 | 1346 | } |
| 1311 | 1347 | |
| 1312 | 1348 | /// Caller owns the memory. |
| 1313 | fn preadShdrContentsAlloc(self: Object, gpa: Allocator, handle: fs.File, index: u32) ![]u8 { | |
| 1349 | fn preadShdrContentsAlloc(self: Object, gpa: Allocator, io: Io, handle: Io.File, index: u32) ![]u8 { | |
| 1314 | 1350 | assert(index < self.shdrs.items.len); |
| 1315 | 1351 | const offset = if (self.archive) |ar| ar.offset else 0; |
| 1316 | 1352 | const shdr = self.shdrs.items[index]; |
| 1317 | 1353 | const sh_offset = math.cast(u64, shdr.sh_offset) orelse return error.Overflow; |
| 1318 | 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 | } |
| 1321 | 1357 | |
| 1322 | 1358 | /// Caller owns the memory. |
| 1323 | fn preadRelocsAlloc(self: Object, gpa: Allocator, handle: fs.File, shndx: u32) ![]align(1) const elf.Elf64_Rela { | |
| 1324 | const raw = try self.preadShdrContentsAlloc(gpa, handle, shndx); | |
| 1359 | fn preadRelocsAlloc(self: Object, gpa: Allocator, io: Io, handle: Io.File, shndx: u32) ![]align(1) const elf.Elf64_Rela { | |
| 1360 | const raw = try self.preadShdrContentsAlloc(gpa, io, handle, shndx); | |
| 1325 | 1361 | const num = @divExact(raw.len, @sizeOf(elf.Elf64_Rela)); |
| 1326 | 1362 | return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num]; |
| 1327 | 1363 | } |
| ... | ... | @@ -1552,29 +1588,3 @@ const InArchive = struct { |
| 1552 | 1588 | offset: u64, |
| 1553 | 1589 | size: u32, |
| 1554 | 1590 | }; |
| 1555 | ||
| 1556 | const Object = @This(); | |
| 1557 | ||
| 1558 | const std = @import("std"); | |
| 1559 | const assert = std.debug.assert; | |
| 1560 | const eh_frame = @import("eh_frame.zig"); | |
| 1561 | const elf = std.elf; | |
| 1562 | const fs = std.fs; | |
| 1563 | const log = std.log.scoped(.link); | |
| 1564 | const math = std.math; | |
| 1565 | const mem = std.mem; | |
| 1566 | const Path = std.Build.Cache.Path; | |
| 1567 | const Allocator = std.mem.Allocator; | |
| 1568 | ||
| 1569 | const Diags = @import("../../link.zig").Diags; | |
| 1570 | const Archive = @import("Archive.zig"); | |
| 1571 | const Atom = @import("Atom.zig"); | |
| 1572 | const AtomList = @import("AtomList.zig"); | |
| 1573 | const Cie = eh_frame.Cie; | |
| 1574 | const Elf = @import("../Elf.zig"); | |
| 1575 | const Fde = eh_frame.Fde; | |
| 1576 | const File = @import("file.zig").File; | |
| 1577 | const Merge = @import("Merge.zig"); | |
| 1578 | const Symbol = @import("Symbol.zig"); | |
| 1579 | const Alignment = Atom.Alignment; | |
| 1580 | const riscv = @import("../riscv.zig"); |
src/link/Elf/SharedObject.zig+28-25| ... | ... | @@ -1,3 +1,20 @@ |
| 1 | const SharedObject = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const assert = std.debug.assert; | |
| 6 | const elf = std.elf; | |
| 7 | const log = std.log.scoped(.elf); | |
| 8 | const mem = std.mem; | |
| 9 | const Path = std.Build.Cache.Path; | |
| 10 | const Stat = std.Build.Cache.File.Stat; | |
| 11 | const Allocator = mem.Allocator; | |
| 12 | ||
| 13 | const Elf = @import("../Elf.zig"); | |
| 14 | const File = @import("file.zig").File; | |
| 15 | const Symbol = @import("Symbol.zig"); | |
| 16 | const Diags = @import("../../link.zig").Diags; | |
| 17 | ||
| 1 | 18 | path: Path, |
| 2 | 19 | index: File.Index, |
| 3 | 20 | |
| ... | ... | @@ -92,16 +109,17 @@ pub const Parsed = struct { |
| 92 | 109 | |
| 93 | 110 | pub fn parseHeader( |
| 94 | 111 | gpa: Allocator, |
| 112 | io: Io, | |
| 95 | 113 | diags: *Diags, |
| 96 | 114 | file_path: Path, |
| 97 | fs_file: std.fs.File, | |
| 115 | file: Io.File, | |
| 98 | 116 | stat: Stat, |
| 99 | 117 | target: *const std.Target, |
| 100 | 118 | ) !Header { |
| 101 | 119 | var ehdr: elf.Elf64_Ehdr = undefined; |
| 102 | 120 | { |
| 103 | 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 | 123 | if (amt != buf.len) return error.UnexpectedEndOfFile; |
| 106 | 124 | } |
| 107 | 125 | if (!mem.eql(u8, ehdr.e_ident[0..4], "\x7fELF")) return error.BadMagic; |
| ... | ... | @@ -118,7 +136,7 @@ pub fn parseHeader( |
| 118 | 136 | errdefer gpa.free(sections); |
| 119 | 137 | { |
| 120 | 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 | 140 | if (amt != buf.len) return error.UnexpectedEndOfFile; |
| 123 | 141 | } |
| 124 | 142 | |
| ... | ... | @@ -143,7 +161,7 @@ pub fn parseHeader( |
| 143 | 161 | const dynamic_table = try gpa.alloc(elf.Elf64_Dyn, n); |
| 144 | 162 | errdefer gpa.free(dynamic_table); |
| 145 | 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 | 165 | if (amt != buf.len) return error.UnexpectedEndOfFile; |
| 148 | 166 | break :dt dynamic_table; |
| 149 | 167 | } else &.{}; |
| ... | ... | @@ -158,7 +176,7 @@ pub fn parseHeader( |
| 158 | 176 | const strtab_shdr = sections[dynsym_shdr.sh_link]; |
| 159 | 177 | const n = std.math.cast(usize, strtab_shdr.sh_size) orelse return error.Overflow; |
| 160 | 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 | 180 | if (amt != buf.len) return error.UnexpectedEndOfFile; |
| 163 | 181 | } |
| 164 | 182 | |
| ... | ... | @@ -190,9 +208,10 @@ pub fn parseHeader( |
| 190 | 208 | |
| 191 | 209 | pub fn parse( |
| 192 | 210 | gpa: Allocator, |
| 211 | io: Io, | |
| 193 | 212 | /// Moves resources from header. Caller may unconditionally deinit. |
| 194 | 213 | header: *Header, |
| 195 | fs_file: std.fs.File, | |
| 214 | file: Io.File, | |
| 196 | 215 | ) !Parsed { |
| 197 | 216 | const symtab = if (header.dynsym_sect_index) |index| st: { |
| 198 | 217 | const shdr = header.sections[index]; |
| ... | ... | @@ -200,7 +219,7 @@ pub fn parse( |
| 200 | 219 | const symtab = try gpa.alloc(elf.Elf64_Sym, n); |
| 201 | 220 | errdefer gpa.free(symtab); |
| 202 | 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 | 223 | if (amt != buf.len) return error.UnexpectedEndOfFile; |
| 205 | 224 | break :st symtab; |
| 206 | 225 | } else &.{}; |
| ... | ... | @@ -211,7 +230,7 @@ pub fn parse( |
| 211 | 230 | |
| 212 | 231 | if (header.verdef_sect_index) |shndx| { |
| 213 | 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 | 234 | defer gpa.free(verdefs); |
| 216 | 235 | |
| 217 | 236 | var offset: u32 = 0; |
| ... | ... | @@ -237,7 +256,7 @@ pub fn parse( |
| 237 | 256 | const versyms = try gpa.alloc(elf.Versym, symtab.len); |
| 238 | 257 | errdefer gpa.free(versyms); |
| 239 | 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 | 260 | if (amt != buf.len) return error.UnexpectedEndOfFile; |
| 242 | 261 | break :vs versyms; |
| 243 | 262 | } else &.{}; |
| ... | ... | @@ -534,19 +553,3 @@ const Format = struct { |
| 534 | 553 | } |
| 535 | 554 | } |
| 536 | 555 | }; |
| 537 | ||
| 538 | const SharedObject = @This(); | |
| 539 | ||
| 540 | const std = @import("std"); | |
| 541 | const assert = std.debug.assert; | |
| 542 | const elf = std.elf; | |
| 543 | const log = std.log.scoped(.elf); | |
| 544 | const mem = std.mem; | |
| 545 | const Path = std.Build.Cache.Path; | |
| 546 | const Stat = std.Build.Cache.File.Stat; | |
| 547 | const Allocator = mem.Allocator; | |
| 548 | ||
| 549 | const Elf = @import("../Elf.zig"); | |
| 550 | const File = @import("file.zig").File; | |
| 551 | const Symbol = @import("Symbol.zig"); | |
| 552 | const 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 | 740 | /// We need this so that we can write to an archive. |
| 741 | 741 | /// TODO implement writing ZigObject data directly to a buffer instead. |
| 742 | 742 | pub 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 | 746 | const shsize: u64 = switch (elf_file.ptr_width) { |
| 745 | 747 | .p32 => @sizeOf(elf.Elf32_Shdr), |
| 746 | 748 | .p64 => @sizeOf(elf.Elf64_Shdr), |
| ... | ... | @@ -753,7 +755,7 @@ pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void { |
| 753 | 755 | const size = std.math.cast(usize, end_pos) orelse return error.Overflow; |
| 754 | 756 | try self.data.resize(gpa, size); |
| 755 | 757 | |
| 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 | 759 | if (amt != size) return error.InputOutput; |
| 758 | 760 | } |
| 759 | 761 | |
| ... | ... | @@ -901,13 +903,15 @@ pub fn writeSymtab(self: ZigObject, elf_file: *Elf) void { |
| 901 | 903 | /// Returns atom's code. |
| 902 | 904 | /// Caller owns the memory. |
| 903 | 905 | pub 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 | 909 | const atom_ptr = self.atom(atom_index).?; |
| 906 | 910 | const file_offset = atom_ptr.offset(elf_file); |
| 907 | 911 | const size = std.math.cast(usize, atom_ptr.size) orelse return error.Overflow; |
| 908 | 912 | const code = try gpa.alloc(u8, size); |
| 909 | 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 | 915 | if (amt != code.len) { |
| 912 | 916 | log.err("fetching code for {s} failed", .{atom_ptr.name(elf_file)}); |
| 913 | 917 | return error.InputOutput; |
| ... | ... | @@ -1365,6 +1369,8 @@ fn updateNavCode( |
| 1365 | 1369 | ) link.File.UpdateNavError!void { |
| 1366 | 1370 | const zcu = pt.zcu; |
| 1367 | 1371 | const gpa = zcu.gpa; |
| 1372 | const comp = elf_file.base.comp; | |
| 1373 | const io = comp.io; | |
| 1368 | 1374 | const ip = &zcu.intern_pool; |
| 1369 | 1375 | const nav = ip.getNav(nav_index); |
| 1370 | 1376 | |
| ... | ... | @@ -1449,8 +1455,8 @@ fn updateNavCode( |
| 1449 | 1455 | const shdr = elf_file.sections.items(.shdr)[shdr_index]; |
| 1450 | 1456 | if (shdr.sh_type != elf.SHT_NOBITS) { |
| 1451 | 1457 | const file_offset = atom_ptr.offset(elf_file); |
| 1452 | elf_file.base.file.?.pwriteAll(code, file_offset) catch |err| | |
| 1453 | return elf_file.base.cgFail(nav_index, "failed to write to output file: {s}", .{@errorName(err)}); | |
| 1458 | elf_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err| | |
| 1459 | return elf_file.base.cgFail(nav_index, "failed to write to output file: {t}", .{err}); | |
| 1454 | 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 | 1473 | const zcu = pt.zcu; |
| 1468 | 1474 | const ip = &zcu.intern_pool; |
| 1469 | 1475 | const gpa = zcu.gpa; |
| 1476 | const comp = elf_file.base.comp; | |
| 1477 | const io = comp.io; | |
| 1470 | 1478 | const nav = ip.getNav(nav_index); |
| 1471 | 1479 | |
| 1472 | 1480 | log.debug("updateTlv {f}({d})", .{ nav.fqn.fmt(ip), nav_index }); |
| ... | ... | @@ -1503,8 +1511,8 @@ fn updateTlv( |
| 1503 | 1511 | const shdr = elf_file.sections.items(.shdr)[shndx]; |
| 1504 | 1512 | if (shdr.sh_type != elf.SHT_NOBITS) { |
| 1505 | 1513 | const file_offset = atom_ptr.offset(elf_file); |
| 1506 | elf_file.base.file.?.pwriteAll(code, file_offset) catch |err| | |
| 1507 | return elf_file.base.cgFail(nav_index, "failed to write to output file: {s}", .{@errorName(err)}); | |
| 1514 | elf_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err| | |
| 1515 | return elf_file.base.cgFail(nav_index, "failed to write to output file: {t}", .{err}); | |
| 1508 | 1516 | log.debug("writing TLV {s} from 0x{x} to 0x{x}", .{ |
| 1509 | 1517 | atom_ptr.name(elf_file), |
| 1510 | 1518 | file_offset, |
| ... | ... | @@ -2003,6 +2011,8 @@ fn trampolineSize(cpu_arch: std.Target.Cpu.Arch) u64 { |
| 2003 | 2011 | } |
| 2004 | 2012 | |
| 2005 | 2013 | fn writeTrampoline(tr_sym: Symbol, target: Symbol, elf_file: *Elf) !void { |
| 2014 | const comp = elf_file.base.comp; | |
| 2015 | const io = comp.io; | |
| 2006 | 2016 | const atom_ptr = tr_sym.atom(elf_file).?; |
| 2007 | 2017 | const fileoff = atom_ptr.offset(elf_file); |
| 2008 | 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 | 2022 | .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf), |
| 2013 | 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); | |
| 2016 | 2026 | |
| 2017 | 2027 | if (elf_file.base.child_pid) |pid| { |
| 2018 | 2028 | switch (builtin.os.tag) { |
src/link/Elf/file.zig+18-17| ... | ... | @@ -1,3 +1,20 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Io = std.Io; | |
| 3 | const elf = std.elf; | |
| 4 | const log = std.log.scoped(.link); | |
| 5 | const Path = std.Build.Cache.Path; | |
| 6 | const Allocator = std.mem.Allocator; | |
| 7 | ||
| 8 | const Archive = @import("Archive.zig"); | |
| 9 | const Atom = @import("Atom.zig"); | |
| 10 | const Cie = @import("eh_frame.zig").Cie; | |
| 11 | const Elf = @import("../Elf.zig"); | |
| 12 | const LinkerDefined = @import("LinkerDefined.zig"); | |
| 13 | const Object = @import("Object.zig"); | |
| 14 | const SharedObject = @import("SharedObject.zig"); | |
| 15 | const Symbol = @import("Symbol.zig"); | |
| 16 | const ZigObject = @import("ZigObject.zig"); | |
| 17 | ||
| 1 | 18 | pub const File = union(enum) { |
| 2 | 19 | zig_object: *ZigObject, |
| 3 | 20 | linker_defined: *LinkerDefined, |
| ... | ... | @@ -279,22 +296,6 @@ pub const File = union(enum) { |
| 279 | 296 | shared_object: SharedObject, |
| 280 | 297 | }; |
| 281 | 298 | |
| 282 | pub const Handle = std.fs.File; | |
| 299 | pub const Handle = Io.File; | |
| 283 | 300 | pub const HandleIndex = Index; |
| 284 | 301 | }; |
| 285 | ||
| 286 | const std = @import("std"); | |
| 287 | const elf = std.elf; | |
| 288 | const log = std.log.scoped(.link); | |
| 289 | const Path = std.Build.Cache.Path; | |
| 290 | const Allocator = std.mem.Allocator; | |
| 291 | ||
| 292 | const Archive = @import("Archive.zig"); | |
| 293 | const Atom = @import("Atom.zig"); | |
| 294 | const Cie = @import("eh_frame.zig").Cie; | |
| 295 | const Elf = @import("../Elf.zig"); | |
| 296 | const LinkerDefined = @import("LinkerDefined.zig"); | |
| 297 | const Object = @import("Object.zig"); | |
| 298 | const SharedObject = @import("SharedObject.zig"); | |
| 299 | const Symbol = @import("Symbol.zig"); | |
| 300 | const ZigObject = @import("ZigObject.zig"); |
src/link/Elf/relocatable.zig+34-34| ... | ... | @@ -1,5 +1,26 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const elf = std.elf; | |
| 4 | const math = std.math; | |
| 5 | const mem = std.mem; | |
| 6 | const Path = std.Build.Cache.Path; | |
| 7 | const log = std.log.scoped(.link); | |
| 8 | const state_log = std.log.scoped(.link_state); | |
| 9 | ||
| 10 | const build_options = @import("build_options"); | |
| 11 | ||
| 12 | const eh_frame = @import("eh_frame.zig"); | |
| 13 | const link = @import("../../link.zig"); | |
| 14 | const Archive = @import("Archive.zig"); | |
| 15 | const Compilation = @import("../../Compilation.zig"); | |
| 16 | const Elf = @import("../Elf.zig"); | |
| 17 | const File = @import("file.zig").File; | |
| 18 | const Object = @import("Object.zig"); | |
| 19 | const Symbol = @import("Symbol.zig"); | |
| 20 | ||
| 1 | 21 | pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void { |
| 2 | 22 | const gpa = comp.gpa; |
| 23 | const io = comp.io; | |
| 3 | 24 | const diags = &comp.link_diags; |
| 4 | 25 | |
| 5 | 26 | if (diags.hasErrors()) return error.LinkFailure; |
| ... | ... | @@ -125,8 +146,8 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void { |
| 125 | 146 | |
| 126 | 147 | assert(writer.buffered().len == total_size); |
| 127 | 148 | |
| 128 | try elf_file.base.file.?.setEndPos(total_size); | |
| 129 | try elf_file.base.file.?.pwriteAll(writer.buffered(), 0); | |
| 149 | try elf_file.base.file.?.setLength(io, total_size); | |
| 150 | try elf_file.base.file.?.writePositionalAll(io, writer.buffered(), 0); | |
| 130 | 151 | |
| 131 | 152 | if (diags.hasErrors()) return error.LinkFailure; |
| 132 | 153 | } |
| ... | ... | @@ -330,13 +351,7 @@ fn allocateAllocSections(elf_file: *Elf) !void { |
| 330 | 351 | |
| 331 | 352 | if (shdr.sh_offset > 0) { |
| 332 | 353 | const existing_size = elf_file.sectionSize(@intCast(shndx)); |
| 333 | const amt = try elf_file.base.file.?.copyRangeAll( | |
| 334 | shdr.sh_offset, | |
| 335 | elf_file.base.file.?, | |
| 336 | new_offset, | |
| 337 | existing_size, | |
| 338 | ); | |
| 339 | if (amt != existing_size) return error.InputOutput; | |
| 354 | try elf_file.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size); | |
| 340 | 355 | } |
| 341 | 356 | |
| 342 | 357 | shdr.sh_offset = new_offset; |
| ... | ... | @@ -360,7 +375,9 @@ fn writeAtoms(elf_file: *Elf) !void { |
| 360 | 375 | } |
| 361 | 376 | |
| 362 | 377 | fn 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 | 381 | const slice = elf_file.sections.slice(); |
| 365 | 382 | |
| 366 | 383 | const SortRelocs = struct { |
| ... | ... | @@ -397,7 +414,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void { |
| 397 | 414 | shdr.sh_offset + shdr.sh_size, |
| 398 | 415 | }); |
| 399 | 416 | |
| 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 | } |
| 402 | 419 | |
| 403 | 420 | if (elf_file.section_indexes.eh_frame) |shndx| { |
| ... | ... | @@ -417,7 +434,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void { |
| 417 | 434 | shdr.sh_offset + sh_size, |
| 418 | 435 | }); |
| 419 | 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 | 439 | if (elf_file.section_indexes.eh_frame_rela) |shndx| { |
| 423 | 440 | const shdr = slice.items(.shdr)[shndx]; |
| ... | ... | @@ -435,7 +452,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void { |
| 435 | 452 | shdr.sh_offset, |
| 436 | 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 | } |
| 440 | 457 | |
| 441 | 458 | try writeGroups(elf_file); |
| ... | ... | @@ -444,7 +461,9 @@ fn writeSyntheticSections(elf_file: *Elf) !void { |
| 444 | 461 | } |
| 445 | 462 | |
| 446 | 463 | fn 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 | 467 | for (elf_file.group_sections.items) |cgs| { |
| 449 | 468 | const shdr = elf_file.sections.items(.shdr)[cgs.shndx]; |
| 450 | 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 | 476 | shdr.sh_offset, |
| 458 | 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 | ||
| 464 | const assert = std.debug.assert; | |
| 465 | const build_options = @import("build_options"); | |
| 466 | const eh_frame = @import("eh_frame.zig"); | |
| 467 | const elf = std.elf; | |
| 468 | const link = @import("../../link.zig"); | |
| 469 | const log = std.log.scoped(.link); | |
| 470 | const math = std.math; | |
| 471 | const mem = std.mem; | |
| 472 | const state_log = std.log.scoped(.link_state); | |
| 473 | const Path = std.Build.Cache.Path; | |
| 474 | const std = @import("std"); | |
| 475 | ||
| 476 | const Archive = @import("Archive.zig"); | |
| 477 | const Compilation = @import("../../Compilation.zig"); | |
| 478 | const Elf = @import("../Elf.zig"); | |
| 479 | const File = @import("file.zig").File; | |
| 480 | const Object = @import("Object.zig"); | |
| 481 | const Symbol = @import("Symbol.zig"); |
src/link/Elf2.zig+45-33| ... | ... | @@ -1,3 +1,23 @@ |
| 1 | const Elf = @This(); | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | const native_endian = builtin.cpu.arch.endian(); | |
| 5 | ||
| 6 | const std = @import("std"); | |
| 7 | const Io = std.Io; | |
| 8 | const assert = std.debug.assert; | |
| 9 | const log = std.log.scoped(.link); | |
| 10 | ||
| 11 | const codegen = @import("../codegen.zig"); | |
| 12 | const Compilation = @import("../Compilation.zig"); | |
| 13 | const InternPool = @import("../InternPool.zig"); | |
| 14 | const link = @import("../link.zig"); | |
| 15 | const MappedFile = @import("MappedFile.zig"); | |
| 16 | const target_util = @import("../target.zig"); | |
| 17 | const Type = @import("../Type.zig"); | |
| 18 | const Value = @import("../Value.zig"); | |
| 19 | const Zcu = @import("../Zcu.zig"); | |
| 20 | ||
| 1 | 21 | base: link.File, |
| 2 | 22 | options: link.File.OpenOptions, |
| 3 | 23 | mf: MappedFile, |
| ... | ... | @@ -908,6 +928,7 @@ fn create( |
| 908 | 928 | path: std.Build.Cache.Path, |
| 909 | 929 | options: link.File.OpenOptions, |
| 910 | 930 | ) !*Elf { |
| 931 | const io = comp.io; | |
| 911 | 932 | const target = &comp.root_mod.resolved_target.result; |
| 912 | 933 | assert(target.ofmt == .elf); |
| 913 | 934 | const class: std.elf.CLASS = switch (target.ptrBitWidth()) { |
| ... | ... | @@ -953,11 +974,11 @@ fn create( |
| 953 | 974 | }; |
| 954 | 975 | |
| 955 | 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 | 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 | 982 | elf.* = .{ |
| 962 | 983 | .base = .{ |
| 963 | 984 | .tag = .elf2, |
| ... | ... | @@ -965,7 +986,7 @@ fn create( |
| 965 | 986 | .comp = comp, |
| 966 | 987 | .emit = path, |
| 967 | 988 | |
| 968 | .file = .adaptFromNewApi(file), | |
| 989 | .file = file, | |
| 969 | 990 | .gc_sections = false, |
| 970 | 991 | .print_gc_sections = false, |
| 971 | 992 | .build_id = .none, |
| ... | ... | @@ -973,7 +994,7 @@ fn create( |
| 973 | 994 | .stack_size = 0, |
| 974 | 995 | }, |
| 975 | 996 | .options = options, |
| 976 | .mf = try .init(file, comp.gpa), | |
| 997 | .mf = try .init(file, comp.gpa, io), | |
| 977 | 998 | .ni = .{ |
| 978 | 999 | .tls = .none, |
| 979 | 1000 | }, |
| ... | ... | @@ -1973,8 +1994,8 @@ pub fn lazySymbol(elf: *Elf, lazy: link.File.LazySymbol) !Symbol.Index { |
| 1973 | 1994 | return lazy_gop.value_ptr.*; |
| 1974 | 1995 | } |
| 1975 | 1996 | |
| 1976 | pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError || | |
| 1977 | std.Io.File.Reader.Error || MappedFile.Error || error{ EndOfStream, BadMagic, LinkFailure })!void { | |
| 1997 | pub fn loadInput(elf: *Elf, input: link.Input) (Io.File.Reader.SizeError || | |
| 1998 | Io.File.Reader.Error || MappedFile.Error || error{ EndOfStream, BadMagic, LinkFailure })!void { | |
| 1978 | 1999 | const io = elf.base.comp.io; |
| 1979 | 2000 | var buf: [4096]u8 = undefined; |
| 1980 | 2001 | switch (input) { |
| ... | ... | @@ -2007,7 +2028,7 @@ pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError || |
| 2007 | 2028 | .dso_exact => |dso_exact| try elf.loadDsoExact(dso_exact.name), |
| 2008 | 2029 | } |
| 2009 | 2030 | } |
| 2010 | fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *std.Io.File.Reader) !void { | |
| 2031 | fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { | |
| 2011 | 2032 | const comp = elf.base.comp; |
| 2012 | 2033 | const gpa = comp.gpa; |
| 2013 | 2034 | const diags = &comp.link_diags; |
| ... | ... | @@ -2067,7 +2088,7 @@ fn loadObject( |
| 2067 | 2088 | elf: *Elf, |
| 2068 | 2089 | path: std.Build.Cache.Path, |
| 2069 | 2090 | member: ?[]const u8, |
| 2070 | fr: *std.Io.File.Reader, | |
| 2091 | fr: *Io.File.Reader, | |
| 2071 | 2092 | fl: MappedFile.Node.FileLocation, |
| 2072 | 2093 | ) !void { |
| 2073 | 2094 | const comp = elf.base.comp; |
| ... | ... | @@ -2310,7 +2331,7 @@ fn loadObject( |
| 2310 | 2331 | }, |
| 2311 | 2332 | } |
| 2312 | 2333 | } |
| 2313 | fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *std.Io.File.Reader) !void { | |
| 2334 | fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { | |
| 2314 | 2335 | const comp = elf.base.comp; |
| 2315 | 2336 | const diags = &comp.link_diags; |
| 2316 | 2337 | const r = &fr.interface; |
| ... | ... | @@ -3305,12 +3326,13 @@ fn flushInputSection(elf: *Elf, isi: Node.InputSectionIndex) !void { |
| 3305 | 3326 | const file_loc = isi.fileLocation(elf); |
| 3306 | 3327 | if (file_loc.size == 0) return; |
| 3307 | 3328 | const comp = elf.base.comp; |
| 3329 | const io = comp.io; | |
| 3308 | 3330 | const gpa = comp.gpa; |
| 3309 | 3331 | const ii = isi.input(elf); |
| 3310 | 3332 | const path = ii.path(elf); |
| 3311 | const file = try path.root_dir.handle.adaptToNewApi().openFile(comp.io, path.sub_path, .{}); | |
| 3312 | defer file.close(comp.io); | |
| 3313 | var fr = file.reader(comp.io, &.{}); | |
| 3333 | const file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); | |
| 3334 | defer file.close(io); | |
| 3335 | var fr = file.reader(io, &.{}); | |
| 3314 | 3336 | try fr.seekTo(file_loc.offset); |
| 3315 | 3337 | var nw: MappedFile.Node.Writer = undefined; |
| 3316 | 3338 | const si = isi.symbol(elf); |
| ... | ... | @@ -3707,10 +3729,16 @@ pub fn deleteExport(elf: *Elf, exported: Zcu.Exported, name: InternPool.NullTerm |
| 3707 | 3729 | _ = name; |
| 3708 | 3730 | } |
| 3709 | 3731 | |
| 3710 | pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) void { | |
| 3711 | const w, _ = std.debug.lockStderrWriter(&.{}); | |
| 3712 | defer std.debug.unlockStderrWriter(); | |
| 3713 | elf.printNode(tid, w, .root, 0) catch {}; | |
| 3732 | pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) Io.Cancelable!void { | |
| 3733 | const comp = elf.base.comp; | |
| 3734 | const io = comp.io; | |
| 3735 | var buffer: [512]u8 = undefined; | |
| 3736 | const stderr = try io.lockStderr(&buffer, null); | |
| 3737 | defer io.lockStderr(); | |
| 3738 | const w = &stderr.file_writer.interface; | |
| 3739 | elf.printNode(tid, w, .root, 0) catch |err| switch (err) { | |
| 3740 | error.WriteFailed => return stderr.err.?, | |
| 3741 | }; | |
| 3714 | 3742 | } |
| 3715 | 3743 | |
| 3716 | 3744 | pub fn printNode( |
| ... | ... | @@ -3822,19 +3850,3 @@ pub fn printNode( |
| 3822 | 3850 | try w.writeByte('\n'); |
| 3823 | 3851 | } |
| 3824 | 3852 | } |
| 3825 | ||
| 3826 | const assert = std.debug.assert; | |
| 3827 | const builtin = @import("builtin"); | |
| 3828 | const codegen = @import("../codegen.zig"); | |
| 3829 | const Compilation = @import("../Compilation.zig"); | |
| 3830 | const Elf = @This(); | |
| 3831 | const InternPool = @import("../InternPool.zig"); | |
| 3832 | const link = @import("../link.zig"); | |
| 3833 | const log = std.log.scoped(.link); | |
| 3834 | const MappedFile = @import("MappedFile.zig"); | |
| 3835 | const native_endian = builtin.cpu.arch.endian(); | |
| 3836 | const std = @import("std"); | |
| 3837 | const target_util = @import("../target.zig"); | |
| 3838 | const Type = @import("../Type.zig"); | |
| 3839 | const Value = @import("../Value.zig"); | |
| 3840 | const Zcu = @import("../Zcu.zig"); |
src/link/Lld.zig+34-30| ... | ... | @@ -359,6 +359,7 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void { |
| 359 | 359 | fn coffLink(lld: *Lld, arena: Allocator) !void { |
| 360 | 360 | const comp = lld.base.comp; |
| 361 | 361 | const gpa = comp.gpa; |
| 362 | const io = comp.io; | |
| 362 | 363 | const base = &lld.base; |
| 363 | 364 | const coff = &lld.ofmt.coff; |
| 364 | 365 | |
| ... | ... | @@ -400,11 +401,12 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { |
| 400 | 401 | // regarding eliding redundant object -> object transformations. |
| 401 | 402 | return error.NoObjectsToLink; |
| 402 | 403 | }; |
| 403 | try std.fs.Dir.copyFile( | |
| 404 | try Io.Dir.copyFile( | |
| 404 | 405 | the_object_path.root_dir.handle, |
| 405 | 406 | the_object_path.sub_path, |
| 406 | 407 | directory.handle, |
| 407 | 408 | base.emit.sub_path, |
| 409 | io, | |
| 408 | 410 | .{}, |
| 409 | 411 | ); |
| 410 | 412 | } else { |
| ... | ... | @@ -718,13 +720,13 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { |
| 718 | 720 | argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena)); |
| 719 | 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 | 724 | argv.appendAssumeCapacity(full_path); |
| 723 | 725 | continue; |
| 724 | 726 | } |
| 725 | 727 | if (target.abi.isGnu()) { |
| 726 | 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 | 730 | argv.appendAssumeCapacity(full_path); |
| 729 | 731 | continue; |
| 730 | 732 | } |
| ... | ... | @@ -741,9 +743,9 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { |
| 741 | 743 | try spawnLld(comp, arena, argv.items); |
| 742 | 744 | } |
| 743 | 745 | } |
| 744 | fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Cache.Directory) !?[]const u8 { | |
| 746 | fn findLib(arena: Allocator, io: Io, name: []const u8, lib_directories: []const Cache.Directory) !?[]const u8 { | |
| 745 | 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 | 749 | error.FileNotFound => continue, |
| 748 | 750 | else => |e| return e, |
| 749 | 751 | }; |
| ... | ... | @@ -755,6 +757,7 @@ fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Cache.Di |
| 755 | 757 | fn elfLink(lld: *Lld, arena: Allocator) !void { |
| 756 | 758 | const comp = lld.base.comp; |
| 757 | 759 | const gpa = comp.gpa; |
| 760 | const io = comp.io; | |
| 758 | 761 | const diags = &comp.link_diags; |
| 759 | 762 | const base = &lld.base; |
| 760 | 763 | const elf = &lld.ofmt.elf; |
| ... | ... | @@ -816,11 +819,12 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { |
| 816 | 819 | // regarding eliding redundant object -> object transformations. |
| 817 | 820 | return error.NoObjectsToLink; |
| 818 | 821 | }; |
| 819 | try std.fs.Dir.copyFile( | |
| 822 | try Io.Dir.copyFile( | |
| 820 | 823 | the_object_path.root_dir.handle, |
| 821 | 824 | the_object_path.sub_path, |
| 822 | 825 | directory.handle, |
| 823 | 826 | base.emit.sub_path, |
| 827 | io, | |
| 824 | 828 | .{}, |
| 825 | 829 | ); |
| 826 | 830 | } else { |
| ... | ... | @@ -1326,6 +1330,7 @@ fn getLDMOption(target: *const std.Target) ?[]const u8 { |
| 1326 | 1330 | } |
| 1327 | 1331 | fn wasmLink(lld: *Lld, arena: Allocator) !void { |
| 1328 | 1332 | const comp = lld.base.comp; |
| 1333 | const diags = &comp.link_diags; | |
| 1329 | 1334 | const shared_memory = comp.config.shared_memory; |
| 1330 | 1335 | const export_memory = comp.config.export_memory; |
| 1331 | 1336 | const import_memory = comp.config.import_memory; |
| ... | ... | @@ -1334,6 +1339,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { |
| 1334 | 1339 | const wasm = &lld.ofmt.wasm; |
| 1335 | 1340 | |
| 1336 | 1341 | const gpa = comp.gpa; |
| 1342 | const io = comp.io; | |
| 1337 | 1343 | |
| 1338 | 1344 | const directory = base.emit.root_dir; // Just an alias to make it shorter to type. |
| 1339 | 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 | 1377 | // regarding eliding redundant object -> object transformations. |
| 1372 | 1378 | return error.NoObjectsToLink; |
| 1373 | 1379 | }; |
| 1374 | try fs.Dir.copyFile( | |
| 1380 | try Io.Dir.copyFile( | |
| 1375 | 1381 | the_object_path.root_dir.handle, |
| 1376 | 1382 | the_object_path.sub_path, |
| 1377 | 1383 | directory.handle, |
| 1378 | 1384 | base.emit.sub_path, |
| 1385 | io, | |
| 1379 | 1386 | .{}, |
| 1380 | 1387 | ); |
| 1381 | 1388 | } else { |
| ... | ... | @@ -1565,27 +1572,23 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { |
| 1565 | 1572 | // is not the case, it means we will get "exec format error" when trying to run |
| 1566 | 1573 | // it, and then can react to that in the same way as trying to run an ELF file |
| 1567 | 1574 | // from a foreign CPU architecture. |
| 1568 | if (fs.has_executable_bit and target.os.tag == .wasi and | |
| 1575 | if (Io.File.Permissions.has_executable_bit and target.os.tag == .wasi and | |
| 1569 | 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 | 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) { | |
| 1576 | error.OperationNotSupported => unreachable, // Not a symlink. | |
| 1577 | else => |e| return e, | |
| 1578 | }; | |
| 1579 | Io.Dir.cwd().setFilePermissions(io, full_out_path, .fromMode(0o744), .{}) catch |err| | |
| 1580 | return diags.fail("{s}: failed to enable executable permissions: {t}", .{ full_out_path, err }); | |
| 1579 | 1581 | } |
| 1580 | 1582 | } |
| 1581 | 1583 | } |
| 1582 | 1584 | |
| 1583 | 1585 | fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !void { |
| 1584 | 1586 | const io = comp.io; |
| 1587 | const gpa = comp.gpa; | |
| 1585 | 1588 | |
| 1586 | 1589 | if (comp.verbose_link) { |
| 1587 | 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 | } |
| 1590 | 1593 | |
| 1591 | 1594 | // 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 | 1602 | } |
| 1600 | 1603 | |
| 1601 | 1604 | var stderr: []u8 = &.{}; |
| 1602 | defer comp.gpa.free(stderr); | |
| 1605 | defer gpa.free(stderr); | |
| 1603 | 1606 | |
| 1604 | 1607 | var child = std.process.Child.init(argv, arena); |
| 1605 | 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 | 1610 | child.stdout_behavior = .Inherit; |
| 1608 | 1611 | child.stderr_behavior = .Inherit; |
| 1609 | 1612 | |
| 1610 | break :term child.spawnAndWait(); | |
| 1613 | break :term child.spawnAndWait(io); | |
| 1611 | 1614 | } else term: { |
| 1612 | 1615 | child.stdin_behavior = .Ignore; |
| 1613 | 1616 | child.stdout_behavior = .Ignore; |
| 1614 | 1617 | child.stderr_behavior = .Pipe; |
| 1615 | 1618 | |
| 1616 | child.spawn() catch |err| break :term err; | |
| 1619 | child.spawn(io) catch |err| break :term err; | |
| 1617 | 1620 | var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); |
| 1618 | stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited); | |
| 1619 | break :term child.wait(); | |
| 1621 | stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited); | |
| 1622 | break :term child.wait(io); | |
| 1620 | 1623 | }) catch |first_err| term: { |
| 1621 | 1624 | const err = switch (first_err) { |
| 1622 | 1625 | error.NameTooLong => err: { |
| ... | ... | @@ -1624,13 +1627,13 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi |
| 1624 | 1627 | const rand_int = std.crypto.random.int(u64); |
| 1625 | 1628 | const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp"; |
| 1626 | 1629 | |
| 1627 | const rsp_file = try comp.dirs.local_cache.handle.createFile(rsp_path, .{}); | |
| 1628 | defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err| | |
| 1629 | log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) }); | |
| 1630 | const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{}); | |
| 1631 | defer comp.dirs.local_cache.handle.deleteFile(io, rsp_path) catch |err| | |
| 1632 | log.warn("failed to delete response file {s}: {t}", .{ rsp_path, err }); | |
| 1630 | 1633 | { |
| 1631 | defer rsp_file.close(); | |
| 1634 | defer rsp_file.close(io); | |
| 1632 | 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 | 1637 | const rsp_writer = &rsp_file_writer.interface; |
| 1635 | 1638 | for (argv[2..]) |arg| { |
| 1636 | 1639 | try rsp_writer.writeByte('"'); |
| ... | ... | @@ -1657,16 +1660,16 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi |
| 1657 | 1660 | rsp_child.stdout_behavior = .Inherit; |
| 1658 | 1661 | rsp_child.stderr_behavior = .Inherit; |
| 1659 | 1662 | |
| 1660 | break :term rsp_child.spawnAndWait() catch |err| break :err err; | |
| 1663 | break :term rsp_child.spawnAndWait(io) catch |err| break :err err; | |
| 1661 | 1664 | } else { |
| 1662 | 1665 | rsp_child.stdin_behavior = .Ignore; |
| 1663 | 1666 | rsp_child.stdout_behavior = .Ignore; |
| 1664 | 1667 | rsp_child.stderr_behavior = .Pipe; |
| 1665 | 1668 | |
| 1666 | rsp_child.spawn() catch |err| break :err err; | |
| 1669 | rsp_child.spawn(io) catch |err| break :err err; | |
| 1667 | 1670 | var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{}); |
| 1668 | stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited); | |
| 1669 | break :term rsp_child.wait() catch |err| break :err err; | |
| 1671 | stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited); | |
| 1672 | break :term rsp_child.wait(io) catch |err| break :err err; | |
| 1670 | 1673 | } |
| 1671 | 1674 | }, |
| 1672 | 1675 | else => first_err, |
| ... | ... | @@ -1692,6 +1695,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi |
| 1692 | 1695 | } |
| 1693 | 1696 | |
| 1694 | 1697 | const std = @import("std"); |
| 1698 | const Io = std.Io; | |
| 1695 | 1699 | const Allocator = std.mem.Allocator; |
| 1696 | 1700 | const Cache = std.Build.Cache; |
| 1697 | 1701 | const allocPrint = std.fmt.allocPrint; |
src/link/MachO.zig+146-89| ... | ... | @@ -219,10 +219,12 @@ pub fn createEmpty( |
| 219 | 219 | }; |
| 220 | 220 | errdefer self.base.destroy(); |
| 221 | 221 | |
| 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 | 225 | .truncate = true, |
| 224 | 226 | .read = true, |
| 225 | .mode = link.File.determineMode(output_mode, link_mode), | |
| 227 | .permissions = link.File.determinePermissions(output_mode, link_mode), | |
| 226 | 228 | }); |
| 227 | 229 | |
| 228 | 230 | // Append null file |
| ... | ... | @@ -267,14 +269,16 @@ pub fn open( |
| 267 | 269 | } |
| 268 | 270 | |
| 269 | 271 | pub 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; | |
| 271 | 275 | |
| 272 | 276 | if (self.d_sym) |*d_sym| { |
| 273 | 277 | d_sym.deinit(); |
| 274 | 278 | } |
| 275 | 279 | |
| 276 | 280 | for (self.file_handles.items) |handle| { |
| 277 | handle.close(); | |
| 281 | handle.close(io); | |
| 278 | 282 | } |
| 279 | 283 | self.file_handles.deinit(gpa); |
| 280 | 284 | |
| ... | ... | @@ -343,7 +347,8 @@ pub fn flush( |
| 343 | 347 | |
| 344 | 348 | const comp = self.base.comp; |
| 345 | 349 | const gpa = comp.gpa; |
| 346 | const diags = &self.base.comp.link_diags; | |
| 350 | const io = comp.io; | |
| 351 | const diags = &comp.link_diags; | |
| 347 | 352 | |
| 348 | 353 | const sub_prog_node = prog_node.start("MachO Flush", 0); |
| 349 | 354 | defer sub_prog_node.end(); |
| ... | ... | @@ -376,26 +381,26 @@ pub fn flush( |
| 376 | 381 | // in this set. |
| 377 | 382 | try positionals.ensureUnusedCapacity(comp.c_object_table.keys().len); |
| 378 | 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 | } |
| 381 | 386 | |
| 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)); | |
| 383 | 388 | |
| 384 | 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 | } |
| 387 | 392 | |
| 388 | 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 | } |
| 391 | 396 | |
| 392 | 397 | if (comp.ubsan_rt_lib) |crt_file| { |
| 393 | 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 | 400 | diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); |
| 396 | 401 | } else if (comp.ubsan_rt_obj) |crt_file| { |
| 397 | 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 | 404 | diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); |
| 400 | 405 | } |
| 401 | 406 | |
| ... | ... | @@ -430,7 +435,7 @@ pub fn flush( |
| 430 | 435 | if (comp.config.link_libc and is_exe_or_dyn_lib) { |
| 431 | 436 | if (comp.zigc_static_lib) |zigc| { |
| 432 | 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 | 439 | diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); |
| 435 | 440 | } |
| 436 | 441 | } |
| ... | ... | @@ -453,12 +458,12 @@ pub fn flush( |
| 453 | 458 | for (system_libs.items) |lib| { |
| 454 | 459 | switch (Compilation.classifyFileExt(lib.path.sub_path)) { |
| 455 | 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 | 462 | self.classifyInputFile(dso_input) catch |err| |
| 458 | 463 | diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)}); |
| 459 | 464 | }, |
| 460 | 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 | 467 | self.classifyInputFile(archive_input) catch |err| |
| 463 | 468 | diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)}); |
| 464 | 469 | }, |
| ... | ... | @@ -469,11 +474,11 @@ pub fn flush( |
| 469 | 474 | // Finally, link against compiler_rt. |
| 470 | 475 | if (comp.compiler_rt_lib) |crt_file| { |
| 471 | 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 | 478 | diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); |
| 474 | 479 | } else if (comp.compiler_rt_obj) |crt_file| { |
| 475 | 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 | 482 | diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); |
| 478 | 483 | } |
| 479 | 484 | |
| ... | ... | @@ -564,7 +569,7 @@ pub fn flush( |
| 564 | 569 | self.writeLinkeditSectionsToFile() catch |err| switch (err) { |
| 565 | 570 | error.OutOfMemory => return error.OutOfMemory, |
| 566 | 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 | }; |
| 569 | 574 | |
| 570 | 575 | var codesig: ?CodeSignature = if (self.requiresCodeSig()) blk: { |
| ... | ... | @@ -575,8 +580,8 @@ pub fn flush( |
| 575 | 580 | // where the code signature goes into. |
| 576 | 581 | var codesig = CodeSignature.init(self.getPageSize()); |
| 577 | 582 | codesig.code_directory.ident = fs.path.basename(self.base.emit.sub_path); |
| 578 | if (self.entitlements) |path| codesig.addEntitlements(gpa, path) catch |err| | |
| 579 | return diags.fail("failed to add entitlements from {s}: {s}", .{ path, @errorName(err) }); | |
| 583 | if (self.entitlements) |path| codesig.addEntitlements(gpa, io, path) catch |err| | |
| 584 | return diags.fail("failed to add entitlements from {s}: {t}", .{ path, err }); | |
| 580 | 585 | try self.writeCodeSignaturePadding(&codesig); |
| 581 | 586 | break :blk codesig; |
| 582 | 587 | } else null; |
| ... | ... | @@ -612,15 +617,17 @@ pub fn flush( |
| 612 | 617 | else => |e| return diags.fail("failed to write code signature: {s}", .{@errorName(e)}), |
| 613 | 618 | }; |
| 614 | 619 | const emit = self.base.emit; |
| 615 | invalidateKernelCache(emit.root_dir.handle, emit.sub_path) catch |err| switch (err) { | |
| 616 | else => |e| return diags.fail("failed to invalidate kernel cache: {s}", .{@errorName(e)}), | |
| 620 | invalidateKernelCache(io, emit.root_dir.handle, emit.sub_path) catch |err| switch (err) { | |
| 621 | else => |e| return diags.fail("failed to invalidate kernel cache: {t}", .{e}), | |
| 617 | 622 | }; |
| 618 | 623 | } |
| 619 | 624 | } |
| 620 | 625 | |
| 621 | 626 | /// --verbose-link output |
| 622 | 627 | fn 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 | 631 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); |
| 625 | 632 | defer arena_allocator.deinit(); |
| 626 | 633 | const arena = arena_allocator.allocator(); |
| ... | ... | @@ -815,7 +822,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { |
| 815 | 822 | if (comp.ubsan_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); |
| 816 | 823 | } |
| 817 | 824 | |
| 818 | Compilation.dump_argv(argv.items); | |
| 825 | try Compilation.dumpArgv(io, argv.items); | |
| 819 | 826 | } |
| 820 | 827 | |
| 821 | 828 | /// TODO delete this, libsystem must be resolved when setting up the compilation pipeline |
| ... | ... | @@ -825,7 +832,8 @@ pub fn resolveLibSystem( |
| 825 | 832 | comp: *Compilation, |
| 826 | 833 | out_libs: anytype, |
| 827 | 834 | ) !void { |
| 828 | const diags = &self.base.comp.link_diags; | |
| 835 | const io = comp.io; | |
| 836 | const diags = &comp.link_diags; | |
| 829 | 837 | |
| 830 | 838 | var test_path = std.array_list.Managed(u8).init(arena); |
| 831 | 839 | var checked_paths = std.array_list.Managed([]const u8).init(arena); |
| ... | ... | @@ -834,16 +842,16 @@ pub fn resolveLibSystem( |
| 834 | 842 | if (self.sdk_layout) |sdk_layout| switch (sdk_layout) { |
| 835 | 843 | .sdk => { |
| 836 | 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 | 847 | .vendored => { |
| 840 | 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 | }; |
| 844 | 852 | |
| 845 | 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 | } |
| 848 | 856 | |
| 849 | 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 | 869 | const tracy = trace(@src()); |
| 862 | 870 | defer tracy.end(); |
| 863 | 871 | |
| 872 | const comp = self.base.comp; | |
| 873 | const io = comp.io; | |
| 874 | ||
| 864 | 875 | const path, const file = input.pathAndFile().?; |
| 865 | 876 | // TODO don't classify now, it's too late. The input file has already been classified |
| 866 | 877 | log.debug("classifying input file {f}", .{path}); |
| ... | ... | @@ -871,7 +882,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { |
| 871 | 882 | const fat_arch: ?fat.Arch = try self.parseFatFile(file, path); |
| 872 | 883 | const offset = if (fat_arch) |fa| fa.offset else 0; |
| 873 | 884 | |
| 874 | if (readMachHeader(file, offset) catch null) |h| blk: { | |
| 885 | if (readMachHeader(io, file, offset) catch null) |h| blk: { | |
| 875 | 886 | if (h.magic != macho.MH_MAGIC_64) break :blk; |
| 876 | 887 | switch (h.filetype) { |
| 877 | 888 | macho.MH_OBJECT => try self.addObject(path, fh, offset), |
| ... | ... | @@ -880,7 +891,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { |
| 880 | 891 | } |
| 881 | 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 | 895 | if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk; |
| 885 | 896 | try self.addArchive(input.archive, fh, fat_arch); |
| 886 | 897 | return; |
| ... | ... | @@ -888,12 +899,14 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { |
| 888 | 899 | _ = try self.addTbd(.fromLinkInput(input), true, fh); |
| 889 | 900 | } |
| 890 | 901 | |
| 891 | fn parseFatFile(self: *MachO, file: std.fs.File, path: Path) !?fat.Arch { | |
| 892 | const diags = &self.base.comp.link_diags; | |
| 893 | const fat_h = fat.readFatHeader(file) catch return null; | |
| 902 | fn parseFatFile(self: *MachO, file: Io.File, path: Path) !?fat.Arch { | |
| 903 | const comp = self.base.comp; | |
| 904 | const io = comp.io; | |
| 905 | const diags = &comp.link_diags; | |
| 906 | const fat_h = fat.readFatHeader(io, file) catch return null; | |
| 894 | 907 | if (fat_h.magic != macho.FAT_MAGIC and fat_h.magic != macho.FAT_MAGIC_64) return null; |
| 895 | 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 | 910 | const cpu_arch = self.getTarget().cpu.arch; |
| 898 | 911 | for (fat_archs) |arch| { |
| 899 | 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 | 914 | return diags.failParse(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)}); |
| 902 | 915 | } |
| 903 | 916 | |
| 904 | pub fn readMachHeader(file: std.fs.File, offset: usize) !macho.mach_header_64 { | |
| 917 | pub fn readMachHeader(io: Io, file: Io.File, offset: usize) !macho.mach_header_64 { | |
| 905 | 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 | 920 | if (nread != buffer.len) return error.InputOutput; |
| 908 | 921 | const hdr = @as(*align(1) const macho.mach_header_64, @ptrCast(&buffer)).*; |
| 909 | 922 | return hdr; |
| 910 | 923 | } |
| 911 | 924 | |
| 912 | pub fn readArMagic(file: std.fs.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 { | |
| 913 | const nread = try file.preadAll(buffer, offset); | |
| 925 | pub fn readArMagic(io: Io, file: Io.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 { | |
| 926 | const nread = try file.readPositionalAll(io, buffer, offset); | |
| 914 | 927 | if (nread != buffer.len) return error.InputOutput; |
| 915 | 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 | 934 | |
| 922 | 935 | const comp = self.base.comp; |
| 923 | 936 | const gpa = comp.gpa; |
| 937 | const io = comp.io; | |
| 924 | 938 | |
| 925 | 939 | const abs_path = try std.fs.path.resolvePosix(gpa, &.{ |
| 926 | 940 | comp.dirs.cwd, |
| ... | ... | @@ -930,7 +944,7 @@ fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u |
| 930 | 944 | errdefer gpa.free(abs_path); |
| 931 | 945 | |
| 932 | 946 | const file = self.getFileHandle(handle_index); |
| 933 | const stat = try file.stat(); | |
| 947 | const stat = try file.stat(io); | |
| 934 | 948 | const mtime = stat.mtime.toSeconds(); |
| 935 | 949 | const index: File.Index = @intCast(try self.files.addOne(gpa)); |
| 936 | 950 | self.files.set(index, .{ .object = .{ |
| ... | ... | @@ -1069,6 +1083,7 @@ fn isHoisted(self: *MachO, install_name: []const u8) bool { |
| 1069 | 1083 | /// TODO delete this, libraries must be instead resolved when instantiating the compilation pipeline |
| 1070 | 1084 | fn accessLibPath( |
| 1071 | 1085 | arena: Allocator, |
| 1086 | io: Io, | |
| 1072 | 1087 | test_path: *std.array_list.Managed(u8), |
| 1073 | 1088 | checked_paths: *std.array_list.Managed([]const u8), |
| 1074 | 1089 | search_dir: []const u8, |
| ... | ... | @@ -1080,7 +1095,7 @@ fn accessLibPath( |
| 1080 | 1095 | test_path.clearRetainingCapacity(); |
| 1081 | 1096 | try test_path.print("{s}" ++ sep ++ "lib{s}{s}", .{ search_dir, name, ext }); |
| 1082 | 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 | 1099 | error.FileNotFound => continue, |
| 1085 | 1100 | else => |e| return e, |
| 1086 | 1101 | }; |
| ... | ... | @@ -1092,6 +1107,7 @@ fn accessLibPath( |
| 1092 | 1107 | |
| 1093 | 1108 | fn accessFrameworkPath( |
| 1094 | 1109 | arena: Allocator, |
| 1110 | io: Io, | |
| 1095 | 1111 | test_path: *std.array_list.Managed(u8), |
| 1096 | 1112 | checked_paths: *std.array_list.Managed([]const u8), |
| 1097 | 1113 | search_dir: []const u8, |
| ... | ... | @@ -1108,7 +1124,7 @@ fn accessFrameworkPath( |
| 1108 | 1124 | ext, |
| 1109 | 1125 | }); |
| 1110 | 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 | 1128 | error.FileNotFound => continue, |
| 1113 | 1129 | else => |e| return e, |
| 1114 | 1130 | }; |
| ... | ... | @@ -1124,7 +1140,9 @@ fn parseDependentDylibs(self: *MachO) !void { |
| 1124 | 1140 | |
| 1125 | 1141 | if (self.dylibs.items.len == 0) return; |
| 1126 | 1142 | |
| 1127 | const gpa = self.base.comp.gpa; | |
| 1143 | const comp = self.base.comp; | |
| 1144 | const gpa = comp.gpa; | |
| 1145 | const io = comp.io; | |
| 1128 | 1146 | const framework_dirs = self.framework_dirs; |
| 1129 | 1147 | |
| 1130 | 1148 | // TODO delete this, directories must instead be resolved by the frontend |
| ... | ... | @@ -1165,14 +1183,14 @@ fn parseDependentDylibs(self: *MachO) !void { |
| 1165 | 1183 | // Framework |
| 1166 | 1184 | for (framework_dirs) |dir| { |
| 1167 | 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 | } |
| 1170 | 1188 | |
| 1171 | 1189 | // Library |
| 1172 | 1190 | const lib_name = eatPrefix(stem, "lib") orelse stem; |
| 1173 | 1191 | for (lib_directories) |lib_directory| { |
| 1174 | 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 | } |
| 1178 | 1196 | |
| ... | ... | @@ -1181,13 +1199,13 @@ fn parseDependentDylibs(self: *MachO) !void { |
| 1181 | 1199 | const path = if (existing_ext.len > 0) id.name[0 .. id.name.len - existing_ext.len] else id.name; |
| 1182 | 1200 | for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| { |
| 1183 | 1201 | test_path.clearRetainingCapacity(); |
| 1184 | if (self.base.comp.sysroot) |root| { | |
| 1202 | if (comp.sysroot) |root| { | |
| 1185 | 1203 | try test_path.print("{s}" ++ fs.path.sep_str ++ "{s}{s}", .{ root, path, ext }); |
| 1186 | 1204 | } else { |
| 1187 | 1205 | try test_path.print("{s}{s}", .{ path, ext }); |
| 1188 | 1206 | } |
| 1189 | 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 | 1209 | error.FileNotFound => continue, |
| 1192 | 1210 | else => |e| return e, |
| 1193 | 1211 | }; |
| ... | ... | @@ -1202,7 +1220,8 @@ fn parseDependentDylibs(self: *MachO) !void { |
| 1202 | 1220 | const rel_path = try fs.path.join(arena, &.{ prefix, path }); |
| 1203 | 1221 | try checked_paths.append(rel_path); |
| 1204 | 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 | 1225 | break :full_path try arena.dupe(u8, full_path); |
| 1207 | 1226 | } |
| 1208 | 1227 | } else if (eatPrefix(id.name, "@loader_path/")) |_| { |
| ... | ... | @@ -1215,8 +1234,9 @@ fn parseDependentDylibs(self: *MachO) !void { |
| 1215 | 1234 | |
| 1216 | 1235 | try checked_paths.append(try arena.dupe(u8, id.name)); |
| 1217 | 1236 | var buffer: [fs.max_path_bytes]u8 = undefined; |
| 1218 | if (fs.realpath(id.name, &buffer)) |full_path| { | |
| 1219 | break :full_path try arena.dupe(u8, full_path); | |
| 1237 | // TODO don't use realpath | |
| 1238 | if (Io.Dir.realPathFileAbsolute(io, id.name, &buffer)) |full_path_n| { | |
| 1239 | break :full_path try arena.dupe(u8, buffer[0..full_path_n]); | |
| 1220 | 1240 | } else |_| { |
| 1221 | 1241 | try self.reportMissingDependencyError( |
| 1222 | 1242 | self.getFile(dylib_index).?.dylib.getUmbrella(self).index, |
| ... | ... | @@ -1233,12 +1253,12 @@ fn parseDependentDylibs(self: *MachO) !void { |
| 1233 | 1253 | .path = Path.initCwd(full_path), |
| 1234 | 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 | 1257 | const fh = try self.addFileHandle(file); |
| 1238 | 1258 | const fat_arch = try self.parseFatFile(file, lib.path); |
| 1239 | 1259 | const offset = if (fat_arch) |fa| fa.offset else 0; |
| 1240 | 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 | 1262 | if (h.magic != macho.MH_MAGIC_64) break :blk; |
| 1243 | 1263 | switch (h.filetype) { |
| 1244 | 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 | 3167 | } |
| 3148 | 3168 | } |
| 3149 | 3169 | |
| 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 | 3173 | return null; |
| 3152 | 3174 | } |
| 3153 | 3175 | |
| ... | ... | @@ -3232,21 +3254,36 @@ pub fn findFreeSpaceVirtual(self: *MachO, object_size: u64, min_alignment: u32) |
| 3232 | 3254 | } |
| 3233 | 3255 | |
| 3234 | 3256 | pub fn copyRangeAll(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void { |
| 3235 | const file = self.base.file.?; | |
| 3236 | const amt = try file.copyRangeAll(old_offset, file, new_offset, size); | |
| 3237 | if (amt != size) return error.InputOutput; | |
| 3257 | return self.base.copyRangeAll(old_offset, new_offset, size); | |
| 3238 | 3258 | } |
| 3239 | 3259 | |
| 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 | 3261 | /// This is so that we guarantee zeroed out regions for mapping of zerofill sections by the loader. |
| 3242 | 3262 | fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void { |
| 3243 | const gpa = self.base.comp.gpa; | |
| 3244 | try self.copyRangeAll(old_offset, new_offset, size); | |
| 3263 | const comp = self.base.comp; | |
| 3264 | const io = comp.io; | |
| 3265 | const file = self.base.file.?; | |
| 3266 | var write_buffer: [2048]u8 = undefined; | |
| 3267 | var file_reader = file.reader(io, &.{}); | |
| 3268 | file_reader.pos = old_offset; | |
| 3269 | var file_writer = file.writer(io, &write_buffer); | |
| 3270 | file_writer.pos = new_offset; | |
| 3245 | 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. | |
| 3247 | defer gpa.free(zeroes); | |
| 3248 | @memset(zeroes, 0); | |
| 3249 | try self.base.file.?.pwriteAll(zeroes, old_offset); | |
| 3272 | const n = file_writer.interface.sendFileAll(&file_reader, .limited(size_u)) catch |err| switch (err) { | |
| 3273 | error.ReadFailed => return file_reader.err.?, | |
| 3274 | error.WriteFailed => return file_writer.err.?, | |
| 3275 | }; | |
| 3276 | assert(n == size_u); | |
| 3277 | file_writer.seekTo(old_offset) catch |err| switch (err) { | |
| 3278 | error.WriteFailed => return file_writer.err.?, | |
| 3279 | else => |e| return e, | |
| 3280 | }; | |
| 3281 | file_writer.interface.splatByteAll(0, size_u) catch |err| switch (err) { | |
| 3282 | error.WriteFailed => return file_writer.err.?, | |
| 3283 | }; | |
| 3284 | file_writer.interface.flush() catch |err| switch (err) { | |
| 3285 | error.WriteFailed => return file_writer.err.?, | |
| 3286 | }; | |
| 3250 | 3287 | } |
| 3251 | 3288 | |
| 3252 | 3289 | const InitMetadataOptions = struct { |
| ... | ... | @@ -3257,8 +3294,10 @@ const InitMetadataOptions = struct { |
| 3257 | 3294 | }; |
| 3258 | 3295 | |
| 3259 | 3296 | pub fn closeDebugInfo(self: *MachO) bool { |
| 3297 | const comp = self.base.comp; | |
| 3298 | const io = comp.io; | |
| 3260 | 3299 | const d_sym = &(self.d_sym orelse return false); |
| 3261 | d_sym.file.?.close(); | |
| 3300 | d_sym.file.?.close(io); | |
| 3262 | 3301 | d_sym.file = null; |
| 3263 | 3302 | return true; |
| 3264 | 3303 | } |
| ... | ... | @@ -3269,7 +3308,9 @@ pub fn reopenDebugInfo(self: *MachO) !void { |
| 3269 | 3308 | assert(!self.base.comp.config.use_llvm); |
| 3270 | 3309 | assert(self.base.comp.config.debug_format == .dwarf); |
| 3271 | 3310 | |
| 3272 | const gpa = self.base.comp.gpa; | |
| 3311 | const comp = self.base.comp; | |
| 3312 | const io = comp.io; | |
| 3313 | const gpa = comp.gpa; | |
| 3273 | 3314 | const sep = fs.path.sep_str; |
| 3274 | 3315 | const d_sym_path = try std.fmt.allocPrint( |
| 3275 | 3316 | gpa, |
| ... | ... | @@ -3278,10 +3319,10 @@ pub fn reopenDebugInfo(self: *MachO) !void { |
| 3278 | 3319 | ); |
| 3279 | 3320 | defer gpa.free(d_sym_path); |
| 3280 | 3321 | |
| 3281 | var d_sym_bundle = try self.base.emit.root_dir.handle.makeOpenPath(d_sym_path, .{}); | |
| 3282 | defer d_sym_bundle.close(); | |
| 3322 | var d_sym_bundle = try self.base.emit.root_dir.handle.createDirPathOpen(io, d_sym_path, .{}); | |
| 3323 | defer d_sym_bundle.close(io); | |
| 3283 | 3324 | |
| 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 | 3326 | .truncate = false, |
| 3286 | 3327 | .read = true, |
| 3287 | 3328 | }); |
| ... | ... | @@ -3289,6 +3330,10 @@ pub fn reopenDebugInfo(self: *MachO) !void { |
| 3289 | 3330 | |
| 3290 | 3331 | // TODO: move to ZigObject |
| 3291 | 3332 | fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { |
| 3333 | const comp = self.base.comp; | |
| 3334 | const gpa = comp.gpa; | |
| 3335 | const io = comp.io; | |
| 3336 | ||
| 3292 | 3337 | if (!self.base.isRelocatable()) { |
| 3293 | 3338 | const base_vmaddr = blk: { |
| 3294 | 3339 | const pagezero_size = self.pagezero_size orelse default_pagezero_size; |
| ... | ... | @@ -3343,7 +3388,11 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { |
| 3343 | 3388 | if (options.zo.dwarf) |*dwarf| { |
| 3344 | 3389 | // Create dSYM bundle. |
| 3345 | 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 | 3396 | try self.reopenDebugInfo(); |
| 3348 | 3397 | try self.d_sym.?.initMetadata(self); |
| 3349 | 3398 | try dwarf.initMetadata(); |
| ... | ... | @@ -3463,6 +3512,9 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo |
| 3463 | 3512 | const seg_id = self.sections.items(.segment_id)[sect_index]; |
| 3464 | 3513 | const seg = &self.segments.items[seg_id]; |
| 3465 | 3514 | |
| 3515 | const comp = self.base.comp; | |
| 3516 | const io = comp.io; | |
| 3517 | ||
| 3466 | 3518 | if (!sect.isZerofill()) { |
| 3467 | 3519 | const allocated_size = self.allocatedSize(sect.offset); |
| 3468 | 3520 | if (needed_size > allocated_size) { |
| ... | ... | @@ -3484,7 +3536,7 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo |
| 3484 | 3536 | |
| 3485 | 3537 | sect.offset = @intCast(new_offset); |
| 3486 | 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 | 3541 | seg.filesize = needed_size; |
| 3490 | 3542 | } |
| ... | ... | @@ -3506,6 +3558,8 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo |
| 3506 | 3558 | } |
| 3507 | 3559 | |
| 3508 | 3560 | fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void { |
| 3561 | const comp = self.base.comp; | |
| 3562 | const io = comp.io; | |
| 3509 | 3563 | const sect = &self.sections.items(.header)[sect_index]; |
| 3510 | 3564 | |
| 3511 | 3565 | if (!sect.isZerofill()) { |
| ... | ... | @@ -3533,7 +3587,7 @@ fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void |
| 3533 | 3587 | sect.offset = @intCast(new_offset); |
| 3534 | 3588 | sect.addr = new_addr; |
| 3535 | 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 | 3593 | sect.size = needed_size; |
| ... | ... | @@ -3567,11 +3621,11 @@ pub fn getTarget(self: *const MachO) *const std.Target { |
| 3567 | 3621 | /// into a new inode, remove the original file, and rename the copy to match |
| 3568 | 3622 | /// the original file. This is super messy, but there doesn't seem any other |
| 3569 | 3623 | /// way to please the XNU. |
| 3570 | pub fn invalidateKernelCache(dir: fs.Dir, sub_path: []const u8) !void { | |
| 3624 | pub fn invalidateKernelCache(io: Io, dir: Io.Dir, sub_path: []const u8) !void { | |
| 3571 | 3625 | const tracy = trace(@src()); |
| 3572 | 3626 | defer tracy.end(); |
| 3573 | 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 | } |
| 3577 | 3631 | |
| ... | ... | @@ -3762,7 +3816,7 @@ pub fn getInternalObject(self: *MachO) ?*InternalObject { |
| 3762 | 3816 | return self.getFile(index).?.internal; |
| 3763 | 3817 | } |
| 3764 | 3818 | |
| 3765 | pub fn addFileHandle(self: *MachO, file: fs.File) !File.HandleIndex { | |
| 3819 | pub fn addFileHandle(self: *MachO, file: Io.File) !File.HandleIndex { | |
| 3766 | 3820 | const gpa = self.base.comp.gpa; |
| 3767 | 3821 | const index: File.HandleIndex = @intCast(self.file_handles.items.len); |
| 3768 | 3822 | const fh = try self.file_handles.addOne(gpa); |
| ... | ... | @@ -4333,11 +4387,13 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi |
| 4333 | 4387 | defer arena_allocator.deinit(); |
| 4334 | 4388 | const arena = arena_allocator.allocator(); |
| 4335 | 4389 | |
| 4390 | const io = comp.io; | |
| 4391 | ||
| 4336 | 4392 | const sdk_dir = switch (sdk_layout) { |
| 4337 | 4393 | .sdk => comp.sysroot.?, |
| 4338 | 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 | 4397 | return parseSdkVersion(ver); |
| 4342 | 4398 | } else |_| { |
| 4343 | 4399 | // Read from settings should always succeed when vendored. |
| ... | ... | @@ -4360,9 +4416,9 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi |
| 4360 | 4416 | // Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout. |
| 4361 | 4417 | // Use property `MinimalDisplayName` to determine version. |
| 4362 | 4418 | // The file/property is also available with vendored libc. |
| 4363 | fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 { | |
| 4419 | fn readSdkVersionFromSettings(arena: Allocator, io: Io, dir: []const u8) ![]const u8 { | |
| 4364 | 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 | 4422 | const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{}); |
| 4367 | 4423 | if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string; |
| 4368 | 4424 | return error.SdkVersionFailure; |
| ... | ... | @@ -5324,18 +5380,18 @@ fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool { |
| 5324 | 5380 | |
| 5325 | 5381 | pub fn pwriteAll(macho_file: *MachO, bytes: []const u8, offset: u64) error{LinkFailure}!void { |
| 5326 | 5382 | const comp = macho_file.base.comp; |
| 5383 | const io = comp.io; | |
| 5327 | 5384 | const diags = &comp.link_diags; |
| 5328 | macho_file.base.file.?.pwriteAll(bytes, offset) catch |err| { | |
| 5329 | return diags.fail("failed to write: {s}", .{@errorName(err)}); | |
| 5330 | }; | |
| 5385 | macho_file.base.file.?.writePositionalAll(io, bytes, offset) catch |err| | |
| 5386 | return diags.fail("failed to write: {t}", .{err}); | |
| 5331 | 5387 | } |
| 5332 | 5388 | |
| 5333 | pub fn setEndPos(macho_file: *MachO, length: u64) error{LinkFailure}!void { | |
| 5389 | pub fn setLength(macho_file: *MachO, length: u64) error{LinkFailure}!void { | |
| 5334 | 5390 | const comp = macho_file.base.comp; |
| 5391 | const io = comp.io; | |
| 5335 | 5392 | const diags = &comp.link_diags; |
| 5336 | macho_file.base.file.?.setEndPos(length) catch |err| { | |
| 5337 | return diags.fail("failed to set file end pos: {s}", .{@errorName(err)}); | |
| 5338 | }; | |
| 5393 | macho_file.base.file.?.setLength(io, length) catch |err| | |
| 5394 | return diags.fail("failed to set file end pos: {t}", .{err}); | |
| 5339 | 5395 | } |
| 5340 | 5396 | |
| 5341 | 5397 | pub 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 | 5423 | const max_allowed_distance = max_distance - 0x500_000; |
| 5368 | 5424 | |
| 5369 | 5425 | const MachO = @This(); |
| 5370 | ||
| 5371 | const std = @import("std"); | |
| 5372 | 5426 | const build_options = @import("build_options"); |
| 5373 | 5427 | const builtin = @import("builtin"); |
| 5428 | ||
| 5429 | const std = @import("std"); | |
| 5430 | const Io = std.Io; | |
| 5374 | 5431 | const assert = std.debug.assert; |
| 5375 | 5432 | const fs = std.fs; |
| 5376 | 5433 | const log = std.log.scoped(.link); |
| ... | ... | @@ -5380,6 +5437,11 @@ const math = std.math; |
| 5380 | 5437 | const mem = std.mem; |
| 5381 | 5438 | const meta = std.meta; |
| 5382 | 5439 | const Writer = std.Io.Writer; |
| 5440 | const AtomicBool = std.atomic.Value(bool); | |
| 5441 | const Cache = std.Build.Cache; | |
| 5442 | const Hash = std.hash.Wyhash; | |
| 5443 | const Md5 = std.crypto.hash.Md5; | |
| 5444 | const Allocator = std.mem.Allocator; | |
| 5383 | 5445 | |
| 5384 | 5446 | const aarch64 = codegen.aarch64.encoding; |
| 5385 | 5447 | const bind = @import("MachO/dyld_info/bind.zig"); |
| ... | ... | @@ -5397,11 +5459,8 @@ const trace = @import("../tracy.zig").trace; |
| 5397 | 5459 | const synthetic = @import("MachO/synthetic.zig"); |
| 5398 | 5460 | |
| 5399 | 5461 | const Alignment = Atom.Alignment; |
| 5400 | const Allocator = mem.Allocator; | |
| 5401 | 5462 | const Archive = @import("MachO/Archive.zig"); |
| 5402 | const AtomicBool = std.atomic.Value(bool); | |
| 5403 | 5463 | const Bind = bind.Bind; |
| 5404 | const Cache = std.Build.Cache; | |
| 5405 | 5464 | const CodeSignature = @import("MachO/CodeSignature.zig"); |
| 5406 | 5465 | const Compilation = @import("../Compilation.zig"); |
| 5407 | 5466 | const DataInCode = synthetic.DataInCode; |
| ... | ... | @@ -5411,14 +5470,12 @@ const ExportTrie = @import("MachO/dyld_info/Trie.zig"); |
| 5411 | 5470 | const Path = Cache.Path; |
| 5412 | 5471 | const File = @import("MachO/file.zig").File; |
| 5413 | 5472 | const GotSection = synthetic.GotSection; |
| 5414 | const Hash = std.hash.Wyhash; | |
| 5415 | 5473 | const Indsymtab = synthetic.Indsymtab; |
| 5416 | 5474 | const InternalObject = @import("MachO/InternalObject.zig"); |
| 5417 | 5475 | const ObjcStubsSection = synthetic.ObjcStubsSection; |
| 5418 | 5476 | const Object = @import("MachO/Object.zig"); |
| 5419 | 5477 | const LazyBind = bind.LazyBind; |
| 5420 | 5478 | const LaSymbolPtrSection = synthetic.LaSymbolPtrSection; |
| 5421 | const Md5 = std.crypto.hash.Md5; | |
| 5422 | 5479 | const Zcu = @import("../Zcu.zig"); |
| 5423 | 5480 | const InternPool = @import("../InternPool.zig"); |
| 5424 | 5481 | const 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 | |
| 7 | 7 | pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void { |
| 8 | 8 | const comp = macho_file.base.comp; |
| 9 | const io = comp.io; | |
| 9 | 10 | const gpa = comp.gpa; |
| 10 | 11 | const diags = &comp.link_diags; |
| 11 | 12 | |
| ... | ... | @@ -14,7 +15,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File |
| 14 | 15 | |
| 15 | 16 | const handle = macho_file.getFileHandle(handle_index); |
| 16 | 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; | |
| 18 | 19 | |
| 19 | 20 | var pos: usize = offset + SARMAG; |
| 20 | 21 | while (true) { |
| ... | ... | @@ -23,7 +24,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File |
| 23 | 24 | |
| 24 | 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 | 28 | if (amt != @sizeOf(ar_hdr)) return error.InputOutput; |
| 28 | 29 | } |
| 29 | 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 | 42 | if (try hdr.nameLength()) |len| { |
| 42 | 43 | hdr_size -= len; |
| 43 | 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 | 46 | if (amt != len) return error.InputOutput; |
| 46 | 47 | pos += len; |
| 47 | 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 | 1 | const CodeSignature = @This(); |
| 2 | 2 | |
| 3 | 3 | const std = @import("std"); |
| 4 | const Io = std.Io; | |
| 4 | 5 | const assert = std.debug.assert; |
| 5 | 6 | const fs = std.fs; |
| 6 | 7 | const log = std.log.scoped(.link); |
| 7 | 8 | const macho = std.macho; |
| 8 | 9 | const mem = std.mem; |
| 9 | 10 | const testing = std.testing; |
| 11 | const Sha256 = std.crypto.hash.sha2.Sha256; | |
| 12 | const Allocator = std.mem.Allocator; | |
| 13 | ||
| 10 | 14 | const trace = @import("../../tracy.zig").trace; |
| 11 | const Allocator = mem.Allocator; | |
| 12 | const Hasher = @import("hasher.zig").ParallelHasher; | |
| 15 | const ParallelHasher = @import("hasher.zig").ParallelHasher; | |
| 13 | 16 | const MachO = @import("../MachO.zig"); |
| 14 | const Sha256 = std.crypto.hash.sha2.Sha256; | |
| 15 | 17 | |
| 16 | 18 | const hash_size = Sha256.digest_length; |
| 17 | 19 | |
| 20 | page_size: u16, | |
| 21 | code_directory: CodeDirectory, | |
| 22 | requirements: ?Requirements = null, | |
| 23 | entitlements: ?Entitlements = null, | |
| 24 | signature: ?Signature = null, | |
| 25 | ||
| 18 | 26 | const Blob = union(enum) { |
| 19 | 27 | code_directory: *CodeDirectory, |
| 20 | 28 | requirements: *Requirements, |
| ... | ... | @@ -218,12 +226,6 @@ const Signature = struct { |
| 218 | 226 | } |
| 219 | 227 | }; |
| 220 | 228 | |
| 221 | page_size: u16, | |
| 222 | code_directory: CodeDirectory, | |
| 223 | requirements: ?Requirements = null, | |
| 224 | entitlements: ?Entitlements = null, | |
| 225 | signature: ?Signature = null, | |
| 226 | ||
| 227 | 229 | pub fn init(page_size: u16) CodeSignature { |
| 228 | 230 | return .{ |
| 229 | 231 | .page_size = page_size, |
| ... | ... | @@ -244,13 +246,13 @@ pub fn deinit(self: *CodeSignature, allocator: Allocator) void { |
| 244 | 246 | } |
| 245 | 247 | } |
| 246 | 248 | |
| 247 | pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void { | |
| 248 | const inner = try fs.cwd().readFileAlloc(path, allocator, .limited(std.math.maxInt(u32))); | |
| 249 | pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, io: Io, path: []const u8) !void { | |
| 250 | const inner = try Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(std.math.maxInt(u32))); | |
| 249 | 251 | self.entitlements = .{ .inner = inner }; |
| 250 | 252 | } |
| 251 | 253 | |
| 252 | 254 | pub const WriteOpts = struct { |
| 253 | file: fs.File, | |
| 255 | file: Io.File, | |
| 254 | 256 | exec_seg_base: u64, |
| 255 | 257 | exec_seg_limit: u64, |
| 256 | 258 | file_size: u32, |
| ... | ... | @@ -266,7 +268,9 @@ pub fn writeAdhocSignature( |
| 266 | 268 | const tracy = trace(@src()); |
| 267 | 269 | defer tracy.end(); |
| 268 | 270 | |
| 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; | |
| 270 | 274 | |
| 271 | 275 | var header: macho.SuperBlob = .{ |
| 272 | 276 | .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE, |
| ... | ... | @@ -274,7 +278,7 @@ pub fn writeAdhocSignature( |
| 274 | 278 | .count = 0, |
| 275 | 279 | }; |
| 276 | 280 | |
| 277 | var blobs = std.array_list.Managed(Blob).init(allocator); | |
| 281 | var blobs = std.array_list.Managed(Blob).init(gpa); | |
| 278 | 282 | defer blobs.deinit(); |
| 279 | 283 | |
| 280 | 284 | self.code_directory.inner.execSegBase = opts.exec_seg_base; |
| ... | ... | @@ -284,13 +288,12 @@ pub fn writeAdhocSignature( |
| 284 | 288 | |
| 285 | 289 | const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size)); |
| 286 | 290 | |
| 287 | try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages); | |
| 291 | try self.code_directory.code_slots.ensureTotalCapacityPrecise(gpa, total_pages); | |
| 288 | 292 | self.code_directory.code_slots.items.len = total_pages; |
| 289 | 293 | self.code_directory.inner.nCodeSlots = total_pages; |
| 290 | 294 | |
| 291 | 295 | // Calculate hash for each page (in file) and write it to the buffer |
| 292 | var hasher = Hasher(Sha256){ .allocator = allocator, .io = macho_file.base.comp.io }; | |
| 293 | try hasher.hash(opts.file, self.code_directory.code_slots.items, .{ | |
| 296 | try ParallelHasher(Sha256).hash(gpa, io, opts.file, self.code_directory.code_slots.items, .{ | |
| 294 | 297 | .chunk_size = self.page_size, |
| 295 | 298 | .max_file_size = opts.file_size, |
| 296 | 299 | }); |
| ... | ... | @@ -302,7 +305,7 @@ pub fn writeAdhocSignature( |
| 302 | 305 | var hash: [hash_size]u8 = undefined; |
| 303 | 306 | |
| 304 | 307 | if (self.requirements) |*req| { |
| 305 | var a: std.Io.Writer.Allocating = .init(allocator); | |
| 308 | var a: std.Io.Writer.Allocating = .init(gpa); | |
| 306 | 309 | defer a.deinit(); |
| 307 | 310 | try req.write(&a.writer); |
| 308 | 311 | Sha256.hash(a.written(), &hash, .{}); |
| ... | ... | @@ -314,7 +317,7 @@ pub fn writeAdhocSignature( |
| 314 | 317 | } |
| 315 | 318 | |
| 316 | 319 | if (self.entitlements) |*ents| { |
| 317 | var a: std.Io.Writer.Allocating = .init(allocator); | |
| 320 | var a: std.Io.Writer.Allocating = .init(gpa); | |
| 318 | 321 | defer a.deinit(); |
| 319 | 322 | try ents.write(&a.writer); |
| 320 | 323 | Sha256.hash(a.written(), &hash, .{}); |
src/link/MachO/DebugSymbols.zig+45-46| ... | ... | @@ -1,5 +1,28 @@ |
| 1 | const DebugSymbols = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const assert = std.debug.assert; | |
| 6 | const fs = std.fs; | |
| 7 | const log = std.log.scoped(.link_dsym); | |
| 8 | const macho = std.macho; | |
| 9 | const makeStaticString = MachO.makeStaticString; | |
| 10 | const math = std.math; | |
| 11 | const mem = std.mem; | |
| 12 | const Writer = std.Io.Writer; | |
| 13 | const Allocator = std.mem.Allocator; | |
| 14 | ||
| 15 | const link = @import("../../link.zig"); | |
| 16 | const MachO = @import("../MachO.zig"); | |
| 17 | const StringTable = @import("../StringTable.zig"); | |
| 18 | const Type = @import("../../Type.zig"); | |
| 19 | const trace = @import("../../tracy.zig").trace; | |
| 20 | const load_commands = @import("load_commands.zig"); | |
| 21 | const padToIdeal = MachO.padToIdeal; | |
| 22 | ||
| 23 | io: Io, | |
| 1 | 24 | allocator: Allocator, |
| 2 | file: ?fs.File, | |
| 25 | file: ?Io.File, | |
| 3 | 26 | |
| 4 | 27 | symtab_cmd: macho.symtab_command = .{}, |
| 5 | 28 | uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 }, |
| ... | ... | @@ -102,6 +125,7 @@ pub fn growSection( |
| 102 | 125 | requires_file_copy: bool, |
| 103 | 126 | macho_file: *MachO, |
| 104 | 127 | ) !void { |
| 128 | const io = self.io; | |
| 105 | 129 | const sect = self.getSectionPtr(sect_index); |
| 106 | 130 | |
| 107 | 131 | const allocated_size = self.allocatedSize(sect.offset); |
| ... | ... | @@ -111,25 +135,17 @@ pub fn growSection( |
| 111 | 135 | const new_offset = try self.findFreeSpace(needed_size, 1); |
| 112 | 136 | |
| 113 | 137 | log.debug("moving {s} section: {} bytes from 0x{x} to 0x{x}", .{ |
| 114 | sect.sectName(), | |
| 115 | existing_size, | |
| 116 | sect.offset, | |
| 117 | new_offset, | |
| 138 | sect.sectName(), existing_size, sect.offset, new_offset, | |
| 118 | 139 | }); |
| 119 | 140 | |
| 120 | 141 | if (requires_file_copy) { |
| 121 | const amt = try self.file.?.copyRangeAll( | |
| 122 | sect.offset, | |
| 123 | self.file.?, | |
| 124 | new_offset, | |
| 125 | existing_size, | |
| 126 | ); | |
| 127 | if (amt != existing_size) return error.InputOutput; | |
| 142 | const file = self.file.?; | |
| 143 | try link.File.copyRangeAll2(io, file, file, sect.offset, new_offset, existing_size); | |
| 128 | 144 | } |
| 129 | 145 | |
| 130 | 146 | sect.offset = @intCast(new_offset); |
| 131 | 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 | } |
| 134 | 150 | |
| 135 | 151 | sect.size = needed_size; |
| ... | ... | @@ -153,6 +169,7 @@ pub fn markDirty(self: *DebugSymbols, sect_index: u8, macho_file: *MachO) void { |
| 153 | 169 | } |
| 154 | 170 | |
| 155 | 171 | fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 { |
| 172 | const io = self.io; | |
| 156 | 173 | var at_end = true; |
| 157 | 174 | const end = start + padToIdeal(size); |
| 158 | 175 | |
| ... | ... | @@ -165,7 +182,7 @@ fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 { |
| 165 | 182 | } |
| 166 | 183 | } |
| 167 | 184 | |
| 168 | if (at_end) try self.file.?.setEndPos(end); | |
| 185 | if (at_end) try self.file.?.setLength(io, end); | |
| 169 | 186 | return null; |
| 170 | 187 | } |
| 171 | 188 | |
| ... | ... | @@ -179,6 +196,7 @@ fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64 |
| 179 | 196 | } |
| 180 | 197 | |
| 181 | 198 | pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void { |
| 199 | const io = self.io; | |
| 182 | 200 | const zo = macho_file.getZigObject().?; |
| 183 | 201 | for (self.relocs.items) |*reloc| { |
| 184 | 202 | const sym = zo.symbols.items[reloc.target]; |
| ... | ... | @@ -190,12 +208,9 @@ pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void { |
| 190 | 208 | const sect = &self.sections.items[self.debug_info_section_index.?]; |
| 191 | 209 | const file_offset = sect.offset + reloc.offset; |
| 192 | 210 | log.debug("resolving relocation: {d}@{x} ('{s}') at offset {x}", .{ |
| 193 | reloc.target, | |
| 194 | addr, | |
| 195 | sym_name, | |
| 196 | file_offset, | |
| 211 | reloc.target, addr, sym_name, file_offset, | |
| 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 | } |
| 200 | 215 | |
| 201 | 216 | self.finalizeDwarfSegment(macho_file); |
| ... | ... | @@ -208,7 +223,8 @@ pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void { |
| 208 | 223 | |
| 209 | 224 | pub fn deinit(self: *DebugSymbols) void { |
| 210 | 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 | 228 | self.segments.deinit(gpa); |
| 213 | 229 | self.sections.deinit(gpa); |
| 214 | 230 | self.relocs.deinit(gpa); |
| ... | ... | @@ -268,6 +284,7 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void { |
| 268 | 284 | } |
| 269 | 285 | |
| 270 | 286 | fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, usize } { |
| 287 | const io = self.io; | |
| 271 | 288 | const gpa = self.allocator; |
| 272 | 289 | const needed_size = load_commands.calcLoadCommandsSizeDsym(macho_file, self); |
| 273 | 290 | const buffer = try gpa.alloc(u8, needed_size); |
| ... | ... | @@ -319,12 +336,13 @@ fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, u |
| 319 | 336 | |
| 320 | 337 | assert(writer.end == needed_size); |
| 321 | 338 | |
| 322 | try self.file.?.pwriteAll(buffer, @sizeOf(macho.mach_header_64)); | |
| 339 | try self.file.?.writePositionalAll(io, buffer, @sizeOf(macho.mach_header_64)); | |
| 323 | 340 | |
| 324 | 341 | return .{ ncmds, buffer.len }; |
| 325 | 342 | } |
| 326 | 343 | |
| 327 | 344 | fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void { |
| 345 | const io = self.io; | |
| 328 | 346 | var header: macho.mach_header_64 = .{}; |
| 329 | 347 | header.filetype = macho.MH_DSYM; |
| 330 | 348 | |
| ... | ... | @@ -345,7 +363,7 @@ fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds |
| 345 | 363 | |
| 346 | 364 | log.debug("writing Mach-O header {}", .{header}); |
| 347 | 365 | |
| 348 | try self.file.?.pwriteAll(mem.asBytes(&header), 0); | |
| 366 | try self.file.?.writePositionalAll(io, mem.asBytes(&header), 0); | |
| 349 | 367 | } |
| 350 | 368 | |
| 351 | 369 | fn allocatedSize(self: *DebugSymbols, start: u64) u64 { |
| ... | ... | @@ -380,6 +398,8 @@ fn writeLinkeditSegmentData(self: *DebugSymbols, macho_file: *MachO) !void { |
| 380 | 398 | pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 { |
| 381 | 399 | const tracy = trace(@src()); |
| 382 | 400 | defer tracy.end(); |
| 401 | ||
| 402 | const io = self.io; | |
| 383 | 403 | const gpa = self.allocator; |
| 384 | 404 | const cmd = &self.symtab_cmd; |
| 385 | 405 | cmd.nsyms = macho_file.symtab_cmd.nsyms; |
| ... | ... | @@ -403,15 +423,16 @@ pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 { |
| 403 | 423 | internal.writeSymtab(macho_file, self); |
| 404 | 424 | } |
| 405 | 425 | |
| 406 | try self.file.?.pwriteAll(@ptrCast(self.symtab.items), cmd.symoff); | |
| 426 | try self.file.?.writePositionalAll(io, @ptrCast(self.symtab.items), cmd.symoff); | |
| 407 | 427 | |
| 408 | 428 | return off + cmd.nsyms * @sizeOf(macho.nlist_64); |
| 409 | 429 | } |
| 410 | 430 | |
| 411 | 431 | pub fn writeStrtab(self: *DebugSymbols, off: u32) !u32 { |
| 432 | const io = self.io; | |
| 412 | 433 | const cmd = &self.symtab_cmd; |
| 413 | 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 | 436 | return off + cmd.strsize; |
| 416 | 437 | } |
| 417 | 438 | |
| ... | ... | @@ -443,25 +464,3 @@ pub fn getSection(self: DebugSymbols, sect: u8) macho.section_64 { |
| 443 | 464 | assert(sect < self.sections.items.len); |
| 444 | 465 | return self.sections.items[sect]; |
| 445 | 466 | } |
| 446 | ||
| 447 | const DebugSymbols = @This(); | |
| 448 | ||
| 449 | const std = @import("std"); | |
| 450 | const build_options = @import("build_options"); | |
| 451 | const assert = std.debug.assert; | |
| 452 | const fs = std.fs; | |
| 453 | const link = @import("../../link.zig"); | |
| 454 | const load_commands = @import("load_commands.zig"); | |
| 455 | const log = std.log.scoped(.link_dsym); | |
| 456 | const macho = std.macho; | |
| 457 | const makeStaticString = MachO.makeStaticString; | |
| 458 | const math = std.math; | |
| 459 | const mem = std.mem; | |
| 460 | const padToIdeal = MachO.padToIdeal; | |
| 461 | const trace = @import("../../tracy.zig").trace; | |
| 462 | const Writer = std.Io.Writer; | |
| 463 | ||
| 464 | const Allocator = mem.Allocator; | |
| 465 | const MachO = @import("../MachO.zig"); | |
| 466 | const StringTable = @import("../StringTable.zig"); | |
| 467 | const Type = @import("../../Type.zig"); |
src/link/MachO/Dylib.zig+12-8| ... | ... | @@ -57,7 +57,9 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void { |
| 57 | 57 | const tracy = trace(@src()); |
| 58 | 58 | defer tracy.end(); |
| 59 | 59 | |
| 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 | 63 | const file = macho_file.getFileHandle(self.file_handle); |
| 62 | 64 | const offset = self.offset; |
| 63 | 65 | |
| ... | ... | @@ -65,7 +67,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void { |
| 65 | 67 | |
| 66 | 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 | 71 | if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput; |
| 70 | 72 | } |
| 71 | 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 | 88 | const lc_buffer = try gpa.alloc(u8, header.sizeofcmds); |
| 87 | 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 | 92 | if (amt != lc_buffer.len) return error.InputOutput; |
| 91 | 93 | } |
| 92 | 94 | |
| ... | ... | @@ -103,7 +105,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void { |
| 103 | 105 | const dyld_cmd = cmd.cast(macho.dyld_info_command).?; |
| 104 | 106 | const data = try gpa.alloc(u8, dyld_cmd.export_size); |
| 105 | 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 | 109 | if (amt != data.len) return error.InputOutput; |
| 108 | 110 | try self.parseTrie(data, macho_file); |
| 109 | 111 | }, |
| ... | ... | @@ -111,7 +113,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void { |
| 111 | 113 | const ld_cmd = cmd.cast(macho.linkedit_data_command).?; |
| 112 | 114 | const data = try gpa.alloc(u8, ld_cmd.datasize); |
| 113 | 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 | 117 | if (amt != data.len) return error.InputOutput; |
| 116 | 118 | try self.parseTrie(data, macho_file); |
| 117 | 119 | }, |
| ... | ... | @@ -238,13 +240,15 @@ fn parseTbd(self: *Dylib, macho_file: *MachO) !void { |
| 238 | 240 | const tracy = trace(@src()); |
| 239 | 241 | defer tracy.end(); |
| 240 | 242 | |
| 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; | |
| 242 | 246 | |
| 243 | 247 | log.debug("parsing dylib from stub: {f}", .{self.path}); |
| 244 | 248 | |
| 245 | 249 | const file = macho_file.getFileHandle(self.file_handle); |
| 246 | var lib_stub = LibStub.loadFromFile(gpa, file) catch |err| { | |
| 247 | try macho_file.reportParseError2(self.index, "failed to parse TBD file: {s}", .{@errorName(err)}); | |
| 250 | var lib_stub = LibStub.loadFromFile(gpa, io, file) catch |err| { | |
| 251 | try macho_file.reportParseError2(self.index, "failed to parse TBD file: {t}", .{err}); | |
| 248 | 252 | return error.MalformedTbd; |
| 249 | 253 | }; |
| 250 | 254 | defer lib_stub.deinit(); |
src/link/MachO/Object.zig+91-62| ... | ... | @@ -1,3 +1,30 @@ |
| 1 | const Object = @This(); | |
| 2 | ||
| 3 | const trace = @import("../../tracy.zig").trace; | |
| 4 | const Archive = @import("Archive.zig"); | |
| 5 | const Atom = @import("Atom.zig"); | |
| 6 | const Dwarf = @import("Dwarf.zig"); | |
| 7 | const File = @import("file.zig").File; | |
| 8 | const MachO = @import("../MachO.zig"); | |
| 9 | const Relocation = @import("Relocation.zig"); | |
| 10 | const Symbol = @import("Symbol.zig"); | |
| 11 | const UnwindInfo = @import("UnwindInfo.zig"); | |
| 12 | ||
| 13 | const std = @import("std"); | |
| 14 | const Io = std.Io; | |
| 15 | const Writer = std.Io.Writer; | |
| 16 | const assert = std.debug.assert; | |
| 17 | const log = std.log.scoped(.link); | |
| 18 | const macho = std.macho; | |
| 19 | const LoadCommandIterator = macho.LoadCommandIterator; | |
| 20 | const math = std.math; | |
| 21 | const mem = std.mem; | |
| 22 | const Allocator = std.mem.Allocator; | |
| 23 | ||
| 24 | const eh_frame = @import("eh_frame.zig"); | |
| 25 | const Cie = eh_frame.Cie; | |
| 26 | const Fde = eh_frame.Fde; | |
| 27 | ||
| 1 | 28 | /// Non-zero for fat object files or archives |
| 2 | 29 | offset: u64, |
| 3 | 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 | 102 | |
| 76 | 103 | log.debug("parsing {f}", .{self.fmtPath()}); |
| 77 | 104 | |
| 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 | 108 | const handle = macho_file.getFileHandle(self.file_handle); |
| 80 | 109 | const cpu_arch = macho_file.getTarget().cpu.arch; |
| 81 | 110 | |
| ... | ... | @@ -84,7 +113,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { |
| 84 | 113 | |
| 85 | 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 | 117 | if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput; |
| 89 | 118 | } |
| 90 | 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 | 134 | const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds); |
| 106 | 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 | 138 | if (amt != self.header.?.sizeofcmds) return error.InputOutput; |
| 110 | 139 | } |
| 111 | 140 | |
| ... | ... | @@ -129,14 +158,14 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { |
| 129 | 158 | const cmd = lc.cast(macho.symtab_command).?; |
| 130 | 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 | 162 | if (amt != self.strtab.items.len) return error.InputOutput; |
| 134 | 163 | } |
| 135 | 164 | |
| 136 | 165 | const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64)); |
| 137 | 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 | 169 | if (amt != symtab_buffer.len) return error.InputOutput; |
| 141 | 170 | } |
| 142 | 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 | 183 | const buffer = try gpa.alloc(u8, cmd.datasize); |
| 155 | 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 | 187 | if (amt != buffer.len) return error.InputOutput; |
| 159 | 188 | } |
| 160 | 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 | 469 | const tracy = trace(@src()); |
| 441 | 470 | defer tracy.end(); |
| 442 | 471 | |
| 472 | const comp = macho_file.base.comp; | |
| 473 | const io = comp.io; | |
| 443 | 474 | const slice = self.sections.slice(); |
| 444 | 475 | |
| 445 | 476 | for (slice.items(.header), 0..) |sect, n_sect| { |
| 446 | 477 | if (!isCstringLiteral(sect)) continue; |
| 447 | 478 | |
| 448 | const data = try self.readSectionData(allocator, file, @intCast(n_sect)); | |
| 479 | const data = try self.readSectionData(allocator, io, file, @intCast(n_sect)); | |
| 449 | 480 | defer allocator.free(data); |
| 450 | 481 | |
| 451 | 482 | var count: u32 = 0; |
| ... | ... | @@ -628,7 +659,9 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO |
| 628 | 659 | const tracy = trace(@src()); |
| 629 | 660 | defer tracy.end(); |
| 630 | 661 | |
| 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 | 665 | const file = macho_file.getFileHandle(self.file_handle); |
| 633 | 666 | |
| 634 | 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 | 680 | const slice = self.sections.slice(); |
| 648 | 681 | for (slice.items(.header), slice.items(.subsections), 0..) |header, subs, n_sect| { |
| 649 | 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 | 684 | defer gpa.free(data); |
| 652 | 685 | |
| 653 | 686 | for (subs.items) |sub| { |
| ... | ... | @@ -682,7 +715,7 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO |
| 682 | 715 | buffer.resize(target_size) catch unreachable; |
| 683 | 716 | const gop = try sections_data.getOrPut(target.n_sect); |
| 684 | 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 | 720 | const data = gop.value_ptr.*; |
| 688 | 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 | 1070 | const sect = slice.items(.header)[sect_id]; |
| 1038 | 1071 | const relocs = slice.items(.relocs)[sect_id]; |
| 1039 | 1072 | |
| 1073 | const comp = macho_file.base.comp; | |
| 1074 | const io = comp.io; | |
| 1040 | 1075 | const size = try macho_file.cast(usize, sect.size); |
| 1041 | 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 | 1078 | if (amt != self.eh_frame_data.items.len) return error.InputOutput; |
| 1044 | 1079 | |
| 1045 | 1080 | // 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 | 1173 | } |
| 1139 | 1174 | }; |
| 1140 | 1175 | |
| 1176 | const comp = macho_file.base.comp; | |
| 1177 | const io = comp.io; | |
| 1141 | 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 | 1180 | defer allocator.free(data); |
| 1144 | 1181 | |
| 1145 | 1182 | const nrecs = @divExact(data.len, @sizeOf(macho.compact_unwind_entry)); |
| ... | ... | @@ -1348,7 +1385,9 @@ fn parseDebugInfo(self: *Object, macho_file: *MachO) !void { |
| 1348 | 1385 | const tracy = trace(@src()); |
| 1349 | 1386 | defer tracy.end(); |
| 1350 | 1387 | |
| 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 | 1391 | const file = macho_file.getFileHandle(self.file_handle); |
| 1353 | 1392 | |
| 1354 | 1393 | var dwarf: Dwarf = .{}; |
| ... | ... | @@ -1358,18 +1397,18 @@ fn parseDebugInfo(self: *Object, macho_file: *MachO) !void { |
| 1358 | 1397 | const n_sect: u8 = @intCast(index); |
| 1359 | 1398 | if (sect.attrs() & macho.S_ATTR_DEBUG == 0) continue; |
| 1360 | 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 | 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 | 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 | 1408 | // __debug_str_offs[ets] section is a new addition in DWARFv5 and is generally |
| 1370 | 1409 | // required in order to correctly parse strings. |
| 1371 | 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 | } |
| 1375 | 1414 | |
| ... | ... | @@ -1611,12 +1650,14 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void { |
| 1611 | 1650 | const tracy = trace(@src()); |
| 1612 | 1651 | defer tracy.end(); |
| 1613 | 1652 | |
| 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 | 1656 | const handle = macho_file.getFileHandle(self.file_handle); |
| 1616 | 1657 | |
| 1617 | 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 | 1661 | if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput; |
| 1621 | 1662 | } |
| 1622 | 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 | 1678 | const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds); |
| 1638 | 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 | 1682 | if (amt != self.header.?.sizeofcmds) return error.InputOutput; |
| 1642 | 1683 | } |
| 1643 | 1684 | |
| ... | ... | @@ -1647,14 +1688,14 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void { |
| 1647 | 1688 | const cmd = lc.cast(macho.symtab_command).?; |
| 1648 | 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 | 1692 | if (amt != self.strtab.items.len) return error.InputOutput; |
| 1652 | 1693 | } |
| 1653 | 1694 | |
| 1654 | 1695 | const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64)); |
| 1655 | 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 | 1699 | if (amt != symtab_buffer.len) return error.InputOutput; |
| 1659 | 1700 | } |
| 1660 | 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 | 1730 | } |
| 1690 | 1731 | |
| 1691 | 1732 | pub fn updateArSize(self: *Object, macho_file: *MachO) !void { |
| 1733 | const comp = macho_file.base.comp; | |
| 1734 | const io = comp.io; | |
| 1692 | 1735 | self.output_ar_state.size = if (self.in_archive) |ar| ar.size else size: { |
| 1693 | 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 | } |
| 1697 | 1740 | |
| 1698 | pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writer: anytype) !void { | |
| 1741 | pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writer: *Writer) !void { | |
| 1699 | 1742 | // Header |
| 1700 | 1743 | const size = try macho_file.cast(usize, self.output_ar_state.size); |
| 1701 | 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 | 1746 | // Data |
| 1704 | 1747 | const file = macho_file.getFileHandle(self.file_handle); |
| 1705 | 1748 | // 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 | 1752 | const data = try gpa.alloc(u8, size); |
| 1708 | 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 | 1755 | if (amt != size) return error.InputOutput; |
| 1711 | 1756 | try writer.writeAll(data); |
| 1712 | 1757 | } |
| ... | ... | @@ -1811,7 +1856,9 @@ pub fn writeAtoms(self: *Object, macho_file: *MachO) !void { |
| 1811 | 1856 | const tracy = trace(@src()); |
| 1812 | 1857 | defer tracy.end(); |
| 1813 | 1858 | |
| 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 | 1862 | const headers = self.sections.items(.header); |
| 1816 | 1863 | const sections_data = try gpa.alloc([]const u8, headers.len); |
| 1817 | 1864 | defer { |
| ... | ... | @@ -1827,7 +1874,7 @@ pub fn writeAtoms(self: *Object, macho_file: *MachO) !void { |
| 1827 | 1874 | if (header.isZerofill()) continue; |
| 1828 | 1875 | const size = try macho_file.cast(usize, header.size); |
| 1829 | 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 | 1878 | if (amt != data.len) return error.InputOutput; |
| 1832 | 1879 | sections_data[n_sect] = data; |
| 1833 | 1880 | } |
| ... | ... | @@ -1850,7 +1897,9 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void { |
| 1850 | 1897 | const tracy = trace(@src()); |
| 1851 | 1898 | defer tracy.end(); |
| 1852 | 1899 | |
| 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 | 1903 | const headers = self.sections.items(.header); |
| 1855 | 1904 | const sections_data = try gpa.alloc([]const u8, headers.len); |
| 1856 | 1905 | defer { |
| ... | ... | @@ -1866,7 +1915,7 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void { |
| 1866 | 1915 | if (header.isZerofill()) continue; |
| 1867 | 1916 | const size = try macho_file.cast(usize, header.size); |
| 1868 | 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 | 1919 | if (amt != data.len) return error.InputOutput; |
| 1871 | 1920 | sections_data[n_sect] = data; |
| 1872 | 1921 | } |
| ... | ... | @@ -2482,11 +2531,11 @@ pub fn getUnwindRecord(self: *Object, index: UnwindInfo.Record.Index) *UnwindInf |
| 2482 | 2531 | } |
| 2483 | 2532 | |
| 2484 | 2533 | /// Caller owns the memory. |
| 2485 | pub fn readSectionData(self: Object, allocator: Allocator, file: File.Handle, n_sect: u8) ![]u8 { | |
| 2534 | pub fn readSectionData(self: Object, allocator: Allocator, io: Io, file: File.Handle, n_sect: u8) ![]u8 { | |
| 2486 | 2535 | const header = self.sections.items(.header)[n_sect]; |
| 2487 | 2536 | const size = math.cast(usize, header.size) orelse return error.Overflow; |
| 2488 | 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 | 2539 | errdefer allocator.free(data); |
| 2491 | 2540 | if (amt != data.len) return error.InputOutput; |
| 2492 | 2541 | return data; |
| ... | ... | @@ -2710,15 +2759,17 @@ const x86_64 = struct { |
| 2710 | 2759 | handle: File.Handle, |
| 2711 | 2760 | macho_file: *MachO, |
| 2712 | 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; | |
| 2714 | 2765 | |
| 2715 | 2766 | const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info)); |
| 2716 | 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 | 2769 | if (amt != relocs_buffer.len) return error.InputOutput; |
| 2719 | 2770 | const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(relocs_buffer.ptr))[0..sect.nreloc]; |
| 2720 | 2771 | |
| 2721 | const code = try self.readSectionData(gpa, handle, n_sect); | |
| 2772 | const code = try self.readSectionData(gpa, io, handle, n_sect); | |
| 2722 | 2773 | defer gpa.free(code); |
| 2723 | 2774 | |
| 2724 | 2775 | try out.ensureTotalCapacityPrecise(gpa, relocs.len); |
| ... | ... | @@ -2877,15 +2928,17 @@ const aarch64 = struct { |
| 2877 | 2928 | handle: File.Handle, |
| 2878 | 2929 | macho_file: *MachO, |
| 2879 | 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; | |
| 2881 | 2934 | |
| 2882 | 2935 | const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info)); |
| 2883 | 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 | 2938 | if (amt != relocs_buffer.len) return error.InputOutput; |
| 2886 | 2939 | const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(relocs_buffer.ptr))[0..sect.nreloc]; |
| 2887 | 2940 | |
| 2888 | const code = try self.readSectionData(gpa, handle, n_sect); | |
| 2941 | const code = try self.readSectionData(gpa, io, handle, n_sect); | |
| 2889 | 2942 | defer gpa.free(code); |
| 2890 | 2943 | |
| 2891 | 2944 | try out.ensureTotalCapacityPrecise(gpa, relocs.len); |
| ... | ... | @@ -3061,27 +3114,3 @@ const aarch64 = struct { |
| 3061 | 3114 | } |
| 3062 | 3115 | } |
| 3063 | 3116 | }; |
| 3064 | ||
| 3065 | const std = @import("std"); | |
| 3066 | const assert = std.debug.assert; | |
| 3067 | const log = std.log.scoped(.link); | |
| 3068 | const macho = std.macho; | |
| 3069 | const math = std.math; | |
| 3070 | const mem = std.mem; | |
| 3071 | const Allocator = std.mem.Allocator; | |
| 3072 | const Writer = std.Io.Writer; | |
| 3073 | ||
| 3074 | const eh_frame = @import("eh_frame.zig"); | |
| 3075 | const trace = @import("../../tracy.zig").trace; | |
| 3076 | const Archive = @import("Archive.zig"); | |
| 3077 | const Atom = @import("Atom.zig"); | |
| 3078 | const Cie = eh_frame.Cie; | |
| 3079 | const Dwarf = @import("Dwarf.zig"); | |
| 3080 | const Fde = eh_frame.Fde; | |
| 3081 | const File = @import("file.zig").File; | |
| 3082 | const LoadCommandIterator = macho.LoadCommandIterator; | |
| 3083 | const MachO = @import("../MachO.zig"); | |
| 3084 | const Object = @This(); | |
| 3085 | const Relocation = @import("Relocation.zig"); | |
| 3086 | const Symbol = @import("Symbol.zig"); | |
| 3087 | const 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 | 171 | const isec = atom.getInputSection(macho_file); |
| 172 | 172 | assert(!isec.isZerofill()); |
| 173 | 173 | |
| 174 | const comp = macho_file.base.comp; | |
| 175 | const io = comp.io; | |
| 176 | ||
| 174 | 177 | switch (isec.type()) { |
| 175 | 178 | macho.S_THREAD_LOCAL_REGULAR => { |
| 176 | 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 | 185 | else => { |
| 183 | 186 | const sect = macho_file.sections.items(.header)[atom.out_n_sect]; |
| 184 | 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 | 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 | 293 | /// We need this so that we can write to an archive. |
| 291 | 294 | /// TODO implement writing ZigObject data directly to a buffer instead. |
| 292 | 295 | pub 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 | 300 | // Size of the output object file is always the offset + size of the strtab |
| 295 | 301 | const size = macho_file.symtab_cmd.stroff + macho_file.symtab_cmd.strsize; |
| 296 | const gpa = macho_file.base.comp.gpa; | |
| 297 | 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 | 304 | return diags.fail("failed to read output file: {s}", .{@errorName(err)}); |
| 300 | 305 | if (amt != size) |
| 301 | 306 | return diags.fail("unexpected EOF reading from output file", .{}); |
| ... | ... | @@ -945,6 +950,8 @@ fn updateNavCode( |
| 945 | 950 | ) link.File.UpdateNavError!void { |
| 946 | 951 | const zcu = pt.zcu; |
| 947 | 952 | const gpa = zcu.gpa; |
| 953 | const comp = zcu.comp; | |
| 954 | const io = comp.io; | |
| 948 | 955 | const ip = &zcu.intern_pool; |
| 949 | 956 | const nav = ip.getNav(nav_index); |
| 950 | 957 | |
| ... | ... | @@ -1012,8 +1019,8 @@ fn updateNavCode( |
| 1012 | 1019 | |
| 1013 | 1020 | if (!sect.isZerofill()) { |
| 1014 | 1021 | const file_offset = sect.offset + atom.value; |
| 1015 | macho_file.base.file.?.pwriteAll(code, file_offset) catch |err| | |
| 1016 | return macho_file.base.cgFail(nav_index, "failed to write output file: {s}", .{@errorName(err)}); | |
| 1022 | macho_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err| | |
| 1023 | return macho_file.base.cgFail(nav_index, "failed to write output file: {t}", .{err}); | |
| 1017 | 1024 | } |
| 1018 | 1025 | } |
| 1019 | 1026 | |
| ... | ... | @@ -1493,7 +1500,7 @@ fn writeTrampoline(tr_sym: Symbol, target: Symbol, macho_file: *MachO) !void { |
| 1493 | 1500 | .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf), |
| 1494 | 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 | } |
| 1498 | 1505 | |
| 1499 | 1506 | pub fn getOrCreateMetadataForNav( |
src/link/MachO/fat.zig+10-8| ... | ... | @@ -1,20 +1,22 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 3 | ||
| 1 | 4 | const std = @import("std"); |
| 5 | const Io = std.Io; | |
| 2 | 6 | const assert = std.debug.assert; |
| 3 | const builtin = @import("builtin"); | |
| 4 | 7 | const log = std.log.scoped(.macho); |
| 5 | 8 | const macho = std.macho; |
| 6 | 9 | const mem = std.mem; |
| 7 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 8 | 10 | |
| 9 | 11 | const MachO = @import("../MachO.zig"); |
| 10 | 12 | |
| 11 | pub fn readFatHeader(file: std.fs.File) !macho.fat_header { | |
| 12 | return readFatHeaderGeneric(macho.fat_header, file, 0); | |
| 13 | pub fn readFatHeader(io: Io, file: Io.File) !macho.fat_header { | |
| 14 | return readFatHeaderGeneric(io, macho.fat_header, file, 0); | |
| 13 | 15 | } |
| 14 | 16 | |
| 15 | fn readFatHeaderGeneric(comptime Hdr: type, file: std.fs.File, offset: usize) !Hdr { | |
| 17 | fn readFatHeaderGeneric(io: Io, comptime Hdr: type, file: Io.File, offset: usize) !Hdr { | |
| 16 | 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 | 20 | if (nread != buffer.len) return error.InputOutput; |
| 19 | 21 | var hdr = @as(*align(1) const Hdr, @ptrCast(&buffer)).*; |
| 20 | 22 | mem.byteSwapAllFields(Hdr, &hdr); |
| ... | ... | @@ -27,12 +29,12 @@ pub const Arch = struct { |
| 27 | 29 | size: u32, |
| 28 | 30 | }; |
| 29 | 31 | |
| 30 | pub fn parseArchs(file: std.fs.File, fat_header: macho.fat_header, out: *[2]Arch) ![]const Arch { | |
| 32 | pub fn parseArchs(io: Io, file: Io.File, fat_header: macho.fat_header, out: *[2]Arch) ![]const Arch { | |
| 31 | 33 | var count: usize = 0; |
| 32 | 34 | var fat_arch_index: u32 = 0; |
| 33 | 35 | while (fat_arch_index < fat_header.nfat_arch and count < out.len) : (fat_arch_index += 1) { |
| 34 | 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 | 38 | // If we come across an architecture that we do not know how to handle, that's |
| 37 | 39 | // fine because we can keep looking for one that might match. |
| 38 | 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 | 355 | dylib: Dylib, |
| 356 | 356 | }; |
| 357 | 357 | |
| 358 | pub const Handle = std.fs.File; | |
| 358 | pub const Handle = Io.File; | |
| 359 | 359 | pub const HandleIndex = Index; |
| 360 | 360 | }; |
| 361 | 361 | |
| 362 | 362 | const std = @import("std"); |
| 363 | const Io = std.Io; | |
| 363 | 364 | const assert = std.debug.assert; |
| 364 | 365 | const log = std.log.scoped(.link); |
| 365 | 366 | const macho = std.macho; |
src/link/MachO/hasher.zig+20-28| ... | ... | @@ -1,34 +1,36 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Io = std.Io; | |
| 3 | const assert = std.debug.assert; | |
| 4 | const Allocator = std.mem.Allocator; | |
| 5 | ||
| 6 | const trace = @import("../../tracy.zig").trace; | |
| 7 | ||
| 1 | 8 | pub fn ParallelHasher(comptime Hasher: type) type { |
| 2 | 9 | const hash_size = Hasher.digest_length; |
| 3 | 10 | |
| 4 | 11 | return struct { |
| 5 | allocator: Allocator, | |
| 6 | io: std.Io, | |
| 7 | ||
| 8 | pub fn hash(self: Self, file: fs.File, out: [][hash_size]u8, opts: struct { | |
| 12 | pub fn hash(gpa: Allocator, io: Io, file: Io.File, out: [][hash_size]u8, opts: struct { | |
| 9 | 13 | chunk_size: u64 = 0x4000, |
| 10 | 14 | max_file_size: ?u64 = null, |
| 11 | 15 | }) !void { |
| 12 | 16 | const tracy = trace(@src()); |
| 13 | 17 | defer tracy.end(); |
| 14 | 18 | |
| 15 | const io = self.io; | |
| 16 | ||
| 17 | 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 | 21 | break :blk std.math.cast(usize, file_size) orelse return error.Overflow; |
| 20 | 22 | }; |
| 21 | 23 | const chunk_size = std.math.cast(usize, opts.chunk_size) orelse return error.Overflow; |
| 22 | 24 | |
| 23 | const buffer = try self.allocator.alloc(u8, chunk_size * out.len); | |
| 24 | defer self.allocator.free(buffer); | |
| 25 | const buffer = try gpa.alloc(u8, chunk_size * out.len); | |
| 26 | defer gpa.free(buffer); | |
| 25 | 27 | |
| 26 | const results = try self.allocator.alloc(fs.File.PReadError!usize, out.len); | |
| 27 | defer self.allocator.free(results); | |
| 28 | const results = try gpa.alloc(Io.File.ReadPositionalError!usize, out.len); | |
| 29 | defer gpa.free(results); | |
| 28 | 30 | |
| 29 | 31 | { |
| 30 | var group: std.Io.Group = .init; | |
| 31 | errdefer group.cancel(io); | |
| 32 | var group: Io.Group = .init; | |
| 33 | defer group.cancel(io); | |
| 32 | 34 | |
| 33 | 35 | for (out, results, 0..) |*out_buf, *result, i| { |
| 34 | 36 | const fstart = i * chunk_size; |
| ... | ... | @@ -37,6 +39,7 @@ pub fn ParallelHasher(comptime Hasher: type) type { |
| 37 | 39 | else |
| 38 | 40 | chunk_size; |
| 39 | 41 | group.async(io, worker, .{ |
| 42 | io, | |
| 40 | 43 | file, |
| 41 | 44 | fstart, |
| 42 | 45 | buffer[fstart..][0..fsize], |
| ... | ... | @@ -51,26 +54,15 @@ pub fn ParallelHasher(comptime Hasher: type) type { |
| 51 | 54 | } |
| 52 | 55 | |
| 53 | 56 | fn worker( |
| 54 | file: fs.File, | |
| 57 | io: Io, | |
| 58 | file: Io.File, | |
| 55 | 59 | fstart: usize, |
| 56 | 60 | buffer: []u8, |
| 57 | 61 | out: *[hash_size]u8, |
| 58 | err: *fs.File.PReadError!usize, | |
| 62 | err: *Io.File.ReadPositionalError!usize, | |
| 59 | 63 | ) void { |
| 60 | const tracy = trace(@src()); | |
| 61 | defer tracy.end(); | |
| 62 | err.* = file.preadAll(buffer, fstart); | |
| 64 | err.* = file.readPositionalAll(io, buffer, fstart); | |
| 63 | 65 | Hasher.hash(buffer, out, .{}); |
| 64 | 66 | } |
| 65 | ||
| 66 | const Self = @This(); | |
| 67 | 67 | }; |
| 68 | 68 | } |
| 69 | ||
| 70 | const assert = std.debug.assert; | |
| 71 | const fs = std.fs; | |
| 72 | const mem = std.mem; | |
| 73 | const std = @import("std"); | |
| 74 | const trace = @import("../../tracy.zig").trace; | |
| 75 | ||
| 76 | const Allocator = mem.Allocator; |
src/link/MachO/relocatable.zig+15-15| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 | pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void { |
| 2 | const gpa = macho_file.base.comp.gpa; | |
| 3 | const diags = &macho_file.base.comp.link_diags; | |
| 2 | const gpa = comp.gpa; | |
| 3 | const io = comp.io; | |
| 4 | const diags = &comp.link_diags; | |
| 4 | 5 | |
| 5 | 6 | // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list. |
| 6 | 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 | 10 | positionals.appendSliceAssumeCapacity(comp.link_inputs); |
| 10 | 11 | |
| 11 | 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 | } |
| 14 | 15 | |
| 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)); | |
| 16 | 17 | |
| 17 | 18 | if (macho_file.getZigObject() == null and positionals.items.len == 1) { |
| 18 | 19 | // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all |
| 19 | 20 | // debug info segments/sections (this is apparently by design by Apple), we copy |
| 20 | 21 | // the *only* input file over. |
| 21 | 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 | 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 | 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 | return diags.fail("failed to copy range of file {f}: {s}", .{ path, @errorName(err) }); | |
| 28 | if (amt != stat.size) | |
| 29 | return diags.fail("unexpected short write in copy range of file {f}", .{path}); | |
| 27 | link.File.copyRangeAll2(io, in_file, macho_file.base.file.?, 0, 0, stat.size) catch |err| | |
| 28 | return diags.fail("failed to copy range of file {f}: {t}", .{ path, err }); | |
| 30 | 29 | return; |
| 31 | 30 | } |
| 32 | 31 | |
| ... | ... | @@ -79,6 +78,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat |
| 79 | 78 | |
| 80 | 79 | pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void { |
| 81 | 80 | const gpa = comp.gpa; |
| 81 | const io = comp.io; | |
| 82 | 82 | const diags = &macho_file.base.comp.link_diags; |
| 83 | 83 | |
| 84 | 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 | 88 | positionals.appendSliceAssumeCapacity(comp.link_inputs); |
| 89 | 89 | |
| 90 | 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 | } |
| 93 | 93 | |
| 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)); | |
| 95 | 95 | |
| 96 | 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 | } |
| 99 | 99 | |
| 100 | 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 | } |
| 103 | 103 | |
| 104 | 104 | for (positionals.items) |link_input| { |
| ... | ... | @@ -229,7 +229,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? |
| 229 | 229 | |
| 230 | 230 | assert(writer.end == total_size); |
| 231 | 231 | |
| 232 | try macho_file.setEndPos(total_size); | |
| 232 | try macho_file.setLength(total_size); | |
| 233 | 233 | try macho_file.pwriteAll(writer.buffered(), 0); |
| 234 | 234 | |
| 235 | 235 | if (diags.hasErrors()) return error.LinkFailure; |
src/link/MachO/uuid.zig+17-16| ... | ... | @@ -1,28 +1,38 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Io = std.Io; | |
| 3 | const Md5 = std.crypto.hash.Md5; | |
| 4 | ||
| 5 | const trace = @import("../../tracy.zig").trace; | |
| 6 | const Compilation = @import("../../Compilation.zig"); | |
| 7 | const ParallelHasher = @import("hasher.zig").ParallelHasher; | |
| 8 | ||
| 1 | 9 | /// Calculates Md5 hash of each chunk in parallel and then hashes all Md5 hashes to produce |
| 2 | 10 | /// the final digest. |
| 3 | 11 | /// While this is NOT a correct MD5 hash of the contents, this methodology is used by LLVM/LLD |
| 4 | 12 | /// and we will use it too as it seems accepted by Apple OSes. |
| 5 | 13 | /// TODO LLD also hashes the output filename to disambiguate between same builds with different |
| 6 | 14 | /// output files. Should we also do that? |
| 7 | pub fn calcUuid(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void { | |
| 15 | pub fn calcUuid(comp: *const Compilation, file: Io.File, file_size: u64, out: *[Md5.digest_length]u8) !void { | |
| 8 | 16 | const tracy = trace(@src()); |
| 9 | 17 | defer tracy.end(); |
| 10 | 18 | |
| 19 | const gpa = comp.gpa; | |
| 20 | const io = comp.io; | |
| 21 | ||
| 11 | 22 | const chunk_size: usize = 1024 * 1024; |
| 12 | 23 | const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow; |
| 13 | 24 | const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks; |
| 14 | 25 | |
| 15 | const hashes = try comp.gpa.alloc([Md5.digest_length]u8, actual_num_chunks); | |
| 16 | defer comp.gpa.free(hashes); | |
| 26 | const hashes = try gpa.alloc([Md5.digest_length]u8, actual_num_chunks); | |
| 27 | defer gpa.free(hashes); | |
| 17 | 28 | |
| 18 | var hasher = Hasher(Md5){ .allocator = comp.gpa, .io = comp.io }; | |
| 19 | try hasher.hash(file, hashes, .{ | |
| 29 | try ParallelHasher(Md5).hash(gpa, io, file, hashes, .{ | |
| 20 | 30 | .chunk_size = chunk_size, |
| 21 | 31 | .max_file_size = file_size, |
| 22 | 32 | }); |
| 23 | 33 | |
| 24 | const final_buffer = try comp.gpa.alloc(u8, actual_num_chunks * Md5.digest_length); | |
| 25 | defer comp.gpa.free(final_buffer); | |
| 34 | const final_buffer = try gpa.alloc(u8, actual_num_chunks * Md5.digest_length); | |
| 35 | defer gpa.free(final_buffer); | |
| 26 | 36 | |
| 27 | 37 | for (hashes, 0..) |hash, i| { |
| 28 | 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 | 47 | out[6] = (out[6] & 0x0F) | (3 << 4); |
| 38 | 48 | out[8] = (out[8] & 0x3F) | 0x80; |
| 39 | 49 | } |
| 40 | ||
| 41 | const fs = std.fs; | |
| 42 | const mem = std.mem; | |
| 43 | const std = @import("std"); | |
| 44 | const trace = @import("../../tracy.zig").trace; | |
| 45 | ||
| 46 | const Compilation = @import("../../Compilation.zig"); | |
| 47 | const Md5 = std.crypto.hash.Md5; | |
| 48 | const Hasher = @import("hasher.zig").ParallelHasher; |
src/link/MappedFile.zig+61-26| ... | ... | @@ -1,3 +1,17 @@ |
| 1 | /// TODO add a mapped file abstraction to std.Io | |
| 2 | const MappedFile = @This(); | |
| 3 | ||
| 4 | const builtin = @import("builtin"); | |
| 5 | const is_linux = builtin.os.tag == .linux; | |
| 6 | const is_windows = builtin.os.tag == .windows; | |
| 7 | ||
| 8 | const std = @import("std"); | |
| 9 | const Io = std.Io; | |
| 10 | const assert = std.debug.assert; | |
| 11 | const linux = std.os.linux; | |
| 12 | const windows = std.os.windows; | |
| 13 | ||
| 14 | io: Io, | |
| 1 | 15 | file: std.Io.File, |
| 2 | 16 | flags: packed struct { |
| 3 | 17 | block_size: std.mem.Alignment, |
| ... | ... | @@ -16,16 +30,22 @@ writers: std.SinglyLinkedList, |
| 16 | 30 | |
| 17 | 31 | pub const growth_factor = 4; |
| 18 | 32 | |
| 19 | pub const Error = std.posix.MMapError || std.posix.MRemapError || std.fs.File.SetEndPosError || error{ | |
| 33 | pub const Error = std.posix.MMapError || std.posix.MRemapError || Io.File.LengthError || error{ | |
| 20 | 34 | NotFile, |
| 21 | 35 | SystemResources, |
| 22 | 36 | IsDir, |
| 23 | 37 | Unseekable, |
| 24 | 38 | NoSpaceLeft, |
| 39 | ||
| 40 | InputOutput, | |
| 41 | FileTooBig, | |
| 42 | FileBusy, | |
| 43 | NonResizable, | |
| 25 | 44 | }; |
| 26 | 45 | |
| 27 | pub fn init(file: std.Io.File, gpa: std.mem.Allocator) !MappedFile { | |
| 46 | pub fn init(file: std.Io.File, gpa: std.mem.Allocator, io: Io) !MappedFile { | |
| 28 | 47 | var mf: MappedFile = .{ |
| 48 | .io = io, | |
| 29 | 49 | .file = file, |
| 30 | 50 | .flags = undefined, |
| 31 | 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 | 75 | }; |
| 56 | 76 | } |
| 57 | 77 | if (is_linux) { |
| 58 | const statx = try linux.wrapped.statx( | |
| 59 | mf.file.handle, | |
| 60 | "", | |
| 61 | std.posix.AT.EMPTY_PATH, | |
| 62 | .{ .TYPE = true, .SIZE = true, .BLOCKS = true }, | |
| 63 | ); | |
| 64 | assert(statx.mask.TYPE); | |
| 65 | assert(statx.mask.SIZE); | |
| 66 | assert(statx.mask.BLOCKS); | |
| 67 | ||
| 68 | if (!std.posix.S.ISREG(statx.mode)) return error.PathAlreadyExists; | |
| 69 | break :stat .{ statx.size, @max(std.heap.pageSize(), statx.blksize) }; | |
| 78 | const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) | |
| 79 | .{ .major = 30, .minor = 0, .patch = 0 } | |
| 80 | else | |
| 81 | .{ .major = 2, .minor = 28, .patch = 0 }); | |
| 82 | const sys = if (use_c) std.c else std.os.linux; | |
| 83 | while (true) { | |
| 84 | var statx = std.mem.zeroes(linux.Statx); | |
| 85 | const rc = sys.statx( | |
| 86 | mf.file.handle, | |
| 87 | "", | |
| 88 | std.posix.AT.EMPTY_PATH, | |
| 89 | .{ .TYPE = true, .SIZE = true, .BLOCKS = true }, | |
| 90 | &statx, | |
| 91 | ); | |
| 92 | switch (sys.errno(rc)) { | |
| 93 | .SUCCESS => { | |
| 94 | assert(statx.mask.TYPE); | |
| 95 | assert(statx.mask.SIZE); | |
| 96 | assert(statx.mask.BLOCKS); | |
| 97 | if (!std.posix.S.ISREG(statx.mode)) return error.PathAlreadyExists; | |
| 98 | break :stat .{ statx.size, @max(std.heap.pageSize(), statx.blksize) }; | |
| 99 | }, | |
| 100 | .INTR => continue, | |
| 101 | .ACCES => return error.AccessDenied, | |
| 102 | .BADF => if (std.debug.runtime_safety) unreachable else return error.Unexpected, | |
| 103 | .FAULT => if (std.debug.runtime_safety) unreachable else return error.Unexpected, | |
| 104 | .INVAL => if (std.debug.runtime_safety) unreachable else return error.Unexpected, | |
| 105 | .LOOP => return error.SymLinkLoop, | |
| 106 | .NAMETOOLONG => return error.NameTooLong, | |
| 107 | .NOENT => return error.FileNotFound, | |
| 108 | .NOTDIR => return error.FileNotFound, | |
| 109 | .NOMEM => return error.SystemResources, | |
| 110 | else => |err| return std.posix.unexpectedErrno(err), | |
| 111 | } | |
| 112 | } | |
| 70 | 113 | } |
| 71 | 114 | const stat = try std.posix.fstat(mf.file.handle); |
| 72 | 115 | if (!std.posix.S.ISREG(stat.mode)) return error.PathAlreadyExists; |
| ... | ... | @@ -433,8 +476,8 @@ pub const Node = extern struct { |
| 433 | 476 | return n; |
| 434 | 477 | }, |
| 435 | 478 | .streaming, |
| 436 | .streaming_reading, | |
| 437 | .positional_reading, | |
| 479 | .streaming_simple, | |
| 480 | .positional_simple, | |
| 438 | 481 | .failure, |
| 439 | 482 | => { |
| 440 | 483 | const dest = limit.slice(interface.unusedCapacitySlice()); |
| ... | ... | @@ -612,13 +655,14 @@ pub fn addNodeAfter( |
| 612 | 655 | } |
| 613 | 656 | |
| 614 | 657 | fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested_size: u64) !void { |
| 658 | const io = mf.io; | |
| 615 | 659 | const node = ni.get(mf); |
| 616 | 660 | const old_offset, const old_size = node.location().resolve(mf); |
| 617 | 661 | const new_size = node.flags.alignment.forward(@intCast(requested_size)); |
| 618 | 662 | // Resize the entire file |
| 619 | 663 | if (ni == Node.Index.root) { |
| 620 | 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 | 666 | try mf.ensureTotalCapacity(@intCast(new_size)); |
| 623 | 667 | ni.setLocationAssumeCapacity(mf, old_offset, new_size); |
| 624 | 668 | return; |
| ... | ... | @@ -1059,12 +1103,3 @@ fn verifyNode(mf: *MappedFile, parent_ni: Node.Index) void { |
| 1059 | 1103 | ni = node.next; |
| 1060 | 1104 | } |
| 1061 | 1105 | } |
| 1062 | ||
| 1063 | const assert = std.debug.assert; | |
| 1064 | const builtin = @import("builtin"); | |
| 1065 | const is_linux = builtin.os.tag == .linux; | |
| 1066 | const is_windows = builtin.os.tag == .windows; | |
| 1067 | const linux = std.os.linux; | |
| 1068 | const MappedFile = @This(); | |
| 1069 | const std = @import("std"); | |
| 1070 | const windows = std.os.windows; |
src/link/Queue.zig+3-1| ... | ... | @@ -121,7 +121,7 @@ pub fn enqueueZcu( |
| 121 | 121 | link.doZcuTask(comp, tid, task); |
| 122 | 122 | } |
| 123 | 123 | |
| 124 | pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) void { | |
| 124 | pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) Io.Cancelable!void { | |
| 125 | 125 | if (q.future != null) { |
| 126 | 126 | q.prelink_queue.close(comp.io); |
| 127 | 127 | return; |
| ... | ... | @@ -136,6 +136,7 @@ pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) void { |
| 136 | 136 | } else |err| switch (err) { |
| 137 | 137 | error.OutOfMemory => comp.link_diags.setAllocFailure(), |
| 138 | 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 | 176 | lf.post_prelink = true; |
| 176 | 177 | } else |err| switch (err) { |
| 177 | 178 | error.OutOfMemory => comp.link_diags.setAllocFailure(), |
| 179 | error.Canceled => @panic("TODO"), | |
| 178 | 180 | error.LinkFailure => {}, |
| 179 | 181 | } |
| 180 | 182 | } |
src/link/SpirV.zig+5-3| ... | ... | @@ -33,6 +33,7 @@ pub fn createEmpty( |
| 33 | 33 | options: link.File.OpenOptions, |
| 34 | 34 | ) !*Linker { |
| 35 | 35 | const gpa = comp.gpa; |
| 36 | const io = comp.io; | |
| 36 | 37 | const target = &comp.root_mod.resolved_target.result; |
| 37 | 38 | |
| 38 | 39 | assert(!comp.config.use_lld); // Caught by Compilation.Config.resolve |
| ... | ... | @@ -78,7 +79,7 @@ pub fn createEmpty( |
| 78 | 79 | }; |
| 79 | 80 | errdefer linker.deinit(); |
| 80 | 81 | |
| 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 | 83 | .truncate = true, |
| 83 | 84 | .read = true, |
| 84 | 85 | }); |
| ... | ... | @@ -245,6 +246,7 @@ pub fn flush( |
| 245 | 246 | const comp = linker.base.comp; |
| 246 | 247 | const diags = &comp.link_diags; |
| 247 | 248 | const gpa = comp.gpa; |
| 249 | const io = comp.io; | |
| 248 | 250 | |
| 249 | 251 | // We need to export the list of error names somewhere so that we can pretty-print them in the |
| 250 | 252 | // 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 | 288 | }; |
| 287 | 289 | |
| 288 | 290 | // TODO endianness bug. use file writer and call writeSliceEndian instead |
| 289 | linker.base.file.?.writeAll(@ptrCast(linked_module)) catch |err| | |
| 290 | return diags.fail("failed to write: {s}", .{@errorName(err)}); | |
| 291 | linker.base.file.?.writeStreamingAll(io, @ptrCast(linked_module)) catch |err| | |
| 292 | return diags.fail("failed to write: {t}", .{err}); | |
| 291 | 293 | } |
| 292 | 294 | |
| 293 | 295 | fn 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 | 20 | const build_options = @import("build_options"); |
| 21 | 21 | |
| 22 | 22 | const std = @import("std"); |
| 23 | const Io = std.Io; | |
| 23 | 24 | const Allocator = std.mem.Allocator; |
| 24 | 25 | const Cache = std.Build.Cache; |
| 25 | 26 | const Path = Cache.Path; |
| ... | ... | @@ -428,7 +429,11 @@ pub const OutputFunctionIndex = enum(u32) { |
| 428 | 429 | |
| 429 | 430 | pub fn fromSymbolName(wasm: *const Wasm, name: String) OutputFunctionIndex { |
| 430 | 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 | }; |
| 434 | 439 | |
| ... | ... | @@ -2996,16 +3001,18 @@ pub fn createEmpty( |
| 2996 | 3001 | .named => |name| (try wasm.internString(name)).toOptional(), |
| 2997 | 3002 | }; |
| 2998 | 3003 | |
| 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 | 3007 | .truncate = true, |
| 3001 | 3008 | .read = true, |
| 3002 | .mode = if (fs.has_executable_bit) | |
| 3009 | .permissions = if (Io.File.Permissions.has_executable_bit) | |
| 3003 | 3010 | if (target.os.tag == .wasi and output_mode == .Exe) |
| 3004 | fs.File.default_mode | 0b001_000_000 | |
| 3011 | .executable_file | |
| 3005 | 3012 | else |
| 3006 | fs.File.default_mode | |
| 3013 | .default_file | |
| 3007 | 3014 | else |
| 3008 | 0, | |
| 3015 | .default_file, | |
| 3009 | 3016 | }); |
| 3010 | 3017 | wasm.name = emit.sub_path; |
| 3011 | 3018 | |
| ... | ... | @@ -3013,14 +3020,16 @@ pub fn createEmpty( |
| 3013 | 3020 | } |
| 3014 | 3021 | |
| 3015 | 3022 | fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void { |
| 3016 | const diags = &wasm.base.comp.link_diags; | |
| 3017 | const obj = link.openObject(path, false, false) catch |err| { | |
| 3018 | switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) { | |
| 3023 | const comp = wasm.base.comp; | |
| 3024 | const io = comp.io; | |
| 3025 | const diags = &comp.link_diags; | |
| 3026 | const obj = link.openObject(io, path, false, false) catch |err| { | |
| 3027 | switch (diags.failParse(path, "failed to open object: {t}", .{err})) { | |
| 3019 | 3028 | error.LinkFailure => return, |
| 3020 | 3029 | } |
| 3021 | 3030 | }; |
| 3022 | 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 | 3033 | error.LinkFailure => return, |
| 3025 | 3034 | } |
| 3026 | 3035 | }; |
| ... | ... | @@ -3032,7 +3041,7 @@ fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void { |
| 3032 | 3041 | const io = wasm.base.comp.io; |
| 3033 | 3042 | const gc_sections = wasm.base.gc_sections; |
| 3034 | 3043 | |
| 3035 | defer obj.file.close(); | |
| 3044 | defer obj.file.close(io); | |
| 3036 | 3045 | |
| 3037 | 3046 | var file_reader = obj.file.reader(io, &.{}); |
| 3038 | 3047 | |
| ... | ... | @@ -3060,7 +3069,7 @@ fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void { |
| 3060 | 3069 | const io = wasm.base.comp.io; |
| 3061 | 3070 | const gc_sections = wasm.base.gc_sections; |
| 3062 | 3071 | |
| 3063 | defer obj.file.close(); | |
| 3072 | defer obj.file.close(io); | |
| 3064 | 3073 | |
| 3065 | 3074 | var file_reader = obj.file.reader(io, &.{}); |
| 3066 | 3075 | |
| ... | ... | @@ -3529,7 +3538,10 @@ pub fn markFunctionImport( |
| 3529 | 3538 | import: *FunctionImport, |
| 3530 | 3539 | func_index: FunctionImport.Index, |
| 3531 | 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 | 3545 | import.flags.alive = true; |
| 3534 | 3546 | |
| 3535 | 3547 | const comp = wasm.base.comp; |
| ... | ... | @@ -3549,8 +3561,9 @@ pub fn markFunctionImport( |
| 3549 | 3561 | } else { |
| 3550 | 3562 | try wasm.function_imports.put(gpa, name, .fromObject(func_index, wasm)); |
| 3551 | 3563 | } |
| 3552 | } else { | |
| 3553 | try markFunction(wasm, import.resolution.unpack(wasm).object_function, import.flags.exported); | |
| 3564 | } else switch (import.resolution.unpack(wasm)) { | |
| 3565 | .object_function => try markFunction(wasm, import.resolution.unpack(wasm).object_function, import.flags.exported), | |
| 3566 | else => return, | |
| 3554 | 3567 | } |
| 3555 | 3568 | } |
| 3556 | 3569 | |
| ... | ... | @@ -3589,7 +3602,10 @@ fn markGlobalImport( |
| 3589 | 3602 | import: *GlobalImport, |
| 3590 | 3603 | global_index: GlobalImport.Index, |
| 3591 | 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 | 3609 | import.flags.alive = true; |
| 3594 | 3610 | |
| 3595 | 3611 | const comp = wasm.base.comp; |
| ... | ... | @@ -3619,8 +3635,9 @@ fn markGlobalImport( |
| 3619 | 3635 | } else { |
| 3620 | 3636 | try wasm.global_imports.put(gpa, name, .fromObject(global_index, wasm)); |
| 3621 | 3637 | } |
| 3622 | } else { | |
| 3623 | try markGlobal(wasm, import.resolution.unpack(wasm).object_global, import.flags.exported); | |
| 3638 | } else switch (import.resolution.unpack(wasm)) { | |
| 3639 | .object_global => try markGlobal(wasm, import.resolution.unpack(wasm).object_global, import.flags.exported), | |
| 3640 | else => return, | |
| 3624 | 3641 | } |
| 3625 | 3642 | } |
| 3626 | 3643 | |
| ... | ... | @@ -3823,8 +3840,9 @@ pub fn flush( |
| 3823 | 3840 | const comp = wasm.base.comp; |
| 3824 | 3841 | const diags = &comp.link_diags; |
| 3825 | 3842 | const gpa = comp.gpa; |
| 3843 | const io = comp.io; | |
| 3826 | 3844 | |
| 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); | |
| 3828 | 3846 | |
| 3829 | 3847 | if (wasm.base.zcu_object_basename) |raw| { |
| 3830 | 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 | 4055 | const comp = wasm.base.comp; |
| 4038 | 4056 | assert(comp.config.output_mode == .Obj); |
| 4039 | 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 | 4059 | const gop = try wasm.symbol_table.getOrPut(gpa, name); |
| 4042 | 4060 | gop.value_ptr.* = {}; |
| 4043 | 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 | 108 | |
| 109 | 109 | pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 110 | 110 | const comp = wasm.base.comp; |
| 111 | const io = comp.io; | |
| 111 | 112 | const shared_memory = comp.config.shared_memory; |
| 112 | 113 | const diags = &comp.link_diags; |
| 113 | 114 | const gpa = comp.gpa; |
| ... | ... | @@ -127,17 +128,20 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 127 | 128 | if (comp.zcu) |zcu| { |
| 128 | 129 | const ip: *const InternPool = &zcu.intern_pool; // No mutations allowed! |
| 129 | 130 | |
| 131 | log.debug("total MIR instructions: {d}", .{wasm.mir_instructions.len}); | |
| 132 | ||
| 130 | 133 | // Detect any intrinsics that were called; they need to have dependencies on the symbols marked. |
| 131 | 134 | // Likewise detect `@tagName` calls so those functions can be included in the output and synthesized. |
| 132 | 135 | for (wasm.mir_instructions.items(.tag), wasm.mir_instructions.items(.data)) |tag, *data| switch (tag) { |
| 133 | 136 | .call_intrinsic => { |
| 134 | 137 | const symbol_name = try wasm.internString(@tagName(data.intrinsic)); |
| 135 | 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)", .{ | |
| 137 | @tagName(data.intrinsic), | |
| 139 | return diags.fail("missing compiler runtime intrinsic '{t}' (undefined linker symbol)", .{ | |
| 140 | data.intrinsic, | |
| 138 | 141 | }); |
| 139 | 142 | }); |
| 140 | 143 | try wasm.markFunctionImport(symbol_name, i.value(wasm), i); |
| 144 | log.debug("markFunctionImport intrinsic {d}={t}", .{ i, data.intrinsic }); | |
| 141 | 145 | }, |
| 142 | 146 | .call_tag_name => { |
| 143 | 147 | assert(ip.indexToKey(data.ip_index) == .enum_type); |
| ... | ... | @@ -146,11 +150,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 146 | 150 | wasm.tag_name_table_ref_count += 1; |
| 147 | 151 | const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu); |
| 148 | 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 | 154 | .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .slice_const_u8_sentinel_0, target), |
| 151 | 155 | .table_index = @intCast(wasm.tag_name_offs.items.len), |
| 152 | 156 | } }; |
| 153 | try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {}); | |
| 154 | 157 | const tag_names = ip.loadEnumType(data.ip_index).names; |
| 155 | 158 | for (tag_names.get(ip)) |tag_name| { |
| 156 | 159 | const slice = tag_name.toSlice(ip); |
| ... | ... | @@ -158,6 +161,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 158 | 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 | 166 | else => continue, |
| 163 | 167 | }; |
| ... | ... | @@ -1067,7 +1071,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 1067 | 1071 | } |
| 1068 | 1072 | |
| 1069 | 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 | 1075 | file_writer.interface.writeAll(binary_bytes.items) catch |err| switch (err) { |
| 1072 | 1076 | error.WriteFailed => return file_writer.err.?, |
| 1073 | 1077 | }; |
src/link/tapi.zig+7-7| ... | ... | @@ -1,10 +1,10 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const fs = std.fs; | |
| 2 | const Io = std.Io; | |
| 3 | 3 | const mem = std.mem; |
| 4 | 4 | const log = std.log.scoped(.tapi); |
| 5 | const yaml = @import("tapi/yaml.zig"); | |
| 5 | const Allocator = std.mem.Allocator; | |
| 6 | 6 | |
| 7 | const Allocator = mem.Allocator; | |
| 7 | const yaml = @import("tapi/yaml.zig"); | |
| 8 | 8 | const Yaml = yaml.Yaml; |
| 9 | 9 | |
| 10 | 10 | const VersionField = union(enum) { |
| ... | ... | @@ -130,7 +130,7 @@ pub const Tbd = union(enum) { |
| 130 | 130 | pub const TapiError = error{ |
| 131 | 131 | NotLibStub, |
| 132 | 132 | InputOutput, |
| 133 | } || yaml.YamlError || std.fs.File.PReadError; | |
| 133 | } || yaml.YamlError || Io.File.ReadPositionalError; | |
| 134 | 134 | |
| 135 | 135 | pub const LibStub = struct { |
| 136 | 136 | /// Underlying memory for stub's contents. |
| ... | ... | @@ -139,14 +139,14 @@ pub const LibStub = struct { |
| 139 | 139 | /// Typed contents of the tbd file. |
| 140 | 140 | inner: []Tbd, |
| 141 | 141 | |
| 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 | 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 | 145 | break :blk @min(stat.size, std.math.maxInt(u32)); |
| 146 | 146 | }; |
| 147 | 147 | const source = try allocator.alloc(u8, filesize); |
| 148 | 148 | defer allocator.free(source); |
| 149 | const amt = try file.preadAll(source, 0); | |
| 149 | const amt = try file.readPositionalAll(io, source, 0); | |
| 150 | 150 | if (amt != filesize) return error.InputOutput; |
| 151 | 151 | |
| 152 | 152 | var lib_stub = LibStub{ |
src/main.zig+255-244| ... | ... | @@ -162,17 +162,20 @@ var debug_allocator: std.heap.DebugAllocator(.{ |
| 162 | 162 | .stack_trace_frames = build_options.mem_leak_frames, |
| 163 | 163 | }) = .init; |
| 164 | 164 | |
| 165 | const 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 | ||
| 165 | 171 | pub fn main() anyerror!void { |
| 166 | const gpa, const is_debug = gpa: { | |
| 167 | if (build_options.debug_gpa) break :gpa .{ debug_allocator.allocator(), true }; | |
| 168 | if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false }; | |
| 169 | if (builtin.link_libc) break :gpa .{ std.heap.c_allocator, false }; | |
| 170 | break :gpa switch (builtin.mode) { | |
| 171 | .Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true }, | |
| 172 | .ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false }, | |
| 173 | }; | |
| 172 | const gpa = gpa: { | |
| 173 | if (use_debug_allocator) break :gpa debug_allocator.allocator(); | |
| 174 | if (native_os == .wasi) break :gpa std.heap.wasm_allocator; | |
| 175 | if (builtin.link_libc) break :gpa std.heap.c_allocator; | |
| 176 | break :gpa std.heap.smp_allocator; | |
| 174 | 177 | }; |
| 175 | defer if (is_debug) { | |
| 178 | defer if (use_debug_allocator) { | |
| 176 | 179 | _ = debug_allocator.deinit(); |
| 177 | 180 | }; |
| 178 | 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 | 241 | } |
| 239 | 242 | } |
| 240 | 243 | |
| 241 | var threaded: Io.Threaded = .init(gpa); | |
| 244 | var threaded: Io.Threaded = .init(gpa, .{}); | |
| 242 | 245 | defer threaded.deinit(); |
| 243 | 246 | threaded_impl_ptr = &threaded; |
| 244 | 247 | threaded.stack_size = thread_stack_size; |
| ... | ... | @@ -328,23 +331,24 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 328 | 331 | .prepend_global_cache_path = true, |
| 329 | 332 | }); |
| 330 | 333 | } else if (mem.eql(u8, cmd, "init")) { |
| 331 | return cmdInit(gpa, arena, cmd_args); | |
| 334 | return cmdInit(gpa, arena, io, cmd_args); | |
| 332 | 335 | } else if (mem.eql(u8, cmd, "targets")) { |
| 333 | 336 | dev.check(.targets_command); |
| 334 | 337 | const host = std.zig.resolveTargetQueryOrFatal(io, .{}); |
| 335 | var stdout_writer = fs.File.stdout().writer(&stdout_buffer); | |
| 336 | try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host); | |
| 338 | var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); | |
| 339 | try @import("print_targets.zig").cmdTargets(arena, io, cmd_args, &stdout_writer.interface, &host); | |
| 337 | 340 | return stdout_writer.interface.flush(); |
| 338 | 341 | } else if (mem.eql(u8, cmd, "version")) { |
| 339 | 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 | 344 | return; |
| 342 | 345 | } else if (mem.eql(u8, cmd, "env")) { |
| 343 | 346 | dev.check(.env_command); |
| 344 | 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 | 349 | try @import("print_env.zig").cmdEnv( |
| 347 | 350 | arena, |
| 351 | io, | |
| 348 | 352 | &stdout_writer.interface, |
| 349 | 353 | args, |
| 350 | 354 | if (native_os == .wasi) wasi_preopens, |
| ... | ... | @@ -358,10 +362,10 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 358 | 362 | }); |
| 359 | 363 | } else if (mem.eql(u8, cmd, "zen")) { |
| 360 | 364 | dev.check(.zen_command); |
| 361 | return fs.File.stdout().writeAll(info_zen); | |
| 365 | return Io.File.stdout().writeStreamingAll(io, info_zen); | |
| 362 | 366 | } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) { |
| 363 | 367 | dev.check(.help_command); |
| 364 | return fs.File.stdout().writeAll(usage); | |
| 368 | return Io.File.stdout().writeStreamingAll(io, usage); | |
| 365 | 369 | } else if (mem.eql(u8, cmd, "ast-check")) { |
| 366 | 370 | return cmdAstCheck(arena, io, cmd_args); |
| 367 | 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 | 375 | } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "dump-zir")) { |
| 372 | 376 | return cmdDumpZir(arena, io, cmd_args); |
| 373 | 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 | 379 | } else { |
| 376 | 380 | std.log.info("{s}", .{usage}); |
| 377 | 381 | fatal("unknown command: {s}", .{args[1]}); |
| ... | ... | @@ -698,7 +702,7 @@ const Emit = union(enum) { |
| 698 | 702 | yes: []const u8, |
| 699 | 703 | |
| 700 | 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 | 706 | return switch (emit) { |
| 703 | 707 | .no => .no, |
| 704 | 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 | 717 | } else e: { |
| 714 | 718 | // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would. |
| 715 | 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 | 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 | 725 | break :e .{ .yes_path = path }; |
| 722 | 726 | }, |
| ... | ... | @@ -1029,13 +1033,12 @@ fn buildOutputType( |
| 1029 | 1033 | if (mem.cutPrefix(u8, arg, "@")) |resp_file_path| { |
| 1030 | 1034 | // This is a "compiler response file". We must parse the file and treat its |
| 1031 | 1035 | // contents as command line parameters. |
| 1032 | args_iter.resp_file = initArgIteratorResponseFile(arena, resp_file_path) catch |err| { | |
| 1033 | fatal("unable to read response file '{s}': {s}", .{ resp_file_path, @errorName(err) }); | |
| 1034 | }; | |
| 1036 | args_iter.resp_file = initArgIteratorResponseFile(arena, io, resp_file_path) catch |err| | |
| 1037 | fatal("unable to read response file '{s}': {t}", .{ resp_file_path, err }); | |
| 1035 | 1038 | } else if (mem.startsWith(u8, arg, "-")) { |
| 1036 | 1039 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 1037 | try fs.File.stdout().writeAll(usage_build_generic); | |
| 1038 | return cleanExit(); | |
| 1040 | try Io.File.stdout().writeStreamingAll(io, usage_build_generic); | |
| 1041 | return cleanExit(io); | |
| 1039 | 1042 | } else if (mem.eql(u8, arg, "--")) { |
| 1040 | 1043 | if (arg_mode == .run) { |
| 1041 | 1044 | // args_iter.i is 1, referring the next arg after "--" in ["--", ...] |
| ... | ... | @@ -1856,9 +1859,7 @@ fn buildOutputType( |
| 1856 | 1859 | var must_link = false; |
| 1857 | 1860 | var file_ext: ?Compilation.FileExt = null; |
| 1858 | 1861 | while (it.has_next) { |
| 1859 | it.next() catch |err| { | |
| 1860 | fatal("unable to parse command line parameters: {s}", .{@errorName(err)}); | |
| 1861 | }; | |
| 1862 | it.next(io) catch |err| fatal("unable to parse command line parameters: {t}", .{err}); | |
| 1862 | 1863 | switch (it.zig_equivalent) { |
| 1863 | 1864 | .target => target_arch_os_abi = it.only_arg, // example: -target riscv64-linux-unknown |
| 1864 | 1865 | .o => { |
| ... | ... | @@ -2834,9 +2835,9 @@ fn buildOutputType( |
| 2834 | 2835 | } else if (mem.eql(u8, arg, "-V")) { |
| 2835 | 2836 | warn("ignoring request for supported emulations: unimplemented", .{}); |
| 2836 | 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 | 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 | 2841 | process.exit(0); |
| 2841 | 2842 | } else { |
| 2842 | 2843 | fatal("unsupported linker arg: {s}", .{arg}); |
| ... | ... | @@ -3075,14 +3076,13 @@ fn buildOutputType( |
| 3075 | 3076 | |
| 3076 | 3077 | const self_exe_path = switch (native_os) { |
| 3077 | 3078 | .wasi => {}, |
| 3078 | else => fs.selfExePathAlloc(arena) catch |err| { | |
| 3079 | fatal("unable to find zig self exe path: {s}", .{@errorName(err)}); | |
| 3080 | }, | |
| 3079 | else => process.executablePathAlloc(io, arena) catch |err| fatal("unable to find zig self exe path: {t}", .{err}), | |
| 3081 | 3080 | }; |
| 3082 | 3081 | |
| 3083 | 3082 | // This `init` calls `fatal` on error. |
| 3084 | 3083 | var dirs: Compilation.Directories = .init( |
| 3085 | 3084 | arena, |
| 3085 | io, | |
| 3086 | 3086 | override_lib_dir, |
| 3087 | 3087 | override_global_cache_dir, |
| 3088 | 3088 | s: { |
| ... | ... | @@ -3095,11 +3095,9 @@ fn buildOutputType( |
| 3095 | 3095 | if (native_os == .wasi) wasi_preopens, |
| 3096 | 3096 | self_exe_path, |
| 3097 | 3097 | ); |
| 3098 | defer dirs.deinit(); | |
| 3098 | defer dirs.deinit(io); | |
| 3099 | 3099 | |
| 3100 | if (linker_optimization) |o| { | |
| 3101 | warn("ignoring deprecated linker optimization setting '{s}'", .{o}); | |
| 3102 | } | |
| 3100 | if (linker_optimization) |o| warn("ignoring deprecated linker optimization setting '{s}'", .{o}); | |
| 3103 | 3101 | |
| 3104 | 3102 | create_module.dirs = dirs; |
| 3105 | 3103 | create_module.opts.emit_llvm_ir = emit_llvm_ir != .no; |
| ... | ... | @@ -3208,6 +3206,7 @@ fn buildOutputType( |
| 3208 | 3206 | |
| 3209 | 3207 | for (create_module.framework_dirs.items) |framework_dir_path| { |
| 3210 | 3208 | if (try accessFrameworkPath( |
| 3209 | io, | |
| 3211 | 3210 | &test_path, |
| 3212 | 3211 | &checked_paths, |
| 3213 | 3212 | framework_dir_path, |
| ... | ... | @@ -3251,8 +3250,8 @@ fn buildOutputType( |
| 3251 | 3250 | } |
| 3252 | 3251 | } |
| 3253 | 3252 | |
| 3254 | var cleanup_emit_bin_dir: ?fs.Dir = null; | |
| 3255 | defer if (cleanup_emit_bin_dir) |*dir| dir.close(); | |
| 3253 | var cleanup_emit_bin_dir: ?Io.Dir = null; | |
| 3254 | defer if (cleanup_emit_bin_dir) |*dir| dir.close(io); | |
| 3256 | 3255 | |
| 3257 | 3256 | // For `zig run` and `zig test`, we don't want to put the binary in the cwd by default. So, if |
| 3258 | 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 | 3303 | } else emit: { |
| 3305 | 3304 | // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would. |
| 3306 | 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 | 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 | 3311 | break :emit .{ .yes_path = path }; |
| 3313 | 3312 | }, |
| ... | ... | @@ -3321,18 +3320,18 @@ fn buildOutputType( |
| 3321 | 3320 | }; |
| 3322 | 3321 | |
| 3323 | 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); | |
| 3325 | 3324 | |
| 3326 | 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); | |
| 3328 | 3327 | |
| 3329 | 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); | |
| 3331 | 3330 | |
| 3332 | 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); | |
| 3334 | 3333 | |
| 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); | |
| 3336 | 3335 | |
| 3337 | 3336 | const is_exe_or_dyn_lib = switch (create_module.resolved_options.output_mode) { |
| 3338 | 3337 | .Obj => false, |
| ... | ... | @@ -3353,7 +3352,7 @@ fn buildOutputType( |
| 3353 | 3352 | const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name}); |
| 3354 | 3353 | const emit_implib_resolved: Compilation.CreateOptions.Emit = switch (emit_implib) { |
| 3355 | 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 | 3356 | .yes_default_path => emit: { |
| 3358 | 3357 | if (output_to_cache != null) break :emit .yes_cache; |
| 3359 | 3358 | const p = try fs.path.join(arena, &.{ |
| ... | ... | @@ -3382,24 +3381,24 @@ fn buildOutputType( |
| 3382 | 3381 | const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{ |
| 3383 | 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"); | |
| 3386 | 3385 | |
| 3387 | 3386 | // Note that in one of the happy paths, execve() is used to switch to |
| 3388 | 3387 | // clang in which case any cleanup logic that exists for this temporary |
| 3389 | 3388 | // file will not run and this temp file will be leaked. The filename |
| 3390 | 3389 | // will be a hash of its contents — so multiple invocations of |
| 3391 | 3390 | // `zig cc -` will result in the same temp file name. |
| 3392 | var f = try dirs.local_cache.handle.createFile(dump_path, .{}); | |
| 3393 | defer f.close(); | |
| 3391 | var f = try dirs.local_cache.handle.createFile(io, dump_path, .{}); | |
| 3392 | defer f.close(io); | |
| 3394 | 3393 | |
| 3395 | 3394 | // Re-using the hasher from Cache, since the functional requirements |
| 3396 | 3395 | // for the hashing algorithm here and in the cache are the same. |
| 3397 | 3396 | // We are providing our own cache key, because this file has nothing |
| 3398 | 3397 | // to do with the cache manifest. |
| 3399 | var file_writer = f.writer(&.{}); | |
| 3398 | var file_writer = f.writer(io, &.{}); | |
| 3400 | 3399 | var buffer: [1000]u8 = undefined; |
| 3401 | 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 | 3402 | _ = hasher.writer.sendFileAll(&stdin_reader, .unlimited) catch |err| switch (err) { |
| 3404 | 3403 | error.WriteFailed => fatal("failed to write {s}: {t}", .{ dump_path, file_writer.err.? }), |
| 3405 | 3404 | else => fatal("failed to pipe stdin to {s}: {t}", .{ dump_path, err }), |
| ... | ... | @@ -3411,7 +3410,7 @@ fn buildOutputType( |
| 3411 | 3410 | const sub_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-stdin{s}", .{ |
| 3412 | 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); | |
| 3415 | 3414 | |
| 3416 | 3415 | // Convert `sub_path` to be relative to current working directory. |
| 3417 | 3416 | src.src_path = try dirs.local_cache.join(arena, &.{sub_path}); |
| ... | ... | @@ -3630,13 +3629,13 @@ fn buildOutputType( |
| 3630 | 3629 | if (show_builtin) { |
| 3631 | 3630 | const builtin_opts = comp.root_mod.getBuiltinOptions(comp.config); |
| 3632 | 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 | 3634 | switch (listen) { |
| 3636 | 3635 | .none => {}, |
| 3637 | 3636 | .stdio => { |
| 3638 | var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer); | |
| 3639 | var stdout_writer = fs.File.stdout().writer(&stdout_buffer); | |
| 3637 | var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer); | |
| 3638 | var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); | |
| 3640 | 3639 | try serve( |
| 3641 | 3640 | comp, |
| 3642 | 3641 | &stdin_reader.interface, |
| ... | ... | @@ -3647,7 +3646,7 @@ fn buildOutputType( |
| 3647 | 3646 | all_args, |
| 3648 | 3647 | runtime_args_start, |
| 3649 | 3648 | ); |
| 3650 | return cleanExit(); | |
| 3649 | return cleanExit(io); | |
| 3651 | 3650 | }, |
| 3652 | 3651 | .ip4 => |ip4_addr| { |
| 3653 | 3652 | const addr: Io.net.IpAddress = .{ .ip4 = ip4_addr }; |
| ... | ... | @@ -3673,12 +3672,12 @@ fn buildOutputType( |
| 3673 | 3672 | all_args, |
| 3674 | 3673 | runtime_args_start, |
| 3675 | 3674 | ); |
| 3676 | return cleanExit(); | |
| 3675 | return cleanExit(io); | |
| 3677 | 3676 | }, |
| 3678 | 3677 | } |
| 3679 | 3678 | |
| 3680 | 3679 | { |
| 3681 | const root_prog_node = std.Progress.start(.{ | |
| 3680 | const root_prog_node = std.Progress.start(io, .{ | |
| 3682 | 3681 | .disable_printing = (color == .off), |
| 3683 | 3682 | }); |
| 3684 | 3683 | defer root_prog_node.end(); |
| ... | ... | @@ -3756,7 +3755,7 @@ fn buildOutputType( |
| 3756 | 3755 | } |
| 3757 | 3756 | |
| 3758 | 3757 | // Skip resource deallocation in release builds; let the OS do it. |
| 3759 | return cleanExit(); | |
| 3758 | return cleanExit(io); | |
| 3760 | 3759 | } |
| 3761 | 3760 | |
| 3762 | 3761 | const CreateModule = struct { |
| ... | ... | @@ -3927,11 +3926,8 @@ fn createModule( |
| 3927 | 3926 | } |
| 3928 | 3927 | |
| 3929 | 3928 | if (target.isMinGW()) { |
| 3930 | const exists = mingw.libExists(arena, target, create_module.dirs.zig_lib, lib_name) catch |err| { | |
| 3931 | fatal("failed to check zig installation for DLL import libs: {s}", .{ | |
| 3932 | @errorName(err), | |
| 3933 | }); | |
| 3934 | }; | |
| 3929 | const exists = mingw.libExists(arena, io, target, create_module.dirs.zig_lib, lib_name) catch |err| | |
| 3930 | fatal("failed to check zig installation for DLL import libs: {t}", .{err}); | |
| 3935 | 3931 | if (exists) { |
| 3936 | 3932 | try create_module.windows_libs.put(arena, lib_name, {}); |
| 3937 | 3933 | continue; |
| ... | ... | @@ -3959,14 +3955,14 @@ fn createModule( |
| 3959 | 3955 | if (fs.path.isAbsolute(lib_dir_arg)) { |
| 3960 | 3956 | const stripped_dir = lib_dir_arg[fs.path.parsePath(lib_dir_arg).root.len..]; |
| 3961 | 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 | 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 | 3963 | } else { |
| 3968 | 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 | 3968 | create_module.lib_dir_args = undefined; // From here we use lib_directories instead. |
| ... | ... | @@ -3989,9 +3985,8 @@ fn createModule( |
| 3989 | 3985 | resolved_target.is_native_os and resolved_target.is_native_abi and |
| 3990 | 3986 | create_module.want_native_include_dirs) |
| 3991 | 3987 | { |
| 3992 | var paths = std.zig.system.NativePaths.detect(arena, target) catch |err| { | |
| 3993 | fatal("unable to detect native system paths: {s}", .{@errorName(err)}); | |
| 3994 | }; | |
| 3988 | var paths = std.zig.system.NativePaths.detect(arena, io, target) catch |err| | |
| 3989 | fatal("unable to detect native system paths: {t}", .{err}); | |
| 3995 | 3990 | for (paths.warnings.items) |warning| { |
| 3996 | 3991 | warn("{s}", .{warning}); |
| 3997 | 3992 | } |
| ... | ... | @@ -4002,38 +3997,35 @@ fn createModule( |
| 4002 | 3997 | try create_module.rpath_list.appendSlice(arena, paths.rpaths.items); |
| 4003 | 3998 | |
| 4004 | 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 | } |
| 4007 | 4002 | |
| 4008 | 4003 | if (create_module.libc_paths_file) |paths_file| { |
| 4009 | create_module.libc_installation = LibCInstallation.parse(arena, paths_file, target) catch |err| { | |
| 4010 | fatal("unable to parse libc paths file at path {s}: {s}", .{ | |
| 4011 | paths_file, @errorName(err), | |
| 4012 | }); | |
| 4013 | }; | |
| 4004 | create_module.libc_installation = LibCInstallation.parse(arena, io, paths_file, target) catch |err| | |
| 4005 | fatal("unable to parse libc paths file at path {s}: {t}", .{ paths_file, err }); | |
| 4014 | 4006 | } |
| 4015 | 4007 | |
| 4016 | 4008 | if (target.os.tag == .windows and (target.abi == .msvc or target.abi == .itanium) and |
| 4017 | 4009 | any_name_queries_remaining) |
| 4018 | 4010 | { |
| 4019 | 4011 | if (create_module.libc_installation == null) { |
| 4020 | create_module.libc_installation = LibCInstallation.findNative(.{ | |
| 4021 | .allocator = arena, | |
| 4012 | create_module.libc_installation = LibCInstallation.findNative(arena, io, .{ | |
| 4022 | 4013 | .verbose = true, |
| 4023 | 4014 | .target = target, |
| 4024 | 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 | 4019 | try create_module.lib_directories.ensureUnusedCapacity(arena, 2); |
| 4029 | addLibDirectoryWarn(&create_module.lib_directories, create_module.libc_installation.?.msvc_lib_dir.?); | |
| 4030 | addLibDirectoryWarn(&create_module.lib_directories, create_module.libc_installation.?.kernel32_lib_dir.?); | |
| 4020 | addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.msvc_lib_dir.?); | |
| 4021 | addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.kernel32_lib_dir.?); | |
| 4031 | 4022 | } |
| 4032 | 4023 | |
| 4033 | 4024 | // Destructively mutates but does not transfer ownership of `unresolved_link_inputs`. |
| 4034 | 4025 | link.resolveInputs( |
| 4035 | 4026 | gpa, |
| 4036 | 4027 | arena, |
| 4028 | io, | |
| 4037 | 4029 | target, |
| 4038 | 4030 | &unresolved_link_inputs, |
| 4039 | 4031 | &create_module.link_inputs, |
| ... | ... | @@ -4160,7 +4152,9 @@ fn serve( |
| 4160 | 4152 | |
| 4161 | 4153 | var child_pid: ?std.process.Child.Id = null; |
| 4162 | 4154 | |
| 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 | 4158 | const file_system_inputs = comp.file_system_inputs.?; |
| 4165 | 4159 | |
| 4166 | 4160 | const IncrementalDebugServer = if (build_options.enable_debug_extensions and !builtin.single_threaded) |
| ... | ... | @@ -4183,7 +4177,7 @@ fn serve( |
| 4183 | 4177 | defer if (comp.debugIncremental()) ids.mutex.unlock(io); |
| 4184 | 4178 | |
| 4185 | 4179 | switch (hdr.tag) { |
| 4186 | .exit => return cleanExit(), | |
| 4180 | .exit => return cleanExit(io), | |
| 4187 | 4181 | .update => { |
| 4188 | 4182 | tracy.frameMark(); |
| 4189 | 4183 | file_system_inputs.clearRetainingCapacity(); |
| ... | ... | @@ -4436,12 +4430,12 @@ fn runOrTest( |
| 4436 | 4430 | // the error message and invocation below. |
| 4437 | 4431 | if (process.can_execv and arg_mode == .run) { |
| 4438 | 4432 | // execv releases the locks; no need to destroy the Compilation here. |
| 4439 | std.debug.lockStdErr(); | |
| 4433 | _ = try io.lockStderr(&.{}, .no_color); | |
| 4440 | 4434 | const err = process.execve(gpa, argv.items, &env_map); |
| 4441 | std.debug.unlockStdErr(); | |
| 4435 | io.unlockStderr(); | |
| 4442 | 4436 | try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); |
| 4443 | 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 | 4439 | } else if (process.can_spawn) { |
| 4446 | 4440 | var child = std.process.Child.init(argv.items, gpa); |
| 4447 | 4441 | child.env_map = &env_map; |
| ... | ... | @@ -4455,9 +4449,9 @@ fn runOrTest( |
| 4455 | 4449 | comp_destroyed.* = true; |
| 4456 | 4450 | |
| 4457 | 4451 | const term_result = t: { |
| 4458 | std.debug.lockStdErr(); | |
| 4459 | defer std.debug.unlockStdErr(); | |
| 4460 | break :t child.spawnAndWait(); | |
| 4452 | _ = try io.lockStderr(&.{}, .no_color); | |
| 4453 | defer io.unlockStderr(); | |
| 4454 | break :t child.spawnAndWait(io); | |
| 4461 | 4455 | }; |
| 4462 | 4456 | const term = term_result catch |err| { |
| 4463 | 4457 | try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); |
| ... | ... | @@ -4469,7 +4463,7 @@ fn runOrTest( |
| 4469 | 4463 | switch (term) { |
| 4470 | 4464 | .Exited => |code| { |
| 4471 | 4465 | if (code == 0) { |
| 4472 | return cleanExit(); | |
| 4466 | return cleanExit(io); | |
| 4473 | 4467 | } else { |
| 4474 | 4468 | process.exit(code); |
| 4475 | 4469 | } |
| ... | ... | @@ -4483,7 +4477,7 @@ fn runOrTest( |
| 4483 | 4477 | switch (term) { |
| 4484 | 4478 | .Exited => |code| { |
| 4485 | 4479 | if (code == 0) { |
| 4486 | return cleanExit(); | |
| 4480 | return cleanExit(io); | |
| 4487 | 4481 | } else { |
| 4488 | 4482 | const cmd = try std.mem.join(arena, " ", argv.items); |
| 4489 | 4483 | fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd }); |
| ... | ... | @@ -4512,6 +4506,7 @@ fn runOrTestHotSwap( |
| 4512 | 4506 | all_args: []const []const u8, |
| 4513 | 4507 | runtime_args_start: ?usize, |
| 4514 | 4508 | ) !std.process.Child.Id { |
| 4509 | const io = comp.io; | |
| 4515 | 4510 | const lf = comp.bin_file.?; |
| 4516 | 4511 | |
| 4517 | 4512 | const exe_path = switch (builtin.target.os.tag) { |
| ... | ... | @@ -4520,7 +4515,7 @@ fn runOrTestHotSwap( |
| 4520 | 4515 | // tmp zig-cache and use it to spawn the child process. This way we are free to update |
| 4521 | 4516 | // the binary with each requested hot update. |
| 4522 | 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 | 4519 | break :blk try fs.path.join(gpa, &.{ comp.dirs.local_cache.path orelse ".", lf.emit.sub_path }); |
| 4525 | 4520 | }, |
| 4526 | 4521 | |
| ... | ... | @@ -4593,7 +4588,7 @@ fn runOrTestHotSwap( |
| 4593 | 4588 | child.stdout_behavior = .Inherit; |
| 4594 | 4589 | child.stderr_behavior = .Inherit; |
| 4595 | 4590 | |
| 4596 | try child.spawn(); | |
| 4591 | try child.spawn(io); | |
| 4597 | 4592 | |
| 4598 | 4593 | return child.id; |
| 4599 | 4594 | }, |
| ... | ... | @@ -4604,6 +4599,8 @@ const UpdateModuleError = Compilation.UpdateError || error{ |
| 4604 | 4599 | /// The update caused compile errors. The error bundle has already been |
| 4605 | 4600 | /// reported to the user by being rendered to stderr. |
| 4606 | 4601 | CompileErrorsReported, |
| 4602 | /// Error occurred printing compilation errors to stderr. | |
| 4603 | PrintingErrorsFailed, | |
| 4607 | 4604 | }; |
| 4608 | 4605 | fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node) UpdateModuleError!void { |
| 4609 | 4606 | try comp.update(prog_node); |
| ... | ... | @@ -4612,7 +4609,11 @@ fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node) |
| 4612 | 4609 | defer errors.deinit(comp.gpa); |
| 4613 | 4610 | |
| 4614 | 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 | 4617 | return error.CompileErrorsReported; |
| 4617 | 4618 | } |
| 4618 | 4619 | } |
| ... | ... | @@ -4665,7 +4666,7 @@ fn cmdTranslateC( |
| 4665 | 4666 | return; |
| 4666 | 4667 | } else { |
| 4667 | 4668 | const color: Color = .auto; |
| 4668 | result.errors.renderToStdErr(.{}, color); | |
| 4669 | result.errors.renderToStderr(io, .{}, color) catch {}; | |
| 4669 | 4670 | process.exit(1); |
| 4670 | 4671 | } |
| 4671 | 4672 | } |
| ... | ... | @@ -4680,7 +4681,7 @@ fn cmdTranslateC( |
| 4680 | 4681 | } else { |
| 4681 | 4682 | const hex_digest = Cache.binToHex(result.digest); |
| 4682 | 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 | 4685 | const path = comp.dirs.local_cache.path orelse "."; |
| 4685 | 4686 | fatal("unable to open cached translated zig file '{s}{s}{s}': {s}", .{ |
| 4686 | 4687 | path, |
| ... | ... | @@ -4689,12 +4690,12 @@ fn cmdTranslateC( |
| 4689 | 4690 | @errorName(err), |
| 4690 | 4691 | }); |
| 4691 | 4692 | }; |
| 4692 | defer zig_file.close(); | |
| 4693 | var stdout_writer = fs.File.stdout().writer(&stdout_buffer); | |
| 4693 | defer zig_file.close(io); | |
| 4694 | var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); | |
| 4694 | 4695 | var file_reader = zig_file.reader(io, &.{}); |
| 4695 | 4696 | _ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited); |
| 4696 | 4697 | try stdout_writer.interface.flush(); |
| 4697 | return cleanExit(); | |
| 4698 | return cleanExit(io); | |
| 4698 | 4699 | } |
| 4699 | 4700 | } |
| 4700 | 4701 | |
| ... | ... | @@ -4728,7 +4729,7 @@ const usage_init = |
| 4728 | 4729 | \\ |
| 4729 | 4730 | ; |
| 4730 | 4731 | |
| 4731 | fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { | |
| 4732 | fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void { | |
| 4732 | 4733 | dev.check(.init_command); |
| 4733 | 4734 | |
| 4734 | 4735 | var template: enum { example, minimal } = .example; |
| ... | ... | @@ -4740,8 +4741,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4740 | 4741 | if (mem.eql(u8, arg, "-m") or mem.eql(u8, arg, "--minimal")) { |
| 4741 | 4742 | template = .minimal; |
| 4742 | 4743 | } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 4743 | try fs.File.stdout().writeAll(usage_init); | |
| 4744 | return cleanExit(); | |
| 4744 | try Io.File.stdout().writeStreamingAll(io, usage_init); | |
| 4745 | return cleanExit(io); | |
| 4745 | 4746 | } else { |
| 4746 | 4747 | fatal("unrecognized parameter: '{s}'", .{arg}); |
| 4747 | 4748 | } |
| ... | ... | @@ -4759,8 +4760,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4759 | 4760 | |
| 4760 | 4761 | switch (template) { |
| 4761 | 4762 | .example => { |
| 4762 | var templates = findTemplates(gpa, arena); | |
| 4763 | defer templates.deinit(); | |
| 4763 | var templates = findTemplates(gpa, arena, io); | |
| 4764 | defer templates.deinit(io); | |
| 4764 | 4765 | |
| 4765 | 4766 | const s = fs.path.sep_str; |
| 4766 | 4767 | const template_paths = [_][]const u8{ |
| ... | ... | @@ -4772,7 +4773,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4772 | 4773 | var ok_count: usize = 0; |
| 4773 | 4774 | |
| 4774 | 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 | 4777 | std.log.info("created {s}", .{template_path}); |
| 4777 | 4778 | ok_count += 1; |
| 4778 | 4779 | } else |err| switch (err) { |
| ... | ... | @@ -4786,10 +4787,10 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4786 | 4787 | if (ok_count == template_paths.len) { |
| 4787 | 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 | 4792 | .minimal => { |
| 4792 | writeSimpleTemplateFile(Package.Manifest.basename, | |
| 4793 | writeSimpleTemplateFile(io, Package.Manifest.basename, | |
| 4793 | 4794 | \\.{{ |
| 4794 | 4795 | \\ .name = .{s}, |
| 4795 | 4796 | \\ .version = "0.0.1", |
| ... | ... | @@ -4806,7 +4807,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4806 | 4807 | else => fatal("failed to create '{s}': {s}", .{ Package.Manifest.basename, @errorName(err) }), |
| 4807 | 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 | 4811 | \\const std = @import("std"); |
| 4811 | 4812 | \\ |
| 4812 | 4813 | \\pub fn build(b: *std.Build) void {{ |
| ... | ... | @@ -4819,11 +4820,11 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4819 | 4820 | // their `build.zig.zon` *after* writing their `build.zig`. So this one isn't fatal. |
| 4820 | 4821 | error.PathAlreadyExists => { |
| 4821 | 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 | 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 | 4895 | const argv_index_exe = child_argv.items.len; |
| 4895 | 4896 | _ = try child_argv.addOne(); |
| 4896 | 4897 | |
| 4897 | const self_exe_path = try fs.selfExePathAlloc(arena); | |
| 4898 | const self_exe_path = try process.executablePathAlloc(io, arena); | |
| 4898 | 4899 | try child_argv.append(self_exe_path); |
| 4899 | 4900 | |
| 4900 | 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 | 5076 | |
| 5076 | 5077 | const work_around_btrfs_bug = native_os == .linux and |
| 5077 | 5078 | EnvVar.ZIG_BTRFS_WORKAROUND.isSet(); |
| 5078 | const root_prog_node = std.Progress.start(.{ | |
| 5079 | const root_prog_node = std.Progress.start(io, .{ | |
| 5079 | 5080 | .disable_printing = (color == .off), |
| 5080 | 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 | 5111 | const paths_file = debug_libc_paths_file orelse break :lci null; |
| 5111 | 5112 | if (!build_options.enable_debug_extensions) unreachable; |
| 5112 | 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 | 5115 | break :lci lci; |
| 5115 | 5116 | }; |
| 5116 | 5117 | |
| 5117 | 5118 | process.raiseFileDescriptorLimit(); |
| 5118 | 5119 | |
| 5119 | 5120 | const cwd_path = try introspect.getResolvedCwd(arena); |
| 5120 | const build_root = try findBuildRoot(arena, .{ | |
| 5121 | const build_root = try findBuildRoot(arena, io, .{ | |
| 5121 | 5122 | .cwd_path = cwd_path, |
| 5122 | 5123 | .build_file = build_file, |
| 5123 | 5124 | }); |
| ... | ... | @@ -5125,6 +5126,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) |
| 5125 | 5126 | // This `init` calls `fatal` on error. |
| 5126 | 5127 | var dirs: Compilation.Directories = .init( |
| 5127 | 5128 | arena, |
| 5129 | io, | |
| 5128 | 5130 | override_lib_dir, |
| 5129 | 5131 | override_global_cache_dir, |
| 5130 | 5132 | .{ .override = path: { |
| ... | ... | @@ -5134,7 +5136,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) |
| 5134 | 5136 | {}, |
| 5135 | 5137 | self_exe_path, |
| 5136 | 5138 | ); |
| 5137 | defer dirs.deinit(); | |
| 5139 | defer dirs.deinit(io); | |
| 5138 | 5140 | |
| 5139 | 5141 | child_argv.items[argv_index_zig_lib_dir] = dirs.zig_lib.path orelse cwd_path; |
| 5140 | 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 | 5205 | .parent = root_mod, |
| 5204 | 5206 | }); |
| 5205 | 5207 | |
| 5206 | var cleanup_build_dir: ?fs.Dir = null; | |
| 5207 | defer if (cleanup_build_dir) |*dir| dir.close(); | |
| 5208 | var cleanup_build_dir: ?Io.Dir = null; | |
| 5209 | defer if (cleanup_build_dir) |*dir| dir.close(io); | |
| 5208 | 5210 | |
| 5209 | 5211 | if (dev.env.supports(.fetch_command)) { |
| 5210 | 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 | 5228 | if (system_pkg_dir_path) |p| { |
| 5227 | 5229 | job_queue.global_cache = .{ |
| 5228 | 5230 | .path = p, |
| 5229 | .handle = fs.cwd().openDir(p, .{}) catch |err| { | |
| 5231 | .handle = Io.Dir.cwd().openDir(io, p, .{}) catch |err| { | |
| 5230 | 5232 | fatal("unable to open system package directory '{s}': {s}", .{ |
| 5231 | 5233 | p, @errorName(err), |
| 5232 | 5234 | }); |
| ... | ... | @@ -5285,17 +5287,18 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) |
| 5285 | 5287 | |
| 5286 | 5288 | if (fetch.error_bundle.root_list.items.len > 0) { |
| 5287 | 5289 | var errors = try fetch.error_bundle.toOwnedBundle(""); |
| 5288 | errors.renderToStdErr(.{}, color); | |
| 5290 | errors.renderToStderr(io, .{}, color) catch {}; | |
| 5289 | 5291 | process.exit(1); |
| 5290 | 5292 | } |
| 5291 | 5293 | |
| 5292 | if (fetch_only) return cleanExit(); | |
| 5294 | if (fetch_only) return cleanExit(io); | |
| 5293 | 5295 | |
| 5294 | 5296 | var source_buf = std.array_list.Managed(u8).init(gpa); |
| 5295 | 5297 | defer source_buf.deinit(); |
| 5296 | 5298 | try job_queue.createDependenciesSource(&source_buf); |
| 5297 | 5299 | const deps_mod = try createDependenciesModule( |
| 5298 | 5300 | arena, |
| 5301 | io, | |
| 5299 | 5302 | source_buf.items, |
| 5300 | 5303 | root_mod, |
| 5301 | 5304 | dirs, |
| ... | ... | @@ -5357,6 +5360,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) |
| 5357 | 5360 | } |
| 5358 | 5361 | } else try createEmptyDependenciesModule( |
| 5359 | 5362 | arena, |
| 5363 | io, | |
| 5360 | 5364 | root_mod, |
| 5361 | 5365 | dirs, |
| 5362 | 5366 | config, |
| ... | ... | @@ -5415,16 +5419,15 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) |
| 5415 | 5419 | child.stderr_behavior = .Inherit; |
| 5416 | 5420 | |
| 5417 | 5421 | const term = t: { |
| 5418 | std.debug.lockStdErr(); | |
| 5419 | defer std.debug.unlockStdErr(); | |
| 5420 | break :t child.spawnAndWait() catch |err| { | |
| 5421 | fatal("failed to spawn build runner {s}: {s}", .{ child_argv.items[0], @errorName(err) }); | |
| 5422 | }; | |
| 5422 | _ = try io.lockStderr(&.{}, .no_color); | |
| 5423 | defer io.unlockStderr(); | |
| 5424 | break :t child.spawnAndWait(io) catch |err| | |
| 5425 | fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err }); | |
| 5423 | 5426 | }; |
| 5424 | 5427 | |
| 5425 | 5428 | switch (term) { |
| 5426 | 5429 | .Exited => |code| { |
| 5427 | if (code == 0) return cleanExit(); | |
| 5430 | if (code == 0) return cleanExit(io); | |
| 5428 | 5431 | // Indicates that the build runner has reported compile errors |
| 5429 | 5432 | // and this parent process does not need to report any further |
| 5430 | 5433 | // diagnostics. |
| ... | ... | @@ -5437,12 +5440,12 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) |
| 5437 | 5440 | // that are missing. |
| 5438 | 5441 | const s = fs.path.sep_str; |
| 5439 | 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 | 5444 | fatal("unable to read results of configure phase from '{f}{s}': {s}", .{ |
| 5442 | 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 {}; | |
| 5446 | 5449 | |
| 5447 | 5450 | var it = mem.splitScalar(u8, stdout, '\n'); |
| 5448 | 5451 | var any_errors = false; |
| ... | ... | @@ -5511,9 +5514,10 @@ fn jitCmd( |
| 5511 | 5514 | dev.check(.jit_command); |
| 5512 | 5515 | |
| 5513 | 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 | 5518 | .disable_printing = (color == .off), |
| 5516 | 5519 | }); |
| 5520 | defer root_prog_node.end(); | |
| 5517 | 5521 | |
| 5518 | 5522 | const target_query: std.Target.Query = .{}; |
| 5519 | 5523 | const resolved_target: Package.Module.ResolvedTarget = .{ |
| ... | ... | @@ -5523,9 +5527,8 @@ fn jitCmd( |
| 5523 | 5527 | .is_explicit_dynamic_linker = false, |
| 5524 | 5528 | }; |
| 5525 | 5529 | |
| 5526 | const self_exe_path = fs.selfExePathAlloc(arena) catch |err| { | |
| 5527 | fatal("unable to find self exe path: {s}", .{@errorName(err)}); | |
| 5528 | }; | |
| 5530 | const self_exe_path = process.executablePathAlloc(io, arena) catch |err| | |
| 5531 | fatal("unable to find self exe path: {t}", .{err}); | |
| 5529 | 5532 | |
| 5530 | 5533 | const optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet()) |
| 5531 | 5534 | .Debug |
| ... | ... | @@ -5538,13 +5541,14 @@ fn jitCmd( |
| 5538 | 5541 | // This `init` calls `fatal` on error. |
| 5539 | 5542 | var dirs: Compilation.Directories = .init( |
| 5540 | 5543 | arena, |
| 5544 | io, | |
| 5541 | 5545 | override_lib_dir, |
| 5542 | 5546 | override_global_cache_dir, |
| 5543 | 5547 | .global, |
| 5544 | 5548 | if (native_os == .wasi) wasi_preopens, |
| 5545 | 5549 | self_exe_path, |
| 5546 | 5550 | ); |
| 5547 | defer dirs.deinit(); | |
| 5551 | defer dirs.deinit(io); | |
| 5548 | 5552 | |
| 5549 | 5553 | const thread_limit = @min( |
| 5550 | 5554 | @max(std.Thread.getCpuCount() catch 1, 1), |
| ... | ... | @@ -5623,7 +5627,7 @@ fn jitCmd( |
| 5623 | 5627 | defer comp.destroy(); |
| 5624 | 5628 | |
| 5625 | 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 | 5631 | var server: std.zig.Server = .{ |
| 5628 | 5632 | .out = &stdout_writer.interface, |
| 5629 | 5633 | .in = undefined, // won't be receiving messages |
| ... | ... | @@ -5683,19 +5687,23 @@ fn jitCmd( |
| 5683 | 5687 | child.stdout_behavior = if (options.capture == null) .Inherit else .Pipe; |
| 5684 | 5688 | child.stderr_behavior = .Inherit; |
| 5685 | 5689 | |
| 5686 | try child.spawn(); | |
| 5690 | const term = t: { | |
| 5691 | _ = try io.lockStderr(&.{}, .no_color); | |
| 5692 | defer io.unlockStderr(); | |
| 5693 | try child.spawn(io); | |
| 5687 | 5694 | |
| 5688 | if (options.capture) |ptr| { | |
| 5689 | var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); | |
| 5690 | ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); | |
| 5691 | } | |
| 5695 | if (options.capture) |ptr| { | |
| 5696 | var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); | |
| 5697 | ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); | |
| 5698 | } | |
| 5692 | 5699 | |
| 5693 | const term = try child.wait(); | |
| 5700 | break :t try child.wait(io); | |
| 5701 | }; | |
| 5694 | 5702 | switch (term) { |
| 5695 | 5703 | .Exited => |code| { |
| 5696 | 5704 | if (code == 0) { |
| 5697 | 5705 | if (options.capture != null) return; |
| 5698 | return cleanExit(); | |
| 5706 | return cleanExit(io); | |
| 5699 | 5707 | } |
| 5700 | 5708 | const cmd = try std.mem.join(arena, " ", child_argv.items); |
| 5701 | 5709 | fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); |
| ... | ... | @@ -5818,9 +5826,9 @@ pub fn lldMain( |
| 5818 | 5826 | const ArgIteratorResponseFile = process.ArgIteratorGeneral(.{ .comments = true, .single_quotes = true }); |
| 5819 | 5827 | |
| 5820 | 5828 | /// Initialize the arguments from a Response File. "*.rsp" |
| 5821 | fn initArgIteratorResponseFile(allocator: Allocator, resp_file_path: []const u8) !ArgIteratorResponseFile { | |
| 5829 | fn initArgIteratorResponseFile(allocator: Allocator, io: Io, resp_file_path: []const u8) !ArgIteratorResponseFile { | |
| 5822 | 5830 | 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 | 5832 | errdefer allocator.free(cmd_line); |
| 5825 | 5833 | |
| 5826 | 5834 | return ArgIteratorResponseFile.initTakeOwnership(allocator, cmd_line); |
| ... | ... | @@ -5948,7 +5956,7 @@ pub const ClangArgIterator = struct { |
| 5948 | 5956 | }; |
| 5949 | 5957 | } |
| 5950 | 5958 | |
| 5951 | fn next(self: *ClangArgIterator) !void { | |
| 5959 | fn next(self: *ClangArgIterator, io: Io) !void { | |
| 5952 | 5960 | assert(self.has_next); |
| 5953 | 5961 | assert(self.next_index < self.argv.len); |
| 5954 | 5962 | // 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 | 5974 | const arena = self.arena; |
| 5967 | 5975 | const resp_file_path = arg[1..]; |
| 5968 | 5976 | |
| 5969 | self.arg_iterator_response_file = | |
| 5970 | initArgIteratorResponseFile(arena, resp_file_path) catch |err| { | |
| 5971 | fatal("unable to read response file '{s}': {s}", .{ resp_file_path, @errorName(err) }); | |
| 5972 | }; | |
| 5977 | self.arg_iterator_response_file = initArgIteratorResponseFile(arena, io, resp_file_path) catch |err| | |
| 5978 | fatal("unable to read response file '{s}': {t}", .{ resp_file_path, err }); | |
| 5973 | 5979 | // NOTE: The ArgIteratorResponseFile returns tokens from next() that are slices of an |
| 5974 | 5980 | // internal buffer. This internal buffer is arena allocated, so it is not cleaned up here. |
| 5975 | 5981 | |
| ... | ... | @@ -6156,8 +6162,8 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { |
| 6156 | 6162 | const arg = args[i]; |
| 6157 | 6163 | if (mem.startsWith(u8, arg, "-")) { |
| 6158 | 6164 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 6159 | try fs.File.stdout().writeAll(usage_ast_check); | |
| 6160 | return cleanExit(); | |
| 6165 | try Io.File.stdout().writeStreamingAll(io, usage_ast_check); | |
| 6166 | return cleanExit(io); | |
| 6161 | 6167 | } else if (mem.eql(u8, arg, "-t")) { |
| 6162 | 6168 | want_output_text = true; |
| 6163 | 6169 | } else if (mem.eql(u8, arg, "--zon")) { |
| ... | ... | @@ -6184,12 +6190,12 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { |
| 6184 | 6190 | const display_path = zig_source_path orelse "<stdin>"; |
| 6185 | 6191 | const source: [:0]const u8 = s: { |
| 6186 | 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 | 6194 | fatal("unable to open file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) }); |
| 6189 | 6195 | }; |
| 6190 | } else fs.File.stdin(); | |
| 6191 | defer if (zig_source_path != null) f.close(); | |
| 6192 | var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer); | |
| 6196 | } else Io.File.stdin(); | |
| 6197 | defer if (zig_source_path != null) f.close(io); | |
| 6198 | var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer); | |
| 6193 | 6199 | break :s std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| { |
| 6194 | 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 | 6213 | |
| 6208 | 6214 | const tree = try Ast.parse(arena, source, mode); |
| 6209 | 6215 | |
| 6210 | var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); | |
| 6216 | var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); | |
| 6211 | 6217 | const stdout_bw = &stdout_writer.interface; |
| 6212 | 6218 | switch (mode) { |
| 6213 | 6219 | .zig => { |
| ... | ... | @@ -6218,7 +6224,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { |
| 6218 | 6224 | try wip_errors.init(arena); |
| 6219 | 6225 | try wip_errors.addZirErrorMessages(zir, tree, source, display_path); |
| 6220 | 6226 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 6221 | error_bundle.renderToStdErr(.{}, color); | |
| 6227 | try error_bundle.renderToStderr(io, .{}, color); | |
| 6222 | 6228 | if (zir.loweringFailed()) { |
| 6223 | 6229 | process.exit(1); |
| 6224 | 6230 | } |
| ... | ... | @@ -6228,7 +6234,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { |
| 6228 | 6234 | if (zir.hasCompileErrors()) { |
| 6229 | 6235 | process.exit(1); |
| 6230 | 6236 | } else { |
| 6231 | return cleanExit(); | |
| 6237 | return cleanExit(io); | |
| 6232 | 6238 | } |
| 6233 | 6239 | } |
| 6234 | 6240 | if (!build_options.enable_debug_extensions) { |
| ... | ... | @@ -6279,7 +6285,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { |
| 6279 | 6285 | if (zir.hasCompileErrors()) { |
| 6280 | 6286 | process.exit(1); |
| 6281 | 6287 | } else { |
| 6282 | return cleanExit(); | |
| 6288 | return cleanExit(io); | |
| 6283 | 6289 | } |
| 6284 | 6290 | }, |
| 6285 | 6291 | .zon => { |
| ... | ... | @@ -6289,12 +6295,12 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { |
| 6289 | 6295 | try wip_errors.init(arena); |
| 6290 | 6296 | try wip_errors.addZoirErrorMessages(zoir, tree, source, display_path); |
| 6291 | 6297 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 6292 | error_bundle.renderToStdErr(.{}, color); | |
| 6298 | error_bundle.renderToStderr(io, .{}, color) catch {}; | |
| 6293 | 6299 | process.exit(1); |
| 6294 | 6300 | } |
| 6295 | 6301 | |
| 6296 | 6302 | if (!want_output_text) { |
| 6297 | return cleanExit(); | |
| 6303 | return cleanExit(io); | |
| 6298 | 6304 | } |
| 6299 | 6305 | |
| 6300 | 6306 | if (!build_options.enable_debug_extensions) { |
| ... | ... | @@ -6303,7 +6309,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { |
| 6303 | 6309 | |
| 6304 | 6310 | try @import("print_zoir.zig").renderToWriter(zoir, arena, stdout_bw); |
| 6305 | 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 | 6336 | const arg = args[i]; |
| 6331 | 6337 | if (mem.startsWith(u8, arg, "-")) { |
| 6332 | 6338 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 6333 | try fs.File.stdout().writeAll(detect_cpu_usage); | |
| 6334 | return cleanExit(); | |
| 6339 | try Io.File.stdout().writeStreamingAll(io, detect_cpu_usage); | |
| 6340 | return cleanExit(io); | |
| 6335 | 6341 | } else if (mem.eql(u8, arg, "--llvm")) { |
| 6336 | 6342 | use_llvm = true; |
| 6337 | 6343 | } else { |
| ... | ... | @@ -6351,10 +6357,10 @@ fn cmdDetectCpu(io: Io, args: []const []const u8) !void { |
| 6351 | 6357 | const name = llvm.GetHostCPUName() orelse fatal("LLVM could not figure out the host cpu name", .{}); |
| 6352 | 6358 | const features = llvm.GetHostCPUFeatures() orelse fatal("LLVM could not figure out the host cpu feature set", .{}); |
| 6353 | 6359 | const cpu = try detectNativeCpuWithLLVM(builtin.cpu.arch, name, features); |
| 6354 | try printCpu(cpu); | |
| 6360 | try printCpu(io, cpu); | |
| 6355 | 6361 | } else { |
| 6356 | 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 | } |
| 6360 | 6366 | |
| ... | ... | @@ -6421,8 +6427,8 @@ fn detectNativeCpuWithLLVM( |
| 6421 | 6427 | return result; |
| 6422 | 6428 | } |
| 6423 | 6429 | |
| 6424 | fn printCpu(cpu: std.Target.Cpu) !void { | |
| 6425 | var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); | |
| 6430 | fn printCpu(io: Io, cpu: std.Target.Cpu) !void { | |
| 6431 | var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); | |
| 6426 | 6432 | const stdout_bw = &stdout_writer.interface; |
| 6427 | 6433 | |
| 6428 | 6434 | if (cpu.model.llvm_name) |llvm_name| { |
| ... | ... | @@ -6444,6 +6450,7 @@ fn printCpu(cpu: std.Target.Cpu) !void { |
| 6444 | 6450 | fn cmdDumpLlvmInts( |
| 6445 | 6451 | gpa: Allocator, |
| 6446 | 6452 | arena: Allocator, |
| 6453 | io: Io, | |
| 6447 | 6454 | args: []const []const u8, |
| 6448 | 6455 | ) !void { |
| 6449 | 6456 | dev.check(.llvm_ints_command); |
| ... | ... | @@ -6471,7 +6478,7 @@ fn cmdDumpLlvmInts( |
| 6471 | 6478 | const dl = tm.createTargetDataLayout(); |
| 6472 | 6479 | const context = llvm.Context.create(); |
| 6473 | 6480 | |
| 6474 | var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); | |
| 6481 | var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); | |
| 6475 | 6482 | const stdout_bw = &stdout_writer.interface; |
| 6476 | 6483 | for ([_]u16{ 1, 8, 16, 32, 64, 128, 256 }) |bits| { |
| 6477 | 6484 | const int_type = context.intType(bits); |
| ... | ... | @@ -6480,7 +6487,7 @@ fn cmdDumpLlvmInts( |
| 6480 | 6487 | } |
| 6481 | 6488 | try stdout_bw.flush(); |
| 6482 | 6489 | |
| 6483 | return cleanExit(); | |
| 6490 | return cleanExit(io); | |
| 6484 | 6491 | } |
| 6485 | 6492 | |
| 6486 | 6493 | /// This is only enabled for debug builds. |
| ... | ... | @@ -6491,13 +6498,13 @@ fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void { |
| 6491 | 6498 | |
| 6492 | 6499 | const cache_file = args[0]; |
| 6493 | 6500 | |
| 6494 | var f = fs.cwd().openFile(cache_file, .{}) catch |err| { | |
| 6501 | var f = Io.Dir.cwd().openFile(io, cache_file, .{}) catch |err| { | |
| 6495 | 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); | |
| 6498 | 6505 | |
| 6499 | 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 | 6508 | const stdout_bw = &stdout_writer.interface; |
| 6502 | 6509 | { |
| 6503 | 6510 | const instruction_bytes = zir.instructions.len * |
| ... | ... | @@ -6538,18 +6545,18 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { |
| 6538 | 6545 | const new_source_path = args[1]; |
| 6539 | 6546 | |
| 6540 | 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 | 6549 | fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); |
| 6543 | defer f.close(); | |
| 6544 | var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer); | |
| 6550 | defer f.close(io); | |
| 6551 | var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer); | |
| 6545 | 6552 | break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| |
| 6546 | 6553 | fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); |
| 6547 | 6554 | }; |
| 6548 | 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 | 6557 | fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) }); |
| 6551 | defer f.close(); | |
| 6552 | var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer); | |
| 6558 | defer f.close(io); | |
| 6559 | var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer); | |
| 6553 | 6560 | break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| |
| 6554 | 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 | 6569 | try wip_errors.init(arena); |
| 6563 | 6570 | try wip_errors.addZirErrorMessages(old_zir, old_tree, old_source, old_source_path); |
| 6564 | 6571 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 6565 | error_bundle.renderToStdErr(.{}, color); | |
| 6572 | error_bundle.renderToStderr(io, .{}, color) catch {}; | |
| 6566 | 6573 | process.exit(1); |
| 6567 | 6574 | } |
| 6568 | 6575 | |
| ... | ... | @@ -6574,14 +6581,14 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { |
| 6574 | 6581 | try wip_errors.init(arena); |
| 6575 | 6582 | try wip_errors.addZirErrorMessages(new_zir, new_tree, new_source, new_source_path); |
| 6576 | 6583 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 6577 | error_bundle.renderToStdErr(.{}, color); | |
| 6584 | error_bundle.renderToStderr(io, .{}, color) catch {}; | |
| 6578 | 6585 | process.exit(1); |
| 6579 | 6586 | } |
| 6580 | 6587 | |
| 6581 | 6588 | var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .empty; |
| 6582 | 6589 | try Zcu.mapOldZirToNew(arena, old_zir, new_zir, &inst_map); |
| 6583 | 6590 | |
| 6584 | var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); | |
| 6591 | var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); | |
| 6585 | 6592 | const stdout_bw = &stdout_writer.interface; |
| 6586 | 6593 | { |
| 6587 | 6594 | try stdout_bw.print("Instruction mappings:\n", .{}); |
| ... | ... | @@ -6623,7 +6630,7 @@ fn warnAboutForeignBinaries( |
| 6623 | 6630 | const host_query: std.Target.Query = .{}; |
| 6624 | 6631 | const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query); |
| 6625 | 6632 | |
| 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 | 6634 | .native => return, |
| 6628 | 6635 | .rosetta => { |
| 6629 | 6636 | const host_name = try host_target.zigTriple(arena); |
| ... | ... | @@ -6829,6 +6836,7 @@ const ClangSearchSanitizer = struct { |
| 6829 | 6836 | }; |
| 6830 | 6837 | |
| 6831 | 6838 | fn accessFrameworkPath( |
| 6839 | io: Io, | |
| 6832 | 6840 | test_path: *std.array_list.Managed(u8), |
| 6833 | 6841 | checked_paths: *std.array_list.Managed(u8), |
| 6834 | 6842 | framework_dir_path: []const u8, |
| ... | ... | @@ -6842,7 +6850,7 @@ fn accessFrameworkPath( |
| 6842 | 6850 | framework_dir_path, framework_name, framework_name, ext, |
| 6843 | 6851 | }); |
| 6844 | 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 | 6854 | error.FileNotFound => continue, |
| 6847 | 6855 | else => |e| fatal("unable to search for {s} framework '{s}': {s}", .{ |
| 6848 | 6856 | ext, test_path.items, @errorName(e), |
| ... | ... | @@ -6912,8 +6920,8 @@ fn cmdFetch( |
| 6912 | 6920 | const arg = args[i]; |
| 6913 | 6921 | if (mem.startsWith(u8, arg, "-")) { |
| 6914 | 6922 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 6915 | try fs.File.stdout().writeAll(usage_fetch); | |
| 6916 | return cleanExit(); | |
| 6923 | try Io.File.stdout().writeStreamingAll(io, usage_fetch); | |
| 6924 | return cleanExit(io); | |
| 6917 | 6925 | } else if (mem.eql(u8, arg, "--global-cache-dir")) { |
| 6918 | 6926 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); |
| 6919 | 6927 | i += 1; |
| ... | ... | @@ -6946,7 +6954,7 @@ fn cmdFetch( |
| 6946 | 6954 | |
| 6947 | 6955 | try http_client.initDefaultProxies(arena); |
| 6948 | 6956 | |
| 6949 | var root_prog_node = std.Progress.start(.{ | |
| 6957 | var root_prog_node = std.Progress.start(io, .{ | |
| 6950 | 6958 | .root_name = "Fetch", |
| 6951 | 6959 | }); |
| 6952 | 6960 | defer root_prog_node.end(); |
| ... | ... | @@ -6954,11 +6962,11 @@ fn cmdFetch( |
| 6954 | 6962 | var global_cache_directory: Directory = l: { |
| 6955 | 6963 | const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena); |
| 6956 | 6964 | break :l .{ |
| 6957 | .handle = try fs.cwd().makeOpenPath(p, .{}), | |
| 6965 | .handle = try Io.Dir.cwd().createDirPathOpen(io, p, .{}), | |
| 6958 | 6966 | .path = p, |
| 6959 | 6967 | }; |
| 6960 | 6968 | }; |
| 6961 | defer global_cache_directory.handle.close(); | |
| 6969 | defer global_cache_directory.handle.close(io); | |
| 6962 | 6970 | |
| 6963 | 6971 | var job_queue: Package.Fetch.JobQueue = .{ |
| 6964 | 6972 | .io = io, |
| ... | ... | @@ -7009,7 +7017,7 @@ fn cmdFetch( |
| 7009 | 7017 | |
| 7010 | 7018 | if (fetch.error_bundle.root_list.items.len > 0) { |
| 7011 | 7019 | var errors = try fetch.error_bundle.toOwnedBundle(""); |
| 7012 | errors.renderToStdErr(.{}, color); | |
| 7020 | errors.renderToStderr(io, .{}, color) catch {}; | |
| 7013 | 7021 | process.exit(1); |
| 7014 | 7022 | } |
| 7015 | 7023 | |
| ... | ... | @@ -7021,10 +7029,10 @@ fn cmdFetch( |
| 7021 | 7029 | |
| 7022 | 7030 | const name = switch (save) { |
| 7023 | 7031 | .no => { |
| 7024 | var stdout = fs.File.stdout().writerStreaming(&stdout_buffer); | |
| 7032 | var stdout = Io.File.stdout().writerStreaming(io, &stdout_buffer); | |
| 7025 | 7033 | try stdout.interface.print("{s}\n", .{package_hash_slice}); |
| 7026 | 7034 | try stdout.interface.flush(); |
| 7027 | return cleanExit(); | |
| 7035 | return cleanExit(io); | |
| 7028 | 7036 | }, |
| 7029 | 7037 | .yes, .exact => |name| name: { |
| 7030 | 7038 | if (name) |n| break :name n; |
| ... | ... | @@ -7036,14 +7044,14 @@ fn cmdFetch( |
| 7036 | 7044 | |
| 7037 | 7045 | const cwd_path = try introspect.getResolvedCwd(arena); |
| 7038 | 7046 | |
| 7039 | var build_root = try findBuildRoot(arena, .{ | |
| 7047 | var build_root = try findBuildRoot(arena, io, .{ | |
| 7040 | 7048 | .cwd_path = cwd_path, |
| 7041 | 7049 | }); |
| 7042 | defer build_root.deinit(); | |
| 7050 | defer build_root.deinit(io); | |
| 7043 | 7051 | |
| 7044 | 7052 | // The name to use in case the manifest file needs to be created now. |
| 7045 | 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 | 7055 | .root_name = try sanitizeExampleName(arena, init_root_name), |
| 7048 | 7056 | .dir = build_root.directory.handle, |
| 7049 | 7057 | .color = color, |
| ... | ... | @@ -7159,15 +7167,16 @@ fn cmdFetch( |
| 7159 | 7167 | try ast.render(gpa, &aw.writer, fixups); |
| 7160 | 7168 | const rendered = aw.written(); |
| 7161 | 7169 | |
| 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 | 7171 | fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err }); |
| 7164 | 7172 | }; |
| 7165 | 7173 | |
| 7166 | return cleanExit(); | |
| 7174 | return cleanExit(io); | |
| 7167 | 7175 | } |
| 7168 | 7176 | |
| 7169 | 7177 | fn createEmptyDependenciesModule( |
| 7170 | 7178 | arena: Allocator, |
| 7179 | io: Io, | |
| 7171 | 7180 | main_mod: *Package.Module, |
| 7172 | 7181 | dirs: Compilation.Directories, |
| 7173 | 7182 | global_options: Compilation.Config, |
| ... | ... | @@ -7176,6 +7185,7 @@ fn createEmptyDependenciesModule( |
| 7176 | 7185 | try Package.Fetch.JobQueue.createEmptyDependenciesSource(&source); |
| 7177 | 7186 | _ = try createDependenciesModule( |
| 7178 | 7187 | arena, |
| 7188 | io, | |
| 7179 | 7189 | source.items, |
| 7180 | 7190 | main_mod, |
| 7181 | 7191 | dirs, |
| ... | ... | @@ -7187,6 +7197,7 @@ fn createEmptyDependenciesModule( |
| 7187 | 7197 | /// build runner to obtain via `@import("@dependencies")`. |
| 7188 | 7198 | fn createDependenciesModule( |
| 7189 | 7199 | arena: Allocator, |
| 7200 | io: Io, | |
| 7190 | 7201 | source: []const u8, |
| 7191 | 7202 | main_mod: *Package.Module, |
| 7192 | 7203 | dirs: Compilation.Directories, |
| ... | ... | @@ -7197,9 +7208,9 @@ fn createDependenciesModule( |
| 7197 | 7208 | const rand_int = std.crypto.random.int(u64); |
| 7198 | 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, .{}); | |
| 7201 | defer tmp_dir.close(); | |
| 7202 | try tmp_dir.writeFile(.{ .sub_path = basename, .data = source }); | |
| 7211 | var tmp_dir = try dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}); | |
| 7212 | defer tmp_dir.close(io); | |
| 7213 | try tmp_dir.writeFile(io, .{ .sub_path = basename, .data = source }); | |
| 7203 | 7214 | } |
| 7204 | 7215 | |
| 7205 | 7216 | var hh: Cache.HashHelper = .{}; |
| ... | ... | @@ -7208,11 +7219,7 @@ fn createDependenciesModule( |
| 7208 | 7219 | const hex_digest = hh.final(); |
| 7209 | 7220 | |
| 7210 | 7221 | const o_dir_sub_path = try arena.dupe(u8, "o" ++ fs.path.sep_str ++ hex_digest); |
| 7211 | try Package.Fetch.renameTmpIntoCache( | |
| 7212 | dirs.local_cache.handle, | |
| 7213 | tmp_dir_sub_path, | |
| 7214 | o_dir_sub_path, | |
| 7215 | ); | |
| 7222 | try Package.Fetch.renameTmpIntoCache(io, dirs.local_cache.handle, tmp_dir_sub_path, o_dir_sub_path); | |
| 7216 | 7223 | |
| 7217 | 7224 | const deps_mod = try Package.Module.create(arena, .{ |
| 7218 | 7225 | .paths = .{ |
| ... | ... | @@ -7232,10 +7239,10 @@ fn createDependenciesModule( |
| 7232 | 7239 | const BuildRoot = struct { |
| 7233 | 7240 | directory: Cache.Directory, |
| 7234 | 7241 | build_zig_basename: []const u8, |
| 7235 | cleanup_build_dir: ?fs.Dir, | |
| 7242 | cleanup_build_dir: ?Io.Dir, | |
| 7236 | 7243 | |
| 7237 | fn deinit(br: *BuildRoot) void { | |
| 7238 | if (br.cleanup_build_dir) |*dir| dir.close(); | |
| 7244 | fn deinit(br: *BuildRoot, io: Io) void { | |
| 7245 | if (br.cleanup_build_dir) |*dir| dir.close(io); | |
| 7239 | 7246 | br.* = undefined; |
| 7240 | 7247 | } |
| 7241 | 7248 | }; |
| ... | ... | @@ -7245,7 +7252,7 @@ const FindBuildRootOptions = struct { |
| 7245 | 7252 | cwd_path: ?[]const u8 = null, |
| 7246 | 7253 | }; |
| 7247 | 7254 | |
| 7248 | fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { | |
| 7255 | fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !BuildRoot { | |
| 7249 | 7256 | const cwd_path = options.cwd_path orelse try introspect.getResolvedCwd(arena); |
| 7250 | 7257 | const build_zig_basename = if (options.build_file) |bf| |
| 7251 | 7258 | fs.path.basename(bf) |
| ... | ... | @@ -7254,7 +7261,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { |
| 7254 | 7261 | |
| 7255 | 7262 | if (options.build_file) |bf| { |
| 7256 | 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 | 7265 | fatal("unable to open directory to build file from argument 'build-file', '{s}': {s}", .{ dirname, @errorName(err) }); |
| 7259 | 7266 | }; |
| 7260 | 7267 | return .{ |
| ... | ... | @@ -7266,7 +7273,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { |
| 7266 | 7273 | |
| 7267 | 7274 | return .{ |
| 7268 | 7275 | .build_zig_basename = build_zig_basename, |
| 7269 | .directory = .{ .path = null, .handle = fs.cwd() }, | |
| 7276 | .directory = .{ .path = null, .handle = Io.Dir.cwd() }, | |
| 7270 | 7277 | .cleanup_build_dir = null, |
| 7271 | 7278 | }; |
| 7272 | 7279 | } |
| ... | ... | @@ -7274,8 +7281,8 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { |
| 7274 | 7281 | var dirname: []const u8 = cwd_path; |
| 7275 | 7282 | while (true) { |
| 7276 | 7283 | const joined_path = try fs.path.join(arena, &[_][]const u8{ dirname, build_zig_basename }); |
| 7277 | if (fs.cwd().access(joined_path, .{})) |_| { | |
| 7278 | const dir = fs.cwd().openDir(dirname, .{}) catch |err| { | |
| 7284 | if (Io.Dir.cwd().access(io, joined_path, .{})) |_| { | |
| 7285 | const dir = Io.Dir.cwd().openDir(io, dirname, .{}) catch |err| { | |
| 7279 | 7286 | fatal("unable to open directory while searching for build.zig file, '{s}': {s}", .{ dirname, @errorName(err) }); |
| 7280 | 7287 | }; |
| 7281 | 7288 | return .{ |
| ... | ... | @@ -7304,17 +7311,19 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { |
| 7304 | 7311 | |
| 7305 | 7312 | const LoadManifestOptions = struct { |
| 7306 | 7313 | root_name: []const u8, |
| 7307 | dir: fs.Dir, | |
| 7314 | dir: Io.Dir, | |
| 7308 | 7315 | color: Color, |
| 7309 | 7316 | }; |
| 7310 | 7317 | |
| 7311 | 7318 | fn loadManifest( |
| 7312 | 7319 | gpa: Allocator, |
| 7313 | 7320 | arena: Allocator, |
| 7321 | io: Io, | |
| 7314 | 7322 | options: LoadManifestOptions, |
| 7315 | 7323 | ) !struct { Package.Manifest, Ast } { |
| 7316 | 7324 | const manifest_bytes = while (true) { |
| 7317 | 7325 | break options.dir.readFileAllocOptions( |
| 7326 | io, | |
| 7318 | 7327 | Package.Manifest.basename, |
| 7319 | 7328 | arena, |
| 7320 | 7329 | .limited(Package.Manifest.max_bytes), |
| ... | ... | @@ -7322,7 +7331,7 @@ fn loadManifest( |
| 7322 | 7331 | 0, |
| 7323 | 7332 | ) catch |err| switch (err) { |
| 7324 | 7333 | error.FileNotFound => { |
| 7325 | writeSimpleTemplateFile(Package.Manifest.basename, | |
| 7334 | writeSimpleTemplateFile(io, Package.Manifest.basename, | |
| 7326 | 7335 | \\.{{ |
| 7327 | 7336 | \\ .name = .{s}, |
| 7328 | 7337 | \\ .version = "{s}", |
| ... | ... | @@ -7348,7 +7357,7 @@ fn loadManifest( |
| 7348 | 7357 | errdefer ast.deinit(gpa); |
| 7349 | 7358 | |
| 7350 | 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 | 7361 | process.exit(2); |
| 7353 | 7362 | } |
| 7354 | 7363 | |
| ... | ... | @@ -7365,7 +7374,7 @@ fn loadManifest( |
| 7365 | 7374 | |
| 7366 | 7375 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 7367 | 7376 | defer error_bundle.deinit(gpa); |
| 7368 | error_bundle.renderToStdErr(.{}, options.color); | |
| 7377 | error_bundle.renderToStderr(io, .{}, options.color) catch {}; | |
| 7369 | 7378 | |
| 7370 | 7379 | process.exit(2); |
| 7371 | 7380 | } |
| ... | ... | @@ -7374,12 +7383,12 @@ fn loadManifest( |
| 7374 | 7383 | |
| 7375 | 7384 | const Templates = struct { |
| 7376 | 7385 | zig_lib_directory: Cache.Directory, |
| 7377 | dir: fs.Dir, | |
| 7386 | dir: Io.Dir, | |
| 7378 | 7387 | buffer: std.array_list.Managed(u8), |
| 7379 | 7388 | |
| 7380 | fn deinit(templates: *Templates) void { | |
| 7381 | templates.zig_lib_directory.handle.close(); | |
| 7382 | templates.dir.close(); | |
| 7389 | fn deinit(templates: *Templates, io: Io) void { | |
| 7390 | templates.zig_lib_directory.handle.close(io); | |
| 7391 | templates.dir.close(io); | |
| 7383 | 7392 | templates.buffer.deinit(); |
| 7384 | 7393 | templates.* = undefined; |
| 7385 | 7394 | } |
| ... | ... | @@ -7387,20 +7396,21 @@ const Templates = struct { |
| 7387 | 7396 | fn write( |
| 7388 | 7397 | templates: *Templates, |
| 7389 | 7398 | arena: Allocator, |
| 7390 | out_dir: fs.Dir, | |
| 7399 | io: Io, | |
| 7400 | out_dir: Io.Dir, | |
| 7391 | 7401 | root_name: []const u8, |
| 7392 | 7402 | template_path: []const u8, |
| 7393 | 7403 | fingerprint: Package.Fingerprint, |
| 7394 | 7404 | ) !void { |
| 7395 | 7405 | if (fs.path.dirname(template_path)) |dirname| { |
| 7396 | out_dir.makePath(dirname) catch |err| { | |
| 7397 | fatal("unable to make path '{s}': {s}", .{ dirname, @errorName(err) }); | |
| 7406 | out_dir.createDirPath(io, dirname) catch |err| { | |
| 7407 | fatal("unable to make path '{s}': {t}", .{ dirname, err }); | |
| 7398 | 7408 | }; |
| 7399 | 7409 | } |
| 7400 | 7410 | |
| 7401 | 7411 | const max_bytes = 10 * 1024 * 1024; |
| 7402 | const contents = templates.dir.readFileAlloc(template_path, arena, .limited(max_bytes)) catch |err| { | |
| 7403 | fatal("unable to read template file '{s}': {s}", .{ template_path, @errorName(err) }); | |
| 7412 | const contents = templates.dir.readFileAlloc(io, template_path, arena, .limited(max_bytes)) catch |err| { | |
| 7413 | fatal("unable to read template file '{s}': {t}", .{ template_path, err }); | |
| 7404 | 7414 | }; |
| 7405 | 7415 | templates.buffer.clearRetainingCapacity(); |
| 7406 | 7416 | try templates.buffer.ensureUnusedCapacity(contents.len); |
| ... | ... | @@ -7428,39 +7438,39 @@ const Templates = struct { |
| 7428 | 7438 | i += 1; |
| 7429 | 7439 | } |
| 7430 | 7440 | |
| 7431 | return out_dir.writeFile(.{ | |
| 7441 | return out_dir.writeFile(io, .{ | |
| 7432 | 7442 | .sub_path = template_path, |
| 7433 | 7443 | .data = templates.buffer.items, |
| 7434 | 7444 | .flags = .{ .exclusive = true }, |
| 7435 | 7445 | }); |
| 7436 | 7446 | } |
| 7437 | 7447 | }; |
| 7438 | fn writeSimpleTemplateFile(file_name: []const u8, comptime fmt: []const u8, args: anytype) !void { | |
| 7439 | const f = try fs.cwd().createFile(file_name, .{ .exclusive = true }); | |
| 7440 | defer f.close(); | |
| 7448 | fn writeSimpleTemplateFile(io: Io, file_name: []const u8, comptime fmt: []const u8, args: anytype) !void { | |
| 7449 | const f = try Io.Dir.cwd().createFile(io, file_name, .{ .exclusive = true }); | |
| 7450 | defer f.close(io); | |
| 7441 | 7451 | var buf: [4096]u8 = undefined; |
| 7442 | var fw = f.writer(&buf); | |
| 7452 | var fw = f.writer(io, &buf); | |
| 7443 | 7453 | try fw.interface.print(fmt, args); |
| 7444 | 7454 | try fw.interface.flush(); |
| 7445 | 7455 | } |
| 7446 | 7456 | |
| 7447 | fn findTemplates(gpa: Allocator, arena: Allocator) Templates { | |
| 7457 | fn findTemplates(gpa: Allocator, arena: Allocator, io: Io) Templates { | |
| 7448 | 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| { | |
| 7452 | fatal("unable to find self exe path: {s}", .{@errorName(err)}); | |
| 7461 | const self_exe_path = process.executablePathAlloc(io, arena) catch |err| { | |
| 7462 | fatal("unable to find self exe path: {t}", .{err}); | |
| 7453 | 7463 | }; |
| 7454 | var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, cwd_path, self_exe_path) catch |err| { | |
| 7455 | fatal("unable to find zig installation directory '{s}': {s}", .{ self_exe_path, @errorName(err) }); | |
| 7464 | var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, io, cwd_path, self_exe_path) catch |err| { | |
| 7465 | fatal("unable to find zig installation directory '{s}': {t}", .{ self_exe_path, err }); | |
| 7456 | 7466 | }; |
| 7457 | 7467 | |
| 7458 | 7468 | const s = fs.path.sep_str; |
| 7459 | 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 | 7471 | const path = zig_lib_directory.path orelse "."; |
| 7462 | fatal("unable to open zig project template directory '{s}{s}{s}': {s}", .{ | |
| 7463 | path, s, template_sub_path, @errorName(err), | |
| 7472 | fatal("unable to open zig project template directory '{s}{s}{s}': {t}", .{ | |
| 7473 | path, s, template_sub_path, err, | |
| 7464 | 7474 | }); |
| 7465 | 7475 | }; |
| 7466 | 7476 | |
| ... | ... | @@ -7574,17 +7584,18 @@ fn anyObjectLinkInputs(link_inputs: []const link.UnresolvedInput) bool { |
| 7574 | 7584 | return false; |
| 7575 | 7585 | } |
| 7576 | 7586 | |
| 7577 | fn addLibDirectoryWarn(lib_directories: *std.ArrayList(Directory), path: []const u8) void { | |
| 7578 | return addLibDirectoryWarn2(lib_directories, path, false); | |
| 7587 | fn addLibDirectoryWarn(io: Io, lib_directories: *std.ArrayList(Directory), path: []const u8) void { | |
| 7588 | return addLibDirectoryWarn2(io, lib_directories, path, false); | |
| 7579 | 7589 | } |
| 7580 | 7590 | |
| 7581 | 7591 | fn addLibDirectoryWarn2( |
| 7592 | io: Io, | |
| 7582 | 7593 | lib_directories: *std.ArrayList(Directory), |
| 7583 | 7594 | path: []const u8, |
| 7584 | 7595 | ignore_not_found: bool, |
| 7585 | 7596 | ) void { |
| 7586 | 7597 | lib_directories.appendAssumeCapacity(.{ |
| 7587 | .handle = fs.cwd().openDir(path, .{}) catch |err| { | |
| 7598 | .handle = Io.Dir.cwd().openDir(io, path, .{}) catch |err| { | |
| 7588 | 7599 | if (err == error.FileNotFound and ignore_not_found) return; |
| 7589 | 7600 | warn("unable to open library directory '{s}': {s}", .{ path, @errorName(err) }); |
| 7590 | 7601 | return; |
src/print_env.zig+11-6| ... | ... | @@ -1,13 +1,17 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 3 | const build_options = @import("build_options"); | |
| 4 | const Compilation = @import("Compilation.zig"); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | 5 | const Allocator = std.mem.Allocator; |
| 6 | 6 | const EnvVar = std.zig.EnvVar; |
| 7 | 7 | const fatal = std.process.fatal; |
| 8 | 8 | |
| 9 | const build_options = @import("build_options"); | |
| 10 | const Compilation = @import("Compilation.zig"); | |
| 11 | ||
| 9 | 12 | pub fn cmdEnv( |
| 10 | 13 | arena: Allocator, |
| 14 | io: Io, | |
| 11 | 15 | out: *std.Io.Writer, |
| 12 | 16 | args: []const []const u8, |
| 13 | 17 | wasi_preopens: switch (builtin.target.os.tag) { |
| ... | ... | @@ -21,20 +25,21 @@ pub fn cmdEnv( |
| 21 | 25 | |
| 22 | 26 | const self_exe_path = switch (builtin.target.os.tag) { |
| 23 | 27 | .wasi => args[0], |
| 24 | else => std.fs.selfExePathAlloc(arena) catch |err| { | |
| 25 | fatal("unable to find zig self exe path: {s}", .{@errorName(err)}); | |
| 28 | else => std.process.executablePathAlloc(io, arena) catch |err| { | |
| 29 | fatal("unable to find zig self exe path: {t}", .{err}); | |
| 26 | 30 | }, |
| 27 | 31 | }; |
| 28 | 32 | |
| 29 | 33 | var dirs: Compilation.Directories = .init( |
| 30 | 34 | arena, |
| 35 | io, | |
| 31 | 36 | override_lib_dir, |
| 32 | 37 | override_global_cache_dir, |
| 33 | 38 | .global, |
| 34 | 39 | if (builtin.target.os.tag == .wasi) wasi_preopens, |
| 35 | 40 | if (builtin.target.os.tag != .wasi) self_exe_path, |
| 36 | 41 | ); |
| 37 | defer dirs.deinit(); | |
| 42 | defer dirs.deinit(io); | |
| 38 | 43 | |
| 39 | 44 | const zig_lib_dir = dirs.zig_lib.path orelse ""; |
| 40 | 45 | const zig_std_dir = try dirs.zig_lib.join(arena, &.{"std"}); |
src/print_targets.zig+10-9| ... | ... | @@ -1,35 +1,38 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const fs = std.fs; |
| 3 | 4 | const mem = std.mem; |
| 4 | 5 | const meta = std.meta; |
| 5 | 6 | const fatal = std.process.fatal; |
| 6 | 7 | const Allocator = std.mem.Allocator; |
| 7 | 8 | const Target = std.Target; |
| 8 | const target = @import("target.zig"); | |
| 9 | 9 | const assert = std.debug.assert; |
| 10 | ||
| 10 | 11 | const glibc = @import("libs/glibc.zig"); |
| 11 | 12 | const introspect = @import("introspect.zig"); |
| 13 | const target = @import("target.zig"); | |
| 12 | 14 | |
| 13 | 15 | pub fn cmdTargets( |
| 14 | 16 | allocator: Allocator, |
| 17 | io: Io, | |
| 15 | 18 | args: []const []const u8, |
| 16 | 19 | out: *std.Io.Writer, |
| 17 | 20 | native_target: *const Target, |
| 18 | 21 | ) !void { |
| 19 | 22 | _ = args; |
| 20 | var zig_lib_directory = introspect.findZigLibDir(allocator) catch |err| { | |
| 21 | fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)}); | |
| 22 | }; | |
| 23 | defer zig_lib_directory.handle.close(); | |
| 23 | var zig_lib_directory = introspect.findZigLibDir(allocator, io) catch |err| | |
| 24 | fatal("unable to find zig installation directory: {t}", .{err}); | |
| 25 | defer zig_lib_directory.handle.close(io); | |
| 24 | 26 | defer allocator.free(zig_lib_directory.path.?); |
| 25 | 27 | |
| 26 | 28 | const abilists_contents = zig_lib_directory.handle.readFileAlloc( |
| 29 | io, | |
| 27 | 30 | glibc.abilists_path, |
| 28 | 31 | allocator, |
| 29 | 32 | .limited(glibc.abilists_max_size), |
| 30 | 33 | ) catch |err| switch (err) { |
| 31 | 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 | 37 | defer allocator.free(abilists_contents); |
| 35 | 38 | |
| ... | ... | @@ -48,9 +51,7 @@ pub fn cmdTargets( |
| 48 | 51 | { |
| 49 | 52 | var libc_obj = try root_obj.beginTupleField("libc", .{}); |
| 50 | 53 | for (std.zig.target.available_libcs) |libc| { |
| 51 | const tmp = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ | |
| 52 | @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi), | |
| 53 | }); | |
| 54 | const tmp = try std.fmt.allocPrint(allocator, "{t}-{t}-{t}", .{ libc.arch, libc.os, libc.abi }); | |
| 54 | 55 | defer allocator.free(tmp); |
| 55 | 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 | 939 | return wasi_errno_success; |
| 940 | 940 | } |
| 941 | 941 | |
| 942 | uint32_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 | ||
| 958 | uint32_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 | ||
| 976 | uint32_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 | ||
| 942 | 994 | uint32_t wasi_snapshot_preview1_path_unlink_file(uint32_t fd, uint32_t path, uint32_t path_len) { |
| 943 | 995 | uint8_t *const m = *wasm_memory; |
| 944 | 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 | 1090 | return wasi_errno_success; |
| 1039 | 1091 | } |
| 1040 | 1092 | |
| 1093 | uint32_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 | ||
| 1041 | 1102 | uint32_t wasi_snapshot_preview1_poll_oneoff(uint32_t in, uint32_t out, uint32_t nsubscriptions, uint32_t res_nevents) { |
| 1042 | 1103 | (void)in; |
| 1043 | 1104 | (void)out; |
test/cases/disable_stack_tracing.zig+3-3| ... | ... | @@ -5,16 +5,16 @@ pub const std_options: std.Options = .{ |
| 5 | 5 | pub fn main() !void { |
| 6 | 6 | var st_buf: [8]usize = undefined; |
| 7 | 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); | |
| 9 | 9 | |
| 10 | 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 | 12 | try stdout.interface.print("stack trace index: {d}\n", .{captured_st.index}); |
| 13 | 13 | |
| 14 | 14 | try stdout.interface.flush(); |
| 15 | 15 | } |
| 16 | 16 | fn 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 | 18 | return std.debug.captureCurrentStackTrace(.{}, st_buf); |
| 19 | 19 | } |
| 20 | 20 |
test/incremental/add_decl+13-7| ... | ... | @@ -7,57 +7,63 @@ |
| 7 | 7 | #file=main.zig |
| 8 | 8 | const std = @import("std"); |
| 9 | 9 | pub fn main() !void { |
| 10 | try std.fs.File.stdout().writeAll(foo); | |
| 10 | try std.Io.File.stdout().writeStreamingAll(io, foo); | |
| 11 | 11 | } |
| 12 | 12 | const foo = "good morning\n"; |
| 13 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 13 | 14 | #expect_stdout="good morning\n" |
| 14 | 15 | |
| 15 | 16 | #update=add new declaration |
| 16 | 17 | #file=main.zig |
| 17 | 18 | const std = @import("std"); |
| 18 | 19 | pub fn main() !void { |
| 19 | try std.fs.File.stdout().writeAll(foo); | |
| 20 | try std.Io.File.stdout().writeStreamingAll(io, foo); | |
| 20 | 21 | } |
| 21 | 22 | const foo = "good morning\n"; |
| 22 | 23 | const bar = "good evening\n"; |
| 24 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 23 | 25 | #expect_stdout="good morning\n" |
| 24 | 26 | |
| 25 | 27 | #update=reference new declaration |
| 26 | 28 | #file=main.zig |
| 27 | 29 | const std = @import("std"); |
| 28 | 30 | pub fn main() !void { |
| 29 | try std.fs.File.stdout().writeAll(bar); | |
| 31 | try std.Io.File.stdout().writeStreamingAll(io, bar); | |
| 30 | 32 | } |
| 31 | 33 | const foo = "good morning\n"; |
| 32 | 34 | const bar = "good evening\n"; |
| 35 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 33 | 36 | #expect_stdout="good evening\n" |
| 34 | 37 | |
| 35 | 38 | #update=reference missing declaration |
| 36 | 39 | #file=main.zig |
| 37 | 40 | const std = @import("std"); |
| 38 | 41 | pub fn main() !void { |
| 39 | try std.fs.File.stdout().writeAll(qux); | |
| 42 | try std.Io.File.stdout().writeStreamingAll(io, qux); | |
| 40 | 43 | } |
| 41 | 44 | const foo = "good morning\n"; |
| 42 | 45 | const bar = "good evening\n"; |
| 43 | #expect_error=main.zig:3:39: error: use of undeclared identifier 'qux' | |
| 46 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 47 | #expect_error=main.zig:3:52: error: use of undeclared identifier 'qux' | |
| 44 | 48 | |
| 45 | 49 | #update=add missing declaration |
| 46 | 50 | #file=main.zig |
| 47 | 51 | const std = @import("std"); |
| 48 | 52 | pub fn main() !void { |
| 49 | try std.fs.File.stdout().writeAll(qux); | |
| 53 | try std.Io.File.stdout().writeStreamingAll(io, qux); | |
| 50 | 54 | } |
| 51 | 55 | const foo = "good morning\n"; |
| 52 | 56 | const bar = "good evening\n"; |
| 53 | 57 | const qux = "good night\n"; |
| 58 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 54 | 59 | #expect_stdout="good night\n" |
| 55 | 60 | |
| 56 | 61 | #update=remove unused declarations |
| 57 | 62 | #file=main.zig |
| 58 | 63 | const std = @import("std"); |
| 59 | 64 | pub fn main() !void { |
| 60 | try std.fs.File.stdout().writeAll(qux); | |
| 65 | try std.Io.File.stdout().writeStreamingAll(io, qux); | |
| 61 | 66 | } |
| 62 | 67 | const qux = "good night\n"; |
| 68 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 63 | 69 | #expect_stdout="good night\n" |
test/incremental/add_decl_namespaced+13-7| ... | ... | @@ -7,58 +7,64 @@ |
| 7 | 7 | #file=main.zig |
| 8 | 8 | const std = @import("std"); |
| 9 | 9 | pub fn main() !void { |
| 10 | try std.fs.File.stdout().writeAll(@This().foo); | |
| 10 | try std.Io.File.stdout().writeStreamingAll(io, @This().foo); | |
| 11 | 11 | } |
| 12 | 12 | const foo = "good morning\n"; |
| 13 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 13 | 14 | #expect_stdout="good morning\n" |
| 14 | 15 | |
| 15 | 16 | #update=add new declaration |
| 16 | 17 | #file=main.zig |
| 17 | 18 | const std = @import("std"); |
| 18 | 19 | pub fn main() !void { |
| 19 | try std.fs.File.stdout().writeAll(@This().foo); | |
| 20 | try std.Io.File.stdout().writeStreamingAll(io, @This().foo); | |
| 20 | 21 | } |
| 21 | 22 | const foo = "good morning\n"; |
| 22 | 23 | const bar = "good evening\n"; |
| 24 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 23 | 25 | #expect_stdout="good morning\n" |
| 24 | 26 | |
| 25 | 27 | #update=reference new declaration |
| 26 | 28 | #file=main.zig |
| 27 | 29 | const std = @import("std"); |
| 28 | 30 | pub fn main() !void { |
| 29 | try std.fs.File.stdout().writeAll(@This().bar); | |
| 31 | try std.Io.File.stdout().writeStreamingAll(io, @This().bar); | |
| 30 | 32 | } |
| 31 | 33 | const foo = "good morning\n"; |
| 32 | 34 | const bar = "good evening\n"; |
| 35 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 33 | 36 | #expect_stdout="good evening\n" |
| 34 | 37 | |
| 35 | 38 | #update=reference missing declaration |
| 36 | 39 | #file=main.zig |
| 37 | 40 | const std = @import("std"); |
| 38 | 41 | pub fn main() !void { |
| 39 | try std.fs.File.stdout().writeAll(@This().qux); | |
| 42 | try std.Io.File.stdout().writeStreamingAll(io, @This().qux); | |
| 40 | 43 | } |
| 41 | 44 | const foo = "good morning\n"; |
| 42 | 45 | const bar = "good evening\n"; |
| 43 | #expect_error=main.zig:3:46: error: root source file struct 'main' has no member named 'qux' | |
| 46 | const 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 | 48 | #expect_error=main.zig:1:1: note: struct declared here |
| 45 | 49 | |
| 46 | 50 | #update=add missing declaration |
| 47 | 51 | #file=main.zig |
| 48 | 52 | const std = @import("std"); |
| 49 | 53 | pub fn main() !void { |
| 50 | try std.fs.File.stdout().writeAll(@This().qux); | |
| 54 | try std.Io.File.stdout().writeStreamingAll(io, @This().qux); | |
| 51 | 55 | } |
| 52 | 56 | const foo = "good morning\n"; |
| 53 | 57 | const bar = "good evening\n"; |
| 54 | 58 | const qux = "good night\n"; |
| 59 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 55 | 60 | #expect_stdout="good night\n" |
| 56 | 61 | |
| 57 | 62 | #update=remove unused declarations |
| 58 | 63 | #file=main.zig |
| 59 | 64 | const std = @import("std"); |
| 60 | 65 | pub fn main() !void { |
| 61 | try std.fs.File.stdout().writeAll(@This().qux); | |
| 66 | try std.Io.File.stdout().writeStreamingAll(io, @This().qux); | |
| 62 | 67 | } |
| 63 | 68 | const qux = "good night\n"; |
| 69 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 64 | 70 | #expect_stdout="good night\n" |
test/incremental/bad_import+4-2| ... | ... | @@ -8,9 +8,10 @@ |
| 8 | 8 | #file=main.zig |
| 9 | 9 | pub fn main() !void { |
| 10 | 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 | } |
| 13 | 13 | const std = @import("std"); |
| 14 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 14 | 15 | #file=foo.zig |
| 15 | 16 | comptime { |
| 16 | 17 | _ = @import("bad.zig"); |
| ... | ... | @@ -30,7 +31,8 @@ comptime { |
| 30 | 31 | #file=main.zig |
| 31 | 32 | pub fn main() !void { |
| 32 | 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 | } |
| 35 | 36 | const std = @import("std"); |
| 37 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 36 | 38 | #expect_stdout="success\n" |
test/incremental/change_embed_file+6-3| ... | ... | @@ -8,8 +8,9 @@ |
| 8 | 8 | const std = @import("std"); |
| 9 | 9 | const string = @embedFile("string.txt"); |
| 10 | 10 | pub fn main() !void { |
| 11 | try std.fs.File.stdout().writeAll(string); | |
| 11 | try std.Io.File.stdout().writeStreamingAll(io, string); | |
| 12 | 12 | } |
| 13 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 13 | 14 | #file=string.txt |
| 14 | 15 | Hello, World! |
| 15 | 16 | #expect_stdout="Hello, World!\n" |
| ... | ... | @@ -28,8 +29,9 @@ Hello again, World! |
| 28 | 29 | const std = @import("std"); |
| 29 | 30 | const string = @embedFile("string.txt"); |
| 30 | 31 | pub 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 | } |
| 34 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 33 | 35 | #expect_stdout="a hardcoded string\n" |
| 34 | 36 | |
| 35 | 37 | #update=re-introduce reference to file |
| ... | ... | @@ -37,8 +39,9 @@ pub fn main() !void { |
| 37 | 39 | const std = @import("std"); |
| 38 | 40 | const string = @embedFile("string.txt"); |
| 39 | 41 | pub fn main() !void { |
| 40 | try std.fs.File.stdout().writeAll(string); | |
| 42 | try std.Io.File.stdout().writeStreamingAll(io, string); | |
| 41 | 43 | } |
| 44 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 42 | 45 | #expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound |
| 43 | 46 | |
| 44 | 47 | #update=recreate file |
test/incremental/change_enum_tag_type+6-3| ... | ... | @@ -15,10 +15,11 @@ const Foo = enum(Tag) { |
| 15 | 15 | pub fn main() !void { |
| 16 | 16 | var val: Foo = undefined; |
| 17 | 17 | val = .a; |
| 18 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 18 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 19 | 19 | try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); |
| 20 | 20 | } |
| 21 | 21 | const std = @import("std"); |
| 22 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 22 | 23 | #expect_stdout="a\n" |
| 23 | 24 | #update=too many enum fields |
| 24 | 25 | #file=main.zig |
| ... | ... | @@ -33,7 +34,7 @@ const Foo = enum(Tag) { |
| 33 | 34 | pub fn main() !void { |
| 34 | 35 | var val: Foo = undefined; |
| 35 | 36 | val = .a; |
| 36 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 37 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 37 | 38 | try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); |
| 38 | 39 | } |
| 39 | 40 | comptime { |
| ... | ... | @@ -42,6 +43,7 @@ comptime { |
| 42 | 43 | std.debug.assert(@TypeOf(@intFromEnum(Foo.e)) == Tag); |
| 43 | 44 | } |
| 44 | 45 | const std = @import("std"); |
| 46 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 45 | 47 | #expect_error=main.zig:7:5: error: enumeration value '4' too large for type 'u2' |
| 46 | 48 | #update=increase tag size |
| 47 | 49 | #file=main.zig |
| ... | ... | @@ -56,8 +58,9 @@ const Foo = enum(Tag) { |
| 56 | 58 | pub fn main() !void { |
| 57 | 59 | var val: Foo = undefined; |
| 58 | 60 | val = .a; |
| 59 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 61 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 60 | 62 | try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); |
| 61 | 63 | } |
| 62 | 64 | const std = @import("std"); |
| 65 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 63 | 66 | #expect_stdout="a\n" |
test/incremental/change_exports+12-6| ... | ... | @@ -17,10 +17,11 @@ pub fn main() !void { |
| 17 | 17 | extern const bar: u32; |
| 18 | 18 | }; |
| 19 | 19 | S.foo(); |
| 20 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 20 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 21 | 21 | try stdout_writer.interface.print("{}\n", .{S.bar}); |
| 22 | 22 | } |
| 23 | 23 | const std = @import("std"); |
| 24 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 24 | 25 | #expect_stdout="123\n" |
| 25 | 26 | |
| 26 | 27 | #update=add conflict |
| ... | ... | @@ -39,10 +40,11 @@ pub fn main() !void { |
| 39 | 40 | extern const other: u32; |
| 40 | 41 | }; |
| 41 | 42 | S.foo(); |
| 42 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 43 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 43 | 44 | try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); |
| 44 | 45 | } |
| 45 | 46 | const std = @import("std"); |
| 47 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 46 | 48 | #expect_error=main.zig:6:5: error: exported symbol collision: foo |
| 47 | 49 | #expect_error=main.zig:1:1: note: other symbol here |
| 48 | 50 | |
| ... | ... | @@ -62,10 +64,11 @@ pub fn main() !void { |
| 62 | 64 | extern const other: u32; |
| 63 | 65 | }; |
| 64 | 66 | S.foo(); |
| 65 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 67 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 66 | 68 | try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); |
| 67 | 69 | } |
| 68 | 70 | const std = @import("std"); |
| 71 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 69 | 72 | #expect_stdout="123 456\n" |
| 70 | 73 | |
| 71 | 74 | #update=put exports in decl |
| ... | ... | @@ -87,10 +90,11 @@ pub fn main() !void { |
| 87 | 90 | extern const other: u32; |
| 88 | 91 | }; |
| 89 | 92 | S.foo(); |
| 90 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 93 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 91 | 94 | try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); |
| 92 | 95 | } |
| 93 | 96 | const std = @import("std"); |
| 97 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 94 | 98 | #expect_stdout="123 456\n" |
| 95 | 99 | |
| 96 | 100 | #update=remove reference to exporting decl |
| ... | ... | @@ -133,10 +137,11 @@ pub fn main() !void { |
| 133 | 137 | extern const other: u32; |
| 134 | 138 | }; |
| 135 | 139 | S.foo(); |
| 136 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 140 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 137 | 141 | try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); |
| 138 | 142 | } |
| 139 | 143 | const std = @import("std"); |
| 144 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 140 | 145 | #expect_stdout="123 456\n" |
| 141 | 146 | |
| 142 | 147 | #update=reintroduce reference to exporting decl, introducing conflict |
| ... | ... | @@ -158,10 +163,11 @@ pub fn main() !void { |
| 158 | 163 | extern const other: u32; |
| 159 | 164 | }; |
| 160 | 165 | S.foo(); |
| 161 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 166 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 162 | 167 | try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); |
| 163 | 168 | } |
| 164 | 169 | const std = @import("std"); |
| 170 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 165 | 171 | #expect_error=main.zig:5:5: error: exported symbol collision: bar |
| 166 | 172 | #expect_error=main.zig:2:1: note: other symbol here |
| 167 | 173 | #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 | 8 | try foo(123); |
| 9 | 9 | } |
| 10 | 10 | fn foo(x: u8) !void { |
| 11 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 11 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 12 | 12 | return stdout_writer.interface.print("{d}\n", .{x}); |
| 13 | 13 | } |
| 14 | 14 | const std = @import("std"); |
| 15 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 15 | 16 | #expect_stdout="123\n" |
| 16 | 17 | |
| 17 | 18 | #update=change function type |
| ... | ... | @@ -20,10 +21,11 @@ pub fn main() !void { |
| 20 | 21 | try foo(123); |
| 21 | 22 | } |
| 22 | 23 | fn foo(x: i64) !void { |
| 23 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 24 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 24 | 25 | return stdout_writer.interface.print("{d}\n", .{x}); |
| 25 | 26 | } |
| 26 | 27 | const std = @import("std"); |
| 28 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 27 | 29 | #expect_stdout="123\n" |
| 28 | 30 | |
| 29 | 31 | #update=change function argument |
| ... | ... | @@ -32,8 +34,9 @@ pub fn main() !void { |
| 32 | 34 | try foo(-42); |
| 33 | 35 | } |
| 34 | 36 | fn foo(x: i64) !void { |
| 35 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 37 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 36 | 38 | return stdout_writer.interface.print("{d}\n", .{x}); |
| 37 | 39 | } |
| 38 | 40 | const std = @import("std"); |
| 41 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 39 | 42 | #expect_stdout="-42\n" |
test/incremental/change_generic_line_number+4-2| ... | ... | @@ -4,10 +4,11 @@ |
| 4 | 4 | #update=initial version |
| 5 | 5 | #file=main.zig |
| 6 | 6 | const std = @import("std"); |
| 7 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 7 | 8 | fn Printer(message: []const u8) type { |
| 8 | 9 | return struct { |
| 9 | 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 | 20 | #update=change line number |
| 20 | 21 | #file=main.zig |
| 21 | 22 | const std = @import("std"); |
| 23 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 22 | 24 | |
| 23 | 25 | fn Printer(message: []const u8) type { |
| 24 | 26 | return struct { |
| 25 | 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 | 5 | #file=main.zig |
| 6 | 6 | const std = @import("std"); |
| 7 | 7 | pub fn main() !void { |
| 8 | try std.fs.File.stdout().writeAll("foo\n"); | |
| 8 | try std.Io.File.stdout().writeStreamingAll(io, "foo\n"); | |
| 9 | 9 | } |
| 10 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 10 | 11 | #expect_stdout="foo\n" |
| 11 | 12 | #update=change line number |
| 12 | 13 | #file=main.zig |
| 13 | 14 | const std = @import("std"); |
| 14 | 15 | |
| 15 | 16 | pub fn main() !void { |
| 16 | try std.fs.File.stdout().writeAll("foo\n"); | |
| 17 | try std.Io.File.stdout().writeStreamingAll(io, "foo\n"); | |
| 17 | 18 | } |
| 19 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 18 | 20 | #expect_stdout="foo\n" |
test/incremental/change_panic_handler+6-3| ... | ... | @@ -12,11 +12,12 @@ pub fn main() !u8 { |
| 12 | 12 | } |
| 13 | 13 | pub const panic = std.debug.FullPanic(myPanic); |
| 14 | 14 | fn 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 | 16 | stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {}; |
| 17 | 17 | std.process.exit(0); |
| 18 | 18 | } |
| 19 | 19 | const std = @import("std"); |
| 20 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 20 | 21 | #expect_stdout="panic message: integer overflow\n" |
| 21 | 22 | |
| 22 | 23 | #update=change the panic handler body |
| ... | ... | @@ -29,11 +30,12 @@ pub fn main() !u8 { |
| 29 | 30 | } |
| 30 | 31 | pub const panic = std.debug.FullPanic(myPanic); |
| 31 | 32 | fn 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 | 34 | stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {}; |
| 34 | 35 | std.process.exit(0); |
| 35 | 36 | } |
| 36 | 37 | const std = @import("std"); |
| 38 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 37 | 39 | #expect_stdout="new panic message: integer overflow\n" |
| 38 | 40 | |
| 39 | 41 | #update=change the panic handler function value |
| ... | ... | @@ -46,9 +48,10 @@ pub fn main() !u8 { |
| 46 | 48 | } |
| 47 | 49 | pub const panic = std.debug.FullPanic(myPanicNew); |
| 48 | 50 | fn 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 | 52 | stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {}; |
| 51 | 53 | std.process.exit(0); |
| 52 | 54 | } |
| 53 | 55 | const std = @import("std"); |
| 56 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 54 | 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 | 42 | pub const noreturnReturned = no_panic.noreturnReturned; |
| 43 | 43 | }; |
| 44 | 44 | fn 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 | 46 | stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {}; |
| 47 | 47 | std.process.exit(0); |
| 48 | 48 | } |
| 49 | 49 | const std = @import("std"); |
| 50 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 50 | 51 | #expect_stdout="panic message: integer overflow\n" |
| 51 | 52 | |
| 52 | 53 | #update=change the panic handler body |
| ... | ... | @@ -89,11 +90,12 @@ pub const panic = struct { |
| 89 | 90 | pub const noreturnReturned = no_panic.noreturnReturned; |
| 90 | 91 | }; |
| 91 | 92 | fn 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 | 94 | stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {}; |
| 94 | 95 | std.process.exit(0); |
| 95 | 96 | } |
| 96 | 97 | const std = @import("std"); |
| 98 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 97 | 99 | #expect_stdout="new panic message: integer overflow\n" |
| 98 | 100 | |
| 99 | 101 | #update=change the panic handler function value |
| ... | ... | @@ -136,9 +138,10 @@ pub const panic = struct { |
| 136 | 138 | pub const noreturnReturned = no_panic.noreturnReturned; |
| 137 | 139 | }; |
| 138 | 140 | fn 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 | 142 | stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {}; |
| 141 | 143 | std.process.exit(0); |
| 142 | 144 | } |
| 143 | 145 | const std = @import("std"); |
| 146 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 144 | 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 | 9 | try foo(0x1300); |
| 10 | 10 | } |
| 11 | 11 | fn foo(x: u16) !void { |
| 12 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 12 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 13 | 13 | try stdout_writer.interface.print("0x{x}\n", .{x << 4}); |
| 14 | 14 | } |
| 15 | 15 | const std = @import("std"); |
| 16 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 16 | 17 | #expect_stdout="0x3000\n" |
| 17 | 18 | #update=change to right shift |
| 18 | 19 | #file=main.zig |
| ... | ... | @@ -20,8 +21,9 @@ pub fn main() !void { |
| 20 | 21 | try foo(0x1300); |
| 21 | 22 | } |
| 22 | 23 | fn foo(x: u16) !void { |
| 23 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 24 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 24 | 25 | try stdout_writer.interface.print("0x{x}\n", .{x >> 4}); |
| 25 | 26 | } |
| 26 | 27 | const std = @import("std"); |
| 28 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 27 | 29 | #expect_stdout="0x130\n" |
test/incremental/change_struct_same_fields+6-3| ... | ... | @@ -11,13 +11,14 @@ pub fn main() !void { |
| 11 | 11 | try foo(&val); |
| 12 | 12 | } |
| 13 | 13 | fn 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 | 15 | try stdout_writer.interface.print( |
| 16 | 16 | "{d} {d}\n", |
| 17 | 17 | .{ val.x, val.y }, |
| 18 | 18 | ); |
| 19 | 19 | } |
| 20 | 20 | const std = @import("std"); |
| 21 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 21 | 22 | #expect_stdout="100 200\n" |
| 22 | 23 | |
| 23 | 24 | #update=change struct layout |
| ... | ... | @@ -28,13 +29,14 @@ pub fn main() !void { |
| 28 | 29 | try foo(&val); |
| 29 | 30 | } |
| 30 | 31 | fn 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 | 33 | try stdout_writer.interface.print( |
| 33 | 34 | "{d} {d}\n", |
| 34 | 35 | .{ val.x, val.y }, |
| 35 | 36 | ); |
| 36 | 37 | } |
| 37 | 38 | const std = @import("std"); |
| 39 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 38 | 40 | #expect_stdout="100 200\n" |
| 39 | 41 | |
| 40 | 42 | #update=change values |
| ... | ... | @@ -45,11 +47,12 @@ pub fn main() !void { |
| 45 | 47 | try foo(&val); |
| 46 | 48 | } |
| 47 | 49 | fn 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 | 51 | try stdout_writer.interface.print( |
| 50 | 52 | "{d} {d}\n", |
| 51 | 53 | .{ val.x, val.y }, |
| 52 | 54 | ); |
| 53 | 55 | } |
| 54 | 56 | const std = @import("std"); |
| 57 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 55 | 58 | #expect_stdout="1234 5678\n" |
test/incremental/change_zon_file+6-3| ... | ... | @@ -8,8 +8,9 @@ |
| 8 | 8 | const std = @import("std"); |
| 9 | 9 | const message: []const u8 = @import("message.zon"); |
| 10 | 10 | pub fn main() !void { |
| 11 | try std.fs.File.stdout().writeAll(message); | |
| 11 | try std.Io.File.stdout().writeStreamingAll(io, message); | |
| 12 | 12 | } |
| 13 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 13 | 14 | #file=message.zon |
| 14 | 15 | "Hello, World!\n" |
| 15 | 16 | #expect_stdout="Hello, World!\n" |
| ... | ... | @@ -29,8 +30,9 @@ pub fn main() !void { |
| 29 | 30 | const std = @import("std"); |
| 30 | 31 | const message: []const u8 = @import("message.zon"); |
| 31 | 32 | pub 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 | } |
| 35 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 34 | 36 | #expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound |
| 35 | 37 | #expect_error=main.zig:2:37: note: file imported here |
| 36 | 38 | |
| ... | ... | @@ -44,6 +46,7 @@ pub fn main() !void { |
| 44 | 46 | const std = @import("std"); |
| 45 | 47 | const message: []const u8 = @import("message.zon"); |
| 46 | 48 | pub fn main() !void { |
| 47 | try std.fs.File.stdout().writeAll(message); | |
| 49 | try std.Io.File.stdout().writeStreamingAll(io, message); | |
| 48 | 50 | } |
| 51 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 49 | 52 | #expect_stdout="We're back, World!\n" |
test/incremental/change_zon_file_no_result_type+2-1| ... | ... | @@ -6,8 +6,9 @@ |
| 6 | 6 | #update=initial version |
| 7 | 7 | #file=main.zig |
| 8 | 8 | const std = @import("std"); |
| 9 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 9 | 10 | pub 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 | 13 | #file=foo.zon |
| 13 | 14 | .{ |
test/incremental/compile_log+6-3| ... | ... | @@ -8,17 +8,19 @@ |
| 8 | 8 | #file=main.zig |
| 9 | 9 | const std = @import("std"); |
| 10 | 10 | pub 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 | } |
| 13 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 13 | 14 | #expect_stdout="Hello, World!\n" |
| 14 | 15 | |
| 15 | 16 | #update=add compile log |
| 16 | 17 | #file=main.zig |
| 17 | 18 | const std = @import("std"); |
| 18 | 19 | pub 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 | 21 | @compileLog("this is a log"); |
| 21 | 22 | } |
| 23 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 22 | 24 | #expect_error=main.zig:4:5: error: found compile log statement |
| 23 | 25 | #expect_compile_log=@as(*const [13:0]u8, "this is a log") |
| 24 | 26 | |
| ... | ... | @@ -26,6 +28,7 @@ pub fn main() !void { |
| 26 | 28 | #file=main.zig |
| 27 | 29 | const std = @import("std"); |
| 28 | 30 | pub 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 | } |
| 33 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 31 | 34 | #expect_stdout="Hello, World!\n" |
test/incremental/fix_astgen_failure+8-5| ... | ... | @@ -10,28 +10,31 @@ pub fn main() !void { |
| 10 | 10 | } |
| 11 | 11 | #file=foo.zig |
| 12 | 12 | pub 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 | 15 | #expect_error=foo.zig:2:9: error: use of undeclared identifier 'std' |
| 16 | 16 | #update=fix the error |
| 17 | 17 | #file=foo.zig |
| 18 | 18 | const std = @import("std"); |
| 19 | 19 | pub 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 | } |
| 22 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 22 | 23 | #expect_stdout="Hello, World!\n" |
| 23 | 24 | #update=add new error |
| 24 | 25 | #file=foo.zig |
| 25 | 26 | const std = @import("std"); |
| 26 | 27 | pub 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' | |
| 30 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 31 | #expect_error=foo.zig:3:52: error: use of undeclared identifier 'hello_str' | |
| 30 | 32 | #update=fix the new error |
| 31 | 33 | #file=foo.zig |
| 32 | 34 | const std = @import("std"); |
| 33 | 35 | const hello_str = "Hello, World! Again!\n"; |
| 34 | 36 | pub fn hello() !void { |
| 35 | try std.fs.File.stdout().writeAll(hello_str); | |
| 37 | try std.Io.File.stdout().writeStreamingAll(io, hello_str); | |
| 36 | 38 | } |
| 39 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 37 | 40 | #expect_stdout="Hello, World! Again!\n" |
test/incremental/function_becomes_inline+6-3| ... | ... | @@ -8,9 +8,10 @@ pub fn main() !void { |
| 8 | 8 | try foo(); |
| 9 | 9 | } |
| 10 | 10 | fn 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 | } |
| 13 | 13 | const std = @import("std"); |
| 14 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 14 | 15 | #expect_stdout="Hello, World!\n" |
| 15 | 16 | |
| 16 | 17 | #update=make function inline |
| ... | ... | @@ -19,9 +20,10 @@ pub fn main() !void { |
| 19 | 20 | try foo(); |
| 20 | 21 | } |
| 21 | 22 | inline 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 | } |
| 24 | 25 | const std = @import("std"); |
| 26 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 25 | 27 | #expect_stdout="Hello, World!\n" |
| 26 | 28 | |
| 27 | 29 | #update=change string |
| ... | ... | @@ -30,7 +32,8 @@ pub fn main() !void { |
| 30 | 32 | try foo(); |
| 31 | 33 | } |
| 32 | 34 | inline 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 | } |
| 35 | 37 | const std = @import("std"); |
| 38 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 36 | 39 | #expect_stdout="Hello, `inline` World!\n" |
test/incremental/hello+4-2| ... | ... | @@ -6,14 +6,16 @@ |
| 6 | 6 | #update=initial version |
| 7 | 7 | #file=main.zig |
| 8 | 8 | const std = @import("std"); |
| 9 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 9 | 10 | pub 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 | 13 | #expect_stdout="good morning\n" |
| 13 | 14 | #update=change the string |
| 14 | 15 | #file=main.zig |
| 15 | 16 | const std = @import("std"); |
| 17 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 16 | 18 | pub fn main() !void { |
| 17 | try std.fs.File.stdout().writeAll("おはようございます\n"); | |
| 19 | try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n"); | |
| 18 | 20 | } |
| 19 | 21 | #expect_stdout="おはようございます\n" |
test/incremental/make_decl_pub+4-2| ... | ... | @@ -12,8 +12,9 @@ pub fn main() !void { |
| 12 | 12 | #file=foo.zig |
| 13 | 13 | const std = @import("std"); |
| 14 | 14 | fn 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 | } |
| 17 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 17 | 18 | #expect_error=main.zig:3:12: error: 'hello' is not marked 'pub' |
| 18 | 19 | #expect_error=foo.zig:2:1: note: declared here |
| 19 | 20 | |
| ... | ... | @@ -21,6 +22,7 @@ fn hello() !void { |
| 21 | 22 | #file=foo.zig |
| 22 | 23 | const std = @import("std"); |
| 23 | 24 | pub 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 | } |
| 27 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 26 | 28 | #expect_stdout="Hello, World!\n" |
test/incremental/modify_inline_fn+4-2| ... | ... | @@ -8,20 +8,22 @@ |
| 8 | 8 | const std = @import("std"); |
| 9 | 9 | pub fn main() !void { |
| 10 | 10 | const str = getStr(); |
| 11 | try std.fs.File.stdout().writeAll(str); | |
| 11 | try std.Io.File.stdout().writeStreamingAll(io, str); | |
| 12 | 12 | } |
| 13 | 13 | inline fn getStr() []const u8 { |
| 14 | 14 | return "foo\n"; |
| 15 | 15 | } |
| 16 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 16 | 17 | #expect_stdout="foo\n" |
| 17 | 18 | #update=change the string |
| 18 | 19 | #file=main.zig |
| 19 | 20 | const std = @import("std"); |
| 20 | 21 | pub fn main() !void { |
| 21 | 22 | const str = getStr(); |
| 22 | try std.fs.File.stdout().writeAll(str); | |
| 23 | try std.Io.File.stdout().writeStreamingAll(io, str); | |
| 23 | 24 | } |
| 24 | 25 | inline fn getStr() []const u8 { |
| 25 | 26 | return "bar\n"; |
| 26 | 27 | } |
| 28 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 27 | 29 | #expect_stdout="bar\n" |
test/incremental/move_src+4-2| ... | ... | @@ -7,7 +7,7 @@ |
| 7 | 7 | #file=main.zig |
| 8 | 8 | const std = @import("std"); |
| 9 | 9 | pub fn main() !void { |
| 10 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 10 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 11 | 11 | try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() }); |
| 12 | 12 | } |
| 13 | 13 | fn foo() u32 { |
| ... | ... | @@ -16,13 +16,14 @@ fn foo() u32 { |
| 16 | 16 | fn bar() u32 { |
| 17 | 17 | return 123; |
| 18 | 18 | } |
| 19 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 19 | 20 | #expect_stdout="7 123\n" |
| 20 | 21 | |
| 21 | 22 | #update=add newline |
| 22 | 23 | #file=main.zig |
| 23 | 24 | const std = @import("std"); |
| 24 | 25 | pub fn main() !void { |
| 25 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 26 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 26 | 27 | try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() }); |
| 27 | 28 | } |
| 28 | 29 | |
| ... | ... | @@ -32,4 +33,5 @@ fn foo() u32 { |
| 32 | 33 | fn bar() u32 { |
| 33 | 34 | return 123; |
| 34 | 35 | } |
| 36 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 35 | 37 | #expect_stdout="8 123\n" |
test/incremental/no_change_preserves_tag_names+4-2| ... | ... | @@ -7,15 +7,17 @@ |
| 7 | 7 | #file=main.zig |
| 8 | 8 | const std = @import("std"); |
| 9 | 9 | var some_enum: enum { first, second } = .first; |
| 10 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 10 | 11 | pub 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 | 14 | #expect_stdout="first" |
| 14 | 15 | #update=no change |
| 15 | 16 | #file=main.zig |
| 16 | 17 | const std = @import("std"); |
| 17 | 18 | var some_enum: enum { first, second } = .first; |
| 19 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 18 | 20 | pub 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 | 23 | #expect_stdout="first" |
test/incremental/recursive_function_becomes_non_recursive+7-5| ... | ... | @@ -9,11 +9,12 @@ pub fn main() !void { |
| 9 | 9 | try foo(false); |
| 10 | 10 | } |
| 11 | 11 | fn foo(recurse: bool) !void { |
| 12 | const stdout = std.fs.File.stdout(); | |
| 12 | const stdout = std.Io.File.stdout(); | |
| 13 | 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 | } |
| 16 | 16 | const std = @import("std"); |
| 17 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 17 | 18 | #expect_stdout="non-recursive path\n" |
| 18 | 19 | |
| 19 | 20 | #update=eliminate recursion and change argument |
| ... | ... | @@ -22,9 +23,10 @@ pub fn main() !void { |
| 22 | 23 | try foo(true); |
| 23 | 24 | } |
| 24 | 25 | fn foo(recurse: bool) !void { |
| 25 | const stdout = std.fs.File.stdout(); | |
| 26 | if (recurse) return stdout.writeAll("x==1\n"); | |
| 27 | try stdout.writeAll("non-recursive path\n"); | |
| 26 | const stdout = std.Io.File.stdout(); | |
| 27 | if (recurse) return stdout.writeStreamingAll(io, "x==1\n"); | |
| 28 | try stdout.writeStreamingAll(io, "non-recursive path\n"); | |
| 28 | 29 | } |
| 29 | 30 | const std = @import("std"); |
| 31 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 30 | 32 | #expect_stdout="x==1\n" |
test/incremental/remove_enum_field+4-2| ... | ... | @@ -10,10 +10,11 @@ const MyEnum = enum(u8) { |
| 10 | 10 | bar = 2, |
| 11 | 11 | }; |
| 12 | 12 | pub fn main() !void { |
| 13 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 13 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 14 | 14 | try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)}); |
| 15 | 15 | } |
| 16 | 16 | const std = @import("std"); |
| 17 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 17 | 18 | #expect_stdout="1\n" |
| 18 | 19 | #update=remove enum field |
| 19 | 20 | #file=main.zig |
| ... | ... | @@ -22,9 +23,10 @@ const MyEnum = enum(u8) { |
| 22 | 23 | bar = 2, |
| 23 | 24 | }; |
| 24 | 25 | pub fn main() !void { |
| 25 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 26 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 26 | 27 | try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)}); |
| 27 | 28 | } |
| 28 | 29 | const std = @import("std"); |
| 30 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 29 | 31 | #expect_error=main.zig:7:69: error: enum 'main.MyEnum' has no member named 'foo' |
| 30 | 32 | #expect_error=main.zig:1:16: note: enum declared here |
test/incremental/unreferenced_error+8-4| ... | ... | @@ -7,36 +7,40 @@ |
| 7 | 7 | #file=main.zig |
| 8 | 8 | const std = @import("std"); |
| 9 | 9 | pub fn main() !void { |
| 10 | try std.fs.File.stdout().writeAll(a); | |
| 10 | try std.Io.File.stdout().writeStreamingAll(io, a); | |
| 11 | 11 | } |
| 12 | 12 | const a = "Hello, World!\n"; |
| 13 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 13 | 14 | #expect_stdout="Hello, World!\n" |
| 14 | 15 | |
| 15 | 16 | #update=introduce compile error |
| 16 | 17 | #file=main.zig |
| 17 | 18 | const std = @import("std"); |
| 18 | 19 | pub fn main() !void { |
| 19 | try std.fs.File.stdout().writeAll(a); | |
| 20 | try std.Io.File.stdout().writeStreamingAll(io, a); | |
| 20 | 21 | } |
| 21 | 22 | const a = @compileError("bad a"); |
| 23 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 22 | 24 | #expect_error=main.zig:5:11: error: bad a |
| 23 | 25 | |
| 24 | 26 | #update=remove error reference |
| 25 | 27 | #file=main.zig |
| 26 | 28 | const std = @import("std"); |
| 27 | 29 | pub fn main() !void { |
| 28 | try std.fs.File.stdout().writeAll(b); | |
| 30 | try std.Io.File.stdout().writeStreamingAll(io, b); | |
| 29 | 31 | } |
| 30 | 32 | const a = @compileError("bad a"); |
| 31 | 33 | const b = "Hi there!\n"; |
| 34 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 32 | 35 | #expect_stdout="Hi there!\n" |
| 33 | 36 | |
| 34 | 37 | #update=introduce and remove reference to error |
| 35 | 38 | #file=main.zig |
| 36 | 39 | const std = @import("std"); |
| 37 | 40 | pub fn main() !void { |
| 38 | try std.fs.File.stdout().writeAll(a); | |
| 41 | try std.Io.File.stdout().writeStreamingAll(io, a); | |
| 39 | 42 | } |
| 40 | 43 | const a = "Back to a\n"; |
| 41 | 44 | const b = @compileError("bad b"); |
| 45 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 42 | 46 | #expect_stdout="Back to a\n" |
test/link/bss/main.zig+1-1| ... | ... | @@ -4,7 +4,7 @@ const std = @import("std"); |
| 4 | 4 | var buffer: [0x1000000]u64 = [1]u64{0} ** 0x1000000; |
| 5 | 5 | |
| 6 | 6 | pub 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, &.{}); | |
| 8 | 8 | |
| 9 | 9 | buffer[0x10] = 1; |
| 10 | 10 |
test/link/elf.zig+2-2| ... | ... | @@ -1323,7 +1323,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step { |
| 1323 | 1323 | \\extern var live_var2: i32; |
| 1324 | 1324 | \\extern fn live_fn2() void; |
| 1325 | 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 | 1327 | \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail"); |
| 1328 | 1328 | \\ live_fn2(); |
| 1329 | 1329 | \\} |
| ... | ... | @@ -1365,7 +1365,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step { |
| 1365 | 1365 | \\extern var live_var2: i32; |
| 1366 | 1366 | \\extern fn live_fn2() void; |
| 1367 | 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 | 1369 | \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail"); |
| 1370 | 1370 | \\ live_fn2(); |
| 1371 | 1371 | \\} |
test/link/macho.zig+4-3| ... | ... | @@ -716,7 +716,7 @@ fn testHelloZig(b: *Build, opts: Options) *Step { |
| 716 | 716 | const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes = |
| 717 | 717 | \\const std = @import("std"); |
| 718 | 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 | }); |
| 722 | 722 | |
| ... | ... | @@ -868,9 +868,10 @@ fn testLayout(b: *Build, opts: Options) *Step { |
| 868 | 868 | } |
| 869 | 869 | |
| 870 | 870 | fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step { |
| 871 | const io = b.graph.io; | |
| 871 | 872 | const test_step = addTestStep(b, "link-directly-cpp-tbd", opts); |
| 872 | 873 | |
| 873 | const sdk = std.zig.system.darwin.getSdk(b.allocator, &opts.target.result) orelse | |
| 874 | const sdk = std.zig.system.darwin.getSdk(b.allocator, io, &opts.target.result) orelse | |
| 874 | 875 | @panic("macOS SDK is required to run the test"); |
| 875 | 876 | |
| 876 | 877 | const exe = addExecutable(b, opts, .{ |
| ... | ... | @@ -2371,7 +2372,7 @@ fn testTlsZig(b: *Build, opts: Options) *Step { |
| 2371 | 2372 | \\threadlocal var x: i32 = 0; |
| 2372 | 2373 | \\threadlocal var y: i32 = -1; |
| 2373 | 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 | 2376 | \\ stdout_writer.interface.print("{d} {d}\n", .{x, y}) catch unreachable; |
| 2376 | 2377 | \\ x -= 1; |
| 2377 | 2378 | \\ y += 1; |
test/link/wasm/extern/main.zig+1-1| ... | ... | @@ -3,6 +3,6 @@ const std = @import("std"); |
| 3 | 3 | extern const foo: u32; |
| 4 | 4 | |
| 5 | 5 | pub 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 | 7 | stdout_writer.interface.print("Result: {d}", .{foo}) catch {}; |
| 8 | 8 | } |
test/src/Cases.zig+18-6| ... | ... | @@ -1,6 +1,8 @@ |
| 1 | 1 | const Cases = @This(); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | ||
| 3 | 4 | const std = @import("std"); |
| 5 | const Io = std.Io; | |
| 4 | 6 | const assert = std.debug.assert; |
| 5 | 7 | const Allocator = std.mem.Allocator; |
| 6 | 8 | const getExternalExecutor = std.zig.system.getExternalExecutor; |
| ... | ... | @@ -8,6 +10,7 @@ const ArrayList = std.ArrayList; |
| 8 | 10 | |
| 9 | 11 | gpa: Allocator, |
| 10 | 12 | arena: Allocator, |
| 13 | io: Io, | |
| 11 | 14 | cases: std.array_list.Managed(Case), |
| 12 | 15 | |
| 13 | 16 | pub const IncrementalCase = struct { |
| ... | ... | @@ -313,7 +316,7 @@ pub fn addCompile( |
| 313 | 316 | /// Each file should include a test manifest as a contiguous block of comments at |
| 314 | 317 | /// the end of the file. The first line should be the test type, followed by a set of |
| 315 | 318 | /// key-value config values, followed by a blank line, then the expected output. |
| 316 | pub fn addFromDir(ctx: *Cases, dir: std.fs.Dir, b: *std.Build) void { | |
| 319 | pub fn addFromDir(ctx: *Cases, dir: Io.Dir, b: *std.Build) void { | |
| 317 | 320 | var current_file: []const u8 = "none"; |
| 318 | 321 | ctx.addFromDirInner(dir, &current_file, b) catch |err| { |
| 319 | 322 | std.debug.panicExtra( |
| ... | ... | @@ -326,16 +329,17 @@ pub fn addFromDir(ctx: *Cases, dir: std.fs.Dir, b: *std.Build) void { |
| 326 | 329 | |
| 327 | 330 | fn addFromDirInner( |
| 328 | 331 | ctx: *Cases, |
| 329 | iterable_dir: std.fs.Dir, | |
| 332 | iterable_dir: Io.Dir, | |
| 330 | 333 | /// This is kept up to date with the currently being processed file so |
| 331 | 334 | /// that if any errors occur the caller knows it happened during this file. |
| 332 | 335 | current_file: *[]const u8, |
| 333 | 336 | b: *std.Build, |
| 334 | 337 | ) !void { |
| 338 | const io = ctx.io; | |
| 335 | 339 | var it = try iterable_dir.walk(ctx.arena); |
| 336 | 340 | var filenames: ArrayList([]const u8) = .empty; |
| 337 | 341 | |
| 338 | while (try it.next()) |entry| { | |
| 342 | while (try it.next(io)) |entry| { | |
| 339 | 343 | if (entry.kind != .file) continue; |
| 340 | 344 | |
| 341 | 345 | // Ignore stuff such as .swp files |
| ... | ... | @@ -347,7 +351,7 @@ fn addFromDirInner( |
| 347 | 351 | current_file.* = filename; |
| 348 | 352 | |
| 349 | 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); | |
| 351 | 355 | |
| 352 | 356 | // Parse the manifest |
| 353 | 357 | var manifest = try TestManifest.parse(ctx.arena, src); |
| ... | ... | @@ -376,6 +380,12 @@ fn addFromDirInner( |
| 376 | 380 | // Other backends don't support new liveness format |
| 377 | 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 | 389 | if (backend == .selfhosted and target.os.tag == .macos and |
| 380 | 390 | target.cpu.arch == .x86_64 and builtin.cpu.arch == .aarch64) |
| 381 | 391 | { |
| ... | ... | @@ -427,9 +437,10 @@ fn addFromDirInner( |
| 427 | 437 | } |
| 428 | 438 | } |
| 429 | 439 | |
| 430 | pub fn init(gpa: Allocator, arena: Allocator) Cases { | |
| 440 | pub fn init(gpa: Allocator, arena: Allocator, io: Io) Cases { | |
| 431 | 441 | return .{ |
| 432 | 442 | .gpa = gpa, |
| 443 | .io = io, | |
| 433 | 444 | .cases = .init(gpa), |
| 434 | 445 | .arena = arena, |
| 435 | 446 | }; |
| ... | ... | @@ -457,6 +468,7 @@ pub fn lowerToBuildSteps( |
| 457 | 468 | parent_step: *std.Build.Step, |
| 458 | 469 | options: CaseTestOptions, |
| 459 | 470 | ) void { |
| 471 | const io = self.io; | |
| 460 | 472 | const host = b.resolveTargetQuery(.{}); |
| 461 | 473 | const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM"); |
| 462 | 474 | |
| ... | ... | @@ -591,7 +603,7 @@ pub fn lowerToBuildSteps( |
| 591 | 603 | }, |
| 592 | 604 | .Execution => |expected_stdout| no_exec: { |
| 593 | 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 | 607 | // We wouldn't be able to run the compiled C code. |
| 596 | 608 | break :no_exec; |
| 597 | 609 | } |
test/src/convert-stack-trace.zig+5-5| ... | ... | @@ -34,20 +34,20 @@ pub fn main() !void { |
| 34 | 34 | |
| 35 | 35 | const gpa = arena; |
| 36 | 36 | |
| 37 | var threaded: std.Io.Threaded = .init(gpa); | |
| 37 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 38 | 38 | defer threaded.deinit(); |
| 39 | 39 | const io = threaded.io(); |
| 40 | 40 | |
| 41 | 41 | var read_buf: [1024]u8 = undefined; |
| 42 | 42 | var write_buf: [1024]u8 = undefined; |
| 43 | 43 | |
| 44 | const in_file = try std.fs.cwd().openFile(args[1], .{}); | |
| 45 | defer in_file.close(); | |
| 44 | const in_file = try std.Io.Dir.cwd().openFile(io, args[1], .{}); | |
| 45 | defer in_file.close(io); | |
| 46 | 46 | |
| 47 | const out_file: std.fs.File = .stdout(); | |
| 47 | const out_file: std.Io.File = .stdout(); | |
| 48 | 48 | |
| 49 | 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); | |
| 51 | 51 | |
| 52 | 52 | const w = &out_fw.interface; |
| 53 | 53 |
test/standalone/child_process/child.zig+8-8| ... | ... | @@ -8,7 +8,7 @@ pub fn main() !void { |
| 8 | 8 | var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 9 | 9 | const arena = arena_state.allocator(); |
| 10 | 10 | |
| 11 | var threaded: std.Io.Threaded = .init(arena); | |
| 11 | var threaded: std.Io.Threaded = .init(arena, .{}); | |
| 12 | 12 | defer threaded.deinit(); |
| 13 | 13 | const io = threaded.io(); |
| 14 | 14 | |
| ... | ... | @@ -26,28 +26,28 @@ fn run(allocator: std.mem.Allocator, io: Io) !void { |
| 26 | 26 | const hello_arg = "hello arg"; |
| 27 | 27 | const a1 = args.next() orelse unreachable; |
| 28 | 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 | 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 | } |
| 34 | 34 | |
| 35 | 35 | // 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"); | |
| 37 | 37 | |
| 38 | 38 | // test stdin pipe from parent |
| 39 | 39 | const hello_stdin = "hello from stdin"; |
| 40 | 40 | var buf: [hello_stdin.len]u8 = undefined; |
| 41 | const stdin: std.fs.File = .stdin(); | |
| 41 | const stdin: std.Io.File = .stdin(); | |
| 42 | 42 | var reader = stdin.reader(io, &.{}); |
| 43 | 43 | const n = try reader.interface.readSliceShort(&buf); |
| 44 | 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 | } |
| 48 | 48 | |
| 49 | fn testError(comptime fmt: []const u8, args: anytype) void { | |
| 50 | var stderr_writer = std.fs.File.stderr().writer(&.{}); | |
| 49 | fn testError(io: Io, comptime fmt: []const u8, args: anytype) void { | |
| 50 | var stderr_writer = std.Io.File.stderr().writer(io, &.{}); | |
| 51 | 51 | const stderr = &stderr_writer.interface; |
| 52 | 52 | stderr.print("CHILD TEST ERROR: ", .{}) catch {}; |
| 53 | 53 | stderr.print(fmt, args) catch {}; |
test/standalone/child_process/main.zig+12-11| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | |
| 3 | 4 | pub fn main() !void { |
| 4 | 5 | // make sure safety checks are enabled even in release modes |
| ... | ... | @@ -20,7 +21,7 @@ pub fn main() !void { |
| 20 | 21 | }; |
| 21 | 22 | defer if (needs_free) gpa.free(child_path); |
| 22 | 23 | |
| 23 | var threaded: std.Io.Threaded = .init(gpa); | |
| 24 | var threaded: Io.Threaded = .init(gpa, .{}); | |
| 24 | 25 | defer threaded.deinit(); |
| 25 | 26 | const io = threaded.io(); |
| 26 | 27 | |
| ... | ... | @@ -28,10 +29,10 @@ pub fn main() !void { |
| 28 | 29 | child.stdin_behavior = .Pipe; |
| 29 | 30 | child.stdout_behavior = .Pipe; |
| 30 | 31 | child.stderr_behavior = .Inherit; |
| 31 | try child.spawn(); | |
| 32 | try child.spawn(io); | |
| 32 | 33 | const child_stdin = child.stdin.?; |
| 33 | try child_stdin.writeAll("hello from stdin"); // verified in child | |
| 34 | child_stdin.close(); | |
| 34 | try child_stdin.writeStreamingAll(io, "hello from stdin"); // verified in child | |
| 35 | child_stdin.close(io); | |
| 35 | 36 | child.stdin = null; |
| 36 | 37 | |
| 37 | 38 | const hello_stdout = "hello from stdout"; |
| ... | ... | @@ -39,30 +40,30 @@ pub fn main() !void { |
| 39 | 40 | var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); |
| 40 | 41 | const n = try stdout_reader.interface.readSliceShort(&buf); |
| 41 | 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 | } |
| 44 | 45 | |
| 45 | switch (try child.wait()) { | |
| 46 | switch (try child.wait(io)) { | |
| 46 | 47 | .Exited => |code| { |
| 47 | 48 | const child_ok_code = 42; // set by child if no test errors |
| 48 | 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 | 55 | if (parent_test_error) return error.ParentTestError; |
| 55 | 56 | |
| 56 | 57 | // Check that FileNotFound is consistent across platforms when trying to spawn an executable that doesn't exist |
| 57 | 58 | const missing_child_path = try std.mem.concat(gpa, u8, &.{ child_path, "_intentionally_missing" }); |
| 58 | 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 | } |
| 61 | 62 | |
| 62 | 63 | var parent_test_error = false; |
| 63 | 64 | |
| 64 | fn testError(comptime fmt: []const u8, args: anytype) void { | |
| 65 | var stderr_writer = std.fs.File.stderr().writer(&.{}); | |
| 65 | fn testError(io: Io, comptime fmt: []const u8, args: anytype) void { | |
| 66 | var stderr_writer = Io.File.stderr().writer(io, &.{}); | |
| 66 | 67 | const stderr = &stderr_writer.interface; |
| 67 | 68 | stderr.print("PARENT TEST ERROR: ", .{}) catch {}; |
| 68 | 69 | stderr.print(fmt, args) catch {}; |
test/standalone/cmakedefine/check.zig+4-2| ... | ... | @@ -9,8 +9,10 @@ pub fn main() !void { |
| 9 | 9 | const actual_path = args[1]; |
| 10 | 10 | const expected_path = args[2]; |
| 11 | 11 | |
| 12 | const actual = try std.fs.cwd().readFileAlloc(actual_path, arena, .limited(1024 * 1024)); | |
| 13 | const expected = try std.fs.cwd().readFileAlloc(expected_path, arena, .limited(1024 * 1024)); | |
| 12 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 13 | ||
| 14 | const actual = try std.Io.Dir.cwd().readFileAlloc(io, actual_path, arena, .limited(1024 * 1024)); | |
| 15 | const expected = try std.Io.Dir.cwd().readFileAlloc(io, expected_path, arena, .limited(1024 * 1024)); | |
| 14 | 16 | |
| 15 | 17 | // The actual output starts with a comment which we should strip out before comparing. |
| 16 | 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 | 11 | var di: std.debug.SelfInfo = .init; |
| 12 | 12 | defer di.deinit(gpa); |
| 13 | 13 | |
| 14 | var threaded: std.Io.Threaded = .init(gpa); | |
| 14 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 15 | 15 | defer threaded.deinit(); |
| 16 | 16 | const io = threaded.io(); |
| 17 | 17 |
test/standalone/dirname/build.zig+6-4| ... | ... | @@ -59,13 +59,15 @@ pub fn build(b: *std.Build) void { |
| 59 | 59 | |
| 60 | 60 | // Absolute path: |
| 61 | 61 | const abs_path = setup_abspath: { |
| 62 | // TODO this is a bad pattern, don't do this | |
| 63 | const io = b.graph.io; | |
| 62 | 64 | const temp_dir = b.makeTempPath(); |
| 63 | 65 | |
| 64 | var dir = std.fs.cwd().openDir(temp_dir, .{}) catch @panic("failed to open temp dir"); | |
| 65 | defer dir.close(); | |
| 66 | var dir = std.Io.Dir.cwd().openDir(io, temp_dir, .{}) catch @panic("failed to open temp dir"); | |
| 67 | defer dir.close(io); | |
| 66 | 68 | |
| 67 | var file = dir.createFile("foo.txt", .{}) catch @panic("failed to create file"); | |
| 68 | file.close(); | |
| 69 | var file = dir.createFile(io, "foo.txt", .{}) catch @panic("failed to create file"); | |
| 70 | file.close(io); | |
| 69 | 71 | |
| 70 | 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 | 34 | return error.BadUsage; |
| 35 | 35 | }; |
| 36 | 36 | |
| 37 | var dir = try std.fs.cwd().openDir(dir_path, .{}); | |
| 38 | defer dir.close(); | |
| 37 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 39 | 38 | |
| 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 | 26 | return error.BadUsage; |
| 27 | 27 | }; |
| 28 | 28 | |
| 29 | const dir_path = std.fs.path.dirname(path) orelse unreachable; | |
| 30 | const basename = std.fs.path.basename(path); | |
| 29 | const dir_path = std.Io.Dir.path.dirname(path) orelse unreachable; | |
| 30 | const basename = std.Io.Dir.path.basename(path); | |
| 31 | 31 | |
| 32 | var dir = try std.fs.cwd().openDir(dir_path, .{}); | |
| 33 | defer dir.close(); | |
| 32 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 34 | 33 | |
| 35 | _ = dir.statFile(basename) catch { | |
| 36 | var file = try dir.createFile(basename, .{}); | |
| 37 | file.close(); | |
| 34 | var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{}); | |
| 35 | defer dir.close(io); | |
| 36 | ||
| 37 | _ = dir.statFile(io, basename, .{}) catch { | |
| 38 | var file = try dir.createFile(io, basename, .{}); | |
| 39 | file.close(io); | |
| 38 | 40 | }; |
| 39 | 41 | } |
test/standalone/entry_point/check_differ.zig+4-2| ... | ... | @@ -6,8 +6,10 @@ pub fn main() !void { |
| 6 | 6 | const args = try std.process.argsAlloc(arena); |
| 7 | 7 | if (args.len != 3) return error.BadUsage; // usage: 'check_differ <path a> <path b>' |
| 8 | 8 | |
| 9 | const contents_1 = try std.fs.cwd().readFileAlloc(args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty | |
| 10 | const contents_2 = try std.fs.cwd().readFileAlloc(args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty | |
| 9 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 10 | ||
| 11 | const contents_1 = try std.Io.Dir.cwd().readFileAlloc(io, args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty | |
| 12 | const contents_2 = try std.Io.Dir.cwd().readFileAlloc(io, args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty | |
| 11 | 13 | |
| 12 | 14 | if (std.mem.eql(u8, contents_1, contents_2)) { |
| 13 | 15 | return error.FilesMatch; |
test/standalone/env_vars/main.zig+2| ... | ... | @@ -3,6 +3,8 @@ const builtin = @import("builtin"); |
| 3 | 3 | |
| 4 | 4 | // Note: the environment variables under test are set by the build.zig |
| 5 | 5 | pub fn main() !void { |
| 6 | @setEvalBranchQuota(10000); | |
| 7 | ||
| 6 | 8 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; |
| 7 | 9 | defer _ = gpa.deinit(); |
| 8 | 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 | 28 | |
| 29 | 29 | // PR #17034 - fstat moved between libc_nonshared and libc |
| 30 | 30 | fn checkStat() !void { |
| 31 | const cwdFd = std.fs.cwd().fd; | |
| 31 | const cwd_fd = std.Io.Dir.cwd().handle; | |
| 32 | 32 | |
| 33 | 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 | 35 | assert(result == -1); |
| 36 | 36 | assert(std.posix.errno(result) == .NOENT); |
| 37 | 37 |
test/standalone/install_headers/check_exists.zig+6-4| ... | ... | @@ -11,8 +11,10 @@ pub fn main() !void { |
| 11 | 11 | var arg_it = try std.process.argsWithAllocator(arena); |
| 12 | 12 | _ = arg_it.next(); |
| 13 | 13 | |
| 14 | const cwd = std.fs.cwd(); | |
| 15 | const cwd_realpath = try cwd.realpathAlloc(arena, "."); | |
| 14 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 15 | ||
| 16 | const cwd = std.Io.Dir.cwd(); | |
| 17 | const cwd_realpath = try cwd.realPathFileAlloc(io, ".", arena); | |
| 16 | 18 | |
| 17 | 19 | while (arg_it.next()) |file_path| { |
| 18 | 20 | if (file_path.len > 0 and file_path[0] == '!') { |
| ... | ... | @@ -20,7 +22,7 @@ pub fn main() !void { |
| 20 | 22 | "exclusive file check '{s}{c}{s}' failed", |
| 21 | 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 | 26 | return error.FileFound; |
| 25 | 27 | } else |err| switch (err) { |
| 26 | 28 | error.FileNotFound => {}, |
| ... | ... | @@ -31,7 +33,7 @@ pub fn main() !void { |
| 31 | 33 | "inclusive file check '{s}{c}{s}' failed", |
| 32 | 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 | 23 | }), |
| 24 | 24 | }); |
| 25 | 25 | |
| 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 | 29 | b.sysroot = sdk; |
| 28 | 30 | exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) }); |
| 29 | 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 | 15 | defer args.deinit(); |
| 16 | 16 | _ = args.skip(); // executable name |
| 17 | 17 | |
| 18 | var threaded: std.Io.Threaded = .init(gpa); | |
| 18 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 19 | 19 | defer threaded.deinit(); |
| 20 | 20 | const io = threaded.io(); |
| 21 | 21 | |
| 22 | 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, .{}); | |
| 24 | defer cache_dir.close(); | |
| 23 | var cache_dir = try std.Io.Dir.cwd().openDir(io, cache_dir_path, .{}); | |
| 24 | defer cache_dir.close(io); | |
| 25 | 25 | |
| 26 | 26 | abi.fuzzer_init(.fromSlice(cache_dir_path)); |
| 27 | 27 | abi.fuzzer_init_test(testOne, .fromSlice("test")); |
| ... | ... | @@ -30,8 +30,8 @@ pub fn main() !void { |
| 30 | 30 | |
| 31 | 31 | const pc_digest = abi.fuzzer_coverage().id; |
| 32 | 32 | const coverage_file_path = "v/" ++ std.fmt.hex(pc_digest); |
| 33 | const coverage_file = try cache_dir.openFile(coverage_file_path, .{}); | |
| 34 | defer coverage_file.close(); | |
| 33 | const coverage_file = try cache_dir.openFile(io, coverage_file_path, .{}); | |
| 34 | defer coverage_file.close(io); | |
| 35 | 35 | |
| 36 | 36 | var read_buf: [@sizeOf(abi.SeenPcsHeader)]u8 = undefined; |
| 37 | 37 | var r = coverage_file.reader(io, &read_buf); |
| ... | ... | @@ -42,6 +42,6 @@ pub fn main() !void { |
| 42 | 42 | const expected_len = @sizeOf(abi.SeenPcsHeader) + |
| 43 | 43 | try std.math.divCeil(usize, pcs_header.pcs_len, @bitSizeOf(usize)) * @sizeOf(usize) + |
| 44 | 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 | 46 | return error.WrongEnd; |
| 47 | 47 | } |
test/standalone/posix/cwd.zig+63-16| ... | ... | @@ -1,21 +1,30 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 3 | 2 | |
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Allocator = std.mem.Allocator; | |
| 6 | const assert = std.debug.assert; | |
| 7 | ||
| 4 | 8 | const path_max = std.fs.max_path_bytes; |
| 5 | 9 | |
| 6 | 10 | pub fn main() !void { |
| 7 | if (builtin.target.os.tag == .wasi) { | |
| 8 | // WASI doesn't support changing the working directory at all. | |
| 9 | return; | |
| 11 | switch (builtin.target.os.tag) { | |
| 12 | .wasi => return, // WASI doesn't support changing the working directory at all. | |
| 13 | .windows => return, // POSIX is not implemented by Windows | |
| 14 | else => {}, | |
| 10 | 15 | } |
| 11 | 16 | |
| 12 | var Allocator = std.heap.DebugAllocator(.{}){}; | |
| 13 | const a = Allocator.allocator(); | |
| 14 | defer std.debug.assert(Allocator.deinit() == .ok); | |
| 17 | var debug_allocator: std.heap.DebugAllocator(.{}) = .{}; | |
| 18 | defer assert(debug_allocator.deinit() == .ok); | |
| 19 | const gpa = debug_allocator.allocator(); | |
| 20 | ||
| 21 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 22 | defer threaded.deinit(); | |
| 23 | const io = threaded.io(); | |
| 15 | 24 | |
| 16 | 25 | try test_chdir_self(); |
| 17 | 26 | try test_chdir_absolute(); |
| 18 | try test_chdir_relative(a); | |
| 27 | try test_chdir_relative(gpa, io); | |
| 19 | 28 | } |
| 20 | 29 | |
| 21 | 30 | // get current working directory and expect it to match given path |
| ... | ... | @@ -46,20 +55,20 @@ fn test_chdir_absolute() !void { |
| 46 | 55 | try expect_cwd(parent); |
| 47 | 56 | } |
| 48 | 57 | |
| 49 | fn test_chdir_relative(a: std.mem.Allocator) !void { | |
| 50 | var tmp = std.testing.tmpDir(.{}); | |
| 51 | defer tmp.cleanup(); | |
| 58 | fn test_chdir_relative(gpa: Allocator, io: Io) !void { | |
| 59 | var tmp = tmpDir(io, .{}); | |
| 60 | defer tmp.cleanup(io); | |
| 52 | 61 | |
| 53 | 62 | // 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); | |
| 55 | 64 | |
| 56 | 65 | // Capture base working directory path, to build expected full path |
| 57 | 66 | var base_cwd_buf: [path_max]u8 = undefined; |
| 58 | 67 | const base_cwd = try std.posix.getcwd(base_cwd_buf[0..]); |
| 59 | 68 | |
| 60 | 69 | const relative_dir_name = &tmp.sub_path; |
| 61 | const expected_path = try std.fs.path.resolve(a, &.{ base_cwd, relative_dir_name }); | |
| 62 | defer a.free(expected_path); | |
| 70 | const expected_path = try std.fs.path.resolve(gpa, &.{ base_cwd, relative_dir_name }); | |
| 71 | defer gpa.free(expected_path); | |
| 63 | 72 | |
| 64 | 73 | // change current working directory to new test directory |
| 65 | 74 | try std.posix.chdir(relative_dir_name); |
| ... | ... | @@ -68,8 +77,46 @@ fn test_chdir_relative(a: std.mem.Allocator) !void { |
| 68 | 77 | const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]); |
| 69 | 78 | |
| 70 | 79 | // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase |
| 71 | const resolved_cwd = try std.fs.path.resolve(a, &.{new_cwd}); | |
| 72 | defer a.free(resolved_cwd); | |
| 80 | const resolved_cwd = try std.fs.path.resolve(gpa, &.{new_cwd}); | |
| 81 | defer gpa.free(resolved_cwd); | |
| 73 | 82 | |
| 74 | 83 | try std.testing.expectEqualStrings(expected_path, resolved_cwd); |
| 75 | 84 | } |
| 85 | ||
| 86 | pub 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 | ||
| 108 | pub 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 | 1 | // Test relative paths through POSIX APIS. These tests have to change the cwd, so |
| 2 | 2 | // they shouldn't be Zig unit tests. |
| 3 | 3 | |
| 4 | const std = @import("std"); | |
| 5 | 4 | const builtin = @import("builtin"); |
| 6 | 5 | |
| 6 | const std = @import("std"); | |
| 7 | const Io = std.Io; | |
| 8 | ||
| 7 | 9 | pub fn main() !void { |
| 8 | 10 | if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir |
| 9 | 11 | |
| 10 | var Allocator = std.heap.DebugAllocator(.{}){}; | |
| 11 | const a = Allocator.allocator(); | |
| 12 | defer std.debug.assert(Allocator.deinit() == .ok); | |
| 12 | var debug_allocator: std.heap.DebugAllocator(.{}) = .init; | |
| 13 | const gpa = debug_allocator.allocator(); | |
| 14 | defer std.debug.assert(debug_allocator.deinit() == .ok); | |
| 13 | 15 | |
| 14 | var tmp = std.testing.tmpDir(.{}); | |
| 15 | defer tmp.cleanup(); | |
| 16 | ||
| 17 | // Want to test relative paths, so cd into the tmpdir for these tests | |
| 18 | try tmp.dir.setAsCwd(); | |
| 19 | ||
| 20 | try test_symlink(a, tmp); | |
| 21 | try test_link(tmp); | |
| 22 | } | |
| 23 | ||
| 24 | fn test_symlink(a: std.mem.Allocator, tmp: std.testing.TmpDir) !void { | |
| 25 | const target_name = "symlink-target"; | |
| 26 | const symlink_name = "symlinker"; | |
| 27 | ||
| 28 | // Create the target file | |
| 29 | try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "nonsense" }); | |
| 30 | ||
| 31 | if (builtin.target.os.tag == .windows) { | |
| 32 | const wtarget_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, target_name); | |
| 33 | const wsymlink_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, symlink_name); | |
| 34 | defer a.free(wtarget_name); | |
| 35 | defer a.free(wsymlink_name); | |
| 36 | ||
| 37 | std.os.windows.CreateSymbolicLink(tmp.dir.fd, wsymlink_name, wtarget_name, false) catch |err| switch (err) { | |
| 38 | // Symlink requires admin privileges on windows, so this test can legitimately fail. | |
| 39 | error.AccessDenied => return, | |
| 40 | else => return err, | |
| 41 | }; | |
| 42 | } else { | |
| 43 | try std.posix.symlink(target_name, symlink_name); | |
| 44 | } | |
| 16 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 17 | defer threaded.deinit(); | |
| 18 | const io = threaded.io(); | |
| 45 | 19 | |
| 46 | var buffer: [std.fs.max_path_bytes]u8 = undefined; | |
| 47 | const given = try std.posix.readlink(symlink_name, buffer[0..]); | |
| 48 | try std.testing.expectEqualStrings(target_name, given); | |
| 49 | } | |
| 20 | var tmp = tmpDir(io, .{}); | |
| 21 | defer tmp.cleanup(io); | |
| 50 | 22 | |
| 51 | fn getLinkInfo(fd: std.posix.fd_t) !struct { std.posix.ino_t, std.posix.nlink_t } { | |
| 52 | if (builtin.target.os.tag == .linux) { | |
| 53 | const stx = try std.os.linux.wrapped.statx( | |
| 54 | fd, | |
| 55 | "", | |
| 56 | std.posix.AT.EMPTY_PATH, | |
| 57 | .{ .INO = true, .NLINK = true }, | |
| 58 | ); | |
| 59 | std.debug.assert(stx.mask.INO); | |
| 60 | std.debug.assert(stx.mask.NLINK); | |
| 61 | return .{ stx.ino, stx.nlink }; | |
| 62 | } | |
| 23 | // Want to test relative paths, so cd into the tmpdir for these tests | |
| 24 | try std.process.setCurrentDir(io, tmp.dir); | |
| 63 | 25 | |
| 64 | const st = try std.posix.fstat(fd); | |
| 65 | return .{ st.ino, st.nlink }; | |
| 26 | try test_link(io, tmp); | |
| 66 | 27 | } |
| 67 | 28 | |
| 68 | fn test_link(tmp: std.testing.TmpDir) !void { | |
| 29 | fn test_link(io: Io, tmp: TmpDir) !void { | |
| 69 | 30 | switch (builtin.target.os.tag) { |
| 70 | 31 | .linux, .illumos => {}, |
| 71 | 32 | else => return, |
| ... | ... | @@ -74,29 +35,67 @@ fn test_link(tmp: std.testing.TmpDir) !void { |
| 74 | 35 | const target_name = "link-target"; |
| 75 | 36 | const link_name = "newlink"; |
| 76 | 37 | |
| 77 | try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" }); | |
| 38 | try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" }); | |
| 78 | 39 | |
| 79 | 40 | // 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, .{}); | |
| 81 | 42 | |
| 82 | 43 | // Verify |
| 83 | const efd = try tmp.dir.openFile(target_name, .{}); | |
| 84 | defer efd.close(); | |
| 44 | const efd = try tmp.dir.openFile(io, target_name, .{}); | |
| 45 | defer efd.close(io); | |
| 85 | 46 | |
| 86 | const nfd = try tmp.dir.openFile(link_name, .{}); | |
| 87 | defer nfd.close(); | |
| 47 | const nfd = try tmp.dir.openFile(io, link_name, .{}); | |
| 48 | defer nfd.close(io); | |
| 88 | 49 | |
| 89 | 50 | { |
| 90 | const eino, _ = try getLinkInfo(efd.handle); | |
| 91 | const nino, const nlink = try getLinkInfo(nfd.handle); | |
| 92 | try std.testing.expectEqual(eino, nino); | |
| 93 | try std.testing.expectEqual(@as(std.posix.nlink_t, 2), nlink); | |
| 51 | const e_stat = try efd.stat(io); | |
| 52 | const n_stat = try nfd.stat(io); | |
| 53 | try std.testing.expectEqual(e_stat.inode, n_stat.inode); | |
| 54 | try std.testing.expectEqual(2, n_stat.nlink); | |
| 94 | 55 | } |
| 95 | 56 | |
| 96 | 57 | // 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); | |
| 100 | try std.testing.expectEqual(@as(std.posix.nlink_t, 1), elink); | |
| 60 | const e_stat = try efd.stat(io); | |
| 61 | try std.testing.expectEqual(1, e_stat.nlink); | |
| 101 | 62 | } |
| 102 | 63 | } |
| 64 | ||
| 65 | pub 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 | ||
| 87 | pub 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 | 8 | if (args.len != 2) return error.BadUsage; |
| 9 | 9 | const path = args[1]; |
| 10 | 10 | |
| 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 | } |
| 13 | 15 | |
| 14 | 16 | const std = @import("std"); |
test/standalone/run_output_caching/main.zig+4-3| ... | ... | @@ -1,10 +1,11 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | |
| 3 | 3 | pub fn main() !void { |
| 4 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 4 | 5 | var args = try std.process.argsWithAllocator(std.heap.page_allocator); |
| 5 | 6 | _ = args.skip(); |
| 6 | 7 | const filename = args.next().?; |
| 7 | const file = try std.fs.cwd().createFile(filename, .{}); | |
| 8 | defer file.close(); | |
| 9 | try file.writeAll(filename); | |
| 8 | const file = try std.Io.Dir.cwd().createFile(io, filename, .{}); | |
| 9 | defer file.close(io); | |
| 10 | try file.writeStreamingAll(io, filename); | |
| 10 | 11 | } |
test/standalone/run_output_paths/create_file.zig+4-3| ... | ... | @@ -1,16 +1,17 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | |
| 3 | 3 | pub fn main() !void { |
| 4 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 4 | 5 | var args = try std.process.argsWithAllocator(std.heap.page_allocator); |
| 5 | 6 | _ = args.skip(); |
| 6 | 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 | 9 | dir_name["--dir=".len..] |
| 9 | 10 | else |
| 10 | 11 | dir_name, .{}); |
| 11 | 12 | const file_name = args.next().?; |
| 12 | const file = try dir.createFile(file_name, .{}); | |
| 13 | var file_writer = file.writer(&.{}); | |
| 13 | const file = try dir.createFile(io, file_name, .{}); | |
| 14 | var file_writer = file.writer(io, &.{}); | |
| 14 | 15 | try file_writer.interface.print( |
| 15 | 16 | \\{s} |
| 16 | 17 | \\{s} |
test/standalone/self_exe_symlink/build.zig-4| ... | ... | @@ -9,10 +9,6 @@ pub fn build(b: *std.Build) void { |
| 9 | 9 | const optimize: std.builtin.OptimizeMode = .Debug; |
| 10 | 10 | const target = b.graph.host; |
| 11 | 11 | |
| 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 | 12 | const main = b.addExecutable(.{ |
| 17 | 13 | .name = "main", |
| 18 | 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 | 14 | // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`. |
| 15 | 15 | const exe_rel_path = try std.fs.path.relative(allocator, std.fs.path.dirname(symlink_path) orelse ".", exe_path); |
| 16 | 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 | 1 | const std = @import("std"); |
| 2 | 2 | |
| 3 | 3 | pub fn main() !void { |
| 4 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; | |
| 5 | defer std.debug.assert(gpa.deinit() == .ok); | |
| 6 | const allocator = gpa.allocator(); | |
| 4 | var debug_allocator: std.heap.DebugAllocator(.{}) = .init; | |
| 5 | defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks"); | |
| 6 | const gpa = debug_allocator.allocator(); | |
| 7 | 7 | |
| 8 | const self_path = try std.fs.selfExePathAlloc(allocator); | |
| 9 | defer allocator.free(self_path); | |
| 8 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 9 | defer threaded.deinit(); | |
| 10 | const io = threaded.io(); | |
| 11 | ||
| 12 | const self_path = try std.process.executablePathAlloc(io, gpa); | |
| 13 | defer gpa.free(self_path); | |
| 14 | ||
| 15 | var self_exe = try std.process.openExecutable(io, .{}); | |
| 16 | defer self_exe.close(io); | |
| 10 | 17 | |
| 11 | var self_exe = try std.fs.openSelfExe(.{}); | |
| 12 | defer self_exe.close(); | |
| 13 | 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)]; | |
| 15 | 20 | |
| 16 | 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 | 1 | const std = @import("std"); |
| 2 | const fs = std.fs; | |
| 2 | const Io = std.Io; | |
| 3 | 3 | const mem = std.mem; |
| 4 | 4 | const warn = std.log.warn; |
| 5 | 5 | const fatal = std.process.fatal; |
| ... | ... | @@ -9,7 +9,7 @@ pub fn main() !void { |
| 9 | 9 | defer arena_instance.deinit(); |
| 10 | 10 | const arena = arena_instance.allocator(); |
| 11 | 11 | |
| 12 | var threaded: std.Io.Threaded = .init(arena); | |
| 12 | var threaded: std.Io.Threaded = .init(arena, .{}); | |
| 13 | 13 | defer threaded.deinit(); |
| 14 | 14 | const io = threaded.io(); |
| 15 | 15 | |
| ... | ... | @@ -18,11 +18,11 @@ pub fn main() !void { |
| 18 | 18 | const exe = args[0]; |
| 19 | 19 | var catted_anything = false; |
| 20 | 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 | 22 | const stdout = &stdout_writer.interface; |
| 23 | var stdin_reader = fs.File.stdin().readerStreaming(io, &.{}); | |
| 23 | var stdin_reader = Io.File.stdin().readerStreaming(io, &.{}); | |
| 24 | 24 | |
| 25 | const cwd = fs.cwd(); | |
| 25 | const cwd = Io.Dir.cwd(); | |
| 26 | 26 | |
| 27 | 27 | for (args[1..]) |arg| { |
| 28 | 28 | if (mem.eql(u8, arg, "-")) { |
| ... | ... | @@ -32,8 +32,8 @@ pub fn main() !void { |
| 32 | 32 | } else if (mem.startsWith(u8, arg, "-")) { |
| 33 | 33 | return usage(exe); |
| 34 | 34 | } else { |
| 35 | const file = cwd.openFile(arg, .{}) catch |err| fatal("unable to open file: {t}\n", .{err}); | |
| 36 | defer file.close(); | |
| 35 | const file = cwd.openFile(io, arg, .{}) catch |err| fatal("unable to open file: {t}\n", .{err}); | |
| 36 | defer file.close(io); | |
| 37 | 37 | |
| 38 | 38 | catted_anything = true; |
| 39 | 39 | var file_reader = file.reader(io, &.{}); |
test/standalone/simple/guess_number/main.zig+24-9| ... | ... | @@ -1,10 +1,23 @@ |
| 1 | 1 | const builtin = @import("builtin"); |
| 2 | 2 | const std = @import("std"); |
| 3 | 3 | |
| 4 | // See https://github.com/ziglang/zig/issues/24510 | |
| 5 | // for the plan to simplify this code. | |
| 4 | 6 | pub 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 | 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; | |
| 8 | 21 | |
| 9 | 22 | try out.writeAll("Welcome to the Guess Number Game in Zig.\n"); |
| 10 | 23 | |
| ... | ... | @@ -12,13 +25,15 @@ pub fn main() !void { |
| 12 | 25 | |
| 13 | 26 | while (true) { |
| 14 | 27 | try out.writeAll("\nGuess a number between 1 and 100: "); |
| 15 | var line_buf: [20]u8 = undefined; | |
| 16 | const amt = try stdin.read(&line_buf); | |
| 17 | if (amt == line_buf.len) { | |
| 18 | try out.writeAll("Input too long.\n"); | |
| 19 | continue; | |
| 20 | } | |
| 21 | const line = std.mem.trimEnd(u8, line_buf[0..amt], "\r\n"); | |
| 28 | const untrimmed_line = in.takeSentinel('\n') catch |err| switch (err) { | |
| 29 | error.StreamTooLong => { | |
| 30 | try out.writeAll("Line too long.\n"); | |
| 31 | _ = try in.discardDelimiterInclusive('\n'); | |
| 32 | continue; | |
| 33 | }, | |
| 34 | else => |e| return e, | |
| 35 | }; | |
| 36 | const line = std.mem.trimEnd(u8, untrimmed_line, "\r\n"); | |
| 22 | 37 | |
| 23 | 38 | const guess = std.fmt.parseUnsigned(u8, line, 10) catch { |
| 24 | 39 | try out.writeAll("Invalid number.\n"); |
test/standalone/simple/hello_world/hello.zig+11-1| ... | ... | @@ -1,5 +1,15 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | |
| 3 | // See https://github.com/ziglang/zig/issues/24510 | |
| 4 | // for the plan to simplify this code. | |
| 3 | 5 | pub 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 | 67 | |
| 68 | 68 | // Only target the MSVC ABI if MSVC/Windows SDK is available |
| 69 | 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 | 71 | error.OutOfMemory => @panic("oom"), |
| 72 | 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 | 5 | defer arena_state.deinit(); |
| 6 | 6 | const arena = arena_state.allocator(); |
| 7 | 7 | |
| 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 | 11 | const stdout = &stdout_writer.interface; |
| 10 | 12 | var args = try std.process.argsAlloc(arena); |
| 11 | 13 | for (args[1..], 1..) |arg, i| { |
test/standalone/windows_bat_args/fuzz.zig+59-16| ... | ... | @@ -1,5 +1,7 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 3 | 5 | const Allocator = std.mem.Allocator; |
| 4 | 6 | |
| 5 | 7 | pub fn main() anyerror!void { |
| ... | ... | @@ -7,6 +9,10 @@ pub fn main() anyerror!void { |
| 7 | 9 | defer std.debug.assert(debug_alloc_inst.deinit() == .ok); |
| 8 | 10 | const gpa = debug_alloc_inst.allocator(); |
| 9 | 11 | |
| 12 | var threaded: Io.Threaded = .init(gpa, .{}); | |
| 13 | defer threaded.deinit(); | |
| 14 | const io = threaded.io(); | |
| 15 | ||
| 10 | 16 | var it = try std.process.argsWithAllocator(gpa); |
| 11 | 17 | defer it.deinit(); |
| 12 | 18 | _ = it.next() orelse unreachable; // skip binary name |
| ... | ... | @@ -36,11 +42,11 @@ pub fn main() anyerror!void { |
| 36 | 42 | std.debug.print("rand seed: {}\n", .{seed}); |
| 37 | 43 | } |
| 38 | 44 | |
| 39 | var tmp = std.testing.tmpDir(.{}); | |
| 40 | defer tmp.cleanup(); | |
| 45 | var tmp = tmpDir(io, .{}); | |
| 46 | defer tmp.cleanup(io); | |
| 41 | 47 | |
| 42 | try tmp.dir.setAsCwd(); | |
| 43 | defer tmp.parent_dir.setAsCwd() catch {}; | |
| 48 | try std.process.setCurrentDir(io, tmp.dir); | |
| 49 | defer std.process.setCurrentDir(io, tmp.parent_dir) catch {}; | |
| 44 | 50 | |
| 45 | 51 | // `child_exe_path_orig` might be relative; make it relative to our new cwd. |
| 46 | 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 | 62 | const preamble_len = buf.items.len; |
| 57 | 63 | |
| 58 | 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 | 66 | buf.shrinkRetainingCapacity(preamble_len); |
| 61 | 67 | |
| 62 | 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 | 70 | buf.shrinkRetainingCapacity(preamble_len); |
| 65 | 71 | |
| 66 | 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 | 74 | buf.shrinkRetainingCapacity(preamble_len); |
| 69 | 75 | |
| 70 | 76 | var i: u64 = 0; |
| ... | ... | @@ -72,19 +78,19 @@ pub fn main() anyerror!void { |
| 72 | 78 | const rand_arg = try randomArg(gpa, rand); |
| 73 | 79 | defer gpa.free(rand_arg); |
| 74 | 80 | |
| 75 | try testExec(gpa, &.{rand_arg}, null); | |
| 81 | try testExec(gpa, io, &.{rand_arg}, null); | |
| 76 | 82 | |
| 77 | 83 | i += 1; |
| 78 | 84 | } |
| 79 | 85 | } |
| 80 | 86 | |
| 81 | fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 82 | try testExecBat(gpa, "args1.bat", args, env); | |
| 83 | try testExecBat(gpa, "args2.bat", args, env); | |
| 84 | try testExecBat(gpa, "args3.bat", args, env); | |
| 87 | fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 88 | try testExecBat(gpa, io, "args1.bat", args, env); | |
| 89 | try testExecBat(gpa, io, "args2.bat", args, env); | |
| 90 | try testExecBat(gpa, io, "args3.bat", args, env); | |
| 85 | 91 | } |
| 86 | 92 | |
| 87 | fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 93 | fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 88 | 94 | const argv = try gpa.alloc([]const u8, 1 + args.len); |
| 89 | 95 | defer gpa.free(argv); |
| 90 | 96 | argv[0] = bat; |
| ... | ... | @@ -92,8 +98,7 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8 |
| 92 | 98 | |
| 93 | 99 | const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat"); |
| 94 | 100 | |
| 95 | const result = try std.process.Child.run(.{ | |
| 96 | .allocator = gpa, | |
| 101 | const result = try std.process.Child.run(gpa, io, .{ | |
| 97 | 102 | .env_map = env, |
| 98 | 103 | .argv = argv, |
| 99 | 104 | }); |
| ... | ... | @@ -163,3 +168,41 @@ fn randomArg(gpa: Allocator, rand: std.Random) ![]const u8 { |
| 163 | 168 | |
| 164 | 169 | return buf.toOwnedSlice(gpa); |
| 165 | 170 | } |
| 171 | ||
| 172 | pub 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 | ||
| 194 | pub 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 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 3 | const Allocator = std.mem.Allocator; | |
| 2 | 4 | |
| 3 | 5 | pub fn main() anyerror!void { |
| 4 | 6 | var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init; |
| 5 | 7 | defer std.debug.assert(debug_alloc_inst.deinit() == .ok); |
| 6 | 8 | const gpa = debug_alloc_inst.allocator(); |
| 7 | 9 | |
| 10 | var threaded: Io.Threaded = .init(gpa, .{}); | |
| 11 | const io = threaded.io(); | |
| 12 | ||
| 8 | 13 | var it = try std.process.argsWithAllocator(gpa); |
| 9 | 14 | defer it.deinit(); |
| 10 | 15 | _ = it.next() orelse unreachable; // skip binary name |
| 11 | 16 | const child_exe_path_orig = it.next() orelse unreachable; |
| 12 | 17 | |
| 13 | var tmp = std.testing.tmpDir(.{}); | |
| 14 | defer tmp.cleanup(); | |
| 18 | var tmp = tmpDir(io, .{}); | |
| 19 | defer tmp.cleanup(io); | |
| 15 | 20 | |
| 16 | try tmp.dir.setAsCwd(); | |
| 17 | defer tmp.parent_dir.setAsCwd() catch {}; | |
| 21 | try std.process.setCurrentDir(io, tmp.dir); | |
| 22 | defer std.process.setCurrentDir(io, tmp.parent_dir) catch {}; | |
| 18 | 23 | |
| 19 | 24 | // `child_exe_path_orig` might be relative; make it relative to our new cwd. |
| 20 | 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 | 35 | const preamble_len = buf.items.len; |
| 31 | 36 | |
| 32 | 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 | 39 | buf.shrinkRetainingCapacity(preamble_len); |
| 35 | 40 | |
| 36 | 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 | 43 | buf.shrinkRetainingCapacity(preamble_len); |
| 39 | 44 | |
| 40 | 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 | 47 | buf.shrinkRetainingCapacity(preamble_len); |
| 43 | 48 | |
| 44 | 49 | // Test cases are from https://github.com/rust-lang/rust/blob/master/tests/ui/std/windows-bat-args.rs |
| 45 | try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\x00"}); | |
| 46 | try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\n"}); | |
| 47 | try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\r"}); | |
| 48 | try testExec(gpa, &.{ "a", "b" }, null); | |
| 49 | try testExec(gpa, &.{ "c is for cat", "d is for dog" }, null); | |
| 50 | try testExec(gpa, &.{ "\"", " \"" }, null); | |
| 51 | try testExec(gpa, &.{ "\\", "\\" }, null); | |
| 52 | try testExec(gpa, &.{">file.txt"}, null); | |
| 53 | try testExec(gpa, &.{"whoami.exe"}, null); | |
| 54 | try testExec(gpa, &.{"&a.exe"}, null); | |
| 55 | try testExec(gpa, &.{"&echo hello "}, null); | |
| 56 | try testExec(gpa, &.{ "&echo hello", "&whoami", ">file.txt" }, null); | |
| 57 | try testExec(gpa, &.{"!TMP!"}, null); | |
| 58 | try testExec(gpa, &.{"key=value"}, null); | |
| 59 | try testExec(gpa, &.{"\"key=value\""}, null); | |
| 60 | try testExec(gpa, &.{"key = value"}, null); | |
| 61 | try testExec(gpa, &.{"key=[\"value\"]"}, null); | |
| 62 | try testExec(gpa, &.{ "", "a=b" }, null); | |
| 63 | try testExec(gpa, &.{"key=\"foo bar\""}, null); | |
| 64 | try testExec(gpa, &.{"key=[\"my_value]"}, null); | |
| 65 | try testExec(gpa, &.{"key=[\"my_value\",\"other-value\"]"}, null); | |
| 66 | try testExec(gpa, &.{"key\\=value"}, null); | |
| 67 | try testExec(gpa, &.{"key=\"&whoami\""}, null); | |
| 68 | try testExec(gpa, &.{"key=\"value\"=5"}, null); | |
| 69 | try testExec(gpa, &.{"key=[\">file.txt\"]"}, null); | |
| 70 | try testExec(gpa, &.{"%hello"}, null); | |
| 71 | try testExec(gpa, &.{"%PATH%"}, null); | |
| 72 | try testExec(gpa, &.{"%%cd:~,%"}, null); | |
| 73 | try testExec(gpa, &.{"%PATH%PATH%"}, null); | |
| 74 | try testExec(gpa, &.{"\">file.txt"}, null); | |
| 75 | try testExec(gpa, &.{"abc\"&echo hello"}, null); | |
| 76 | try testExec(gpa, &.{"123\">file.txt"}, null); | |
| 77 | try testExec(gpa, &.{"\"&echo hello&whoami.exe"}, null); | |
| 78 | try testExec(gpa, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null); | |
| 79 | try testExec(gpa, &.{"&whoami.exe"}, null); | |
| 50 | try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\x00"}); | |
| 51 | try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\n"}); | |
| 52 | try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\r"}); | |
| 53 | try testExec(gpa, io, &.{ "a", "b" }, null); | |
| 54 | try testExec(gpa, io, &.{ "c is for cat", "d is for dog" }, null); | |
| 55 | try testExec(gpa, io, &.{ "\"", " \"" }, null); | |
| 56 | try testExec(gpa, io, &.{ "\\", "\\" }, null); | |
| 57 | try testExec(gpa, io, &.{">file.txt"}, null); | |
| 58 | try testExec(gpa, io, &.{"whoami.exe"}, null); | |
| 59 | try testExec(gpa, io, &.{"&a.exe"}, null); | |
| 60 | try testExec(gpa, io, &.{"&echo hello "}, null); | |
| 61 | try testExec(gpa, io, &.{ "&echo hello", "&whoami", ">file.txt" }, null); | |
| 62 | try testExec(gpa, io, &.{"!TMP!"}, null); | |
| 63 | try testExec(gpa, io, &.{"key=value"}, null); | |
| 64 | try testExec(gpa, io, &.{"\"key=value\""}, null); | |
| 65 | try testExec(gpa, io, &.{"key = value"}, null); | |
| 66 | try testExec(gpa, io, &.{"key=[\"value\"]"}, null); | |
| 67 | try testExec(gpa, io, &.{ "", "a=b" }, null); | |
| 68 | try testExec(gpa, io, &.{"key=\"foo bar\""}, null); | |
| 69 | try testExec(gpa, io, &.{"key=[\"my_value]"}, null); | |
| 70 | try testExec(gpa, io, &.{"key=[\"my_value\",\"other-value\"]"}, null); | |
| 71 | try testExec(gpa, io, &.{"key\\=value"}, null); | |
| 72 | try testExec(gpa, io, &.{"key=\"&whoami\""}, null); | |
| 73 | try testExec(gpa, io, &.{"key=\"value\"=5"}, null); | |
| 74 | try testExec(gpa, io, &.{"key=[\">file.txt\"]"}, null); | |
| 75 | try testExec(gpa, io, &.{"%hello"}, null); | |
| 76 | try testExec(gpa, io, &.{"%PATH%"}, null); | |
| 77 | try testExec(gpa, io, &.{"%%cd:~,%"}, null); | |
| 78 | try testExec(gpa, io, &.{"%PATH%PATH%"}, null); | |
| 79 | try testExec(gpa, io, &.{"\">file.txt"}, null); | |
| 80 | try testExec(gpa, io, &.{"abc\"&echo hello"}, null); | |
| 81 | try testExec(gpa, io, &.{"123\">file.txt"}, null); | |
| 82 | try testExec(gpa, io, &.{"\"&echo hello&whoami.exe"}, null); | |
| 83 | try testExec(gpa, io, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null); | |
| 84 | try testExec(gpa, io, &.{"&whoami.exe"}, null); | |
| 80 | 85 | |
| 81 | 86 | // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution. |
| 82 | 87 | // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped |
| ... | ... | @@ -94,14 +99,14 @@ pub fn main() anyerror!void { |
| 94 | 99 | // > "args1.bat .. " |
| 95 | 100 | // '"args1.bat .. "' is not recognized as an internal or external command, |
| 96 | 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 | 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 | 105 | defer gpa.free(absolute_path); |
| 101 | 106 | break :blk try std.mem.concat(gpa, u8, &.{ absolute_path, " .. " }); |
| 102 | 107 | }; |
| 103 | 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)); | |
| 105 | 110 | |
| 106 | 111 | var env = env: { |
| 107 | 112 | var env = try std.process.getEnvMap(gpa); |
| ... | ... | @@ -115,23 +120,23 @@ pub fn main() anyerror!void { |
| 115 | 120 | break :env env; |
| 116 | 121 | }; |
| 117 | 122 | defer env.deinit(); |
| 118 | try testExec(gpa, &.{"%FOO%"}, &env); | |
| 123 | try testExec(gpa, io, &.{"%FOO%"}, &env); | |
| 119 | 124 | |
| 120 | 125 | // 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 | } |
| 123 | 128 | |
| 124 | fn testExecError(err: anyerror, gpa: std.mem.Allocator, args: []const []const u8) !void { | |
| 125 | return std.testing.expectError(err, testExec(gpa, args, null)); | |
| 129 | fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8) !void { | |
| 130 | return std.testing.expectError(err, testExec(gpa, io, args, null)); | |
| 126 | 131 | } |
| 127 | 132 | |
| 128 | fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 129 | try testExecBat(gpa, "args1.bat", args, env); | |
| 130 | try testExecBat(gpa, "args2.bat", args, env); | |
| 131 | try testExecBat(gpa, "args3.bat", args, env); | |
| 133 | fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 134 | try testExecBat(gpa, io, "args1.bat", args, env); | |
| 135 | try testExecBat(gpa, io, "args2.bat", args, env); | |
| 136 | try testExecBat(gpa, io, "args3.bat", args, env); | |
| 132 | 137 | } |
| 133 | 138 | |
| 134 | fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 139 | fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 135 | 140 | const argv = try gpa.alloc([]const u8, 1 + args.len); |
| 136 | 141 | defer gpa.free(argv); |
| 137 | 142 | argv[0] = bat; |
| ... | ... | @@ -139,8 +144,7 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8 |
| 139 | 144 | |
| 140 | 145 | const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat"); |
| 141 | 146 | |
| 142 | const result = try std.process.Child.run(.{ | |
| 143 | .allocator = gpa, | |
| 147 | const result = try std.process.Child.run(gpa, io, .{ | |
| 144 | 148 | .env_map = env, |
| 145 | 149 | .argv = argv, |
| 146 | 150 | }); |
| ... | ... | @@ -160,3 +164,41 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8 |
| 160 | 164 | i += 1; |
| 161 | 165 | } |
| 162 | 166 | } |
| 167 | ||
| 168 | pub 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 | ||
| 190 | pub 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 | |
| 11 | 11 | if (args.len < 3) return error.MissingArgs; |
| 12 | 12 | |
| 13 | var threaded: std.Io.Threaded = .init(allocator, .{}); | |
| 14 | defer threaded.deinit(); | |
| 15 | const io = threaded.io(); | |
| 16 | ||
| 13 | 17 | const relative = try std.fs.path.relative(allocator, args[1], args[2]); |
| 14 | 18 | defer allocator.free(relative); |
| 15 | 19 | |
| 16 | var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); | |
| 20 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); | |
| 17 | 21 | const stdout = &stdout_writer.interface; |
| 18 | 22 | try stdout.writeAll(relative); |
| 19 | 23 | } |
test/standalone/windows_paths/test.zig+25-22| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | |
| 3 | 4 | pub fn main() anyerror!void { |
| 4 | 5 | var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| ... | ... | @@ -9,6 +10,8 @@ pub fn main() anyerror!void { |
| 9 | 10 | |
| 10 | 11 | if (args.len < 2) return error.MissingArgs; |
| 11 | 12 | |
| 13 | const io = std.Io.Threaded.global_single_threaded.ioBasic(); | |
| 14 | ||
| 12 | 15 | const exe_path = args[1]; |
| 13 | 16 | |
| 14 | 17 | const cwd_path = try std.process.getCwdAlloc(arena); |
| ... | ... | @@ -33,39 +36,39 @@ pub fn main() anyerror!void { |
| 33 | 36 | |
| 34 | 37 | // With the special =X: environment variable set, drive-relative paths that |
| 35 | 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); | |
| 37 | try checkRelative(arena, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &alt_drive_env_map); | |
| 39 | try checkRelative(arena, io, "..\\..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &alt_drive_env_map); | |
| 40 | try checkRelative(arena, io, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &alt_drive_env_map); | |
| 38 | 41 | |
| 39 | 42 | // Without that environment variable set, drive-relative paths that don't match the |
| 40 | 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); | |
| 42 | try checkRelative(arena, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env); | |
| 44 | try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env); | |
| 45 | try checkRelative(arena, io, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env); | |
| 43 | 46 | |
| 44 | 47 | // Bare drive-relative path with no components |
| 45 | try checkRelative(arena, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &empty_env); | |
| 46 | try checkRelative(arena, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &empty_env); | |
| 48 | try checkRelative(arena, io, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &empty_env); | |
| 49 | try checkRelative(arena, io, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &empty_env); | |
| 47 | 50 | |
| 48 | 51 | // Bare drive-relative path with no components, drive-CWD set |
| 49 | try checkRelative(arena, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &alt_drive_env_map); | |
| 50 | try checkRelative(arena, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &alt_drive_env_map); | |
| 52 | try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &alt_drive_env_map); | |
| 53 | try checkRelative(arena, io, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &alt_drive_env_map); | |
| 51 | 54 | |
| 52 | 55 | // Bare drive-relative path relative to the CWD should be equivalent if drive-CWD is set |
| 53 | try checkRelative(arena, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, null, &alt_drive_env_map); | |
| 54 | try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, null, &alt_drive_env_map); | |
| 56 | try checkRelative(arena, io, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, null, &alt_drive_env_map); | |
| 57 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, null, &alt_drive_env_map); | |
| 55 | 58 | |
| 56 | 59 | // Bare drive-relative should always be equivalent to itself |
| 57 | try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map); | |
| 58 | try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map); | |
| 59 | try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env); | |
| 60 | try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env); | |
| 60 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map); | |
| 61 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map); | |
| 62 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env); | |
| 63 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env); | |
| 61 | 64 | } |
| 62 | 65 | |
| 63 | 66 | if (parsed_cwd_path.kind == .unc_absolute) { |
| 64 | 67 | const drive_abs_path = try std.fmt.allocPrint(arena, "{c}:\\foo\\bar", .{alt_drive_letter}); |
| 65 | 68 | |
| 66 | 69 | { |
| 67 | try checkRelative(arena, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, null, &empty_env); | |
| 68 | try checkRelative(arena, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, null, &empty_env); | |
| 70 | try checkRelative(arena, io, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, null, &empty_env); | |
| 71 | try checkRelative(arena, io, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, null, &empty_env); | |
| 69 | 72 | } |
| 70 | 73 | } else if (parsed_cwd_path.kind == .drive_absolute) { |
| 71 | 74 | const cur_drive_letter = parsed_cwd_path.root[0]; |
| ... | ... | @@ -73,14 +76,14 @@ pub fn main() anyerror!void { |
| 73 | 76 | const unc_cwd = try std.fmt.allocPrint(arena, "\\\\127.0.0.1\\{c}$\\{s}", .{ cur_drive_letter, path_beyond_root }); |
| 74 | 77 | |
| 75 | 78 | { |
| 76 | try checkRelative(arena, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, null, &empty_env); | |
| 77 | try checkRelative(arena, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, null, &empty_env); | |
| 79 | try checkRelative(arena, io, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, null, &empty_env); | |
| 80 | try checkRelative(arena, io, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, null, &empty_env); | |
| 78 | 81 | } |
| 79 | 82 | { |
| 80 | 83 | const drive_abs = cwd_path; |
| 81 | 84 | const drive_rel = parsed_cwd_path.root[0..2]; |
| 82 | try checkRelative(arena, "", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env); | |
| 83 | try checkRelative(arena, "", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env); | |
| 85 | try checkRelative(arena, io, "", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env); | |
| 86 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env); | |
| 84 | 87 | } |
| 85 | 88 | } else { |
| 86 | 89 | return error.UnexpectedPathType; |
| ... | ... | @@ -89,13 +92,13 @@ pub fn main() anyerror!void { |
| 89 | 92 | |
| 90 | 93 | fn checkRelative( |
| 91 | 94 | allocator: std.mem.Allocator, |
| 95 | io: Io, | |
| 92 | 96 | expected_stdout: []const u8, |
| 93 | 97 | argv: []const []const u8, |
| 94 | 98 | cwd: ?[]const u8, |
| 95 | 99 | env_map: ?*const std.process.EnvMap, |
| 96 | 100 | ) !void { |
| 97 | const result = try std.process.Child.run(.{ | |
| 98 | .allocator = allocator, | |
| 101 | const result = try std.process.Child.run(allocator, io, .{ | |
| 99 | 102 | .argv = argv, |
| 100 | 103 | .cwd = cwd, |
| 101 | 104 | .env_map = env_map, |
test/standalone/windows_spawn/hello.zig+2-1| ... | ... | @@ -1,7 +1,8 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | |
| 3 | 3 | pub 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 | 6 | const stdout = &stdout_writer.interface; |
| 6 | 7 | try stdout.writeAll("hello from exe\n"); |
| 7 | 8 | } |
test/standalone/windows_spawn/main.zig+119-76| ... | ... | @@ -1,29 +1,35 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 3 | const Allocator = std.mem.Allocator; | |
| 2 | 4 | |
| 3 | 5 | const windows = std.os.windows; |
| 4 | 6 | const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral; |
| 5 | 7 | |
| 6 | 8 | pub fn main() anyerror!void { |
| 7 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; | |
| 8 | defer if (gpa.deinit() == .leak) @panic("found memory leaks"); | |
| 9 | const allocator = gpa.allocator(); | |
| 9 | var debug_allocator: std.heap.DebugAllocator(.{}) = .init; | |
| 10 | defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks"); | |
| 11 | const gpa = debug_allocator.allocator(); | |
| 10 | 12 | |
| 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 | 18 | defer it.deinit(); |
| 13 | 19 | _ = it.next() orelse unreachable; // skip binary name |
| 14 | 20 | const hello_exe_cache_path = it.next() orelse unreachable; |
| 15 | 21 | |
| 16 | var tmp = std.testing.tmpDir(.{}); | |
| 17 | defer tmp.cleanup(); | |
| 22 | var tmp = tmpDir(io, .{}); | |
| 23 | defer tmp.cleanup(io); | |
| 18 | 24 | |
| 19 | const tmp_absolute_path = try tmp.dir.realpathAlloc(allocator, "."); | |
| 20 | defer allocator.free(tmp_absolute_path); | |
| 21 | const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeAllocZ(allocator, tmp_absolute_path); | |
| 22 | defer allocator.free(tmp_absolute_path_w); | |
| 23 | const cwd_absolute_path = try std.fs.cwd().realpathAlloc(allocator, "."); | |
| 24 | defer allocator.free(cwd_absolute_path); | |
| 25 | const tmp_relative_path = try std.fs.path.relative(allocator, cwd_absolute_path, tmp_absolute_path); | |
| 26 | defer allocator.free(tmp_relative_path); | |
| 25 | const tmp_absolute_path = try tmp.dir.realPathFileAlloc(io, ".", gpa); | |
| 26 | defer gpa.free(tmp_absolute_path); | |
| 27 | const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeAllocZ(gpa, tmp_absolute_path); | |
| 28 | defer gpa.free(tmp_absolute_path_w); | |
| 29 | const cwd_absolute_path = try Io.Dir.cwd().realPathFileAlloc(io, ".", gpa); | |
| 30 | defer gpa.free(cwd_absolute_path); | |
| 31 | const tmp_relative_path = try std.fs.path.relative(gpa, cwd_absolute_path, tmp_absolute_path); | |
| 32 | defer gpa.free(tmp_relative_path); | |
| 27 | 33 | |
| 28 | 34 | // Clear PATH |
| 29 | 35 | std.debug.assert(windows.kernel32.SetEnvironmentVariableW( |
| ... | ... | @@ -38,10 +44,10 @@ pub fn main() anyerror!void { |
| 38 | 44 | ) == windows.TRUE); |
| 39 | 45 | |
| 40 | 46 | // 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"); | |
| 42 | 48 | |
| 43 | 49 | // 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"); | |
| 45 | 51 | |
| 46 | 52 | std.debug.assert(windows.kernel32.SetEnvironmentVariableW( |
| 47 | 53 | utf16Literal("PATH"), |
| ... | ... | @@ -49,82 +55,82 @@ pub fn main() anyerror!void { |
| 49 | 55 | ) == windows.TRUE); |
| 50 | 56 | |
| 51 | 57 | // 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, .{}); | |
| 53 | 59 | |
| 54 | 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 | 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 | 64 | // 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", "")); | |
| 60 | 66 | |
| 61 | 67 | // 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 | 69 | // 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" }); | |
| 65 | 71 | |
| 66 | 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 | 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 | 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"); | |
| 72 | 78 | |
| 73 | 79 | // 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"); | |
| 75 | 81 | |
| 76 | 82 | // with extension should now fail |
| 77 | try testExecError(error.FileNotFound, allocator, "hello.exe"); | |
| 83 | try testExecError(error.FileNotFound, gpa, io, "hello.exe"); | |
| 78 | 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"); | |
| 80 | 86 | |
| 81 | try tmp.dir.makeDir("something"); | |
| 82 | try renameExe(tmp.dir, "hello", "something/hello.exe"); | |
| 87 | try tmp.dir.createDir(io, "something", .default_dir); | |
| 88 | try renameExe(tmp.dir, io, "hello", "something/hello.exe"); | |
| 83 | 89 | |
| 84 | const relative_path_no_ext = try std.fs.path.join(allocator, &.{ tmp_relative_path, "something/hello" }); | |
| 85 | defer allocator.free(relative_path_no_ext); | |
| 90 | const relative_path_no_ext = try std.fs.path.join(gpa, &.{ tmp_relative_path, "something/hello" }); | |
| 91 | defer gpa.free(relative_path_no_ext); | |
| 86 | 92 | |
| 87 | 93 | // 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 | 95 | // 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"); | |
| 91 | 97 | |
| 92 | 98 | // 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"); | |
| 94 | 100 | |
| 95 | 101 | // 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" }); | |
| 97 | 103 | |
| 98 | 104 | // Trying to execute it with extension will give InvalidExe. This is a special |
| 99 | 105 | // case for .EXE extensions, where if they ever try to get executed but they are |
| 100 | 106 | // invalid, that gets treated as a fatal error wherever they are found and InvalidExe |
| 101 | 107 | // is returned immediately. |
| 102 | try testExecError(error.InvalidExe, allocator, "hello.exe"); | |
| 108 | try testExecError(error.InvalidExe, gpa, io, "hello.exe"); | |
| 103 | 109 | // Same thing applies to the command with no extension--even though there is a |
| 104 | 110 | // hello.bat that could be executed, it should stop after it tries executing |
| 105 | 111 | // hello.exe and getting InvalidExe. |
| 106 | try testExecError(error.InvalidExe, allocator, "hello"); | |
| 112 | try testExecError(error.InvalidExe, gpa, io, "hello"); | |
| 107 | 113 | |
| 108 | 114 | // 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"); | |
| 110 | 116 | |
| 111 | 117 | // Now, trying to execute it without an extension should treat InvalidExe as recoverable |
| 112 | 118 | // 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"); | |
| 114 | 120 | |
| 115 | 121 | // 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 | 123 | // Then we should now get FileNotFound when trying to execute 'goodbye', |
| 118 | 124 | // since that is what the original error will be after searching for 'goodbye' |
| 119 | 125 | // in the cwd. It will try to execute 'goodbye' from the PATH but the InvalidExe error |
| 120 | 126 | // should be ignored in this case. |
| 121 | try testExecError(error.FileNotFound, allocator, "goodbye"); | |
| 127 | try testExecError(error.FileNotFound, gpa, io, "goodbye"); | |
| 122 | 128 | |
| 123 | 129 | // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir |
| 124 | try tmp.dir.setAsCwd(); | |
| 125 | defer tmp.parent_dir.setAsCwd() catch {}; | |
| 126 | const something_subdir_abs_path = try std.mem.concatWithSentinel(allocator, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0); | |
| 127 | defer allocator.free(something_subdir_abs_path); | |
| 130 | try std.process.setCurrentDir(io, tmp.dir); | |
| 131 | defer std.process.setCurrentDir(io, tmp.parent_dir) catch {}; | |
| 132 | const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0); | |
| 133 | defer gpa.free(something_subdir_abs_path); | |
| 128 | 134 | |
| 129 | 135 | std.debug.assert(windows.kernel32.SetEnvironmentVariableW( |
| 130 | 136 | utf16Literal("PATH"), |
| ... | ... | @@ -133,37 +139,37 @@ pub fn main() anyerror!void { |
| 133 | 139 | |
| 134 | 140 | // Now trying to execute goodbye should give error.InvalidExe since it's the original |
| 135 | 141 | // error that we got when trying within the cwd |
| 136 | try testExecError(error.InvalidExe, allocator, "goodbye"); | |
| 142 | try testExecError(error.InvalidExe, gpa, io, "goodbye"); | |
| 137 | 143 | |
| 138 | 144 | // 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"); | |
| 140 | 146 | |
| 141 | 147 | // 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 | 149 | // And try to execute goodbye, then the one in something should be found |
| 144 | 150 | // 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"); | |
| 146 | 152 | |
| 147 | 153 | // If we use an absolute path to execute the invalid goodbye |
| 148 | const goodbye_abs_path = try std.mem.join(allocator, "\\", &.{ tmp_absolute_path, "goodbye" }); | |
| 149 | defer allocator.free(goodbye_abs_path); | |
| 154 | const goodbye_abs_path = try std.mem.join(gpa, "\\", &.{ tmp_absolute_path, "goodbye" }); | |
| 155 | defer gpa.free(goodbye_abs_path); | |
| 150 | 156 | // 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); | |
| 152 | 158 | |
| 153 | 159 | // If we try to exec but provide a cwd that is an absolute path, the PATH |
| 154 | 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"); | |
| 156 | 162 | |
| 157 | 163 | // introduce some extra path separators into the path which is dealt with inside the spawn call. |
| 158 | 164 | const denormed_something_subdir_size = std.mem.replacementSize(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\")); |
| 159 | 165 | |
| 160 | const denormed_something_subdir_abs_path = try allocator.allocSentinel(u16, denormed_something_subdir_size, 0); | |
| 161 | defer allocator.free(denormed_something_subdir_abs_path); | |
| 166 | const denormed_something_subdir_abs_path = try gpa.allocSentinel(u16, denormed_something_subdir_size, 0); | |
| 167 | defer gpa.free(denormed_something_subdir_abs_path); | |
| 162 | 168 | |
| 163 | 169 | _ = std.mem.replace(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\"), denormed_something_subdir_abs_path); |
| 164 | 170 | |
| 165 | const denormed_something_subdir_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, denormed_something_subdir_abs_path); | |
| 166 | defer allocator.free(denormed_something_subdir_wtf8); | |
| 171 | const denormed_something_subdir_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, denormed_something_subdir_abs_path); | |
| 172 | defer gpa.free(denormed_something_subdir_wtf8); | |
| 167 | 173 | |
| 168 | 174 | // clear the path to ensure that the match comes from the cwd |
| 169 | 175 | std.debug.assert(windows.kernel32.SetEnvironmentVariableW( |
| ... | ... | @@ -171,21 +177,21 @@ pub fn main() anyerror!void { |
| 171 | 177 | null, |
| 172 | 178 | ) == windows.TRUE); |
| 173 | 179 | |
| 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"); | |
| 175 | 181 | |
| 176 | 182 | // normalization should also work if the non-normalized path is found in the PATH var. |
| 177 | 183 | std.debug.assert(windows.kernel32.SetEnvironmentVariableW( |
| 178 | 184 | utf16Literal("PATH"), |
| 179 | 185 | denormed_something_subdir_abs_path, |
| 180 | 186 | ) == windows.TRUE); |
| 181 | try testExec(allocator, "goodbye", "hello from exe\n"); | |
| 187 | try testExec(gpa, io, "goodbye", "hello from exe\n"); | |
| 182 | 188 | |
| 183 | 189 | // now make sure we can launch executables "outside" of the cwd |
| 184 | var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{}); | |
| 185 | defer subdir_cwd.close(); | |
| 190 | var subdir_cwd = try tmp.dir.openDir(io, denormed_something_subdir_wtf8, .{}); | |
| 191 | defer subdir_cwd.close(io); | |
| 186 | 192 | |
| 187 | try renameExe(tmp.dir, "something/goodbye.exe", "hello.exe"); | |
| 188 | try subdir_cwd.setAsCwd(); | |
| 193 | try renameExe(tmp.dir, io, "something/goodbye.exe", "hello.exe"); | |
| 194 | try std.process.setCurrentDir(io, subdir_cwd); | |
| 189 | 195 | |
| 190 | 196 | // clear the PATH again |
| 191 | 197 | std.debug.assert(windows.kernel32.SetEnvironmentVariableW( |
| ... | ... | @@ -194,33 +200,32 @@ pub fn main() anyerror!void { |
| 194 | 200 | ) == windows.TRUE); |
| 195 | 201 | |
| 196 | 202 | // 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 | } |
| 199 | 205 | |
| 200 | fn testExecError(err: anyerror, allocator: std.mem.Allocator, command: []const u8) !void { | |
| 201 | return std.testing.expectError(err, testExec(allocator, command, "")); | |
| 206 | fn testExecError(err: anyerror, gpa: Allocator, io: Io, command: []const u8) !void { | |
| 207 | return std.testing.expectError(err, testExec(gpa, io, command, "")); | |
| 202 | 208 | } |
| 203 | 209 | |
| 204 | fn testExec(allocator: std.mem.Allocator, command: []const u8, expected_stdout: []const u8) !void { | |
| 205 | return testExecWithCwd(allocator, command, null, expected_stdout); | |
| 210 | fn testExec(gpa: Allocator, io: Io, command: []const u8, expected_stdout: []const u8) !void { | |
| 211 | return testExecWithCwd(gpa, io, command, null, expected_stdout); | |
| 206 | 212 | } |
| 207 | 213 | |
| 208 | fn testExecWithCwd(allocator: std.mem.Allocator, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void { | |
| 209 | const result = try std.process.Child.run(.{ | |
| 210 | .allocator = allocator, | |
| 214 | fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void { | |
| 215 | const result = try std.process.Child.run(gpa, io, .{ | |
| 211 | 216 | .argv = &[_][]const u8{command}, |
| 212 | 217 | .cwd = cwd, |
| 213 | 218 | }); |
| 214 | defer allocator.free(result.stdout); | |
| 215 | defer allocator.free(result.stderr); | |
| 219 | defer gpa.free(result.stdout); | |
| 220 | defer gpa.free(result.stderr); | |
| 216 | 221 | |
| 217 | 222 | try std.testing.expectEqualStrings("", result.stderr); |
| 218 | 223 | try std.testing.expectEqualStrings(expected_stdout, result.stdout); |
| 219 | 224 | } |
| 220 | 225 | |
| 221 | fn renameExe(dir: std.fs.Dir, old_sub_path: []const u8, new_sub_path: []const u8) !void { | |
| 226 | fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void { | |
| 222 | 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 | 229 | error.AccessDenied => { |
| 225 | 230 | if (attempt == 13) return error.AccessDenied; |
| 226 | 231 | // 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 | 236 | else => |e| return e, |
| 232 | 237 | }; |
| 233 | 238 | } |
| 239 | ||
| 240 | pub 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 | ||
| 262 | pub 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 | 187 | .link_libc = true, |
| 188 | 188 | }, |
| 189 | 189 | |
| 190 | .{ | |
| 191 | .target = .{ | |
| 192 | .cpu_arch = .aarch64, | |
| 193 | .os_tag = .linux, | |
| 194 | .abi = .none, | |
| 195 | }, | |
| 196 | .use_llvm = false, | |
| 197 | .use_lld = false, | |
| 198 | .optimize_mode = .ReleaseFast, | |
| 199 | .strip = true, | |
| 200 | }, | |
| 201 | .{ | |
| 202 | .target = .{ | |
| 203 | .cpu_arch = .aarch64, | |
| 204 | .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.neoverse_n1 }, | |
| 205 | .os_tag = .linux, | |
| 206 | .abi = .none, | |
| 207 | }, | |
| 208 | .use_llvm = false, | |
| 209 | .use_lld = false, | |
| 210 | .optimize_mode = .ReleaseFast, | |
| 211 | .strip = true, | |
| 212 | }, | |
| 190 | // Disabled due to https://codeberg.org/ziglang/zig/pulls/30232#issuecomment-9203351 | |
| 191 | //.{ | |
| 192 | // .target = .{ | |
| 193 | // .cpu_arch = .aarch64, | |
| 194 | // .os_tag = .linux, | |
| 195 | // .abi = .none, | |
| 196 | // }, | |
| 197 | // .use_llvm = false, | |
| 198 | // .use_lld = false, | |
| 199 | // .optimize_mode = .ReleaseFast, | |
| 200 | // .strip = true, | |
| 201 | //}, | |
| 202 | //.{ | |
| 203 | // .target = .{ | |
| 204 | // .cpu_arch = .aarch64, | |
| 205 | // .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.neoverse_n1 }, | |
| 206 | // .os_tag = .linux, | |
| 207 | // .abi = .none, | |
| 208 | // }, | |
| 209 | // .use_llvm = false, | |
| 210 | // .use_lld = false, | |
| 211 | // .optimize_mode = .ReleaseFast, | |
| 212 | // .strip = true, | |
| 213 | //}, | |
| 213 | 214 | |
| 214 | 215 | .{ |
| 215 | 216 | .target = .{ |
| ... | ... | @@ -1204,17 +1205,18 @@ const test_targets = blk: { |
| 1204 | 1205 | }, |
| 1205 | 1206 | }, |
| 1206 | 1207 | |
| 1207 | .{ | |
| 1208 | .target = .{ | |
| 1209 | .cpu_arch = .aarch64, | |
| 1210 | .os_tag = .macos, | |
| 1211 | .abi = .none, | |
| 1212 | }, | |
| 1213 | .use_llvm = false, | |
| 1214 | .use_lld = false, | |
| 1215 | .optimize_mode = .ReleaseFast, | |
| 1216 | .strip = true, | |
| 1217 | }, | |
| 1208 | // Disabled due to https://codeberg.org/ziglang/zig/pulls/30232#issuecomment-9203351 | |
| 1209 | //.{ | |
| 1210 | // .target = .{ | |
| 1211 | // .cpu_arch = .aarch64, | |
| 1212 | // .os_tag = .macos, | |
| 1213 | // .abi = .none, | |
| 1214 | // }, | |
| 1215 | // .use_llvm = false, | |
| 1216 | // .use_lld = false, | |
| 1217 | // .optimize_mode = .ReleaseFast, | |
| 1218 | // .strip = true, | |
| 1219 | //}, | |
| 1218 | 1220 | |
| 1219 | 1221 | .{ |
| 1220 | 1222 | .target = .{ |
| ... | ... | @@ -2024,6 +2026,7 @@ pub fn addLinkTests( |
| 2024 | 2026 | pub fn addCliTests(b: *std.Build) *Step { |
| 2025 | 2027 | const step = b.step("test-cli", "Test the command line interface"); |
| 2026 | 2028 | const s = std.fs.path.sep_str; |
| 2029 | const io = b.graph.io; | |
| 2027 | 2030 | |
| 2028 | 2031 | { |
| 2029 | 2032 | // Test `zig init`. |
| ... | ... | @@ -2132,14 +2135,14 @@ pub fn addCliTests(b: *std.Build) *Step { |
| 2132 | 2135 | const tmp_path = b.makeTempPath(); |
| 2133 | 2136 | const unformatted_code = " // no reason for indent"; |
| 2134 | 2137 | |
| 2135 | var dir = std.fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled"); | |
| 2136 | defer dir.close(); | |
| 2137 | dir.writeFile(.{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled"); | |
| 2138 | dir.writeFile(.{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled"); | |
| 2139 | dir.makeDir("subdir") catch @panic("unhandled"); | |
| 2140 | var subdir = dir.openDir("subdir", .{}) catch @panic("unhandled"); | |
| 2141 | defer subdir.close(); | |
| 2142 | subdir.writeFile(.{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled"); | |
| 2138 | var dir = std.Io.Dir.cwd().openDir(io, tmp_path, .{}) catch @panic("unhandled"); | |
| 2139 | defer dir.close(io); | |
| 2140 | dir.writeFile(io, .{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled"); | |
| 2141 | dir.writeFile(io, .{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled"); | |
| 2142 | dir.createDir(io, "subdir", .default_dir) catch @panic("unhandled"); | |
| 2143 | var subdir = dir.openDir(io, "subdir", .{}) catch @panic("unhandled"); | |
| 2144 | defer subdir.close(io); | |
| 2145 | subdir.writeFile(io, .{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled"); | |
| 2143 | 2146 | |
| 2144 | 2147 | // Test zig fmt affecting only the appropriate files. |
| 2145 | 2148 | const run1 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "fmt1.zig" }); |
| ... | ... | @@ -2629,11 +2632,12 @@ pub fn addCases( |
| 2629 | 2632 | ) !void { |
| 2630 | 2633 | const arena = b.allocator; |
| 2631 | 2634 | const gpa = b.allocator; |
| 2635 | const io = b.graph.io; | |
| 2632 | 2636 | |
| 2633 | var cases = @import("src/Cases.zig").init(gpa, arena); | |
| 2637 | var cases = @import("src/Cases.zig").init(gpa, arena, io); | |
| 2634 | 2638 | |
| 2635 | var dir = try b.build_root.handle.openDir("test/cases", .{ .iterate = true }); | |
| 2636 | defer dir.close(); | |
| 2639 | var dir = try b.build_root.handle.openDir(io, "test/cases", .{ .iterate = true }); | |
| 2640 | defer dir.close(io); | |
| 2637 | 2641 | |
| 2638 | 2642 | cases.addFromDir(dir, b); |
| 2639 | 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 | 2682 | return step; |
| 2679 | 2683 | } |
| 2680 | 2684 | |
| 2681 | pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void { | |
| 2685 | pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []const []const u8) !void { | |
| 2686 | const io = b.graph.io; | |
| 2687 | ||
| 2682 | 2688 | const incr_check = b.addExecutable(.{ |
| 2683 | 2689 | .name = "incr-check", |
| 2684 | 2690 | .root_module = b.createModule(.{ |
| ... | ... | @@ -2688,12 +2694,17 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void { |
| 2688 | 2694 | }), |
| 2689 | 2695 | }); |
| 2690 | 2696 | |
| 2691 | var dir = try b.build_root.handle.openDir("test/incremental", .{ .iterate = true }); | |
| 2692 | defer dir.close(); | |
| 2697 | var dir = try b.build_root.handle.openDir(io, "test/incremental", .{ .iterate = true }); | |
| 2698 | defer dir.close(io); | |
| 2693 | 2699 | |
| 2694 | 2700 | var it = try dir.walk(b.graph.arena); |
| 2695 | while (try it.next()) |entry| { | |
| 2701 | while (try it.next(io)) |entry| { | |
| 2696 | 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; | |
| 2697 | 2708 | |
| 2698 | 2709 | const run = b.addRunArtifact(incr_check); |
| 2699 | 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 | 2713 | run.addFileArg(b.path("test/incremental/").path(b, entry.path)); |
| 2703 | 2714 | run.addArgs(&.{ "--zig-lib-dir", b.fmt("{f}", .{b.graph.zig_lib_directory}) }); |
| 2704 | 2715 | |
| 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 | 2721 | run.addCheck(.{ .expect_term = .{ .Exited = 0 } }); |
| 2706 | 2722 | |
| 2707 | 2723 | test_step.dependOn(&run.step); |
tools/docgen.zig+18-16| ... | ... | @@ -1,6 +1,8 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 3 | const fs = std.fs; | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Dir = std.Io.Dir; | |
| 4 | 6 | const process = std.process; |
| 5 | 7 | const Progress = std.Progress; |
| 6 | 8 | const print = std.debug.print; |
| ... | ... | @@ -8,7 +10,6 @@ const mem = std.mem; |
| 8 | 10 | const testing = std.testing; |
| 9 | 11 | const Allocator = std.mem.Allocator; |
| 10 | 12 | const ArrayList = std.ArrayList; |
| 11 | const getExternalExecutor = std.zig.system.getExternalExecutor; | |
| 12 | 13 | const fatal = std.process.fatal; |
| 13 | 14 | const Writer = std.Io.Writer; |
| 14 | 15 | |
| ... | ... | @@ -38,7 +39,7 @@ pub fn main() !void { |
| 38 | 39 | |
| 39 | 40 | const gpa = arena; |
| 40 | 41 | |
| 41 | var threaded: std.Io.Threaded = .init(gpa); | |
| 42 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 42 | 43 | defer threaded.deinit(); |
| 43 | 44 | const io = threaded.io(); |
| 44 | 45 | |
| ... | ... | @@ -49,7 +50,7 @@ pub fn main() !void { |
| 49 | 50 | while (args_it.next()) |arg| { |
| 50 | 51 | if (mem.startsWith(u8, arg, "-")) { |
| 51 | 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 | 54 | process.exit(0); |
| 54 | 55 | } else if (mem.eql(u8, arg, "--code-dir")) { |
| 55 | 56 | if (args_it.next()) |param| { |
| ... | ... | @@ -72,16 +73,16 @@ pub fn main() !void { |
| 72 | 73 | const output_path = opt_output orelse fatal("missing output file", .{}); |
| 73 | 74 | const code_dir_path = opt_code_dir orelse fatal("missing --code-dir argument", .{}); |
| 74 | 75 | |
| 75 | var in_file = try fs.cwd().openFile(input_path, .{}); | |
| 76 | defer in_file.close(); | |
| 76 | var in_file = try Dir.cwd().openFile(io, input_path, .{}); | |
| 77 | defer in_file.close(io); | |
| 77 | 78 | |
| 78 | var out_file = try fs.cwd().createFile(output_path, .{}); | |
| 79 | defer out_file.close(); | |
| 79 | var out_file = try Dir.cwd().createFile(io, output_path, .{}); | |
| 80 | defer out_file.close(io); | |
| 80 | 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); | |
| 82 | 83 | |
| 83 | var code_dir = try fs.cwd().openDir(code_dir_path, .{}); | |
| 84 | defer code_dir.close(); | |
| 84 | var code_dir = try Dir.cwd().openDir(io, code_dir_path, .{}); | |
| 85 | defer code_dir.close(io); | |
| 85 | 86 | |
| 86 | 87 | var in_file_reader = in_file.reader(io, &.{}); |
| 87 | 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 | 90 | var tokenizer = Tokenizer.init(input_path, input_file_bytes); |
| 90 | 91 | var toc = try genToc(arena, &tokenizer); |
| 91 | 92 | |
| 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 | 94 | try out_file_writer.end(); |
| 94 | 95 | } |
| 95 | 96 | |
| ... | ... | @@ -988,9 +989,10 @@ fn printShell(out: *Writer, shell_content: []const u8, escape: bool) !void { |
| 988 | 989 | |
| 989 | 990 | fn genHtml( |
| 990 | 991 | allocator: Allocator, |
| 992 | io: Io, | |
| 991 | 993 | tokenizer: *Tokenizer, |
| 992 | 994 | toc: *Toc, |
| 993 | code_dir: std.fs.Dir, | |
| 995 | code_dir: Dir, | |
| 994 | 996 | out: *Writer, |
| 995 | 997 | ) !void { |
| 996 | 998 | for (toc.nodes) |node| { |
| ... | ... | @@ -1042,11 +1044,11 @@ fn genHtml( |
| 1042 | 1044 | }, |
| 1043 | 1045 | .Code => |code| { |
| 1044 | 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 | 1049 | defer allocator.free(out_basename); |
| 1048 | 1050 | |
| 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 | 1052 | return parseError(tokenizer, code.token, "unable to open '{s}': {t}", .{ out_basename, err }); |
| 1051 | 1053 | }; |
| 1052 | 1054 | defer allocator.free(contents); |
tools/doctest.zig+29-34| ... | ... | @@ -2,10 +2,10 @@ const builtin = @import("builtin"); |
| 2 | 2 | |
| 3 | 3 | const std = @import("std"); |
| 4 | 4 | const Io = std.Io; |
| 5 | const Dir = std.Io.Dir; | |
| 5 | 6 | const Writer = std.Io.Writer; |
| 6 | 7 | const fatal = std.process.fatal; |
| 7 | 8 | const mem = std.mem; |
| 8 | const fs = std.fs; | |
| 9 | 9 | const process = std.process; |
| 10 | 10 | const Allocator = std.mem.Allocator; |
| 11 | 11 | const testing = std.testing; |
| ... | ... | @@ -40,7 +40,7 @@ pub fn main() !void { |
| 40 | 40 | |
| 41 | 41 | const gpa = arena; |
| 42 | 42 | |
| 43 | var threaded: std.Io.Threaded = .init(gpa); | |
| 43 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 44 | 44 | defer threaded.deinit(); |
| 45 | 45 | const io = threaded.io(); |
| 46 | 46 | |
| ... | ... | @@ -53,7 +53,7 @@ pub fn main() !void { |
| 53 | 53 | while (args_it.next()) |arg| { |
| 54 | 54 | if (mem.startsWith(u8, arg, "-")) { |
| 55 | 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 | 57 | process.exit(0); |
| 58 | 58 | } else if (mem.eql(u8, arg, "-i")) { |
| 59 | 59 | opt_input = args_it.next() orelse fatal("expected parameter after -i", .{}); |
| ... | ... | @@ -78,37 +78,37 @@ pub fn main() !void { |
| 78 | 78 | const zig_path = opt_zig orelse fatal("missing zig compiler path (--zig)", .{}); |
| 79 | 79 | const cache_root = opt_cache_root orelse fatal("missing cache root path (--cache-root)", .{}); |
| 80 | 80 | |
| 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 | 82 | const code = try parseManifest(arena, source_bytes); |
| 83 | 83 | const source = stripManifest(source_bytes); |
| 84 | 84 | |
| 85 | 85 | const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{ |
| 86 | 86 | cache_root, std.crypto.random.int(u64), |
| 87 | 87 | }); |
| 88 | fs.cwd().makePath(tmp_dir_path) catch |err| | |
| 89 | fatal("unable to create tmp dir '{s}': {s}", .{ tmp_dir_path, @errorName(err) }); | |
| 90 | defer fs.cwd().deleteTree(tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {s}", .{ | |
| 91 | tmp_dir_path, @errorName(err), | |
| 88 | Dir.cwd().createDirPath(io, tmp_dir_path) catch |err| | |
| 89 | fatal("unable to create tmp dir '{s}': {t}", .{ tmp_dir_path, err }); | |
| 90 | defer Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{ | |
| 91 | tmp_dir_path, err, | |
| 92 | 92 | }); |
| 93 | 93 | |
| 94 | var out_file = try fs.cwd().createFile(output_path, .{}); | |
| 95 | defer out_file.close(); | |
| 94 | var out_file = try Dir.cwd().createFile(io, output_path, .{}); | |
| 95 | defer out_file.close(io); | |
| 96 | 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); | |
| 98 | 98 | |
| 99 | 99 | const out = &out_file_writer.interface; |
| 100 | 100 | |
| 101 | try printSourceBlock(arena, out, source, fs.path.basename(input_path)); | |
| 101 | try printSourceBlock(arena, out, source, Dir.path.basename(input_path)); | |
| 102 | 102 | try printOutput( |
| 103 | 103 | arena, |
| 104 | 104 | io, |
| 105 | 105 | out, |
| 106 | 106 | code, |
| 107 | 107 | tmp_dir_path, |
| 108 | try std.fs.path.relative(arena, tmp_dir_path, zig_path), | |
| 109 | try std.fs.path.relative(arena, tmp_dir_path, input_path), | |
| 108 | try Dir.path.relative(arena, tmp_dir_path, zig_path), | |
| 109 | try Dir.path.relative(arena, tmp_dir_path, input_path), | |
| 110 | 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 | 112 | else |
| 113 | 113 | null, |
| 114 | 114 | ); |
| ... | ... | @@ -141,7 +141,7 @@ fn printOutput( |
| 141 | 141 | defer shell_buffer.deinit(); |
| 142 | 142 | const shell_out = &shell_buffer.writer; |
| 143 | 143 | |
| 144 | const code_name = std.fs.path.stem(input_path); | |
| 144 | const code_name = Dir.path.stem(input_path); | |
| 145 | 145 | |
| 146 | 146 | switch (code.id) { |
| 147 | 147 | .exe => |expected_outcome| code_block: { |
| ... | ... | @@ -201,8 +201,7 @@ fn printOutput( |
| 201 | 201 | try shell_out.print("\n", .{}); |
| 202 | 202 | |
| 203 | 203 | if (expected_outcome == .build_fail) { |
| 204 | const result = try process.Child.run(.{ | |
| 205 | .allocator = arena, | |
| 204 | const result = try process.Child.run(arena, io, .{ | |
| 206 | 205 | .argv = build_args.items, |
| 207 | 206 | .cwd = tmp_dir_path, |
| 208 | 207 | .env_map = &env_map, |
| ... | ... | @@ -227,7 +226,7 @@ fn printOutput( |
| 227 | 226 | try shell_out.writeAll(colored_stderr); |
| 228 | 227 | break :code_block; |
| 229 | 228 | } |
| 230 | const exec_result = run(arena, &env_map, tmp_dir_path, build_args.items) catch | |
| 229 | const exec_result = run(arena, io, &env_map, tmp_dir_path, build_args.items) catch | |
| 231 | 230 | fatal("example failed to compile", .{}); |
| 232 | 231 | |
| 233 | 232 | if (code.verbose_cimport) { |
| ... | ... | @@ -258,8 +257,7 @@ fn printOutput( |
| 258 | 257 | var exited_with_signal = false; |
| 259 | 258 | |
| 260 | 259 | const result = if (expected_outcome == .fail) blk: { |
| 261 | const result = try process.Child.run(.{ | |
| 262 | .allocator = arena, | |
| 260 | const result = try process.Child.run(arena, io, .{ | |
| 263 | 261 | .argv = run_args, |
| 264 | 262 | .env_map = &env_map, |
| 265 | 263 | .cwd = tmp_dir_path, |
| ... | ... | @@ -278,7 +276,7 @@ fn printOutput( |
| 278 | 276 | } |
| 279 | 277 | break :blk result; |
| 280 | 278 | } else blk: { |
| 281 | break :blk run(arena, &env_map, tmp_dir_path, run_args) catch | |
| 279 | break :blk run(arena, io, &env_map, tmp_dir_path, run_args) catch | |
| 282 | 280 | fatal("example crashed", .{}); |
| 283 | 281 | }; |
| 284 | 282 | |
| ... | ... | @@ -327,7 +325,7 @@ fn printOutput( |
| 327 | 325 | .arch_os_abi = triple, |
| 328 | 326 | }); |
| 329 | 327 | const target = try std.zig.system.resolveTargetQuery(io, target_query); |
| 330 | switch (getExternalExecutor(&host, &target, .{ | |
| 328 | switch (getExternalExecutor(io, &host, &target, .{ | |
| 331 | 329 | .link_libc = code.link_libc, |
| 332 | 330 | })) { |
| 333 | 331 | .native => {}, |
| ... | ... | @@ -347,7 +345,7 @@ fn printOutput( |
| 347 | 345 | } |
| 348 | 346 | } |
| 349 | 347 | |
| 350 | const result = run(arena, &env_map, tmp_dir_path, test_args.items) catch | |
| 348 | const result = run(arena, io, &env_map, tmp_dir_path, test_args.items) catch | |
| 351 | 349 | fatal("test failed", .{}); |
| 352 | 350 | const escaped_stderr = try escapeHtml(arena, result.stderr); |
| 353 | 351 | const escaped_stdout = try escapeHtml(arena, result.stdout); |
| ... | ... | @@ -378,8 +376,7 @@ fn printOutput( |
| 378 | 376 | try test_args.append("-lc"); |
| 379 | 377 | try shell_out.print("-lc ", .{}); |
| 380 | 378 | } |
| 381 | const result = try process.Child.run(.{ | |
| 382 | .allocator = arena, | |
| 379 | const result = try process.Child.run(arena, io, .{ | |
| 383 | 380 | .argv = test_args.items, |
| 384 | 381 | .env_map = &env_map, |
| 385 | 382 | .cwd = tmp_dir_path, |
| ... | ... | @@ -435,8 +432,7 @@ fn printOutput( |
| 435 | 432 | }, |
| 436 | 433 | } |
| 437 | 434 | |
| 438 | const result = try process.Child.run(.{ | |
| 439 | .allocator = arena, | |
| 435 | const result = try process.Child.run(arena, io, .{ | |
| 440 | 436 | .argv = test_args.items, |
| 441 | 437 | .env_map = &env_map, |
| 442 | 438 | .cwd = tmp_dir_path, |
| ... | ... | @@ -512,8 +508,7 @@ fn printOutput( |
| 512 | 508 | } |
| 513 | 509 | |
| 514 | 510 | if (maybe_error_match) |error_match| { |
| 515 | const result = try process.Child.run(.{ | |
| 516 | .allocator = arena, | |
| 511 | const result = try process.Child.run(arena, io, .{ | |
| 517 | 512 | .argv = build_args.items, |
| 518 | 513 | .env_map = &env_map, |
| 519 | 514 | .cwd = tmp_dir_path, |
| ... | ... | @@ -541,7 +536,7 @@ fn printOutput( |
| 541 | 536 | const colored_stderr = try termColor(arena, escaped_stderr); |
| 542 | 537 | try shell_out.print("\n{s} ", .{colored_stderr}); |
| 543 | 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 | 541 | try shell_out.writeAll("\n"); |
| 547 | 542 | }, |
| ... | ... | @@ -600,7 +595,7 @@ fn printOutput( |
| 600 | 595 | try test_args.append(option); |
| 601 | 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 | 599 | const escaped_stderr = try escapeHtml(arena, result.stderr); |
| 605 | 600 | const escaped_stdout = try escapeHtml(arena, result.stdout); |
| 606 | 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 | 1127 | |
| 1133 | 1128 | fn run( |
| 1134 | 1129 | allocator: Allocator, |
| 1130 | io: Io, | |
| 1135 | 1131 | env_map: *process.EnvMap, |
| 1136 | 1132 | cwd: []const u8, |
| 1137 | 1133 | args: []const []const u8, |
| 1138 | 1134 | ) !process.Child.RunResult { |
| 1139 | const result = try process.Child.run(.{ | |
| 1140 | .allocator = allocator, | |
| 1135 | const result = try process.Child.run(allocator, io, .{ | |
| 1141 | 1136 | .argv = args, |
| 1142 | 1137 | .env_map = env_map, |
| 1143 | 1138 | .cwd = cwd, |
tools/dump-cov.zig+7-5| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | //! including file:line:column information for each PC. |
| 3 | 3 | |
| 4 | 4 | const std = @import("std"); |
| 5 | const Io = std.Io; | |
| 5 | 6 | const fatal = std.process.fatal; |
| 6 | 7 | const Path = std.Build.Cache.Path; |
| 7 | 8 | const assert = std.debug.assert; |
| ... | ... | @@ -16,7 +17,7 @@ pub fn main() !void { |
| 16 | 17 | defer arena_instance.deinit(); |
| 17 | 18 | const arena = arena_instance.allocator(); |
| 18 | 19 | |
| 19 | var threaded: std.Io.Threaded = .init(gpa); | |
| 20 | var threaded: Io.Threaded = .init(gpa, .{}); | |
| 20 | 21 | defer threaded.deinit(); |
| 21 | 22 | const io = threaded.io(); |
| 22 | 23 | |
| ... | ... | @@ -51,12 +52,13 @@ pub fn main() !void { |
| 51 | 52 | var coverage: std.debug.Coverage = .init; |
| 52 | 53 | defer coverage.deinit(gpa); |
| 53 | 54 | |
| 54 | var debug_info = std.debug.Info.load(gpa, exe_path, &coverage, target.ofmt, target.cpu.arch) catch |err| { | |
| 55 | fatal("failed to load debug info for {f}: {s}", .{ exe_path, @errorName(err) }); | |
| 55 | var debug_info = std.debug.Info.load(gpa, io, exe_path, &coverage, target.ofmt, target.cpu.arch) catch |err| { | |
| 56 | fatal("failed to load debug info for {f}: {t}", .{ exe_path, err }); | |
| 56 | 57 | }; |
| 57 | 58 | defer debug_info.deinit(gpa); |
| 58 | 59 | |
| 59 | 60 | const cov_bytes = cov_path.root_dir.handle.readFileAllocOptions( |
| 61 | io, | |
| 60 | 62 | cov_path.sub_path, |
| 61 | 63 | arena, |
| 62 | 64 | .limited(1 << 30), |
| ... | ... | @@ -67,7 +69,7 @@ pub fn main() !void { |
| 67 | 69 | }; |
| 68 | 70 | |
| 69 | 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 | 73 | const stdout = &stdout_writer.interface; |
| 72 | 74 | |
| 73 | 75 | const header: *SeenPcsHeader = @ptrCast(cov_bytes); |
| ... | ... | @@ -83,7 +85,7 @@ pub fn main() !void { |
| 83 | 85 | std.mem.sortUnstable(usize, sorted_pcs, {}, std.sort.asc(usize)); |
| 84 | 86 | |
| 85 | 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); | |
| 87 | 89 | |
| 88 | 90 | const seen_pcs = header.seenBits(); |
| 89 | 91 |
tools/fetch_them_macos_headers.zig+28-33| ... | ... | @@ -1,10 +1,9 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const Io = std.Io; |
| 3 | const fs = std.fs; | |
| 3 | const Dir = std.Io.Dir; | |
| 4 | 4 | const mem = std.mem; |
| 5 | 5 | const process = std.process; |
| 6 | 6 | const assert = std.debug.assert; |
| 7 | const tmpDir = std.testing.tmpDir; | |
| 8 | 7 | const fatal = std.process.fatal; |
| 9 | 8 | const info = std.log.info; |
| 10 | 9 | |
| ... | ... | @@ -86,19 +85,19 @@ pub fn main() anyerror!void { |
| 86 | 85 | } else try argv.append(arg); |
| 87 | 86 | } |
| 88 | 87 | |
| 89 | var threaded: Io.Threaded = .init(gpa); | |
| 88 | var threaded: Io.Threaded = .init(gpa, .{}); | |
| 90 | 89 | defer threaded.deinit(); |
| 91 | 90 | const io = threaded.io(); |
| 92 | 91 | |
| 93 | 92 | const sysroot_path = sysroot orelse blk: { |
| 94 | 93 | const target = try std.zig.system.resolveTargetQuery(io, .{}); |
| 95 | break :blk std.zig.system.darwin.getSdk(allocator, &target) orelse | |
| 94 | break :blk std.zig.system.darwin.getSdk(allocator, io, &target) orelse | |
| 96 | 95 | fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{}); |
| 97 | 96 | }; |
| 98 | 97 | |
| 99 | var sdk_dir = try std.fs.cwd().openDir(sysroot_path, .{}); | |
| 100 | defer sdk_dir.close(); | |
| 101 | const sdk_info = try sdk_dir.readFileAlloc("SDKSettings.json", allocator, .limited(std.math.maxInt(u32))); | |
| 98 | var sdk_dir = try Dir.cwd().openDir(io, sysroot_path, .{}); | |
| 99 | defer sdk_dir.close(io); | |
| 100 | const sdk_info = try sdk_dir.readFileAlloc(io, "SDKSettings.json", allocator, .limited(std.math.maxInt(u32))); | |
| 102 | 101 | |
| 103 | 102 | const parsed_json = try std.json.parseFromSlice(struct { |
| 104 | 103 | DefaultProperties: struct { MACOSX_DEPLOYMENT_TARGET: []const u8 }, |
| ... | ... | @@ -111,15 +110,14 @@ pub fn main() anyerror!void { |
| 111 | 110 | const os_ver: OsVer = @enumFromInt(version.major); |
| 112 | 111 | info("found SDK deployment target macOS {f} aka '{t}'", .{ version, os_ver }); |
| 113 | 112 | |
| 114 | var tmp = tmpDir(.{}); | |
| 115 | defer tmp.cleanup(); | |
| 113 | const tmp_dir: Io.Dir = .cwd(); | |
| 116 | 114 | |
| 117 | 115 | for (&[_]Arch{ .aarch64, .x86_64 }) |arch| { |
| 118 | 116 | const target: Target = .{ |
| 119 | 117 | .arch = arch, |
| 120 | 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 | } |
| 125 | 123 | |
| ... | ... | @@ -130,13 +128,13 @@ fn fetchTarget( |
| 130 | 128 | sysroot: []const u8, |
| 131 | 129 | target: Target, |
| 132 | 130 | ver: Version, |
| 133 | tmp: std.testing.TmpDir, | |
| 131 | tmp_dir: Io.Dir, | |
| 134 | 132 | ) !void { |
| 135 | 133 | const tmp_filename = "macos-headers"; |
| 136 | 134 | const headers_list_filename = "macos-headers.o.d"; |
| 137 | const tmp_path = try tmp.dir.realpathAlloc(arena, "."); | |
| 138 | const tmp_file_path = try fs.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename }); | |
| 139 | const headers_list_path = try fs.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename }); | |
| 135 | const tmp_path = try tmp_dir.realPathFileAlloc(io, ".", arena); | |
| 136 | const tmp_file_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename }); | |
| 137 | const headers_list_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename }); | |
| 140 | 138 | |
| 141 | 139 | const macos_version = try std.fmt.allocPrint(arena, "-mmacosx-version-min={d}.{d}", .{ |
| 142 | 140 | ver.major, |
| ... | ... | @@ -166,20 +164,17 @@ fn fetchTarget( |
| 166 | 164 | }); |
| 167 | 165 | try cc_argv.appendSlice(args); |
| 168 | 166 | |
| 169 | const res = try std.process.Child.run(.{ | |
| 170 | .allocator = arena, | |
| 171 | .argv = cc_argv.items, | |
| 172 | }); | |
| 167 | const res = try std.process.Child.run(arena, io, .{ .argv = cc_argv.items }); | |
| 173 | 168 | |
| 174 | 169 | if (res.stderr.len != 0) { |
| 175 | 170 | std.log.err("{s}", .{res.stderr}); |
| 176 | 171 | } |
| 177 | 172 | |
| 178 | 173 | // Read in the contents of `macos-headers.o.d` |
| 179 | const headers_list_file = try tmp.dir.openFile(headers_list_filename, .{}); | |
| 180 | defer headers_list_file.close(); | |
| 174 | const headers_list_file = try tmp_dir.openFile(io, headers_list_filename, .{}); | |
| 175 | defer headers_list_file.close(io); | |
| 181 | 176 | |
| 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 | 178 | error.FileNotFound, |
| 184 | 179 | error.NotDir, |
| 185 | 180 | => fatal("path '{s}' not found or not a directory. Did you accidentally delete it?", .{ |
| ... | ... | @@ -187,13 +182,13 @@ fn fetchTarget( |
| 187 | 182 | }), |
| 188 | 183 | else => return err, |
| 189 | 184 | }; |
| 190 | defer headers_dir.close(); | |
| 185 | defer headers_dir.close(io); | |
| 191 | 186 | |
| 192 | 187 | const dest_path = try target.fullName(arena); |
| 193 | try headers_dir.deleteTree(dest_path); | |
| 188 | try headers_dir.deleteTree(io, dest_path); | |
| 194 | 189 | |
| 195 | var dest_dir = try headers_dir.makeOpenPath(dest_path, .{}); | |
| 196 | var dirs = std.StringHashMap(fs.Dir).init(arena); | |
| 190 | var dest_dir = try headers_dir.createDirPathOpen(io, dest_path, .{}); | |
| 191 | var dirs = std.StringHashMap(Dir).init(arena); | |
| 197 | 192 | try dirs.putNoClobber(".", dest_dir); |
| 198 | 193 | |
| 199 | 194 | var headers_list_file_reader = headers_list_file.reader(io, &.{}); |
| ... | ... | @@ -206,25 +201,25 @@ fn fetchTarget( |
| 206 | 201 | if (mem.lastIndexOf(u8, line, prefix[0..])) |idx| { |
| 207 | 202 | const out_rel_path = line[idx + prefix.len + 1 ..]; |
| 208 | 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 | 205 | const maybe_dir = try dirs.getOrPut(dirname); |
| 211 | 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); | |
| 215 | 210 | |
| 216 | 211 | const line_stripped = mem.trim(u8, line, " \\"); |
| 217 | const abs_dirname = fs.path.dirname(line_stripped).?; | |
| 218 | var orig_subdir = try fs.cwd().openDir(abs_dirname, .{}); | |
| 219 | defer orig_subdir.close(); | |
| 212 | const abs_dirname = Dir.path.dirname(line_stripped).?; | |
| 213 | var orig_subdir = try Dir.cwd().openDir(io, abs_dirname, .{}); | |
| 214 | defer orig_subdir.close(io); | |
| 220 | 215 | |
| 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 | } |
| 224 | 219 | |
| 225 | 220 | var dir_it = dirs.iterator(); |
| 226 | 221 | while (dir_it.next()) |entry| { |
| 227 | entry.value_ptr.close(); | |
| 222 | entry.value_ptr.close(io); | |
| 228 | 223 | } |
| 229 | 224 | } |
| 230 | 225 |
tools/gen_macos_headers_c.zig+19-13| ... | ... | @@ -1,8 +1,9 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 3 | const Dir = std.Io.Dir; | |
| 2 | 4 | const assert = std.debug.assert; |
| 3 | 5 | const info = std.log.info; |
| 4 | 6 | const fatal = std.process.fatal; |
| 5 | ||
| 6 | 7 | const Allocator = std.mem.Allocator; |
| 7 | 8 | |
| 8 | 9 | var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; |
| ... | ... | @@ -20,6 +21,10 @@ pub fn main() anyerror!void { |
| 20 | 21 | defer arena_allocator.deinit(); |
| 21 | 22 | const arena = arena_allocator.allocator(); |
| 22 | 23 | |
| 24 | var threaded: Io.Threaded = .init(gpa, .{}); | |
| 25 | defer threaded.deinit(); | |
| 26 | const io = threaded.io(); | |
| 27 | ||
| 23 | 28 | const args = try std.process.argsAlloc(arena); |
| 24 | 29 | if (args.len == 1) fatal("no command or option specified", .{}); |
| 25 | 30 | |
| ... | ... | @@ -33,10 +38,10 @@ pub fn main() anyerror!void { |
| 33 | 38 | |
| 34 | 39 | if (positionals.items.len != 1) fatal("expected one positional argument: [dir]", .{}); |
| 35 | 40 | |
| 36 | var dir = try std.fs.cwd().openDir(positionals.items[0], .{ .follow_symlinks = false }); | |
| 37 | defer dir.close(); | |
| 41 | var dir = try Io.Dir.cwd().openDir(io, positionals.items[0], .{ .follow_symlinks = false }); | |
| 42 | defer dir.close(io); | |
| 38 | 43 | var paths = std.array_list.Managed([]const u8).init(arena); |
| 39 | try findHeaders(arena, dir, "", &paths); | |
| 44 | try findHeaders(arena, io, dir, "", &paths); | |
| 40 | 45 | |
| 41 | 46 | const SortFn = struct { |
| 42 | 47 | pub fn lessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool { |
| ... | ... | @@ -48,7 +53,7 @@ pub fn main() anyerror!void { |
| 48 | 53 | std.mem.sort([]const u8, paths.items, {}, SortFn.lessThan); |
| 49 | 54 | |
| 50 | 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 | 57 | const w = &stdout_writer.interface; |
| 53 | 58 | try w.writeAll("#define _XOPEN_SOURCE\n"); |
| 54 | 59 | for (paths.items) |path| { |
| ... | ... | @@ -64,23 +69,24 @@ pub fn main() anyerror!void { |
| 64 | 69 | |
| 65 | 70 | fn findHeaders( |
| 66 | 71 | arena: Allocator, |
| 67 | dir: std.fs.Dir, | |
| 72 | io: Io, | |
| 73 | dir: Dir, | |
| 68 | 74 | prefix: []const u8, |
| 69 | 75 | paths: *std.array_list.Managed([]const u8), |
| 70 | 76 | ) anyerror!void { |
| 71 | 77 | var it = dir.iterate(); |
| 72 | while (try it.next()) |entry| { | |
| 78 | while (try it.next(io)) |entry| { | |
| 73 | 79 | switch (entry.kind) { |
| 74 | 80 | .directory => { |
| 75 | const path = try std.fs.path.join(arena, &.{ prefix, entry.name }); | |
| 76 | var subdir = try dir.openDir(entry.name, .{ .follow_symlinks = false }); | |
| 77 | defer subdir.close(); | |
| 78 | try findHeaders(arena, subdir, path, paths); | |
| 81 | const path = try Io.Dir.path.join(arena, &.{ prefix, entry.name }); | |
| 82 | var subdir = try dir.openDir(io, entry.name, .{ .follow_symlinks = false }); | |
| 83 | defer subdir.close(io); | |
| 84 | try findHeaders(arena, io, subdir, path, paths); | |
| 79 | 85 | }, |
| 80 | 86 | .file, .sym_link => { |
| 81 | const ext = std.fs.path.extension(entry.name); | |
| 87 | const ext = Io.Dir.path.extension(entry.name); | |
| 82 | 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 | 90 | try paths.append(path); |
| 85 | 91 | }, |
| 86 | 92 | else => {}, |
tools/gen_outline_atomics.zig+6-1| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const Allocator = std.mem.Allocator; |
| 3 | 4 | |
| 4 | 5 | const AtomicOp = enum { |
| ... | ... | @@ -15,10 +16,14 @@ pub fn main() !void { |
| 15 | 16 | defer arena_instance.deinit(); |
| 16 | 17 | const arena = arena_instance.allocator(); |
| 17 | 18 | |
| 19 | var threaded: std.Io.Threaded = .init(arena, .{}); | |
| 20 | defer threaded.deinit(); | |
| 21 | const io = threaded.io(); | |
| 22 | ||
| 18 | 23 | //const args = try std.process.argsAlloc(arena); |
| 19 | 24 | |
| 20 | 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 | 27 | const w = &stdout_writer.interface; |
| 23 | 28 | |
| 24 | 29 | try w.writeAll( |
tools/gen_spirv_spec.zig+23-16| ... | ... | @@ -1,5 +1,7 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 2 | 3 | const Allocator = std.mem.Allocator; |
| 4 | ||
| 3 | 5 | const g = @import("spirv/grammar.zig"); |
| 4 | 6 | const CoreRegistry = g.CoreRegistry; |
| 5 | 7 | const ExtensionRegistry = g.ExtensionRegistry; |
| ... | ... | @@ -63,24 +65,28 @@ pub fn main() !void { |
| 63 | 65 | usageAndExit(args[0], 1); |
| 64 | 66 | } |
| 65 | 67 | |
| 66 | const json_path = try std.fs.path.join(allocator, &.{ args[1], "include/spirv/unified1/" }); | |
| 67 | const dir = try std.fs.cwd().openDir(json_path, .{ .iterate = true }); | |
| 68 | var threaded: std.Io.Threaded = .init(allocator, .{}); | |
| 69 | defer threaded.deinit(); | |
| 70 | const io = threaded.io(); | |
| 71 | ||
| 72 | const json_path = try Io.Dir.path.join(allocator, &.{ args[1], "include/spirv/unified1/" }); | |
| 73 | const dir = try Io.Dir.cwd().openDir(io, json_path, .{ .iterate = true }); | |
| 68 | 74 | |
| 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 | 76 | std.mem.sortUnstable(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt); |
| 71 | 77 | |
| 72 | 78 | var exts = std.array_list.Managed(Extension).init(allocator); |
| 73 | 79 | |
| 74 | 80 | var it = dir.iterate(); |
| 75 | while (try it.next()) |entry| { | |
| 81 | while (try it.next(io)) |entry| { | |
| 76 | 82 | if (entry.kind != .file) { |
| 77 | 83 | continue; |
| 78 | 84 | } |
| 79 | 85 | |
| 80 | try readExtRegistry(&exts, dir, entry.name); | |
| 86 | try readExtRegistry(io, &exts, dir, entry.name); | |
| 81 | 87 | } |
| 82 | 88 | |
| 83 | try readExtRegistry(&exts, std.fs.cwd(), args[2]); | |
| 89 | try readExtRegistry(io, &exts, Io.Dir.cwd(), args[2]); | |
| 84 | 90 | |
| 85 | 91 | var allocating: std.Io.Writer.Allocating = .init(allocator); |
| 86 | 92 | defer allocating.deinit(); |
| ... | ... | @@ -91,7 +97,7 @@ pub fn main() !void { |
| 91 | 97 | var tree = try std.zig.Ast.parse(allocator, output, .zig); |
| 92 | 98 | |
| 93 | 99 | if (tree.errors.len != 0) { |
| 94 | try std.zig.printAstErrorsToStderr(allocator, tree, "", .auto); | |
| 100 | try std.zig.printAstErrorsToStderr(allocator, io, tree, "", .auto); | |
| 95 | 101 | return; |
| 96 | 102 | } |
| 97 | 103 | |
| ... | ... | @@ -103,22 +109,22 @@ pub fn main() !void { |
| 103 | 109 | try wip_errors.addZirErrorMessages(zir, tree, output, ""); |
| 104 | 110 | var error_bundle = try wip_errors.toOwnedBundle(""); |
| 105 | 111 | defer error_bundle.deinit(allocator); |
| 106 | error_bundle.renderToStdErr(.{}, .auto); | |
| 112 | try error_bundle.renderToStderr(io, .{}, .auto); | |
| 107 | 113 | } |
| 108 | 114 | |
| 109 | 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 | } |
| 112 | 118 | |
| 113 | fn readExtRegistry(exts: *std.array_list.Managed(Extension), dir: std.fs.Dir, sub_path: []const u8) !void { | |
| 114 | const filename = std.fs.path.basename(sub_path); | |
| 119 | fn readExtRegistry(io: Io, exts: *std.array_list.Managed(Extension), dir: Io.Dir, sub_path: []const u8) !void { | |
| 120 | const filename = Io.Dir.path.basename(sub_path); | |
| 115 | 121 | if (!std.mem.startsWith(u8, filename, "extinst.")) { |
| 116 | 122 | return; |
| 117 | 123 | } |
| 118 | 124 | |
| 119 | 125 | std.debug.assert(std.mem.endsWith(u8, filename, ".grammar.json")); |
| 120 | 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); | |
| 122 | 128 | |
| 123 | 129 | const set_name = set_names.get(name) orelse { |
| 124 | 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 | 140 | }); |
| 135 | 141 | } |
| 136 | 142 | |
| 137 | fn readRegistry(comptime RegistryType: type, dir: std.fs.Dir, path: []const u8) !RegistryType { | |
| 138 | const spec = try dir.readFileAlloc(path, allocator, .unlimited); | |
| 143 | fn readRegistry(io: Io, comptime RegistryType: type, dir: Io.Dir, path: []const u8) !RegistryType { | |
| 144 | const spec = try dir.readFileAlloc(io, path, allocator, .unlimited); | |
| 139 | 145 | // Required for json parsing. |
| 140 | 146 | // TODO: ALI |
| 141 | 147 | @setEvalBranchQuota(10000); |
| ... | ... | @@ -930,8 +936,9 @@ fn parseHexInt(text: []const u8) !u31 { |
| 930 | 936 | } |
| 931 | 937 | |
| 932 | 938 | fn usageAndExit(arg0: []const u8, code: u8) noreturn { |
| 933 | const stderr, _ = std.debug.lockStderrWriter(&.{}); | |
| 934 | stderr.print( | |
| 939 | const stderr = std.debug.lockStderr(&.{}); | |
| 940 | const w = &stderr.file_writer.interface; | |
| 941 | w.print( | |
| 935 | 942 | \\Usage: {s} <SPIRV-Headers repository path> <path/to/zig/src/codegen/spirv/extinst.zig.grammar.json> |
| 936 | 943 | \\ |
| 937 | 944 | \\Generates Zig bindings for SPIR-V specifications found in the SPIRV-Headers |
tools/gen_stubs.zig+12-5| ... | ... | @@ -55,12 +55,14 @@ |
| 55 | 55 | // - e.g. find a common previous symbol and put it after that one |
| 56 | 56 | // - they definitely need to go into the correct section |
| 57 | 57 | |
| 58 | const builtin = @import("builtin"); | |
| 59 | const native_endian = builtin.cpu.arch.endian(); | |
| 60 | ||
| 58 | 61 | const std = @import("std"); |
| 59 | const builtin = std.builtin; | |
| 62 | const Io = std.Io; | |
| 60 | 63 | const mem = std.mem; |
| 61 | 64 | const log = std.log; |
| 62 | 65 | const elf = std.elf; |
| 63 | const native_endian = @import("builtin").cpu.arch.endian(); | |
| 64 | 66 | |
| 65 | 67 | const Arch = enum { |
| 66 | 68 | aarch64, |
| ... | ... | @@ -284,10 +286,14 @@ pub fn main() !void { |
| 284 | 286 | defer arena_instance.deinit(); |
| 285 | 287 | const arena = arena_instance.allocator(); |
| 286 | 288 | |
| 289 | var threaded: std.Io.Threaded = .init(arena, .{}); | |
| 290 | defer threaded.deinit(); | |
| 291 | const io = threaded.io(); | |
| 292 | ||
| 287 | 293 | const args = try std.process.argsAlloc(arena); |
| 288 | 294 | const build_all_path = args[1]; |
| 289 | 295 | |
| 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, .{}); | |
| 291 | 297 | |
| 292 | 298 | var sym_table = std.StringArrayHashMap(MultiSym).init(arena); |
| 293 | 299 | var sections = std.StringArrayHashMap(void).init(arena); |
| ... | ... | @@ -299,6 +305,7 @@ pub fn main() !void { |
| 299 | 305 | |
| 300 | 306 | // Read the ELF header. |
| 301 | 307 | const elf_bytes = build_all_dir.readFileAllocOptions( |
| 308 | io, | |
| 302 | 309 | libc_so_path, |
| 303 | 310 | arena, |
| 304 | 311 | .limited(100 * 1024 * 1024), |
| ... | ... | @@ -334,7 +341,7 @@ pub fn main() !void { |
| 334 | 341 | } |
| 335 | 342 | |
| 336 | 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 | 345 | const stdout = &stdout_writer.interface; |
| 339 | 346 | try stdout.writeAll( |
| 340 | 347 | \\#ifdef PTR64 |
| ... | ... | @@ -539,7 +546,7 @@ pub fn main() !void { |
| 539 | 546 | try stdout.flush(); |
| 540 | 547 | } |
| 541 | 548 | |
| 542 | fn parseElf(parse: Parse, comptime is_64: bool, comptime endian: builtin.Endian) !void { | |
| 549 | fn parseElf(parse: Parse, comptime is_64: bool, comptime endian: std.builtin.Endian) !void { | |
| 543 | 550 | const arena = parse.arena; |
| 544 | 551 | const elf_bytes = parse.elf_bytes; |
| 545 | 552 | const header = parse.header; |
tools/generate_JSONTestSuite.zig+9-4| ... | ... | @@ -1,13 +1,18 @@ |
| 1 | 1 | // zig run this file inside the test_parsing/ directory of this repo: https://github.com/nst/JSONTestSuite |
| 2 | 2 | |
| 3 | 3 | const std = @import("std"); |
| 4 | const Io = std.Io; | |
| 4 | 5 | |
| 5 | 6 | pub fn main() !void { |
| 6 | 7 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; |
| 7 | 8 | var allocator = gpa.allocator(); |
| 8 | 9 | |
| 10 | var threaded: std.Io.Threaded = .init(allocator, .{}); | |
| 11 | defer threaded.deinit(); | |
| 12 | const io = threaded.io(); | |
| 13 | ||
| 9 | 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 | 16 | const output = &stdout_writer.interface; |
| 12 | 17 | try output.writeAll( |
| 13 | 18 | \\// This file was generated by _generate_JSONTestSuite.zig |
| ... | ... | @@ -20,9 +25,9 @@ pub fn main() !void { |
| 20 | 25 | ); |
| 21 | 26 | |
| 22 | 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 | 29 | var it = cwd.iterate(); |
| 25 | while (try it.next()) |entry| { | |
| 30 | while (try it.next(io)) |entry| { | |
| 26 | 31 | try names.append(try allocator.dupe(u8, entry.name)); |
| 27 | 32 | } |
| 28 | 33 | std.mem.sort([]const u8, names.items, {}, (struct { |
| ... | ... | @@ -32,7 +37,7 @@ pub fn main() !void { |
| 32 | 37 | }).lessThan); |
| 33 | 38 | |
| 34 | 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 | 41 | try output.writeAll("test "); |
| 37 | 42 | try writeString(output, name); |
| 38 | 43 | try output.writeAll(" {\n try "); |
tools/generate_c_size_and_align_checks.zig+3-2| ... | ... | @@ -7,6 +7,7 @@ |
| 7 | 7 | //! target. |
| 8 | 8 | |
| 9 | 9 | const std = @import("std"); |
| 10 | const Io = std.Io; | |
| 10 | 11 | |
| 11 | 12 | fn cName(ty: std.Target.CType) []const u8 { |
| 12 | 13 | return switch (ty) { |
| ... | ... | @@ -39,7 +40,7 @@ pub fn main() !void { |
| 39 | 40 | std.process.exit(1); |
| 40 | 41 | } |
| 41 | 42 | |
| 42 | var threaded: std.Io.Threaded = .init(gpa); | |
| 43 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 43 | 44 | defer threaded.deinit(); |
| 44 | 45 | const io = threaded.io(); |
| 45 | 46 | |
| ... | ... | @@ -47,7 +48,7 @@ pub fn main() !void { |
| 47 | 48 | const target = try std.zig.system.resolveTargetQuery(io, query); |
| 48 | 49 | |
| 49 | 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 | 52 | const w = &stdout_writer.interface; |
| 52 | 53 | inline for (@typeInfo(std.Target.CType).@"enum".fields) |field| { |
| 53 | 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 | 175 | defer arena.deinit(); |
| 176 | 176 | const gpa = arena.allocator(); |
| 177 | 177 | |
| 178 | var threaded: Io.Threaded = .init(gpa, .{}); | |
| 179 | defer threaded.deinit(); | |
| 180 | const io = threaded.io(); | |
| 181 | ||
| 178 | 182 | const args = try std.process.argsAlloc(gpa); |
| 179 | 183 | if (args.len < 2 or mem.eql(u8, args[1], "--help")) { |
| 180 | const w, _ = std.debug.lockStderrWriter(&.{}); | |
| 181 | defer std.debug.unlockStderrWriter(); | |
| 184 | const stderr = std.debug.lockStderr(&.{}); | |
| 185 | const w = &stderr.file_writer.interface; | |
| 182 | 186 | usage(w, args[0]) catch std.process.exit(2); |
| 183 | 187 | std.process.exit(1); |
| 184 | 188 | } |
| 185 | 189 | const linux_path = args[1]; |
| 186 | 190 | |
| 187 | 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 | 193 | const stdout = &stdout_writer.interface; |
| 190 | 194 | |
| 191 | var linux_dir = try std.fs.cwd().openDir(linux_path, .{}); | |
| 192 | defer linux_dir.close(); | |
| 195 | var linux_dir = try Io.Dir.cwd().openDir(io, linux_path, .{}); | |
| 196 | defer linux_dir.close(io); | |
| 193 | 197 | |
| 194 | 198 | // As of 6.11, the largest table is 24195 bytes. |
| 195 | 199 | // 32k should be enough for now. |
| ... | ... | @@ -198,7 +202,7 @@ pub fn main() !void { |
| 198 | 202 | |
| 199 | 203 | // Fetch the kernel version from the Makefile variables. |
| 200 | 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 | 206 | var lines = mem.tokenizeScalar(u8, head, '\n'); |
| 203 | 207 | _ = lines.next(); // Skip SPDX identifier |
| 204 | 208 | |
| ... | ... | @@ -221,7 +225,7 @@ pub fn main() !void { |
| 221 | 225 | , .{version}); |
| 222 | 226 | |
| 223 | 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 | 229 | .generic => "scripts/syscall.tbl", |
| 226 | 230 | .specific => |f| f, |
| 227 | 231 | }, buf); |
tools/incr-check.zig+125-71| ... | ... | @@ -1,9 +1,10 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const Io = std.Io; |
| 3 | const Dir = std.Io.Dir; | |
| 3 | 4 | const Allocator = std.mem.Allocator; |
| 4 | 5 | const Cache = std.Build.Cache; |
| 5 | 6 | |
| 6 | const 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]"; | |
| 7 | const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-log foo] [--preserve-tmp] [--zig-cc-binary /path/to/zig]"; | |
| 7 | 8 | |
| 8 | 9 | pub fn main() !void { |
| 9 | 10 | const fatal = std.process.fatal; |
| ... | ... | @@ -14,7 +15,7 @@ pub fn main() !void { |
| 14 | 15 | |
| 15 | 16 | const gpa = arena; |
| 16 | 17 | |
| 17 | var threaded: Io.Threaded = .init(gpa); | |
| 18 | var threaded: Io.Threaded = .init(gpa, .{}); | |
| 18 | 19 | defer threaded.deinit(); |
| 19 | 20 | const io = threaded.io(); |
| 20 | 21 | |
| ... | ... | @@ -22,27 +23,37 @@ pub fn main() !void { |
| 22 | 23 | var opt_input_file_name: ?[]const u8 = null; |
| 23 | 24 | var opt_lib_dir: ?[]const u8 = null; |
| 24 | 25 | var opt_cc_zig: ?[]const u8 = null; |
| 25 | var debug_zcu = false; | |
| 26 | var debug_dwarf = false; | |
| 27 | var debug_link = false; | |
| 28 | 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; | |
| 29 | 33 | |
| 30 | 34 | var arg_it = try std.process.argsWithAllocator(arena); |
| 31 | 35 | _ = arg_it.skip(); |
| 32 | 36 | while (arg_it.next()) |arg| { |
| 33 | 37 | if (arg.len > 0 and arg[0] == '-') { |
| 34 | 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}); | |
| 36 | } else if (std.mem.eql(u8, arg, "--debug-zcu")) { | |
| 37 | debug_zcu = true; | |
| 38 | } else if (std.mem.eql(u8, arg, "--debug-dwarf")) { | |
| 39 | debug_dwarf = true; | |
| 40 | } else if (std.mem.eql(u8, arg, "--debug-link")) { | |
| 41 | debug_link = true; | |
| 39 | opt_lib_dir = arg_it.next() orelse fatal("expected arg after --zig-lib-dir\n{s}", .{usage}); | |
| 40 | } else if (std.mem.eql(u8, arg, "--debug-log")) { | |
| 41 | try debug_log_args.append( | |
| 42 | arena, | |
| 43 | arg_it.next() orelse fatal("expected arg after --debug-log\n{s}", .{usage}), | |
| 44 | ); | |
| 42 | 45 | } else if (std.mem.eql(u8, arg, "--preserve-tmp")) { |
| 43 | 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 | 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 | 57 | } else { |
| 47 | 58 | fatal("unknown option '{s}'\n{s}", .{ arg, usage }); |
| 48 | 59 | } |
| ... | ... | @@ -59,7 +70,7 @@ pub fn main() !void { |
| 59 | 70 | const zig_exe = opt_zig_exe orelse fatal("missing path to zig\n{s}", .{usage}); |
| 60 | 71 | const input_file_name = opt_input_file_name orelse fatal("missing input file\n{s}", .{usage}); |
| 61 | 72 | |
| 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 | 74 | const case = try Case.parse(arena, io, input_file_bytes); |
| 64 | 75 | |
| 65 | 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 | 82 | } |
| 72 | 83 | } |
| 73 | 84 | |
| 74 | const prog_node = std.Progress.start(.{}); | |
| 85 | const prog_node = std.Progress.start(io, .{}); | |
| 75 | 86 | defer prog_node.end(); |
| 76 | 87 | |
| 77 | 88 | const rand_int = std.crypto.random.int(u64); |
| 78 | 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 | 91 | defer { |
| 81 | tmp_dir.close(); | |
| 92 | tmp_dir.close(io); | |
| 82 | 93 | if (!preserve_tmp) { |
| 83 | std.fs.cwd().deleteTree(tmp_dir_path) catch |err| { | |
| 84 | std.log.warn("failed to delete tree '{s}': {s}", .{ tmp_dir_path, @errorName(err) }); | |
| 94 | Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| { | |
| 95 | std.log.warn("failed to delete tree '{s}': {t}", .{ tmp_dir_path, err }); | |
| 85 | 96 | }; |
| 86 | 97 | } |
| 87 | 98 | } |
| 88 | 99 | |
| 89 | 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 | 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 | 104 | else |
| 94 | 105 | null; |
| 95 | 106 | |
| 96 | 107 | const host = try std.zig.system.resolveTargetQuery(io, .{}); |
| 97 | 108 | |
| 98 | const debug_log_verbose = debug_zcu or debug_dwarf or debug_link; | |
| 109 | const debug_log_verbose = debug_log_args.items.len != 0; | |
| 99 | 110 | |
| 100 | 111 | for (case.targets) |target| { |
| 101 | 112 | const target_prog_node = node: { |
| ... | ... | @@ -133,14 +144,8 @@ pub fn main() !void { |
| 133 | 144 | .llvm => try child_args.appendSlice(arena, &.{ "-fllvm", "-flld" }), |
| 134 | 145 | .cbe => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }), |
| 135 | 146 | } |
| 136 | if (debug_zcu) { | |
| 137 | try child_args.appendSlice(arena, &.{ "--debug-log", "zcu" }); | |
| 138 | } | |
| 139 | if (debug_dwarf) { | |
| 140 | try child_args.appendSlice(arena, &.{ "--debug-log", "dwarf" }); | |
| 141 | } | |
| 142 | if (debug_link) { | |
| 143 | try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" }); | |
| 147 | for (debug_log_args.items) |arg| { | |
| 148 | try child_args.appendSlice(arena, &.{ "--debug-log", arg }); | |
| 144 | 149 | } |
| 145 | 150 | for (case.modules) |mod| { |
| 146 | 151 | try child_args.appendSlice(arena, &.{ "--dep", mod.name }); |
| ... | ... | @@ -164,7 +169,7 @@ pub fn main() !void { |
| 164 | 169 | var cc_child_args: std.ArrayList([]const u8) = .empty; |
| 165 | 170 | if (target.backend == .cbe) { |
| 166 | 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 | 173 | else |
| 169 | 174 | resolved_zig_exe; |
| 170 | 175 | |
| ... | ... | @@ -185,6 +190,7 @@ pub fn main() !void { |
| 185 | 190 | |
| 186 | 191 | var eval: Eval = .{ |
| 187 | 192 | .arena = arena, |
| 193 | .io = io, | |
| 188 | 194 | .case = case, |
| 189 | 195 | .host = host, |
| 190 | 196 | .target = target, |
| ... | ... | @@ -194,11 +200,15 @@ pub fn main() !void { |
| 194 | 200 | .allow_stderr = debug_log_verbose, |
| 195 | 201 | .preserve_tmp_on_fatal = preserve_tmp, |
| 196 | 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 | }; |
| 198 | 208 | |
| 199 | try child.spawn(); | |
| 209 | try child.spawn(io); | |
| 200 | 210 | errdefer { |
| 201 | _ = child.kill() catch {}; | |
| 211 | _ = child.kill(io) catch {}; | |
| 202 | 212 | } |
| 203 | 213 | |
| 204 | 214 | var poller = Io.poll(arena, Eval.StreamEnum, .{ |
| ... | ... | @@ -228,10 +238,11 @@ pub fn main() !void { |
| 228 | 238 | |
| 229 | 239 | const Eval = struct { |
| 230 | 240 | arena: Allocator, |
| 241 | io: Io, | |
| 231 | 242 | host: std.Target, |
| 232 | 243 | case: Case, |
| 233 | 244 | target: Case.Target, |
| 234 | tmp_dir: std.fs.Dir, | |
| 245 | tmp_dir: Dir, | |
| 235 | 246 | tmp_dir_path: []const u8, |
| 236 | 247 | child: *std.process.Child, |
| 237 | 248 | allow_stderr: bool, |
| ... | ... | @@ -240,22 +251,28 @@ const Eval = struct { |
| 240 | 251 | /// The arguments `out.c in.c` must be appended before spawning the subprocess. |
| 241 | 252 | cc_child_args: *std.ArrayList([]const u8), |
| 242 | 253 | |
| 254 | enable_qemu: bool, | |
| 255 | enable_wine: bool, | |
| 256 | enable_wasmtime: bool, | |
| 257 | enable_darling: bool, | |
| 258 | ||
| 243 | 259 | const StreamEnum = enum { stdout, stderr }; |
| 244 | 260 | const Poller = Io.Poller(StreamEnum); |
| 245 | 261 | |
| 246 | 262 | /// Currently this function assumes the previous updates have already been written. |
| 247 | 263 | fn write(eval: *Eval, update: Case.Update) void { |
| 264 | const io = eval.io; | |
| 248 | 265 | for (update.changes) |full_contents| { |
| 249 | eval.tmp_dir.writeFile(.{ | |
| 266 | eval.tmp_dir.writeFile(io, .{ | |
| 250 | 267 | .sub_path = full_contents.name, |
| 251 | 268 | .data = full_contents.bytes, |
| 252 | 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 | 273 | for (update.deletes) |doomed_name| { |
| 257 | eval.tmp_dir.deleteFile(doomed_name) catch |err| { | |
| 258 | eval.fatal("failed to delete '{s}': {s}", .{ doomed_name, @errorName(err) }); | |
| 274 | eval.tmp_dir.deleteFile(io, doomed_name) catch |err| { | |
| 275 | eval.fatal("failed to delete '{s}': {t}", .{ doomed_name, err }); | |
| 259 | 276 | }; |
| 260 | 277 | } |
| 261 | 278 | } |
| ... | ... | @@ -307,14 +324,14 @@ const Eval = struct { |
| 307 | 324 | } |
| 308 | 325 | |
| 309 | 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.*); | |
| 311 | 328 | |
| 312 | 329 | const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{ |
| 313 | 330 | .root_name = "root", // corresponds to the module name "root" |
| 314 | 331 | .target = &eval.target.resolved, |
| 315 | 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 }); | |
| 318 | 335 | |
| 319 | 336 | try eval.checkSuccessOutcome(update, bin_path, prog_node); |
| 320 | 337 | // This message indicates the end of the update. |
| ... | ... | @@ -338,11 +355,12 @@ const Eval = struct { |
| 338 | 355 | } |
| 339 | 356 | |
| 340 | 357 | fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void { |
| 358 | const io = eval.io; | |
| 341 | 359 | const expected = switch (update.outcome) { |
| 342 | 360 | .unknown => return, |
| 343 | 361 | .compile_errors => |ce| ce, |
| 344 | 362 | .stdout, .exit_code => { |
| 345 | error_bundle.renderToStdErr(.{}, .auto); | |
| 363 | try error_bundle.renderToStderr(io, .{}, .auto); | |
| 346 | 364 | eval.fatal("update '{s}': unexpected compile errors", .{update.name}); |
| 347 | 365 | }, |
| 348 | 366 | }; |
| ... | ... | @@ -351,7 +369,7 @@ const Eval = struct { |
| 351 | 369 | |
| 352 | 370 | for (error_bundle.getMessages()) |err_idx| { |
| 353 | 371 | if (expected_idx == expected.errors.len) { |
| 354 | error_bundle.renderToStdErr(.{}, .auto); | |
| 372 | try error_bundle.renderToStderr(io, .{}, .auto); | |
| 355 | 373 | eval.fatal("update '{s}': more errors than expected", .{update.name}); |
| 356 | 374 | } |
| 357 | 375 | try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], false, err_idx); |
| ... | ... | @@ -359,7 +377,7 @@ const Eval = struct { |
| 359 | 377 | |
| 360 | 378 | for (error_bundle.getNotes(err_idx)) |note_idx| { |
| 361 | 379 | if (expected_idx == expected.errors.len) { |
| 362 | error_bundle.renderToStdErr(.{}, .auto); | |
| 380 | try error_bundle.renderToStderr(io, .{}, .auto); | |
| 363 | 381 | eval.fatal("update '{s}': more error notes than expected", .{update.name}); |
| 364 | 382 | } |
| 365 | 383 | try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], true, note_idx); |
| ... | ... | @@ -368,7 +386,7 @@ const Eval = struct { |
| 368 | 386 | } |
| 369 | 387 | |
| 370 | 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 | 390 | eval.fatal("update '{s}': unexpected compile log output", .{update.name}); |
| 373 | 391 | } |
| 374 | 392 | } |
| ... | ... | @@ -388,6 +406,8 @@ const Eval = struct { |
| 388 | 406 | const src = eb.getSourceLocation(err.src_loc); |
| 389 | 407 | const raw_filename = eb.nullTerminatedString(src.src_path); |
| 390 | 408 | |
| 409 | const io = eval.io; | |
| 410 | ||
| 391 | 411 | // We need to replace backslashes for consistency between platforms. |
| 392 | 412 | const filename = name: { |
| 393 | 413 | if (std.mem.indexOfScalar(u8, raw_filename, '\\') == null) break :name raw_filename; |
| ... | ... | @@ -402,7 +422,7 @@ const Eval = struct { |
| 402 | 422 | expected.column != src.column + 1 or |
| 403 | 423 | !std.mem.eql(u8, expected.msg, msg)) |
| 404 | 424 | { |
| 405 | eb.renderToStdErr(.{}, .auto); | |
| 425 | eb.renderToStderr(io, .{}, .auto) catch {}; | |
| 406 | 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 | 449 | }, |
| 430 | 450 | }; |
| 431 | 451 | |
| 452 | const io = eval.io; | |
| 453 | ||
| 432 | 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 | 457 | &eval.host, |
| 435 | 458 | &eval.target.resolved, |
| 436 | 459 | .{ .link_libc = eval.target.backend == .cbe }, |
| ... | ... | @@ -449,18 +472,48 @@ const Eval = struct { |
| 449 | 472 | argv_buf[0] = binary_path; |
| 450 | 473 | break :argv .{ argv_buf[0..1], false }; |
| 451 | 474 | }, |
| 452 | .qemu, .wine, .wasmtime, .darling => |executor_cmd| argv: { | |
| 453 | argv_buf[0] = executor_cmd; | |
| 454 | argv_buf[1] = binary_path; | |
| 455 | break :argv .{ argv_buf[0..2], true }; | |
| 475 | .qemu => |executor_cmd| argv: { | |
| 476 | if (eval.enable_qemu) { | |
| 477 | argv_buf[0] = executor_cmd; | |
| 478 | argv_buf[1] = binary_path; | |
| 479 | break :argv .{ argv_buf[0..2], true }; | |
| 480 | } else { | |
| 481 | continue :sw .bad_os_or_cpu; | |
| 482 | } | |
| 483 | }, | |
| 484 | .wine => |executor_cmd| argv: { | |
| 485 | if (eval.enable_wine) { | |
| 486 | argv_buf[0] = executor_cmd; | |
| 487 | argv_buf[1] = binary_path; | |
| 488 | break :argv .{ argv_buf[0..2], true }; | |
| 489 | } else { | |
| 490 | continue :sw .bad_os_or_cpu; | |
| 491 | } | |
| 492 | }, | |
| 493 | .wasmtime => |executor_cmd| argv: { | |
| 494 | if (eval.enable_wasmtime) { | |
| 495 | argv_buf[0] = executor_cmd; | |
| 496 | argv_buf[1] = binary_path; | |
| 497 | break :argv .{ argv_buf[0..2], true }; | |
| 498 | } else { | |
| 499 | continue :sw .bad_os_or_cpu; | |
| 500 | } | |
| 501 | }, | |
| 502 | .darling => |executor_cmd| argv: { | |
| 503 | if (eval.enable_darling) { | |
| 504 | argv_buf[0] = executor_cmd; | |
| 505 | argv_buf[1] = binary_path; | |
| 506 | break :argv .{ argv_buf[0..2], true }; | |
| 507 | } else { | |
| 508 | continue :sw .bad_os_or_cpu; | |
| 509 | } | |
| 456 | 510 | }, |
| 457 | 511 | }; |
| 458 | 512 | |
| 459 | 513 | const run_prog_node = prog_node.start("run generated executable", 0); |
| 460 | 514 | defer run_prog_node.end(); |
| 461 | 515 | |
| 462 | const result = std.process.Child.run(.{ | |
| 463 | .allocator = eval.arena, | |
| 516 | const result = std.process.Child.run(eval.arena, io, .{ | |
| 464 | 517 | .argv = argv, |
| 465 | 518 | .cwd_dir = eval.tmp_dir, |
| 466 | 519 | .cwd = eval.tmp_dir_path, |
| ... | ... | @@ -468,17 +521,17 @@ const Eval = struct { |
| 468 | 521 | if (is_foreign) { |
| 469 | 522 | // Chances are the foreign executor isn't available. Skip this evaluation. |
| 470 | 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 | 525 | update.name, |
| 473 | 526 | binary_path, |
| 474 | 527 | try eval.target.resolved.zigTriple(eval.arena), |
| 475 | @errorName(err), | |
| 528 | err, | |
| 476 | 529 | }); |
| 477 | 530 | } |
| 478 | 531 | return; |
| 479 | 532 | } |
| 480 | eval.fatal("update '{s}': failed to run the generated executable '{s}': {s}", .{ | |
| 481 | update.name, binary_path, @errorName(err), | |
| 533 | eval.fatal("update '{s}': failed to run the generated executable '{s}': {t}", .{ | |
| 534 | update.name, binary_path, err, | |
| 482 | 535 | }); |
| 483 | 536 | }; |
| 484 | 537 | |
| ... | ... | @@ -514,11 +567,12 @@ const Eval = struct { |
| 514 | 567 | } |
| 515 | 568 | |
| 516 | 569 | fn requestUpdate(eval: *Eval) !void { |
| 570 | const io = eval.io; | |
| 517 | 571 | const header: std.zig.Client.Message.Header = .{ |
| 518 | 572 | .tag = .update, |
| 519 | 573 | .bytes_len = 0, |
| 520 | 574 | }; |
| 521 | var w = eval.child.stdin.?.writer(&.{}); | |
| 575 | var w = eval.child.stdin.?.writer(io, &.{}); | |
| 522 | 576 | w.interface.writeStruct(header, .little) catch |err| switch (err) { |
| 523 | 577 | error.WriteFailed => return w.err.?, |
| 524 | 578 | }; |
| ... | ... | @@ -552,16 +606,13 @@ const Eval = struct { |
| 552 | 606 | try eval.cc_child_args.appendSlice(eval.arena, &.{ out_path, c_path }); |
| 553 | 607 | defer eval.cc_child_args.items.len -= 2; |
| 554 | 608 | |
| 555 | const result = std.process.Child.run(.{ | |
| 556 | .allocator = eval.arena, | |
| 609 | const result = std.process.Child.run(eval.arena, eval.io, .{ | |
| 557 | 610 | .argv = eval.cc_child_args.items, |
| 558 | 611 | .cwd_dir = eval.tmp_dir, |
| 559 | 612 | .cwd = eval.tmp_dir_path, |
| 560 | 613 | .progress_node = child_prog_node, |
| 561 | 614 | }) catch |err| { |
| 562 | eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{ | |
| 563 | update.name, c_path, @errorName(err), | |
| 564 | }); | |
| 615 | eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {t}", .{ update.name, c_path, err }); | |
| 565 | 616 | }; |
| 566 | 617 | switch (result.term) { |
| 567 | 618 | .Exited => |code| if (code != 0) { |
| ... | ... | @@ -588,12 +639,13 @@ const Eval = struct { |
| 588 | 639 | } |
| 589 | 640 | |
| 590 | 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 | 644 | if (!eval.preserve_tmp_on_fatal) { |
| 593 | 645 | // Kill the child since it holds an open handle to its CWD which is the tmp dir path |
| 594 | _ = eval.child.kill() catch {}; | |
| 595 | std.fs.cwd().deleteTree(eval.tmp_dir_path) catch |err| { | |
| 596 | std.log.warn("failed to delete tree '{s}': {s}", .{ eval.tmp_dir_path, @errorName(err) }); | |
| 646 | _ = eval.child.kill(io) catch {}; | |
| 647 | Dir.cwd().deleteTree(io, eval.tmp_dir_path) catch |err| { | |
| 648 | std.log.warn("failed to delete tree '{s}': {t}", .{ eval.tmp_dir_path, err }); | |
| 597 | 649 | }; |
| 598 | 650 | } |
| 599 | 651 | std.process.fatal(fmt, args); |
| ... | ... | @@ -759,7 +811,7 @@ const Case = struct { |
| 759 | 811 | if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n}); |
| 760 | 812 | last_update.outcome = .{ |
| 761 | 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 | 817 | } else if (std.mem.eql(u8, key, "expect_error")) { |
| ... | ... | @@ -833,27 +885,29 @@ const Case = struct { |
| 833 | 885 | |
| 834 | 886 | fn requestExit(child: *std.process.Child, eval: *Eval) void { |
| 835 | 887 | if (child.stdin == null) return; |
| 888 | const io = eval.io; | |
| 836 | 889 | |
| 837 | 890 | const header: std.zig.Client.Message.Header = .{ |
| 838 | 891 | .tag = .exit, |
| 839 | 892 | .bytes_len = 0, |
| 840 | 893 | }; |
| 841 | var w = eval.child.stdin.?.writer(&.{}); | |
| 894 | var w = eval.child.stdin.?.writer(io, &.{}); | |
| 842 | 895 | w.interface.writeStruct(header, .little) catch |err| switch (err) { |
| 843 | 896 | error.WriteFailed => switch (w.err.?) { |
| 844 | 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 | }; |
| 848 | 901 | |
| 849 | 902 | // Send EOF to stdin. |
| 850 | child.stdin.?.close(); | |
| 903 | child.stdin.?.close(io); | |
| 851 | 904 | child.stdin = null; |
| 852 | 905 | } |
| 853 | 906 | |
| 854 | 907 | fn waitChild(child: *std.process.Child, eval: *Eval) void { |
| 908 | const io = eval.io; | |
| 855 | 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 | 911 | switch (term) { |
| 858 | 912 | .Exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}), |
| 859 | 913 | .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}), |
tools/migrate_langref.zig+19-16| ... | ... | @@ -1,13 +1,16 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 3 | const fs = std.fs; | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Dir = std.Io.Dir; | |
| 4 | 6 | const print = std.debug.print; |
| 5 | 7 | const mem = std.mem; |
| 6 | 8 | const testing = std.testing; |
| 7 | 9 | const Allocator = std.mem.Allocator; |
| 8 | const max_doc_file_size = 10 * 1024 * 1024; | |
| 9 | 10 | const fatal = std.process.fatal; |
| 10 | 11 | |
| 12 | const max_doc_file_size = 10 * 1024 * 1024; | |
| 13 | ||
| 11 | 14 | pub fn main() !void { |
| 12 | 15 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 13 | 16 | defer arena_instance.deinit(); |
| ... | ... | @@ -19,27 +22,27 @@ pub fn main() !void { |
| 19 | 22 | const input_file = args[1]; |
| 20 | 23 | const output_file = args[2]; |
| 21 | 24 | |
| 22 | var threaded: std.Io.Threaded = .init(gpa); | |
| 25 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 23 | 26 | defer threaded.deinit(); |
| 24 | 27 | const io = threaded.io(); |
| 25 | 28 | |
| 26 | var in_file = try fs.cwd().openFile(input_file, .{ .mode = .read_only }); | |
| 27 | defer in_file.close(); | |
| 29 | var in_file = try Dir.cwd().openFile(io, input_file, .{ .mode = .read_only }); | |
| 30 | defer in_file.close(io); | |
| 28 | 31 | |
| 29 | var out_file = try fs.cwd().createFile(output_file, .{}); | |
| 30 | defer out_file.close(); | |
| 32 | var out_file = try Dir.cwd().createFile(io, output_file, .{}); | |
| 33 | defer out_file.close(io); | |
| 31 | 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); | |
| 33 | 36 | |
| 34 | var out_dir = try fs.cwd().openDir(fs.path.dirname(output_file).?, .{}); | |
| 35 | defer out_dir.close(); | |
| 37 | var out_dir = try Dir.cwd().openDir(io, Dir.path.dirname(output_file).?, .{}); | |
| 38 | defer out_dir.close(io); | |
| 36 | 39 | |
| 37 | 40 | var in_file_reader = in_file.reader(io, &.{}); |
| 38 | 41 | const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .unlimited); |
| 39 | 42 | |
| 40 | 43 | var tokenizer = Tokenizer.init(input_file, input_file_bytes); |
| 41 | 44 | |
| 42 | try walk(arena, &tokenizer, out_dir, &out_file_writer.interface); | |
| 45 | try walk(arena, io, &tokenizer, out_dir, &out_file_writer.interface); | |
| 43 | 46 | |
| 44 | 47 | try out_file_writer.end(); |
| 45 | 48 | } |
| ... | ... | @@ -266,7 +269,7 @@ const Code = struct { |
| 266 | 269 | }; |
| 267 | 270 | }; |
| 268 | 271 | |
| 269 | fn walk(arena: Allocator, tokenizer: *Tokenizer, out_dir: std.fs.Dir, w: anytype) !void { | |
| 272 | fn walk(arena: Allocator, io: Io, tokenizer: *Tokenizer, out_dir: Dir, w: anytype) !void { | |
| 270 | 273 | while (true) { |
| 271 | 274 | const token = tokenizer.next(); |
| 272 | 275 | switch (token.id) { |
| ... | ... | @@ -384,12 +387,12 @@ fn walk(arena: Allocator, tokenizer: *Tokenizer, out_dir: std.fs.Dir, w: anytype |
| 384 | 387 | |
| 385 | 388 | const basename = try std.fmt.allocPrint(arena, "{s}.zig", .{name}); |
| 386 | 389 | |
| 387 | var file = out_dir.createFile(basename, .{ .exclusive = true }) catch |err| { | |
| 390 | var file = out_dir.createFile(io, basename, .{ .exclusive = true }) catch |err| { | |
| 388 | 391 | fatal("unable to create file '{s}': {s}", .{ name, @errorName(err) }); |
| 389 | 392 | }; |
| 390 | defer file.close(); | |
| 393 | defer file.close(io); | |
| 391 | 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 | 396 | const code = &file_writer.interface; |
| 394 | 397 | |
| 395 | 398 | const source = tokenizer.buffer[source_token.start..source_token.end]; |
tools/process_headers.zig+22-17| ... | ... | @@ -12,6 +12,8 @@ |
| 12 | 12 | //! You'll then have to manually update Zig source repo with these new files. |
| 13 | 13 | |
| 14 | 14 | const std = @import("std"); |
| 15 | const Io = std.Io; | |
| 16 | const Dir = std.Io.Dir; | |
| 15 | 17 | const Arch = std.Target.Cpu.Arch; |
| 16 | 18 | const Abi = std.Target.Abi; |
| 17 | 19 | const OsTag = std.Target.Os.Tag; |
| ... | ... | @@ -128,6 +130,11 @@ const LibCVendor = enum { |
| 128 | 130 | pub fn main() !void { |
| 129 | 131 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 130 | 132 | const allocator = arena.allocator(); |
| 133 | ||
| 134 | var threaded: Io.Threaded = .init(allocator, .{}); | |
| 135 | defer threaded.deinit(); | |
| 136 | const io = threaded.io(); | |
| 137 | ||
| 131 | 138 | const args = try std.process.argsAlloc(allocator); |
| 132 | 139 | var search_paths = std.array_list.Managed([]const u8).init(allocator); |
| 133 | 140 | var opt_out_dir: ?[]const u8 = null; |
| ... | ... | @@ -232,28 +239,28 @@ pub fn main() !void { |
| 232 | 239 | => &[_][]const u8{ search_path, libc_dir, "usr", "include" }, |
| 233 | 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 | 243 | var dir_stack = std.array_list.Managed([]const u8).init(allocator); |
| 237 | 244 | try dir_stack.append(target_include_dir); |
| 238 | 245 | |
| 239 | 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 | 248 | error.FileNotFound => continue :search, |
| 242 | 249 | error.AccessDenied => continue :search, |
| 243 | 250 | else => return err, |
| 244 | 251 | }; |
| 245 | defer dir.close(); | |
| 252 | defer dir.close(io); | |
| 246 | 253 | |
| 247 | 254 | var dir_it = dir.iterate(); |
| 248 | 255 | |
| 249 | while (try dir_it.next()) |entry| { | |
| 250 | const full_path = try std.fs.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name }); | |
| 256 | while (try dir_it.next(io)) |entry| { | |
| 257 | const full_path = try Dir.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name }); | |
| 251 | 258 | switch (entry.kind) { |
| 252 | 259 | .directory => try dir_stack.append(full_path), |
| 253 | 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 | 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 | 264 | const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t"); |
| 258 | 265 | total_bytes += raw_bytes.len; |
| 259 | 266 | const hash = try allocator.alloc(u8, 32); |
| ... | ... | @@ -266,9 +273,7 @@ pub fn main() !void { |
| 266 | 273 | max_bytes_saved += raw_bytes.len; |
| 267 | 274 | gop.value_ptr.hit_count += 1; |
| 268 | 275 | std.debug.print("duplicate: {s} {s} ({B})\n", .{ |
| 269 | libc_dir, | |
| 270 | rel_path, | |
| 271 | raw_bytes.len, | |
| 276 | libc_dir, rel_path, raw_bytes.len, | |
| 272 | 277 | }); |
| 273 | 278 | } else { |
| 274 | 279 | gop.value_ptr.* = Contents{ |
| ... | ... | @@ -314,7 +319,7 @@ pub fn main() !void { |
| 314 | 319 | total_bytes, |
| 315 | 320 | total_bytes - max_bytes_saved, |
| 316 | 321 | }); |
| 317 | try std.fs.cwd().makePath(out_dir); | |
| 322 | try Dir.cwd().createDirPath(io, out_dir); | |
| 318 | 323 | |
| 319 | 324 | var missed_opportunity_bytes: usize = 0; |
| 320 | 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 | 339 | const best_contents = contents_list.pop().?; |
| 335 | 340 | if (best_contents.hit_count > 1) { |
| 336 | 341 | // worth it to make it generic |
| 337 | const full_path = try std.fs.path.join(allocator, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* }); | |
| 338 | try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?); | |
| 339 | try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = best_contents.bytes }); | |
| 342 | const full_path = try Dir.path.join(allocator, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* }); | |
| 343 | try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?); | |
| 344 | try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = best_contents.bytes }); | |
| 340 | 345 | best_contents.is_generic = true; |
| 341 | 346 | while (contents_list.pop()) |contender| { |
| 342 | 347 | if (contender.hit_count > 1) { |
| ... | ... | @@ -355,9 +360,9 @@ pub fn main() !void { |
| 355 | 360 | if (contents.is_generic) continue; |
| 356 | 361 | |
| 357 | 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.* }); | |
| 359 | try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?); | |
| 360 | try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = contents.bytes }); | |
| 363 | const full_path = try Dir.path.join(allocator, &[_][]const u8{ out_dir, dest_target, path_kv.key_ptr.* }); | |
| 364 | try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?); | |
| 365 | try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = contents.bytes }); | |
| 361 | 366 | } |
| 362 | 367 | } |
| 363 | 368 | } |
tools/update-linux-headers.zig+23-16| ... | ... | @@ -15,6 +15,8 @@ |
| 15 | 15 | //! You'll then have to manually update Zig source repo with these new files. |
| 16 | 16 | |
| 17 | 17 | const std = @import("std"); |
| 18 | const Io = std.Io; | |
| 19 | const Dir = std.Io.Dir; | |
| 18 | 20 | const Arch = std.Target.Cpu.Arch; |
| 19 | 21 | const Abi = std.Target.Abi; |
| 20 | 22 | const assert = std.debug.assert; |
| ... | ... | @@ -142,6 +144,11 @@ const PathTable = std.StringHashMap(*TargetToHash); |
| 142 | 144 | pub fn main() !void { |
| 143 | 145 | var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 144 | 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 | 152 | const args = try std.process.argsAlloc(arena); |
| 146 | 153 | var search_paths = std.array_list.Managed([]const u8).init(arena); |
| 147 | 154 | var opt_out_dir: ?[]const u8 = null; |
| ... | ... | @@ -183,30 +190,30 @@ pub fn main() !void { |
| 183 | 190 | .arch = linux_target.arch, |
| 184 | 191 | }; |
| 185 | 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 | 194 | search_path, linux_target.name, "include", |
| 188 | 195 | }); |
| 189 | 196 | var dir_stack = std.array_list.Managed([]const u8).init(arena); |
| 190 | 197 | try dir_stack.append(target_include_dir); |
| 191 | 198 | |
| 192 | 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 | 201 | error.FileNotFound => continue :search, |
| 195 | 202 | error.AccessDenied => continue :search, |
| 196 | 203 | else => return err, |
| 197 | 204 | }; |
| 198 | defer dir.close(); | |
| 205 | defer dir.close(io); | |
| 199 | 206 | |
| 200 | 207 | var dir_it = dir.iterate(); |
| 201 | 208 | |
| 202 | while (try dir_it.next()) |entry| { | |
| 203 | const full_path = try std.fs.path.join(arena, &[_][]const u8{ full_dir_name, entry.name }); | |
| 209 | while (try dir_it.next(io)) |entry| { | |
| 210 | const full_path = try Dir.path.join(arena, &[_][]const u8{ full_dir_name, entry.name }); | |
| 204 | 211 | switch (entry.kind) { |
| 205 | 212 | .directory => try dir_stack.append(full_path), |
| 206 | 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 | 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 | 217 | const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t"); |
| 211 | 218 | total_bytes += raw_bytes.len; |
| 212 | 219 | const hash = try arena.alloc(u8, 32); |
| ... | ... | @@ -253,7 +260,7 @@ pub fn main() !void { |
| 253 | 260 | total_bytes, |
| 254 | 261 | total_bytes - max_bytes_saved, |
| 255 | 262 | }); |
| 256 | try std.fs.cwd().makePath(out_dir); | |
| 263 | try Dir.cwd().createDirPath(io, out_dir); | |
| 257 | 264 | |
| 258 | 265 | var missed_opportunity_bytes: usize = 0; |
| 259 | 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 | 280 | const best_contents = contents_list.pop().?; |
| 274 | 281 | if (best_contents.hit_count > 1) { |
| 275 | 282 | // worth it to make it generic |
| 276 | const full_path = try std.fs.path.join(arena, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* }); | |
| 277 | try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?); | |
| 278 | try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = best_contents.bytes }); | |
| 283 | const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* }); | |
| 284 | try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?); | |
| 285 | try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = best_contents.bytes }); | |
| 279 | 286 | best_contents.is_generic = true; |
| 280 | 287 | while (contents_list.pop()) |contender| { |
| 281 | 288 | if (contender.hit_count > 1) { |
| ... | ... | @@ -299,9 +306,9 @@ pub fn main() !void { |
| 299 | 306 | else => @tagName(dest_target.arch), |
| 300 | 307 | }; |
| 301 | 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.* }); | |
| 303 | try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?); | |
| 304 | try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = contents.bytes }); | |
| 309 | const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, out_subpath, path_kv.key_ptr.* }); | |
| 310 | try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?); | |
| 311 | try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = contents.bytes }); | |
| 305 | 312 | } |
| 306 | 313 | } |
| 307 | 314 | |
| ... | ... | @@ -316,8 +323,8 @@ pub fn main() !void { |
| 316 | 323 | "any-linux-any/linux/netfilter_ipv6/ip6t_HL.h", |
| 317 | 324 | }; |
| 318 | 325 | for (bad_files) |bad_file| { |
| 319 | const full_path = try std.fs.path.join(arena, &[_][]const u8{ out_dir, bad_file }); | |
| 320 | try std.fs.cwd().deleteFile(full_path); | |
| 326 | const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, bad_file }); | |
| 327 | try Dir.cwd().deleteFile(io, full_path); | |
| 321 | 328 | } |
| 322 | 329 | } |
| 323 | 330 |
tools/update_clang_options.zig+9-6| ... | ... | @@ -10,7 +10,7 @@ |
| 10 | 10 | //! would mean that the next parameter specifies the target. |
| 11 | 11 | |
| 12 | 12 | const std = @import("std"); |
| 13 | const fs = std.fs; | |
| 13 | const Io = std.Io; | |
| 14 | 14 | const assert = std.debug.assert; |
| 15 | 15 | const json = std.json; |
| 16 | 16 | |
| ... | ... | @@ -634,8 +634,12 @@ pub fn main() anyerror!void { |
| 634 | 634 | const allocator = arena.allocator(); |
| 635 | 635 | const args = try std.process.argsAlloc(allocator); |
| 636 | 636 | |
| 637 | var threaded: std.Io.Threaded = .init(allocator, .{}); | |
| 638 | defer threaded.deinit(); | |
| 639 | const io = threaded.io(); | |
| 640 | ||
| 637 | 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 | 643 | const stdout = &stdout_writer.interface; |
| 640 | 644 | |
| 641 | 645 | if (args.len <= 1) printUsageAndExit(args[0]); |
| ... | ... | @@ -676,8 +680,7 @@ pub fn main() anyerror!void { |
| 676 | 680 | try std.fmt.allocPrint(allocator, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}), |
| 677 | 681 | }; |
| 678 | 682 | |
| 679 | const child_result = try std.process.Child.run(.{ | |
| 680 | .allocator = allocator, | |
| 683 | const child_result = try std.process.Child.run(allocator, io, .{ | |
| 681 | 684 | .argv = &child_args, |
| 682 | 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 | 964 | } |
| 962 | 965 | |
| 963 | 966 | fn printUsageAndExit(arg0: []const u8) noreturn { |
| 964 | const w, _ = std.debug.lockStderrWriter(&.{}); | |
| 965 | defer std.debug.unlockStderrWriter(); | |
| 967 | const stderr = std.debug.lockStderr(&.{}); | |
| 968 | const w = &stderr.file_writer.interface; | |
| 966 | 969 | printUsage(w, arg0) catch std.process.exit(2); |
| 967 | 970 | std.process.exit(1); |
| 968 | 971 | } |
tools/update_cpu_features.zig+22-20| ... | ... | @@ -1,6 +1,8 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 3 | const fs = std.fs; | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Dir = std.Io.Dir; | |
| 4 | 6 | const mem = std.mem; |
| 5 | 7 | const json = std.json; |
| 6 | 8 | const assert = std.debug.assert; |
| ... | ... | @@ -1890,7 +1892,7 @@ pub fn main() anyerror!void { |
| 1890 | 1892 | defer arena_state.deinit(); |
| 1891 | 1893 | const arena = arena_state.allocator(); |
| 1892 | 1894 | |
| 1893 | var threaded: std.Io.Threaded = .init(gpa); | |
| 1895 | var threaded: std.Io.Threaded = .init(gpa, .{}); | |
| 1894 | 1896 | defer threaded.deinit(); |
| 1895 | 1897 | const io = threaded.io(); |
| 1896 | 1898 | |
| ... | ... | @@ -1927,26 +1929,26 @@ pub fn main() anyerror!void { |
| 1927 | 1929 | // there shouldn't be any more argument after the optional filter |
| 1928 | 1930 | if (args.skip()) usageAndExit(args0, 1); |
| 1929 | 1931 | |
| 1930 | var zig_src_dir = try fs.cwd().openDir(zig_src_root, .{}); | |
| 1931 | defer zig_src_dir.close(); | |
| 1932 | var zig_src_dir = try Dir.cwd().openDir(io, zig_src_root, .{}); | |
| 1933 | defer zig_src_dir.close(io); | |
| 1932 | 1934 | |
| 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 | 1936 | defer root_progress.end(); |
| 1935 | 1937 | |
| 1936 | var group: std.Io.Group = .init; | |
| 1938 | var group: Io.Group = .init; | |
| 1937 | 1939 | defer group.cancel(io); |
| 1938 | 1940 | |
| 1939 | 1941 | for (targets) |target| { |
| 1940 | 1942 | if (filter) |zig_name| { |
| 1941 | 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 | 1946 | .llvm_tblgen_exe = llvm_tblgen_exe, |
| 1945 | 1947 | .llvm_src_root = llvm_src_root, |
| 1946 | 1948 | .zig_src_dir = zig_src_dir, |
| 1947 | 1949 | .root_progress = root_progress, |
| 1948 | 1950 | .target = target, |
| 1949 | }}); | |
| 1951 | } }); | |
| 1950 | 1952 | } |
| 1951 | 1953 | |
| 1952 | 1954 | group.wait(io); |
| ... | ... | @@ -1955,12 +1957,12 @@ pub fn main() anyerror!void { |
| 1955 | 1957 | const Job = struct { |
| 1956 | 1958 | llvm_tblgen_exe: []const u8, |
| 1957 | 1959 | llvm_src_root: []const u8, |
| 1958 | zig_src_dir: std.fs.Dir, | |
| 1960 | zig_src_dir: Dir, | |
| 1959 | 1961 | root_progress: std.Progress.Node, |
| 1960 | 1962 | target: ArchTarget, |
| 1961 | 1963 | }; |
| 1962 | 1964 | |
| 1963 | fn processOneTarget(job: Job) void { | |
| 1965 | fn processOneTarget(io: Io, job: Job) void { | |
| 1964 | 1966 | errdefer |err| std.debug.panic("panic: {s}", .{@errorName(err)}); |
| 1965 | 1967 | const target = job.target; |
| 1966 | 1968 | |
| ... | ... | @@ -1992,8 +1994,7 @@ fn processOneTarget(job: Job) void { |
| 1992 | 1994 | }), |
| 1993 | 1995 | }; |
| 1994 | 1996 | |
| 1995 | const child_result = try std.process.Child.run(.{ | |
| 1996 | .allocator = arena, | |
| 1997 | const child_result = try std.process.Child.run(arena, io, .{ | |
| 1997 | 1998 | .argv = &child_args, |
| 1998 | 1999 | .max_output_bytes = 500 * 1024 * 1024, |
| 1999 | 2000 | }); |
| ... | ... | @@ -2240,15 +2241,15 @@ fn processOneTarget(job: Job) void { |
| 2240 | 2241 | |
| 2241 | 2242 | const render_progress = progress_node.start("rendering Zig code", 0); |
| 2242 | 2243 | |
| 2243 | var target_dir = try job.zig_src_dir.openDir("lib/std/Target", .{}); | |
| 2244 | defer target_dir.close(); | |
| 2244 | var target_dir = try job.zig_src_dir.openDir(io, "lib/std/Target", .{}); | |
| 2245 | defer target_dir.close(io); | |
| 2245 | 2246 | |
| 2246 | 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 | defer zig_code_file.close(); | |
| 2248 | var zig_code_file = try target_dir.createFile(io, zig_code_basename, .{}); | |
| 2249 | defer zig_code_file.close(io); | |
| 2249 | 2250 | |
| 2250 | 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 | 2253 | const w = &zig_code_file_writer.interface; |
| 2253 | 2254 | |
| 2254 | 2255 | try w.writeAll( |
| ... | ... | @@ -2424,8 +2425,9 @@ fn processOneTarget(job: Job) void { |
| 2424 | 2425 | } |
| 2425 | 2426 | |
| 2426 | 2427 | fn usageAndExit(arg0: []const u8, code: u8) noreturn { |
| 2427 | const stderr, _ = std.debug.lockStderrWriter(&.{}); | |
| 2428 | stderr.print( | |
| 2428 | const stderr = std.debug.lockStderr(&.{}); | |
| 2429 | const w = &stderr.file_writer.interface; | |
| 2430 | w.print( | |
| 2429 | 2431 | \\Usage: {s} /path/to/llvm-tblgen /path/git/llvm-project /path/git/zig [zig_name filter] |
| 2430 | 2432 | \\ |
| 2431 | 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 | 1 | const std = @import("std"); |
| 2 | const fs = std.fs; | |
| 2 | const Io = std.Io; | |
| 3 | const Dir = std.Io.Dir; | |
| 3 | 4 | const mem = std.mem; |
| 4 | 5 | const ascii = std.ascii; |
| 5 | 6 | |
| ... | ... | @@ -10,27 +11,31 @@ pub fn main() anyerror!void { |
| 10 | 11 | defer arena_state.deinit(); |
| 11 | 12 | const arena = arena_state.allocator(); |
| 12 | 13 | |
| 14 | var threaded: Io.Threaded = .init(arena, .{}); | |
| 15 | defer threaded.deinit(); | |
| 16 | const io = threaded.io(); | |
| 17 | ||
| 13 | 18 | const args = try std.process.argsAlloc(arena); |
| 14 | 19 | if (args.len <= 1) printUsageAndExit(args[0]); |
| 15 | 20 | |
| 16 | 21 | const zig_src_root = args[1]; |
| 17 | 22 | if (mem.startsWith(u8, zig_src_root, "-")) printUsageAndExit(args[0]); |
| 18 | 23 | |
| 19 | var zig_src_dir = try fs.cwd().openDir(zig_src_root, .{}); | |
| 20 | defer zig_src_dir.close(); | |
| 24 | var zig_src_dir = try Dir.cwd().openDir(io, zig_src_root, .{}); | |
| 25 | defer zig_src_dir.close(io); | |
| 21 | 26 | |
| 22 | const hash_sub_path = try fs.path.join(arena, &.{ "lib", "std", "hash" }); | |
| 23 | var hash_target_dir = try zig_src_dir.makeOpenPath(hash_sub_path, .{}); | |
| 24 | defer hash_target_dir.close(); | |
| 27 | const hash_sub_path = try Dir.path.join(arena, &.{ "lib", "std", "hash" }); | |
| 28 | var hash_target_dir = try zig_src_dir.createDirPathOpen(io, hash_sub_path, .{}); | |
| 29 | defer hash_target_dir.close(io); | |
| 25 | 30 | |
| 26 | const crc_sub_path = try fs.path.join(arena, &.{ "lib", "std", "hash", "crc" }); | |
| 27 | var crc_target_dir = try zig_src_dir.makeOpenPath(crc_sub_path, .{}); | |
| 28 | defer crc_target_dir.close(); | |
| 31 | const crc_sub_path = try Dir.path.join(arena, &.{ "lib", "std", "hash", "crc" }); | |
| 32 | var crc_target_dir = try zig_src_dir.createDirPathOpen(io, crc_sub_path, .{}); | |
| 33 | defer crc_target_dir.close(io); | |
| 29 | 34 | |
| 30 | var zig_code_file = try hash_target_dir.createFile("crc.zig", .{}); | |
| 31 | defer zig_code_file.close(); | |
| 35 | var zig_code_file = try hash_target_dir.createFile(io, "crc.zig", .{}); | |
| 36 | defer zig_code_file.close(io); | |
| 32 | 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 | 39 | const code_writer = &zig_code_file_writer.interface; |
| 35 | 40 | |
| 36 | 41 | try code_writer.writeAll( |
| ... | ... | @@ -51,10 +56,10 @@ pub fn main() anyerror!void { |
| 51 | 56 | \\ |
| 52 | 57 | ); |
| 53 | 58 | |
| 54 | var zig_test_file = try crc_target_dir.createFile("test.zig", .{}); | |
| 55 | defer zig_test_file.close(); | |
| 59 | var zig_test_file = try crc_target_dir.createFile(io, "test.zig", .{}); | |
| 60 | defer zig_test_file.close(io); | |
| 56 | 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 | 63 | const test_writer = &zig_test_file_writer.interface; |
| 59 | 64 | |
| 60 | 65 | try test_writer.writeAll( |
| ... | ... | @@ -190,8 +195,8 @@ pub fn main() anyerror!void { |
| 190 | 195 | } |
| 191 | 196 | |
| 192 | 197 | fn printUsageAndExit(arg0: []const u8) noreturn { |
| 193 | const w, _ = std.debug.lockStderrWriter(&.{}); | |
| 194 | defer std.debug.unlockStderrWriter(); | |
| 198 | const stderr = std.debug.lockStderr(&.{}); | |
| 199 | const w = &stderr.file_writer.interface; | |
| 195 | 200 | printUsage(w, arg0) catch std.process.exit(2); |
| 196 | 201 | std.process.exit(1); |
| 197 | 202 | } |
tools/update_freebsd_libc.zig+16-16| ... | ... | @@ -5,6 +5,7 @@ |
| 5 | 5 | //! `zig run tools/update_freebsd_libc.zig -- ~/Downloads/freebsd-src .` |
| 6 | 6 | |
| 7 | 7 | const std = @import("std"); |
| 8 | const Io = std.Io; | |
| 8 | 9 | |
| 9 | 10 | const exempt_files = [_][]const u8{ |
| 10 | 11 | // This file is maintained by a separate project and does not come from FreeBSD. |
| ... | ... | @@ -16,29 +17,31 @@ pub fn main() !void { |
| 16 | 17 | defer arena_instance.deinit(); |
| 17 | 18 | const arena = arena_instance.allocator(); |
| 18 | 19 | |
| 20 | var threaded: Io.Threaded = .init(arena, .{}); | |
| 21 | defer threaded.deinit(); | |
| 22 | const io = threaded.io(); | |
| 23 | ||
| 19 | 24 | const args = try std.process.argsAlloc(arena); |
| 20 | 25 | const freebsd_src_path = args[1]; |
| 21 | 26 | const zig_src_path = args[2]; |
| 22 | 27 | |
| 23 | 28 | const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/freebsd", .{zig_src_path}); |
| 24 | 29 | |
| 25 | var dest_dir = std.fs.cwd().openDir(dest_dir_path, .{ .iterate = true }) catch |err| { | |
| 26 | std.log.err("unable to open destination directory '{s}': {s}", .{ | |
| 27 | dest_dir_path, @errorName(err), | |
| 28 | }); | |
| 30 | var dest_dir = Io.Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| { | |
| 31 | std.log.err("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err }); | |
| 29 | 32 | std.process.exit(1); |
| 30 | 33 | }; |
| 31 | defer dest_dir.close(); | |
| 34 | defer dest_dir.close(io); | |
| 32 | 35 | |
| 33 | var freebsd_src_dir = try std.fs.cwd().openDir(freebsd_src_path, .{}); | |
| 34 | defer freebsd_src_dir.close(); | |
| 36 | var freebsd_src_dir = try Io.Dir.cwd().openDir(io, freebsd_src_path, .{}); | |
| 37 | defer freebsd_src_dir.close(io); | |
| 35 | 38 | |
| 36 | 39 | // Copy updated files from upstream. |
| 37 | 40 | { |
| 38 | 41 | var walker = try dest_dir.walk(arena); |
| 39 | 42 | defer walker.deinit(); |
| 40 | 43 | |
| 41 | walk: while (try walker.next()) |entry| { | |
| 44 | walk: while (try walker.next(io)) |entry| { | |
| 42 | 45 | if (entry.kind != .file) continue; |
| 43 | 46 | if (std.mem.startsWith(u8, entry.basename, ".")) continue; |
| 44 | 47 | for (exempt_files) |p| { |
| ... | ... | @@ -46,18 +49,15 @@ pub fn main() !void { |
| 46 | 49 | } |
| 47 | 50 | |
| 48 | 51 | std.log.info("updating '{s}/{s}' from '{s}/{s}'", .{ |
| 49 | dest_dir_path, entry.path, | |
| 50 | freebsd_src_path, entry.path, | |
| 52 | dest_dir_path, entry.path, freebsd_src_path, entry.path, | |
| 51 | 53 | }); |
| 52 | 54 | |
| 53 | freebsd_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| { | |
| 54 | std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{ | |
| 55 | freebsd_src_path, entry.path, | |
| 56 | dest_dir_path, entry.path, | |
| 57 | @errorName(err), | |
| 55 | freebsd_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| { | |
| 56 | std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{ | |
| 57 | freebsd_src_path, entry.path, dest_dir_path, entry.path, err, | |
| 58 | 58 | }); |
| 59 | 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 | 7 | //! `zig run ../tools/update_glibc.zig -- ~/Downloads/glibc ..` |
| 8 | 8 | |
| 9 | 9 | const std = @import("std"); |
| 10 | const Io = std.Io; | |
| 11 | const Dir = std.Io.Dir; | |
| 10 | 12 | const mem = std.mem; |
| 11 | 13 | const log = std.log; |
| 12 | const fs = std.fs; | |
| 14 | const fatal = std.process.fatal; | |
| 13 | 15 | |
| 14 | 16 | const exempt_files = [_][]const u8{ |
| 15 | 17 | // This file is maintained by a separate project and does not come from glibc. |
| ... | ... | @@ -41,28 +43,30 @@ pub fn main() !void { |
| 41 | 43 | defer arena_instance.deinit(); |
| 42 | 44 | const arena = arena_instance.allocator(); |
| 43 | 45 | |
| 46 | var threaded: Io.Threaded = .init(arena, .{}); | |
| 47 | defer threaded.deinit(); | |
| 48 | const io = threaded.io(); | |
| 49 | ||
| 44 | 50 | const args = try std.process.argsAlloc(arena); |
| 45 | 51 | const glibc_src_path = args[1]; |
| 46 | 52 | const zig_src_path = args[2]; |
| 47 | 53 | |
| 48 | 54 | const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/glibc", .{zig_src_path}); |
| 49 | 55 | |
| 50 | var dest_dir = fs.cwd().openDir(dest_dir_path, .{ .iterate = true }) catch |err| { | |
| 51 | fatal("unable to open destination directory '{s}': {s}", .{ | |
| 52 | dest_dir_path, @errorName(err), | |
| 53 | }); | |
| 56 | var dest_dir = Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| { | |
| 57 | fatal("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err }); | |
| 54 | 58 | }; |
| 55 | defer dest_dir.close(); | |
| 59 | defer dest_dir.close(io); | |
| 56 | 60 | |
| 57 | var glibc_src_dir = try fs.cwd().openDir(glibc_src_path, .{}); | |
| 58 | defer glibc_src_dir.close(); | |
| 61 | var glibc_src_dir = try Dir.cwd().openDir(io, glibc_src_path, .{}); | |
| 62 | defer glibc_src_dir.close(io); | |
| 59 | 63 | |
| 60 | 64 | // Copy updated files from upstream. |
| 61 | 65 | { |
| 62 | 66 | var walker = try dest_dir.walk(arena); |
| 63 | 67 | defer walker.deinit(); |
| 64 | 68 | |
| 65 | walk: while (try walker.next()) |entry| { | |
| 69 | walk: while (try walker.next(io)) |entry| { | |
| 66 | 70 | if (entry.kind != .file) continue; |
| 67 | 71 | if (mem.startsWith(u8, entry.basename, ".")) continue; |
| 68 | 72 | for (exempt_files) |p| { |
| ... | ... | @@ -72,14 +76,12 @@ pub fn main() !void { |
| 72 | 76 | if (mem.endsWith(u8, entry.path, ext)) continue :walk; |
| 73 | 77 | } |
| 74 | 78 | |
| 75 | glibc_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| { | |
| 76 | log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{ | |
| 77 | glibc_src_path, entry.path, | |
| 78 | dest_dir_path, entry.path, | |
| 79 | @errorName(err), | |
| 79 | glibc_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| { | |
| 80 | log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{ | |
| 81 | glibc_src_path, entry.path, dest_dir_path, entry.path, err, | |
| 80 | 82 | }); |
| 81 | 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 | 90 | // Warn about duplicated files inside glibc/include/* that can be omitted |
| 89 | 91 | // because they are already in generic-glibc/*. |
| 90 | 92 | |
| 91 | var include_dir = dest_dir.openDir("include", .{ .iterate = true }) catch |err| { | |
| 92 | fatal("unable to open directory '{s}/include': {s}", .{ | |
| 93 | dest_dir_path, @errorName(err), | |
| 94 | }); | |
| 93 | var include_dir = dest_dir.openDir(io, "include", .{ .iterate = true }) catch |err| { | |
| 94 | fatal("unable to open directory '{s}/include': {t}", .{ dest_dir_path, err }); | |
| 95 | 95 | }; |
| 96 | defer include_dir.close(); | |
| 96 | defer include_dir.close(io); | |
| 97 | 97 | |
| 98 | 98 | const generic_glibc_path = try std.fmt.allocPrint( |
| 99 | 99 | arena, |
| 100 | 100 | "{s}/lib/libc/include/generic-glibc", |
| 101 | 101 | .{zig_src_path}, |
| 102 | 102 | ); |
| 103 | var generic_glibc_dir = try fs.cwd().openDir(generic_glibc_path, .{}); | |
| 104 | defer generic_glibc_dir.close(); | |
| 103 | var generic_glibc_dir = try Dir.cwd().openDir(io, generic_glibc_path, .{}); | |
| 104 | defer generic_glibc_dir.close(io); | |
| 105 | 105 | |
| 106 | 106 | var walker = try include_dir.walk(arena); |
| 107 | 107 | defer walker.deinit(); |
| 108 | 108 | |
| 109 | walk: while (try walker.next()) |entry| { | |
| 109 | walk: while (try walker.next(io)) |entry| { | |
| 110 | 110 | if (entry.kind != .file) continue; |
| 111 | 111 | if (mem.startsWith(u8, entry.basename, ".")) continue; |
| 112 | 112 | for (exempt_files) |p| { |
| ... | ... | @@ -116,23 +116,21 @@ pub fn main() !void { |
| 116 | 116 | const max_file_size = 10 * 1024 * 1024; |
| 117 | 117 | |
| 118 | 118 | const generic_glibc_contents = generic_glibc_dir.readFileAlloc( |
| 119 | io, | |
| 119 | 120 | entry.path, |
| 120 | 121 | arena, |
| 121 | 122 | .limited(max_file_size), |
| 122 | 123 | ) catch |err| switch (err) { |
| 123 | 124 | error.FileNotFound => continue, |
| 124 | else => |e| fatal("unable to load '{s}/include/{s}': {s}", .{ | |
| 125 | generic_glibc_path, entry.path, @errorName(e), | |
| 126 | }), | |
| 125 | else => |e| fatal("unable to load '{s}/include/{s}': {t}", .{ generic_glibc_path, entry.path, e }), | |
| 127 | 126 | }; |
| 128 | 127 | const glibc_include_contents = include_dir.readFileAlloc( |
| 128 | io, | |
| 129 | 129 | entry.path, |
| 130 | 130 | arena, |
| 131 | 131 | .limited(max_file_size), |
| 132 | 132 | ) catch |err| { |
| 133 | fatal("unable to load '{s}/include/{s}': {s}", .{ | |
| 134 | dest_dir_path, entry.path, @errorName(err), | |
| 135 | }); | |
| 133 | fatal("unable to load '{s}/include/{s}': {t}", .{ dest_dir_path, entry.path, err }); | |
| 136 | 134 | }; |
| 137 | 135 | |
| 138 | 136 | const whitespace = " \r\n\t"; |
| ... | ... | @@ -140,14 +138,8 @@ pub fn main() !void { |
| 140 | 138 | const glibc_include_trimmed = mem.trim(u8, glibc_include_contents, whitespace); |
| 141 | 139 | if (mem.eql(u8, generic_glibc_trimmed, glibc_include_trimmed)) { |
| 142 | 140 | log.warn("same contents: '{s}/include/{s}' and '{s}/include/{s}'", .{ |
| 143 | generic_glibc_path, entry.path, | |
| 144 | dest_dir_path, entry.path, | |
| 141 | generic_glibc_path, entry.path, dest_dir_path, entry.path, | |
| 145 | 142 | }); |
| 146 | 143 | } |
| 147 | 144 | } |
| 148 | 145 | } |
| 149 | ||
| 150 | fn 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 | 1 | const std = @import("std"); |
| 2 | const Io = std.Io; | |
| 3 | const Dir = std.Io.Dir; | |
| 2 | 4 | |
| 3 | 5 | pub fn main() !void { |
| 4 | 6 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 5 | 7 | defer arena_instance.deinit(); |
| 6 | 8 | const arena = arena_instance.allocator(); |
| 7 | 9 | |
| 10 | var threaded: Io.Threaded = .init(arena, .{}); | |
| 11 | defer threaded.deinit(); | |
| 12 | const io = threaded.io(); | |
| 13 | ||
| 8 | 14 | const args = try std.process.argsAlloc(arena); |
| 9 | 15 | const zig_src_lib_path = args[1]; |
| 10 | 16 | const mingw_src_path = args[2]; |
| 11 | 17 | |
| 12 | const dest_mingw_crt_path = try std.fs.path.join(arena, &.{ | |
| 18 | const dest_mingw_crt_path = try Dir.path.join(arena, &.{ | |
| 13 | 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 | 22 | mingw_src_path, "mingw-w64-crt", |
| 17 | 23 | }); |
| 18 | 24 | |
| 19 | 25 | // Update only the set of existing files we have already chosen to include |
| 20 | 26 | // in zig's installation. |
| 21 | 27 | |
| 22 | var dest_crt_dir = std.fs.cwd().openDir(dest_mingw_crt_path, .{ .iterate = true }) catch |err| { | |
| 23 | std.log.err("unable to open directory '{s}': {s}", .{ dest_mingw_crt_path, @errorName(err) }); | |
| 28 | var dest_crt_dir = Dir.cwd().openDir(io, dest_mingw_crt_path, .{ .iterate = true }) catch |err| { | |
| 29 | std.log.err("unable to open directory '{s}': {t}", .{ dest_mingw_crt_path, err }); | |
| 24 | 30 | std.process.exit(1); |
| 25 | 31 | }; |
| 26 | defer dest_crt_dir.close(); | |
| 32 | defer dest_crt_dir.close(io); | |
| 27 | 33 | |
| 28 | var src_crt_dir = std.fs.cwd().openDir(src_mingw_crt_path, .{ .iterate = true }) catch |err| { | |
| 29 | std.log.err("unable to open directory '{s}': {s}", .{ src_mingw_crt_path, @errorName(err) }); | |
| 34 | var src_crt_dir = Dir.cwd().openDir(io, src_mingw_crt_path, .{ .iterate = true }) catch |err| { | |
| 35 | std.log.err("unable to open directory '{s}': {t}", .{ src_mingw_crt_path, err }); | |
| 30 | 36 | std.process.exit(1); |
| 31 | 37 | }; |
| 32 | defer src_crt_dir.close(); | |
| 38 | defer src_crt_dir.close(io); | |
| 33 | 39 | |
| 34 | 40 | { |
| 35 | 41 | var walker = try dest_crt_dir.walk(arena); |
| ... | ... | @@ -37,10 +43,10 @@ pub fn main() !void { |
| 37 | 43 | |
| 38 | 44 | var fail = false; |
| 39 | 45 | |
| 40 | while (try walker.next()) |entry| { | |
| 46 | while (try walker.next(io)) |entry| { | |
| 41 | 47 | if (entry.kind != .file) continue; |
| 42 | 48 | |
| 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 | 50 | error.FileNotFound => { |
| 45 | 51 | const keep = for (kept_crt_files) |item| { |
| 46 | 52 | if (std.mem.eql(u8, entry.path, item)) break true; |
| ... | ... | @@ -49,11 +55,11 @@ pub fn main() !void { |
| 49 | 55 | |
| 50 | 56 | if (!keep) { |
| 51 | 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 | 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 | 63 | fail = true; |
| 58 | 64 | }, |
| 59 | 65 | }; |
| ... | ... | @@ -63,24 +69,24 @@ pub fn main() !void { |
| 63 | 69 | } |
| 64 | 70 | |
| 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 | 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 | 76 | mingw_src_path, "mingw-w64-libraries", "winpthreads", "src", |
| 71 | 77 | }); |
| 72 | 78 | |
| 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 | 80 | std.log.err("unable to open directory '{s}': {s}", .{ dest_mingw_winpthreads_path, @errorName(err) }); |
| 75 | 81 | std.process.exit(1); |
| 76 | 82 | }; |
| 77 | defer dest_winpthreads_dir.close(); | |
| 83 | defer dest_winpthreads_dir.close(io); | |
| 78 | 84 | |
| 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 | 86 | std.log.err("unable to open directory '{s}': {s}", .{ src_mingw_libraries_winpthreads_src_path, @errorName(err) }); |
| 81 | 87 | std.process.exit(1); |
| 82 | 88 | }; |
| 83 | defer src_winpthreads_dir.close(); | |
| 89 | defer src_winpthreads_dir.close(io); | |
| 84 | 90 | |
| 85 | 91 | { |
| 86 | 92 | var walker = try dest_winpthreads_dir.walk(arena); |
| ... | ... | @@ -88,16 +94,16 @@ pub fn main() !void { |
| 88 | 94 | |
| 89 | 95 | var fail = false; |
| 90 | 96 | |
| 91 | while (try walker.next()) |entry| { | |
| 97 | while (try walker.next(io)) |entry| { | |
| 92 | 98 | if (entry.kind != .file) continue; |
| 93 | 99 | |
| 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 | 101 | error.FileNotFound => { |
| 96 | 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 | 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 | 107 | fail = true; |
| 102 | 108 | }, |
| 103 | 109 | }; |
| ... | ... | @@ -114,17 +120,17 @@ pub fn main() !void { |
| 114 | 120 | |
| 115 | 121 | var fail = false; |
| 116 | 122 | |
| 117 | while (try walker.next()) |entry| { | |
| 123 | while (try walker.next(io)) |entry| { | |
| 118 | 124 | switch (entry.kind) { |
| 119 | 125 | .directory => { |
| 120 | 126 | switch (entry.depth()) { |
| 121 | 127 | 1 => if (def_dirs.has(entry.basename)) { |
| 122 | try walker.enter(entry); | |
| 128 | try walker.enter(io, entry); | |
| 123 | 129 | continue; |
| 124 | 130 | }, |
| 125 | 131 | else => { |
| 126 | 132 | // The top-level directory was already validated |
| 127 | try walker.enter(entry); | |
| 133 | try walker.enter(io, entry); | |
| 128 | 134 | continue; |
| 129 | 135 | }, |
| 130 | 136 | } |
| ... | ... | @@ -151,20 +157,20 @@ pub fn main() !void { |
| 151 | 157 | if (std.mem.endsWith(u8, entry.basename, "_onecore.def")) |
| 152 | 158 | continue; |
| 153 | 159 | |
| 154 | src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, .{}) catch |err| { | |
| 155 | std.log.err("unable to copy {s}: {s}", .{ entry.path, @errorName(err) }); | |
| 160 | src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, io, .{}) catch |err| { | |
| 161 | std.log.err("unable to copy {s}: {t}", .{ entry.path, err }); | |
| 156 | 162 | fail = true; |
| 157 | 163 | }; |
| 158 | 164 | } |
| 159 | 165 | if (fail) std.process.exit(1); |
| 160 | 166 | } |
| 161 | 167 | |
| 162 | return std.process.cleanExit(); | |
| 168 | return std.process.cleanExit(io); | |
| 163 | 169 | } |
| 164 | 170 | |
| 165 | 171 | const kept_crt_files = [_][]const u8{ |
| 166 | 172 | "COPYING", |
| 167 | "include" ++ std.fs.path.sep_str ++ "config.h", | |
| 173 | "include" ++ Dir.path.sep_str ++ "config.h", | |
| 168 | 174 | }; |
| 169 | 175 | |
| 170 | 176 | const def_exts = [_][]const u8{ |
tools/update_netbsd_libc.zig+15-14| ... | ... | @@ -5,6 +5,7 @@ |
| 5 | 5 | //! `zig run tools/update_netbsd_libc.zig -- ~/Downloads/netbsd-src .` |
| 6 | 6 | |
| 7 | 7 | const std = @import("std"); |
| 8 | const Io = std.Io; | |
| 8 | 9 | |
| 9 | 10 | const exempt_files = [_][]const u8{ |
| 10 | 11 | // This file is maintained by a separate project and does not come from NetBSD. |
| ... | ... | @@ -16,29 +17,31 @@ pub fn main() !void { |
| 16 | 17 | defer arena_instance.deinit(); |
| 17 | 18 | const arena = arena_instance.allocator(); |
| 18 | 19 | |
| 20 | var threaded: Io.Threaded = .init(arena, .{}); | |
| 21 | defer threaded.deinit(); | |
| 22 | const io = threaded.io(); | |
| 23 | ||
| 19 | 24 | const args = try std.process.argsAlloc(arena); |
| 20 | 25 | const netbsd_src_path = args[1]; |
| 21 | 26 | const zig_src_path = args[2]; |
| 22 | 27 | |
| 23 | 28 | const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/netbsd", .{zig_src_path}); |
| 24 | 29 | |
| 25 | var dest_dir = std.fs.cwd().openDir(dest_dir_path, .{ .iterate = true }) catch |err| { | |
| 26 | std.log.err("unable to open destination directory '{s}': {s}", .{ | |
| 27 | dest_dir_path, @errorName(err), | |
| 28 | }); | |
| 30 | var dest_dir = Io.Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| { | |
| 31 | std.log.err("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err }); | |
| 29 | 32 | std.process.exit(1); |
| 30 | 33 | }; |
| 31 | defer dest_dir.close(); | |
| 34 | defer dest_dir.close(io); | |
| 32 | 35 | |
| 33 | var netbsd_src_dir = try std.fs.cwd().openDir(netbsd_src_path, .{}); | |
| 34 | defer netbsd_src_dir.close(); | |
| 36 | var netbsd_src_dir = try Io.Dir.cwd().openDir(io, netbsd_src_path, .{}); | |
| 37 | defer netbsd_src_dir.close(io); | |
| 35 | 38 | |
| 36 | 39 | // Copy updated files from upstream. |
| 37 | 40 | { |
| 38 | 41 | var walker = try dest_dir.walk(arena); |
| 39 | 42 | defer walker.deinit(); |
| 40 | 43 | |
| 41 | walk: while (try walker.next()) |entry| { | |
| 44 | walk: while (try walker.next(io)) |entry| { | |
| 42 | 45 | if (entry.kind != .file) continue; |
| 43 | 46 | if (std.mem.startsWith(u8, entry.basename, ".")) continue; |
| 44 | 47 | for (exempt_files) |p| { |
| ... | ... | @@ -50,14 +53,12 @@ pub fn main() !void { |
| 50 | 53 | netbsd_src_path, entry.path, |
| 51 | 54 | }); |
| 52 | 55 | |
| 53 | netbsd_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| { | |
| 54 | std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{ | |
| 55 | netbsd_src_path, entry.path, | |
| 56 | dest_dir_path, entry.path, | |
| 57 | @errorName(err), | |
| 56 | netbsd_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| { | |
| 57 | std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{ | |
| 58 | netbsd_src_path, entry.path, dest_dir_path, entry.path, err, | |
| 58 | 59 | }); |
| 59 | 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 | } |