From 89bda60d28de5a44f445eaf056db45735176c1e3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 25 Nov 2025 06:36:49 -0800 Subject: [PATCH 001/195] std.Io.Threaded: implement makePath --- lib/std/Io.zig | 2 +- lib/std/Io/Dir.zig | 47 ++++++++++-------------------------- lib/std/Io/Threaded.zig | 53 ++++++++++++++++++++++++++--------------- 3 files changed, 47 insertions(+), 55 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index dbf544c6d538b95c765a569f41416ff406c7c578..2453d102173ee15bc8f9683a0797b91753ed8044 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -663,7 +663,7 @@ pub const VTable = struct { futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void, - dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void, + dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus, dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat, dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 3429fa6fdfae940c7b198c3dd6a4994b8dd3cf1d..91164341915dc8ffca4f811bb8e7dfd03b13667d 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -206,7 +206,7 @@ pub fn updateFile( } if (std.fs.path.dirname(dest_path)) |dirname| { - try dest_dir.makePath(io, dirname); + try dest_dir.makePathMode(io, dirname, default_mode); } var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available. @@ -287,13 +287,17 @@ pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8) MakeError!void { pub const MakePathError = MakeError || StatPathError; -/// Calls makeDir iteratively to make an entire path, creating any parent -/// directories that do not exist. +/// Same as `makePathMode` but passes `default_mode`. +pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void { + _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, default_mode); +} + +/// Creates parent directories as necessary to ensure `sub_path` exists as a directory. /// /// Returns success if the path already exists and is a directory. /// -/// This function is not atomic, and if it returns an error, the file system -/// may have been modified regardless. +/// This function may not be atomic. If it returns an error, the file system +/// may have been modified. /// /// Fails on an empty path with `error.BadPathName` as that is not a path that /// can be created. @@ -309,8 +313,8 @@ pub const MakePathError = MakeError || StatPathError; /// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`, /// meaning a `sub_path` like "first/../second" will create both a `./first` /// and a `./second` directory. -pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void { - _ = try makePathStatus(dir, io, sub_path); +pub fn makePathMode(dir: Dir, io: Io, sub_path: []const u8, mode: Mode) MakePathError!void { + _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, mode); } pub const MakePathStatus = enum { existed, created }; @@ -318,34 +322,7 @@ pub const MakePathStatus = enum { existed, created }; /// Same as `makePath` except returns whether the path already existed or was /// successfully created. pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus { - var it = std.fs.path.componentIterator(sub_path); - var status: MakePathStatus = .existed; - var component = it.last() orelse return error.BadPathName; - while (true) { - if (makeDir(dir, io, component.path)) { - status = .created; - } else |err| switch (err) { - error.PathAlreadyExists => { - // stat the file and return an error if it's not a directory - // this is important because otherwise a dangling symlink - // could cause an infinite loop - check_dir: { - // workaround for windows, see https://github.com/ziglang/zig/issues/16738 - const fstat = statPath(dir, io, component.path, .{}) catch |stat_err| switch (stat_err) { - error.IsDir => break :check_dir, - else => |e| return e, - }; - if (fstat.kind != .directory) return error.NotDir; - } - }, - error.FileNotFound => |e| { - component = it.previous() orelse return e; - continue; - }, - else => |e| return e, - } - component = it.next() orelse return status; - } + return io.vtable.dirMakePath(io.userdata, dir, sub_path, default_mode); } pub const MakeOpenPathError = MakeError || OpenError || StatPathError; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 2a38fdf543516cc1549010d0e824073d62fd51da..91f990498c9df1cd355bd0c6252ce6096df67f09 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1455,27 +1455,42 @@ fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode windows.CloseHandle(sub_dir_handle); } -const dirMakePath = switch (native_os) { - .windows => dirMakePathWindows, - else => dirMakePathPosix, -}; - -fn dirMakePathPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { +fn dirMakePath( + userdata: ?*anyopaque, + dir: Io.Dir, + sub_path: []const u8, + mode: Io.Dir.Mode, +) Io.Dir.MakePathError!Io.Dir.MakePathStatus { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - _ = dir; - _ = sub_path; - _ = mode; - @panic("TODO implement dirMakePathPosix"); -} -fn dirMakePathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - _ = dir; - _ = sub_path; - _ = mode; - @panic("TODO implement dirMakePathWindows"); + var it = std.fs.path.componentIterator(sub_path); + var status: Io.Dir.MakePathStatus = .existed; + var component = it.last() orelse return error.BadPathName; + while (true) { + if (dirMake(t, dir, component.path, mode)) |_| { + status = .created; + } else |err| switch (err) { + error.PathAlreadyExists => { + // stat the file and return an error if it's not a directory + // this is important because otherwise a dangling symlink + // could cause an infinite loop + check_dir: { + // workaround for windows, see https://github.com/ziglang/zig/issues/16738 + const fstat = dirStatPath(t, dir, component.path, .{}) catch |stat_err| switch (stat_err) { + error.IsDir => break :check_dir, + else => |e| return e, + }; + if (fstat.kind != .directory) return error.NotDir; + } + }, + error.FileNotFound => |e| { + component = it.previous() orelse return e; + continue; + }, + else => |e| return e, + } + component = it.next() orelse return status; + } } const dirMakeOpenPath = switch (native_os) { -- 2.54.0 From 81214278ca14b832e53876feb70fa3c072c14dd6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 25 Nov 2025 06:46:53 -0800 Subject: [PATCH 002/195] std.Io.Threaded: implement dirStat --- lib/std/Io/Threaded.zig | 5 ++--- lib/std/fs/Dir.zig | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 91f990498c9df1cd355bd0c6252ce6096df67f09..088a1510b8fa4426c61d3f1a1bde8a808982bfc1 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1650,9 +1650,8 @@ fn dirMakeOpenPathWasi( fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - _ = dir; - @panic("TODO implement dirStat"); + const file: Io.File = .{ .handle = dir.handle }; + return fileStat(t, file); } const dirStatPath = switch (native_os) { diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index ea9c6408bf2e09d18c8eeb4b86cfa9121ae27f73..ff75081b9e49e303d1c5de69f3eab1d166471b2c 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -2008,8 +2008,9 @@ pub const StatError = File.StatError; /// Deprecated in favor of `Io.Dir.stat`. pub fn stat(self: Dir) StatError!Stat { - const file: File = .{ .handle = self.fd }; - return file.stat(); + var threaded: Io.Threaded = .init_single_threaded; + const io = threaded.ioBasic(); + return Io.Dir.stat(.{ .handle = self.fd }, io); } pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError; -- 2.54.0 From d1d2c37af26902f953b2b72335b326c4b01e3bb2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 3 Dec 2025 20:37:43 -0800 Subject: [PATCH 003/195] std: all Dir functions moved to std.Io --- CMakeLists.txt | 2 - build.zig | 35 +- lib/compiler/build_runner.zig | 14 +- lib/std/Build.zig | 7 +- lib/std/Build/Cache.zig | 83 +- lib/std/Build/Cache/Directory.zig | 10 +- lib/std/Build/Cache/Path.zig | 24 +- lib/std/Build/Step.zig | 10 +- lib/std/Build/Step/Compile.zig | 35 +- lib/std/Build/Step/InstallDir.zig | 7 +- lib/std/Build/Step/Options.zig | 42 +- lib/std/Io.zig | 27 +- lib/std/Io/Dir.zig | 1204 ++++++++++++++++- lib/std/Io/File.zig | 190 ++- lib/std/Io/Threaded.zig | 1451 +++++++++++++++++++- lib/std/debug.zig | 80 +- lib/std/fs.zig | 69 +- lib/std/fs/AtomicFile.zig | 94 -- lib/std/fs/Dir.zig | 2066 ----------------------------- lib/std/fs/File.zig | 99 +- lib/std/fs/test.zig | 15 - lib/std/os.zig | 130 -- lib/std/posix.zig | 1014 -------------- lib/std/zig/system.zig | 6 +- 24 files changed, 3064 insertions(+), 3650 deletions(-) delete mode 100644 lib/std/fs/AtomicFile.zig delete mode 100644 lib/std/fs/Dir.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index ce4bb1eef733e406b913a180894e6f622e60851e..2a5db03471c4076519fde2d9c1b2abc019dae3c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -436,8 +436,6 @@ set(ZIG_STAGE2_SOURCES lib/std/fmt.zig lib/std/fmt/parse_float.zig lib/std/fs.zig - lib/std/fs/AtomicFile.zig - lib/std/fs/Dir.zig lib/std/fs/File.zig lib/std/fs/get_app_data_dir.zig lib/std/fs/path.zig diff --git a/build.zig b/build.zig index b7a8e282ba9f8ea0b77f2dd4b0695fb1cb9ac8b7..ade482592728c13caed619d6338593a792d341d2 100644 --- a/build.zig +++ b/build.zig @@ -1,18 +1,20 @@ const std = @import("std"); const builtin = std.builtin; -const tests = @import("test/tests.zig"); const BufMap = std.BufMap; const mem = std.mem; -const io = std.io; const fs = std.fs; const InstallDirectoryOptions = std.Build.InstallDirectoryOptions; const assert = std.debug.assert; +const Io = std.Io; + +const tests = @import("test/tests.zig"); const DevEnv = @import("src/dev.zig").Env; -const ValueInterpretMode = enum { direct, by_name }; const zig_version: std.SemanticVersion = .{ .major = 0, .minor = 16, .patch = 0 }; const stack_size = 46 * 1024 * 1024; +const ValueInterpretMode = enum { direct, by_name }; + pub fn build(b: *std.Build) !void { const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false; const target = b.standardTargetOptions(.{ @@ -306,8 +308,10 @@ pub fn build(b: *std.Build) !void { if (enable_llvm) { const cmake_cfg = if (static_llvm) null else blk: { + const io = b.graph.io; + const cwd: Io.Dir = .cwd(); if (findConfigH(b, config_h_path_option)) |config_h_path| { - const file_contents = fs.cwd().readFileAlloc(config_h_path, b.allocator, .limited(max_config_h_bytes)) catch unreachable; + const file_contents = cwd.readFileAlloc(io, config_h_path, b.allocator, .limited(max_config_h_bytes)) catch unreachable; break :blk parseConfigH(b, file_contents); } else { std.log.warn("config.h could not be located automatically. Consider providing it explicitly via \"-Dconfig_h\"", .{}); @@ -1153,10 +1157,13 @@ const CMakeConfig = struct { const max_config_h_bytes = 1 * 1024 * 1024; fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 { + const io = b.graph.io; + const cwd: Io.Dir = .cwd(); + if (config_h_path_option) |path| { - var config_h_or_err = fs.cwd().openFile(path, .{}); + var config_h_or_err = cwd.openFile(io, path, .{}); if (config_h_or_err) |*file| { - file.close(); + file.close(io); return path; } else |_| { std.log.err("Could not open provided config.h: \"{s}\"", .{path}); @@ -1166,13 +1173,13 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 { var check_dir = fs.path.dirname(b.graph.zig_exe).?; while (true) { - var dir = fs.cwd().openDir(check_dir, .{}) catch unreachable; - defer dir.close(); + var dir = cwd.openDir(io, check_dir, .{}) catch unreachable; + defer dir.close(io); // Check if config.h is present in dir - var config_h_or_err = dir.openFile("config.h", .{}); + var config_h_or_err = dir.openFile(io, "config.h", .{}); if (config_h_or_err) |*file| { - file.close(); + file.close(io); return fs.path.join( b.allocator, &[_][]const u8{ check_dir, "config.h" }, @@ -1183,9 +1190,9 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 { } // Check if we reached the source root by looking for .git, and bail if so - var git_dir_or_err = dir.openDir(".git", .{}); + var git_dir_or_err = dir.openDir(io, ".git", .{}); if (git_dir_or_err) |*git_dir| { - git_dir.close(); + git_dir.close(io); return null; } else |_| {} @@ -1581,6 +1588,8 @@ const llvm_libs_xtensa = [_][]const u8{ }; fn generateLangRef(b: *std.Build) std.Build.LazyPath { + const io = b.graph.io; + const doctest_exe = b.addExecutable(.{ .name = "doctest", .root_module = b.createModule(.{ @@ -1590,7 +1599,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath { }), }); - var dir = b.build_root.handle.openDir("doc/langref", .{ .iterate = true }) catch |err| { + var dir = b.build_root.handle.openDir(io, "doc/langref", .{ .iterate = true }) catch |err| { std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{ b.build_root, @errorName(err), }); diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index eed48e79adfa839ebdcd395c297ae1f47761c6a1..2c8d71e3e2b83d487a07d0ad72301a6a0c503a5d 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -53,24 +53,26 @@ pub fn main() !void { const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{}); const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{}); + const cwd: Io.Dir = .cwd(); + const zig_lib_directory: std.Build.Cache.Directory = .{ .path = zig_lib_dir, - .handle = try std.fs.cwd().openDir(zig_lib_dir, .{}), + .handle = try cwd.openDir(io, zig_lib_dir, .{}), }; const build_root_directory: std.Build.Cache.Directory = .{ .path = build_root, - .handle = try std.fs.cwd().openDir(build_root, .{}), + .handle = try cwd.openDir(io, build_root, .{}), }; const local_cache_directory: std.Build.Cache.Directory = .{ .path = cache_root, - .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}), + .handle = try cwd.makeOpenPath(io, cache_root, .{}), }; const global_cache_directory: std.Build.Cache.Directory = .{ .path = global_cache_root, - .handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}), + .handle = try cwd.makeOpenPath(io, global_cache_root, .{}), }; var graph: std.Build.Graph = .{ @@ -79,7 +81,7 @@ pub fn main() !void { .cache = .{ .io = io, .gpa = arena, - .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}), + .manifest_dir = try local_cache_directory.handle.makeOpenPath(io, "h", .{}), }, .zig_exe = zig_exe, .env_map = try process.getEnvMap(arena), @@ -92,7 +94,7 @@ pub fn main() !void { .time_report = false, }; - graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() }); + graph.cache.addPrefix(.{ .path = null, .handle = cwd }); graph.cache.addPrefix(build_root_directory); graph.cache.addPrefix(local_cache_directory); graph.cache.addPrefix(global_cache_directory); diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 50a280493891a9c823d666b5257420cfed9e3f6b..fcd94ce1345cd629d0d240952bd65a668e02cee4 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1700,9 +1700,8 @@ pub fn addCheckFile( } pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Dir.StatFileError)!void { - if (b.verbose) { - log.info("truncate {s}", .{dest_path}); - } + const io = b.graph.io; + if (b.verbose) log.info("truncate {s}", .{dest_path}); const cwd = fs.cwd(); var src_file = cwd.createFile(dest_path, .{}) catch |err| switch (err) { error.FileNotFound => blk: { @@ -1713,7 +1712,7 @@ pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Di }, else => |e| return e, }; - src_file.close(); + src_file.close(io); } /// References a file or directory relative to the source root. diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 5e8412cfcf0a24320cd8b48dd38f2a8b96df6dbc..b06547dc537bea3f485c14fe5303b9c49e797298 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -8,7 +8,6 @@ const builtin = @import("builtin"); const std = @import("std"); const Io = std.Io; const crypto = std.crypto; -const fs = std.fs; const assert = std.debug.assert; const testing = std.testing; const mem = std.mem; @@ -18,7 +17,7 @@ const log = std.log.scoped(.cache); gpa: Allocator, io: Io, -manifest_dir: fs.Dir, +manifest_dir: Io.Dir, hash: HashHelper = .{}, /// This value is accessed from multiple threads, protected by mutex. recent_problematic_timestamp: Io.Timestamp = .zero, @@ -71,7 +70,7 @@ const PrefixedPath = struct { fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath { const gpa = cache.gpa; - const resolved_path = try fs.path.resolve(gpa, &.{file_path}); + const resolved_path = try std.fs.path.resolve(gpa, &.{file_path}); errdefer gpa.free(resolved_path); return findPrefixResolved(cache, resolved_path); } @@ -102,9 +101,9 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath { } fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 { - const relative = try fs.path.relative(allocator, prefix, path); + const relative = try std.fs.path.relative(allocator, prefix, path); errdefer allocator.free(relative); - var component_iterator = fs.path.NativeComponentIterator.init(relative); + var component_iterator = std.fs.path.NativeComponentIterator.init(relative); if (component_iterator.root() != null) { return error.NotASubPath; } @@ -145,17 +144,17 @@ pub const File = struct { max_file_size: ?usize, /// Populated if the user calls `addOpenedFile`. /// The handle is not owned here. - handle: ?fs.File, + handle: ?Io.File, stat: Stat, bin_digest: BinDigest, contents: ?[]const u8, pub const Stat = struct { - inode: fs.File.INode, + inode: Io.File.INode, size: u64, mtime: Io.Timestamp, - pub fn fromFs(fs_stat: fs.File.Stat) Stat { + pub fn fromFs(fs_stat: Io.File.Stat) Stat { return .{ .inode = fs_stat.inode, .size = fs_stat.size, @@ -178,7 +177,7 @@ pub const File = struct { file.max_file_size = if (file.max_file_size) |old| @max(old, new) else new; } - pub fn updateHandle(file: *File, new_handle: ?fs.File) void { + pub fn updateHandle(file: *File, new_handle: ?Io.File) void { const handle = new_handle orelse return; file.handle = handle; } @@ -293,16 +292,16 @@ pub fn binToHex(bin_digest: BinDigest) HexDigest { } pub const Lock = struct { - manifest_file: fs.File, + manifest_file: Io.File, - pub fn release(lock: *Lock) void { + pub fn release(lock: *Lock, io: Io) void { if (builtin.os.tag == .windows) { // Windows does not guarantee that locks are immediately unlocked when // the file handle is closed. See LockFileEx documentation. lock.manifest_file.unlock(); } - lock.manifest_file.close(); + lock.manifest_file.close(io); lock.* = undefined; } }; @@ -311,7 +310,7 @@ pub const Manifest = struct { cache: *Cache, /// Current state for incremental hashing. hash: HashHelper, - manifest_file: ?fs.File, + manifest_file: ?Io.File, manifest_dirty: bool, /// Set this flag to true before calling hit() in order to indicate that /// upon a cache hit, the code using the cache will not modify the files @@ -332,9 +331,9 @@ pub const Manifest = struct { pub const Diagnostic = union(enum) { none, - manifest_create: fs.File.OpenError, - manifest_read: fs.File.ReadError, - manifest_lock: fs.File.LockError, + manifest_create: Io.File.OpenError, + manifest_read: Io.File.Reader.Error, + manifest_lock: Io.File.LockError, file_open: FileOp, file_stat: FileOp, file_read: FileOp, @@ -393,10 +392,10 @@ pub const Manifest = struct { } /// Same as `addFilePath` except the file has already been opened. - pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?fs.File, max_file_size: ?usize) !usize { + pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?Io.File, max_file_size: ?usize) !usize { const gpa = m.cache.gpa; try m.files.ensureUnusedCapacity(gpa, 1); - const resolved_path = try fs.path.resolve(gpa, &.{ + const resolved_path = try std.fs.path.resolve(gpa, &.{ path.root_dir.path orelse ".", path.subPathOrDot(), }); @@ -417,7 +416,7 @@ pub const Manifest = struct { return addFileInner(self, prefixed_path, null, max_file_size); } - fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?fs.File, max_file_size: ?usize) usize { + fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?Io.File, max_file_size: ?usize) usize { const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{}); if (gop.found_existing) { self.cache.gpa.free(prefixed_path.sub_path); @@ -460,7 +459,7 @@ pub const Manifest = struct { } } - pub fn addDepFile(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void { + pub fn addDepFile(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void { assert(self.manifest_file == null); return self.addDepFileMaybePost(dir, dep_file_sub_path); } @@ -702,7 +701,7 @@ pub const Manifest = struct { const file_path = iter.rest(); const stat_size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat; - const stat_inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat; + const stat_inode = fmt.parseInt(Io.File.INode, inode, 10) catch return error.InvalidFormat; const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; const file_bin_digest = b: { if (digest_str.len != hex_digest_len) return error.InvalidFormat; @@ -772,7 +771,7 @@ pub const Manifest = struct { return error.CacheCheckFailed; }, }; - defer this_file.close(); + defer this_file.close(io); const actual_stat = this_file.stat() catch |err| { self.diagnostic = .{ .file_stat = .{ @@ -879,7 +878,7 @@ pub const Manifest = struct { error.Canceled => return error.Canceled, else => return true, }; - defer file.close(); + defer file.close(io); // Save locally and also save globally (we still hold the global lock). const stat = file.stat() catch |err| switch (err) { @@ -894,18 +893,20 @@ pub const Manifest = struct { } fn populateFileHash(self: *Manifest, ch_file: *File) !void { + const io = self.cache.io; + if (ch_file.handle) |handle| { return populateFileHashHandle(self, ch_file, handle); } else { const pp = ch_file.prefixed_path; const dir = self.cache.prefixes()[pp.prefix].handle; const handle = try dir.openFile(pp.sub_path, .{}); - defer handle.close(); + defer handle.close(io); return populateFileHashHandle(self, ch_file, handle); } } - fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: fs.File) !void { + fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: Io.File) !void { const actual_stat = try handle.stat(); ch_file.stat = .{ .size = actual_stat.size, @@ -1064,12 +1065,12 @@ pub const Manifest = struct { self.hash.hasher.update(&new_file.bin_digest); } - pub fn addDepFilePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void { + pub fn addDepFilePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void { assert(self.manifest_file != null); return self.addDepFileMaybePost(dir, dep_file_sub_path); } - fn addDepFileMaybePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void { + fn addDepFileMaybePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void { const gpa = self.cache.gpa; const dep_file_contents = try dir.readFileAlloc(dep_file_sub_path, gpa, .limited(manifest_file_size_max)); defer gpa.free(dep_file_contents); @@ -1148,7 +1149,7 @@ pub const Manifest = struct { } } - fn writeDirtyManifestToStream(self: *Manifest, fw: *fs.File.Writer) !void { + fn writeDirtyManifestToStream(self: *Manifest, fw: *Io.File.Writer) !void { try fw.interface.writeAll(manifest_header ++ "\n"); for (self.files.keys()) |file| { try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{ @@ -1214,13 +1215,15 @@ pub const Manifest = struct { /// `Manifest.hit` must be called first. /// Don't forget to call `writeManifest` before this! pub fn deinit(self: *Manifest) void { + const io = self.cache.io; + if (self.manifest_file) |file| { if (builtin.os.tag == .windows) { // See Lock.release for why this is required on Windows file.unlock(); } - file.close(); + file.close(io); } for (self.files.keys()) |*file| { file.deinit(self.cache.gpa); @@ -1281,7 +1284,7 @@ pub const Manifest = struct { /// On operating systems that support symlinks, does a readlink. On other operating systems, /// uses the file contents. Windows supports symlinks but only with elevated privileges, so /// it is treated as not supporting symlinks. -pub fn readSmallFile(dir: fs.Dir, sub_path: []const u8, buffer: []u8) ![]u8 { +pub fn readSmallFile(dir: Io.Dir, sub_path: []const u8, buffer: []u8) ![]u8 { if (builtin.os.tag == .windows) { return dir.readFile(sub_path, buffer); } else { @@ -1293,7 +1296,7 @@ pub fn readSmallFile(dir: fs.Dir, sub_path: []const u8, buffer: []u8) ![]u8 { /// uses the file contents. Windows supports symlinks but only with elevated privileges, so /// it is treated as not supporting symlinks. /// `data` must be a valid UTF-8 encoded file path and 255 bytes or fewer. -pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void { +pub fn writeSmallFile(dir: Io.Dir, sub_path: []const u8, data: []const u8) !void { assert(data.len <= 255); if (builtin.os.tag == .windows) { return dir.writeFile(.{ .sub_path = sub_path, .data = data }); @@ -1302,7 +1305,7 @@ pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void } } -fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadError!void { +fn hashFile(file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.PReadError!void { var buf: [1024]u8 = undefined; var hasher = hasher_init; var off: u64 = 0; @@ -1316,7 +1319,7 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadErro } // Create/Write a file, close it, then grab its stat.mtime timestamp. -fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp { +fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp { const test_out_file = "test-filetimestamp.tmp"; var file = try dir.createFile(test_out_file, .{ @@ -1324,7 +1327,7 @@ fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp { .truncate = true, }); defer { - file.close(); + file.close(io); dir.deleteFile(test_out_file) catch {}; } @@ -1343,8 +1346,8 @@ test "cache file and then recall it" { try tmp.dir.writeFile(.{ .sub_path = temp_file, .data = "Hello, world!\n" }); // Wait for file timestamps to tick - const initial_time = try testGetCurrentFileTimestamp(tmp.dir); - while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) { + const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); + while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) { try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); } @@ -1358,7 +1361,7 @@ test "cache file and then recall it" { .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); - defer cache.manifest_dir.close(); + defer cache.manifest_dir.close(io); { var ch = cache.obtain(); @@ -1424,7 +1427,7 @@ test "check that changing a file makes cache fail" { .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); - defer cache.manifest_dir.close(); + defer cache.manifest_dir.close(io); { var ch = cache.obtain(); @@ -1484,7 +1487,7 @@ test "no file inputs" { .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); - defer cache.manifest_dir.close(); + defer cache.manifest_dir.close(io); { var man = cache.obtain(); @@ -1543,7 +1546,7 @@ test "Manifest with files added after initial hash work" { .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); - defer cache.manifest_dir.close(); + defer cache.manifest_dir.close(io); { var ch = cache.obtain(); diff --git a/lib/std/Build/Cache/Directory.zig b/lib/std/Build/Cache/Directory.zig index a105a91ed6ecf292edb1482fd49fa36562c61eaf..305ef253614d49280b21243c7c582497d7d99875 100644 --- a/lib/std/Build/Cache/Directory.zig +++ b/lib/std/Build/Cache/Directory.zig @@ -1,7 +1,9 @@ const Directory = @This(); + const std = @import("../../std.zig"); -const assert = std.debug.assert; +const Io = std.Io; const fs = std.fs; +const assert = std.debug.assert; const fmt = std.fmt; const Allocator = std.mem.Allocator; @@ -9,7 +11,7 @@ const Allocator = std.mem.Allocator; /// directly, but it is needed when passing the directory to a child process. /// `null` means cwd. path: ?[]const u8, -handle: fs.Dir, +handle: Io.Dir, pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory { return .{ @@ -21,7 +23,7 @@ pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory { pub fn cwd() Directory { return .{ .path = null, - .handle = fs.cwd(), + .handle = .cwd(), }; } @@ -64,5 +66,5 @@ pub fn format(self: Directory, writer: *std.Io.Writer) std.Io.Writer.Error!void } pub fn eql(self: Directory, other: Directory) bool { - return self.handle.fd == other.handle.fd; + return self.handle.handle == other.handle.handle; } diff --git a/lib/std/Build/Cache/Path.zig b/lib/std/Build/Cache/Path.zig index 92290cfdf4d380fde3145d3b9602db28b2c5592c..f6f76c1e8f6bf6eb1451207acb7ce3358ea2115a 100644 --- a/lib/std/Build/Cache/Path.zig +++ b/lib/std/Build/Cache/Path.zig @@ -2,8 +2,8 @@ const Path = @This(); const std = @import("../../std.zig"); const Io = std.Io; -const assert = std.debug.assert; const fs = std.fs; +const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; @@ -62,8 +62,8 @@ pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Erro pub fn openFile( p: Path, sub_path: []const u8, - flags: fs.File.OpenFlags, -) !fs.File { + flags: Io.File.OpenFlags, +) !Io.File { var buf: [fs.max_path_bytes]u8 = undefined; const joined_path = if (p.sub_path.len == 0) sub_path else p: { break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ @@ -76,8 +76,8 @@ pub fn openFile( pub fn openDir( p: Path, sub_path: []const u8, - args: fs.Dir.OpenOptions, -) fs.Dir.OpenError!fs.Dir { + args: Io.Dir.OpenOptions, +) Io.Dir.OpenError!Io.Dir { var buf: [fs.max_path_bytes]u8 = undefined; const joined_path = if (p.sub_path.len == 0) sub_path else p: { break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ @@ -87,7 +87,7 @@ pub fn openDir( return p.root_dir.handle.openDir(joined_path, args); } -pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs.Dir { +pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io.Dir { var buf: [fs.max_path_bytes]u8 = undefined; const joined_path = if (p.sub_path.len == 0) sub_path else p: { break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ @@ -97,7 +97,7 @@ pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs return p.root_dir.handle.makeOpenPath(joined_path, opts); } -pub fn statFile(p: Path, sub_path: []const u8) !fs.Dir.Stat { +pub fn statFile(p: Path, sub_path: []const u8) !Io.Dir.Stat { var buf: [fs.max_path_bytes]u8 = undefined; const joined_path = if (p.sub_path.len == 0) sub_path else p: { break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ @@ -110,7 +110,7 @@ pub fn statFile(p: Path, sub_path: []const u8) !fs.Dir.Stat { pub fn atomicFile( p: Path, sub_path: []const u8, - options: fs.Dir.AtomicFileOptions, + options: Io.Dir.AtomicFileOptions, buf: *[fs.max_path_bytes]u8, ) !fs.AtomicFile { const joined_path = if (p.sub_path.len == 0) sub_path else p: { @@ -180,7 +180,7 @@ pub fn formatEscapeChar(path: Path, writer: *Io.Writer) Io.Writer.Error!void { } pub fn format(self: Path, writer: *Io.Writer) Io.Writer.Error!void { - if (std.fs.path.isAbsolute(self.sub_path)) { + if (fs.path.isAbsolute(self.sub_path)) { try writer.writeAll(self.sub_path); return; } @@ -225,9 +225,9 @@ pub const TableAdapter = struct { pub fn hash(self: TableAdapter, a: Cache.Path) u32 { _ = self; - const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.fd))) { - .pointer => @intFromPtr(a.root_dir.handle.fd), - .int => @as(u32, @bitCast(a.root_dir.handle.fd)), + const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.handle))) { + .pointer => @intFromPtr(a.root_dir.handle.handle), + .int => @as(u32, @bitCast(a.root_dir.handle.handle)), else => @compileError("unimplemented hash function"), }; return @truncate(Hash.hash(seed, a.sub_path)); diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index c247e69461764fd2064a99087317b57ba5eeb831..33fe755c2b81b494384a1610116eadbbf4d3d2c3 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -510,20 +510,16 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u const io = b.graph.io; const src_path = src_lazy_path.getPath3(b, s); try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path }); - return Io.Dir.updateFile(src_path.root_dir.handle.adaptToNewApi(), io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| { - return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ - src_path, dest_path, err, - }); - }; + return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| + return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err }); } /// Wrapper around `std.fs.Dir.makePathStatus` that handles verbose and error output. pub fn installDir(s: *Step, dest_path: []const u8) !std.fs.Dir.MakePathStatus { const b = s.owner; try handleVerbose(b, null, &.{ "install", "-d", dest_path }); - return std.fs.cwd().makePathStatus(dest_path) catch |err| { + return std.fs.cwd().makePathStatus(dest_path) catch |err| return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err }); - }; } fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.WebServer, gpa: Allocator) !?Path { diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index c57c7750be903c464eeda7852560361e4fadaf56..4f1e83a5736c783b58a9bc8b934ef5055c627971 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1,12 +1,15 @@ +const Compile = @This(); const builtin = @import("builtin"); + const std = @import("std"); +const Io = std.Io; const mem = std.mem; const fs = std.fs; const assert = std.debug.assert; const panic = std.debug.panic; const StringHashMap = std.StringHashMap; const Sha256 = std.crypto.hash.sha2.Sha256; -const Allocator = mem.Allocator; +const Allocator = std.mem.Allocator; const Step = std.Build.Step; const LazyPath = std.Build.LazyPath; const PkgConfigPkg = std.Build.PkgConfigPkg; @@ -15,7 +18,6 @@ const RunError = std.Build.RunError; const Module = std.Build.Module; const InstallDir = std.Build.InstallDir; const GeneratedFile = std.Build.GeneratedFile; -const Compile = @This(); const Path = std.Build.Cache.Path; pub const base_id: Step.Id = .compile; @@ -1561,19 +1563,22 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { } // -I and -L arguments that appear after the last --mod argument apply to all modules. + const cwd: Io.Dir = .cwd(); + const io = b.graph.io; + for (b.search_prefixes.items) |search_prefix| { - var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| { + var prefix_dir = cwd.openDir(io, search_prefix, .{}) catch |err| { return step.fail("unable to open prefix directory '{s}': {s}", .{ search_prefix, @errorName(err), }); }; - defer prefix_dir.close(); + defer prefix_dir.close(io); // Avoid passing -L and -I flags for nonexistent directories. // This prevents a warning, that should probably be upgraded to an error in Zig's // CLI parsing code, when the linker sees an -L directory that does not exist. - if (prefix_dir.access("lib", .{})) |_| { + if (prefix_dir.access(io, "lib", .{})) |_| { try zig_args.appendSlice(&.{ "-L", b.pathJoin(&.{ search_prefix, "lib" }), }); @@ -1584,7 +1589,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { }), } - if (prefix_dir.access("include", .{})) |_| { + if (prefix_dir.access(io, "include", .{})) |_| { try zig_args.appendSlice(&.{ "-I", b.pathJoin(&.{ search_prefix, "include" }), }); @@ -1660,7 +1665,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { args_length += arg.len + 1; // +1 to account for null terminator } if (args_length >= 30 * 1024) { - try b.cache_root.handle.makePath("args"); + try b.cache_root.handle.makePath(io, "args"); const args_to_escape = zig_args.items[2..]; var escaped_args = try std.array_list.Managed([]const u8).initCapacity(arena, args_to_escape.len); @@ -1693,18 +1698,18 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { _ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash}); const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash; - if (b.cache_root.handle.access(args_file, .{})) |_| { + if (b.cache_root.handle.access(io, args_file, .{})) |_| { // The args file is already present from a previous run. } else |err| switch (err) { error.FileNotFound => { - try b.cache_root.handle.makePath("tmp"); + try b.cache_root.handle.makePath(io, "tmp"); const rand_int = std.crypto.random.int(u64); const tmp_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); - try b.cache_root.handle.writeFile(.{ .sub_path = tmp_path, .data = args }); - defer b.cache_root.handle.deleteFile(tmp_path) catch { + try b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_path, .data = args }); + defer b.cache_root.handle.deleteFile(io, tmp_path) catch { // It's fine if the temporary file can't be cleaned up. }; - b.cache_root.handle.rename(tmp_path, args_file) catch |rename_err| switch (rename_err) { + b.cache_root.handle.rename(io, tmp_path, args_file) catch |rename_err| switch (rename_err) { error.PathAlreadyExists => { // The args file was created by another concurrent build process. }, @@ -1816,18 +1821,20 @@ pub fn doAtomicSymLinks( filename_name_only: []const u8, ) !void { const b = step.owner; + const io = b.graph.io; const out_dir = fs.path.dirname(output_path) orelse "."; const out_basename = fs.path.basename(output_path); // sym link for libfoo.so.1 to libfoo.so.1.2.3 const major_only_path = b.pathJoin(&.{ out_dir, filename_major_only }); - fs.cwd().atomicSymLink(out_basename, major_only_path, .{}) catch |err| { + const cwd: Io.Dir = .cwd(); + cwd.atomicSymLink(io, out_basename, major_only_path, .{}) catch |err| { return step.fail("unable to symlink {s} -> {s}: {s}", .{ major_only_path, out_basename, @errorName(err), }); }; // sym link for libfoo.so to libfoo.so.1 const name_only_path = b.pathJoin(&.{ out_dir, filename_name_only }); - fs.cwd().atomicSymLink(filename_major_only, name_only_path, .{}) catch |err| { + cwd.atomicSymLink(io, filename_major_only, name_only_path, .{}) catch |err| { return step.fail("Unable to symlink {s} -> {s}: {s}", .{ name_only_path, filename_major_only, @errorName(err), }); diff --git a/lib/std/Build/Step/InstallDir.zig b/lib/std/Build/Step/InstallDir.zig index fd8a7d113f57dc954f3974e957a490c3690a624b..ecb0959cc7d883801fae4ee2633a660b919d1875 100644 --- a/lib/std/Build/Step/InstallDir.zig +++ b/lib/std/Build/Step/InstallDir.zig @@ -58,16 +58,15 @@ pub fn create(owner: *std.Build, options: Options) *InstallDir { fn make(step: *Step, options: Step.MakeOptions) !void { _ = options; const b = step.owner; + const io = b.graph.io; const install_dir: *InstallDir = @fieldParentPtr("step", step); step.clearWatchInputs(); const arena = b.allocator; const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir); const src_dir_path = install_dir.options.source_dir.getPath3(b, step); const need_derived_inputs = try step.addDirectoryWatchInput(install_dir.options.source_dir); - var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { - return step.fail("unable to open source directory '{f}': {s}", .{ - src_dir_path, @errorName(err), - }); + var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { + return step.fail("unable to open source directory '{f}': {t}", .{ src_dir_path, err }); }; defer src_dir.close(); var it = try src_dir.walk(arena); diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 441928d5b8eceac52b62402b72febd067108bb93..9f5665e93a30399c3ea19f7102b425279a1e5cfb 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -441,6 +441,7 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { _ = make_options; const b = step.owner; + const io = b.graph.io; const options: *Options = @fieldParentPtr("step", step); for (options.args.items) |item| { @@ -468,18 +469,15 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { // Optimize for the hot path. Stat the file, and if it already exists, // cache hit. - if (b.cache_root.handle.access(sub_path, .{})) |_| { + if (b.cache_root.handle.access(io, sub_path, .{})) |_| { // This is the hot path, success. step.result_cached = true; return; } else |outer_err| switch (outer_err) { error.FileNotFound => { const sub_dirname = fs.path.dirname(sub_path).?; - b.cache_root.handle.makePath(sub_dirname) catch |e| { - return step.fail("unable to make path '{f}{s}': {s}", .{ - b.cache_root, sub_dirname, @errorName(e), - }); - }; + b.cache_root.handle.makePath(io, sub_dirname) catch |e| + return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, sub_dirname, e }); const rand_int = std.crypto.random.int(u64); const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ @@ -487,40 +485,40 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { basename; const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?; - b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| { - return step.fail("unable to make temporary directory '{f}{s}': {s}", .{ - b.cache_root, tmp_sub_path_dirname, @errorName(err), + b.cache_root.handle.makePath(io, tmp_sub_path_dirname) catch |err| { + return step.fail("unable to make temporary directory '{f}{s}': {t}", .{ + b.cache_root, tmp_sub_path_dirname, err, }); }; - b.cache_root.handle.writeFile(.{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| { - return step.fail("unable to write options to '{f}{s}': {s}", .{ - b.cache_root, tmp_sub_path, @errorName(err), + b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| { + return step.fail("unable to write options to '{f}{s}': {t}", .{ + b.cache_root, tmp_sub_path, err, }); }; - b.cache_root.handle.rename(tmp_sub_path, sub_path) catch |err| switch (err) { + b.cache_root.handle.rename(io, tmp_sub_path, sub_path) catch |err| switch (err) { error.PathAlreadyExists => { // Other process beat us to it. Clean up the temp file. - b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| { - try step.addError("warning: unable to delete temp file '{f}{s}': {s}", .{ - b.cache_root, tmp_sub_path, @errorName(e), + b.cache_root.handle.deleteFile(io, tmp_sub_path) catch |e| { + try step.addError("warning: unable to delete temp file '{f}{s}': {t}", .{ + b.cache_root, tmp_sub_path, e, }); }; step.result_cached = true; return; }, else => { - return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {s}", .{ - b.cache_root, tmp_sub_path, - b.cache_root, sub_path, - @errorName(err), + return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {t}", .{ + b.cache_root, tmp_sub_path, + b.cache_root, sub_path, + err, }); }, }; }, - else => |e| return step.fail("unable to access options file '{f}{s}': {s}", .{ - b.cache_root, sub_path, @errorName(e), + else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{ + b.cache_root, sub_path, e, }), } } diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 2453d102173ee15bc8f9683a0797b91753ed8044..8633ae80b0b3a8abc26a97040def8cae9dcc9c2b 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -662,16 +662,27 @@ pub const VTable = struct { futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, - dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void, - dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus, - dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, + dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakeError!void, + dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus, + dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat, - dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat, - dirAccess: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.AccessOptions) Dir.AccessError!void, - dirCreateFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File, - dirOpenFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File, - dirOpenDir: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.OpenError!Dir, + dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat, + dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void, + dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File, + dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File, + dirOpenDir: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.OpenError!Dir, dirClose: *const fn (?*anyopaque, Dir) void, + dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize, + dirRealPath: *const fn (?*anyopaque, Dir, path_name: []const u8, out_buffer: []u8) Dir.RealPathError!usize, + dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void, + dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void, + dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void, + dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.RenameError!void, + dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize, + dirSetMode: *const fn (?*anyopaque, Dir, File.Mode) Dir.SetModeError!void, + dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void, + dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void, + fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, fileClose: *const fn (?*anyopaque, File) void, fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 91164341915dc8ffca4f811bb8e7dfd03b13667d..c743ba7ccac55c6c4a55ac82cc39e3ebe3d7fae3 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -6,12 +6,20 @@ const native_os = builtin.os.tag; const std = @import("../std.zig"); const Io = std.Io; const File = Io.File; +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; handle: Handle, pub const Mode = Io.File.Mode; pub const default_mode: Mode = 0o755; +pub const Entry = struct { + name: []const u8, + kind: File.Kind, + inode: File.INode, +}; + /// Returns a handle to the current working directory. /// /// It is not opened with iteration capability. Iterating over the result is @@ -20,6 +28,8 @@ pub const default_mode: Mode = 0o755; /// Closing the returned `Dir` is checked illegal behavior. /// /// On POSIX targets, this function is comptime-callable. +/// +/// On WASI, the value this returns is application-configurable. pub fn cwd() Dir { return switch (native_os) { .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle }, @@ -28,6 +38,270 @@ pub fn cwd() Dir { }; } +pub const Reader = struct { + dir: Dir, + state: State, + /// Stores I/O implementation specific data. + buffer: [2048]u8 align(@alignOf(usize)), + index: usize, + + pub const State = enum { + /// Indicates the next call to `read` should rewind and start over the + /// directory listing. + reset, + reading, + finished, + }; + + pub const Error = error{ + AccessDenied, + PermissionDenied, + SystemResources, + } || Io.UnexpectedError || Io.Cancelable; + + pub fn init(dir: Dir) Reader { + return .{ + .dir = dir, + .state = .reset, + .index = 0, + .buffer = undefined, + }; + } + + pub fn read(r: *Reader, io: Io, buffer: []Entry) Error!usize { + return io.vtable.dirRead(io.userdata, r, buffer); + } +}; + +pub const Iterator = struct { + reader: Reader, + buffer: [32]Entry, + /// Index of next entry in `buffer`. + index: usize, + /// Fill position of `buffer`. + end: usize, + + pub const Error = Reader.Error; + + pub fn init(dir: Dir, reader_state: Reader.State) Iterator { + return .{ + .reader = .{ + .dir = dir, + .state = reader_state, + .index = 0, + .buffer = undefined, + }, + .buffer = undefined, + .index = 0, + .end = 0, + }; + } + + pub fn next(it: *Iterator, io: Io) Error!?Entry { + if (it.end - it.index == 0) { + if (it.reader.state == .finished) return null; + it.end = try it.reader.read(io, &it.buffer); + it.index = 0; + if (it.end - it.index == 0) { + assert(it.reader.state == .finished); + return null; + } + } + const index = it.index; + it.index = index + 1; + return it.buffer[index]; + } +}; + +pub fn iterate(dir: Dir) Iterator { + return .init(dir, .reset); +} + +/// Like `iterate`, but will not reset the directory cursor before the first +/// iteration. This should only be used in cases where it is known that the +/// `Dir` has not had its cursor modified yet (e.g. it was just opened). +pub fn iterateAssumeFirstIteration(dir: Dir) Iterator { + return .init(dir, .reading); +} + +pub const SelectiveWalker = struct { + stack: std.ArrayList(Walker.StackItem), + name_buffer: std.ArrayList(u8), + allocator: Allocator, + + pub const Error = Io.Dir.Iterator.Error || Allocator.Error; + + /// After each call to this function, and on deinit(), the memory returned + /// from this function becomes invalid. A copy must be made in order to keep + /// a reference to the path. + pub fn next(self: *SelectiveWalker) Error!?Walker.Entry { + while (self.stack.items.len > 0) { + const top = &self.stack.items[self.stack.items.len - 1]; + var dirname_len = top.dirname_len; + if (top.iter.next() catch |err| { + // If we get an error, then we want the user to be able to continue + // walking if they want, which means that we need to pop the directory + // that errored from the stack. Otherwise, all future `next` calls would + // likely just fail with the same error. + var item = self.stack.pop().?; + if (self.stack.items.len != 0) { + item.iter.dir.close(); + } + return err; + }) |entry| { + self.name_buffer.shrinkRetainingCapacity(dirname_len); + if (self.name_buffer.items.len != 0) { + try self.name_buffer.append(self.allocator, std.fs.path.sep); + dirname_len += 1; + } + try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1); + self.name_buffer.appendSliceAssumeCapacity(entry.name); + self.name_buffer.appendAssumeCapacity(0); + const walker_entry: Walker.Entry = .{ + .dir = top.iter.dir, + .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0], + .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0], + .kind = entry.kind, + }; + return walker_entry; + } else { + var item = self.stack.pop().?; + if (self.stack.items.len != 0) { + item.iter.dir.close(); + } + } + } + return null; + } + + /// Traverses into the directory, continuing walking one level down. + pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void { + if (entry.kind != .directory) { + @branchHint(.cold); + return; + } + + var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| { + switch (err) { + error.NameTooLong => unreachable, + else => |e| return e, + } + }; + errdefer new_dir.close(); + + try self.stack.append(self.allocator, .{ + .iter = new_dir.iterateAssumeFirstIteration(), + .dirname_len = self.name_buffer.items.len - 1, + }); + } + + pub fn deinit(self: *SelectiveWalker) void { + self.name_buffer.deinit(self.allocator); + self.stack.deinit(self.allocator); + } + + /// Leaves the current directory, continuing walking one level up. + /// If the current entry is a directory entry, then the "current directory" + /// will pertain to that entry if `enter` is called before `leave`. + pub fn leave(self: *SelectiveWalker) void { + var item = self.stack.pop().?; + if (self.stack.items.len != 0) { + @branchHint(.likely); + item.iter.dir.close(); + } + } +}; + +/// Recursively iterates over a directory, but requires the user to +/// opt-in to recursing into each directory entry. +/// +/// `dir` must have been opened with `OpenOptions{.iterate = true}`. +/// +/// `Walker.deinit` releases allocated memory and directory handles. +/// +/// The order of returned file system entries is undefined. +/// +/// `dir` will not be closed after walking it. +/// +/// See also `walk`. +pub fn walkSelectively(dir: Dir, allocator: Allocator) !SelectiveWalker { + var stack: std.ArrayList(Walker.StackItem) = .empty; + + try stack.append(allocator, .{ + .iter = dir.iterate(), + .dirname_len = 0, + }); + + return .{ + .stack = stack, + .name_buffer = .{}, + .allocator = allocator, + }; +} + +pub const Walker = struct { + inner: SelectiveWalker, + + pub const Entry = struct { + /// The containing directory. This can be used to operate directly on `basename` + /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. + /// The directory remains open until `next` or `deinit` is called. + dir: Dir, + basename: [:0]const u8, + path: [:0]const u8, + kind: Dir.Entry.Kind, + + /// Returns the depth of the entry relative to the initial directory. + /// Returns 1 for a direct child of the initial directory, 2 for an entry + /// within a direct child of the initial directory, etc. + pub fn depth(self: Walker.Entry) usize { + return std.mem.countScalar(u8, self.path, std.fs.path.sep) + 1; + } + }; + + const StackItem = struct { + iter: Dir.Iterator, + dirname_len: usize, + }; + + /// After each call to this function, and on deinit(), the memory returned + /// from this function becomes invalid. A copy must be made in order to keep + /// a reference to the path. + pub fn next(self: *Walker) !?Walker.Entry { + const entry = try self.inner.next(); + if (entry != null and entry.?.kind == .directory) { + try self.inner.enter(entry.?); + } + return entry; + } + + pub fn deinit(self: *Walker) void { + self.inner.deinit(); + } + + /// Leaves the current directory, continuing walking one level up. + /// If the current entry is a directory entry, then the "current directory" + /// is the directory pertaining to the current entry. + pub fn leave(self: *Walker) void { + self.inner.leave(); + } +}; + +/// Recursively iterates over a directory. +/// +/// `dir` must have been opened with `OpenOptions{.iterate = true}`. +/// +/// `Walker.deinit` releases allocated memory and directory handles. +/// +/// The order of returned file system entries is undefined. +/// +/// `dir` will not be closed after walking it. +/// +/// See also `walkSelectively`. +pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker { + return .{ .inner = try walkSelectively(dir, allocator) }; +} + pub const Handle = std.posix.fd_t; pub const PathNameError = error{ @@ -145,7 +419,7 @@ pub const WriteFileOptions = struct { flags: File.CreateFlags = .{}, }; -pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable; +pub const WriteFileError = File.WriteError || File.OpenError; /// Writes content to the file system, using the file creation flags provided. pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void { @@ -179,7 +453,7 @@ pub fn updateFile( dest_dir: Dir, /// If directories in this path do not exist, they are created. dest_path: []const u8, - options: std.fs.Dir.CopyFileOptions, + options: CopyFileOptions, ) !PrevStatus { var src_file = try source_dir.openFile(io, source_path, .{}); defer src_file.close(io); @@ -367,3 +641,929 @@ pub const StatPathOptions = struct { pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat { return io.vtable.dirStatPath(io.userdata, dir, sub_path, options); } + +pub const RealPathError = error{ + FileNotFound, + AccessDenied, + PermissionDenied, + NameTooLong, + NotSupported, + NotDir, + SymLinkLoop, + InputOutput, + FileTooBig, + IsDir, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + NoSpaceLeft, + FileSystem, + DeviceBusy, + ProcessNotFound, + SharingViolation, + PipeBusy, + /// Windows: file paths provided by the user must be valid WTF-8. + /// https://wtf-8.codeberg.page/ + BadPathName, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, + PathAlreadyExists, + /// On Windows, antivirus software is enabled by default. It can be + /// disabled, but Windows Update sometimes ignores the user's preference + /// and re-enables it. When enabled, antivirus software on Windows + /// intercepts file system operations and makes them significantly slower + /// in addition to possibly failing with this error code. + AntivirusInterference, + /// On Windows, the volume does not contain a recognized file system. File + /// system drivers might not be loaded, or the volume may be corrupt. + UnrecognizedVolume, +} || Io.Cancelable || Io.UnexpectedError; + +/// This function returns the canonicalized absolute pathname of `pathname` +/// relative to this `Dir`. If `pathname` is absolute, ignores this `Dir` +/// handle and returns the canonicalized absolute pathname of `pathname` +/// argument. +/// +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. +/// +/// This function is not universally supported by all platforms. Currently +/// supported hosts are: Linux, macOS, and Windows. +/// +/// See also: +/// * `realpathAlloc`. +pub fn realPath(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathError!usize { + return io.vtable.dirRealPath(io.userdata, dir, sub_path, out_buffer); +} + +pub const RealPathAllocError = RealPathError || Allocator.Error; + +/// Same as `Dir.realpath` except caller must free the returned memory. +/// See also `Dir.realpath`. +pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]u8 { + // Use of max_path_bytes here is valid as the realpath function does not + // have a variant that takes an arbitrary-size buffer. + // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008 + // NULL out parameter (GNU's canonicalize_file_name) to handle overelong + // paths. musl supports passing NULL but restricts the output to PATH_MAX + // anyway. + var buf: [std.fs.max_path_bytes]u8 = undefined; + return allocator.dupe(u8, try self.realpath(pathname, &buf)); +} + +pub const DeleteFileError = error{ + FileNotFound, + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to unlink a resource by path relative to it. + AccessDenied, + PermissionDenied, + FileBusy, + FileSystem, + IsDir, + SymLinkLoop, + NameTooLong, + NotDir, + SystemResources, + ReadOnlyFileSystem, + /// WASI: file paths must be valid UTF-8. + /// Windows: file paths provided by the user must be valid WTF-8. + /// https://wtf-8.codeberg.page/ + /// Windows: file paths cannot contain these characters: + /// '/', '*', '?', '"', '<', '>', '|' + BadPathName, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, +} || Io.Cancelable || Io.UnexpectedError; + +/// Delete a file name and possibly the file it refers to, based on an open directory handle. +/// +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, `sub_path` should be encoded as valid UTF-8. +/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. +/// +/// Asserts that the path parameter has no null bytes. +pub fn deleteFile(dir: Dir, io: Io, sub_path: []const u8) DeleteFileError!void { + return io.vtable.dirDeleteFile(io.userdata, dir, sub_path); +} + +pub const DeleteDirError = error{ + DirNotEmpty, + FileNotFound, + AccessDenied, + PermissionDenied, + FileBusy, + FileSystem, + SymLinkLoop, + NameTooLong, + NotDir, + SystemResources, + ReadOnlyFileSystem, + /// WASI: file paths must be valid UTF-8. + /// Windows: file paths provided by the user must be valid WTF-8. + /// https://wtf-8.codeberg.page/ + BadPathName, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, +} || Io.Cancelable || Io.UnexpectedError; + +/// Returns `error.DirNotEmpty` if the directory is not empty. +/// +/// To delete a directory recursively, see `deleteTree`. +/// +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, `sub_path` should be encoded as valid UTF-8. +/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. +pub fn deleteDir(dir: Dir, io: Io, sub_path: []const u8) DeleteDirError!void { + return io.vtable.dirDeleteDir(io.userdata, dir, sub_path); +} + +pub const RenameError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to rename a resource by path relative to it. + /// + /// On Windows, this error may be returned instead of PathAlreadyExists when + /// renaming a directory over an existing directory. + AccessDenied, + PermissionDenied, + FileBusy, + DiskQuota, + IsDir, + SymLinkLoop, + LinkQuotaExceeded, + NameTooLong, + FileNotFound, + NotDir, + SystemResources, + NoSpaceLeft, + PathAlreadyExists, + ReadOnlyFileSystem, + RenameAcrossMountPoints, + /// WASI: file paths must be valid UTF-8. + /// Windows: file paths provided by the user must be valid WTF-8. + /// https://wtf-8.codeberg.page/ + BadPathName, + NoDevice, + SharingViolation, + PipeBusy, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, + /// On Windows, antivirus software is enabled by default. It can be + /// disabled, but Windows Update sometimes ignores the user's preference + /// and re-enables it. When enabled, antivirus software on Windows + /// intercepts file system operations and makes them significantly slower + /// in addition to possibly failing with this error code. + AntivirusInterference, +} || Io.Cancelable || Io.UnexpectedError; + +/// Change the name or location of a file or directory. +/// +/// If `new_sub_path` already exists, it will be replaced. +/// +/// Renaming a file over an existing directory or a directory over an existing +/// file will fail with `error.IsDir` or `error.NotDir` +/// +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, both paths should be encoded as valid UTF-8. +/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +pub fn rename( + old_dir: Dir, + old_sub_path: []const u8, + new_dir: Dir, + new_sub_path: []const u8, + io: Io, +) RenameError!void { + return io.vtable.dirRename(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path); +} + +/// Use with `Dir.symLink`, `Dir.symLinkAtomic`, and `symLinkAbsolute` to +/// specify whether the symlink will point to a file or a directory. This value +/// is ignored on all hosts except Windows where creating symlinks to different +/// resource types, requires different flags. By default, `symLinkAbsolute` is +/// assumed to point to a file. +pub const SymLinkFlags = struct { + is_directory: bool = false, +}; + +pub const SymLinkError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to create a new symbolic link relative to it. + AccessDenied, + PermissionDenied, + DiskQuota, + PathAlreadyExists, + FileSystem, + SymLinkLoop, + FileNotFound, + SystemResources, + NoSpaceLeft, + ReadOnlyFileSystem, + NotDir, + NameTooLong, + /// WASI: file paths must be valid UTF-8. + /// Windows: file paths provided by the user must be valid WTF-8. + /// https://wtf-8.codeberg.page/ + BadPathName, +} || Io.Cancelable || Io.UnexpectedError; + +/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. +/// +/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent +/// one; the latter case is known as a dangling link. +/// +/// If `sym_link_path` exists, it will not be overwritten. +/// +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, both paths should be encoded as valid UTF-8. +/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +pub fn symLink( + dir: Dir, + io: Io, + target_path: []const u8, + sym_link_path: []const u8, + flags: SymLinkFlags, +) SymLinkError!void { + return io.vtable.dirSymLink(io.userdata, dir, target_path, sym_link_path, flags); +} + +/// Same as `symLink`, except tries to create the symbolic link until it +/// succeeds or encounters an error other than `error.PathAlreadyExists`. +/// +/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// * On WASI, both paths should be encoded as valid UTF-8. +/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +pub fn symLinkAtomic( + dir: Dir, + io: Io, + target_path: []const u8, + sym_link_path: []const u8, + flags: SymLinkFlags, +) !void { + if (dir.symLink(io, target_path, sym_link_path, flags)) { + return; + } else |err| switch (err) { + error.PathAlreadyExists => {}, + else => |e| return e, + } + + const dirname = std.fs.path.dirname(sym_link_path) orelse "."; + + const rand_len = @sizeOf(u64) * 2; + const temp_path_len = dirname.len + 1 + rand_len; + var temp_path_buf: [std.fs.max_path_bytes]u8 = undefined; + + if (temp_path_len > temp_path_buf.len) return error.NameTooLong; + @memcpy(temp_path_buf[0..dirname.len], dirname); + temp_path_buf[dirname.len] = std.fs.path.sep; + + const temp_path = temp_path_buf[0..temp_path_len]; + + while (true) { + const random_integer = std.crypto.random.int(u64); + temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer); + + if (dir.symLink(io, target_path, temp_path, flags)) { + return dir.rename(temp_path, dir, io, sym_link_path); + } else |err| switch (err) { + error.PathAlreadyExists => continue, + else => |e| return e, + } + } +} + +pub const ReadLinkError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to read value of a symbolic link relative to it. + AccessDenied, + PermissionDenied, + FileSystem, + SymLinkLoop, + NameTooLong, + FileNotFound, + SystemResources, + NotLink, + NotDir, + /// WASI: file paths must be valid UTF-8. + /// Windows: file paths provided by the user must be valid WTF-8. + /// https://wtf-8.codeberg.page/ + BadPathName, + /// Windows-only. This error may occur if the opened reparse point is + /// of unsupported type. + UnsupportedReparsePointType, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, + /// On Windows, antivirus software is enabled by default. It can be + /// disabled, but Windows Update sometimes ignores the user's preference + /// and re-enables it. When enabled, antivirus software on Windows + /// intercepts file system operations and makes them significantly slower + /// in addition to possibly failing with this error code. + AntivirusInterference, +} || Io.Cancelable || Io.UnexpectedError; + +/// Obtain target of a symbolic link. +/// +/// Returns how many bytes of `buffer` are populated. +/// +/// Asserts that the path parameter has no null bytes. +/// +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, `sub_path` should be encoded as valid UTF-8. +/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. +pub fn readLink(dir: Dir, io: Io, sub_path: []const u8, buffer: []u8) ReadLinkError!usize { + return io.vtable.dirReadLink(io.userdata, dir, sub_path, buffer); +} + +pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{ + /// File size reached or exceeded the provided limit. + StreamTooLong, +}; + +/// Reads all the bytes from the named file. On success, caller owns returned +/// buffer. +/// +/// If the file size is already known, a better alternative is to initialize a +/// `File.Reader`. +/// +/// If the file size cannot be obtained, an error is returned. If +/// this is a realistic possibility, a better alternative is to initialize a +/// `File.Reader` which handles this seamlessly. +pub fn readFileAlloc( + dir: Dir, + io: Io, + /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/). + /// On WASI, should be encoded as valid UTF-8. + /// On other platforms, an opaque sequence of bytes with no particular encoding. + sub_path: []const u8, + /// Used to allocate the result. + gpa: Allocator, + /// If reached or exceeded, `error.StreamTooLong` is returned instead. + limit: Io.Limit, +) ReadFileAllocError![]u8 { + return readFileAllocOptions(dir, io, sub_path, gpa, limit, .of(u8), null); +} + +/// Reads all the bytes from the named file. On success, caller owns returned +/// buffer. +/// +/// If the file size is already known, a better alternative is to initialize a +/// `File.Reader`. +pub fn readFileAllocOptions( + dir: Dir, + io: Io, + /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/). + /// On WASI, should be encoded as valid UTF-8. + /// On other platforms, an opaque sequence of bytes with no particular encoding. + sub_path: []const u8, + /// Used to allocate the result. + gpa: Allocator, + /// If reached or exceeded, `error.StreamTooLong` is returned instead. + limit: Io.Limit, + comptime alignment: std.mem.Alignment, + comptime sentinel: ?u8, +) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) { + var file = try dir.openFile(io, sub_path, .{}); + defer file.close(io); + var file_reader = file.reader(io, &.{}); + return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) { + error.ReadFailed => return file_reader.err.?, + error.OutOfMemory, error.StreamTooLong => |e| return e, + }; +} + +pub const DeleteTreeError = error{ + AccessDenied, + PermissionDenied, + FileTooBig, + SymLinkLoop, + ProcessFdQuotaExceeded, + NameTooLong, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + ReadOnlyFileSystem, + FileSystem, + FileBusy, + DeviceBusy, + ProcessNotFound, + /// One of the path components was not a directory. + /// This error is unreachable if `sub_path` does not contain a path separator. + NotDir, + /// WASI: file paths must be valid UTF-8. + /// Windows: file paths provided by the user must be valid WTF-8. + /// https://wtf-8.codeberg.page/ + /// On Windows, file paths cannot contain these characters: + /// '/', '*', '?', '"', '<', '>', '|' + BadPathName, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, +} || Io.Cancelable || Io.UnexpectedError; + +/// Whether `sub_path` describes a symlink, file, or directory, this function +/// removes it. If it cannot be removed because it is a non-empty directory, +/// this function recursively removes its entries and then tries again. +/// +/// This operation is not atomic on most file systems. +/// +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, `sub_path` should be encoded as valid UTF-8. +/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. +pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { + var initial_iterable_dir = (try dir.deleteTreeOpenInitialSubpath(io, sub_path, .file)) orelse return; + + const StackItem = struct { + name: []const u8, + parent_dir: Dir, + iter: Dir.Iterator, + + fn closeAll(inner_io: Io, items: []@This()) void { + for (items) |*item| item.iter.dir.close(inner_io); + } + }; + + var stack_buffer: [16]StackItem = undefined; + var stack = std.ArrayList(StackItem).initBuffer(&stack_buffer); + defer StackItem.closeAll(io, stack.items); + + stack.appendAssumeCapacity(.{ + .name = sub_path, + .parent_dir = dir, + .iter = initial_iterable_dir.iterateAssumeFirstIteration(), + }); + + process_stack: while (stack.items.len != 0) { + var top = &stack.items[stack.items.len - 1]; + while (try top.iter.next()) |entry| { + var treat_as_dir = entry.kind == .directory; + handle_entry: while (true) { + if (treat_as_dir) { + if (stack.unusedCapacitySlice().len >= 1) { + var iterable_dir = top.iter.dir.openDir(io, entry.name, .{ + .follow_symlinks = false, + .iterate = true, + }) catch |err| switch (err) { + error.NotDir => { + treat_as_dir = false; + continue :handle_entry; + }, + error.FileNotFound => { + // That's fine, we were trying to remove this directory anyway. + break :handle_entry; + }, + + error.AccessDenied, + error.PermissionDenied, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.NameTooLong, + error.SystemFdQuotaExceeded, + error.NoDevice, + error.SystemResources, + error.Unexpected, + error.BadPathName, + error.NetworkNotFound, + error.DeviceBusy, + error.Canceled, + => |e| return e, + }; + stack.appendAssumeCapacity(.{ + .name = entry.name, + .parent_dir = top.iter.dir, + .iter = iterable_dir.iterateAssumeFirstIteration(), + }); + continue :process_stack; + } else { + try top.iter.dir.deleteTreeMinStackSizeWithKindHint(io, entry.name, entry.kind); + break :handle_entry; + } + } else { + if (top.iter.dir.deleteFile(io, entry.name)) { + break :handle_entry; + } else |err| switch (err) { + error.FileNotFound => break :handle_entry, + + // Impossible because we do not pass any path separators. + error.NotDir => unreachable, + + error.IsDir => { + treat_as_dir = true; + continue :handle_entry; + }, + + error.AccessDenied, + error.PermissionDenied, + error.SymLinkLoop, + error.NameTooLong, + error.SystemResources, + error.ReadOnlyFileSystem, + error.FileSystem, + error.FileBusy, + error.BadPathName, + error.NetworkNotFound, + error.Unexpected, + => |e| return e, + } + } + } + } + + // On Windows, we can't delete until the dir's handle has been closed, so + // close it before we try to delete. + top.iter.dir.close(io); + + // In order to avoid double-closing the directory when cleaning up + // the stack in the case of an error, we save the relevant portions and + // pop the value from the stack. + const parent_dir = top.parent_dir; + const name = top.name; + stack.items.len -= 1; + + var need_to_retry: bool = false; + parent_dir.deleteDir(name) catch |err| switch (err) { + error.FileNotFound => {}, + error.DirNotEmpty => need_to_retry = true, + else => |e| return e, + }; + + if (need_to_retry) { + // Since we closed the handle that the previous iterator used, we + // need to re-open the dir and re-create the iterator. + var iterable_dir = iterable_dir: { + var treat_as_dir = true; + handle_entry: while (true) { + if (treat_as_dir) { + break :iterable_dir parent_dir.openDir(name, .{ + .follow_symlinks = false, + .iterate = true, + }) catch |err| switch (err) { + error.NotDir => { + treat_as_dir = false; + continue :handle_entry; + }, + error.FileNotFound => { + // That's fine, we were trying to remove this directory anyway. + continue :process_stack; + }, + + error.AccessDenied, + error.PermissionDenied, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.NameTooLong, + error.SystemFdQuotaExceeded, + error.NoDevice, + error.SystemResources, + error.Unexpected, + error.BadPathName, + error.NetworkNotFound, + error.DeviceBusy, + error.Canceled, + => |e| return e, + }; + } else { + if (parent_dir.deleteFile(name)) { + continue :process_stack; + } else |err| switch (err) { + error.FileNotFound => continue :process_stack, + + // Impossible because we do not pass any path separators. + error.NotDir => unreachable, + + error.IsDir => { + treat_as_dir = true; + continue :handle_entry; + }, + + error.AccessDenied, + error.PermissionDenied, + error.SymLinkLoop, + error.NameTooLong, + error.SystemResources, + error.ReadOnlyFileSystem, + error.FileSystem, + error.FileBusy, + error.BadPathName, + error.NetworkNotFound, + error.Unexpected, + => |e| return e, + } + } + } + }; + // We know there is room on the stack since we are just re-adding + // the StackItem that we previously popped. + stack.appendAssumeCapacity(.{ + .name = name, + .parent_dir = parent_dir, + .iter = iterable_dir.iterateAssumeFirstIteration(), + }); + continue :process_stack; + } + } +} + +/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size. +/// This is slower than `deleteTree` but uses less stack space. +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, `sub_path` should be encoded as valid UTF-8. +/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. +pub fn deleteTreeMinStackSize(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { + return dir.deleteTreeMinStackSizeWithKindHint(io, sub_path, .file); +} + +fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void { + start_over: while (true) { + var dir = (try parent.deleteTreeOpenInitialSubpath(io, sub_path, kind_hint)) orelse return; + var cleanup_dir_parent: ?Dir = null; + defer if (cleanup_dir_parent) |*d| d.close(); + + var cleanup_dir = true; + defer if (cleanup_dir) dir.close(); + + // Valid use of max_path_bytes because dir_name_buf will only + // ever store a single path component that was returned from the + // filesystem. + var dir_name_buf: [std.fs.max_path_bytes]u8 = undefined; + var dir_name: []const u8 = sub_path; + + // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function. + // Go through each entry and if it is not a directory, delete it. If it is a directory, + // open it, and close the original directory. Repeat. Then start the entire operation over. + + scan_dir: while (true) { + var dir_it = dir.iterateAssumeFirstIteration(); + dir_it: while (try dir_it.next()) |entry| { + var treat_as_dir = entry.kind == .directory; + handle_entry: while (true) { + if (treat_as_dir) { + const new_dir = dir.openDir(entry.name, .{ + .follow_symlinks = false, + .iterate = true, + }) catch |err| switch (err) { + error.NotDir => { + treat_as_dir = false; + continue :handle_entry; + }, + error.FileNotFound => { + // That's fine, we were trying to remove this directory anyway. + continue :dir_it; + }, + + error.AccessDenied, + error.PermissionDenied, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.NameTooLong, + error.SystemFdQuotaExceeded, + error.NoDevice, + error.SystemResources, + error.Unexpected, + error.BadPathName, + error.NetworkNotFound, + error.DeviceBusy, + error.Canceled, + => |e| return e, + }; + if (cleanup_dir_parent) |*d| d.close(); + cleanup_dir_parent = dir; + dir = new_dir; + const result = dir_name_buf[0..entry.name.len]; + @memcpy(result, entry.name); + dir_name = result; + continue :scan_dir; + } else { + if (dir.deleteFile(entry.name)) { + continue :dir_it; + } else |err| switch (err) { + error.FileNotFound => continue :dir_it, + + // Impossible because we do not pass any path separators. + error.NotDir => unreachable, + + error.IsDir => { + treat_as_dir = true; + continue :handle_entry; + }, + + error.AccessDenied, + error.PermissionDenied, + error.SymLinkLoop, + error.NameTooLong, + error.SystemResources, + error.ReadOnlyFileSystem, + error.FileSystem, + error.FileBusy, + error.BadPathName, + error.NetworkNotFound, + error.Unexpected, + => |e| return e, + } + } + } + } + // Reached the end of the directory entries, which means we successfully deleted all of them. + // Now to remove the directory itself. + dir.close(); + cleanup_dir = false; + + if (cleanup_dir_parent) |d| { + d.deleteDir(io, dir_name) catch |err| switch (err) { + // These two things can happen due to file system race conditions. + error.FileNotFound, error.DirNotEmpty => continue :start_over, + else => |e| return e, + }; + continue :start_over; + } else { + parent.deleteDir(io, sub_path) catch |err| switch (err) { + error.FileNotFound => return, + error.DirNotEmpty => continue :start_over, + else => |e| return e, + }; + return; + } + } + } +} + +/// On successful delete, returns null. +fn deleteTreeOpenInitialSubpath(dir: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir { + return iterable_dir: { + // Treat as a file by default + var treat_as_dir = kind_hint == .directory; + + handle_entry: while (true) { + if (treat_as_dir) { + break :iterable_dir dir.openDir(sub_path, .{ + .follow_symlinks = false, + .iterate = true, + }) catch |err| switch (err) { + error.NotDir => { + treat_as_dir = false; + continue :handle_entry; + }, + error.FileNotFound => { + // That's fine, we were trying to remove this directory anyway. + return null; + }, + + error.AccessDenied, + error.PermissionDenied, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.NameTooLong, + error.SystemFdQuotaExceeded, + error.NoDevice, + error.SystemResources, + error.Unexpected, + error.BadPathName, + error.DeviceBusy, + error.NetworkNotFound, + error.Canceled, + => |e| return e, + }; + } else { + if (dir.deleteFile(sub_path)) { + return null; + } else |err| switch (err) { + error.FileNotFound => return null, + + error.IsDir => { + treat_as_dir = true; + continue :handle_entry; + }, + + error.AccessDenied, + error.PermissionDenied, + error.SymLinkLoop, + error.NameTooLong, + error.SystemResources, + error.ReadOnlyFileSystem, + error.NotDir, + error.FileSystem, + error.FileBusy, + error.BadPathName, + error.NetworkNotFound, + error.Unexpected, + => |e| return e, + } + } + } + }; +} + +pub const CopyFileOptions = struct { + /// When this is `null` the mode is copied from the source file. + override_mode: ?File.Mode = null, +}; + +pub const CopyFileError = File.OpenError || File.StatError || + File.Atomic.InitError || File.Atomic.FinishError || + File.ReadError || File.WriteError || error{InvalidFileName}; + +/// Atomically creates a new file at `dest_path` within `dest_dir` with the +/// same contents as `source_path` within `source_dir`, overwriting any already +/// existing file. +/// +/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and +/// readily available, there is a possibility of power loss or application +/// termination leaving temporary files present in the same directory as +/// dest_path. +/// +/// On Windows, both paths should be encoded as +/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be +/// encoded as valid UTF-8. On other platforms, both paths are an opaque +/// sequence of bytes with no particular encoding. +pub fn copyFile( + source_dir: Dir, + source_path: []const u8, + dest_dir: Dir, + dest_path: []const u8, + io: Io, + options: CopyFileOptions, +) CopyFileError!void { + const file = try source_dir.openFile(io, source_path, .{}); + var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{}); + defer file_reader.file.close(io); + + const mode = options.override_mode orelse blk: { + const st = try file_reader.file.stat(io); + file_reader.size = st.size; + break :blk st.mode; + }; + + var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available. + var atomic_file = try dest_dir.atomicFile(io, dest_path, .{ + .mode = mode, + .write_buffer = &buffer, + }); + defer atomic_file.deinit(io); + + _ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { + error.ReadFailed => return file_reader.err.?, + error.WriteFailed => return atomic_file.file_writer.err.?, + }; + + try atomic_file.finish(); +} + +pub const AtomicFileOptions = struct { + mode: File.Mode = File.default_mode, + make_path: bool = false, + write_buffer: []u8, +}; + +/// Directly access the `.file` field, and then call `File.Atomic.finish` to +/// atomically replace `dest_path` with contents. +/// +/// Always call `File.Atomic.deinit` to clean up, regardless of whether +/// `File.Atomic.finish` succeeded. `dest_path` must remain valid until +/// `File.Atomic.deinit` is called. +/// +/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, `dest_path` should be encoded as valid UTF-8. +/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding. +pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFileOptions) !File.Atomic { + if (std.fs.path.dirname(dest_path)) |dirname| { + const dir = if (options.make_path) + try parent.makeOpenPath(io, dirname, .{}) + else + try parent.openDir(io, dirname, .{}); + + return .init(std.fs.path.basename(dest_path), options.mode, dir, true, options.write_buffer); + } else { + return .init(dest_path, options.mode, parent, false, options.write_buffer); + } +} + +pub const SetModeError = File.SetModeError; + +/// Also known as "chmod". +/// +/// The process must have the correct privileges in order to do this +/// successfully, or must have the effective user ID matching the owner +/// of the directory. Additionally, the directory must have been opened +/// with `OpenOptions.iterate` set to `true`. +pub fn setMode(dir: Dir, io: Io, new_mode: File.Mode) SetModeError!void { + return io.vtable.dirSetMode(io.userdata, dir, new_mode); +} + +pub const SetOwnerError = File.SetOwnerError; + +/// Also known as "chown". +/// +/// The process must have the correct privileges in order to do this +/// successfully. The group may be changed by the owner of the directory to +/// any group of which the owner is a member. Additionally, the directory +/// must have been opened with `OpenOptions.iterate` set to `true`. If the +/// owner or group is specified as `null`, the ID is not changed. +pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerError!void { + return io.vtable.dirSetOwner(io.userdata, dir, owner, group); +} + +pub const SetPermissionsError = File.SetPermissionsError; +pub const Permissions = File.Permissions; + +pub fn setPermissions(dir: Dir, io: Io, permissions: Permissions) SetPermissionsError!void { + return io.vtable.dirSetPermissions(io.userdata, dir, permissions); +} diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 7c90176519c6fdb9647404d61fe4d27ee6dd2af0..eeca7e95db4999e5c12e079a1375508e98794c4a 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -7,12 +7,23 @@ const is_windows = native_os == .windows; const std = @import("../std.zig"); const Io = std.Io; const assert = std.debug.assert; +const Dir = std.Io.Dir; handle: Handle, pub const Handle = std.posix.fd_t; pub const Mode = std.posix.mode_t; pub const INode = std.posix.ino_t; +pub const Uid = std.posix.uid_t; +pub const Gid = std.posix.gid_t; + +/// This is the default mode given to POSIX operating systems for creating +/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first, +/// since most people would expect "-rw-r--r--", for example, when using +/// the `touch` command, which would correspond to `0o644`. However, POSIX +/// libc implementations use `0o666` inside `fopen` and then rely on the +/// process-scoped "umask" setting to adjust this number for file creation. +pub const default_mode: Mode = if (Mode == u0) 0 else 0o666; pub const Kind = enum { block_device, @@ -92,6 +103,11 @@ pub const Lock = enum { exclusive, }; +pub const LockError = error{ + SystemResources, + FileLocksNotSupported, +} || Io.UnexpectedError; + pub const OpenFlags = struct { mode: OpenMode = .read_only, @@ -141,7 +157,53 @@ pub const OpenFlags = struct { } }; -pub const CreateFlags = std.fs.File.CreateFlags; +pub const CreateFlags = struct { + /// Whether the file will be created with read access. + read: bool = false, + + /// If the file already exists, and is a regular file, and the access + /// mode allows writing, it will be truncated to length 0. + truncate: bool = true, + + /// Ensures that this open call creates the file, otherwise causes + /// `error.PathAlreadyExists` to be returned. + exclusive: bool = false, + + /// Open the file with an advisory lock to coordinate with other processes + /// accessing it at the same time. An exclusive lock will prevent other + /// processes from acquiring a lock. A shared lock will prevent other + /// processes from acquiring a exclusive lock, but does not prevent + /// other process from getting their own shared locks. + /// + /// The lock is advisory, except on Linux in very specific circumstances[1]. + /// This means that a process that does not respect the locking API can still get access + /// to the file, despite the lock. + /// + /// On these operating systems, the lock is acquired atomically with + /// opening the file: + /// * Darwin + /// * DragonFlyBSD + /// * FreeBSD + /// * Haiku + /// * NetBSD + /// * OpenBSD + /// On these operating systems, the lock is acquired via a separate syscall + /// after opening the file: + /// * Linux + /// * Windows + /// + /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt + lock: Lock = .none, + + /// Sets whether or not to wait until the file is locked to return. If set to true, + /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file + /// is available to proceed. + lock_nonblocking: bool = false, + + /// For POSIX systems this is the file system mode the file will + /// be created with. On other systems this is always 0. + mode: Mode = default_mode, +}; pub const OpenError = error{ SharingViolation, @@ -231,6 +293,17 @@ pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) Wr return io.vtable.fileWritePositional(io.userdata, file, buffer, offset); } +/// Opens a file for reading or writing, without attempting to create a new +/// file, based on an absolute path. +/// +/// Returns an open resource to be released with `close`. +/// +/// Asserts that the path is absolute. See `Dir.openFile` for a function that +/// operates on both absolute and relative paths. +/// +/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, `absolute_path` should be encoded as valid UTF-8. +/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File { assert(std.fs.path.isAbsolute(absolute_path)); return Io.Dir.cwd().openFile(io, absolute_path, flags); @@ -364,11 +437,6 @@ pub const Reader = struct { }; } - /// Takes a legacy `std.fs.File` to help with upgrading. - pub fn initAdapted(file: std.fs.File, io: Io, buffer: []u8) Reader { - return .init(.{ .handle = file.handle }, io, buffer); - } - pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader { return .{ .io = io, @@ -652,3 +720,113 @@ pub const Reader = struct { return size - logicalPos(r) == 0; } }; + +pub const Atomic = struct { + file_writer: File.Writer, + random_integer: u64, + dest_basename: []const u8, + file_open: bool, + file_exists: bool, + close_dir_on_deinit: bool, + dir: Dir, + + pub const InitError = File.OpenError; + + /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. + pub fn init( + dest_basename: []const u8, + mode: File.Mode, + dir: Dir, + close_dir_on_deinit: bool, + write_buffer: []u8, + ) InitError!Atomic { + while (true) { + const random_integer = std.crypto.random.int(u64); + const tmp_sub_path = std.fmt.hex(random_integer); + const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) { + error.PathAlreadyExists => continue, + else => |e| return e, + }; + return .{ + .file_writer = file.writer(write_buffer), + .random_integer = random_integer, + .dest_basename = dest_basename, + .file_open = true, + .file_exists = true, + .close_dir_on_deinit = close_dir_on_deinit, + .dir = dir, + }; + } + } + + /// Always call deinit, even after a successful finish(). + pub fn deinit(af: *Atomic) void { + if (af.file_open) { + af.file_writer.file.close(); + af.file_open = false; + } + if (af.file_exists) { + const tmp_sub_path = std.fmt.hex(af.random_integer); + af.dir.deleteFile(&tmp_sub_path) catch {}; + af.file_exists = false; + } + if (af.close_dir_on_deinit) { + af.dir.close(); + } + af.* = undefined; + } + + pub const FlushError = File.WriteError; + + pub fn flush(af: *Atomic) FlushError!void { + af.file_writer.interface.flush() catch |err| switch (err) { + error.WriteFailed => return af.file_writer.err.?, + }; + } + + pub const RenameIntoPlaceError = Dir.RenameError; + + /// On Windows, this function introduces a period of time where some file + /// system operations on the destination file will result in + /// `error.AccessDenied`, including rename operations (such as the one used in + /// this function). + pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void { + const io = af.file_writer.io; + assert(af.file_exists); + if (af.file_open) { + af.file_writer.file.close(); + af.file_open = false; + } + const tmp_sub_path = std.fmt.hex(af.random_integer); + try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io); + af.file_exists = false; + } + + pub const FinishError = FlushError || RenameIntoPlaceError; + + /// Combination of `flush` followed by `renameIntoPlace`. + pub fn finish(af: *Atomic) FinishError!void { + try af.flush(); + try af.renameIntoPlace(); + } +}; + +pub const SetModeError = error{ + AccessDenied, + PermissionDenied, + InputOutput, + SymLinkLoop, + FileNotFound, + SystemResources, + ReadOnlyFileSystem, +} || Io.Cancelable || Io.UnexpectedError; + +pub const SetOwnerError = error{ + AccessDenied, + PermissionDenied, + InputOutput, + SymLinkLoop, + FileNotFound, + SystemResources, + ReadOnlyFileSystem, +} || Io.Cancelable || Io.UnexpectedError; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 088a1510b8fa4426c61d3f1a1bde8a808982bfc1..26fe3aac7570f88f9e7db8fe39a61a0d52a5737c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -657,12 +657,22 @@ pub fn io(t: *Threaded) Io { .dirMakeOpenPath = dirMakeOpenPath, .dirStat = dirStat, .dirStatPath = dirStatPath, - .fileStat = fileStat, .dirAccess = dirAccess, .dirCreateFile = dirCreateFile, .dirOpenFile = dirOpenFile, .dirOpenDir = dirOpenDir, .dirClose = dirClose, + .dirRealPath = dirRealPath, + .dirDeleteFile = dirDeleteFile, + .dirDeleteDir = dirDeleteDir, + .dirRename = dirRename, + .dirSymLink = dirSymLink, + .dirReadLink = dirReadLink, + .dirSetMode = dirSetMode, + .dirSetOwner = dirSetOwner, + .dirSetPermissions = dirSetPermissions, + + .fileStat = fileStat, .fileClose = fileClose, .fileWriteStreaming = fileWriteStreaming, .fileWritePositional = fileWritePositional, @@ -753,12 +763,22 @@ pub fn ioBasic(t: *Threaded) Io { .dirMakeOpenPath = dirMakeOpenPath, .dirStat = dirStat, .dirStatPath = dirStatPath, - .fileStat = fileStat, .dirAccess = dirAccess, .dirCreateFile = dirCreateFile, .dirOpenFile = dirOpenFile, .dirOpenDir = dirOpenDir, .dirClose = dirClose, + .dirRealPath = dirRealPath, + .dirDeleteFile = dirDeleteFile, + .dirDeleteDir = dirDeleteDir, + .dirRename = dirRename, + .dirSymLink = dirSymLink, + .dirReadLink = dirReadLink, + .dirSetMode = dirSetMode, + .dirSetOwner = dirSetOwner, + .dirSetPermissions = dirSetPermissions, + + .fileStat = fileStat, .fileClose = fileClose, .fileWriteStreaming = fileWriteStreaming, .fileWritePositional = fileWritePositional, @@ -3093,6 +3113,1335 @@ fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void { posix.close(dir.handle); } +const dirRealPath = switch (native_os) { + .windows => dirRealPathWindows, + else => dirRealPathPosix, +}; + +fn dirRealPathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, out_buffer: []u8) Io.Dir.RealPathError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const w = windows; + const current_thread = Thread.getCurrent(t); + + try current_thread.checkCancel(); + + var path_name_w = try w.sliceToPrefixedFileW(dir.handle, sub_path); + + const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; + const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE; + const creation = w.FILE_OPEN; + const h_file = blk: { + const res = w.OpenFile(path_name_w.span(), .{ + .dir = dir.handle, + .access_mask = access_mask, + .share_access = share_access, + .creation = creation, + .filter = .any, + }) catch |err| switch (err) { + error.WouldBlock => unreachable, + else => |e| return e, + }; + break :blk res; + }; + defer w.CloseHandle(h_file); + + const wide_slice = w.GetFinalPathNameByHandle(h_file, .{}, out_buffer); + + const len = std.unicode.calcWtf8Len(wide_slice); + if (len > out_buffer.len) + return error.NameTooLong; + + return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); +} + +fn dirRealPathPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, out_buffer: []u8) Io.Dir.RealPathError!usize { + if (native_os == .wasi) @compileError("unsupported operating system"); + const max_path_bytes = std.fs.max_path_bytes; + + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + var path_buffer: [posix.PATH_MAX]u8 = undefined; + const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + + var flags: posix.O = .{}; + if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true; + if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true; + if (@hasField(posix.O, "PATH")) flags.PATH = true; + + try current_thread.beginSyscall(); + const fd: posix.fd_t = while (true) { + const rc = openat_sym(dir.handle, sub_path_posix, flags, 0); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .FAULT => |err| return errnoBug(err), + .INVAL => return error.BadPathName, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .FBIG => return error.FileTooBig, + .OVERFLOW => return error.FileTooBig, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOENT => return error.FileNotFound, + .SRCH => return error.ProcessNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .EXIST => return error.PathAlreadyExists, + .BUSY => return error.DeviceBusy, + .NXIO => return error.NoDevice, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + }; + errdefer posix.close(fd); + + switch (native_os) { + .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { + // On macOS, we can use F.GETPATH fcntl command to query the OS for + // the path to the file descriptor. + @memset(out_buffer, 0); + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => return error.FileNotFound, + .NOSPC => return error.NameTooLong, + .NOENT => return error.FileNotFound, + // TODO man pages for fcntl on macOS don't really tell you what + // errno values to expect when command is F.GETPATH... + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + return std.mem.indexOfScalar(u8, &out_buffer, 0) orelse out_buffer.len; + }, + .linux, .serenity, .illumos => { + var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined; + const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}"; + const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, template, .{fd}, 0) catch unreachable; + try current_thread.beginSyscall(); + while (true) { + const rc = posix.system.readlink(proc_path, out_buffer.ptr, out_buffer.len); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + const len: usize = @bitCast(rc); + return len; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.NotLink, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .ILSEQ => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + }, + .freebsd => { + var kfile: std.c.kinfo_file = undefined; + kfile.structsize = std.c.KINFO_FILE_SIZE; + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => return error.FileNotFound, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + const len = std.mem.indexOfScalar(u8, &kfile.path, 0) orelse kfile.path.len; + if (len == 0) return error.NameTooLong; + return len; + }, + .netbsd, .dragonfly => { + @memset(out_buffer[0..max_path_bytes], 0); + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .BADF => return error.FileNotFound, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .RANGE => return error.NameTooLong, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + return std.mem.indexOfScalar(u8, &out_buffer, 0) orelse out_buffer.len; + }, + else => @compileError("unsupported OS"), + } + comptime unreachable; +} + +const dirDeleteFile = switch (native_os) { + .windows => dirDeleteFileWindows, + .wasi => dirDeleteFileWasi, + else => dirDeleteFilePosix, +}; + +fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void { + return dirDeleteWindows(userdata, dir, sub_path, false); +} + +fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void { + if (builtin.link_libc) return dirDeleteFilePosix(userdata, dir, sub_path); + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); + while (true) { + const res = std.os.wasi.path_unlink_file(dir.handle, sub_path.ptr, sub_path.len); + switch (res) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BUSY => return error.FileBusy, + .FAULT => |err| return errnoBug(err), + .IO => return error.FileSystem, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .ROFS => return error.ReadOnlyFileSystem, + .NOTEMPTY => return error.DirNotEmpty, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + var path_buffer: [posix.PATH_MAX]u8 = undefined; + const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, 0))) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + // Some systems return permission errors when trying to delete a + // directory, so we need to handle that case specifically and + // translate the error. + .PERM => switch (native_os) { + .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => { + + // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them). + var st = std.mem.zeroes(posix.Stat); + while (true) { + try current_thread.checkCancel(); + switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &st, posix.AT.SYMLINK_NOFOLLOW))) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => continue, + .CANCELED => return current_thread.endSyscallCanceled(), + else => { + current_thread.endSyscall(); + return error.PermissionDenied; + }, + } + } + const is_dir = st.mode & posix.S.IFMT == posix.S.IFDIR; + if (is_dir) + return error.IsDir + else + return error.PermissionDenied; + }, + else => { + current_thread.endSyscall(); + return error.PermissionDenied; + }, + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .BUSY => return error.FileBusy, + .FAULT => |err| return errnoBug(err), + .IO => return error.FileSystem, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .ROFS => return error.ReadOnlyFileSystem, + .EXIST => |err| return errnoBug(err), + .NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR + .ILSEQ => return error.BadPathName, + .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +const dirDeleteDir = switch (native_os) { + .windows => dirDeleteDirWindows, + .wasi => dirDeleteDirWasi, + else => dirDeleteDirPosix, +}; + +fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void { + return dirDeleteWindows(userdata, dir, sub_path, true); +} + +fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, remove_dir: bool) Io.Dir.DeleteFileError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + const w = windows; + + try current_thread.checkCancel(); + + const sub_path_w = try w.sliceToPrefixedFileW(dir.handle, sub_path); + + const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2)); + var nt_name: w.UNICODE_STRING = .{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + // The Windows API makes this mutable, but it will not mutate here. + .Buffer = @constCast(sub_path_w.ptr), + }; + + if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { + // Windows does not recognize this, but it does work with empty string. + nt_name.Length = 0; + } + if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { + // Can't remove the parent directory with an open handle. + return error.FileBusy; + } + + const create_options_flags: w.ULONG = if (remove_dir) + w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT + else + w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT; + + var attr: w.OBJECT_ATTRIBUTES = .{ + .Length = @sizeOf(w.OBJECT_ATTRIBUTES), + .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .Attributes = w.OBJ_CASE_INSENSITIVE, + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + var io_status_block: w.IO_STATUS_BLOCK = undefined; + var tmp_handle: w.HANDLE = undefined; + var rc = w.ntdll.NtCreateFile( + &tmp_handle, + w.SYNCHRONIZE | w.DELETE, + &attr, + &io_status_block, + null, + 0, + w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE, + w.FILE_OPEN, + create_options_flags, + null, + 0, + ); + switch (rc) { + .SUCCESS => {}, + .OBJECT_NAME_INVALID => unreachable, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found + .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't + .INVALID_PARAMETER => unreachable, + .FILE_IS_A_DIRECTORY => return error.IsDir, + .NOT_A_DIRECTORY => return error.NotDir, + .SHARING_VIOLATION => return error.FileBusy, + .ACCESS_DENIED => return error.AccessDenied, + .DELETE_PENDING => return, + else => return w.unexpectedStatus(rc), + } + defer w.CloseHandle(tmp_handle); + + // FileDispositionInformationEx has varying levels of support: + // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1 + // (INVALID_INFO_CLASS is returned if not supported) + // - Requires the NTFS filesystem + // (on filesystems like FAT32, INVALID_PARAMETER is returned) + // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1 + // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 + // (NOT_SUPPORTED is returned if a flag is unsupported) + // + // The strategy here is just to try using FileDispositionInformationEx and fall back to + // FileDispositionInformation if the return value lets us know that some aspect of it is not supported. + const need_fallback = need_fallback: { + try current_thread.checkCancel(); + + // Deletion with posix semantics if the filesystem supports it. + var info: w.FILE_DISPOSITION_INFORMATION_EX = .{ + .Flags = w.FILE_DISPOSITION_DELETE | + w.FILE_DISPOSITION_POSIX_SEMANTICS | + w.FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE, + }; + + rc = w.ntdll.NtSetInformationFile( + tmp_handle, + &io_status_block, + &info, + @sizeOf(w.FILE_DISPOSITION_INFORMATION_EX), + .FileDispositionInformationEx, + ); + switch (rc) { + .SUCCESS => return, + // The filesystem does not support FileDispositionInformationEx + .INVALID_PARAMETER, + // The operating system does not support FileDispositionInformationEx + .INVALID_INFO_CLASS, + // The operating system does not support one of the flags + .NOT_SUPPORTED, + => break :need_fallback true, + // For all other statuses, fall down to the switch below to handle them. + else => break :need_fallback false, + } + }; + + if (need_fallback) { + try current_thread.checkCancel(); + + // Deletion with file pending semantics, which requires waiting or moving + // files to get them removed (from here). + var file_dispo: w.FILE_DISPOSITION_INFORMATION = .{ + .DeleteFile = w.TRUE, + }; + + rc = w.ntdll.NtSetInformationFile( + tmp_handle, + &io_status_block, + &file_dispo, + @sizeOf(w.FILE_DISPOSITION_INFORMATION), + .FileDispositionInformation, + ); + } + switch (rc) { + .SUCCESS => {}, + .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, + .INVALID_PARAMETER => unreachable, + .CANNOT_DELETE => return error.AccessDenied, + .MEDIA_WRITE_PROTECTED => return error.AccessDenied, + .ACCESS_DENIED => return error.AccessDenied, + else => return w.unexpectedStatus(rc), + } +} + +fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void { + if (builtin.link_libc) return dirDeleteDirPosix(userdata, dir, sub_path); + + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + try current_thread.beginSyscall(); + while (true) { + const res = std.os.wasi.path_remove_directory(dir.handle, sub_path.ptr, sub_path.len); + switch (res) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BUSY => return error.FileBusy, + .FAULT => |err| return errnoBug(err), + .IO => return error.FileSystem, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .ROFS => return error.ReadOnlyFileSystem, + .NOTEMPTY => return error.DirNotEmpty, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + var path_buffer: [posix.PATH_MAX]u8 = undefined; + const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, posix.AT.REMOVEDIR))) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BUSY => return error.FileBusy, + .FAULT => |err| return errnoBug(err), + .IO => return error.FileSystem, + .ISDIR => |err| return errnoBug(err), + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .ROFS => return error.ReadOnlyFileSystem, + .EXIST => |err| return errnoBug(err), + .NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR + .ILSEQ => return error.BadPathName, + .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +const dirRename = switch (native_os) { + .windows => dirRenameWindows, + .wasi => dirRenameWasi, + else => dirRenamePosix, +}; + +fn dirRenameWindows( + userdata: ?*anyopaque, + old_dir: Io.Dir, + old_sub_path: []const u8, + new_dir: Io.Dir, + new_sub_path: []const u8, +) Io.Dir.RenameError!void { + const w = windows; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + const old_path_w = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path); + const new_path_w = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path); + const replace_if_exists = true; + + try current_thread.checkCancel(); + + const src_fd = w.OpenFile(old_path_w, .{ + .dir = old_dir.handle, + .access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | w.DELETE, + .creation = w.FILE_OPEN, + .filter = .any, // This function is supposed to rename both files and directories. + .follow_symlinks = false, + }) catch |err| switch (err) { + error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. + else => |e| return e, + }; + defer w.CloseHandle(src_fd); + + var rc: w.NTSTATUS = undefined; + // FileRenameInformationEx has varying levels of support: + // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 + // (INVALID_INFO_CLASS is returned if not supported) + // - Requires the NTFS filesystem + // (on filesystems like FAT32, INVALID_PARAMETER is returned) + // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1 + // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 + // (NOT_SUPPORTED is returned if a flag is unsupported) + // + // The strategy here is just to try using FileRenameInformationEx and fall back to + // FileRenameInformation if the return value lets us know that some aspect of it is not supported. + const need_fallback = need_fallback: { + const struct_buf_len = @sizeOf(w.FILE_RENAME_INFORMATION_EX) + (w.PATH_MAX_WIDE * 2); + var rename_info_buf: [struct_buf_len]u8 align(@alignOf(w.FILE_RENAME_INFORMATION_EX)) = undefined; + const struct_len = @sizeOf(w.FILE_RENAME_INFORMATION_EX) + new_path_w.len * 2; + if (struct_len > struct_buf_len) return error.NameTooLong; + + const rename_info: *w.FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf); + var io_status_block: w.IO_STATUS_BLOCK = undefined; + + var flags: w.ULONG = w.FILE_RENAME_POSIX_SEMANTICS | w.FILE_RENAME_IGNORE_READONLY_ATTRIBUTE; + if (replace_if_exists) flags |= w.FILE_RENAME_REPLACE_IF_EXISTS; + rename_info.* = .{ + .Flags = flags, + .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, + .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong + .FileName = undefined, + }; + @memcpy((&rename_info.FileName).ptr, new_path_w); + rc = w.ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info, + @intCast(struct_len), // already checked for error.NameTooLong + .FileRenameInformationEx, + ); + switch (rc) { + .SUCCESS => return, + // The filesystem does not support FileDispositionInformationEx + .INVALID_PARAMETER, + // The operating system does not support FileDispositionInformationEx + .INVALID_INFO_CLASS, + // The operating system does not support one of the flags + .NOT_SUPPORTED, + => break :need_fallback true, + // For all other statuses, fall down to the switch below to handle them. + else => break :need_fallback false, + } + }; + + if (need_fallback) { + const struct_buf_len = @sizeOf(w.FILE_RENAME_INFORMATION) + (w.PATH_MAX_WIDE * 2); + var rename_info_buf: [struct_buf_len]u8 align(@alignOf(w.FILE_RENAME_INFORMATION)) = undefined; + const struct_len = @sizeOf(w.FILE_RENAME_INFORMATION) + new_path_w.len * 2; + if (struct_len > struct_buf_len) return error.NameTooLong; + + const rename_info: *w.FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf); + var io_status_block: w.IO_STATUS_BLOCK = undefined; + + rename_info.* = .{ + .Flags = @intFromBool(replace_if_exists), + .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, + .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong + .FileName = undefined, + }; + @memcpy((&rename_info.FileName).ptr, new_path_w); + + rc = w.ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info, + @intCast(struct_len), // already checked for error.NameTooLong + .FileRenameInformation, + ); + } + + switch (rc) { + .SUCCESS => {}, + .INVALID_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + .OBJECT_PATH_SYNTAX_BAD => unreachable, + .ACCESS_DENIED => return error.AccessDenied, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, + .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, + .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, + .FILE_IS_A_DIRECTORY => return error.IsDir, + .NOT_A_DIRECTORY => return error.NotDir, + else => return w.unexpectedStatus(rc), + } +} + +fn dirRenameWasi( + userdata: ?*anyopaque, + old_dir: Io.Dir, + old_sub_path: []const u8, + new_dir: Io.Dir, + new_sub_path: []const u8, +) Io.Dir.RenameError!void { + if (builtin.link_libc) return dirRenamePosix(userdata, old_dir, old_sub_path, new_dir, new_sub_path); + + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + try current_thread.beginSyscall(); + while (true) { + 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)) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BUSY => return error.FileBusy, + .DQUOT => return error.DiskQuota, + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .EXIST => return error.PathAlreadyExists, + .NOTEMPTY => return error.PathAlreadyExists, + .ROFS => return error.ReadOnlyFileSystem, + .XDEV => return error.RenameAcrossMountPoints, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn dirRenamePosix( + userdata: ?*anyopaque, + old_dir: Io.Dir, + old_sub_path: []const u8, + new_dir: Io.Dir, + new_sub_path: []const u8, +) Io.Dir.RenameError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + var old_path_buffer: [posix.PATH_MAX]u8 = undefined; + var new_path_buffer: [posix.PATH_MAX]u8 = undefined; + + const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); + const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BUSY => return error.FileBusy, + .DQUOT => return error.DiskQuota, + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .EXIST => return error.PathAlreadyExists, + .NOTEMPTY => return error.PathAlreadyExists, + .ROFS => return error.ReadOnlyFileSystem, + .XDEV => return error.RenameAcrossMountPoints, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +const dirSymLink = switch (native_os) { + .windows => dirSymLinkWindows, + .wasi => dirSymLinkWasi, + else => dirSymLinkPosix, +}; + +fn dirSymLinkWindows( + userdata: ?*anyopaque, + dir: Io.Dir, + target_path: []const u8, + sym_link_path: []const u8, + flags: Io.Dir.SymLinkFlags, +) Io.Dir.SymLinkError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + const w = windows; + + try current_thread.checkCancel(); + + // Target path does not use sliceToPrefixedFileW because certain paths + // are handled differently when creating a symlink than they would be + // when converting to an NT namespaced path. CreateSymbolicLink in + // symLinkW will handle the necessary conversion. + var target_path_w: w.PathSpace = undefined; + target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path); + target_path_w.data[target_path_w.len] = 0; + // However, we need to canonicalize any path separators to `\`, since if + // the target path is relative, then it must use `\` as the path separator. + std.mem.replaceScalar( + u16, + target_path_w.data[0..target_path_w.len], + std.mem.nativeToLittle(u16, '/'), + std.mem.nativeToLittle(u16, '\\'), + ); + + const sym_link_path_w = try w.sliceToPrefixedFileW(dir.handle, sym_link_path); + + const SYMLINK_DATA = extern struct { + ReparseTag: w.ULONG, + ReparseDataLength: w.USHORT, + Reserved: w.USHORT, + SubstituteNameOffset: w.USHORT, + SubstituteNameLength: w.USHORT, + PrintNameOffset: w.USHORT, + PrintNameLength: w.USHORT, + Flags: w.ULONG, + }; + + const symlink_handle = w.OpenFile(sym_link_path_w.span(), .{ + .access_mask = w.SYNCHRONIZE | w.GENERIC_READ | w.GENERIC_WRITE, + .dir = dir, + .creation = w.FILE_CREATE, + .filter = if (flags.is_directory) .dir_only else .file_only, + }) catch |err| switch (err) { + error.IsDir => return error.PathAlreadyExists, + error.NotDir => return error.Unexpected, + error.WouldBlock => return error.Unexpected, + error.PipeBusy => return error.Unexpected, + error.NoDevice => return error.Unexpected, + error.AntivirusInterference => return error.Unexpected, + else => |e| return e, + }; + defer w.CloseHandle(symlink_handle); + + // Relevant portions of the documentation: + // > Relative links are specified using the following conventions: + // > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32". + // > - Current working directory–relative—for example, if the current working directory is + // > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt". + // > Note: If you specify a current working directory–relative link, it is created as an absolute + // > link, due to the way the current working directory is processed based on the user and the thread. + // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw + var is_target_absolute = false; + const final_target_path = target_path: { + if (w.hasCommonNtPrefix(u16, target_path)) { + // Already an NT path, no need to do anything to it + break :target_path target_path; + } else { + switch (w.getWin32PathType(u16, target_path)) { + // Rooted paths need to avoid getting put through wToPrefixedFileW + // (and they are treated as relative in this context) + // Note: It seems that rooted paths in symbolic links are relative to + // the drive that the symbolic exists on, not to the CWD's drive. + // So, if the symlink is on C:\ and the CWD is on D:\, + // it will still resolve the path relative to the root of + // the C:\ drive. + .rooted => break :target_path target_path, + // Keep relative paths relative, but anything else needs to get NT-prefixed. + else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path)) + break :target_path target_path, + } + } + var prefixed_target_path = try w.wToPrefixedFileW(dir, target_path); + // We do this after prefixing to ensure that drive-relative paths are treated as absolute + is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span()); + break :target_path prefixed_target_path.span(); + }; + + // prepare reparse data buffer + var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; + const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4; + const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2; + const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path); + const symlink_data = SYMLINK_DATA{ + .ReparseTag = w.IO_REPARSE_TAG_SYMLINK, + .ReparseDataLength = @intCast(buf_len - header_len), + .Reserved = 0, + .SubstituteNameOffset = @intCast(final_target_path.len * 2), + .SubstituteNameLength = @intCast(final_target_path.len * 2), + .PrintNameOffset = 0, + .PrintNameLength = @intCast(final_target_path.len * 2), + .Flags = if (!target_is_absolute) w.SYMLINK_FLAG_RELATIVE else 0, + }; + + @memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data)); + @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); + const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; + @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); + _ = try w.DeviceIoControl(symlink_handle, w.FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null); +} + +fn dirSymLinkWasi( + userdata: ?*anyopaque, + dir: Io.Dir, + target_path: []const u8, + sym_link_path: []const u8, + flags: Io.Dir.SymLinkFlags, +) Io.Dir.SymLinkError!void { + if (builtin.link_libc) return dirSymLinkPosix(dir, target_path, sym_link_path, flags); + + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.path_symlink(target_path.ptr, target_path.len, dir.handle, sym_link_path.ptr, sym_link_path.len)) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .ROFS => return error.ReadOnlyFileSystem, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn dirSymLinkPosix( + userdata: ?*anyopaque, + dir: Io.Dir, + target_path: []const u8, + sym_link_path: []const u8, + flags: Io.Dir.SymLinkFlags, +) Io.Dir.SymLinkError!void { + _ = flags; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + var target_path_buffer: [posix.PATH_MAX]u8 = undefined; + var sym_link_path_buffer: [posix.PATH_MAX]u8 = undefined; + + const target_path_posix = try pathToPosix(target_path, &target_path_buffer); + const sym_link_path_posix = try pathToPosix(sym_link_path, &sym_link_path_buffer); + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.symlinkat(target_path_posix, dir.handle, sym_link_path_posix))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .ROFS => return error.ReadOnlyFileSystem, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +const dirReadLink = switch (native_os) { + .windows => dirReadLinkWindows, + .wasi => dirReadLinkWasi, + else => dirReadLinkPosix, +}; + +fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + const w = windows; + + try current_thread.checkCancel(); + + var sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); + + const result_handle = w.OpenFile(sub_path_w.span(), .{ + .access_mask = w.FILE_READ_ATTRIBUTES | w.SYNCHRONIZE, + .dir = dir, + .creation = w.FILE_OPEN, + .follow_symlinks = false, + .filter = .any, + }) catch |err| switch (err) { + error.IsDir, error.NotDir => return error.Unexpected, // filter = .any + error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN + error.WouldBlock => return error.Unexpected, + error.NoDevice => return error.FileNotFound, + error.PipeBusy => return error.AccessDenied, + else => |e| return e, + }; + defer w.CloseHandle(result_handle); + + var reparse_buf: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(w.REPARSE_DATA_BUFFER)) = undefined; + _ = w.DeviceIoControl(result_handle, w.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]) catch |err| switch (err) { + error.AccessDenied => return error.Unexpected, + error.UnrecognizedVolume => return error.Unexpected, + else => |e| return e, + }; + + const reparse_struct: *const w.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0])); + const wide_result = switch (reparse_struct.ReparseTag) { + w.IO_REPARSE_TAG_SYMLINK => r: { + const buf: *const w.SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); + const offset = buf.SubstituteNameOffset >> 1; + const len = buf.SubstituteNameLength >> 1; + const path_buf: [*]const u16 = &buf.PathBuffer; + const is_relative = buf.Flags & w.SYMLINK_FLAG_RELATIVE != 0; + break :r try w.parseReadLinkPath(path_buf[offset..][0..len], is_relative, buffer); + }, + w.IO_REPARSE_TAG_MOUNT_POINT => r: { + const buf: *const w.MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); + const offset = buf.SubstituteNameOffset >> 1; + const len = buf.SubstituteNameLength >> 1; + const path_buf: [*]const u16 = &buf.PathBuffer; + break :r try w.parseReadLinkPath(path_buf[offset..][0..len], false, buffer); + }, + else => return error.UnsupportedReparsePointType, + }; + + const len = std.unicode.calcWtf8Len(wide_result); + if (len > buffer.len) return error.NameTooLong; + + return std.unicode.wtf16LeToWtf8(buffer, wide_result); +} + +fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize { + if (builtin.link_libc) return dirReadLinkPosix(userdata, dir, sub_path, buffer); + + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + var n: usize = undefined; + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.path_readlink(dir.handle, sub_path.ptr, sub_path.len, buffer.ptr, buffer.len, &n)) { + .SUCCESS => { + current_thread.endSyscall(); + return buffer[0..n]; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.NotLink, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + var sub_path_buffer: [posix.PATH_MAX]u8 = undefined; + const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer); + + try current_thread.beginSyscall(); + while (true) { + const rc = posix.system.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + const len: usize = @bitCast(rc); + return len; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.NotLink, + .IO => return error.FileSystem, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +const dirSetMode = switch (native_os) { + .windows => dirSetModeUnsupported, + else => dirSetModePosix, +}; + +fn dirSetModeUnsupported(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void { + _ = userdata; + _ = dir; + _ = new_mode; + return error.Unexpected; +} + +fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.fchmod(dir.handle, new_mode))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ACCES => return error.AccessDenied, + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.FileNotFound, + .PERM => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +const dirSetOwner = switch (native_os) { + .windows => dirSetOwnerUnsupported, + else => dirSetOwnerPosix, +}; + +fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void { + _ = userdata; + _ = dir; + _ = owner; + _ = group; + return error.Unexpected; +} + +fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + const uid = owner orelse ~@as(posix.uid_t, 0); + const gid = group orelse ~@as(posix.gid_t, 0); + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.fchown(dir.handle, uid, gid))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Io.Dir.OpenOptions.iterate` + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ACCES => return error.AccessDenied, + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.FileNotFound, + .PERM => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +const dirSetPermissions = switch (native_os) { + .windows => dirSetPermissionsWindows, + else => dirSetPermissionsPosix, +}; + +fn dirSetPermissionsWindows( + userdata: ?*anyopaque, + dir: Io.Dir, + permissions: Io.Dir.Permissions, +) Io.Dir.SetPermissionsError!void { + _ = userdata; + _ = dir; + _ = permissions; + @panic("TODO"); +} + +fn dirSetPermissionsPosix( + userdata: ?*anyopaque, + dir: Io.Dir, + permissions: Io.Dir.Permissions, +) Io.Dir.SetPermissionsError!void { + _ = userdata; + _ = dir; + _ = permissions; + @panic("TODO"); +} + fn dirOpenDirWasi( userdata: ?*anyopaque, dir: Io.Dir, @@ -3445,10 +4794,100 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - _ = file; - _ = offset; - @panic("TODO implement fileSeekBy"); + const current_thread = Thread.getCurrent(t); + const fd = file.handle; + + if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + var result: u64 = undefined; + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.CUR))) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + + if (native_os == .windows) { + try current_thread.checkCancel(); + return windows.SetFilePointerEx_CURRENT(fd, offset); + } + + if (native_os == .wasi and !builtin.link_libc) { + var new_offset: std.os.wasi.filesize_t = undefined; + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.fd_seek(fd, offset, .CUR, &new_offset)) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + + if (posix.SEEK == void) return error.Unseekable; + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(lseek_sym(fd, offset, posix.SEEK.CUR))) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } } fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void { diff --git a/lib/std/debug.zig b/lib/std/debug.zig index feea5f9a412aaccb39ca9bf1413fcc9f0dee9f7c..7c5993dfcf00c943532bb8cf09b790283399b793 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1104,7 +1104,14 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { return ptr; } -fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void { +fn printSourceAtAddress( + gpa: Allocator, + io: Io, + debug_info: *SelfInfo, + writer: *Writer, + address: usize, + tty_config: tty.Config, +) Writer.Error!void { const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) { error.MissingDebugInfo, error.UnsupportedDebugInfo, @@ -1125,6 +1132,7 @@ fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: * }; defer if (symbol.source_location) |sl| gpa.free(sl.file_name); return printLineInfo( + io, writer, symbol.source_location, address, @@ -1134,6 +1142,7 @@ fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: * ); } fn printLineInfo( + io: Io, writer: *Writer, source_location: ?SourceLocation, address: usize, @@ -1159,7 +1168,7 @@ fn printLineInfo( // Show the matching source code line if possible if (source_location) |sl| { - if (printLineFromFile(writer, sl)) { + if (printLineFromFile(io, writer, sl)) { if (sl.column > 0) { // The caret already takes one char const space_needed = @as(usize, @intCast(sl.column - 1)); @@ -1177,16 +1186,17 @@ fn printLineInfo( } } } -fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void { +fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !void { // Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`. if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) { - return root.debug.printLineFromFile(writer, source_location); + return root.debug.printLineFromFile(io, writer, source_location); } // Need this to always block even in async I/O mode, because this could potentially // be called from e.g. the event loop code crashing. - var f = try fs.cwd().openFile(source_location.file_name, .{}); - defer f.close(); + const cwd: Io.Dir = .cwd(); + var f = try cwd.openFile(io, source_location.file_name, .{}); + defer f.close(io); // TODO fstat and make sure that the file has the correct size var buf: [4096]u8 = undefined; @@ -1237,11 +1247,13 @@ fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void { } test printLineFromFile { - var aw: Writer.Allocating = .init(std.testing.allocator); + const io = std.testing.io; + const gpa = std.testing.allocator; + + var aw: Writer.Allocating = .init(gpa); defer aw.deinit(); const output_stream = &aw.writer; - const allocator = std.testing.allocator; const join = std.fs.path.join; const expectError = std.testing.expectError; const expectEqualStrings = std.testing.expectEqualStrings; @@ -1249,24 +1261,24 @@ test printLineFromFile { var test_dir = std.testing.tmpDir(.{}); defer test_dir.cleanup(); // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths. - const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] }); - defer allocator.free(test_dir_path); + const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] }); + defer gpa.free(test_dir_path); // Cases { - const path = try join(allocator, &.{ test_dir_path, "one_line.zig" }); - defer allocator.free(path); + const path = try join(gpa, &.{ test_dir_path, "one_line.zig" }); + defer gpa.free(path); try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); - try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); + try expectError(error.EndOfFile, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 })); - try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written()); aw.clearRetainingCapacity(); } { - const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" }); - defer allocator.free(path); + const path = try fs.path.join(gpa, &.{ test_dir_path, "three_lines.zig" }); + defer gpa.free(path); try test_dir.dir.writeFile(.{ .sub_path = "three_lines.zig", .data = @@ -1276,19 +1288,19 @@ test printLineFromFile { , }); - try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings("1\n", aw.written()); aw.clearRetainingCapacity(); - try printLineFromFile(output_stream, .{ .file_name = path, .line = 3, .column = 0 }); + try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 3, .column = 0 }); try expectEqualStrings("3\n", aw.written()); aw.clearRetainingCapacity(); } { const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{}); defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); - defer allocator.free(path); + const path = try fs.path.join(gpa, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); + defer gpa.free(path); const overlap = 10; var buf: [16]u8 = undefined; @@ -1299,55 +1311,55 @@ test printLineFromFile { try writer.splatByteAll('a', overlap); try writer.flush(); - try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); + try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }); try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written()); aw.clearRetainingCapacity(); } { const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{}); defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); - defer allocator.free(path); + const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); + defer gpa.free(path); var file_writer = file.writer(&.{}); const writer = &file_writer.interface; try writer.splatByteAll('a', std.heap.page_size_max); - try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written()); aw.clearRetainingCapacity(); } { const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{}); defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); - defer allocator.free(path); + const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); + defer gpa.free(path); var file_writer = file.writer(&.{}); const writer = &file_writer.interface; try writer.splatByteAll('a', 3 * std.heap.page_size_max); - try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); + try expectError(error.EndOfFile, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 })); - try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written()); aw.clearRetainingCapacity(); try writer.writeAll("a\na"); - try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written()); aw.clearRetainingCapacity(); - try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); + try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }); try expectEqualStrings("a\n", aw.written()); aw.clearRetainingCapacity(); } { const file = try test_dir.dir.createFile("file_of_newlines.zig", .{}); defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" }); - defer allocator.free(path); + const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" }); + defer gpa.free(path); var file_writer = file.writer(&.{}); const writer = &file_writer.interface; @@ -1355,11 +1367,11 @@ test printLineFromFile { try writer.splatByteAll('\n', real_file_start); try writer.writeAll("abc\ndef"); - try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); + try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); try expectEqualStrings("abc\n", aw.written()); aw.clearRetainingCapacity(); - try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 }); + try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 }); try expectEqualStrings("def\n", aw.written()); aw.clearRetainingCapacity(); } diff --git a/lib/std/fs.zig b/lib/std/fs.zig index edc3a5f985d90424505439adcf762ddcf808c95b..81ec7dca9be43620e4aa0e44a4c05e63654db17c 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -15,9 +15,13 @@ const windows = std.os.windows; const is_darwin = native_os.isDarwin(); -pub const AtomicFile = @import("fs/AtomicFile.zig"); -pub const Dir = @import("fs/Dir.zig"); -pub const File = @import("fs/File.zig"); +/// Deprecated. +pub const AtomicFile = std.Io.File.Atomic; +/// Deprecated. +pub const Dir = std.Io.Dir; +/// Deprecated. +pub const File = std.Io.File; + pub const path = @import("fs/path.zig"); pub const has_executable_bit = switch (native_os) { @@ -153,42 +157,9 @@ pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void { return posix.rmdirZ(dir_path); } -/// Same as `Dir.rename` except the paths are absolute. -/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void { - assert(path.isAbsolute(old_path)); - assert(path.isAbsolute(new_path)); - return posix.rename(old_path, new_path); -} - -/// Same as `renameAbsolute` except the path parameters are null-terminated. -pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void { - assert(path.isAbsoluteZ(old_path)); - assert(path.isAbsoluteZ(new_path)); - return posix.renameZ(old_path, new_path); -} - -/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir` -pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void { - return posix.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path); -} - -/// Same as `rename` except the parameters are null-terminated. -pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void { - return posix.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z); -} - /// Deprecated in favor of `Io.Dir.cwd`. -pub fn cwd() Dir { - if (native_os == .windows) { - return .{ .fd = windows.peb().ProcessParameters.CurrentDirectory.Handle }; - } else if (native_os == .wasi) { - return .{ .fd = std.options.wasiCwd() }; - } else { - return .{ .fd = posix.AT.FDCWD }; - } +pub fn cwd() Io.Dir { + return .cwd(); } pub fn defaultWasiCwd() std.os.wasi.fd_t { @@ -209,23 +180,11 @@ pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenOptions) File.O return cwd().openDir(absolute_path, flags); } -/// Same as `openDirAbsolute` but the path parameter is null-terminated. -pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenOptions) File.OpenError!Dir { - assert(path.isAbsoluteZ(absolute_path_c)); - return cwd().openDirZ(absolute_path_c, flags); -} -/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path. -/// Call `File.close` to release the resource. -/// Asserts that the path is absolute. See `Dir.openFile` for a function that -/// operates on both absolute and relative paths. -/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function -/// that accepts a null-terminated path. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `absolute_path` should be encoded as valid UTF-8. -/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. -pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File { - assert(path.isAbsolute(absolute_path)); - return cwd().openFile(absolute_path, flags); +/// Deprecated in favor of `Io.File.openAbsolute`. +pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Io.File.OpenError!Io.File { + var threaded: Io.Threaded = .init_single_threaded; + const io = threaded.ioBasic(); + return Io.File.openAbsolute(io, absolute_path, flags); } /// Test accessing `path`. diff --git a/lib/std/fs/AtomicFile.zig b/lib/std/fs/AtomicFile.zig deleted file mode 100644 index 96793aec729f2f6cb31d9ccb5f7bf26e762f6048..0000000000000000000000000000000000000000 --- a/lib/std/fs/AtomicFile.zig +++ /dev/null @@ -1,94 +0,0 @@ -const AtomicFile = @This(); -const std = @import("../std.zig"); -const File = std.fs.File; -const Dir = std.fs.Dir; -const fs = std.fs; -const assert = std.debug.assert; -const posix = std.posix; - -file_writer: File.Writer, -random_integer: u64, -dest_basename: []const u8, -file_open: bool, -file_exists: bool, -close_dir_on_deinit: bool, -dir: Dir, - -pub const InitError = File.OpenError; - -/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. -pub fn init( - dest_basename: []const u8, - mode: File.Mode, - dir: Dir, - close_dir_on_deinit: bool, - write_buffer: []u8, -) InitError!AtomicFile { - while (true) { - const random_integer = std.crypto.random.int(u64); - const tmp_sub_path = std.fmt.hex(random_integer); - const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) { - error.PathAlreadyExists => continue, - else => |e| return e, - }; - return .{ - .file_writer = file.writer(write_buffer), - .random_integer = random_integer, - .dest_basename = dest_basename, - .file_open = true, - .file_exists = true, - .close_dir_on_deinit = close_dir_on_deinit, - .dir = dir, - }; - } -} - -/// Always call deinit, even after a successful finish(). -pub fn deinit(af: *AtomicFile) void { - if (af.file_open) { - af.file_writer.file.close(); - af.file_open = false; - } - if (af.file_exists) { - const tmp_sub_path = std.fmt.hex(af.random_integer); - af.dir.deleteFile(&tmp_sub_path) catch {}; - af.file_exists = false; - } - if (af.close_dir_on_deinit) { - af.dir.close(); - } - af.* = undefined; -} - -pub const FlushError = File.WriteError; - -pub fn flush(af: *AtomicFile) FlushError!void { - af.file_writer.interface.flush() catch |err| switch (err) { - error.WriteFailed => return af.file_writer.err.?, - }; -} - -pub const RenameIntoPlaceError = posix.RenameError; - -/// On Windows, this function introduces a period of time where some file -/// system operations on the destination file will result in -/// `error.AccessDenied`, including rename operations (such as the one used in -/// this function). -pub fn renameIntoPlace(af: *AtomicFile) RenameIntoPlaceError!void { - assert(af.file_exists); - if (af.file_open) { - af.file_writer.file.close(); - af.file_open = false; - } - const tmp_sub_path = std.fmt.hex(af.random_integer); - try posix.renameat(af.dir.fd, &tmp_sub_path, af.dir.fd, af.dest_basename); - af.file_exists = false; -} - -pub const FinishError = FlushError || RenameIntoPlaceError; - -/// Combination of `flush` followed by `renameIntoPlace`. -pub fn finish(af: *AtomicFile) FinishError!void { - try af.flush(); - try af.renameIntoPlace(); -} diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig deleted file mode 100644 index ff75081b9e49e303d1c5de69f3eab1d166471b2c..0000000000000000000000000000000000000000 --- a/lib/std/fs/Dir.zig +++ /dev/null @@ -1,2066 +0,0 @@ -//! Deprecated in favor of `Io.Dir`. -const Dir = @This(); - -const builtin = @import("builtin"); -const native_os = builtin.os.tag; - -const std = @import("../std.zig"); -const Io = std.Io; -const File = std.fs.File; -const AtomicFile = std.fs.AtomicFile; -const base64_encoder = fs.base64_encoder; -const posix = std.posix; -const mem = std.mem; -const path = fs.path; -const fs = std.fs; -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; -const linux = std.os.linux; -const windows = std.os.windows; -const have_flock = @TypeOf(posix.system.flock) != void; - -fd: Handle, - -pub const Handle = posix.fd_t; - -pub const default_mode = 0o755; - -pub const Entry = struct { - name: []const u8, - kind: Kind, - - pub const Kind = File.Kind; -}; - -const IteratorError = error{ - AccessDenied, - PermissionDenied, - SystemResources, -} || posix.UnexpectedError; - -pub const Iterator = switch (native_os) { - .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => struct { - dir: Dir, - seek: i64, - buf: [1024]u8 align(@alignOf(posix.system.dirent)), - index: usize, - end_index: usize, - first_iter: bool, - - const Self = @This(); - - pub const Error = IteratorError; - - /// Memory such as file names referenced in this returned entry becomes invalid - /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. - pub fn next(self: *Self) Error!?Entry { - switch (native_os) { - .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => return self.nextDarwin(), - .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(), - .illumos => return self.nextIllumos(), - else => @compileError("unimplemented"), - } - } - - fn nextDarwin(self: *Self) !?Entry { - start_over: while (true) { - if (self.index >= self.end_index) { - if (self.first_iter) { - posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions - self.first_iter = false; - } - const rc = posix.system.getdirentries( - self.dir.fd, - &self.buf, - self.buf.len, - &self.seek, - ); - if (rc == 0) return null; - if (rc < 0) { - switch (posix.errno(rc)) { - .BADF => unreachable, // Dir is invalid or was opened without iteration ability - .FAULT => unreachable, - .NOTDIR => unreachable, - .INVAL => unreachable, - else => |err| return posix.unexpectedErrno(err), - } - } - self.index = 0; - self.end_index = @as(usize, @intCast(rc)); - } - const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index])); - const next_index = self.index + darwin_entry.reclen; - self.index = next_index; - - const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen]; - - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) { - continue :start_over; - } - - const entry_kind: Entry.Kind = switch (darwin_entry.type) { - posix.DT.BLK => .block_device, - posix.DT.CHR => .character_device, - posix.DT.DIR => .directory, - posix.DT.FIFO => .named_pipe, - posix.DT.LNK => .sym_link, - posix.DT.REG => .file, - posix.DT.SOCK => .unix_domain_socket, - posix.DT.WHT => .whiteout, - else => .unknown, - }; - return Entry{ - .name = name, - .kind = entry_kind, - }; - } - } - - fn nextIllumos(self: *Self) !?Entry { - start_over: while (true) { - if (self.index >= self.end_index) { - if (self.first_iter) { - posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions - self.first_iter = false; - } - const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len); - switch (posix.errno(rc)) { - .SUCCESS => {}, - .BADF => unreachable, // Dir is invalid or was opened without iteration ability - .FAULT => unreachable, - .NOTDIR => unreachable, - .INVAL => unreachable, - else => |err| return posix.unexpectedErrno(err), - } - if (rc == 0) return null; - self.index = 0; - self.end_index = @as(usize, @intCast(rc)); - } - const entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index])); - const next_index = self.index + entry.reclen; - self.index = next_index; - - const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0); - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) - continue :start_over; - - // illumos dirent doesn't expose type, so we have to call stat to get it. - const stat_info = posix.fstatat( - self.dir.fd, - name, - posix.AT.SYMLINK_NOFOLLOW, - ) catch |err| switch (err) { - error.NameTooLong => unreachable, - error.SymLinkLoop => unreachable, - error.FileNotFound => unreachable, // lost the race - else => |e| return e, - }; - const entry_kind: Entry.Kind = switch (stat_info.mode & posix.S.IFMT) { - posix.S.IFIFO => .named_pipe, - posix.S.IFCHR => .character_device, - posix.S.IFDIR => .directory, - posix.S.IFBLK => .block_device, - posix.S.IFREG => .file, - posix.S.IFLNK => .sym_link, - posix.S.IFSOCK => .unix_domain_socket, - posix.S.IFDOOR => .door, - posix.S.IFPORT => .event_port, - else => .unknown, - }; - return Entry{ - .name = name, - .kind = entry_kind, - }; - } - } - - fn nextBsd(self: *Self) !?Entry { - start_over: while (true) { - if (self.index >= self.end_index) { - if (self.first_iter) { - posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions - self.first_iter = false; - } - const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len); - switch (posix.errno(rc)) { - .SUCCESS => {}, - .BADF => unreachable, // Dir is invalid or was opened without iteration ability - .FAULT => unreachable, - .NOTDIR => unreachable, - .INVAL => unreachable, - // Introduced in freebsd 13.2: directory unlinked but still open. - // To be consistent, iteration ends if the directory being iterated is deleted during iteration. - .NOENT => return null, - else => |err| return posix.unexpectedErrno(err), - } - if (rc == 0) return null; - self.index = 0; - self.end_index = @as(usize, @intCast(rc)); - } - const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index])); - const next_index = self.index + - if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen(); - self.index = next_index; - - const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen]; - - const skip_zero_fileno = switch (native_os) { - // fileno=0 is used to mark invalid entries or deleted files. - .openbsd, .netbsd => true, - else => false, - }; - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or - (skip_zero_fileno and bsd_entry.fileno == 0)) - { - continue :start_over; - } - - const entry_kind: Entry.Kind = switch (bsd_entry.type) { - posix.DT.BLK => .block_device, - posix.DT.CHR => .character_device, - posix.DT.DIR => .directory, - posix.DT.FIFO => .named_pipe, - posix.DT.LNK => .sym_link, - posix.DT.REG => .file, - posix.DT.SOCK => .unix_domain_socket, - posix.DT.WHT => .whiteout, - else => .unknown, - }; - return Entry{ - .name = name, - .kind = entry_kind, - }; - } - } - - pub fn reset(self: *Self) void { - self.index = 0; - self.end_index = 0; - self.first_iter = true; - } - }, - .haiku => struct { - dir: Dir, - buf: [@sizeOf(DirEnt) + posix.PATH_MAX]u8 align(@alignOf(DirEnt)), - offset: usize, - index: usize, - end_index: usize, - first_iter: bool, - - const Self = @This(); - const DirEnt = posix.system.DirEnt; - - pub const Error = IteratorError; - - /// Memory such as file names referenced in this returned entry becomes invalid - /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. - pub fn next(self: *Self) Error!?Entry { - while (true) { - if (self.index >= self.end_index) { - if (self.first_iter) { - switch (@as(posix.E, @enumFromInt(posix.system._kern_rewind_dir(self.dir.fd)))) { - .SUCCESS => {}, - .BADF => unreachable, // Dir is invalid - .FAULT => unreachable, - .NOTDIR => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - else => |err| return posix.unexpectedErrno(err), - } - self.first_iter = false; - } - const rc = posix.system._kern_read_dir( - self.dir.fd, - &self.buf, - self.buf.len, - self.buf.len / @sizeOf(DirEnt), - ); - if (rc == 0) return null; - if (rc < 0) { - switch (@as(posix.E, @enumFromInt(rc))) { - .BADF => unreachable, // Dir is invalid - .FAULT => unreachable, - .NOTDIR => unreachable, - .INVAL => unreachable, - .OVERFLOW => unreachable, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - else => |err| return posix.unexpectedErrno(err), - } - } - self.offset = 0; - self.index = 0; - self.end_index = @intCast(rc); - } - const dirent: *DirEnt = @ptrCast(@alignCast(&self.buf[self.offset])); - self.offset += dirent.reclen; - self.index += 1; - const name = mem.span(dirent.getName()); - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or dirent.ino == 0) continue; - - var stat_info: posix.Stat = undefined; - switch (@as(posix.E, @enumFromInt(posix.system._kern_read_stat( - self.dir.fd, - name, - false, - &stat_info, - @sizeOf(posix.Stat), - )))) { - .SUCCESS => {}, - .INVAL => unreachable, - .BADF => unreachable, // Dir is invalid - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .FAULT => unreachable, - .NAMETOOLONG => unreachable, - .LOOP => unreachable, - .NOENT => continue, - else => |err| return posix.unexpectedErrno(err), - } - const statmode = stat_info.mode & posix.S.IFMT; - - const entry_kind: Entry.Kind = switch (statmode) { - posix.S.IFDIR => .directory, - posix.S.IFBLK => .block_device, - posix.S.IFCHR => .character_device, - posix.S.IFLNK => .sym_link, - posix.S.IFREG => .file, - posix.S.IFIFO => .named_pipe, - else => .unknown, - }; - - return Entry{ - .name = name, - .kind = entry_kind, - }; - } - } - - pub fn reset(self: *Self) void { - self.index = 0; - self.end_index = 0; - self.first_iter = true; - } - }, - .linux => struct { - dir: Dir, - buf: [1024]u8 align(@alignOf(linux.dirent64)), - index: usize, - end_index: usize, - first_iter: bool, - - const Self = @This(); - - pub const Error = IteratorError; - - /// Memory such as file names referenced in this returned entry becomes invalid - /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. - pub fn next(self: *Self) Error!?Entry { - return self.nextLinux() catch |err| switch (err) { - // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration. - // This matches the behavior of non-Linux UNIX platforms. - error.DirNotFound => null, - else => |e| return e, - }; - } - - pub const ErrorLinux = error{DirNotFound} || IteratorError; - - /// Implementation of `next` that can return `error.DirNotFound` if the directory being - /// iterated was deleted during iteration (this error is Linux specific). - pub fn nextLinux(self: *Self) ErrorLinux!?Entry { - start_over: while (true) { - if (self.index >= self.end_index) { - if (self.first_iter) { - posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions - self.first_iter = false; - } - const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len); - switch (linux.errno(rc)) { - .SUCCESS => {}, - .BADF => unreachable, // Dir is invalid or was opened without iteration ability - .FAULT => unreachable, - .NOTDIR => unreachable, - .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration. - .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net. - .ACCES => return error.AccessDenied, // Do not have permission to iterate this directory. - else => |err| return posix.unexpectedErrno(err), - } - if (rc == 0) return null; - self.index = 0; - self.end_index = rc; - } - const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index])); - const next_index = self.index + linux_entry.reclen; - self.index = next_index; - - const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0); - - // skip . and .. entries - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { - continue :start_over; - } - - const entry_kind: Entry.Kind = switch (linux_entry.type) { - linux.DT.BLK => .block_device, - linux.DT.CHR => .character_device, - linux.DT.DIR => .directory, - linux.DT.FIFO => .named_pipe, - linux.DT.LNK => .sym_link, - linux.DT.REG => .file, - linux.DT.SOCK => .unix_domain_socket, - else => .unknown, - }; - return Entry{ - .name = name, - .kind = entry_kind, - }; - } - } - - pub fn reset(self: *Self) void { - self.index = 0; - self.end_index = 0; - self.first_iter = true; - } - }, - .windows => struct { - dir: Dir, - buf: [1024]u8 align(@alignOf(windows.FILE_BOTH_DIR_INFORMATION)), - index: usize, - end_index: usize, - first_iter: bool, - name_data: [fs.max_name_bytes]u8, - - const Self = @This(); - - pub const Error = IteratorError; - - /// Memory such as file names referenced in this returned entry becomes invalid - /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. - pub fn next(self: *Self) Error!?Entry { - const w = windows; - while (true) { - if (self.index >= self.end_index) { - var io: w.IO_STATUS_BLOCK = undefined; - const rc = w.ntdll.NtQueryDirectoryFile( - self.dir.fd, - null, - null, - null, - &io, - &self.buf, - self.buf.len, - .BothDirectory, - w.FALSE, - null, - @intFromBool(self.first_iter), - ); - self.first_iter = false; - if (io.Information == 0) return null; - self.index = 0; - self.end_index = io.Information; - switch (rc) { - .SUCCESS => {}, - .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability - - else => return w.unexpectedStatus(rc), - } - } - - // While the official api docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly - // this may not always be the case (e.g. due to faulty VM/Sandboxing tools) - const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index])); - if (dir_info.NextEntryOffset != 0) { - self.index += dir_info.NextEntryOffset; - } else { - self.index = self.buf.len; - } - - const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2]; - - if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' })) - continue; - const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le); - const name_wtf8 = self.name_data[0..name_wtf8_len]; - const kind: Entry.Kind = blk: { - const attrs = dir_info.FileAttributes; - if (attrs.DIRECTORY) break :blk .directory; - if (attrs.REPARSE_POINT) break :blk .sym_link; - break :blk .file; - }; - return Entry{ - .name = name_wtf8, - .kind = kind, - }; - } - } - - pub fn reset(self: *Self) void { - self.index = 0; - self.end_index = 0; - self.first_iter = true; - } - }, - .wasi => struct { - dir: Dir, - buf: [1024]u8 align(@alignOf(std.os.wasi.dirent_t)), - cookie: u64, - index: usize, - end_index: usize, - - const Self = @This(); - - pub const Error = IteratorError; - - /// Memory such as file names referenced in this returned entry becomes invalid - /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. - pub fn next(self: *Self) Error!?Entry { - return self.nextWasi() catch |err| switch (err) { - // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration. - // This matches the behavior of non-Linux UNIX platforms. - error.DirNotFound => null, - else => |e| return e, - }; - } - - pub const ErrorWasi = error{DirNotFound} || IteratorError; - - /// Implementation of `next` that can return platform-dependent errors depending on the host platform. - /// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being - /// iterated was deleted during iteration. - pub fn nextWasi(self: *Self) ErrorWasi!?Entry { - // We intentinally use fd_readdir even when linked with libc, - // since its implementation is exactly the same as below, - // and we avoid the code complexity here. - const w = std.os.wasi; - start_over: while (true) { - // According to the WASI spec, the last entry might be truncated, - // so we need to check if the left buffer contains the whole dirent. - if (self.end_index - self.index < @sizeOf(w.dirent_t)) { - var bufused: usize = undefined; - switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) { - .SUCCESS => {}, - .BADF => unreachable, // Dir is invalid or was opened without iteration ability - .FAULT => unreachable, - .NOTDIR => unreachable, - .INVAL => unreachable, - .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration. - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), - } - if (bufused == 0) return null; - self.index = 0; - self.end_index = bufused; - } - const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index])); - const entry_size = @sizeOf(w.dirent_t); - const name_index = self.index + entry_size; - if (name_index + entry.namlen > self.end_index) { - // This case, the name is truncated, so we need to call readdir to store the entire name. - self.end_index = self.index; // Force fd_readdir in the next loop. - continue :start_over; - } - const name = self.buf[name_index .. name_index + entry.namlen]; - - const next_index = name_index + entry.namlen; - self.index = next_index; - self.cookie = entry.next; - - // skip . and .. entries - if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { - continue :start_over; - } - - const entry_kind: Entry.Kind = switch (entry.type) { - .BLOCK_DEVICE => .block_device, - .CHARACTER_DEVICE => .character_device, - .DIRECTORY => .directory, - .SYMBOLIC_LINK => .sym_link, - .REGULAR_FILE => .file, - .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, - else => .unknown, - }; - return Entry{ - .name = name, - .kind = entry_kind, - }; - } - } - - pub fn reset(self: *Self) void { - self.index = 0; - self.end_index = 0; - self.cookie = std.os.wasi.DIRCOOKIE_START; - } - }, - else => @compileError("unimplemented"), -}; - -pub fn iterate(self: Dir) Iterator { - return self.iterateImpl(true); -} - -/// Like `iterate`, but will not reset the directory cursor before the first -/// iteration. This should only be used in cases where it is known that the -/// `Dir` has not had its cursor modified yet (e.g. it was just opened). -pub fn iterateAssumeFirstIteration(self: Dir) Iterator { - return self.iterateImpl(false); -} - -fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator { - switch (native_os) { - .driverkit, - .ios, - .maccatalyst, - .macos, - .tvos, - .visionos, - .watchos, - .freebsd, - .netbsd, - .dragonfly, - .openbsd, - .illumos, - => return Iterator{ - .dir = self, - .seek = 0, - .index = 0, - .end_index = 0, - .buf = undefined, - .first_iter = first_iter_start_value, - }, - .linux => return Iterator{ - .dir = self, - .index = 0, - .end_index = 0, - .buf = undefined, - .first_iter = first_iter_start_value, - }, - .haiku => return Iterator{ - .dir = self, - .offset = 0, - .index = 0, - .end_index = 0, - .buf = undefined, - .first_iter = first_iter_start_value, - }, - .windows => return Iterator{ - .dir = self, - .index = 0, - .end_index = 0, - .first_iter = first_iter_start_value, - .buf = undefined, - .name_data = undefined, - }, - .wasi => return Iterator{ - .dir = self, - .cookie = std.os.wasi.DIRCOOKIE_START, - .index = 0, - .end_index = 0, - .buf = undefined, - }, - else => @compileError("unimplemented"), - } -} - -pub const SelectiveWalker = struct { - stack: std.ArrayList(Walker.StackItem), - name_buffer: std.ArrayList(u8), - allocator: Allocator, - - pub const Error = IteratorError || Allocator.Error; - - /// After each call to this function, and on deinit(), the memory returned - /// from this function becomes invalid. A copy must be made in order to keep - /// a reference to the path. - pub fn next(self: *SelectiveWalker) Error!?Walker.Entry { - while (self.stack.items.len > 0) { - const top = &self.stack.items[self.stack.items.len - 1]; - var dirname_len = top.dirname_len; - if (top.iter.next() catch |err| { - // If we get an error, then we want the user to be able to continue - // walking if they want, which means that we need to pop the directory - // that errored from the stack. Otherwise, all future `next` calls would - // likely just fail with the same error. - var item = self.stack.pop().?; - if (self.stack.items.len != 0) { - item.iter.dir.close(); - } - return err; - }) |entry| { - self.name_buffer.shrinkRetainingCapacity(dirname_len); - if (self.name_buffer.items.len != 0) { - try self.name_buffer.append(self.allocator, fs.path.sep); - dirname_len += 1; - } - try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1); - self.name_buffer.appendSliceAssumeCapacity(entry.name); - self.name_buffer.appendAssumeCapacity(0); - const walker_entry: Walker.Entry = .{ - .dir = top.iter.dir, - .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0], - .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0], - .kind = entry.kind, - }; - return walker_entry; - } else { - var item = self.stack.pop().?; - if (self.stack.items.len != 0) { - item.iter.dir.close(); - } - } - } - return null; - } - - /// Traverses into the directory, continuing walking one level down. - pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void { - if (entry.kind != .directory) { - @branchHint(.cold); - return; - } - - var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| { - switch (err) { - error.NameTooLong => unreachable, - else => |e| return e, - } - }; - errdefer new_dir.close(); - - try self.stack.append(self.allocator, .{ - .iter = new_dir.iterateAssumeFirstIteration(), - .dirname_len = self.name_buffer.items.len - 1, - }); - } - - pub fn deinit(self: *SelectiveWalker) void { - self.name_buffer.deinit(self.allocator); - self.stack.deinit(self.allocator); - } - - /// Leaves the current directory, continuing walking one level up. - /// If the current entry is a directory entry, then the "current directory" - /// will pertain to that entry if `enter` is called before `leave`. - pub fn leave(self: *SelectiveWalker) void { - var item = self.stack.pop().?; - if (self.stack.items.len != 0) { - @branchHint(.likely); - item.iter.dir.close(); - } - } -}; - -/// Recursively iterates over a directory, but requires the user to -/// opt-in to recursing into each directory entry. -/// -/// `self` must have been opened with `OpenOptions{.iterate = true}`. -/// -/// `Walker.deinit` releases allocated memory and directory handles. -/// -/// The order of returned file system entries is undefined. -/// -/// `self` will not be closed after walking it. -/// -/// See also `walk`. -pub fn walkSelectively(self: Dir, allocator: Allocator) !SelectiveWalker { - var stack: std.ArrayList(Walker.StackItem) = .empty; - - try stack.append(allocator, .{ - .iter = self.iterate(), - .dirname_len = 0, - }); - - return .{ - .stack = stack, - .name_buffer = .{}, - .allocator = allocator, - }; -} - -pub const Walker = struct { - inner: SelectiveWalker, - - pub const Entry = struct { - /// The containing directory. This can be used to operate directly on `basename` - /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. - /// The directory remains open until `next` or `deinit` is called. - dir: Dir, - basename: [:0]const u8, - path: [:0]const u8, - kind: Dir.Entry.Kind, - - /// Returns the depth of the entry relative to the initial directory. - /// Returns 1 for a direct child of the initial directory, 2 for an entry - /// within a direct child of the initial directory, etc. - pub fn depth(self: Walker.Entry) usize { - return mem.countScalar(u8, self.path, fs.path.sep) + 1; - } - }; - - const StackItem = struct { - iter: Dir.Iterator, - dirname_len: usize, - }; - - /// After each call to this function, and on deinit(), the memory returned - /// from this function becomes invalid. A copy must be made in order to keep - /// a reference to the path. - pub fn next(self: *Walker) !?Walker.Entry { - const entry = try self.inner.next(); - if (entry != null and entry.?.kind == .directory) { - try self.inner.enter(entry.?); - } - return entry; - } - - pub fn deinit(self: *Walker) void { - self.inner.deinit(); - } - - /// Leaves the current directory, continuing walking one level up. - /// If the current entry is a directory entry, then the "current directory" - /// is the directory pertaining to the current entry. - pub fn leave(self: *Walker) void { - self.inner.leave(); - } -}; - -/// Recursively iterates over a directory. -/// -/// `self` must have been opened with `OpenOptions{.iterate = true}`. -/// -/// `Walker.deinit` releases allocated memory and directory handles. -/// -/// The order of returned file system entries is undefined. -/// -/// `self` will not be closed after walking it. -/// -/// See also `walkSelectively`. -pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { - return .{ - .inner = try walkSelectively(self, allocator), - }; -} - -pub const OpenError = Io.Dir.OpenError; - -pub fn close(self: *Dir) void { - posix.close(self.fd); - self.* = undefined; -} - -/// Deprecated in favor of `Io.Dir.openFile`. -pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return .adaptFromNewApi(try Io.Dir.openFile(self.adaptToNewApi(), io, sub_path, flags)); -} - -/// Deprecated in favor of `Io.Dir.createFile`. -pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - const new_file = try Io.Dir.createFile(self.adaptToNewApi(), io, sub_path, flags); - return .adaptFromNewApi(new_file); -} - -/// Deprecated in favor of `Io.Dir.MakeError`. -pub const MakeError = Io.Dir.MakeError; - -/// Deprecated in favor of `Io.Dir.makeDir`. -pub fn makeDir(self: Dir, sub_path: []const u8) MakeError!void { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return Io.Dir.makeDir(.{ .handle = self.fd }, io, sub_path); -} - -/// Deprecated in favor of `Io.Dir.makeDir`. -pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) MakeError!void { - try posix.mkdiratZ(self.fd, sub_path, default_mode); -} - -/// Deprecated in favor of `Io.Dir.makeDir`. -pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void { - try posix.mkdiratW(self.fd, mem.span(sub_path), default_mode); -} - -/// Deprecated in favor of `Io.Dir.makePath`. -pub fn makePath(self: Dir, sub_path: []const u8) MakePathError!void { - _ = try self.makePathStatus(sub_path); -} - -/// Deprecated in favor of `Io.Dir.MakePathStatus`. -pub const MakePathStatus = Io.Dir.MakePathStatus; -/// Deprecated in favor of `Io.Dir.MakePathError`. -pub const MakePathError = Io.Dir.MakePathError; - -/// Deprecated in favor of `Io.Dir.makePathStatus`. -pub fn makePathStatus(self: Dir, sub_path: []const u8) MakePathError!MakePathStatus { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return Io.Dir.makePathStatus(.{ .handle = self.fd }, io, sub_path); -} - -/// Deprecated in favor of `Io.Dir.makeOpenPath`. -pub fn makeOpenPath(dir: Dir, sub_path: []const u8, options: OpenOptions) Io.Dir.MakeOpenPathError!Dir { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return .adaptFromNewApi(try Io.Dir.makeOpenPath(dir.adaptToNewApi(), io, sub_path, options)); -} - -pub const RealPathError = posix.RealPathError || error{Canceled}; - -/// This function returns the canonicalized absolute pathname of -/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this -/// `Dir` handle and returns the canonicalized absolute pathname of `pathname` -/// argument. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -/// This function is not universally supported by all platforms. -/// Currently supported hosts are: Linux, macOS, and Windows. -/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`. -pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError![]u8 { - if (native_os == .wasi) { - @compileError("realpath is not available on WASI"); - } - if (native_os == .windows) { - var pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname); - - const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data); - - const len = std.unicode.calcWtf8Len(wide_slice); - if (len > out_buffer.len) - return error.NameTooLong; - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); - return out_buffer[0..end_index]; - } - const pathname_c = try posix.toPosixPath(pathname); - return self.realpathZ(&pathname_c, out_buffer); -} - -/// Same as `Dir.realpath` except `pathname` is null-terminated. -/// See also `Dir.realpath`, `realpathZ`. -pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 { - if (native_os == .windows) { - var pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname); - - const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data); - - const len = std.unicode.calcWtf8Len(wide_slice); - if (len > out_buffer.len) - return error.NameTooLong; - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); - return out_buffer[0..end_index]; - } - - var flags: posix.O = .{}; - if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true; - if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true; - if (@hasField(posix.O, "PATH")) flags.PATH = true; - - const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) { - error.FileLocksNotSupported => return error.Unexpected, - error.FileBusy => return error.Unexpected, - error.WouldBlock => return error.Unexpected, - else => |e| return e, - }; - defer posix.close(fd); - - var buffer: [fs.max_path_bytes]u8 = undefined; - const out_path = try std.os.getFdPath(fd, &buffer); - - if (out_path.len > out_buffer.len) { - return error.NameTooLong; - } - - const result = out_buffer[0..out_path.len]; - @memcpy(result, out_path); - return result; -} - -/// Deprecated: use `realpathW2`. -/// -/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded. -/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// See also `Dir.realpath`, `realpathW`. -pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 { - var wide_buf: [std.os.windows.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try self.realpathW2(pathname, &wide_buf); - - const len = std.unicode.calcWtf8Len(wide_slice); - if (len > out_buffer.len) return error.NameTooLong; - - const end_index = std.unicode.wtf16LeToWtf8(&out_buffer, wide_slice); - return out_buffer[0..end_index]; -} - -/// Windows-only. Same as `Dir.realpath` except -/// * `pathname` and the result are WTF-16 LE encoded -/// * `pathname` is relative or has the NT namespace prefix. See `windows.wToPrefixedFileW` for details. -/// -/// Additionally, `pathname` will never be accessed after `out_buffer` has been written to, so it -/// is safe to reuse a single buffer for both. -/// -/// See also `Dir.realpath`, `realpathW`. -pub fn realpathW2(self: Dir, pathname: []const u16, out_buffer: []u16) RealPathError![]u16 { - const w = windows; - - const h_file = blk: { - const res = w.OpenFile(pathname, .{ - .dir = self.fd, - .access_mask = .{ - .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ .READ = true }, - }, - .creation = .OPEN, - .filter = .any, - }) catch |err| switch (err) { - error.WouldBlock => unreachable, - else => |e| return e, - }; - break :blk res; - }; - defer w.CloseHandle(h_file); - - return w.GetFinalPathNameByHandle(h_file, .{}, out_buffer); -} - -pub const RealPathAllocError = RealPathError || Allocator.Error; - -/// Same as `Dir.realpath` except caller must free the returned memory. -/// See also `Dir.realpath`. -pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]u8 { - // Use of max_path_bytes here is valid as the realpath function does not - // have a variant that takes an arbitrary-size buffer. - // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008 - // NULL out parameter (GNU's canonicalize_file_name) to handle overelong - // paths. musl supports passing NULL but restricts the output to PATH_MAX - // anyway. - var buf: [fs.max_path_bytes]u8 = undefined; - return allocator.dupe(u8, try self.realpath(pathname, buf[0..])); -} - -/// Changes the current working directory to the open directory handle. -/// This modifies global state and can have surprising effects in multi- -/// threaded applications. Most applications and especially libraries should -/// not call this function as a general rule, however it can have use cases -/// in, for example, implementing a shell, or child process execution. -/// Not all targets support this. For example, WASI does not have the concept -/// of a current working directory. -pub fn setAsCwd(self: Dir) !void { - if (native_os == .wasi) { - @compileError("changing cwd is not currently possible in WASI"); - } - if (native_os == .windows) { - var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; - const dir_path = try windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer); - if (builtin.link_libc) { - return posix.chdirW(dir_path); - } - return windows.SetCurrentDirectory(dir_path); - } - try posix.fchdir(self.fd); -} - -/// Deprecated in favor of `Io.Dir.OpenOptions`. -pub const OpenOptions = Io.Dir.OpenOptions; - -/// Deprecated in favor of `Io.Dir.openDir`. -pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return .adaptFromNewApi(try Io.Dir.openDir(.{ .handle = self.fd }, io, sub_path, args)); -} - -pub const DeleteFileError = posix.UnlinkError; - -/// Delete a file name and possibly the file it refers to, based on an open directory handle. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `sub_path` should be encoded as valid UTF-8. -/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -/// Asserts that the path parameter has no null bytes. -pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { - if (native_os == .windows) { - const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); - return self.deleteFileW(sub_path_w.span()); - } else if (native_os == .wasi and !builtin.link_libc) { - posix.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) { - error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR - else => |e| return e, - }; - } else { - const sub_path_c = try posix.toPosixPath(sub_path); - return self.deleteFileZ(&sub_path_c); - } -} - -/// Same as `deleteFile` except the parameter is null-terminated. -pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void { - posix.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) { - error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR - error.AccessDenied, error.PermissionDenied => |e| switch (native_os) { - // non-Linux POSIX systems return permission errors when trying to delete a - // directory, so we need to handle that case specifically and translate the error - .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => { - // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them) - const fstat = posix.fstatatZ(self.fd, sub_path_c, posix.AT.SYMLINK_NOFOLLOW) catch return e; - const is_dir = fstat.mode & posix.S.IFMT == posix.S.IFDIR; - return if (is_dir) error.IsDir else e; - }, - else => return e, - }, - else => |e| return e, - }; -} - -/// Same as `deleteFile` except the parameter is WTF-16 LE encoded. -pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void { - posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) { - error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR - else => |e| return e, - }; -} - -pub const DeleteDirError = error{ - DirNotEmpty, - FileNotFound, - AccessDenied, - PermissionDenied, - FileBusy, - FileSystem, - SymLinkLoop, - NameTooLong, - NotDir, - SystemResources, - ReadOnlyFileSystem, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, - ProcessNotFound, - Unexpected, -}; - -/// Returns `error.DirNotEmpty` if the directory is not empty. -/// To delete a directory recursively, see `deleteTree`. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `sub_path` should be encoded as valid UTF-8. -/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -/// Asserts that the path parameter has no null bytes. -pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { - if (native_os == .windows) { - const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); - return self.deleteDirW(sub_path_w.span()); - } else if (native_os == .wasi and !builtin.link_libc) { - posix.unlinkat(self.fd, sub_path, posix.AT.REMOVEDIR) catch |err| switch (err) { - error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR - else => |e| return e, - }; - } else { - const sub_path_c = try posix.toPosixPath(sub_path); - return self.deleteDirZ(&sub_path_c); - } -} - -/// Same as `deleteDir` except the parameter is null-terminated. -pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void { - posix.unlinkatZ(self.fd, sub_path_c, posix.AT.REMOVEDIR) catch |err| switch (err) { - error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR - else => |e| return e, - }; -} - -/// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed. -/// This function is Windows-only. -pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void { - posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) { - error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR - else => |e| return e, - }; -} - -pub const RenameError = posix.RenameError; - -/// Change the name or location of a file or directory. -/// If new_sub_path already exists, it will be replaced. -/// Renaming a file over an existing directory or a directory -/// over an existing file will fail with `error.IsDir` or `error.NotDir` -/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void { - return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path); -} - -/// Same as `rename` except the parameters are null-terminated. -pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void { - return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z); -} - -/// Same as `rename` except the parameters are WTF16LE, NT prefixed. -/// This function is Windows-only. -pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void { - return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w, windows.TRUE); -} - -/// Use with `Dir.symLink`, `Dir.atomicSymLink`, and `symLinkAbsolute` to -/// specify whether the symlink will point to a file or a directory. This value -/// is ignored on all hosts except Windows where creating symlinks to different -/// resource types, requires different flags. By default, `symLinkAbsolute` is -/// assumed to point to a file. -pub const SymLinkFlags = struct { - is_directory: bool = false, -}; - -/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. -/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent -/// one; the latter case is known as a dangling link. -/// If `sym_link_path` exists, it will not be overwritten. -/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn symLink( - self: Dir, - target_path: []const u8, - sym_link_path: []const u8, - flags: SymLinkFlags, -) !void { - if (native_os == .wasi and !builtin.link_libc) { - return self.symLinkWasi(target_path, sym_link_path, flags); - } - if (native_os == .windows) { - // Target path does not use sliceToPrefixedFileW because certain paths - // are handled differently when creating a symlink than they would be - // when converting to an NT namespaced path. CreateSymbolicLink in - // symLinkW will handle the necessary conversion. - var target_path_w: windows.PathSpace = undefined; - target_path_w.len = try windows.wtf8ToWtf16Le(&target_path_w.data, target_path); - target_path_w.data[target_path_w.len] = 0; - // However, we need to canonicalize any path separators to `\`, since if - // the target path is relative, then it must use `\` as the path separator. - mem.replaceScalar( - u16, - target_path_w.data[0..target_path_w.len], - mem.nativeToLittle(u16, '/'), - mem.nativeToLittle(u16, '\\'), - ); - - const sym_link_path_w = try windows.sliceToPrefixedFileW(self.fd, sym_link_path); - return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags); - } - const target_path_c = try posix.toPosixPath(target_path); - const sym_link_path_c = try posix.toPosixPath(sym_link_path); - return self.symLinkZ(&target_path_c, &sym_link_path_c, flags); -} - -/// WASI-only. Same as `symLink` except targeting WASI. -pub fn symLinkWasi( - self: Dir, - target_path: []const u8, - sym_link_path: []const u8, - _: SymLinkFlags, -) !void { - return posix.symlinkat(target_path, self.fd, sym_link_path); -} - -/// Same as `symLink`, except the pathname parameters are null-terminated. -pub fn symLinkZ( - self: Dir, - target_path_c: [*:0]const u8, - sym_link_path_c: [*:0]const u8, - flags: SymLinkFlags, -) !void { - if (native_os == .windows) { - const target_path_w = try windows.cStrToPrefixedFileW(self.fd, target_path_c); - const sym_link_path_w = try windows.cStrToPrefixedFileW(self.fd, sym_link_path_c); - return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags); - } - return posix.symlinkatZ(target_path_c, self.fd, sym_link_path_c); -} - -/// Windows-only. Same as `symLink` except the pathname parameters -/// are WTF16 LE encoded. -pub fn symLinkW( - self: Dir, - /// WTF-16, does not need to be NT-prefixed. The NT-prefixing - /// of this path is handled by CreateSymbolicLink. - /// Any path separators must be `\`, not `/`. - target_path_w: [:0]const u16, - /// WTF-16, must be NT-prefixed or relative - sym_link_path_w: []const u16, - flags: SymLinkFlags, -) !void { - return windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory); -} - -/// Same as `symLink`, except tries to create the symbolic link until it -/// succeeds or encounters an error other than `error.PathAlreadyExists`. -/// -/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// * On WASI, both paths should be encoded as valid UTF-8. -/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn atomicSymLink( - dir: Dir, - target_path: []const u8, - sym_link_path: []const u8, - flags: SymLinkFlags, -) !void { - if (dir.symLink(target_path, sym_link_path, flags)) { - return; - } else |err| switch (err) { - error.PathAlreadyExists => {}, - else => |e| return e, - } - - const dirname = path.dirname(sym_link_path) orelse "."; - - const rand_len = @sizeOf(u64) * 2; - const temp_path_len = dirname.len + 1 + rand_len; - var temp_path_buf: [fs.max_path_bytes]u8 = undefined; - - if (temp_path_len > temp_path_buf.len) return error.NameTooLong; - @memcpy(temp_path_buf[0..dirname.len], dirname); - temp_path_buf[dirname.len] = path.sep; - - const temp_path = temp_path_buf[0..temp_path_len]; - - while (true) { - const random_integer = std.crypto.random.int(u64); - temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer); - - if (dir.symLink(target_path, temp_path, flags)) { - return dir.rename(temp_path, sym_link_path); - } else |err| switch (err) { - error.PathAlreadyExists => continue, - else => |e| return e, - } - } -} - -pub const ReadLinkError = posix.ReadLinkError; - -/// Read value of a symbolic link. -/// The return value is a slice of `buffer`, from index `0`. -/// Asserts that the path parameter has no null bytes. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `sub_path` should be encoded as valid UTF-8. -/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 { - if (native_os == .wasi and !builtin.link_libc) { - return self.readLinkWasi(sub_path, buffer); - } - if (native_os == .windows) { - var sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); - const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data); - - const len = std.unicode.calcWtf8Len(result_w); - if (len > buffer.len) return error.NameTooLong; - - const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w); - return buffer[0..end_index]; - } - const sub_path_c = try posix.toPosixPath(sub_path); - return self.readLinkZ(&sub_path_c, buffer); -} - -/// WASI-only. Same as `readLink` except targeting WASI. -pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 { - return posix.readlinkat(self.fd, sub_path, buffer); -} - -/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated. -pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { - if (native_os == .windows) { - var sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c); - const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data); - - const len = std.unicode.calcWtf8Len(result_w); - if (len > buffer.len) return error.NameTooLong; - - const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w); - return buffer[0..end_index]; - } - return posix.readlinkatZ(self.fd, sub_path_c, buffer); -} - -/// Windows-only. Same as `readLink` except the path parameter -/// is WTF-16 LE encoded, NT-prefixed. -/// -/// `sub_path_w` will never be accessed after `buffer` has been written to, so it -/// is safe to reuse a single buffer for both. -pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u16) ![]u16 { - return windows.ReadLink(self.fd, sub_path_w, buffer); -} - -/// Deprecated in favor of `Io.Dir.readFile`. -pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return Io.Dir.readFile(.{ .handle = self.fd }, io, file_path, buffer); -} - -pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{ - /// File size reached or exceeded the provided limit. - StreamTooLong, -}; - -/// Reads all the bytes from the named file. On success, caller owns returned -/// buffer. -/// -/// If the file size is already known, a better alternative is to initialize a -/// `File.Reader`. -/// -/// If the file size cannot be obtained, an error is returned. If -/// this is a realistic possibility, a better alternative is to initialize a -/// `File.Reader` which handles this seamlessly. -pub fn readFileAlloc( - dir: Dir, - /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/). - /// On WASI, should be encoded as valid UTF-8. - /// On other platforms, an opaque sequence of bytes with no particular encoding. - sub_path: []const u8, - /// Used to allocate the result. - gpa: Allocator, - /// If reached or exceeded, `error.StreamTooLong` is returned instead. - limit: Io.Limit, -) ReadFileAllocError![]u8 { - return readFileAllocOptions(dir, sub_path, gpa, limit, .of(u8), null); -} - -/// Reads all the bytes from the named file. On success, caller owns returned -/// buffer. -/// -/// If the file size is already known, a better alternative is to initialize a -/// `File.Reader`. -/// -/// TODO move this function to Io.Dir -pub fn readFileAllocOptions( - dir: Dir, - /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/). - /// On WASI, should be encoded as valid UTF-8. - /// On other platforms, an opaque sequence of bytes with no particular encoding. - sub_path: []const u8, - /// Used to allocate the result. - gpa: Allocator, - /// If reached or exceeded, `error.StreamTooLong` is returned instead. - limit: Io.Limit, - comptime alignment: std.mem.Alignment, - comptime sentinel: ?u8, -) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - - var file = try dir.openFile(sub_path, .{}); - defer file.close(); - var file_reader = file.reader(io, &.{}); - return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) { - error.ReadFailed => return file_reader.err.?, - error.OutOfMemory, error.StreamTooLong => |e| return e, - }; -} - -pub const DeleteTreeError = error{ - AccessDenied, - PermissionDenied, - FileTooBig, - SymLinkLoop, - ProcessFdQuotaExceeded, - NameTooLong, - SystemFdQuotaExceeded, - NoDevice, - SystemResources, - ReadOnlyFileSystem, - FileSystem, - FileBusy, - DeviceBusy, - ProcessNotFound, - /// One of the path components was not a directory. - /// This error is unreachable if `sub_path` does not contain a path separator. - NotDir, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - /// On Windows, file paths cannot contain these characters: - /// '/', '*', '?', '"', '<', '>', '|' - BadPathName, - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, - - Canceled, -} || posix.UnexpectedError; - -/// Whether `sub_path` describes a symlink, file, or directory, this function -/// removes it. If it cannot be removed because it is a non-empty directory, -/// this function recursively removes its entries and then tries again. -/// This operation is not atomic on most file systems. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `sub_path` should be encoded as valid UTF-8. -/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { - var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return; - - const StackItem = struct { - name: []const u8, - parent_dir: Dir, - iter: Dir.Iterator, - - fn closeAll(items: []@This()) void { - for (items) |*item| item.iter.dir.close(); - } - }; - - var stack_buffer: [16]StackItem = undefined; - var stack = std.ArrayList(StackItem).initBuffer(&stack_buffer); - defer StackItem.closeAll(stack.items); - - stack.appendAssumeCapacity(.{ - .name = sub_path, - .parent_dir = self, - .iter = initial_iterable_dir.iterateAssumeFirstIteration(), - }); - - process_stack: while (stack.items.len != 0) { - var top = &stack.items[stack.items.len - 1]; - while (try top.iter.next()) |entry| { - var treat_as_dir = entry.kind == .directory; - handle_entry: while (true) { - if (treat_as_dir) { - if (stack.unusedCapacitySlice().len >= 1) { - var iterable_dir = top.iter.dir.openDir(entry.name, .{ - .follow_symlinks = false, - .iterate = true, - }) catch |err| switch (err) { - error.NotDir => { - treat_as_dir = false; - continue :handle_entry; - }, - error.FileNotFound => { - // That's fine, we were trying to remove this directory anyway. - break :handle_entry; - }, - - error.AccessDenied, - error.PermissionDenied, - error.SymLinkLoop, - error.ProcessFdQuotaExceeded, - error.NameTooLong, - error.SystemFdQuotaExceeded, - error.NoDevice, - error.SystemResources, - error.Unexpected, - error.BadPathName, - error.NetworkNotFound, - error.DeviceBusy, - error.Canceled, - => |e| return e, - }; - stack.appendAssumeCapacity(.{ - .name = entry.name, - .parent_dir = top.iter.dir, - .iter = iterable_dir.iterateAssumeFirstIteration(), - }); - continue :process_stack; - } else { - try top.iter.dir.deleteTreeMinStackSizeWithKindHint(entry.name, entry.kind); - break :handle_entry; - } - } else { - if (top.iter.dir.deleteFile(entry.name)) { - break :handle_entry; - } else |err| switch (err) { - error.FileNotFound => break :handle_entry, - - // Impossible because we do not pass any path separators. - error.NotDir => unreachable, - - error.IsDir => { - treat_as_dir = true; - continue :handle_entry; - }, - - error.AccessDenied, - error.PermissionDenied, - error.SymLinkLoop, - error.NameTooLong, - error.SystemResources, - error.ReadOnlyFileSystem, - error.FileSystem, - error.FileBusy, - error.BadPathName, - error.NetworkNotFound, - error.Unexpected, - => |e| return e, - } - } - } - } - - // On Windows, we can't delete until the dir's handle has been closed, so - // close it before we try to delete. - top.iter.dir.close(); - - // In order to avoid double-closing the directory when cleaning up - // the stack in the case of an error, we save the relevant portions and - // pop the value from the stack. - const parent_dir = top.parent_dir; - const name = top.name; - stack.items.len -= 1; - - var need_to_retry: bool = false; - parent_dir.deleteDir(name) catch |err| switch (err) { - error.FileNotFound => {}, - error.DirNotEmpty => need_to_retry = true, - else => |e| return e, - }; - - if (need_to_retry) { - // Since we closed the handle that the previous iterator used, we - // need to re-open the dir and re-create the iterator. - var iterable_dir = iterable_dir: { - var treat_as_dir = true; - handle_entry: while (true) { - if (treat_as_dir) { - break :iterable_dir parent_dir.openDir(name, .{ - .follow_symlinks = false, - .iterate = true, - }) catch |err| switch (err) { - error.NotDir => { - treat_as_dir = false; - continue :handle_entry; - }, - error.FileNotFound => { - // That's fine, we were trying to remove this directory anyway. - continue :process_stack; - }, - - error.AccessDenied, - error.PermissionDenied, - error.SymLinkLoop, - error.ProcessFdQuotaExceeded, - error.NameTooLong, - error.SystemFdQuotaExceeded, - error.NoDevice, - error.SystemResources, - error.Unexpected, - error.BadPathName, - error.NetworkNotFound, - error.DeviceBusy, - error.Canceled, - => |e| return e, - }; - } else { - if (parent_dir.deleteFile(name)) { - continue :process_stack; - } else |err| switch (err) { - error.FileNotFound => continue :process_stack, - - // Impossible because we do not pass any path separators. - error.NotDir => unreachable, - - error.IsDir => { - treat_as_dir = true; - continue :handle_entry; - }, - - error.AccessDenied, - error.PermissionDenied, - error.SymLinkLoop, - error.NameTooLong, - error.SystemResources, - error.ReadOnlyFileSystem, - error.FileSystem, - error.FileBusy, - error.BadPathName, - error.NetworkNotFound, - error.Unexpected, - => |e| return e, - } - } - } - }; - // We know there is room on the stack since we are just re-adding - // the StackItem that we previously popped. - stack.appendAssumeCapacity(.{ - .name = name, - .parent_dir = parent_dir, - .iter = iterable_dir.iterateAssumeFirstIteration(), - }); - continue :process_stack; - } - } -} - -/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size. -/// This is slower than `deleteTree` but uses less stack space. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `sub_path` should be encoded as valid UTF-8. -/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void { - return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file); -} - -fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void { - start_over: while (true) { - var dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return; - var cleanup_dir_parent: ?Dir = null; - defer if (cleanup_dir_parent) |*d| d.close(); - - var cleanup_dir = true; - defer if (cleanup_dir) dir.close(); - - // Valid use of max_path_bytes because dir_name_buf will only - // ever store a single path component that was returned from the - // filesystem. - var dir_name_buf: [fs.max_path_bytes]u8 = undefined; - var dir_name: []const u8 = sub_path; - - // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function. - // Go through each entry and if it is not a directory, delete it. If it is a directory, - // open it, and close the original directory. Repeat. Then start the entire operation over. - - scan_dir: while (true) { - var dir_it = dir.iterateAssumeFirstIteration(); - dir_it: while (try dir_it.next()) |entry| { - var treat_as_dir = entry.kind == .directory; - handle_entry: while (true) { - if (treat_as_dir) { - const new_dir = dir.openDir(entry.name, .{ - .follow_symlinks = false, - .iterate = true, - }) catch |err| switch (err) { - error.NotDir => { - treat_as_dir = false; - continue :handle_entry; - }, - error.FileNotFound => { - // That's fine, we were trying to remove this directory anyway. - continue :dir_it; - }, - - error.AccessDenied, - error.PermissionDenied, - error.SymLinkLoop, - error.ProcessFdQuotaExceeded, - error.NameTooLong, - error.SystemFdQuotaExceeded, - error.NoDevice, - error.SystemResources, - error.Unexpected, - error.BadPathName, - error.NetworkNotFound, - error.DeviceBusy, - error.Canceled, - => |e| return e, - }; - if (cleanup_dir_parent) |*d| d.close(); - cleanup_dir_parent = dir; - dir = new_dir; - const result = dir_name_buf[0..entry.name.len]; - @memcpy(result, entry.name); - dir_name = result; - continue :scan_dir; - } else { - if (dir.deleteFile(entry.name)) { - continue :dir_it; - } else |err| switch (err) { - error.FileNotFound => continue :dir_it, - - // Impossible because we do not pass any path separators. - error.NotDir => unreachable, - - error.IsDir => { - treat_as_dir = true; - continue :handle_entry; - }, - - error.AccessDenied, - error.PermissionDenied, - error.SymLinkLoop, - error.NameTooLong, - error.SystemResources, - error.ReadOnlyFileSystem, - error.FileSystem, - error.FileBusy, - error.BadPathName, - error.NetworkNotFound, - error.Unexpected, - => |e| return e, - } - } - } - } - // Reached the end of the directory entries, which means we successfully deleted all of them. - // Now to remove the directory itself. - dir.close(); - cleanup_dir = false; - - if (cleanup_dir_parent) |d| { - d.deleteDir(dir_name) catch |err| switch (err) { - // These two things can happen due to file system race conditions. - error.FileNotFound, error.DirNotEmpty => continue :start_over, - else => |e| return e, - }; - continue :start_over; - } else { - self.deleteDir(sub_path) catch |err| switch (err) { - error.FileNotFound => return, - error.DirNotEmpty => continue :start_over, - else => |e| return e, - }; - return; - } - } - } -} - -/// On successful delete, returns null. -fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir { - return iterable_dir: { - // Treat as a file by default - var treat_as_dir = kind_hint == .directory; - - handle_entry: while (true) { - if (treat_as_dir) { - break :iterable_dir self.openDir(sub_path, .{ - .follow_symlinks = false, - .iterate = true, - }) catch |err| switch (err) { - error.NotDir => { - treat_as_dir = false; - continue :handle_entry; - }, - error.FileNotFound => { - // That's fine, we were trying to remove this directory anyway. - return null; - }, - - error.AccessDenied, - error.PermissionDenied, - error.SymLinkLoop, - error.ProcessFdQuotaExceeded, - error.NameTooLong, - error.SystemFdQuotaExceeded, - error.NoDevice, - error.SystemResources, - error.Unexpected, - error.BadPathName, - error.DeviceBusy, - error.NetworkNotFound, - error.Canceled, - => |e| return e, - }; - } else { - if (self.deleteFile(sub_path)) { - return null; - } else |err| switch (err) { - error.FileNotFound => return null, - - error.IsDir => { - treat_as_dir = true; - continue :handle_entry; - }, - - error.AccessDenied, - error.PermissionDenied, - error.SymLinkLoop, - error.NameTooLong, - error.SystemResources, - error.ReadOnlyFileSystem, - error.NotDir, - error.FileSystem, - error.FileBusy, - error.BadPathName, - error.NetworkNotFound, - error.Unexpected, - => |e| return e, - } - } - } - }; -} - -pub const WriteFileError = File.WriteError || File.OpenError; - -pub const WriteFileOptions = struct { - /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). - /// On WASI, `sub_path` should be encoded as valid UTF-8. - /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. - sub_path: []const u8, - data: []const u8, - flags: File.CreateFlags = .{}, -}; - -/// Writes content to the file system, using the file creation flags provided. -pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void { - var file = try self.createFile(options.sub_path, options.flags); - defer file.close(); - try file.writeAll(options.data); -} - -/// Deprecated in favor of `Io.Dir.AccessError`. -pub const AccessError = Io.Dir.AccessError; - -/// Deprecated in favor of `Io.Dir.access`. -pub fn access(self: Dir, sub_path: []const u8, options: Io.Dir.AccessOptions) AccessError!void { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return Io.Dir.access(self.adaptToNewApi(), io, sub_path, options); -} - -pub const CopyFileOptions = struct { - /// When this is `null` the mode is copied from the source file. - override_mode: ?File.Mode = null, -}; - -pub const CopyFileError = File.OpenError || File.StatError || - AtomicFile.InitError || AtomicFile.FinishError || - File.ReadError || File.WriteError || error{InvalidFileName}; - -/// Atomically creates a new file at `dest_path` within `dest_dir` with the -/// same contents as `source_path` within `source_dir`, overwriting any already -/// existing file. -/// -/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and -/// readily available, there is a possibility of power loss or application -/// termination leaving temporary files present in the same directory as -/// dest_path. -/// -/// On Windows, both paths should be encoded as -/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be -/// encoded as valid UTF-8. On other platforms, both paths are an opaque -/// sequence of bytes with no particular encoding. -/// -/// TODO move this function to Io.Dir -pub fn copyFile( - source_dir: Dir, - source_path: []const u8, - dest_dir: Dir, - dest_path: []const u8, - options: CopyFileOptions, -) CopyFileError!void { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - - const file = try source_dir.openFile(source_path, .{}); - var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{}); - defer file_reader.file.close(io); - - const mode = options.override_mode orelse blk: { - const st = try file_reader.file.stat(io); - file_reader.size = st.size; - break :blk st.mode; - }; - - var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available. - var atomic_file = try dest_dir.atomicFile(dest_path, .{ - .mode = mode, - .write_buffer = &buffer, - }); - defer atomic_file.deinit(); - - _ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { - error.ReadFailed => return file_reader.err.?, - error.WriteFailed => return atomic_file.file_writer.err.?, - }; - - try atomic_file.finish(); -} - -pub const AtomicFileOptions = struct { - mode: File.Mode = File.default_mode, - make_path: bool = false, - write_buffer: []u8, -}; - -/// Directly access the `.file` field, and then call `AtomicFile.finish` to -/// atomically replace `dest_path` with contents. -/// Always call `AtomicFile.deinit` to clean up, regardless of whether -/// `AtomicFile.finish` succeeded. `dest_path` must remain valid until -/// `AtomicFile.deinit` is called. -/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `dest_path` should be encoded as valid UTF-8. -/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding. -pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile { - if (fs.path.dirname(dest_path)) |dirname| { - const dir = if (options.make_path) - try self.makeOpenPath(dirname, .{}) - else - try self.openDir(dirname, .{}); - - return .init(fs.path.basename(dest_path), options.mode, dir, true, options.write_buffer); - } else { - return .init(dest_path, options.mode, self, false, options.write_buffer); - } -} - -pub const Stat = File.Stat; -pub const StatError = File.StatError; - -/// Deprecated in favor of `Io.Dir.stat`. -pub fn stat(self: Dir) StatError!Stat { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return Io.Dir.stat(.{ .handle = self.fd }, io); -} - -pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError; - -/// Deprecated in favor of `Io.Dir.statPath`. -pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return Io.Dir.statPath(.{ .handle = self.fd }, io, sub_path, .{}); -} - -pub const ChmodError = File.ChmodError; - -/// Changes the mode of the directory. -/// The process must have the correct privileges in order to do this -/// successfully, or must have the effective user ID matching the owner -/// of the directory. Additionally, the directory must have been opened -/// with `OpenOptions{ .iterate = true }`. -pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void { - const file: File = .{ .handle = self.fd }; - try file.chmod(new_mode); -} - -/// Changes the owner and group of the directory. -/// The process must have the correct privileges in order to do this -/// successfully. The group may be changed by the owner of the directory to -/// any group of which the owner is a member. Additionally, the directory -/// must have been opened with `OpenOptions{ .iterate = true }`. If the -/// owner or group is specified as `null`, the ID is not changed. -pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void { - const file: File = .{ .handle = self.fd }; - try file.chown(owner, group); -} - -pub const ChownError = File.ChownError; - -const Permissions = File.Permissions; -pub const SetPermissionsError = File.SetPermissionsError; - -/// Sets permissions according to the provided `Permissions` struct. -/// This method is *NOT* available on WASI -pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void { - const file: File = .{ .handle = self.fd }; - try file.setPermissions(permissions); -} - -pub fn adaptToNewApi(dir: Dir) Io.Dir { - return .{ .handle = dir.fd }; -} - -pub fn adaptFromNewApi(dir: Io.Dir) Dir { - return .{ .fd = dir.handle }; -} diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 5e54ba5f7a3fc784a60a4a560b7579cbab6ebbb9..4941d9fe85f9bcb8bfaa78b7060fcbbee6bd0543 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -22,18 +22,10 @@ handle: Handle, pub const Handle = Io.File.Handle; pub const Mode = Io.File.Mode; pub const INode = Io.File.INode; -pub const Uid = posix.uid_t; -pub const Gid = posix.gid_t; +pub const Uid = Io.File.Uid; +pub const Gid = Io.File.Gid; pub const Kind = Io.File.Kind; -/// This is the default mode given to POSIX operating systems for creating -/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first, -/// since most people would expect "-rw-r--r--", for example, when using -/// the `touch` command, which would correspond to `0o644`. However, POSIX -/// libc implementations use `0o666` inside `fopen` and then rely on the -/// process-scoped "umask" setting to adjust this number for file creation. -pub const default_mode: Mode = if (Mode == u0) 0 else 0o666; - /// Deprecated in favor of `Io.File.OpenError`. pub const OpenError = Io.File.OpenError || error{WouldBlock}; /// Deprecated in favor of `Io.File.OpenMode`. @@ -43,53 +35,7 @@ pub const Lock = Io.File.Lock; /// Deprecated in favor of `Io.File.OpenFlags`. pub const OpenFlags = Io.File.OpenFlags; -pub const CreateFlags = struct { - /// Whether the file will be created with read access. - read: bool = false, - - /// If the file already exists, and is a regular file, and the access - /// mode allows writing, it will be truncated to length 0. - truncate: bool = true, - - /// Ensures that this open call creates the file, otherwise causes - /// `error.PathAlreadyExists` to be returned. - exclusive: bool = false, - - /// Open the file with an advisory lock to coordinate with other processes - /// accessing it at the same time. An exclusive lock will prevent other - /// processes from acquiring a lock. A shared lock will prevent other - /// processes from acquiring a exclusive lock, but does not prevent - /// other process from getting their own shared locks. - /// - /// The lock is advisory, except on Linux in very specific circumstances[1]. - /// This means that a process that does not respect the locking API can still get access - /// to the file, despite the lock. - /// - /// On these operating systems, the lock is acquired atomically with - /// opening the file: - /// * Darwin - /// * DragonFlyBSD - /// * FreeBSD - /// * Haiku - /// * NetBSD - /// * OpenBSD - /// On these operating systems, the lock is acquired via a separate syscall - /// after opening the file: - /// * Linux - /// * Windows - /// - /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt - lock: Lock = .none, - - /// Sets whether or not to wait until the file is locked to return. If set to true, - /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file - /// is available to proceed. - lock_nonblocking: bool = false, - - /// For POSIX systems this is the file system mode the file will - /// be created with. On other systems this is always 0. - mode: Mode = default_mode, -}; +pub const CreateFlags = std.Io.File.CreateFlags; pub fn stdout() File { return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdOutput else posix.STDOUT_FILENO }; @@ -259,33 +205,6 @@ pub fn setEndPos(self: File, length: u64) SetEndPosError!void { try posix.ftruncate(self.handle, length); } -pub const SeekError = posix.SeekError; - -/// Repositions read/write file offset relative to the current offset. -/// TODO: integrate with async I/O -pub fn seekBy(self: File, offset: i64) SeekError!void { - return posix.lseek_CUR(self.handle, offset); -} - -/// Repositions read/write file offset relative to the end. -/// TODO: integrate with async I/O -pub fn seekFromEnd(self: File, offset: i64) SeekError!void { - return posix.lseek_END(self.handle, offset); -} - -/// Repositions read/write file offset relative to the beginning. -/// TODO: integrate with async I/O -pub fn seekTo(self: File, offset: u64) SeekError!void { - return posix.lseek_SET(self.handle, offset); -} - -pub const GetSeekPosError = posix.SeekError || StatError; - -/// TODO: integrate with async I/O -pub fn getPos(self: File) GetSeekPosError!u64 { - return posix.lseek_CUR_get(self.handle); -} - pub const GetEndPosError = std.os.windows.GetFileSizeError || StatError; /// TODO: integrate with async I/O @@ -306,11 +225,13 @@ pub fn mode(self: File) ModeError!Mode { return (try self.stat()).mode; } +/// Deprecated in favor of `Io.File.Stat`. pub const Stat = Io.File.Stat; +/// Deprecated in favor of `Io.File.StatError`. pub const StatError = posix.FStatError; -/// Returns `Stat` containing basic information about the `File`. +/// Deprecated in favor of `Io.File.stat`. pub fn stat(self: File) StatError!Stat { var threaded: Io.Threaded = .init_single_threaded; const io = threaded.ioBasic(); @@ -710,7 +631,7 @@ pub const Writer = struct { Unexpected, }; - pub const SeekError = File.SeekError; + pub const SeekError = Io.File.SeekError; /// Number of slices to store on the stack, when trying to send as many byte /// vectors through the underlying write calls as possible. @@ -1268,10 +1189,8 @@ pub fn writerStreaming(file: File, buffer: []u8) Writer { const range_off: windows.LARGE_INTEGER = 0; const range_len: windows.LARGE_INTEGER = 1; -pub const LockError = error{ - SystemResources, - FileLocksNotSupported, -} || posix.UnexpectedError; +/// Deprecated +pub const LockError = Io.File.LockError; /// Blocks when an incompatible lock is held by another process. /// A process may hold only one type of lock (shared or exclusive) on diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 4a2c0117b16c54d411ae4f6d27a38e28ac4b08e1..52baa0699d773a07e2aa7e65e415e09f0b24fe06 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -2065,21 +2065,6 @@ test "chown" { try dir.chown(null, null); } -test "delete a setAsCwd directory on Windows" { - if (native_os != .windows) return error.SkipZigTest; - - var tmp = tmpDir(.{}); - // Set tmp dir as current working directory. - try tmp.dir.setAsCwd(); - tmp.dir.close(); - try testing.expectError(error.FileBusy, tmp.parent_dir.deleteTree(&tmp.sub_path)); - // Now set the parent dir as the current working dir for clean up. - try tmp.parent_dir.setAsCwd(); - try tmp.parent_dir.deleteTree(&tmp.sub_path); - // Close the parent "tmp" so we don't leak the HANDLE. - tmp.parent_dir.close(); -} - test "invalid UTF-8/WTF-8 paths" { const expected_err = switch (native_os) { .wasi => error.BadPathName, diff --git a/lib/std/os.zig b/lib/std/os.zig index 3e846cc9bd6a3d497fdfe53006af67d36d842a30..fdd5ab2480a4a1e4f32ab59ece17115ac97ce372 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -21,7 +21,6 @@ const mem = std.mem; const elf = std.elf; const fs = std.fs; const dl = @import("dynamic_library.zig"); -const max_path_bytes = std.fs.max_path_bytes; const posix = std.posix; const native_os = builtin.os.tag; @@ -56,135 +55,6 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_o else => undefined, }; -pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool { - return switch (os.tag) { - .windows, - .driverkit, - .ios, - .maccatalyst, - .macos, - .tvos, - .visionos, - .watchos, - .linux, - .illumos, - .freebsd, - .serenity, - => true, - - .dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt, - .netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt, - else => false, - }; -} - -/// Return canonical path of handle `fd`. -/// -/// This function is very host-specific and is not universally supported by all hosts. -/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is -/// unsupported on WASI. -/// -/// * On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// * On other platforms, the result is an opaque sequence of bytes with no particular encoding. -/// -/// Calling this function is usually a bug. -pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.RealPathError![]u8 { - if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) { - @compileError("querying for canonical path of a handle is unsupported on this host"); - } - switch (native_os) { - .windows => { - var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]); - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); - return out_buffer[0..end_index]; - }, - .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { - // On macOS, we can use F.GETPATH fcntl command to query the OS for - // the path to the file descriptor. - @memset(out_buffer[0..max_path_bytes], 0); - switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) { - .SUCCESS => {}, - .BADF => return error.FileNotFound, - .NOSPC => return error.NameTooLong, - .NOENT => return error.FileNotFound, - // TODO man pages for fcntl on macOS don't really tell you what - // errno values to expect when command is F.GETPATH... - else => |err| return posix.unexpectedErrno(err), - } - const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes; - return out_buffer[0..len]; - }, - .linux, .serenity => { - var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; - const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/fd/{d}", .{fd}, 0) catch unreachable; - - const target = posix.readlinkZ(proc_path, out_buffer) catch |err| { - switch (err) { - error.NotLink => unreachable, - error.BadPathName => unreachable, - error.UnsupportedReparsePointType => unreachable, // Windows-only - error.NetworkNotFound => unreachable, // Windows-only - else => |e| return e, - } - }; - return target; - }, - .illumos => { - var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined; - const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/path/{d}", .{fd}, 0) catch unreachable; - - const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) { - error.UnsupportedReparsePointType => unreachable, - error.NotLink => unreachable, - else => |e| return e, - }; - return target; - }, - .freebsd => { - var kfile: std.c.kinfo_file = undefined; - kfile.structsize = std.c.KINFO_FILE_SIZE; - switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) { - .SUCCESS => {}, - .BADF => return error.FileNotFound, - else => |err| return posix.unexpectedErrno(err), - } - const len = mem.findScalar(u8, &kfile.path, 0) orelse max_path_bytes; - if (len == 0) return error.NameTooLong; - const result = out_buffer[0..len]; - @memcpy(result, kfile.path[0..len]); - return result; - }, - .dragonfly => { - @memset(out_buffer[0..max_path_bytes], 0); - switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) { - .SUCCESS => {}, - .BADF => return error.FileNotFound, - .RANGE => return error.NameTooLong, - else => |err| return posix.unexpectedErrno(err), - } - const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes; - return out_buffer[0..len]; - }, - .netbsd => { - @memset(out_buffer[0..max_path_bytes], 0); - switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) { - .SUCCESS => {}, - .ACCES => return error.AccessDenied, - .BADF => return error.FileNotFound, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .RANGE => return error.NameTooLong, - else => |err| return posix.unexpectedErrno(err), - } - const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes; - return out_buffer[0..len]; - }, - else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above - } -} - pub const FstatError = error{ SystemResources, AccessDenied, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index feeeeb9220fdc252c174a0c2c3ef76123274a7fc..73f1bf8b619659c90df61a305a44e3c892e7c0c3 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -319,35 +319,6 @@ pub const FChmodError = error{ ReadOnlyFileSystem, } || UnexpectedError; -/// Changes the mode of the file referred to by the file descriptor. -/// -/// The process must have the correct privileges in order to do this -/// successfully, or must have the effective user ID matching the owner -/// of the file. -pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void { - if (!fs.has_executable_bit) @compileError("fchmod unsupported by target OS"); - - while (true) { - const res = system.fchmod(fd, mode); - switch (errno(res)) { - .SUCCESS => return, - .INTR => continue, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.FileNotFound, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } - } -} - pub const FChmodAtError = FChmodError || error{ /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded /// `PATH_MAX`. @@ -533,50 +504,6 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr } } -pub const FChownError = error{ - AccessDenied, - PermissionDenied, - InputOutput, - SymLinkLoop, - FileNotFound, - SystemResources, - ReadOnlyFileSystem, -} || UnexpectedError; - -/// Changes the owner and group of the file referred to by the file descriptor. -/// The process must have the correct privileges in order to do this -/// successfully. The group may be changed by the owner of the directory to -/// any group of which the owner is a member. If the owner or group is -/// specified as `null`, the ID is not changed. -pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void { - switch (native_os) { - .windows, .wasi => @compileError("Unsupported OS"), - else => {}, - } - - while (true) { - const res = system.fchown(fd, owner orelse ~@as(uid_t, 0), group orelse ~@as(gid_t, 0)); - - switch (errno(res)) { - .SUCCESS => return, - .INTR => continue, - .BADF => unreachable, // Can be reached if the fd refers to a directory opened without `Dir.OpenOptions{ .iterate = true }` - - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.FileNotFound, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } - } -} - pub const RebootError = error{ PermissionDenied, } || UnexpectedError; @@ -1926,150 +1853,6 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { } } -pub const SymLinkError = error{ - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to create a new symbolic link relative to it. - AccessDenied, - PermissionDenied, - DiskQuota, - PathAlreadyExists, - FileSystem, - SymLinkLoop, - FileNotFound, - SystemResources, - NoSpaceLeft, - ReadOnlyFileSystem, - NotDir, - NameTooLong, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, -} || UnexpectedError; - -/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. -/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent -/// one; the latter case is known as a dangling link. -/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -/// If `sym_link_path` exists, it will not be overwritten. -/// See also `symlinkZ. -pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { - if (native_os == .windows) { - @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); - } else if (native_os == .wasi and !builtin.link_libc) { - return symlinkat(target_path, AT.FDCWD, sym_link_path); - } - const target_path_c = try toPosixPath(target_path); - const sym_link_path_c = try toPosixPath(sym_link_path); - return symlinkZ(&target_path_c, &sym_link_path_c); -} - -/// This is the same as `symlink` except the parameters are null-terminated pointers. -/// See also `symlink`. -pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void { - if (native_os == .windows) { - @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); - } else if (native_os == .wasi and !builtin.link_libc) { - return symlinkatZ(target_path, fs.cwd().fd, sym_link_path); - } - switch (errno(system.symlink(target_path, sym_link_path))) { - .SUCCESS => return, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .ROFS => return error.ReadOnlyFileSystem, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -/// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string -/// `target_path` **relative** to `newdirfd` directory handle. -/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent -/// one; the latter case is known as a dangling link. -/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -/// If `sym_link_path` exists, it will not be overwritten. -/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`. -pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { - if (native_os == .windows) { - @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); - } else if (native_os == .wasi and !builtin.link_libc) { - return symlinkatWasi(target_path, newdirfd, sym_link_path); - } - const target_path_c = try toPosixPath(target_path); - const sym_link_path_c = try toPosixPath(sym_link_path); - return symlinkatZ(&target_path_c, newdirfd, &sym_link_path_c); -} - -/// WASI-only. The same as `symlinkat` but targeting WASI. -/// See also `symlinkat`. -pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { - switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) { - .SUCCESS => {}, - .FAULT => unreachable, - .INVAL => unreachable, - .BADF => unreachable, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .ROFS => return error.ReadOnlyFileSystem, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -/// The same as `symlinkat` except the parameters are null-terminated pointers. -/// See also `symlinkat`. -pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void { - if (native_os == .windows) { - @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); - } else if (native_os == .wasi and !builtin.link_libc) { - return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0)); - } - switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) { - .SUCCESS => return, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .ROFS => return error.ReadOnlyFileSystem, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - pub const LinkError = UnexpectedError || error{ AccessDenied, PermissionDenied, @@ -2216,334 +1999,6 @@ pub fn linkat( return try linkatZ(olddir, &old, newdir, &new, flags); } -pub const UnlinkError = error{ - FileNotFound, - - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to unlink a resource by path relative to it. - AccessDenied, - PermissionDenied, - FileBusy, - FileSystem, - IsDir, - SymLinkLoop, - NameTooLong, - NotDir, - SystemResources, - ReadOnlyFileSystem, - - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - /// Windows: file paths cannot contain these characters: - /// '/', '*', '?', '"', '<', '>', '|' - BadPathName, - - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, -} || UnexpectedError; - -/// Delete a name and possibly the file it refers to. -/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `file_path` should be encoded as valid UTF-8. -/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. -/// See also `unlinkZ`. -pub fn unlink(file_path: []const u8) UnlinkError!void { - if (native_os == .wasi and !builtin.link_libc) { - return unlinkat(AT.FDCWD, file_path, 0) catch |err| switch (err) { - error.DirNotEmpty => unreachable, // only occurs when targeting directories - else => |e| return e, - }; - } else if (native_os == .windows) { - const file_path_w = try windows.sliceToPrefixedFileW(null, file_path); - return unlinkW(file_path_w.span()); - } else { - const file_path_c = try toPosixPath(file_path); - return unlinkZ(&file_path_c); - } -} - -/// Same as `unlink` except the parameter is null terminated. -pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { - if (native_os == .windows) { - const file_path_w = try windows.cStrToPrefixedFileW(null, file_path); - return unlinkW(file_path_w.span()); - } else if (native_os == .wasi and !builtin.link_libc) { - return unlink(mem.sliceTo(file_path, 0)); - } - switch (errno(system.unlink(file_path))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BUSY => return error.FileBusy, - .FAULT => unreachable, - .INVAL => unreachable, - .IO => return error.FileSystem, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .ROFS => return error.ReadOnlyFileSystem, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 LE encoded. -pub fn unlinkW(file_path_w: []const u16) UnlinkError!void { - windows.DeleteFile(file_path_w, .{ .dir = fs.cwd().fd }) catch |err| switch (err) { - error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true - else => |e| return e, - }; -} - -pub const UnlinkatError = UnlinkError || error{ - /// When passing `AT.REMOVEDIR`, this error occurs when the named directory is not empty. - DirNotEmpty, -}; - -/// Delete a file name and possibly the file it refers to, based on an open directory handle. -/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `file_path` should be encoded as valid UTF-8. -/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. -/// Asserts that the path parameter has no null bytes. -pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { - if (native_os == .windows) { - const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path); - return unlinkatW(dirfd, file_path_w.span(), flags); - } else if (native_os == .wasi and !builtin.link_libc) { - return unlinkatWasi(dirfd, file_path, flags); - } else { - const file_path_c = try toPosixPath(file_path); - return unlinkatZ(dirfd, &file_path_c, flags); - } -} - -/// WASI-only. Same as `unlinkat` but targeting WASI. -/// See also `unlinkat`. -pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { - const remove_dir = (flags & AT.REMOVEDIR) != 0; - const res = if (remove_dir) - wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len) - else - wasi.path_unlink_file(dirfd, file_path.ptr, file_path.len); - switch (res) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BUSY => return error.FileBusy, - .FAULT => unreachable, - .IO => return error.FileSystem, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .ROFS => return error.ReadOnlyFileSystem, - .NOTEMPTY => return error.DirNotEmpty, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - - .INVAL => unreachable, // invalid flags, or pathname has . as last component - .BADF => unreachable, // always a race condition - - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `unlinkat` but `file_path` is a null-terminated string. -pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { - if (native_os == .windows) { - const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path_c); - return unlinkatW(dirfd, file_path_w.span(), flags); - } else if (native_os == .wasi and !builtin.link_libc) { - return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags); - } - switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BUSY => return error.FileBusy, - .FAULT => unreachable, - .IO => return error.FileSystem, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .ROFS => return error.ReadOnlyFileSystem, - .EXIST => return error.DirNotEmpty, - .NOTEMPTY => return error.DirNotEmpty, - .ILSEQ => return error.BadPathName, - - .INVAL => unreachable, // invalid flags, or pathname has . as last component - .BADF => unreachable, // always a race condition - - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only. -pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void { - const remove_dir = (flags & AT.REMOVEDIR) != 0; - return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); -} - -pub const RenameError = error{ - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to rename a resource by path relative to it. - /// - /// On Windows, this error may be returned instead of PathAlreadyExists when - /// renaming a directory over an existing directory. - AccessDenied, - PermissionDenied, - FileBusy, - DiskQuota, - IsDir, - SymLinkLoop, - LinkQuotaExceeded, - NameTooLong, - FileNotFound, - NotDir, - SystemResources, - NoSpaceLeft, - PathAlreadyExists, - ReadOnlyFileSystem, - RenameAcrossMountPoints, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, - NoDevice, - SharingViolation, - PipeBusy, - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, - /// On Windows, antivirus software is enabled by default. It can be - /// disabled, but Windows Update sometimes ignores the user's preference - /// and re-enables it. When enabled, antivirus software on Windows - /// intercepts file system operations and makes them significantly slower - /// in addition to possibly failing with this error code. - AntivirusInterference, -} || UnexpectedError; - -/// Change the name or location of a file. -/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { - if (native_os == .wasi and !builtin.link_libc) { - return renameat(AT.FDCWD, old_path, AT.FDCWD, new_path); - } else if (native_os == .windows) { - const old_path_w = try windows.sliceToPrefixedFileW(null, old_path); - const new_path_w = try windows.sliceToPrefixedFileW(null, new_path); - return renameW(old_path_w.span().ptr, new_path_w.span().ptr); - } else { - const old_path_c = try toPosixPath(old_path); - const new_path_c = try toPosixPath(new_path); - return renameZ(&old_path_c, &new_path_c); - } -} - -/// Same as `rename` except the parameters are null-terminated. -pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void { - if (native_os == .windows) { - const old_path_w = try windows.cStrToPrefixedFileW(null, old_path); - const new_path_w = try windows.cStrToPrefixedFileW(null, new_path); - return renameW(old_path_w.span().ptr, new_path_w.span().ptr); - } else if (native_os == .wasi and !builtin.link_libc) { - return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0)); - } - switch (errno(system.rename(old_path, new_path))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BUSY => return error.FileBusy, - .DQUOT => return error.DiskQuota, - .FAULT => unreachable, - .INVAL => unreachable, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .EXIST => return error.PathAlreadyExists, - .NOTEMPTY => return error.PathAlreadyExists, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.RenameAcrossMountPoints, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded. -/// Assumes target is Windows. -pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void { - const cwd_handle = std.fs.cwd().fd; - return windows.RenameFile(cwd_handle, mem.span(old_path), cwd_handle, mem.span(new_path), true); -} - -/// Change the name or location of a file based on an open directory handle. -/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn renameat( - old_dir_fd: fd_t, - old_path: []const u8, - new_dir_fd: fd_t, - new_path: []const u8, -) RenameError!void { - if (native_os == .windows) { - const old_path_w = try windows.sliceToPrefixedFileW(old_dir_fd, old_path); - const new_path_w = try windows.sliceToPrefixedFileW(new_dir_fd, new_path); - return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); - } else if (native_os == .wasi and !builtin.link_libc) { - const old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path }; - const new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path }; - return renameatWasi(old, new); - } else { - const old_path_c = try toPosixPath(old_path); - const new_path_c = try toPosixPath(new_path); - return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c); - } -} - -/// WASI-only. Same as `renameat` expect targeting WASI. -/// See also `renameat`. -fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void { - 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)) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BUSY => return error.FileBusy, - .DQUOT => return error.DiskQuota, - .FAULT => unreachable, - .INVAL => unreachable, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .EXIST => return error.PathAlreadyExists, - .NOTEMPTY => return error.PathAlreadyExists, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.RenameAcrossMountPoints, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - /// An fd-relative file path /// /// This is currently only used for WASI-specific functionality, but the concept @@ -2555,58 +2010,6 @@ const RelativePathWasi = struct { relative_path: []const u8, }; -/// Same as `renameat` except the parameters are null-terminated. -pub fn renameatZ( - old_dir_fd: fd_t, - old_path: [*:0]const u8, - new_dir_fd: fd_t, - new_path: [*:0]const u8, -) RenameError!void { - if (native_os == .windows) { - const old_path_w = try windows.cStrToPrefixedFileW(old_dir_fd, old_path); - const new_path_w = try windows.cStrToPrefixedFileW(new_dir_fd, new_path); - return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); - } else if (native_os == .wasi and !builtin.link_libc) { - return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0)); - } - - switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BUSY => return error.FileBusy, - .DQUOT => return error.DiskQuota, - .FAULT => unreachable, - .INVAL => unreachable, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .EXIST => return error.PathAlreadyExists, - .NOTEMPTY => return error.PathAlreadyExists, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.RenameAcrossMountPoints, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -/// Same as `renameat` but Windows-only and the path parameters are -/// [WTF-16](https://wtf-8.codeberg.page/#potentially-ill-formed-utf-16) encoded. -pub fn renameatW( - old_dir_fd: fd_t, - old_path_w: []const u16, - new_dir_fd: fd_t, - new_path_w: []const u16, - ReplaceIfExists: windows.BOOLEAN, -) RenameError!void { - return windows.RenameFile(old_dir_fd, old_path_w, new_dir_fd, new_path_w, ReplaceIfExists != 0); -} - /// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_dir_path` should be encoded as valid UTF-8. /// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding. @@ -2723,84 +2126,6 @@ pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void { windows.CloseHandle(sub_dir_handle); } -pub const DeleteDirError = error{ - AccessDenied, - PermissionDenied, - FileBusy, - SymLinkLoop, - NameTooLong, - FileNotFound, - SystemResources, - NotDir, - DirNotEmpty, - ReadOnlyFileSystem, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, -} || UnexpectedError; - -/// Deletes an empty directory. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn rmdir(dir_path: []const u8) DeleteDirError!void { - if (native_os == .wasi and !builtin.link_libc) { - return unlinkat(AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) { - error.FileSystem => unreachable, // only occurs when targeting files - error.IsDir => unreachable, // only occurs when targeting files - else => |e| return e, - }; - } else if (native_os == .windows) { - const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path); - return rmdirW(dir_path_w.span()); - } else { - const dir_path_c = try toPosixPath(dir_path); - return rmdirZ(&dir_path_c); - } -} - -/// Same as `rmdir` except the parameter is null-terminated. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { - if (native_os == .windows) { - const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path); - return rmdirW(dir_path_w.span()); - } else if (native_os == .wasi and !builtin.link_libc) { - return rmdir(mem.sliceTo(dir_path, 0)); - } - switch (errno(system.rmdir(dir_path))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BUSY => return error.FileBusy, - .FAULT => unreachable, - .INVAL => return error.BadPathName, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .EXIST => return error.DirNotEmpty, - .NOTEMPTY => return error.DirNotEmpty, - .ROFS => return error.ReadOnlyFileSystem, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -/// Windows-only. Same as `rmdir` except the parameter is WTF-16 LE encoded. -pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void { - return windows.DeleteFile(dir_path_w, .{ .dir = fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) { - error.IsDir => unreachable, - else => |e| return e, - }; -} - pub const ChangeCurDirError = error{ AccessDenied, FileSystem, @@ -2889,194 +2214,6 @@ pub fn fchdir(dirfd: fd_t) FchdirError!void { } } -pub const ReadLinkError = error{ - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to read value of a symbolic link relative to it. - AccessDenied, - PermissionDenied, - FileSystem, - SymLinkLoop, - NameTooLong, - FileNotFound, - SystemResources, - NotLink, - NotDir, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, - /// Windows-only. This error may occur if the opened reparse point is - /// of unsupported type. - UnsupportedReparsePointType, - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, - /// On Windows, antivirus software is enabled by default. It can be - /// disabled, but Windows Update sometimes ignores the user's preference - /// and re-enables it. When enabled, antivirus software on Windows - /// intercepts file system operations and makes them significantly slower - /// in addition to possibly failing with this error code. - AntivirusInterference, -} || UnexpectedError; - -/// Read value of a symbolic link. -/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `file_path` should be encoded as valid UTF-8. -/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. -/// The return value is a slice of `out_buffer` from index 0. -/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, the result is encoded as UTF-8. -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (native_os == .wasi and !builtin.link_libc) { - return readlinkat(AT.FDCWD, file_path, out_buffer); - } else if (native_os == .windows) { - var file_path_w = try windows.sliceToPrefixedFileW(null, file_path); - const result_w = try readlinkW(file_path_w.span(), &file_path_w.data); - - const len = std.unicode.calcWtf8Len(result_w); - if (len > out_buffer.len) return error.NameTooLong; - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w); - return out_buffer[0..end_index]; - } else { - const file_path_c = try toPosixPath(file_path); - return readlinkZ(&file_path_c, out_buffer); - } -} - -/// Windows-only. Same as `readlink` except `file_path` is WTF-16 LE encoded, NT-prefixed. -/// The result is encoded as WTF-16 LE. -/// -/// `file_path` will never be accessed after `out_buffer` has been written to, so it -/// is safe to reuse a single buffer for both. -/// -/// See also `readlinkZ`. -pub fn readlinkW(file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 { - return windows.ReadLink(fs.cwd().fd, file_path, out_buffer); -} - -/// Same as `readlink` except `file_path` is null-terminated. -pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (native_os == .windows) { - var file_path_w = try windows.cStrToPrefixedFileW(null, file_path); - const result_w = try readlinkW(file_path_w.span(), &file_path_w.data); - - const len = std.unicode.calcWtf8Len(result_w); - if (len > out_buffer.len) return error.NameTooLong; - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w); - return out_buffer[0..end_index]; - } else if (native_os == .wasi and !builtin.link_libc) { - return readlink(mem.sliceTo(file_path, 0), out_buffer); - } - const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); - switch (errno(rc)) { - .SUCCESS => return out_buffer[0..@bitCast(rc)], - .ACCES => return error.AccessDenied, - .FAULT => unreachable, - .INVAL => return error.NotLink, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle. -/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `file_path` should be encoded as valid UTF-8. -/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. -/// The return value is a slice of `out_buffer` from index 0. -/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, the result is encoded as UTF-8. -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. -pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (native_os == .wasi and !builtin.link_libc) { - return readlinkatWasi(dirfd, file_path, out_buffer); - } - if (native_os == .windows) { - var file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path); - const result_w = try readlinkatW(dirfd, file_path_w.span(), &file_path_w.data); - - const len = std.unicode.calcWtf8Len(result_w); - if (len > out_buffer.len) return error.NameTooLong; - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w); - return out_buffer[0..end_index]; - } - const file_path_c = try toPosixPath(file_path); - return readlinkatZ(dirfd, &file_path_c, out_buffer); -} - -/// WASI-only. Same as `readlinkat` but targets WASI. -/// See also `readlinkat`. -pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { - var bufused: usize = undefined; - switch (wasi.path_readlink(dirfd, file_path.ptr, file_path.len, out_buffer.ptr, out_buffer.len, &bufused)) { - .SUCCESS => return out_buffer[0..bufused], - .ACCES => return error.AccessDenied, - .FAULT => unreachable, - .INVAL => return error.NotLink, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -/// Windows-only. Same as `readlinkat` except `file_path` WTF16 LE encoded, NT-prefixed. -/// The result is encoded as WTF-16 LE. -/// -/// `file_path` will never be accessed after `out_buffer` has been written to, so it -/// is safe to reuse a single buffer for both. -/// -/// See also `readlinkat`. -pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 { - return windows.ReadLink(dirfd, file_path, out_buffer); -} - -/// Same as `readlinkat` except `file_path` is null-terminated. -/// See also `readlinkat`. -pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (native_os == .windows) { - var file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path); - const result_w = try readlinkatW(dirfd, file_path_w.span(), &file_path_w.data); - - const len = std.unicode.calcWtf8Len(result_w); - if (len > out_buffer.len) return error.NameTooLong; - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w); - return out_buffer[0..end_index]; - } else if (native_os == .wasi and !builtin.link_libc) { - return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer); - } - const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); - switch (errno(rc)) { - .SUCCESS => return out_buffer[0..@bitCast(rc)], - .ACCES => return error.AccessDenied, - .FAULT => unreachable, - .INVAL => return error.NotLink, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - pub const SetEidError = error{ InvalidUserId, PermissionDenied, @@ -4814,157 +3951,6 @@ pub fn flock(fd: fd_t, operation: i32) FlockError!void { } } -pub const RealPathError = error{ - FileNotFound, - AccessDenied, - PermissionDenied, - NameTooLong, - NotSupported, - NotDir, - SymLinkLoop, - InputOutput, - FileTooBig, - IsDir, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - NoDevice, - SystemResources, - NoSpaceLeft, - FileSystem, - DeviceBusy, - ProcessNotFound, - - SharingViolation, - PipeBusy, - - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, - - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, - - PathAlreadyExists, - - /// On Windows, antivirus software is enabled by default. It can be - /// disabled, but Windows Update sometimes ignores the user's preference - /// and re-enables it. When enabled, antivirus software on Windows - /// intercepts file system operations and makes them significantly slower - /// in addition to possibly failing with this error code. - AntivirusInterference, - - /// On Windows, the volume does not contain a recognized file system. File - /// system drivers might not be loaded, or the volume may be corrupt. - UnrecognizedVolume, - - Canceled, -} || UnexpectedError; - -/// Return the canonicalized absolute pathname. -/// -/// Expands all symbolic links and resolves references to `.`, `..`, and -/// extra `/` characters in `pathname`. -/// -/// On Windows, `pathname` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// -/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. -/// -/// The return value is a slice of `out_buffer`, but not necessarily from the beginning. -/// -/// See also `realpathZ` and `realpathW`. -/// -/// * On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// * On other platforms, the result is an opaque sequence of bytes with no particular encoding. -/// -/// Calling this function is usually a bug. -pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { - if (native_os == .windows) { - var pathname_w = try windows.sliceToPrefixedFileW(null, pathname); - - const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data); - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); - return out_buffer[0..end_index]; - } else if (native_os == .wasi and !builtin.link_libc) { - @compileError("WASI does not support os.realpath"); - } - const pathname_c = try toPosixPath(pathname); - return realpathZ(&pathname_c, out_buffer); -} - -/// Same as `realpath` except `pathname` is null-terminated. -/// -/// Calling this function is usually a bug. -pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { - if (native_os == .windows) { - var pathname_w = try windows.cStrToPrefixedFileW(null, pathname); - - const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data); - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); - return out_buffer[0..end_index]; - } else if (native_os == .wasi and !builtin.link_libc) { - return realpath(mem.sliceTo(pathname, 0), out_buffer); - } - if (!builtin.link_libc) { - const flags: O = switch (native_os) { - .linux => .{ - .NONBLOCK = true, - .CLOEXEC = true, - .PATH = true, - }, - else => .{ - .NONBLOCK = true, - .CLOEXEC = true, - }, - }; - const fd = openZ(pathname, flags, 0) catch |err| switch (err) { - error.FileLocksNotSupported => unreachable, - error.WouldBlock => unreachable, - error.FileBusy => unreachable, // not asking for write permissions - else => |e| return e, - }; - defer close(fd); - - return std.os.getFdPath(fd, out_buffer); - } - const result_path = std.c.realpath(pathname, out_buffer) orelse switch (@as(E, @enumFromInt(std.c._errno().*))) { - .SUCCESS => unreachable, - .INVAL => unreachable, - .BADF => unreachable, - .FAULT => unreachable, - .ACCES => return error.AccessDenied, - .NOENT => return error.FileNotFound, - .OPNOTSUPP => return error.NotSupported, - .NOTDIR => return error.NotDir, - .NAMETOOLONG => return error.NameTooLong, - .LOOP => return error.SymLinkLoop, - .IO => return error.InputOutput, - else => |err| return unexpectedErrno(err), - }; - return mem.sliceTo(result_path, 0); -} - -/// Deprecated: use `realpathW2`. -/// -/// Same as `realpath` except `pathname` is WTF16LE-encoded. -/// -/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// -/// Calling this function is usually a bug. -pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { - return fs.cwd().realpathW(pathname, out_buffer); -} - -/// Same as `realpath` except `pathname` is WTF16LE-encoded. -/// -/// The result is encoded as WTF16LE. -/// -/// Calling this function is usually a bug. -pub fn realpathW2(pathname: []const u16, out_buffer: *[std.os.windows.PATH_MAX_WIDE]u16) RealPathError![]u16 { - return fs.cwd().realpathW2(pathname, out_buffer); -} - /// Spurious wakeups are possible and no precision of timing is guaranteed. pub fn nanosleep(seconds: u64, nanoseconds: u64) void { var req = timespec{ diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index a3ce039ee8f3887c8413f2b6ed8f367f1601c28c..a4970b3b5093290ba6a8f8aafd3d1659c8ce59de 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -786,7 +786,9 @@ test glibcVerFromLinkName { } fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { - var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { + const cwd: Io.Dir = .cwd(); + + var dir = cwd.openDir(io, rpath, .{}) catch |err| switch (err) { error.NameTooLong => return error.Unexpected, error.BadPathName => return error.Unexpected, error.DeviceBusy => return error.Unexpected, @@ -805,7 +807,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { error.Unexpected => |e| return e, error.Canceled => |e| return e, }; - defer dir.close(); + defer dir.close(io); // Now we have a candidate for the path to libc shared object. In // the past, we used readlink() here because the link name would -- 2.54.0 From 814480db7cd0ab1d8441919223a88ee411f54ea8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 4 Dec 2025 16:03:33 -0800 Subject: [PATCH 004/195] std: all File functions moved to std.Io --- CMakeLists.txt | 1 - lib/std/Io.zig | 17 +- lib/std/Io/Dir.zig | 89 +- lib/std/Io/File.zig | 811 ++++++----------- lib/std/Io/File/Atomic.zig | 99 ++ lib/std/Io/File/Reader.zig | 395 ++++++++ lib/std/Io/File/Writer.zig | 566 ++++++++++++ lib/std/Io/IoUring.zig | 4 +- lib/std/Io/Threaded.zig | 1024 +++++++++++++++++++-- lib/std/debug/SelfInfo/Windows.zig | 2 +- lib/std/fs.zig | 6 - lib/std/fs/File.zig | 1356 ---------------------------- lib/std/os/windows.zig | 74 -- lib/std/posix.zig | 255 +----- lib/std/process/Child.zig | 2 +- lib/std/std.zig | 3 + lib/std/zig/system.zig | 4 +- 17 files changed, 2376 insertions(+), 2332 deletions(-) create mode 100644 lib/std/Io/File/Atomic.zig create mode 100644 lib/std/Io/File/Reader.zig create mode 100644 lib/std/Io/File/Writer.zig delete mode 100644 lib/std/fs/File.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a5db03471c4076519fde2d9c1b2abc019dae3c8..3b06f36e616574f8e989a36349738030adc0d96c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -436,7 +436,6 @@ set(ZIG_STAGE2_SOURCES lib/std/fmt.zig lib/std/fmt/parse_float.zig lib/std/fs.zig - lib/std/fs/File.zig lib/std/fs/get_app_data_dir.zig lib/std/fs/path.zig lib/std/hash.zig diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 8633ae80b0b3a8abc26a97040def8cae9dcc9c2b..4949466b8e26f2eb8547468cb2c52a67e1e30ae2 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -679,11 +679,13 @@ pub const VTable = struct { dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void, dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.RenameError!void, dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize, - dirSetMode: *const fn (?*anyopaque, Dir, File.Mode) Dir.SetModeError!void, dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void, dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void, + dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, + dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, + fileLength: *const fn (?*anyopaque, File) File.LengthError!u64, fileClose: *const fn (?*anyopaque, File) void, fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize, fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize, @@ -694,6 +696,19 @@ pub const VTable = struct { fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void, fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void, openSelfExe: *const fn (?*anyopaque, File.OpenFlags) File.OpenSelfExeError!File, + fileSync: *const fn (?*anyopaque, File) File.SyncError!void, + fileIsTty: *const fn (?*anyopaque, File) Cancelable!bool, + fileEnableAnsiEscapeCodes: *const fn (?*anyopaque, File) File.EnableAnsiEscapeCodesError!void, + fileSupportsAnsiEscapeCodes: *const fn (?*anyopaque, File) Cancelable!bool, + fileSetLength: *const fn (?*anyopaque, File, u64) File.SetLengthError!void, + fileSetOwner: *const fn (?*anyopaque, File, ?File.Uid, ?File.Gid) File.SetOwnerError!void, + fileSetPermissions: *const fn (?*anyopaque, File, File.Permissions) File.SetPermissionsError!void, + fileSetTimestamps: *const fn (?*anyopaque, File, last_accessed: Timestamp, last_modified: Timestamp, File.SetTimestampsOptions) File.SetTimestampsError!void, + fileSetTimestampsNow: *const fn (?*anyopaque, File, File.SetTimestampsOptions) File.SetTimestampsError!void, + fileLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!void, + fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool, + fileUnlock: *const fn (?*anyopaque, File) void, + fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void, now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, sleep: *const fn (?*anyopaque, Timeout) SleepError!void, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index c743ba7ccac55c6c4a55ac82cc39e3ebe3d7fae3..51fe6bbb5f078f6a971d947d84c07fcd14f56967 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -11,9 +11,6 @@ const Allocator = std.mem.Allocator; handle: Handle, -pub const Mode = Io.File.Mode; -pub const default_mode: Mode = 0o755; - pub const Entry = struct { name: []const u8, kind: File.Kind, @@ -435,10 +432,10 @@ pub const PrevStatus = enum { pub const UpdateFileError = File.OpenError; -/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If +/// Check the file size, mtime, and permissions of `source_path` and `dest_path`. If /// they are equal, does nothing. Otherwise, atomically copies `source_path` to /// `dest_path`, creating the parent directory hierarchy as needed. The -/// destination file gains the mtime, atime, and mode of the source file so +/// destination file gains the mtime, atime, and permissions of the source file so /// that the next call to `updateFile` will not need a copy. /// /// Returns the previous status of the file before updating. @@ -459,7 +456,7 @@ pub fn updateFile( defer src_file.close(io); const src_stat = try src_file.stat(io); - const actual_mode = options.override_mode orelse src_stat.mode; + const actual_permissions = options.override_permissions orelse src_stat.permissions; check_dest_stat: { const dest_stat = blk: { var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) { @@ -473,19 +470,19 @@ pub fn updateFile( if (src_stat.size == dest_stat.size and src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and - actual_mode == dest_stat.mode) + actual_permissions == dest_stat.permissions) { return .fresh; } } if (std.fs.path.dirname(dest_path)) |dirname| { - try dest_dir.makePathMode(io, dirname, default_mode); + try dest_dir.makePath(io, dirname, .default_dir); } var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available. var atomic_file = try std.fs.Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{ - .mode = actual_mode, + .permissions = actual_permissions, .write_buffer = &buffer, }); defer atomic_file.deinit(); @@ -555,17 +552,12 @@ pub const MakeError = error{ /// Related: /// * `makePath` /// * `makeDirAbsolute` -pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8) MakeError!void { - return io.vtable.dirMake(io.userdata, dir, sub_path, default_mode); +pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakeError!void { + return io.vtable.dirMake(io.userdata, dir, sub_path, permissions); } pub const MakePathError = MakeError || StatPathError; -/// Same as `makePathMode` but passes `default_mode`. -pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void { - _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, default_mode); -} - /// Creates parent directories as necessary to ensure `sub_path` exists as a directory. /// /// Returns success if the path already exists and is a directory. @@ -587,16 +579,16 @@ pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void { /// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`, /// meaning a `sub_path` like "first/../second" will create both a `./first` /// and a `./second` directory. -pub fn makePathMode(dir: Dir, io: Io, sub_path: []const u8, mode: Mode) MakePathError!void { - _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, mode); +pub fn makePath(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakePathError!void { + _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, permissions); } pub const MakePathStatus = enum { existed, created }; /// Same as `makePath` except returns whether the path already existed or was /// successfully created. -pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus { - return io.vtable.dirMakePath(io.userdata, dir, sub_path, default_mode); +pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakePathError!MakePathStatus { + return io.vtable.dirMakePath(io.userdata, dir, sub_path, permissions); } pub const MakeOpenPathError = MakeError || OpenError || StatPathError; @@ -609,8 +601,8 @@ pub const MakeOpenPathError = MakeError || OpenError || StatPathError; /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) MakeOpenPathError!Dir { - return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options); +pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions, options: OpenOptions) MakeOpenPathError!Dir { + return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, permissions, options); } pub const Stat = File.Stat; @@ -1453,8 +1445,8 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, sub_path: []const u8, kind_hint: File. } pub const CopyFileOptions = struct { - /// When this is `null` the mode is copied from the source file. - override_mode: ?File.Mode = null, + /// When this is `null` the permissions are copied from the source file. + override_permissions: ?File.Permissions = null, }; pub const CopyFileError = File.OpenError || File.StatError || @@ -1486,15 +1478,15 @@ pub fn copyFile( var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{}); defer file_reader.file.close(io); - const mode = options.override_mode orelse blk: { + const permissions = options.override_permissions orelse blk: { const st = try file_reader.file.stat(io); file_reader.size = st.size; - break :blk st.mode; + break :blk st.permissions; }; var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available. var atomic_file = try dest_dir.atomicFile(io, dest_path, .{ - .mode = mode, + .permissions = permissions, .write_buffer = &buffer, }); defer atomic_file.deinit(io); @@ -1508,7 +1500,7 @@ pub fn copyFile( } pub const AtomicFileOptions = struct { - mode: File.Mode = File.default_mode, + permissions: File.Permissions = .default_file, make_path: bool = false, write_buffer: []u8, }; @@ -1530,13 +1522,14 @@ pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFil else try parent.openDir(io, dirname, .{}); - return .init(std.fs.path.basename(dest_path), options.mode, dir, true, options.write_buffer); + return .init(std.fs.path.basename(dest_path), options.permissions, dir, true, options.write_buffer); } else { - return .init(dest_path, options.mode, parent, false, options.write_buffer); + return .init(dest_path, options.permissions, parent, false, options.write_buffer); } } -pub const SetModeError = File.SetModeError; +pub const SetPermissionsError = File.SetPermissionsError; +pub const Permissions = File.Permissions; /// Also known as "chmod". /// @@ -1544,8 +1537,8 @@ pub const SetModeError = File.SetModeError; /// successfully, or must have the effective user ID matching the owner /// of the directory. Additionally, the directory must have been opened /// with `OpenOptions.iterate` set to `true`. -pub fn setMode(dir: Dir, io: Io, new_mode: File.Mode) SetModeError!void { - return io.vtable.dirSetMode(io.userdata, dir, new_mode); +pub fn setPermissions(dir: Dir, io: Io, new_permissions: File.Permissions) SetPermissionsError!void { + return io.vtable.dirSetPermissions(io.userdata, dir, new_permissions); } pub const SetOwnerError = File.SetOwnerError; @@ -1561,9 +1554,31 @@ pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerEr return io.vtable.dirSetOwner(io.userdata, dir, owner, group); } -pub const SetPermissionsError = File.SetPermissionsError; -pub const Permissions = File.Permissions; +pub const SetTimestampsError = File.SetTimestampsError; -pub fn setPermissions(dir: Dir, io: Io, permissions: Permissions) SetPermissionsError!void { - return io.vtable.dirSetPermissions(io.userdata, dir, permissions); +pub const SetTimestampsOptions = struct { + follow_symlinks: bool = true, +}; + +/// The granularity that ultimately is stored depends on the combination of +/// operating system and file system. When a value as provided that exceeds +/// this range, the value is clamped to the maximum. +pub fn setTimestamps( + dir: Dir, + io: Io, + sub_path: []const u8, + last_accessed: Io.Timestamp, + last_modified: Io.Timestamp, + options: SetTimestampsOptions, +) SetTimestampsError!void { + return io.vtable.dirSetTimestamps(io.userdata, dir, sub_path, last_accessed, last_modified, options); +} + +/// Sets the accessed and modification timestamps of the provided path to the +/// current wall clock time. +/// +/// The granularity that ultimately is stored depends on the combination of +/// operating system and file system. +pub fn setTimestampsNow(dir: Dir, io: Io, sub_path: []const u8, options: SetTimestampsOptions) SetTimestampsError!void { + return io.vtable.fileSetTimestampsNow(io.userdata, dir, sub_path, options); } diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index eeca7e95db4999e5c12e079a1375508e98794c4a..2c1b292b624ea294fdd04b1ed7687bc3062d2c39 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -11,20 +11,15 @@ const Dir = std.Io.Dir; handle: Handle, +pub const Reader = @import("File/Reader.zig"); +pub const Writer = @import("File/Writer.zig"); +pub const Atomic = @import("File/Atomic.zig"); + pub const Handle = std.posix.fd_t; -pub const Mode = std.posix.mode_t; pub const INode = std.posix.ino_t; pub const Uid = std.posix.uid_t; pub const Gid = std.posix.gid_t; -/// This is the default mode given to POSIX operating systems for creating -/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first, -/// since most people would expect "-rw-r--r--", for example, when using -/// the `touch` command, which would correspond to `0o644`. However, POSIX -/// libc implementations use `0o666` inside `fopen` and then rely on the -/// process-scoped "umask" setting to adjust this number for file creation. -pub const default_mode: Mode = if (Mode == u0) 0 else 0o666; - pub const Kind = enum { block_device, character_device, @@ -53,8 +48,7 @@ pub const Stat = struct { /// is unique to each filesystem. inode: INode, size: u64, - /// This is available on POSIX systems and is always 0 otherwise. - mode: Mode, + permissions: Permissions, kind: Kind, /// Last access time in nanoseconds, relative to UTC 1970-01-01. atime: Io.Timestamp, @@ -103,11 +97,6 @@ pub const Lock = enum { exclusive, }; -pub const LockError = error{ - SystemResources, - FileLocksNotSupported, -} || Io.UnexpectedError; - pub const OpenFlags = struct { mode: OpenMode = .read_only, @@ -200,9 +189,7 @@ pub const CreateFlags = struct { /// is available to proceed. lock_nonblocking: bool = false, - /// For POSIX systems this is the file system mode the file will - /// be created with. On other systems this is always 0. - mode: Mode = default_mode, + permissions: Permissions = .default_file, }; pub const OpenError = error{ @@ -251,7 +238,7 @@ pub const OpenError = error{ /// The path already exists and the `CREAT` and `EXCL` flags were provided. PathAlreadyExists, DeviceBusy, - FileLocksNotSupported, + FileLocksUnsupported, /// One of these three things: /// * pathname refers to an executable image which is currently being /// executed and write access was requested. @@ -269,7 +256,216 @@ pub fn close(file: File, io: Io) void { return io.vtable.fileClose(io.userdata, file); } -pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || std.posix.FlockError; +pub const SyncError = error{ + InputOutput, + NoSpaceLeft, + DiskQuota, + AccessDenied, +} || Io.Cancelable || Io.UnexpectedError; + +/// Blocks until all pending file contents and metadata modifications for the +/// file have been synchronized with the underlying filesystem. +/// +/// This does not ensure that metadata for the directory containing the file +/// has also reached disk. +pub fn sync(file: File, io: Io) SyncError!void { + return io.vtable.fileSync(io.userdata, file); +} + +/// Test whether the file refers to a terminal. +/// +/// See also: +/// * `getOrEnableAnsiEscapeSupport` +/// * `supportsAnsiEscapeCodes`. +pub fn isTty(file: File, io: Io) bool { + return io.vtable.fileIsTty(io.userdata, file); +} + +pub const EnableAnsiEscapeCodesError = error{} || Io.Cancelable || Io.UnexpectedError; + +pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError { + return io.vtable.fileEnableAnsiEscapeCodes(io.userdata, file); +} + +/// Test whether ANSI escape codes will be treated as such without +/// attempting to enable support for ANSI escape codes. +pub fn supportsAnsiEscapeCodes(file: File, io: Io) Io.Cancelable!bool { + return io.vtable.fileSupportsAnsiEscapeCodes(io.userdata, file); +} + +pub const SetLengthError = error{ + FileTooBig, + InputOutput, + FileBusy, + AccessDenied, + PermissionDenied, + NonResizable, +} || Io.Cancelable || Io.UnexpectedError; + +/// Truncates or expands the file, populating any new data with zeroes. +/// +/// The file offset after this call is left unchanged. +pub fn setLength(file: File, io: Io, new_length: u64) SetLengthError!void { + return io.vtable.fileSetLength(io.userdata, file, new_length); +} + +pub const LengthError = StatError; + +/// Retrieve the ending byte index of the file. +/// +/// Sometimes cheaper than `stat` if only the length is needed. +pub fn length(file: File, io: Io) LengthError!u64 { + return io.vtable.fileLength(io.userdata, file); +} + +pub const SetPermissionsError = error{ + AccessDenied, + PermissionDenied, + InputOutput, + SymLinkLoop, + FileNotFound, + SystemResources, + ReadOnlyFileSystem, +} || Io.Cancelable || Io.UnexpectedError; + +/// Also known as "chmod". +/// +/// The process must have the correct privileges in order to do this +/// successfully, or must have the effective user ID matching the owner of the +/// file. +pub fn setPermissions(file: File, io: Io, new_permissions: Permissions) SetPermissionsError!void { + return io.vtable.fileSetPermissions(io.userdata, file, new_permissions); +} + +pub const SetOwnerError = error{ + AccessDenied, + PermissionDenied, + InputOutput, + SymLinkLoop, + FileNotFound, + SystemResources, + ReadOnlyFileSystem, +} || Io.Cancelable || Io.UnexpectedError; + +/// Also known as "chown". +/// +/// The process must have the correct privileges in order to do this +/// successfully. The group may be changed by the owner of the file to any +/// group of which the owner is a member. If the owner or group is specified as +/// `null`, the ID is not changed. +pub fn setOwner(file: File, io: Io, owner: ?Uid, group: ?Gid) SetOwnerError!void { + return io.vtable.fileSetOwner(io.userdata, file, owner, group); +} + +/// Cross-platform representation of permissions on a file. +/// +/// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes". +/// +/// Overridable via `std.options`. +pub const Permissions = std.options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) { + default_file = 0, + _, + + pub const default_dir: @This() = .default_file; + pub const has_executable_bit = false; + + const windows = std.os.windows; + + pub fn toAttributes(self: @This()) windows.DWORD { + return @intFromEnum(self); + } + + pub fn readOnly(self: @This()) bool { + const attributes = toAttributes(self); + return attributes & windows.FILE_ATTRIBUTE_READONLY != 0; + } + + pub fn setReadOnly(self: @This(), read_only: bool) @This() { + const attributes = toAttributes(self); + return @enumFromInt(if (read_only) + attributes | windows.FILE_ATTRIBUTE_READONLY + else + attributes & ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY)); + } +} else if (std.posix.mode_t != u0) enum(std.posix.mode_t) { + /// This is the default mode given to POSIX operating systems for creating + /// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first, + /// since most people would expect "-rw-r--r--", for example, when using + /// the `touch` command, which would correspond to `0o644`. However, POSIX + /// libc implementations use `0o666` inside `fopen` and then rely on the + /// process-scoped "umask" setting to adjust this number for file creation. + default_file = 0o666, + default_dir = 0o755, + _, + + pub const has_executable_bit = true; + + pub fn toMode(self: @This()) std.posix.mode_t { + return @intFromEnum(self); + } + + /// Returns `true` if and only if no class has write permissions. + pub fn readOnly(self: @This()) bool { + const mode = toMode(self); + return mode & 0o222 == 0; + } + + /// Enables write permission for all classes. + pub fn setReadOnly(self: @This(), read_only: bool) @This() { + const mode = toMode(self); + const o222 = @as(std.posix.mode_t, 0o222); + return @enumFromInt(if (read_only) mode & ~o222 else mode | o222); + } +} else enum(u0) { + default_file = 0, + pub const default_dir: @This() = .default_file; + pub const has_executable_bit = false; +}; + +pub const SetTimestampsError = error{ + /// times is NULL, or both nsec values are UTIME_NOW, and either: + /// * the effective user ID of the caller does not match the owner + /// of the file, the caller does not have write access to the + /// file, and the caller is not privileged (Linux: does not have + /// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability); + /// or, + /// * the file is marked immutable (see chattr(1)). + AccessDenied, + /// The caller attempted to change one or both timestamps to a value + /// other than the current time, or to change one of the timestamps + /// to the current time while leaving the other timestamp unchanged, + /// (i.e., times is not NULL, neither nsec field is UTIME_NOW, + /// and neither nsec field is UTIME_OMIT) and either: + /// * the caller's effective user ID does not match the owner of + /// file, and the caller is not privileged (Linux: does not have + /// the CAP_FOWNER capability); or, + /// * the file is marked append-only or immutable (see chattr(1)). + PermissionDenied, + ReadOnlyFileSystem, +} || Io.Cancelable || Io.UnexpectedError; + +/// The granularity that ultimately is stored depends on the combination of +/// operating system and file system. When a value as provided that exceeds +/// this range, the value is clamped to the maximum. +pub fn setTimestamps( + file: File, + io: Io, + last_accessed: Io.Timestamp, + last_modified: Io.Timestamp, +) SetTimestampsError!void { + return io.vtable.fileUpdateTimes(io.userdata, file, last_accessed, last_modified); +} + +/// Sets the accessed and modification timestamps of `file` to the current wall +/// clock time. +/// +/// The granularity that ultimately is stored depends on the combination of +/// operating system and file system. +pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { + return io.vtable.fileSetTimestampsNow(io.userdata, file); +} + +pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || LockError; pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File { return io.vtable.openSelfExe(io.userdata, flags); @@ -309,524 +505,75 @@ pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenErr return Io.Dir.cwd().openFile(io, absolute_path, flags); } -/// Defaults to positional reading; falls back to streaming. -/// -/// Positional is more threadsafe, since the global seek position is not -/// affected. -pub fn reader(file: File, io: Io, buffer: []u8) Reader { - return .init(file, io, buffer); -} - -/// Positional is more threadsafe, since the global seek position is not -/// affected, but when such syscalls are not available, preemptively -/// initializing in streaming mode skips a failed syscall. -pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader { - return .initStreaming(file, io, buffer); -} - pub const SeekError = error{ Unseekable, /// The file descriptor does not hold the required rights to seek on it. AccessDenied, } || Io.Cancelable || Io.UnexpectedError; -/// Memoizes key information about a file handle such as: -/// * The size from calling stat, or the error that occurred therein. -/// * The current seek position. -/// * The error that occurred when trying to seek. -/// * Whether reading should be done positionally or streaming. -/// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`) -/// versus plain variants (e.g. `read`). +/// Defaults to positional reading; falls back to streaming. /// -/// Fulfills the `Io.Reader` interface. -pub const Reader = struct { - io: Io, - file: File, - err: ?Error = null, - mode: Reader.Mode = .positional, - /// Tracks the true seek position in the file. To obtain the logical - /// position, use `logicalPos`. - pos: u64 = 0, - size: ?u64 = null, - size_err: ?SizeError = null, - seek_err: ?Reader.SeekError = null, - interface: Io.Reader, - - pub const Error = error{ - InputOutput, - SystemResources, - IsDir, - BrokenPipe, - ConnectionResetByPeer, - Timeout, - /// In WASI, EBADF is mapped to this error because it is returned when - /// trying to read a directory file descriptor as if it were a file. - NotOpenForReading, - SocketUnconnected, - /// This error occurs when no global event loop is configured, - /// and reading from the file descriptor would block. - WouldBlock, - /// In WASI, this error occurs when the file descriptor does - /// not hold the required rights to read from it. - AccessDenied, - /// This error occurs in Linux if the process to be read from - /// no longer exists. - ProcessNotFound, - /// Unable to read file due to lock. - LockViolation, - } || Io.Cancelable || Io.UnexpectedError; - - pub const SizeError = std.os.windows.GetFileSizeError || StatError || error{ - /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. - Streaming, - }; - - pub const SeekError = File.SeekError || error{ - /// Seeking fell back to reading, and reached the end before the requested seek position. - /// `pos` remains at the end of the file. - EndOfStream, - /// Seeking fell back to reading, which failed. - ReadFailed, - }; - - pub const Mode = enum { - streaming, - positional, - /// Avoid syscalls other than `read` and `readv`. - streaming_reading, - /// Avoid syscalls other than `pread` and `preadv`. - positional_reading, - /// Indicates reading cannot continue because of a seek failure. - failure, - - pub fn toStreaming(m: @This()) @This() { - return switch (m) { - .positional, .streaming => .streaming, - .positional_reading, .streaming_reading => .streaming_reading, - .failure => .failure, - }; - } - - pub fn toReading(m: @This()) @This() { - return switch (m) { - .positional, .positional_reading => .positional_reading, - .streaming, .streaming_reading => .streaming_reading, - .failure => .failure, - }; - } - }; - - pub fn initInterface(buffer: []u8) Io.Reader { - return .{ - .vtable = &.{ - .stream = Reader.stream, - .discard = Reader.discard, - .readVec = Reader.readVec, - }, - .buffer = buffer, - .seek = 0, - .end = 0, - }; - } - - pub fn init(file: File, io: Io, buffer: []u8) Reader { - return .{ - .io = io, - .file = file, - .interface = initInterface(buffer), - }; - } - - pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader { - return .{ - .io = io, - .file = file, - .interface = initInterface(buffer), - .size = size, - }; - } - - /// Positional is more threadsafe, since the global seek position is not - /// affected, but when such syscalls are not available, preemptively - /// initializing in streaming mode skips a failed syscall. - pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader { - return .{ - .io = io, - .file = file, - .interface = Reader.initInterface(buffer), - .mode = .streaming, - .seek_err = error.Unseekable, - .size_err = error.Streaming, - }; - } - - pub fn getSize(r: *Reader) SizeError!u64 { - return r.size orelse { - if (r.size_err) |err| return err; - if (stat(r.file, r.io)) |st| { - if (st.kind == .file) { - r.size = st.size; - return st.size; - } else { - r.mode = r.mode.toStreaming(); - r.size_err = error.Streaming; - return error.Streaming; - } - } else |err| { - r.size_err = err; - return err; - } - }; - } - - pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { - const io = r.io; - switch (r.mode) { - .positional, .positional_reading => { - setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); - }, - .streaming, .streaming_reading => { - const seek_err = r.seek_err orelse e: { - if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) { - setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); - return; - } else |err| { - r.seek_err = err; - break :e err; - } - }; - var remaining = std.math.cast(u64, offset) orelse return seek_err; - while (remaining > 0) { - remaining -= discard(&r.interface, .limited64(remaining)) catch |err| { - r.seek_err = err; - return err; - }; - } - r.interface.tossBuffered(); - }, - .failure => return r.seek_err.?, - } - } - - /// Repositions logical read offset relative to the beginning of the file. - pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { - const io = r.io; - switch (r.mode) { - .positional, .positional_reading => { - setLogicalPos(r, offset); - }, - .streaming, .streaming_reading => { - const logical_pos = logicalPos(r); - if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos)); - if (r.seek_err) |err| return err; - io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| { - r.seek_err = err; - return err; - }; - setLogicalPos(r, offset); - }, - .failure => return r.seek_err.?, - } - } - - pub fn logicalPos(r: *const Reader) u64 { - return r.pos - r.interface.bufferedLen(); - } - - fn setLogicalPos(r: *Reader, offset: u64) void { - const logical_pos = r.logicalPos(); - if (offset < logical_pos or offset >= r.pos) { - r.interface.tossBuffered(); - r.pos = offset; - } else r.interface.toss(@intCast(offset - logical_pos)); - } - - /// Number of slices to store on the stack, when trying to send as many byte - /// vectors through the underlying read calls as possible. - const max_buffers_len = 16; - - fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { - const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); - return streamMode(r, w, limit, r.mode); - } - - pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize { - switch (mode) { - .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) { - error.Unimplemented => { - r.mode = r.mode.toReading(); - return 0; - }, - else => |e| return e, - }, - .positional_reading => { - const dest = limit.slice(try w.writableSliceGreedy(1)); - var data: [1][]u8 = .{dest}; - const n = try readVecPositional(r, &data); - w.advance(n); - return n; - }, - .streaming_reading => { - const dest = limit.slice(try w.writableSliceGreedy(1)); - var data: [1][]u8 = .{dest}; - const n = try readVecStreaming(r, &data); - w.advance(n); - return n; - }, - .failure => return error.ReadFailed, - } - } - - fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { - const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); - switch (r.mode) { - .positional, .positional_reading => return readVecPositional(r, data), - .streaming, .streaming_reading => return readVecStreaming(r, data), - .failure => return error.ReadFailed, - } - } - - fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize { - const io = r.io; - var iovecs_buffer: [max_buffers_len][]u8 = undefined; - const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); - const dest = iovecs_buffer[0..dest_n]; - assert(dest[0].len > 0); - const n = io.vtable.fileReadPositional(io.userdata, r.file, dest, r.pos) catch |err| switch (err) { - error.Unseekable => { - r.mode = r.mode.toStreaming(); - const pos = r.pos; - if (pos != 0) { - r.pos = 0; - r.seekBy(@intCast(pos)) catch { - r.mode = .failure; - return error.ReadFailed; - }; - } - return 0; - }, - else => |e| { - r.err = e; - return error.ReadFailed; - }, - }; - if (n == 0) { - r.size = r.pos; - return error.EndOfStream; - } - r.pos += n; - if (n > data_size) { - r.interface.end += n - data_size; - return data_size; - } - return n; - } - - fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize { - const io = r.io; - var iovecs_buffer: [max_buffers_len][]u8 = undefined; - const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); - const dest = iovecs_buffer[0..dest_n]; - assert(dest[0].len > 0); - const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| { - r.err = err; - return error.ReadFailed; - }; - if (n == 0) { - r.size = r.pos; - return error.EndOfStream; - } - r.pos += n; - if (n > data_size) { - r.interface.end += n - data_size; - return data_size; - } - return n; - } - - fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { - const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); - const io = r.io; - const file = r.file; - switch (r.mode) { - .positional, .positional_reading => { - const size = r.getSize() catch { - r.mode = r.mode.toStreaming(); - return 0; - }; - const logical_pos = logicalPos(r); - const delta = @min(@intFromEnum(limit), size - logical_pos); - setLogicalPos(r, logical_pos + delta); - return delta; - }, - .streaming, .streaming_reading => { - // Unfortunately we can't seek forward without knowing the - // size because the seek syscalls provided to us will not - // return the true end position if a seek would exceed the - // end. - fallback: { - if (r.size_err == null and r.seek_err == null) break :fallback; - - const buffered_len = r.interface.bufferedLen(); - var remaining = @intFromEnum(limit); - if (remaining <= buffered_len) { - r.interface.seek += remaining; - return remaining; - } - remaining -= buffered_len; - r.interface.seek = 0; - r.interface.end = 0; - - var trash_buffer: [128]u8 = undefined; - var data: [1][]u8 = .{trash_buffer[0..@min(trash_buffer.len, remaining)]}; - var iovecs_buffer: [max_buffers_len][]u8 = undefined; - const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data); - const dest = iovecs_buffer[0..dest_n]; - assert(dest[0].len > 0); - const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| { - r.err = err; - return error.ReadFailed; - }; - if (n == 0) { - r.size = r.pos; - return error.EndOfStream; - } - r.pos += n; - if (n > data_size) { - r.interface.end += n - data_size; - remaining -= data_size; - } else { - remaining -= n; - } - return @intFromEnum(limit) - remaining; - } - const size = r.getSize() catch return 0; - const n = @min(size - r.pos, std.math.maxInt(i64), @intFromEnum(limit)); - io.vtable.fileSeekBy(io.userdata, file, n) catch |err| { - r.seek_err = err; - return 0; - }; - r.pos += n; - return n; - }, - .failure => return error.ReadFailed, - } - } - - /// Returns whether the stream is at the logical end. - pub fn atEnd(r: *Reader) bool { - // Even if stat fails, size is set when end is encountered. - const size = r.size orelse return false; - return size - logicalPos(r) == 0; - } -}; - -pub const Atomic = struct { - file_writer: File.Writer, - random_integer: u64, - dest_basename: []const u8, - file_open: bool, - file_exists: bool, - close_dir_on_deinit: bool, - dir: Dir, - - pub const InitError = File.OpenError; - - /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. - pub fn init( - dest_basename: []const u8, - mode: File.Mode, - dir: Dir, - close_dir_on_deinit: bool, - write_buffer: []u8, - ) InitError!Atomic { - while (true) { - const random_integer = std.crypto.random.int(u64); - const tmp_sub_path = std.fmt.hex(random_integer); - const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) { - error.PathAlreadyExists => continue, - else => |e| return e, - }; - return .{ - .file_writer = file.writer(write_buffer), - .random_integer = random_integer, - .dest_basename = dest_basename, - .file_open = true, - .file_exists = true, - .close_dir_on_deinit = close_dir_on_deinit, - .dir = dir, - }; - } - } - - /// Always call deinit, even after a successful finish(). - pub fn deinit(af: *Atomic) void { - if (af.file_open) { - af.file_writer.file.close(); - af.file_open = false; - } - if (af.file_exists) { - const tmp_sub_path = std.fmt.hex(af.random_integer); - af.dir.deleteFile(&tmp_sub_path) catch {}; - af.file_exists = false; - } - if (af.close_dir_on_deinit) { - af.dir.close(); - } - af.* = undefined; - } - - pub const FlushError = File.WriteError; - - pub fn flush(af: *Atomic) FlushError!void { - af.file_writer.interface.flush() catch |err| switch (err) { - error.WriteFailed => return af.file_writer.err.?, - }; - } - - pub const RenameIntoPlaceError = Dir.RenameError; - - /// On Windows, this function introduces a period of time where some file - /// system operations on the destination file will result in - /// `error.AccessDenied`, including rename operations (such as the one used in - /// this function). - pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void { - const io = af.file_writer.io; - assert(af.file_exists); - if (af.file_open) { - af.file_writer.file.close(); - af.file_open = false; - } - const tmp_sub_path = std.fmt.hex(af.random_integer); - try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io); - af.file_exists = false; - } - - pub const FinishError = FlushError || RenameIntoPlaceError; - - /// Combination of `flush` followed by `renameIntoPlace`. - pub fn finish(af: *Atomic) FinishError!void { - try af.flush(); - try af.renameIntoPlace(); - } -}; - -pub const SetModeError = error{ - AccessDenied, - PermissionDenied, - InputOutput, - SymLinkLoop, - FileNotFound, +/// Positional is more threadsafe, since the global seek position is not +/// affected. +pub fn reader(file: File, io: Io, buffer: []u8) Reader { + return .init(file, io, buffer); +} + +/// Positional is more threadsafe, since the global seek position is not +/// affected, but when such syscalls are not available, preemptively +/// initializing in streaming mode skips a failed syscall. +pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader { + return .initStreaming(file, io, buffer); +} + +/// Defaults to positional reading; falls back to streaming. +/// +/// Positional is more threadsafe, since the global seek position is not +/// affected. +pub fn writer(file: File, io: Io, buffer: []u8) Writer { + return .init(file, io, buffer); +} + +/// Positional is more threadsafe, since the global seek position is not +/// affected, but when such syscalls are not available, preemptively +/// initializing in streaming mode will skip a failed syscall. +pub fn writerStreaming(file: File, io: Io, buffer: []u8) Writer { + return .initStreaming(file, io, buffer); +} + +pub const LockError = error{ SystemResources, - ReadOnlyFileSystem, + FileLocksUnsupported, } || Io.Cancelable || Io.UnexpectedError; -pub const SetOwnerError = error{ - AccessDenied, - PermissionDenied, - InputOutput, - SymLinkLoop, - FileNotFound, - SystemResources, - ReadOnlyFileSystem, -} || Io.Cancelable || Io.UnexpectedError; +/// Blocks when an incompatible lock is held by another process. A process may +/// hold only one type of lock (shared or exclusive) on a file. When a process +/// terminates in any way, the lock is released. +/// +/// Assumes the file is unlocked. +pub fn lock(file: File, io: Io, l: Lock) LockError!void { + return io.vtable.fileLock(io.userdata, file, l); +} + +/// Assumes the file is locked. +pub fn unlock(file: File, io: Io) void { + return io.vtable.fileUnlock(io.userdata, file); +} + +/// Attempts to obtain a lock, returning `true` if the lock is obtained, and +/// `false` if there was an existing incompatible lock held. A process may hold +/// only one type of lock (shared or exclusive) on a file. When a process +/// terminates in any way, the lock is released. +/// +/// Assumes the file is unlocked. +pub fn tryLock(file: File, io: Io, l: Lock) LockError!bool { + return io.vtable.fileTryLock(io.userdata, file, l); +} + +pub const DowngradeLockError = Io.Cancelable || Io.UnexpectedError; + +/// Assumes the file is already locked in exclusive mode. +/// Atomically modifies the lock to be in shared mode, without releasing it. +pub fn downgradeLock(file: File, io: Io) LockError!void { + return io.vtable.fileDowngradeLock(io.userdata, file); +} diff --git a/lib/std/Io/File/Atomic.zig b/lib/std/Io/File/Atomic.zig new file mode 100644 index 0000000000000000000000000000000000000000..ccb81815ed9404220bded08102dad504586323b7 --- /dev/null +++ b/lib/std/Io/File/Atomic.zig @@ -0,0 +1,99 @@ +const Atomic = @This(); + +const std = @import("../../std.zig"); +const Io = std.Io; +const File = std.Io.File; +const Dir = std.Io.Dir; +const assert = std.debug.assert; + +file_writer: File.Writer, +random_integer: u64, +dest_basename: []const u8, +file_open: bool, +file_exists: bool, +close_dir_on_deinit: bool, +dir: Dir, + +pub const InitError = File.OpenError; + +/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. +pub fn init( + io: Io, + dest_basename: []const u8, + mode: File.Mode, + dir: Dir, + close_dir_on_deinit: bool, + write_buffer: []u8, +) InitError!Atomic { + while (true) { + const random_integer = std.crypto.random.int(u64); + const tmp_sub_path = std.fmt.hex(random_integer); + const file = dir.createFile(io, &tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) { + error.PathAlreadyExists => continue, + else => |e| return e, + }; + return .{ + .file_writer = file.writer(io, write_buffer), + .random_integer = random_integer, + .dest_basename = dest_basename, + .file_open = true, + .file_exists = true, + .close_dir_on_deinit = close_dir_on_deinit, + .dir = dir, + }; + } +} + +/// Always call deinit, even after a successful finish(). +pub fn deinit(af: *Atomic) void { + const io = af.file_writer.io; + + if (af.file_open) { + af.file_writer.file.close(io); + af.file_open = false; + } + if (af.file_exists) { + const tmp_sub_path = std.fmt.hex(af.random_integer); + af.dir.deleteFile(io, &tmp_sub_path) catch {}; + af.file_exists = false; + } + if (af.close_dir_on_deinit) { + af.dir.close(io); + } + af.* = undefined; +} + +pub const FlushError = File.WriteError; + +pub fn flush(af: *Atomic) FlushError!void { + af.file_writer.interface.flush() catch |err| switch (err) { + error.WriteFailed => return af.file_writer.err.?, + }; +} + +pub const RenameIntoPlaceError = Dir.RenameError; + +/// On Windows, this function introduces a period of time where some file +/// system operations on the destination file will result in +/// `error.AccessDenied`, including rename operations (such as the one used in +/// this function). +pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void { + const io = af.file_writer.io; + + assert(af.file_exists); + if (af.file_open) { + af.file_writer.file.close(io); + af.file_open = false; + } + const tmp_sub_path = std.fmt.hex(af.random_integer); + try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io); + af.file_exists = false; +} + +pub const FinishError = FlushError || RenameIntoPlaceError; + +/// Combination of `flush` followed by `renameIntoPlace`. +pub fn finish(af: *Atomic) FinishError!void { + try af.flush(); + try af.renameIntoPlace(); +} diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig new file mode 100644 index 0000000000000000000000000000000000000000..4af77604fa1fa1675823e11da3ce81824599e49e --- /dev/null +++ b/lib/std/Io/File/Reader.zig @@ -0,0 +1,395 @@ +//! Memoizes key information about a file handle such as: +//! * The size from calling stat, or the error that occurred therein. +//! * The current seek position. +//! * The error that occurred when trying to seek. +//! * Whether reading should be done positionally or streaming. +//! * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`) +//! versus plain variants (e.g. `read`). +//! +//! Fulfills the `Io.Reader` interface. +const Reader = @This(); + +const std = @import("../../std.zig"); +const Io = std.Io; +const File = std.Io.File; +const assert = std.debug.assert; + +io: Io, +file: File, +err: ?Error = null, +mode: Reader.Mode = .positional, +/// Tracks the true seek position in the file. To obtain the logical +/// position, use `logicalPos`. +pos: u64 = 0, +size: ?u64 = null, +size_err: ?SizeError = null, +seek_err: ?Reader.SeekError = null, +interface: Io.Reader, + +pub const Error = error{ + InputOutput, + SystemResources, + IsDir, + BrokenPipe, + ConnectionResetByPeer, + Timeout, + /// In WASI, EBADF is mapped to this error because it is returned when + /// trying to read a directory file descriptor as if it were a file. + NotOpenForReading, + SocketUnconnected, + /// This error occurs when no global event loop is configured, + /// and reading from the file descriptor would block. + WouldBlock, + /// In WASI, this error occurs when the file descriptor does + /// not hold the required rights to read from it. + AccessDenied, + /// This error occurs in Linux if the process to be read from + /// no longer exists. + ProcessNotFound, + /// Unable to read file due to lock. + LockViolation, +} || Io.Cancelable || Io.UnexpectedError; + +pub const SizeError = std.os.windows.GetFileSizeError || File.StatError || error{ + /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. + Streaming, +}; + +pub const SeekError = File.SeekError || error{ + /// Seeking fell back to reading, and reached the end before the requested seek position. + /// `pos` remains at the end of the file. + EndOfStream, + /// Seeking fell back to reading, which failed. + ReadFailed, +}; + +pub const Mode = enum { + streaming, + positional, + /// Avoid syscalls other than `read` and `readv`. + streaming_reading, + /// Avoid syscalls other than `pread` and `preadv`. + positional_reading, + /// Indicates reading cannot continue because of a seek failure. + failure, + + pub fn toStreaming(m: @This()) @This() { + return switch (m) { + .positional, .streaming => .streaming, + .positional_reading, .streaming_reading => .streaming_reading, + .failure => .failure, + }; + } + + pub fn toReading(m: @This()) @This() { + return switch (m) { + .positional, .positional_reading => .positional_reading, + .streaming, .streaming_reading => .streaming_reading, + .failure => .failure, + }; + } +}; + +pub fn initInterface(buffer: []u8) Io.Reader { + return .{ + .vtable = &.{ + .stream = Reader.stream, + .discard = Reader.discard, + .readVec = Reader.readVec, + }, + .buffer = buffer, + .seek = 0, + .end = 0, + }; +} + +pub fn init(file: File, io: Io, buffer: []u8) Reader { + return .{ + .io = io, + .file = file, + .interface = initInterface(buffer), + }; +} + +pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader { + return .{ + .io = io, + .file = file, + .interface = initInterface(buffer), + .size = size, + }; +} + +/// Positional is more threadsafe, since the global seek position is not +/// affected, but when such syscalls are not available, preemptively +/// initializing in streaming mode skips a failed syscall. +pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader { + return .{ + .io = io, + .file = file, + .interface = Reader.initInterface(buffer), + .mode = .streaming, + .seek_err = error.Unseekable, + .size_err = error.Streaming, + }; +} + +pub fn getSize(r: *Reader) SizeError!u64 { + return r.size orelse { + if (r.size_err) |err| return err; + if (r.file.stat(r.io)) |st| { + if (st.kind == .file) { + r.size = st.size; + return st.size; + } else { + r.mode = r.mode.toStreaming(); + r.size_err = error.Streaming; + return error.Streaming; + } + } else |err| { + r.size_err = err; + return err; + } + }; +} + +pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { + const io = r.io; + switch (r.mode) { + .positional, .positional_reading => { + setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); + }, + .streaming, .streaming_reading => { + const seek_err = r.seek_err orelse e: { + if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| { + setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); + return; + } else |err| { + r.seek_err = err; + break :e err; + } + }; + var remaining = std.math.cast(u64, offset) orelse return seek_err; + while (remaining > 0) { + remaining -= discard(&r.interface, .limited64(remaining)) catch |err| { + r.seek_err = err; + return err; + }; + } + r.interface.tossBuffered(); + }, + .failure => return r.seek_err.?, + } +} + +/// Repositions logical read offset relative to the beginning of the file. +pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { + const io = r.io; + switch (r.mode) { + .positional, .positional_reading => { + setLogicalPos(r, offset); + }, + .streaming, .streaming_reading => { + const logical_pos = logicalPos(r); + if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos)); + if (r.seek_err) |err| return err; + io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| { + r.seek_err = err; + return err; + }; + setLogicalPos(r, offset); + }, + .failure => return r.seek_err.?, + } +} + +pub fn logicalPos(r: *const Reader) u64 { + return r.pos - r.interface.bufferedLen(); +} + +fn setLogicalPos(r: *Reader, offset: u64) void { + const logical_pos = r.logicalPos(); + if (offset < logical_pos or offset >= r.pos) { + r.interface.tossBuffered(); + r.pos = offset; + } else r.interface.toss(@intCast(offset - logical_pos)); +} + +/// Number of slices to store on the stack, when trying to send as many byte +/// vectors through the underlying read calls as possible. +const max_buffers_len = 16; + +fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { + const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); + return streamMode(r, w, limit, r.mode); +} + +pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize { + switch (mode) { + .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) { + error.Unimplemented => { + r.mode = r.mode.toReading(); + return 0; + }, + else => |e| return e, + }, + .positional_reading => { + const dest = limit.slice(try w.writableSliceGreedy(1)); + var data: [1][]u8 = .{dest}; + const n = try readVecPositional(r, &data); + w.advance(n); + return n; + }, + .streaming_reading => { + const dest = limit.slice(try w.writableSliceGreedy(1)); + var data: [1][]u8 = .{dest}; + const n = try readVecStreaming(r, &data); + w.advance(n); + return n; + }, + .failure => return error.ReadFailed, + } +} + +fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { + const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); + switch (r.mode) { + .positional, .positional_reading => return readVecPositional(r, data), + .streaming, .streaming_reading => return readVecStreaming(r, data), + .failure => return error.ReadFailed, + } +} + +fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize { + const io = r.io; + var iovecs_buffer: [max_buffers_len][]u8 = undefined; + const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); + const dest = iovecs_buffer[0..dest_n]; + assert(dest[0].len > 0); + const n = io.vtable.fileReadPositional(io.userdata, r.file, dest, r.pos) catch |err| switch (err) { + error.Unseekable => { + r.mode = r.mode.toStreaming(); + const pos = r.pos; + if (pos != 0) { + r.pos = 0; + r.seekBy(@intCast(pos)) catch { + r.mode = .failure; + return error.ReadFailed; + }; + } + return 0; + }, + else => |e| { + r.err = e; + return error.ReadFailed; + }, + }; + if (n == 0) { + r.size = r.pos; + return error.EndOfStream; + } + r.pos += n; + if (n > data_size) { + r.interface.end += n - data_size; + return data_size; + } + return n; +} + +fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize { + const io = r.io; + var iovecs_buffer: [max_buffers_len][]u8 = undefined; + const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); + const dest = iovecs_buffer[0..dest_n]; + assert(dest[0].len > 0); + const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| { + r.err = err; + return error.ReadFailed; + }; + if (n == 0) { + r.size = r.pos; + return error.EndOfStream; + } + r.pos += n; + if (n > data_size) { + r.interface.end += n - data_size; + return data_size; + } + return n; +} + +fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { + const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); + const io = r.io; + const file = r.file; + switch (r.mode) { + .positional, .positional_reading => { + const size = r.getSize() catch { + r.mode = r.mode.toStreaming(); + return 0; + }; + const logical_pos = logicalPos(r); + const delta = @min(@intFromEnum(limit), size - logical_pos); + setLogicalPos(r, logical_pos + delta); + return delta; + }, + .streaming, .streaming_reading => { + // Unfortunately we can't seek forward without knowing the + // size because the seek syscalls provided to us will not + // return the true end position if a seek would exceed the + // end. + fallback: { + if (r.size_err == null and r.seek_err == null) break :fallback; + + const buffered_len = r.interface.bufferedLen(); + var remaining = @intFromEnum(limit); + if (remaining <= buffered_len) { + r.interface.seek += remaining; + return remaining; + } + remaining -= buffered_len; + r.interface.seek = 0; + r.interface.end = 0; + + var trash_buffer: [128]u8 = undefined; + var data: [1][]u8 = .{trash_buffer[0..@min(trash_buffer.len, remaining)]}; + var iovecs_buffer: [max_buffers_len][]u8 = undefined; + const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data); + const dest = iovecs_buffer[0..dest_n]; + assert(dest[0].len > 0); + const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| { + r.err = err; + return error.ReadFailed; + }; + if (n == 0) { + r.size = r.pos; + return error.EndOfStream; + } + r.pos += n; + if (n > data_size) { + r.interface.end += n - data_size; + remaining -= data_size; + } else { + remaining -= n; + } + return @intFromEnum(limit) - remaining; + } + const size = r.getSize() catch return 0; + const n = @min(size - r.pos, std.math.maxInt(i64), @intFromEnum(limit)); + io.vtable.fileSeekBy(io.userdata, file, n) catch |err| { + r.seek_err = err; + return 0; + }; + r.pos += n; + return n; + }, + .failure => return error.ReadFailed, + } +} + +/// Returns whether the stream is at the logical end. +pub fn atEnd(r: *Reader) bool { + // Even if stat fails, size is set when end is encountered. + const size = r.size orelse return false; + return size - logicalPos(r) == 0; +} diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig new file mode 100644 index 0000000000000000000000000000000000000000..13cee3e41bd636c4583e6157c764615948eb48bd --- /dev/null +++ b/lib/std/Io/File/Writer.zig @@ -0,0 +1,566 @@ +const Writer = @This(); + +const builtin = @import("builtin"); +const native_os = builtin.os.tag; +const is_windows = native_os == .windows; + +const std = @import("../../std.zig"); +const Io = std.Io; +const File = std.Io.File; +const assert = std.debug.assert; +const windows = std.os.windows; +const posix = std.posix; + +file: File, +err: ?File.WriteError = null, +mode: Writer.Mode = .positional, +/// Tracks the true seek position in the file. To obtain the logical +/// position, add the buffer size to this value. +pos: u64 = 0, +sendfile_err: ?SendfileError = null, +copy_file_range_err: ?CopyFileRangeError = null, +fcopyfile_err: ?FcopyfileError = null, +seek_err: ?Writer.SeekError = null, +interface: Io.Writer, + +pub const Mode = File.Reader.Mode; + +pub const SendfileError = error{ + UnsupportedOperation, + SystemResources, + InputOutput, + BrokenPipe, + WouldBlock, + Unexpected, +}; + +pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError; + +pub const FcopyfileError = error{ + OperationNotSupported, + OutOfMemory, + Unexpected, +}; + +pub const SeekError = Io.File.SeekError; + +/// Number of slices to store on the stack, when trying to send as many byte +/// vectors through the underlying write calls as possible. +const max_buffers_len = 16; + +pub fn init(file: File, buffer: []u8) Writer { + return .{ + .file = file, + .interface = initInterface(buffer), + .mode = .positional, + }; +} + +/// Positional is more threadsafe, since the global seek position is not +/// affected, but when such syscalls are not available, preemptively +/// initializing in streaming mode will skip a failed syscall. +pub fn initStreaming(file: File, buffer: []u8) Writer { + return .{ + .file = file, + .interface = initInterface(buffer), + .mode = .streaming, + }; +} + +pub fn initInterface(buffer: []u8) Io.Writer { + return .{ + .vtable = &.{ + .drain = drain, + .sendFile = sendFile, + }, + .buffer = buffer, + }; +} + +pub fn moveToReader(w: *Writer) File.Reader { + defer w.* = undefined; + return .{ + .io = w.io, + .file = .{ .handle = w.file.handle }, + .mode = w.mode, + .pos = w.pos, + .interface = File.Reader.initInterface(w.interface.buffer), + .seek_err = w.seek_err, + }; +} + +pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { + const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); + const handle = w.file.handle; + const buffered = io_w.buffered(); + if (is_windows) switch (w.mode) { + .positional, .positional_reading => { + if (buffered.len != 0) { + const n = windows.WriteFile(handle, buffered, w.pos) catch |err| { + w.err = err; + return error.WriteFailed; + }; + w.pos += n; + return io_w.consume(n); + } + for (data[0 .. data.len - 1]) |buf| { + if (buf.len == 0) continue; + const n = windows.WriteFile(handle, buf, w.pos) catch |err| { + w.err = err; + return error.WriteFailed; + }; + w.pos += n; + return io_w.consume(n); + } + const pattern = data[data.len - 1]; + if (pattern.len == 0 or splat == 0) return 0; + const n = windows.WriteFile(handle, pattern, w.pos) catch |err| { + w.err = err; + return error.WriteFailed; + }; + w.pos += n; + return io_w.consume(n); + }, + .streaming, .streaming_reading => { + if (buffered.len != 0) { + const n = windows.WriteFile(handle, buffered, null) catch |err| { + w.err = err; + return error.WriteFailed; + }; + w.pos += n; + return io_w.consume(n); + } + for (data[0 .. data.len - 1]) |buf| { + if (buf.len == 0) continue; + const n = windows.WriteFile(handle, buf, null) catch |err| { + w.err = err; + return error.WriteFailed; + }; + w.pos += n; + return io_w.consume(n); + } + const pattern = data[data.len - 1]; + if (pattern.len == 0 or splat == 0) return 0; + const n = windows.WriteFile(handle, pattern, null) catch |err| { + w.err = err; + return error.WriteFailed; + }; + w.pos += n; + return io_w.consume(n); + }, + .failure => return error.WriteFailed, + }; + var iovecs: [max_buffers_len]posix.iovec_const = undefined; + var len: usize = 0; + if (buffered.len > 0) { + iovecs[len] = .{ .base = buffered.ptr, .len = buffered.len }; + len += 1; + } + for (data[0 .. data.len - 1]) |d| { + if (d.len == 0) continue; + iovecs[len] = .{ .base = d.ptr, .len = d.len }; + len += 1; + if (iovecs.len - len == 0) break; + } + const pattern = data[data.len - 1]; + if (iovecs.len - len != 0) switch (splat) { + 0 => {}, + 1 => if (pattern.len != 0) { + iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; + len += 1; + }, + else => switch (pattern.len) { + 0 => {}, + 1 => { + const splat_buffer_candidate = io_w.buffer[io_w.end..]; + var backup_buffer: [64]u8 = undefined; + const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len) + splat_buffer_candidate + else + &backup_buffer; + const memset_len = @min(splat_buffer.len, splat); + const buf = splat_buffer[0..memset_len]; + @memset(buf, pattern[0]); + iovecs[len] = .{ .base = buf.ptr, .len = buf.len }; + len += 1; + var remaining_splat = splat - buf.len; + while (remaining_splat > splat_buffer.len and iovecs.len - len != 0) { + assert(buf.len == splat_buffer.len); + iovecs[len] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len }; + len += 1; + remaining_splat -= splat_buffer.len; + } + if (remaining_splat > 0 and iovecs.len - len != 0) { + iovecs[len] = .{ .base = splat_buffer.ptr, .len = remaining_splat }; + len += 1; + } + }, + else => for (0..splat) |_| { + iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; + len += 1; + if (iovecs.len - len == 0) break; + }, + }, + }; + if (len == 0) return 0; + switch (w.mode) { + .positional, .positional_reading => { + const n = posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) { + error.Unseekable => { + w.mode = w.mode.toStreaming(); + const pos = w.pos; + if (pos != 0) { + w.pos = 0; + w.seekTo(@intCast(pos)) catch { + w.mode = .failure; + return error.WriteFailed; + }; + } + return 0; + }, + else => |e| { + w.err = e; + return error.WriteFailed; + }, + }; + w.pos += n; + return io_w.consume(n); + }, + .streaming, .streaming_reading => { + const n = posix.writev(handle, iovecs[0..len]) catch |err| { + w.err = err; + return error.WriteFailed; + }; + w.pos += n; + return io_w.consume(n); + }, + .failure => return error.WriteFailed, + } +} + +pub fn sendFile( + io_w: *Io.Writer, + file_reader: *Io.File.Reader, + limit: Io.Limit, +) Io.Writer.FileError!usize { + const reader_buffered = file_reader.interface.buffered(); + if (reader_buffered.len >= @intFromEnum(limit)) + return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered)); + const writer_buffered = io_w.buffered(); + const file_limit = @intFromEnum(limit) - reader_buffered.len; + const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); + const out_fd = w.file.handle; + const in_fd = file_reader.file.handle; + + if (file_reader.size) |size| { + if (size - file_reader.pos == 0) { + if (reader_buffered.len != 0) { + return sendFileBuffered(io_w, file_reader, reader_buffered); + } else { + return error.EndOfStream; + } + } + } + + if (native_os == .freebsd and w.mode == .streaming) sf: { + // Try using sendfile on FreeBSD. + if (w.sendfile_err != null) break :sf; + const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; + var hdtr_data: std.c.sf_hdtr = undefined; + var headers: [2]posix.iovec_const = undefined; + var headers_i: u8 = 0; + if (writer_buffered.len != 0) { + headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; + headers_i += 1; + } + if (reader_buffered.len != 0) { + headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; + headers_i += 1; + } + const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { + hdtr_data = .{ + .headers = &headers, + .hdr_cnt = headers_i, + .trailers = null, + .trl_cnt = 0, + }; + break :b &hdtr_data; + }; + var sbytes: std.c.off_t = undefined; + const nbytes: usize = @min(file_limit, std.math.maxInt(usize)); + const flags = 0; + switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { + .SUCCESS, .INTR => {}, + .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, + .BADF => if (builtin.mode == .Debug) @panic("race condition") else { + w.sendfile_err = error.Unexpected; + }, + .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { + w.sendfile_err = error.Unexpected; + }, + .NOTCONN => w.sendfile_err = error.BrokenPipe, + .AGAIN, .BUSY => if (sbytes == 0) { + w.sendfile_err = error.WouldBlock; + }, + .IO => w.sendfile_err = error.InputOutput, + .PIPE => w.sendfile_err = error.BrokenPipe, + .NOBUFS => w.sendfile_err = error.SystemResources, + else => |err| w.sendfile_err = posix.unexpectedErrno(err), + } + if (w.sendfile_err != null) { + // Give calling code chance to observe the error before trying + // something else. + return 0; + } + if (sbytes == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + const consumed = io_w.consume(@intCast(sbytes)); + file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; + return consumed; + } + + if (native_os.isDarwin() and w.mode == .streaming) sf: { + // Try using sendfile on macOS. + if (w.sendfile_err != null) break :sf; + const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; + var hdtr_data: std.c.sf_hdtr = undefined; + var headers: [2]posix.iovec_const = undefined; + var headers_i: u8 = 0; + if (writer_buffered.len != 0) { + headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; + headers_i += 1; + } + if (reader_buffered.len != 0) { + headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; + headers_i += 1; + } + const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { + hdtr_data = .{ + .headers = &headers, + .hdr_cnt = headers_i, + .trailers = null, + .trl_cnt = 0, + }; + break :b &hdtr_data; + }; + const max_count = std.math.maxInt(i32); // Avoid EINVAL. + var len: std.c.off_t = @min(file_limit, max_count); + const flags = 0; + switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { + .SUCCESS, .INTR => {}, + .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, + .BADF => if (builtin.mode == .Debug) @panic("race condition") else { + w.sendfile_err = error.Unexpected; + }, + .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { + w.sendfile_err = error.Unexpected; + }, + .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { + w.sendfile_err = error.Unexpected; + }, + .NOTCONN => w.sendfile_err = error.BrokenPipe, + .AGAIN => if (len == 0) { + w.sendfile_err = error.WouldBlock; + }, + .IO => w.sendfile_err = error.InputOutput, + .PIPE => w.sendfile_err = error.BrokenPipe, + else => |err| w.sendfile_err = posix.unexpectedErrno(err), + } + if (w.sendfile_err != null) { + // Give calling code chance to observe the error before trying + // something else. + return 0; + } + if (len == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + const consumed = io_w.consume(@bitCast(len)); + file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; + return consumed; + } + + if (native_os == .linux and w.mode == .streaming) sf: { + // Try using sendfile on Linux. + if (w.sendfile_err != null) break :sf; + // Linux sendfile does not support headers. + if (writer_buffered.len != 0 or reader_buffered.len != 0) + return sendFileBuffered(io_w, file_reader, reader_buffered); + const max_count = 0x7ffff000; // Avoid EINVAL. + var off: std.os.linux.off_t = undefined; + const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { + .positional => o: { + const size = file_reader.getSize() catch return 0; + off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; + break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; + }, + .streaming => .{ null, limit.minInt(max_count) }, + .streaming_reading, .positional_reading => break :sf, + .failure => return error.ReadFailed, + }; + const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, count) catch |err| switch (err) { + error.Unseekable => { + file_reader.mode = file_reader.mode.toStreaming(); + const pos = file_reader.pos; + if (pos != 0) { + file_reader.pos = 0; + file_reader.seekBy(@intCast(pos)) catch { + file_reader.mode = .failure; + return error.ReadFailed; + }; + } + return 0; + }, + else => |e| { + w.sendfile_err = e; + return 0; + }, + }; + if (n == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + file_reader.pos += n; + w.pos += n; + return n; + } + + const copy_file_range = switch (native_os) { + .freebsd => std.os.freebsd.copy_file_range, + .linux => std.os.linux.wrapped.copy_file_range, + else => {}, + }; + if (@TypeOf(copy_file_range) != void) cfr: { + if (w.copy_file_range_err != null) break :cfr; + if (writer_buffered.len != 0 or reader_buffered.len != 0) + return sendFileBuffered(io_w, file_reader, reader_buffered); + var off_in: i64 = undefined; + var off_out: i64 = undefined; + const off_in_ptr: ?*i64 = switch (file_reader.mode) { + .positional_reading, .streaming_reading => return error.Unimplemented, + .positional => p: { + off_in = @intCast(file_reader.pos); + break :p &off_in; + }, + .streaming => null, + .failure => return error.WriteFailed, + }; + const off_out_ptr: ?*i64 = switch (w.mode) { + .positional_reading, .streaming_reading => return error.Unimplemented, + .positional => p: { + off_out = @intCast(w.pos); + break :p &off_out; + }, + .streaming => null, + .failure => return error.WriteFailed, + }; + const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| { + w.copy_file_range_err = err; + return 0; + }; + if (n == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + file_reader.pos += n; + w.pos += n; + return n; + } + + if (builtin.os.tag.isDarwin()) fcf: { + if (w.fcopyfile_err != null) break :fcf; + if (file_reader.pos != 0) break :fcf; + if (w.pos != 0) break :fcf; + if (limit != .unlimited) break :fcf; + const size = file_reader.getSize() catch break :fcf; + if (writer_buffered.len != 0 or reader_buffered.len != 0) + return sendFileBuffered(io_w, file_reader, reader_buffered); + const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); + switch (posix.errno(rc)) { + .SUCCESS => {}, + .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { + w.fcopyfile_err = error.Unexpected; + return 0; + }, + .NOMEM => { + w.fcopyfile_err = error.OutOfMemory; + return 0; + }, + .OPNOTSUPP => { + w.fcopyfile_err = error.OperationNotSupported; + return 0; + }, + else => |err| { + w.fcopyfile_err = posix.unexpectedErrno(err); + return 0; + }, + } + file_reader.pos = size; + w.pos = size; + return size; + } + + return error.Unimplemented; +} + +fn sendFileBuffered( + io_w: *Io.Writer, + file_reader: *Io.File.Reader, + reader_buffered: []const u8, +) Io.Writer.FileError!usize { + const n = try drain(io_w, &.{reader_buffered}, 1); + file_reader.seekBy(@intCast(n)) catch return error.ReadFailed; + return n; +} + +pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void { + try w.interface.flush(); + try seekToUnbuffered(w, offset); +} + +/// Asserts that no data is currently buffered. +pub fn seekToUnbuffered(w: *Writer, offset: u64) Writer.SeekError!void { + assert(w.interface.buffered().len == 0); + switch (w.mode) { + .positional, .positional_reading => { + w.pos = offset; + }, + .streaming, .streaming_reading => { + if (w.seek_err) |err| return err; + posix.lseek_SET(w.file.handle, offset) catch |err| { + w.seek_err = err; + return err; + }; + w.pos = offset; + }, + .failure => return w.seek_err.?, + } +} + +pub const EndError = File.SetEndPosError || Io.Writer.Error; + +/// Flushes any buffered data and sets the end position of the file. +/// +/// If not overwriting existing contents, then calling `interface.flush` +/// directly is sufficient. +/// +/// Flush failure is handled by setting `err` so that it can be handled +/// along with other write failures. +pub fn end(w: *Writer) EndError!void { + try w.interface.flush(); + switch (w.mode) { + .positional, + .positional_reading, + => w.file.setEndPos(w.pos) catch |err| switch (err) { + error.NonResizable => return, + else => |e| return e, + }, + + .streaming, + .streaming_reading, + .failure, + => {}, + } +} diff --git a/lib/std/Io/IoUring.zig b/lib/std/Io/IoUring.zig index 5561cdebd26275659d3f6ffd435562f3a9af8a10..81cdc242018d6129a52afd0b4232006f52811d77 100644 --- a/lib/std/Io/IoUring.zig +++ b/lib/std/Io/IoUring.zig @@ -1093,7 +1093,7 @@ fn createFile( .PERM => return error.PermissionDenied, .EXIST => return error.PathAlreadyExists, .BUSY => return error.DeviceBusy, - .OPNOTSUPP => return error.FileLocksNotSupported, + .OPNOTSUPP => return error.FileLocksUnsupported, .AGAIN => return error.WouldBlock, .TXTBSY => return error.FileBusy, .NXIO => return error.NoDevice, @@ -1201,7 +1201,7 @@ fn fileOpen( .PERM => return error.PermissionDenied, .EXIST => return error.PathAlreadyExists, .BUSY => return error.DeviceBusy, - .OPNOTSUPP => return error.FileLocksNotSupported, + .OPNOTSUPP => return error.FileLocksUnsupported, .AGAIN => return error.WouldBlock, .TXTBSY => return error.FileBusy, .NXIO => return error.NoDevice, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 26fe3aac7570f88f9e7db8fe39a61a0d52a5737c..7a8fb69a3fc788b0d8ead7fa52bd501b85f6d0ec 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -668,9 +668,10 @@ pub fn io(t: *Threaded) Io { .dirRename = dirRename, .dirSymLink = dirSymLink, .dirReadLink = dirReadLink, - .dirSetMode = dirSetMode, .dirSetOwner = dirSetOwner, .dirSetPermissions = dirSetPermissions, + .dirSetTimestamps = dirSetTimestamps, + .dirSetTimestampsNow = dirSetTimestampsNow, .fileStat = fileStat, .fileClose = fileClose, @@ -681,6 +682,19 @@ pub fn io(t: *Threaded) Io { .fileSeekBy = fileSeekBy, .fileSeekTo = fileSeekTo, .openSelfExe = openSelfExe, + .fileSync = fileSync, + .fileIsTty = fileIsTty, + .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, + .fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes, + .fileSetLength = fileSetLength, + .fileSetOwner = fileSetOwner, + .fileSetPermissions = fileSetPermissions, + .fileSetTimestamps = fileSetTimestamps, + .fileSetTimestampsNow = fileSetTimestampsNow, + .fileLock = fileLock, + .fileTryLock = fileTryLock, + .fileUnlock = fileUnlock, + .fileDowngradeLock = fileDowngradeLock, .now = now, .sleep = sleep, @@ -774,9 +788,10 @@ pub fn ioBasic(t: *Threaded) Io { .dirRename = dirRename, .dirSymLink = dirSymLink, .dirReadLink = dirReadLink, - .dirSetMode = dirSetMode, .dirSetOwner = dirSetOwner, .dirSetPermissions = dirSetPermissions, + .dirSetTimestamps = dirSetTimestamps, + .dirSetTimestampsNow = dirSetTimestampsNow, .fileStat = fileStat, .fileClose = fileClose, @@ -787,6 +802,19 @@ pub fn ioBasic(t: *Threaded) Io { .fileSeekBy = fileSeekBy, .fileSeekTo = fileSeekTo, .openSelfExe = openSelfExe, + .fileSync = fileSync, + .fileIsTty = fileIsTty, + .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, + .fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes, + .fileSetLength = fileSetLength, + .fileSetOwner = fileSetOwner, + .fileSetPermissions = fileSetPermissions, + .fileSetTimestamps = fileSetTimestamps, + .fileSetTimestampsNow = fileSetTimestampsNow, + .fileLock = fileLock, + .fileTryLock = fileTryLock, + .fileUnlock = fileUnlock, + .fileDowngradeLock = fileDowngradeLock, .now = now, .sleep = sleep, @@ -831,6 +859,7 @@ const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fs const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat; const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek; const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv; +const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate; /// Trailing data: /// 1. context @@ -1956,7 +1985,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi // size provided. This is treated as success because the type of variable-length information that this would be relevant for // (name, volume name, etc) we don't care about. .BUFFER_OVERFLOW => {}, - .INVALID_PARAMETER => unreachable, + .INVALID_PARAMETER => |err| return windows.statusBug(err), .ACCESS_DENIED => return error.AccessDenied, else => return windows.unexpectedStatus(rc), } @@ -1971,7 +2000,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi .SUCCESS => {}, // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e - .INFO_LENGTH_MISMATCH => unreachable, + .INFO_LENGTH_MISMATCH => |err| return windows.statusBug(err), .ACCESS_DENIED => return error.AccessDenied, else => return windows.unexpectedStatus(rc), } @@ -2279,7 +2308,7 @@ fn dirCreateFilePosix( .PERM => return error.PermissionDenied, .EXIST => return error.PathAlreadyExists, .BUSY => return error.DeviceBusy, - .OPNOTSUPP => return error.FileLocksNotSupported, + .OPNOTSUPP => return error.FileLocksUnsupported, .AGAIN => return error.WouldBlock, .TXTBSY => return error.FileBusy, .NXIO => return error.NoDevice, @@ -2318,7 +2347,7 @@ fn dirCreateFilePosix( .INVAL => |err| return errnoBug(err), // invalid parameters .NOLCK => return error.SystemResources, .AGAIN => return error.WouldBlock, - .OPNOTSUPP => return error.FileLocksNotSupported, + .OPNOTSUPP => return error.FileLocksUnsupported, else => |err| return posix.unexpectedErrno(err), } }, @@ -2588,7 +2617,7 @@ fn dirOpenFilePosix( .PERM => return error.PermissionDenied, .EXIST => return error.PathAlreadyExists, .BUSY => return error.DeviceBusy, - .OPNOTSUPP => return error.FileLocksNotSupported, + .OPNOTSUPP => return error.FileLocksUnsupported, .AGAIN => return error.WouldBlock, .TXTBSY => return error.FileBusy, .NXIO => return error.NoDevice, @@ -2626,7 +2655,7 @@ fn dirOpenFilePosix( .INVAL => |err| return errnoBug(err), // invalid parameters .NOLCK => return error.SystemResources, .AGAIN => return error.WouldBlock, - .OPNOTSUPP => return error.FileLocksNotSupported, + .OPNOTSUPP => return error.FileLocksUnsupported, else => |err| return posix.unexpectedErrno(err), } }, @@ -3544,12 +3573,12 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, re ); switch (rc) { .SUCCESS => {}, - .OBJECT_NAME_INVALID => unreachable, + .OBJECT_NAME_INVALID => |err| return w.statusBug(err), .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't - .INVALID_PARAMETER => unreachable, + .INVALID_PARAMETER => |err| return w.statusBug(err), .FILE_IS_A_DIRECTORY => return error.IsDir, .NOT_A_DIRECTORY => return error.NotDir, .SHARING_VIOLATION => return error.FileBusy, @@ -3621,7 +3650,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, re switch (rc) { .SUCCESS => {}, .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, - .INVALID_PARAMETER => unreachable, + .INVALID_PARAMETER => |err| return w.statusBug(err), .CANNOT_DELETE => return error.AccessDenied, .MEDIA_WRITE_PROTECTED => return error.AccessDenied, .ACCESS_DENIED => return error.AccessDenied, @@ -3835,9 +3864,9 @@ fn dirRenameWindows( switch (rc) { .SUCCESS => {}, - .INVALID_HANDLE => unreachable, - .INVALID_PARAMETER => unreachable, - .OBJECT_PATH_SYNTAX_BAD => unreachable, + .INVALID_HANDLE => |err| return w.statusBug(err), + .INVALID_PARAMETER => |err| return w.statusBug(err), + .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err), .ACCESS_DENIED => return error.AccessDenied, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, @@ -4320,50 +4349,23 @@ fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, bu } } -const dirSetMode = switch (native_os) { - .windows => dirSetModeUnsupported, - else => dirSetModePosix, +const dirSetPermissions = switch (native_os) { + .windows => dirSetPermissionsWindows, + else => dirSetPermissionsPosix, }; -fn dirSetModeUnsupported(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void { +fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Io.Dir, permissions: Io.Dir.Permissions) Io.Dir.SetPermissionsError!void { + // TODO I think we can actually set permissions on a dir on windows? _ = userdata; _ = dir; - _ = new_mode; + _ = permissions; return error.Unexpected; } -fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void { +fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Io.Dir, permissions: Io.Dir.Permissions) Io.Dir.SetPermissionsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); - - try current_thread.beginSyscall(); - while (true) { - switch (posix.errno(posix.system.fchmod(dir.handle, new_mode))) { - .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), - .INTR => { - try current_thread.checkCancel(); - continue; - }, - else => |e| { - current_thread.endSyscall(); - switch (e) { - .BADF => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.FileNotFound, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } + return setPermissionsPosix(current_thread, dir.handle, permissions.toMode()); } const dirSetOwner = switch (native_os) { @@ -4384,10 +4386,13 @@ fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, gro const current_thread = Thread.getCurrent(t); const uid = owner orelse ~@as(posix.uid_t, 0); const gid = group orelse ~@as(posix.gid_t, 0); + return setOwnerPosix(current_thread, dir.handle, uid, gid); +} +fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) Io.File.SetOwnerError!void { try current_thread.beginSyscall(); while (true) { - switch (posix.errno(posix.system.fchown(dir.handle, uid, gid))) { + switch (posix.errno(posix.system.fchown(fd, uid, gid))) { .SUCCESS => return current_thread.endSyscall(), .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { @@ -4415,31 +4420,916 @@ fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, gro } } -const dirSetPermissions = switch (native_os) { - .windows => dirSetPermissionsWindows, - else => dirSetPermissionsPosix, +const fileSync = switch (native_os) { + .windows => fileSyncWindows, + else => fileSyncPosix, }; -fn dirSetPermissionsWindows( +fn fileSyncWindows(userdata: ?*anyopaque, file: Io.File) Io.File.SyncError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + try current_thread.checkCancel(); + + if (windows.kernel32.FlushFileBuffers(file.handle) != 0) + return; + + switch (windows.GetLastError()) { + .SUCCESS => return, + .INVALID_HANDLE => unreachable, + .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time + .UNEXP_NET_ERR => return error.InputOutput, + else => |err| return windows.unexpectedError(err), + } +} + +fn fileSyncPosix(userdata: ?*anyopaque, file: Io.File) Io.File.SyncError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); + while (true) { + switch (posix.system.fsync(file.handle)) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ROFS => |err| return errnoBug(err), + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .DQUOT => return error.DiskQuota, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn fileIsTty(userdata: ?*anyopaque, file: Io.File) Io.Cancelable!bool { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + return isTty(current_thread, file); +} + +fn isTty(current_thread: *Thread, file: Io.File) Io.Cancelable!bool { + if (is_windows) { + if (try isCygwinPty(current_thread, file)) return true; + try current_thread.checkCancel(); + var out: windows.DWORD = undefined; + return windows.kernel32.GetConsoleMode(file.handle, &out) != 0; + } + + if (builtin.link_libc) { + try current_thread.beginSyscall(); + while (true) { + const rc = posix.system.isatty(file.handle); + switch (posix.errno(rc - 1)) { + .SUCCESS => { + current_thread.endSyscall(); + return true; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => { + current_thread.endSyscall(); + return false; + }, + } + } + } + + if (native_os == .wasi) { + var statbuf: std.os.wasi.fdstat_t = undefined; + const err = std.os.wasi.fd_fdstat_get(file.handle, &statbuf); + if (err != .SUCCESS) + return false; + + // A tty is a character device that we can't seek or tell on. + if (statbuf.fs_filetype != .CHARACTER_DEVICE) + return false; + if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL) + return false; + + return true; + } + + if (native_os == .linux) { + const linux = std.os.linux; + try current_thread.beginSyscall(); + while (true) { + var wsz: linux.winsize = undefined; + const fd: usize = @bitCast(@as(isize, file.handle)); + const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz)); + switch (linux.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + return true; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => { + current_thread.endSyscall(); + return false; + }, + } + } + } + + @compileError("unimplemented"); +} + +fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: Io.File) Io.File.EnableAnsiEscapeCodesError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (is_windows) { + try current_thread.checkCancel(); + + // For Windows Terminal, VT Sequences processing is enabled by default. + var original_console_mode: windows.DWORD = 0; + if (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) != 0) { + if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; + + // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. + // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ + // + // Note: In Microsoft's example for enabling virtual terminal processing, it + // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well: + // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing + // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n) + // to behave unexpectedly (the cursor moves down 1 row but remains on the same column). + // Additionally, the default console mode in Windows Terminal does not have + // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` + // we end up matching the mode of Windows Terminal. + const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING; + const console_mode = original_console_mode | requested_console_modes; + try current_thread.checkCancel(); + if (windows.kernel32.SetConsoleMode(file.handle, console_mode) != 0) return; + } + if (try isCygwinPty(current_thread, file)) return; + } else { + if (try supportsAnsiEscapeCodes(current_thread, file)) return; + } + return error.NotTerminalDevice; +} + +fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: Io.File) Io.Cancelable!bool { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + return supportsAnsiEscapeCodes(current_thread, file); +} + +fn supportsAnsiEscapeCodes(current_thread: *Thread, file: Io.File) Io.Cancelable!bool { + if (is_windows) { + try current_thread.checkCancel(); + var console_mode: windows.DWORD = 0; + if (windows.kernel32.GetConsoleMode(file.handle, &console_mode) != 0) { + if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; + } + return isCygwinPty(current_thread, file); + } + + if (native_os == .wasi) { + // WASI sanitizes stdout when fd is a tty so ANSI escape codes will not + // be interpreted as actual cursor commands, and stderr is always + // sanitized. + return false; + } + + if (try isTty(current_thread, file)) return true; + + return false; +} + +fn isCygwinPty(current_thread: *Thread, file: Io.File) Io.Cancelable!bool { + if (!is_windows) return false; + + const handle = file.handle; + + // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats: + // msys-[...]-ptyN-[...] + // cygwin-[...]-ptyN-[...] + // + // Example: msys-1888ae32e00d56aa-pty0-to-master + + // First, just check that the handle is a named pipe. + // This allows us to avoid the more costly NtQueryInformationFile call + // for handles that aren't named pipes. + { + try current_thread.checkCancel(); + var io_status: windows.IO_STATUS_BLOCK = undefined; + var device_info: windows.FILE_FS_DEVICE_INFORMATION = undefined; + const rc = windows.ntdll.NtQueryVolumeInformationFile(handle, &io_status, &device_info, @sizeOf(windows.FILE_FS_DEVICE_INFORMATION), .FileFsDeviceInformation); + switch (rc) { + .SUCCESS => {}, + else => return false, + } + if (device_info.DeviceType != windows.FILE_DEVICE_NAMED_PIPE) return false; + } + + const name_bytes_offset = @offsetOf(windows.FILE_NAME_INFO, "FileName"); + // `NAME_MAX` UTF-16 code units (2 bytes each) + // This buffer may not be long enough to handle *all* possible paths + // (PATH_MAX_WIDE would be necessary for that), but because we only care + // about certain paths and we know they must be within a reasonable length, + // we can use this smaller buffer and just return false on any error from + // NtQueryInformationFile. + const num_name_bytes = windows.MAX_PATH * 2; + var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); + + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + try current_thread.checkCancel(); + const rc = windows.ntdll.NtQueryInformationFile(handle, &io_status_block, &name_info_bytes, @intCast(name_info_bytes.len), .FileNameInformation); + switch (rc) { + .SUCCESS => {}, + .INVALID_PARAMETER => unreachable, + else => return false, + } + + const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); + const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; + const name_wide = std.mem.bytesAsSlice(u16, name_bytes); + // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master + return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or + std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and + std.mem.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null; +} + +fn fileSetLength(userdata: ?*anyopaque, file: Io.File, length: u64) Io.File.SetLengthError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + const signed_len: i64 = @bitCast(length); + if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors. + + if (is_windows) { + try current_thread.checkCancel(); + + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var eof_info: windows.FILE_END_OF_FILE_INFORMATION = .{ + .EndOfFile = signed_len, + }; + + const status = windows.ntdll.NtSetInformationFile( + file.handle, + &io_status_block, + &eof_info, + @sizeOf(windows.FILE_END_OF_FILE_INFORMATION), + .FileEndOfFileInformation, + ); + switch (status) { + .SUCCESS => return, + .INVALID_HANDLE => |err| return windows.statusBug(err), // Handle not open for writing. + .ACCESS_DENIED => return error.AccessDenied, + .USER_MAPPED_FILE => return error.AccessDenied, + .INVALID_PARAMETER => return error.FileTooBig, + else => return windows.unexpectedStatus(status), + } + } + + if (native_os == .wasi and !builtin.link_libc) { + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.fd_filestat_set_size(file.handle, length)) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .PERM => return error.PermissionDenied, + .TXTBSY => return error.FileBusy, + .BADF => |err| return errnoBug(err), // Handle not open for writing + .INVAL => return error.NonResizable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(ftruncate_sym(file.handle, signed_len))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .PERM => return error.PermissionDenied, + .TXTBSY => return error.FileBusy, + .BADF => |err| return errnoBug(err), // Handle not open for writing. + .INVAL => return error.NonResizable, // This is returned for /dev/null for example. + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn fileSetOwner(userdata: ?*anyopaque, file: Io.File, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.File.SetOwnerError!void { + switch (native_os) { + .windows, .wasi => return error.Unexpected, + else => {}, + } + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + const uid = owner orelse ~@as(posix.uid_t, 0); + const gid = group orelse ~@as(posix.gid_t, 0); + return setOwnerPosix(current_thread, file.handle, uid, gid); +} + +fn fileSetPermissions(userdata: ?*anyopaque, file: Io.File, permissions: Io.File.Permissions) Io.File.SetPermissionsError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + switch (native_os) { + .windows => { + try current_thread.checkCancel(); + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var info: windows.FILE_BASIC_INFORMATION = .{ + .CreationTime = 0, + .LastAccessTime = 0, + .LastWriteTime = 0, + .ChangeTime = 0, + .FileAttributes = permissions.inner.attributes, + }; + const status = windows.ntdll.NtSetInformationFile( + file.handle, + &io_status_block, + &info, + @sizeOf(windows.FILE_BASIC_INFORMATION), + .FileBasicInformation, + ); + switch (status) { + .SUCCESS => return, + .INVALID_HANDLE => |err| return windows.statusBug(err), + .ACCESS_DENIED => return error.AccessDenied, + else => return windows.unexpectedStatus(status), + } + }, + .wasi => return error.Unexpected, // Unsupported OS. + else => return setPermissionsPosix(current_thread, file.handle, permissions.toMode()), + } +} + +fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode_t) Io.File.SetPermissionsError!void { + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.fchmod(fd, mode))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ACCES => return error.AccessDenied, + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.FileNotFound, + .PERM => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn dirSetTimestamps( userdata: ?*anyopaque, dir: Io.Dir, - permissions: Io.Dir.Permissions, -) Io.Dir.SetPermissionsError!void { - _ = userdata; - _ = dir; - _ = permissions; - @panic("TODO"); + sub_path: []const u8, + last_accessed: Io.Timestamp, + last_modified: Io.Timestamp, + options: Io.File.SetTimestampsOptions, +) Io.File.SetTimestampsError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (is_windows) { + @panic("TODO"); + } + + if (native_os == .wasi and !builtin.link_libc) { + @panic("TODO"); + } + + const times: [2]posix.timespec = .{ + timestampToPosix(last_accessed), + timestampToPosix(last_modified), + }; + + const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; + + var path_buffer: [posix.PATH_MAX]u8 = undefined; + const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, ×, flags))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BADF => |err| return errnoBug(err), // always a race condition + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } } -fn dirSetPermissionsPosix( +fn dirSetTimestampsNow( userdata: ?*anyopaque, dir: Io.Dir, - permissions: Io.Dir.Permissions, -) Io.Dir.SetPermissionsError!void { - _ = userdata; - _ = dir; - _ = permissions; - @panic("TODO"); + sub_path: []const u8, + options: Io.File.SetTimestampsOptions, +) Io.File.SetTimestampsError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (is_windows) { + @panic("TODO"); + } + + if (native_os == .wasi and !builtin.link_libc) { + @panic("TODO"); + } + + const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; + + var path_buffer: [posix.PATH_MAX]u8 = undefined; + const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, null, flags))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BADF => |err| return errnoBug(err), // always a race condition + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn fileSetTimestamps( + userdata: ?*anyopaque, + file: Io.File, + last_accessed: Io.Timestamp, + last_modified: Io.Timestamp, +) Io.File.SetTimestampsError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (is_windows) { + try current_thread.checkCancel(); + + const atime_ft = windows.nanoSecondsToFileTime(last_accessed.toNanoseconds()); + const mtime_ft = windows.nanoSecondsToFileTime(last_modified.toNanoseconds()); + + // https://github.com/ziglang/zig/issues/1840 + const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft); + if (rc == 0) { + switch (windows.GetLastError()) { + else => |err| return windows.unexpectedError(err), + } + } + return; + } + + const times: [2]posix.timespec = .{ + timestampToPosix(last_accessed), + timestampToPosix(last_modified), + }; + + if (native_os == .wasi and !builtin.link_libc) { + const atim = times[0].toTimestamp(); + const mtim = times[1].toTimestamp(); + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.fd_filestat_set_times(file.handle, atim, mtim, .{ + .ATIM = true, + .MTIM = true, + })) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BADF => |err| return errnoBug(err), // File descriptor use-after-free. + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.futimens(file.handle, ×))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BADF => |err| return errnoBug(err), // always a race condition + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn fileSetTimestampsNow(userdata: ?*anyopaque, file: Io.File) Io.File.SetTimestampsError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (is_windows) { + @panic("TODO"); + } + + if (native_os == .wasi and !builtin.link_libc) { + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.fd_filestat_set_times(file.handle, 0, 0, .{ + .ATIM_NOW = true, + .MTIM_NOW = true, + })) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BADF => |err| return errnoBug(err), // always a race condition + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.futimens(file.handle, null))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .BADF => |err| return errnoBug(err), // always a race condition + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +const windows_lock_range_off: windows.LARGE_INTEGER = 0; +const windows_lock_range_len: windows.LARGE_INTEGER = 1; + +fn fileLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File.LockError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (is_windows) { + const exclusive = switch (lock) { + .none => return, + .shared => false, + .exclusive => true, + }; + try current_thread.checkCancel(); + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + const status = windows.ntdll.NtLockFile( + file.handle, + null, + null, + null, + &io_status_block, + &windows_lock_range_off, + &windows_lock_range_len, + null, + windows.FALSE, + @intFromBool(exclusive), + ); + switch (status) { + .SUCCESS => return, + .INSUFFICIENT_RESOURCES => return error.SystemResources, + .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // passed FailImmediately=false + .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer + else => return windows.unexpectedStatus(status), + } + } + + const operation = switch (lock) { + .none => posix.LOCK.UN, + .shared => posix.LOCK.SH, + .exclusive => posix.LOCK.EX, + }; + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.flock(file.handle, operation))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOLCK => return error.SystemResources, + .AGAIN => |err| return errnoBug(err), + .OPNOTSUPP => return error.FileLocksUnsupported, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn fileTryLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File.LockError!bool { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (is_windows) { + const exclusive = switch (lock) { + .none => return, + .shared => false, + .exclusive => true, + }; + try current_thread.checkCancel(); + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + const status = windows.ntdll.NtLockFile( + file.handle, + null, + null, + null, + &io_status_block, + &windows_lock_range_off, + &windows_lock_range_len, + null, + windows.TRUE, + @intFromBool(exclusive), + ); + switch (status) { + .SUCCESS => return true, + .INSUFFICIENT_RESOURCES => return error.SystemResources, + .LOCK_NOT_GRANTED => return false, + .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer + else => return windows.unexpectedStatus(status), + } + } + + const operation = switch (lock) { + .none => posix.LOCK.UN, + .shared => posix.LOCK.SH | posix.LOCK.NB, + .exclusive => posix.LOCK.EX | posix.LOCK.NB, + }; + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.flock(file.handle, operation))) { + .SUCCESS => { + current_thread.endSyscall(); + return true; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .AGAIN => { + current_thread.endSyscall(); + return false; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOLCK => return error.SystemResources, + .OPNOTSUPP => return error.FileLocksUnsupported, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn fileUnlock(userdata: ?*anyopaque, file: Io.File) void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (is_windows) { + try current_thread.checkCancel(); + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + const status = windows.ntdll.NtUnlockFile( + file.handle, + &io_status_block, + &windows_lock_range_off, + &windows_lock_range_len, + null, + ); + if (is_debug) switch (status) { + .SUCCESS => {}, + .RANGE_NOT_LOCKED => unreachable, // Function asserts unlocked. + .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer + else => unreachable, // Resource deallocation must succeed. + }; + return; + } + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.flock(file.handle, posix.LOCK.UN))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + if (is_debug) switch (e) { + .AGAIN => unreachable, // unlocking can't block + .BADF => unreachable, // File descriptor used after closed. + .INVAL => unreachable, // invalid parameters + .NOLCK => unreachable, // Resource deallocation. + .OPNOTSUPP => unreachable, // We already got the lock. + else => unreachable, // Resource deallocation must succeed. + }; + return; + }, + } + } +} + +fn fileDowngradeLock(userdata: ?*anyopaque, file: Io.File) Io.File.DowngradeLockError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (is_windows) { + try current_thread.checkCancel(); + // On Windows it works like a semaphore + exclusivity flag. To + // implement this function, we first obtain another lock in shared + // mode. This changes the exclusivity flag, but increments the + // semaphore to 2. So we follow up with an NtUnlockFile which + // decrements the semaphore but does not modify the exclusivity flag. + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + switch (windows.ntdll.NtLockFile( + file.handle, + null, + null, + null, + &io_status_block, + &windows_lock_range_off, + &windows_lock_range_len, + null, + windows.TRUE, + windows.FALSE, + )) { + .SUCCESS => {}, + .INSUFFICIENT_RESOURCES => |err| return windows.statusBug(err), + .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // File was not locked in exclusive mode. + .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer + else => |status| return windows.unexpectedStatus(status), + } + const status = windows.ntdll.NtUnlockFile( + file.handle, + &io_status_block, + &windows_lock_range_off, + &windows_lock_range_len, + null, + ); + if (is_debug) switch (status) { + .SUCCESS => {}, + .RANGE_NOT_LOCKED => unreachable, // File was not locked. + .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer + else => unreachable, // Resource deallocation must succeed. + }; + return; + } + + const operation = posix.LOCK.SH | posix.LOCK.NB; + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.flock(file.handle, operation))) { + .SUCCESS => { + current_thread.endSyscall(); + return true; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .AGAIN => |err| return errnoBug(err), // File was not locked in exclusive mode. + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOLCK => |err| return errnoBug(err), // Lock already obtained. + .OPNOTSUPP => |err| return errnoBug(err), // Lock already obtained. + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } } fn dirOpenDirWasi( diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index ddb6bf73f6ca76aa4b0896b6999f7cf3857452f4..0e923a0f1679077e07c78375cb05588e4da11f59 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -343,7 +343,7 @@ const Module = struct { error.AntivirusInterference, error.ProcessFdQuotaExceeded, error.SystemFdQuotaExceeded, - error.FileLocksNotSupported, + error.FileLocksUnsupported, error.FileBusy, => return error.ReadFailed, }; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 81ec7dca9be43620e4aa0e44a4c05e63654db17c..72fab9c7c24ccfd8e19f85d646911c7343751b71 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -23,12 +23,6 @@ pub const Dir = std.Io.Dir; pub const File = std.Io.File; pub const path = @import("fs/path.zig"); - -pub const has_executable_bit = switch (native_os) { - .windows, .wasi => false, - else => true, -}; - pub const wasi = @import("fs/wasi.zig"); // TODO audit these APIs with respect to Dir and absolute paths diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig deleted file mode 100644 index 4941d9fe85f9bcb8bfaa78b7060fcbbee6bd0543..0000000000000000000000000000000000000000 --- a/lib/std/fs/File.zig +++ /dev/null @@ -1,1356 +0,0 @@ -const File = @This(); - -const builtin = @import("builtin"); -const native_os = builtin.os.tag; -const is_windows = native_os == .windows; - -const std = @import("../std.zig"); -const Io = std.Io; -const Os = std.builtin.Os; -const Allocator = std.mem.Allocator; -const posix = std.posix; -const math = std.math; -const assert = std.debug.assert; -const linux = std.os.linux; -const windows = std.os.windows; -const maxInt = std.math.maxInt; -const Alignment = std.mem.Alignment; - -/// The OS-specific file descriptor or file handle. -handle: Handle, - -pub const Handle = Io.File.Handle; -pub const Mode = Io.File.Mode; -pub const INode = Io.File.INode; -pub const Uid = Io.File.Uid; -pub const Gid = Io.File.Gid; -pub const Kind = Io.File.Kind; - -/// Deprecated in favor of `Io.File.OpenError`. -pub const OpenError = Io.File.OpenError || error{WouldBlock}; -/// Deprecated in favor of `Io.File.OpenMode`. -pub const OpenMode = Io.File.OpenMode; -/// Deprecated in favor of `Io.File.Lock`. -pub const Lock = Io.File.Lock; -/// Deprecated in favor of `Io.File.OpenFlags`. -pub const OpenFlags = Io.File.OpenFlags; - -pub const CreateFlags = std.Io.File.CreateFlags; - -pub fn stdout() File { - return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdOutput else posix.STDOUT_FILENO }; -} - -pub fn stderr() File { - return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdError else posix.STDERR_FILENO }; -} - -pub fn stdin() File { - return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdInput else posix.STDIN_FILENO }; -} - -/// Upon success, the stream is in an uninitialized state. To continue using it, -/// you must use the open() function. -pub fn close(self: File) void { - if (is_windows) { - windows.CloseHandle(self.handle); - } else { - posix.close(self.handle); - } -} - -pub const SyncError = posix.SyncError; - -/// Blocks until all pending file contents and metadata modifications -/// for the file have been synchronized with the underlying filesystem. -/// -/// Note that this does not ensure that metadata for the -/// directory containing the file has also reached disk. -pub fn sync(self: File) SyncError!void { - return posix.fsync(self.handle); -} - -/// Test whether the file refers to a terminal. -/// See also `getOrEnableAnsiEscapeSupport` and `supportsAnsiEscapeCodes`. -pub fn isTty(self: File) bool { - return posix.isatty(self.handle); -} - -pub fn isCygwinPty(file: File) bool { - if (builtin.os.tag != .windows) return false; - - const handle = file.handle; - - // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats: - // msys-[...]-ptyN-[...] - // cygwin-[...]-ptyN-[...] - // - // Example: msys-1888ae32e00d56aa-pty0-to-master - - // First, just check that the handle is a named pipe. - // This allows us to avoid the more costly NtQueryInformationFile call - // for handles that aren't named pipes. - { - var io_status: windows.IO_STATUS_BLOCK = undefined; - var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined; - const rc = windows.ntdll.NtQueryVolumeInformationFile(handle, &io_status, &device_info, @sizeOf(windows.FILE.FS_DEVICE_INFORMATION), .Device); - switch (rc) { - .SUCCESS => {}, - else => return false, - } - if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false; - } - - const name_bytes_offset = @offsetOf(windows.FILE_NAME_INFO, "FileName"); - // `NAME_MAX` UTF-16 code units (2 bytes each) - // This buffer may not be long enough to handle *all* possible paths - // (PATH_MAX_WIDE would be necessary for that), but because we only care - // about certain paths and we know they must be within a reasonable length, - // we can use this smaller buffer and just return false on any error from - // NtQueryInformationFile. - const num_name_bytes = windows.MAX_PATH * 2; - var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); - - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - const rc = windows.ntdll.NtQueryInformationFile(handle, &io_status_block, &name_info_bytes, @intCast(name_info_bytes.len), .Name); - switch (rc) { - .SUCCESS => {}, - .INVALID_PARAMETER => unreachable, - else => return false, - } - - const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); - const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; - const name_wide = std.mem.bytesAsSlice(u16, name_bytes); - // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master - return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or - std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and - std.mem.find(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null; -} - -/// Returns whether or not ANSI escape codes will be treated as such, -/// and attempts to enable support for ANSI escape codes if necessary -/// (on Windows). -/// -/// Returns `true` if ANSI escape codes are supported or support was -/// successfully enabled. Returns false if ANSI escape codes are not -/// supported or support was unable to be enabled. -/// -/// See also `supportsAnsiEscapeCodes`. -pub fn getOrEnableAnsiEscapeSupport(self: File) bool { - if (builtin.os.tag == .windows) { - var original_console_mode: windows.DWORD = 0; - - // For Windows Terminal, VT Sequences processing is enabled by default. - if (windows.kernel32.GetConsoleMode(self.handle, &original_console_mode) != 0) { - if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; - - // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. - // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ - // - // Note: In Microsoft's example for enabling virtual terminal processing, it - // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well: - // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing - // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n) - // to behave unexpectedly (the cursor moves down 1 row but remains on the same column). - // Additionally, the default console mode in Windows Terminal does not have - // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` - // we end up matching the mode of Windows Terminal. - const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING; - const console_mode = original_console_mode | requested_console_modes; - if (windows.kernel32.SetConsoleMode(self.handle, console_mode) != 0) return true; - } - - return self.isCygwinPty(); - } - return self.supportsAnsiEscapeCodes(); -} - -/// Test whether ANSI escape codes will be treated as such without -/// attempting to enable support for ANSI escape codes. -/// -/// See also `getOrEnableAnsiEscapeSupport`. -pub fn supportsAnsiEscapeCodes(self: File) bool { - if (builtin.os.tag == .windows) { - var console_mode: windows.DWORD = 0; - if (windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) { - if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; - } - - return self.isCygwinPty(); - } - if (builtin.os.tag == .wasi) { - // WASI sanitizes stdout when fd is a tty so ANSI escape codes - // will not be interpreted as actual cursor commands, and - // stderr is always sanitized. - return false; - } - if (self.isTty()) { - if (self.handle == posix.STDOUT_FILENO or self.handle == posix.STDERR_FILENO) { - if (posix.getenvZ("TERM")) |term| { - if (std.mem.eql(u8, term, "dumb")) - return false; - } - } - return true; - } - return false; -} - -pub const SetEndPosError = posix.TruncateError; - -/// Shrinks or expands the file. -/// The file offset after this call is left unchanged. -pub fn setEndPos(self: File, length: u64) SetEndPosError!void { - try posix.ftruncate(self.handle, length); -} - -pub const GetEndPosError = std.os.windows.GetFileSizeError || StatError; - -/// TODO: integrate with async I/O -pub fn getEndPos(self: File) GetEndPosError!u64 { - if (builtin.os.tag == .windows) { - return windows.GetFileSizeEx(self.handle); - } - return (try self.stat()).size; -} - -pub const ModeError = StatError; - -/// TODO: integrate with async I/O -pub fn mode(self: File) ModeError!Mode { - if (builtin.os.tag == .windows) { - return 0; - } - return (try self.stat()).mode; -} - -/// Deprecated in favor of `Io.File.Stat`. -pub const Stat = Io.File.Stat; - -/// Deprecated in favor of `Io.File.StatError`. -pub const StatError = posix.FStatError; - -/// Deprecated in favor of `Io.File.stat`. -pub fn stat(self: File) StatError!Stat { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return Io.File.stat(.{ .handle = self.handle }, io); -} - -pub const ChmodError = posix.FChmodError; - -/// Changes the mode of the file. -/// The process must have the correct privileges in order to do this -/// successfully, or must have the effective user ID matching the owner -/// of the file. -pub fn chmod(self: File, new_mode: Mode) ChmodError!void { - try posix.fchmod(self.handle, new_mode); -} - -pub const ChownError = posix.FChownError; - -/// Changes the owner and group of the file. -/// The process must have the correct privileges in order to do this -/// successfully. The group may be changed by the owner of the file to -/// any group of which the owner is a member. If the owner or group is -/// specified as `null`, the ID is not changed. -pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void { - try posix.fchown(self.handle, owner, group); -} - -/// Cross-platform representation of permissions on a file. -/// The `readonly` and `setReadonly` are the only methods available across all platforms. -/// Platform-specific functionality is available through the `inner` field. -pub const Permissions = struct { - /// You may use the `inner` field to use platform-specific functionality - inner: switch (builtin.os.tag) { - .windows => PermissionsWindows, - else => PermissionsUnix, - }, - - const Self = @This(); - - /// Returns `true` if permissions represent an unwritable file. - /// On Unix, `true` is returned only if no class has write permissions. - pub fn readOnly(self: Self) bool { - return self.inner.readOnly(); - } - - /// Sets whether write permissions are provided. - /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`. - /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` - pub fn setReadOnly(self: *Self, read_only: bool) void { - self.inner.setReadOnly(read_only); - } -}; - -pub const PermissionsWindows = struct { - attributes: windows.DWORD, - - const Self = @This(); - - /// Returns `true` if permissions represent an unwritable file. - pub fn readOnly(self: Self) bool { - return self.attributes & windows.FILE_ATTRIBUTE_READONLY != 0; - } - - /// Sets whether write permissions are provided. - /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` - pub fn setReadOnly(self: *Self, read_only: bool) void { - if (read_only) { - self.attributes |= windows.FILE_ATTRIBUTE_READONLY; - } else { - self.attributes &= ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY); - } - } -}; - -pub const PermissionsUnix = struct { - mode: Mode, - - const Self = @This(); - - /// Returns `true` if permissions represent an unwritable file. - /// `true` is returned only if no class has write permissions. - pub fn readOnly(self: Self) bool { - return self.mode & 0o222 == 0; - } - - /// Sets whether write permissions are provided. - /// This affects *all* classes. If this is undesired, use `unixSet`. - /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` - pub fn setReadOnly(self: *Self, read_only: bool) void { - if (read_only) { - self.mode &= ~@as(Mode, 0o222); - } else { - self.mode |= @as(Mode, 0o222); - } - } - - pub const Class = enum(u2) { - user = 2, - group = 1, - other = 0, - }; - - pub const Permission = enum(u3) { - read = 0o4, - write = 0o2, - execute = 0o1, - }; - - /// Returns `true` if the chosen class has the selected permission. - /// This method is only available on Unix platforms. - pub fn unixHas(self: Self, class: Class, permission: Permission) bool { - const mask = @as(Mode, @intFromEnum(permission)) << @as(u3, @intFromEnum(class)) * 3; - return self.mode & mask != 0; - } - - /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged. - /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` - pub fn unixSet(self: *Self, class: Class, permissions: struct { - read: ?bool = null, - write: ?bool = null, - execute: ?bool = null, - }) void { - const shift = @as(u3, @intFromEnum(class)) * 3; - if (permissions.read) |r| { - if (r) { - self.mode |= @as(Mode, 0o4) << shift; - } else { - self.mode &= ~(@as(Mode, 0o4) << shift); - } - } - if (permissions.write) |w| { - if (w) { - self.mode |= @as(Mode, 0o2) << shift; - } else { - self.mode &= ~(@as(Mode, 0o2) << shift); - } - } - if (permissions.execute) |x| { - if (x) { - self.mode |= @as(Mode, 0o1) << shift; - } else { - self.mode &= ~(@as(Mode, 0o1) << shift); - } - } - } - - /// Returns a `Permissions` struct representing the permissions from the passed mode. - pub fn unixNew(new_mode: Mode) Self { - return Self{ - .mode = new_mode, - }; - } -}; - -pub const SetPermissionsError = ChmodError; - -/// Sets permissions according to the provided `Permissions` struct. -/// This method is *NOT* available on WASI -pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void { - switch (builtin.os.tag) { - .windows => { - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - var info = windows.FILE_BASIC_INFORMATION{ - .CreationTime = 0, - .LastAccessTime = 0, - .LastWriteTime = 0, - .ChangeTime = 0, - .FileAttributes = permissions.inner.attributes, - }; - const rc = windows.ntdll.NtSetInformationFile( - self.handle, - &io_status_block, - &info, - @sizeOf(windows.FILE_BASIC_INFORMATION), - .Basic, - ); - switch (rc) { - .SUCCESS => return, - .INVALID_HANDLE => unreachable, - .ACCESS_DENIED => return error.AccessDenied, - else => return windows.unexpectedStatus(rc), - } - }, - .wasi => @compileError("Unsupported OS"), // Wasi filesystem does not *yet* support chmod - else => { - try self.chmod(permissions.inner.mode); - }, - } -} - -pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError; - -/// The underlying file system may have a different granularity than nanoseconds, -/// and therefore this function cannot guarantee any precision will be stored. -/// Further, the maximum value is limited by the system ABI. When a value is provided -/// that exceeds this range, the value is clamped to the maximum. -/// TODO: integrate with async I/O -pub fn updateTimes( - self: File, - /// access timestamp in nanoseconds - atime: Io.Timestamp, - /// last modification timestamp in nanoseconds - mtime: Io.Timestamp, -) UpdateTimesError!void { - if (builtin.os.tag == .windows) { - const atime_ft = windows.nanoSecondsToFileTime(atime); - const mtime_ft = windows.nanoSecondsToFileTime(mtime); - return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft); - } - const times = [2]posix.timespec{ - posix.timespec{ - .sec = math.cast(isize, @divFloor(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), - .nsec = math.cast(isize, @mod(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), - }, - posix.timespec{ - .sec = math.cast(isize, @divFloor(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), - .nsec = math.cast(isize, @mod(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), - }, - }; - try posix.futimens(self.handle, ×); -} - -pub const ReadError = posix.ReadError; -pub const PReadError = posix.PReadError; - -pub fn read(self: File, buffer: []u8) ReadError!usize { - if (is_windows) { - return windows.ReadFile(self.handle, buffer, null); - } - - return posix.read(self.handle, buffer); -} - -/// On Windows, this function currently does alter the file pointer. -/// https://github.com/ziglang/zig/issues/12783 -pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize { - if (is_windows) { - return windows.ReadFile(self.handle, buffer, offset); - } - - return posix.pread(self.handle, buffer, offset); -} - -/// Deprecated in favor of `Reader`. -pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize { - var index: usize = 0; - while (index != buffer.len) { - const amt = try self.pread(buffer[index..], offset + index); - if (amt == 0) break; - index += amt; - } - return index; -} - -/// See https://github.com/ziglang/zig/issues/7699 -pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize { - if (is_windows) { - if (iovecs.len == 0) return 0; - const first = iovecs[0]; - return windows.ReadFile(self.handle, first.base[0..first.len], null); - } - - return posix.readv(self.handle, iovecs); -} - -/// See https://github.com/ziglang/zig/issues/7699 -/// On Windows, this function currently does alter the file pointer. -/// https://github.com/ziglang/zig/issues/12783 -pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!usize { - if (is_windows) { - if (iovecs.len == 0) return 0; - const first = iovecs[0]; - return windows.ReadFile(self.handle, first.base[0..first.len], offset); - } - - return posix.preadv(self.handle, iovecs, offset); -} - -pub const WriteError = posix.WriteError; -pub const PWriteError = posix.PWriteError; - -pub fn write(self: File, bytes: []const u8) WriteError!usize { - if (is_windows) { - return windows.WriteFile(self.handle, bytes, null); - } - - return posix.write(self.handle, bytes); -} - -pub fn writeAll(self: File, bytes: []const u8) WriteError!void { - var index: usize = 0; - while (index < bytes.len) { - index += try self.write(bytes[index..]); - } -} - -/// Deprecated in favor of `Writer`. -pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void { - var index: usize = 0; - while (index < bytes.len) { - index += try self.pwrite(bytes[index..], offset + index); - } -} - -/// On Windows, this function currently does alter the file pointer. -/// https://github.com/ziglang/zig/issues/12783 -pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize { - if (is_windows) { - return windows.WriteFile(self.handle, bytes, offset); - } - - return posix.pwrite(self.handle, bytes, offset); -} - -/// See https://github.com/ziglang/zig/issues/7699 -pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize { - if (is_windows) { - // TODO improve this to use WriteFileScatter - if (iovecs.len == 0) return 0; - const first = iovecs[0]; - return windows.WriteFile(self.handle, first.base[0..first.len], null); - } - - return posix.writev(self.handle, iovecs); -} - -/// See https://github.com/ziglang/zig/issues/7699 -/// On Windows, this function currently does alter the file pointer. -/// https://github.com/ziglang/zig/issues/12783 -pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!usize { - if (is_windows) { - if (iovecs.len == 0) return 0; - const first = iovecs[0]; - return windows.WriteFile(self.handle, first.base[0..first.len], offset); - } - - return posix.pwritev(self.handle, iovecs, offset); -} - -/// Deprecated in favor of `Writer`. -pub const CopyRangeError = posix.CopyFileRangeError; - -/// Deprecated in favor of `Writer`. -pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { - const adjusted_len = math.cast(usize, len) orelse maxInt(usize); - const result = try posix.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0); - return result; -} - -/// Deprecated in favor of `Writer`. -pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { - var total_bytes_copied: u64 = 0; - var in_off = in_offset; - var out_off = out_offset; - while (total_bytes_copied < len) { - const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied); - if (amt_copied == 0) return total_bytes_copied; - total_bytes_copied += amt_copied; - in_off += amt_copied; - out_off += amt_copied; - } - return total_bytes_copied; -} - -/// Deprecated in favor of `Io.File.Reader`. -pub const Reader = Io.File.Reader; - -pub const Writer = struct { - file: File, - err: ?WriteError = null, - mode: Writer.Mode = .positional, - /// Tracks the true seek position in the file. To obtain the logical - /// position, add the buffer size to this value. - pos: u64 = 0, - sendfile_err: ?SendfileError = null, - copy_file_range_err: ?CopyFileRangeError = null, - fcopyfile_err: ?FcopyfileError = null, - seek_err: ?Writer.SeekError = null, - interface: Io.Writer, - - pub const Mode = Reader.Mode; - - pub const SendfileError = error{ - UnsupportedOperation, - SystemResources, - InputOutput, - BrokenPipe, - WouldBlock, - Unexpected, - }; - - pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError; - - pub const FcopyfileError = error{ - OperationNotSupported, - OutOfMemory, - Unexpected, - }; - - pub const SeekError = Io.File.SeekError; - - /// Number of slices to store on the stack, when trying to send as many byte - /// vectors through the underlying write calls as possible. - const max_buffers_len = 16; - - pub fn init(file: File, buffer: []u8) Writer { - return .{ - .file = file, - .interface = initInterface(buffer), - .mode = .positional, - }; - } - - /// Positional is more threadsafe, since the global seek position is not - /// affected, but when such syscalls are not available, preemptively - /// initializing in streaming mode will skip a failed syscall. - pub fn initStreaming(file: File, buffer: []u8) Writer { - return .{ - .file = file, - .interface = initInterface(buffer), - .mode = .streaming, - }; - } - - pub fn initInterface(buffer: []u8) Io.Writer { - return .{ - .vtable = &.{ - .drain = drain, - .sendFile = sendFile, - }, - .buffer = buffer, - }; - } - - /// TODO when this logic moves from fs.File to Io.File the io parameter should be deleted - pub fn moveToReader(w: *Writer, io: Io) Reader { - defer w.* = undefined; - return .{ - .io = io, - .file = .{ .handle = w.file.handle }, - .mode = w.mode, - .pos = w.pos, - .interface = Reader.initInterface(w.interface.buffer), - .seek_err = w.seek_err, - }; - } - - pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { - const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - const handle = w.file.handle; - const buffered = io_w.buffered(); - if (is_windows) switch (w.mode) { - .positional, .positional_reading => { - if (buffered.len != 0) { - const n = windows.WriteFile(handle, buffered, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - for (data[0 .. data.len - 1]) |buf| { - if (buf.len == 0) continue; - const n = windows.WriteFile(handle, buf, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - const pattern = data[data.len - 1]; - if (pattern.len == 0 or splat == 0) return 0; - const n = windows.WriteFile(handle, pattern, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, - .streaming, .streaming_reading => { - if (buffered.len != 0) { - const n = windows.WriteFile(handle, buffered, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - for (data[0 .. data.len - 1]) |buf| { - if (buf.len == 0) continue; - const n = windows.WriteFile(handle, buf, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - const pattern = data[data.len - 1]; - if (pattern.len == 0 or splat == 0) return 0; - const n = windows.WriteFile(handle, pattern, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, - .failure => return error.WriteFailed, - }; - var iovecs: [max_buffers_len]std.posix.iovec_const = undefined; - var len: usize = 0; - if (buffered.len > 0) { - iovecs[len] = .{ .base = buffered.ptr, .len = buffered.len }; - len += 1; - } - for (data[0 .. data.len - 1]) |d| { - if (d.len == 0) continue; - iovecs[len] = .{ .base = d.ptr, .len = d.len }; - len += 1; - if (iovecs.len - len == 0) break; - } - const pattern = data[data.len - 1]; - if (iovecs.len - len != 0) switch (splat) { - 0 => {}, - 1 => if (pattern.len != 0) { - iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; - len += 1; - }, - else => switch (pattern.len) { - 0 => {}, - 1 => { - const splat_buffer_candidate = io_w.buffer[io_w.end..]; - var backup_buffer: [64]u8 = undefined; - const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len) - splat_buffer_candidate - else - &backup_buffer; - const memset_len = @min(splat_buffer.len, splat); - const buf = splat_buffer[0..memset_len]; - @memset(buf, pattern[0]); - iovecs[len] = .{ .base = buf.ptr, .len = buf.len }; - len += 1; - var remaining_splat = splat - buf.len; - while (remaining_splat > splat_buffer.len and iovecs.len - len != 0) { - assert(buf.len == splat_buffer.len); - iovecs[len] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len }; - len += 1; - remaining_splat -= splat_buffer.len; - } - if (remaining_splat > 0 and iovecs.len - len != 0) { - iovecs[len] = .{ .base = splat_buffer.ptr, .len = remaining_splat }; - len += 1; - } - }, - else => for (0..splat) |_| { - iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; - len += 1; - if (iovecs.len - len == 0) break; - }, - }, - }; - if (len == 0) return 0; - switch (w.mode) { - .positional, .positional_reading => { - const n = std.posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) { - error.Unseekable => { - w.mode = w.mode.toStreaming(); - const pos = w.pos; - if (pos != 0) { - w.pos = 0; - w.seekTo(@intCast(pos)) catch { - w.mode = .failure; - return error.WriteFailed; - }; - } - return 0; - }, - else => |e| { - w.err = e; - return error.WriteFailed; - }, - }; - w.pos += n; - return io_w.consume(n); - }, - .streaming, .streaming_reading => { - const n = std.posix.writev(handle, iovecs[0..len]) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, - .failure => return error.WriteFailed, - } - } - - pub fn sendFile( - io_w: *Io.Writer, - file_reader: *Io.File.Reader, - limit: Io.Limit, - ) Io.Writer.FileError!usize { - const reader_buffered = file_reader.interface.buffered(); - if (reader_buffered.len >= @intFromEnum(limit)) - return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered)); - const writer_buffered = io_w.buffered(); - const file_limit = @intFromEnum(limit) - reader_buffered.len; - const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - const out_fd = w.file.handle; - const in_fd = file_reader.file.handle; - - if (file_reader.size) |size| { - if (size - file_reader.pos == 0) { - if (reader_buffered.len != 0) { - return sendFileBuffered(io_w, file_reader, reader_buffered); - } else { - return error.EndOfStream; - } - } - } - - if (native_os == .freebsd and w.mode == .streaming) sf: { - // Try using sendfile on FreeBSD. - if (w.sendfile_err != null) break :sf; - const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; - var hdtr_data: std.c.sf_hdtr = undefined; - var headers: [2]posix.iovec_const = undefined; - var headers_i: u8 = 0; - if (writer_buffered.len != 0) { - headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; - headers_i += 1; - } - if (reader_buffered.len != 0) { - headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; - headers_i += 1; - } - const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { - hdtr_data = .{ - .headers = &headers, - .hdr_cnt = headers_i, - .trailers = null, - .trl_cnt = 0, - }; - break :b &hdtr_data; - }; - var sbytes: std.c.off_t = undefined; - const nbytes: usize = @min(file_limit, maxInt(usize)); - const flags = 0; - switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { - .SUCCESS, .INTR => {}, - .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, - .BADF => if (builtin.mode == .Debug) @panic("race condition") else { - w.sendfile_err = error.Unexpected; - }, - .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { - w.sendfile_err = error.Unexpected; - }, - .NOTCONN => w.sendfile_err = error.BrokenPipe, - .AGAIN, .BUSY => if (sbytes == 0) { - w.sendfile_err = error.WouldBlock; - }, - .IO => w.sendfile_err = error.InputOutput, - .PIPE => w.sendfile_err = error.BrokenPipe, - .NOBUFS => w.sendfile_err = error.SystemResources, - else => |err| w.sendfile_err = posix.unexpectedErrno(err), - } - if (w.sendfile_err != null) { - // Give calling code chance to observe the error before trying - // something else. - return 0; - } - if (sbytes == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - const consumed = io_w.consume(@intCast(sbytes)); - file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; - return consumed; - } - - if (native_os.isDarwin() and w.mode == .streaming) sf: { - // Try using sendfile on macOS. - if (w.sendfile_err != null) break :sf; - const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; - var hdtr_data: std.c.sf_hdtr = undefined; - var headers: [2]posix.iovec_const = undefined; - var headers_i: u8 = 0; - if (writer_buffered.len != 0) { - headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; - headers_i += 1; - } - if (reader_buffered.len != 0) { - headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; - headers_i += 1; - } - const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { - hdtr_data = .{ - .headers = &headers, - .hdr_cnt = headers_i, - .trailers = null, - .trl_cnt = 0, - }; - break :b &hdtr_data; - }; - const max_count = maxInt(i32); // Avoid EINVAL. - var len: std.c.off_t = @min(file_limit, max_count); - const flags = 0; - switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { - .SUCCESS, .INTR => {}, - .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, - .BADF => if (builtin.mode == .Debug) @panic("race condition") else { - w.sendfile_err = error.Unexpected; - }, - .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { - w.sendfile_err = error.Unexpected; - }, - .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { - w.sendfile_err = error.Unexpected; - }, - .NOTCONN => w.sendfile_err = error.BrokenPipe, - .AGAIN => if (len == 0) { - w.sendfile_err = error.WouldBlock; - }, - .IO => w.sendfile_err = error.InputOutput, - .PIPE => w.sendfile_err = error.BrokenPipe, - else => |err| w.sendfile_err = posix.unexpectedErrno(err), - } - if (w.sendfile_err != null) { - // Give calling code chance to observe the error before trying - // something else. - return 0; - } - if (len == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - const consumed = io_w.consume(@bitCast(len)); - file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; - return consumed; - } - - if (native_os == .linux and w.mode == .streaming) sf: { - // Try using sendfile on Linux. - if (w.sendfile_err != null) break :sf; - // Linux sendfile does not support headers. - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - const max_count = 0x7ffff000; // Avoid EINVAL. - var off: std.os.linux.off_t = undefined; - const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { - .positional => o: { - const size = file_reader.getSize() catch return 0; - off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; - break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; - }, - .streaming => .{ null, limit.minInt(max_count) }, - .streaming_reading, .positional_reading => break :sf, - .failure => return error.ReadFailed, - }; - const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, count) catch |err| switch (err) { - error.Unseekable => { - file_reader.mode = file_reader.mode.toStreaming(); - const pos = file_reader.pos; - if (pos != 0) { - file_reader.pos = 0; - file_reader.seekBy(@intCast(pos)) catch { - file_reader.mode = .failure; - return error.ReadFailed; - }; - } - return 0; - }, - else => |e| { - w.sendfile_err = e; - return 0; - }, - }; - if (n == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - file_reader.pos += n; - w.pos += n; - return n; - } - - const copy_file_range = switch (native_os) { - .freebsd => std.os.freebsd.copy_file_range, - .linux => std.os.linux.wrapped.copy_file_range, - else => {}, - }; - if (@TypeOf(copy_file_range) != void) cfr: { - if (w.copy_file_range_err != null) break :cfr; - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - var off_in: i64 = undefined; - var off_out: i64 = undefined; - const off_in_ptr: ?*i64 = switch (file_reader.mode) { - .positional_reading, .streaming_reading => return error.Unimplemented, - .positional => p: { - off_in = @intCast(file_reader.pos); - break :p &off_in; - }, - .streaming => null, - .failure => return error.WriteFailed, - }; - const off_out_ptr: ?*i64 = switch (w.mode) { - .positional_reading, .streaming_reading => return error.Unimplemented, - .positional => p: { - off_out = @intCast(w.pos); - break :p &off_out; - }, - .streaming => null, - .failure => return error.WriteFailed, - }; - const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| { - w.copy_file_range_err = err; - return 0; - }; - if (n == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - file_reader.pos += n; - w.pos += n; - return n; - } - - if (builtin.os.tag.isDarwin()) fcf: { - if (w.fcopyfile_err != null) break :fcf; - if (file_reader.pos != 0) break :fcf; - if (w.pos != 0) break :fcf; - if (limit != .unlimited) break :fcf; - const size = file_reader.getSize() catch break :fcf; - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); - switch (posix.errno(rc)) { - .SUCCESS => {}, - .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { - w.fcopyfile_err = error.Unexpected; - return 0; - }, - .NOMEM => { - w.fcopyfile_err = error.OutOfMemory; - return 0; - }, - .OPNOTSUPP => { - w.fcopyfile_err = error.OperationNotSupported; - return 0; - }, - else => |err| { - w.fcopyfile_err = posix.unexpectedErrno(err); - return 0; - }, - } - file_reader.pos = size; - w.pos = size; - return size; - } - - return error.Unimplemented; - } - - fn sendFileBuffered( - io_w: *Io.Writer, - file_reader: *Io.File.Reader, - reader_buffered: []const u8, - ) Io.Writer.FileError!usize { - const n = try drain(io_w, &.{reader_buffered}, 1); - file_reader.seekBy(@intCast(n)) catch return error.ReadFailed; - return n; - } - - pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void { - try w.interface.flush(); - try seekToUnbuffered(w, offset); - } - - /// Asserts that no data is currently buffered. - pub fn seekToUnbuffered(w: *Writer, offset: u64) Writer.SeekError!void { - assert(w.interface.buffered().len == 0); - switch (w.mode) { - .positional, .positional_reading => { - w.pos = offset; - }, - .streaming, .streaming_reading => { - if (w.seek_err) |err| return err; - posix.lseek_SET(w.file.handle, offset) catch |err| { - w.seek_err = err; - return err; - }; - w.pos = offset; - }, - .failure => return w.seek_err.?, - } - } - - pub const EndError = SetEndPosError || Io.Writer.Error; - - /// Flushes any buffered data and sets the end position of the file. - /// - /// If not overwriting existing contents, then calling `interface.flush` - /// directly is sufficient. - /// - /// Flush failure is handled by setting `err` so that it can be handled - /// along with other write failures. - pub fn end(w: *Writer) EndError!void { - try w.interface.flush(); - switch (w.mode) { - .positional, - .positional_reading, - => w.file.setEndPos(w.pos) catch |err| switch (err) { - error.NonResizable => return, - else => |e| return e, - }, - - .streaming, - .streaming_reading, - .failure, - => {}, - } - } -}; - -/// Defaults to positional reading; falls back to streaming. -/// -/// Positional is more threadsafe, since the global seek position is not -/// affected. -pub fn reader(file: File, io: Io, buffer: []u8) Reader { - return .init(.{ .handle = file.handle }, io, buffer); -} - -/// Positional is more threadsafe, since the global seek position is not -/// affected, but when such syscalls are not available, preemptively -/// initializing in streaming mode skips a failed syscall. -pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader { - return .initStreaming(.{ .handle = file.handle }, io, buffer); -} - -/// Defaults to positional reading; falls back to streaming. -/// -/// Positional is more threadsafe, since the global seek position is not -/// affected. -pub fn writer(file: File, buffer: []u8) Writer { - return .init(file, buffer); -} - -/// Positional is more threadsafe, since the global seek position is not -/// affected, but when such syscalls are not available, preemptively -/// initializing in streaming mode will skip a failed syscall. -pub fn writerStreaming(file: File, buffer: []u8) Writer { - return .initStreaming(file, buffer); -} - -const range_off: windows.LARGE_INTEGER = 0; -const range_len: windows.LARGE_INTEGER = 1; - -/// Deprecated -pub const LockError = Io.File.LockError; - -/// Blocks when an incompatible lock is held by another process. -/// A process may hold only one type of lock (shared or exclusive) on -/// a file. When a process terminates in any way, the lock is released. -/// -/// Assumes the file is unlocked. -/// -/// TODO: integrate with async I/O -pub fn lock(file: File, l: Lock) LockError!void { - if (is_windows) { - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - const exclusive = switch (l) { - .none => return, - .shared => false, - .exclusive => true, - }; - return windows.LockFile( - file.handle, - null, - null, - null, - &io_status_block, - &range_off, - &range_len, - null, - windows.FALSE, // non-blocking=false - @intFromBool(exclusive), - ) catch |err| switch (err) { - error.WouldBlock => unreachable, // non-blocking=false - else => |e| return e, - }; - } else { - return posix.flock(file.handle, switch (l) { - .none => posix.LOCK.UN, - .shared => posix.LOCK.SH, - .exclusive => posix.LOCK.EX, - }) catch |err| switch (err) { - error.WouldBlock => unreachable, // non-blocking=false - else => |e| return e, - }; - } -} - -/// Assumes the file is locked. -pub fn unlock(file: File) void { - if (is_windows) { - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - return windows.UnlockFile( - file.handle, - &io_status_block, - &range_off, - &range_len, - 0, - ) catch |err| switch (err) { - error.RangeNotLocked => unreachable, // Function assumes unlocked. - error.Unexpected => unreachable, // Resource deallocation must succeed. - }; - } else { - return posix.flock(file.handle, posix.LOCK.UN) catch |err| switch (err) { - error.WouldBlock => unreachable, // unlocking can't block - error.SystemResources => unreachable, // We are deallocating resources. - error.FileLocksNotSupported => unreachable, // We already got the lock. - error.Unexpected => unreachable, // Resource deallocation must succeed. - }; - } -} - -/// Attempts to obtain a lock, returning `true` if the lock is -/// obtained, and `false` if there was an existing incompatible lock held. -/// A process may hold only one type of lock (shared or exclusive) on -/// a file. When a process terminates in any way, the lock is released. -/// -/// Assumes the file is unlocked. -/// -/// TODO: integrate with async I/O -pub fn tryLock(file: File, l: Lock) LockError!bool { - if (is_windows) { - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - const exclusive = switch (l) { - .none => return, - .shared => false, - .exclusive => true, - }; - windows.LockFile( - file.handle, - null, - null, - null, - &io_status_block, - &range_off, - &range_len, - null, - windows.TRUE, // non-blocking=true - @intFromBool(exclusive), - ) catch |err| switch (err) { - error.WouldBlock => return false, - else => |e| return e, - }; - } else { - posix.flock(file.handle, switch (l) { - .none => posix.LOCK.UN, - .shared => posix.LOCK.SH | posix.LOCK.NB, - .exclusive => posix.LOCK.EX | posix.LOCK.NB, - }) catch |err| switch (err) { - error.WouldBlock => return false, - else => |e| return e, - }; - } - return true; -} - -/// Assumes the file is already locked in exclusive mode. -/// Atomically modifies the lock to be in shared mode, without releasing it. -/// -/// TODO: integrate with async I/O -pub fn downgradeLock(file: File) LockError!void { - if (is_windows) { - // On Windows it works like a semaphore + exclusivity flag. To implement this - // function, we first obtain another lock in shared mode. This changes the - // exclusivity flag, but increments the semaphore to 2. So we follow up with - // an NtUnlockFile which decrements the semaphore but does not modify the - // exclusivity flag. - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - windows.LockFile( - file.handle, - null, - null, - null, - &io_status_block, - &range_off, - &range_len, - null, - windows.TRUE, // non-blocking=true - windows.FALSE, // exclusive=false - ) catch |err| switch (err) { - error.WouldBlock => unreachable, // File was not locked in exclusive mode. - else => |e| return e, - }; - return windows.UnlockFile( - file.handle, - &io_status_block, - &range_off, - &range_len, - 0, - ) catch |err| switch (err) { - error.RangeNotLocked => unreachable, // File was not locked. - error.Unexpected => unreachable, // Resource deallocation must succeed. - }; - } else { - return posix.flock(file.handle, posix.LOCK.SH | posix.LOCK.NB) catch |err| switch (err) { - error.WouldBlock => unreachable, // File was not locked in exclusive mode. - else => |e| return e, - }; - } -} - -pub fn adaptToNewApi(file: File) Io.File { - return .{ .handle = file.handle }; -} - -pub fn adaptFromNewApi(file: Io.File) File { - return .{ .handle = file.handle }; -} diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index dd41879b3b5205c1f34b59d51df41096f08d871b..6b5b678b20d802560ce9f8d1d40fe2d0d9f1b40d 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -4248,80 +4248,6 @@ pub fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0); } -pub const SetFileTimeError = error{Unexpected}; - -pub fn SetFileTime( - hFile: HANDLE, - lpCreationTime: ?*const FILETIME, - lpLastAccessTime: ?*const FILETIME, - lpLastWriteTime: ?*const FILETIME, -) SetFileTimeError!void { - const rc = kernel32.SetFileTime(hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime); - if (rc == 0) { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } -} - -pub const LockFileError = error{ - SystemResources, - WouldBlock, -} || UnexpectedError; - -pub fn LockFile( - FileHandle: HANDLE, - Event: ?HANDLE, - ApcRoutine: ?*const IO_APC_ROUTINE, - ApcContext: ?*anyopaque, - IoStatusBlock: *IO_STATUS_BLOCK, - ByteOffset: *const LARGE_INTEGER, - Length: *const LARGE_INTEGER, - Key: ?*ULONG, - FailImmediately: BOOLEAN, - ExclusiveLock: BOOLEAN, -) !void { - const rc = ntdll.NtLockFile( - FileHandle, - Event, - ApcRoutine, - ApcContext, - IoStatusBlock, - ByteOffset, - Length, - Key, - FailImmediately, - ExclusiveLock, - ); - switch (rc) { - .SUCCESS => return, - .INSUFFICIENT_RESOURCES => return error.SystemResources, - .LOCK_NOT_GRANTED => return error.WouldBlock, - .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer - else => return unexpectedStatus(rc), - } -} - -pub const UnlockFileError = error{ - RangeNotLocked, -} || UnexpectedError; - -pub fn UnlockFile( - FileHandle: HANDLE, - IoStatusBlock: *IO_STATUS_BLOCK, - ByteOffset: *const LARGE_INTEGER, - Length: *const LARGE_INTEGER, - Key: ULONG, -) !void { - const rc = ntdll.NtUnlockFile(FileHandle, IoStatusBlock, ByteOffset, Length, Key); - switch (rc) { - .SUCCESS => return, - .RANGE_NOT_LOCKED => return error.RangeNotLocked, - .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer - else => return unexpectedStatus(rc), - } -} - /// This is a workaround for the C backend until zig has the ability to put /// C code in inline assembly. extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 73f1bf8b619659c90df61a305a44e3c892e7c0c3..e3809ee9208d8dfcae4c0024f1e14c431dcdda1d 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -309,17 +309,7 @@ pub fn close(fd: fd_t) void { } } -pub const FChmodError = error{ - AccessDenied, - PermissionDenied, - InputOutput, - SymLinkLoop, - FileNotFound, - SystemResources, - ReadOnlyFileSystem, -} || UnexpectedError; - -pub const FChmodAtError = FChmodError || error{ +pub const FChmodAtError = std.Io.File.SetPermissionsError || error{ /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded /// `PATH_MAX`. NameTooLong, @@ -335,7 +325,6 @@ pub const FChmodAtError = FChmodError || error{ ProcessFdQuotaExceeded, /// The procfs fallback was used but the system exceeded it open file limit. SystemFdQuotaExceeded, - Canceled, }; /// Changes the `mode` of `path` relative to the directory referred to by @@ -969,72 +958,6 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { } } -pub const TruncateError = error{ - FileTooBig, - InputOutput, - FileBusy, - AccessDenied, - PermissionDenied, - NonResizable, -} || UnexpectedError; - -/// Length must be positive when treated as an i64. -pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { - const signed_len: i64 = @bitCast(length); - if (signed_len < 0) return error.FileTooBig; // avoid ambiguous EINVAL errors - - if (native_os == .windows) { - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{ - .EndOfFile = signed_len, - }; - const rc = windows.ntdll.NtSetInformationFile( - fd, - &io_status_block, - &eof_info, - @sizeOf(windows.FILE.END_OF_FILE_INFORMATION), - .EndOfFile, - ); - switch (rc) { - .SUCCESS => return, - .INVALID_HANDLE => unreachable, // Handle not open for writing - .ACCESS_DENIED => return error.AccessDenied, - .USER_MAPPED_FILE => return error.AccessDenied, - .INVALID_PARAMETER => return error.FileTooBig, - else => return windows.unexpectedStatus(rc), - } - } - if (native_os == .wasi and !builtin.link_libc) { - switch (wasi.fd_filestat_set_size(fd, length)) { - .SUCCESS => return, - .INTR => unreachable, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .PERM => return error.PermissionDenied, - .TXTBSY => return error.FileBusy, - .BADF => unreachable, // Handle not open for writing - .INVAL => return error.NonResizable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate; - while (true) { - switch (errno(ftruncate_sym(fd, signed_len))) { - .SUCCESS => return, - .INTR => continue, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .PERM => return error.PermissionDenied, - .TXTBSY => return error.FileBusy, - .BADF => unreachable, // Handle not open for writing - .INVAL => return error.NonResizable, // This is returned for /dev/null for example. - else => |err| return unexpectedErrno(err), - } - } -} - /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. /// /// Retries when interrupted by a signal. @@ -1602,7 +1525,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) O .PERM => return error.PermissionDenied, .EXIST => return error.PathAlreadyExists, .BUSY => return error.DeviceBusy, - .OPNOTSUPP => return error.FileLocksNotSupported, + .OPNOTSUPP => return error.FileLocksUnsupported, .AGAIN => return error.WouldBlock, .TXTBSY => return error.FileBusy, .NXIO => return error.NoDevice, @@ -2313,47 +2236,6 @@ pub fn getegid() gid_t { return system.getegid(); } -/// Test whether a file descriptor refers to a terminal. -pub fn isatty(handle: fd_t) bool { - if (native_os == .windows) { - if (fs.File.isCygwinPty(.{ .handle = handle })) - return true; - - var out: windows.DWORD = undefined; - return windows.kernel32.GetConsoleMode(handle, &out) != 0; - } - if (builtin.link_libc) { - return system.isatty(handle) != 0; - } - if (native_os == .wasi) { - var statbuf: wasi.fdstat_t = undefined; - const err = wasi.fd_fdstat_get(handle, &statbuf); - if (err != .SUCCESS) - return false; - - // A tty is a character device that we can't seek or tell on. - if (statbuf.fs_filetype != .CHARACTER_DEVICE) - return false; - if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL) - return false; - - return true; - } - if (native_os == .linux) { - while (true) { - var wsz: winsize = undefined; - const fd: usize = @bitCast(@as(isize, handle)); - const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz)); - switch (linux.errno(rc)) { - .SUCCESS => return true, - .INTR => continue, - else => return false, - } - } - } - return system.isatty(handle) != 0; -} - pub const SocketError = error{ /// Permission to create a socket of the specified type and/or /// pro‐tocol is denied. @@ -3923,34 +3805,6 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { } } -pub const FlockError = error{ - WouldBlock, - - /// The kernel ran out of memory for allocating file locks - SystemResources, - - /// The underlying filesystem does not support file locks - FileLocksNotSupported, -} || UnexpectedError; - -/// Depending on the operating system `flock` may or may not interact with -/// `fcntl` locks made by other processes. -pub fn flock(fd: fd_t, operation: i32) FlockError!void { - while (true) { - const rc = system.flock(fd, operation); - switch (errno(rc)) { - .SUCCESS => return, - .BADF => unreachable, - .INTR => continue, - .INVAL => unreachable, // invalid parameters - .NOLCK => return error.SystemResources, - .AGAIN => return error.WouldBlock, // TODO: integrate with async instead of just returning an error - .OPNOTSUPP => return error.FileLocksNotSupported, - else => |err| return unexpectedErrno(err), - } - } -} - /// Spurious wakeups are possible and no precision of timing is guaranteed. pub fn nanosleep(seconds: u64, nanoseconds: u64) void { var req = timespec{ @@ -4215,74 +4069,6 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?* } } -pub const FutimensError = error{ - /// times is NULL, or both nsec values are UTIME_NOW, and either: - /// * the effective user ID of the caller does not match the owner - /// of the file, the caller does not have write access to the - /// file, and the caller is not privileged (Linux: does not have - /// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability); - /// or, - /// * the file is marked immutable (see chattr(1)). - AccessDenied, - - /// The caller attempted to change one or both timestamps to a value - /// other than the current time, or to change one of the timestamps - /// to the current time while leaving the other timestamp unchanged, - /// (i.e., times is not NULL, neither nsec field is UTIME_NOW, - /// and neither nsec field is UTIME_OMIT) and either: - /// * the caller's effective user ID does not match the owner of - /// file, and the caller is not privileged (Linux: does not have - /// the CAP_FOWNER capability); or, - /// * the file is marked append-only or immutable (see chattr(1)). - PermissionDenied, - - ReadOnlyFileSystem, -} || UnexpectedError; - -pub fn futimens(fd: fd_t, times: ?*const [2]timespec) FutimensError!void { - if (native_os == .wasi and !builtin.link_libc) { - // TODO WASI encodes `wasi.fstflags` to signify magic values - // similar to UTIME_NOW and UTIME_OMIT. Currently, we ignore - // this here, but we should really handle it somehow. - const error_code = blk: { - if (times) |times_arr| { - const atim = times_arr[0].toTimestamp(); - const mtim = times_arr[1].toTimestamp(); - break :blk wasi.fd_filestat_set_times(fd, atim, mtim, .{ - .ATIM = true, - .MTIM = true, - }); - } - - break :blk wasi.fd_filestat_set_times(fd, 0, 0, .{ - .ATIM_NOW = true, - .MTIM_NOW = true, - }); - }; - switch (error_code) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BADF => unreachable, // always a race condition - .FAULT => unreachable, - .INVAL => unreachable, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } - } - - switch (errno(system.futimens(fd, times))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BADF => unreachable, // always a race condition - .FAULT => unreachable, - .INVAL => unreachable, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } -} - pub const GetHostNameError = error{PermissionDenied} || UnexpectedError; pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 { @@ -5104,12 +4890,7 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t { } } -pub const SyncError = error{ - InputOutput, - NoSpaceLeft, - DiskQuota, - AccessDenied, -} || UnexpectedError; +pub const SyncError = std.Io.File.SyncError; /// Write all pending file contents and metadata modifications to all filesystems. pub fn sync() void { @@ -5129,38 +4910,8 @@ pub fn syncfs(fd: fd_t) SyncError!void { } } -/// Write all pending file contents and metadata modifications for the specified file descriptor to the underlying filesystem. -pub fn fsync(fd: fd_t) SyncError!void { - if (native_os == .windows) { - if (windows.kernel32.FlushFileBuffers(fd) != 0) - return; - switch (windows.GetLastError()) { - .SUCCESS => return, - .INVALID_HANDLE => unreachable, - .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time - .UNEXP_NET_ERR => return error.InputOutput, - else => return error.InputOutput, - } - } - const rc = system.fsync(fd); - switch (errno(rc)) { - .SUCCESS => return, - .BADF, .INVAL, .ROFS => unreachable, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .DQUOT => return error.DiskQuota, - else => |err| return unexpectedErrno(err), - } -} - /// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata. pub fn fdatasync(fd: fd_t) SyncError!void { - if (native_os == .windows) { - return fsync(fd) catch |err| switch (err) { - SyncError.AccessDenied => return, // fdatasync doesn't promise that the access time was synced - else => return err, - }; - } const rc = system.fdatasync(fd); switch (errno(rc)) { .SUCCESS => return, diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 5d4d65ec01c3e7c5064a23661c368738a3cfc572..6dad72df44287d91cf8b4393672108523b3113a4 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -596,7 +596,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { error.NoSpaceLeft => unreachable, error.FileTooBig => unreachable, error.DeviceBusy => unreachable, - error.FileLocksNotSupported => unreachable, + error.FileLocksUnsupported => unreachable, error.BadPathName => unreachable, // Windows-only error.WouldBlock => unreachable, error.NetworkNotFound => unreachable, // Windows-only diff --git a/lib/std/std.zig b/lib/std/std.zig index 5c500d3f55d3c70ae831266282bfd9713dd82b62..84e402f52bbdfd465c6022bf65c2ef7c5e3675f9 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -173,6 +173,9 @@ pub const Options = struct { /// If this is `false`, then captured stack traces will always be empty, and attempts to write /// stack traces will just print an error to the relevant `Io.Writer` and return. allow_stack_tracing: bool = !@import("builtin").strip_debug_info, + + /// Overrides `std.Io.File.Permissions`. + FilePermissions: ?type = null, }; // This forces the start.zig file to be imported, and the comptime logic inside that diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index a4970b3b5093290ba6a8f8aafd3d1659c8ce59de..290cd8d38892002945315c5c459eec1772a5ab76 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -824,7 +824,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { error.SharingViolation => return error.Unexpected, // Windows-only error.NetworkNotFound => return error.Unexpected, // Windows-only error.AntivirusInterference => return error.Unexpected, // Windows-only - error.FileLocksNotSupported => return error.Unexpected, // No lock requested. + error.FileLocksUnsupported => return error.Unexpected, // No lock requested. error.NoSpaceLeft => return error.Unexpected, // read-only error.PathAlreadyExists => return error.Unexpected, // read-only error.DeviceBusy => return error.Unexpected, // read-only @@ -1033,7 +1033,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ error.SharingViolation => return error.Unexpected, error.BadPathName => return error.Unexpected, error.PipeBusy => return error.Unexpected, - error.FileLocksNotSupported => return error.Unexpected, + error.FileLocksUnsupported => return error.Unexpected, error.FileBusy => return error.Unexpected, // opened without write permissions error.AntivirusInterference => return error.Unexpected, // Windows-only error -- 2.54.0 From ba999d608c304c37667175b35e4a64f87d90351c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 5 Dec 2025 17:12:57 -0800 Subject: [PATCH 005/195] std: extract sendfile/copy_file_range from Io.File.Writer and move it into std.Io.Threaded (below the VTable) --- lib/std/Io.zig | 11 +- lib/std/Io/Dir.zig | 2 - lib/std/Io/File.zig | 24 +- lib/std/Io/File/Reader.zig | 25 +- lib/std/Io/File/Writer.zig | 548 +++++------------------ lib/std/Io/Threaded.zig | 888 ++++++++++++++++++++++++++++++++++++- lib/std/os.zig | 1 - lib/std/os/freebsd.zig | 50 --- lib/std/os/linux.zig | 162 ------- lib/std/os/linux/test.zig | 8 - lib/std/posix.zig | 166 ------- 11 files changed, 1013 insertions(+), 872 deletions(-) delete mode 100644 lib/std/os/freebsd.zig diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 4949466b8e26f2eb8547468cb2c52a67e1e30ae2..acca9f700c6ec4d74ca40a179f856d1b7571f335 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -662,8 +662,8 @@ pub const VTable = struct { futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, - dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakeError!void, - dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus, + dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakeError!void, + dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakePathError!Dir.MakePathStatus, dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat, dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat, @@ -687,8 +687,10 @@ pub const VTable = struct { fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, fileLength: *const fn (?*anyopaque, File) File.LengthError!u64, fileClose: *const fn (?*anyopaque, File) void, - fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize, - fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize, + fileWriteStreaming: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize) File.Writer.Error!usize, + fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize, + fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize, + fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize, /// Returns 0 on end of stream. fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.Reader.Error!usize, /// Returns 0 on end of stream. @@ -724,6 +726,7 @@ pub const VTable = struct { /// Returns 0 on end of stream. netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize, netWrite: *const fn (?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize, + netWriteFile: *const fn (?*anyopaque, net.Socket.Handle, header: []const u8, *Io.File.Reader, Io.Limit) net.Stream.WriteFileError!usize, netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void, netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 51fe6bbb5f078f6a971d947d84c07fcd14f56967..348950e20a3071d5e640b08683a7a20c86246087 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -652,7 +652,6 @@ pub const RealPathError = error{ NoSpaceLeft, FileSystem, DeviceBusy, - ProcessNotFound, SharingViolation, PipeBusy, /// Windows: file paths provided by the user must be valid WTF-8. @@ -1038,7 +1037,6 @@ pub const DeleteTreeError = error{ FileSystem, FileBusy, DeviceBusy, - ProcessNotFound, /// One of the path components was not a directory. /// This error is unreachable if `sub_path` does not contain a path separator. NotDir, diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 2c1b292b624ea294fdd04b1ed7687bc3062d2c39..46235d73f2bcbc84ae5f22856f7634b23fc1d974 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -198,7 +198,6 @@ pub const OpenError = error{ NoDevice, /// On Windows, `\\server` or `\\server\share` was not found. NetworkNotFound, - ProcessNotFound, /// On Windows, antivirus software is enabled by default. It can be /// disabled, but Windows Update sometimes ignores the user's preference /// and re-enables it. When enabled, antivirus software on Windows @@ -477,18 +476,25 @@ pub fn readPositional(file: File, io: Io, buffer: [][]u8, offset: u64) ReadPosit return io.vtable.fileReadPositional(io.userdata, file, buffer, offset); } -pub const WriteStreamingError = error{} || Io.UnexpectedError || Io.Cancelable; - -pub fn writeStreaming(file: File, io: Io, buffer: [][]const u8) WriteStreamingError!usize { - return file.fileWriteStreaming(io, buffer); -} - -pub const WritePositionalError = WriteStreamingError || error{Unseekable}; +pub const WritePositionalError = Writer.Error || error{Unseekable}; pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize { return io.vtable.fileWritePositional(io.userdata, file, buffer, offset); } +pub const WriteFileStreamingError = error{ + /// `out_fd` is an unconnected socket, or out_fd closed its read end. + BrokenPipe, + /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd. + UnsupportedOperation, + /// Nonblocking I/O has been selected but the write would block. + WouldBlock, + /// Unspecified error while reading from in_fd. + InputOutput, + /// Insufficient kernel memory to read from in_fd. + SystemResources, +} || Io.Cancelable || Io.UnexpectedError; + /// Opens a file for reading or writing, without attempting to create a new /// file, based on an absolute path. /// @@ -511,6 +517,8 @@ pub const SeekError = error{ AccessDenied, } || Io.Cancelable || Io.UnexpectedError; +pub const WriteFilePositionalError = Writer.WriteFileError || error{Unseekable}; + /// Defaults to positional reading; falls back to streaming. /// /// Positional is more threadsafe, since the global seek position is not diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig index 4af77604fa1fa1675823e11da3ce81824599e49e..8b2074a0aa6d52566e0b1b25c1b470ba4e039d1e 100644 --- a/lib/std/Io/File/Reader.zig +++ b/lib/std/Io/File/Reader.zig @@ -17,13 +17,13 @@ const assert = std.debug.assert; io: Io, file: File, err: ?Error = null, -mode: Reader.Mode = .positional, +mode: Mode = .positional, /// Tracks the true seek position in the file. To obtain the logical /// position, use `logicalPos`. pos: u64 = 0, size: ?u64 = null, size_err: ?SizeError = null, -seek_err: ?Reader.SeekError = null, +seek_err: ?SeekError = null, interface: Io.Reader, pub const Error = error{ @@ -37,15 +37,12 @@ pub const Error = error{ /// trying to read a directory file descriptor as if it were a file. NotOpenForReading, SocketUnconnected, - /// This error occurs when no global event loop is configured, - /// and reading from the file descriptor would block. + /// Non-blocking has been enabled, and reading from the file descriptor + /// would block. WouldBlock, /// In WASI, this error occurs when the file descriptor does /// not hold the required rights to read from it. AccessDenied, - /// This error occurs in Linux if the process to be read from - /// no longer exists. - ProcessNotFound, /// Unable to read file due to lock. LockViolation, } || Io.Cancelable || Io.UnexpectedError; @@ -93,9 +90,9 @@ pub const Mode = enum { pub fn initInterface(buffer: []u8) Io.Reader { return .{ .vtable = &.{ - .stream = Reader.stream, - .discard = Reader.discard, - .readVec = Reader.readVec, + .stream = stream, + .discard = discard, + .readVec = readVec, }, .buffer = buffer, .seek = 0, @@ -153,7 +150,7 @@ pub fn getSize(r: *Reader) SizeError!u64 { }; } -pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { +pub fn seekBy(r: *Reader, offset: i64) SeekError!void { const io = r.io; switch (r.mode) { .positional, .positional_reading => { @@ -183,7 +180,7 @@ pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { } /// Repositions logical read offset relative to the beginning of the file. -pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { +pub fn seekTo(r: *Reader, offset: u64) SeekError!void { const io = r.io; switch (r.mode) { .positional, .positional_reading => { @@ -191,7 +188,7 @@ pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { }, .streaming, .streaming_reading => { const logical_pos = logicalPos(r); - if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos)); + if (offset >= logical_pos) return seekBy(r, @intCast(offset - logical_pos)); if (r.seek_err) |err| return err; io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| { r.seek_err = err; @@ -224,7 +221,7 @@ fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.Strea return streamMode(r, w, limit, r.mode); } -pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize { +pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Reader.StreamError!usize { switch (mode) { .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) { error.Unimplemented => { diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 13cee3e41bd636c4583e6157c764615948eb48bd..bb141f9ef6d4b16017111cabb883b0f84af8ba8d 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -1,53 +1,38 @@ const Writer = @This(); -const builtin = @import("builtin"); -const native_os = builtin.os.tag; -const is_windows = native_os == .windows; - const std = @import("../../std.zig"); const Io = std.Io; const File = std.Io.File; const assert = std.debug.assert; -const windows = std.os.windows; -const posix = std.posix; +io: Io, file: File, err: ?File.WriteError = null, -mode: Writer.Mode = .positional, +mode: Mode = .positional, /// Tracks the true seek position in the file. To obtain the logical /// position, add the buffer size to this value. pos: u64 = 0, -sendfile_err: ?SendfileError = null, -copy_file_range_err: ?CopyFileRangeError = null, -fcopyfile_err: ?FcopyfileError = null, -seek_err: ?Writer.SeekError = null, +write_file_err: ?WriteFileError = null, +seek_err: ?SeekError = null, interface: Io.Writer, pub const Mode = File.Reader.Mode; -pub const SendfileError = error{ - UnsupportedOperation, - SystemResources, - InputOutput, +pub const WriteFileError = error{ + /// `out_fd` is an unconnected socket, or out_fd closed its read end. BrokenPipe, + /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd. + UnsupportedOperation, + /// Nonblocking I/O has been selected but the write would block. WouldBlock, - Unexpected, -}; - -pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError; - -pub const FcopyfileError = error{ - OperationNotSupported, - OutOfMemory, - Unexpected, -}; + /// Unspecified error while reading from in_fd. + InputOutput, + /// Insufficient kernel memory to read from in_fd. + SystemResources, +} || Io.Cancelable || Io.UnexpectedError; pub const SeekError = Io.File.SeekError; -/// Number of slices to store on the stack, when trying to send as many byte -/// vectors through the underlying write calls as possible. -const max_buffers_len = 16; - pub fn init(file: File, buffer: []u8) Writer { return .{ .file = file, @@ -91,445 +76,122 @@ pub fn moveToReader(w: *Writer) File.Reader { pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - const handle = w.file.handle; - const buffered = io_w.buffered(); - if (is_windows) switch (w.mode) { - .positional, .positional_reading => { - if (buffered.len != 0) { - const n = windows.WriteFile(handle, buffered, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - for (data[0 .. data.len - 1]) |buf| { - if (buf.len == 0) continue; - const n = windows.WriteFile(handle, buf, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - const pattern = data[data.len - 1]; - if (pattern.len == 0 or splat == 0) return 0; - const n = windows.WriteFile(handle, pattern, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, - .streaming, .streaming_reading => { - if (buffered.len != 0) { - const n = windows.WriteFile(handle, buffered, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - for (data[0 .. data.len - 1]) |buf| { - if (buf.len == 0) continue; - const n = windows.WriteFile(handle, buf, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - const pattern = data[data.len - 1]; - if (pattern.len == 0 or splat == 0) return 0; - const n = windows.WriteFile(handle, pattern, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, - .failure => return error.WriteFailed, - }; - var iovecs: [max_buffers_len]posix.iovec_const = undefined; - var len: usize = 0; - if (buffered.len > 0) { - iovecs[len] = .{ .base = buffered.ptr, .len = buffered.len }; - len += 1; - } - for (data[0 .. data.len - 1]) |d| { - if (d.len == 0) continue; - iovecs[len] = .{ .base = d.ptr, .len = d.len }; - len += 1; - if (iovecs.len - len == 0) break; - } - const pattern = data[data.len - 1]; - if (iovecs.len - len != 0) switch (splat) { - 0 => {}, - 1 => if (pattern.len != 0) { - iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; - len += 1; - }, - else => switch (pattern.len) { - 0 => {}, - 1 => { - const splat_buffer_candidate = io_w.buffer[io_w.end..]; - var backup_buffer: [64]u8 = undefined; - const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len) - splat_buffer_candidate - else - &backup_buffer; - const memset_len = @min(splat_buffer.len, splat); - const buf = splat_buffer[0..memset_len]; - @memset(buf, pattern[0]); - iovecs[len] = .{ .base = buf.ptr, .len = buf.len }; - len += 1; - var remaining_splat = splat - buf.len; - while (remaining_splat > splat_buffer.len and iovecs.len - len != 0) { - assert(buf.len == splat_buffer.len); - iovecs[len] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len }; - len += 1; - remaining_splat -= splat_buffer.len; - } - if (remaining_splat > 0 and iovecs.len - len != 0) { - iovecs[len] = .{ .base = splat_buffer.ptr, .len = remaining_splat }; - len += 1; - } - }, - else => for (0..splat) |_| { - iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; - len += 1; - if (iovecs.len - len == 0) break; - }, - }, - }; - if (len == 0) return 0; switch (w.mode) { - .positional, .positional_reading => { - const n = posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) { - error.Unseekable => { - w.mode = w.mode.toStreaming(); - const pos = w.pos; - if (pos != 0) { - w.pos = 0; - w.seekTo(@intCast(pos)) catch { - w.mode = .failure; - return error.WriteFailed; - }; - } - return 0; - }, - else => |e| { - w.err = e; - return error.WriteFailed; - }, - }; - w.pos += n; - return io_w.consume(n); - }, - .streaming, .streaming_reading => { - const n = posix.writev(handle, iovecs[0..len]) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, + .positional, .positional_reading => return drainPositional(w, data, splat), + .streaming, .streaming_reading => return drainStreaming(w, data, splat), .failure => return error.WriteFailed, } } -pub fn sendFile( - io_w: *Io.Writer, - file_reader: *Io.File.Reader, - limit: Io.Limit, -) Io.Writer.FileError!usize { - const reader_buffered = file_reader.interface.buffered(); - if (reader_buffered.len >= @intFromEnum(limit)) - return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered)); - const writer_buffered = io_w.buffered(); - const file_limit = @intFromEnum(limit) - reader_buffered.len; +fn drainPositional(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { + const io = w.io; + const header = w.interface.buffered(); + const n = io.vtable.fileWritePositional(io.userdata, w.file, header, data, splat, w.pos) catch |err| switch (err) { + error.Unseekable => { + w.mode = w.mode.toStreaming(); + const pos = w.pos; + if (pos != 0) { + w.pos = 0; + w.seekTo(@intCast(pos)) catch { + w.mode = .failure; + return error.WriteFailed; + }; + } + return 0; + }, + else => |e| { + w.err = e; + return error.WriteFailed; + }, + }; + w.pos += n; + return w.interface.consume(n); +} + +fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { + const io = w.io; + const header = w.interface.buffered(); + const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| { + w.err = err; + return error.WriteFailed; + }; + w.pos += n; + return w.interface.consume(n); +} + +pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - const out_fd = w.file.handle; - const in_fd = file_reader.file.handle; + switch (w.mode) { + .positional => return sendFilePositional(w, file_reader, limit), + .positional_reading => return error.Unimplemented, + .streaming => return sendFileStreaming(w, file_reader, limit), + .streaming_reading => return error.Unimplemented, + .failure => return error.WriteFailed, + } +} - if (file_reader.size) |size| { - if (size - file_reader.pos == 0) { - if (reader_buffered.len != 0) { - return sendFileBuffered(io_w, file_reader, reader_buffered); - } else { - return error.EndOfStream; +fn sendFilePositional(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { + const io = w.io; + const header = w.interface.buffered(); + const n = io.vtable.fileSendFilePositional(io.userdata, w.file, header, file_reader, limit, w.pos) catch |err| switch (err) { + error.Unseekable => { + w.mode = w.mode.toStreaming(); + const pos = w.pos; + if (pos != 0) { + w.pos = 0; + w.seekTo(@intCast(pos)) catch { + w.mode = .failure; + return error.WriteFailed; + }; } - } - } - - if (native_os == .freebsd and w.mode == .streaming) sf: { - // Try using sendfile on FreeBSD. - if (w.sendfile_err != null) break :sf; - const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; - var hdtr_data: std.c.sf_hdtr = undefined; - var headers: [2]posix.iovec_const = undefined; - var headers_i: u8 = 0; - if (writer_buffered.len != 0) { - headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; - headers_i += 1; - } - if (reader_buffered.len != 0) { - headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; - headers_i += 1; - } - const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { - hdtr_data = .{ - .headers = &headers, - .hdr_cnt = headers_i, - .trailers = null, - .trl_cnt = 0, - }; - break :b &hdtr_data; - }; - var sbytes: std.c.off_t = undefined; - const nbytes: usize = @min(file_limit, std.math.maxInt(usize)); - const flags = 0; - switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { - .SUCCESS, .INTR => {}, - .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, - .BADF => if (builtin.mode == .Debug) @panic("race condition") else { - w.sendfile_err = error.Unexpected; - }, - .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { - w.sendfile_err = error.Unexpected; - }, - .NOTCONN => w.sendfile_err = error.BrokenPipe, - .AGAIN, .BUSY => if (sbytes == 0) { - w.sendfile_err = error.WouldBlock; - }, - .IO => w.sendfile_err = error.InputOutput, - .PIPE => w.sendfile_err = error.BrokenPipe, - .NOBUFS => w.sendfile_err = error.SystemResources, - else => |err| w.sendfile_err = posix.unexpectedErrno(err), - } - if (w.sendfile_err != null) { - // Give calling code chance to observe the error before trying - // something else. - return 0; - } - if (sbytes == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - const consumed = io_w.consume(@intCast(sbytes)); - file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; - return consumed; - } - - if (native_os.isDarwin() and w.mode == .streaming) sf: { - // Try using sendfile on macOS. - if (w.sendfile_err != null) break :sf; - const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; - var hdtr_data: std.c.sf_hdtr = undefined; - var headers: [2]posix.iovec_const = undefined; - var headers_i: u8 = 0; - if (writer_buffered.len != 0) { - headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; - headers_i += 1; - } - if (reader_buffered.len != 0) { - headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; - headers_i += 1; - } - const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { - hdtr_data = .{ - .headers = &headers, - .hdr_cnt = headers_i, - .trailers = null, - .trl_cnt = 0, - }; - break :b &hdtr_data; - }; - const max_count = std.math.maxInt(i32); // Avoid EINVAL. - var len: std.c.off_t = @min(file_limit, max_count); - const flags = 0; - switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { - .SUCCESS, .INTR => {}, - .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, - .BADF => if (builtin.mode == .Debug) @panic("race condition") else { - w.sendfile_err = error.Unexpected; - }, - .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { - w.sendfile_err = error.Unexpected; - }, - .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { - w.sendfile_err = error.Unexpected; - }, - .NOTCONN => w.sendfile_err = error.BrokenPipe, - .AGAIN => if (len == 0) { - w.sendfile_err = error.WouldBlock; - }, - .IO => w.sendfile_err = error.InputOutput, - .PIPE => w.sendfile_err = error.BrokenPipe, - else => |err| w.sendfile_err = posix.unexpectedErrno(err), - } - if (w.sendfile_err != null) { - // Give calling code chance to observe the error before trying - // something else. return 0; - } - if (len == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - const consumed = io_w.consume(@bitCast(len)); - file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; - return consumed; - } - - if (native_os == .linux and w.mode == .streaming) sf: { - // Try using sendfile on Linux. - if (w.sendfile_err != null) break :sf; - // Linux sendfile does not support headers. - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - const max_count = 0x7ffff000; // Avoid EINVAL. - var off: std.os.linux.off_t = undefined; - const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { - .positional => o: { - const size = file_reader.getSize() catch return 0; - off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; - break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; - }, - .streaming => .{ null, limit.minInt(max_count) }, - .streaming_reading, .positional_reading => break :sf, - .failure => return error.ReadFailed, - }; - const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, count) catch |err| switch (err) { - error.Unseekable => { - file_reader.mode = file_reader.mode.toStreaming(); - const pos = file_reader.pos; - if (pos != 0) { - file_reader.pos = 0; - file_reader.seekBy(@intCast(pos)) catch { - file_reader.mode = .failure; - return error.ReadFailed; - }; - } - return 0; - }, - else => |e| { - w.sendfile_err = e; - return 0; - }, - }; - if (n == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - file_reader.pos += n; - w.pos += n; - return n; - } - - const copy_file_range = switch (native_os) { - .freebsd => std.os.freebsd.copy_file_range, - .linux => std.os.linux.wrapped.copy_file_range, - else => {}, + }, + error.Canceled => { + w.err = error.Canceled; + return error.WriteFailed; + }, + else => |e| { + w.write_file_err = e; + return error.WriteFailed; + }, }; - if (@TypeOf(copy_file_range) != void) cfr: { - if (w.copy_file_range_err != null) break :cfr; - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - var off_in: i64 = undefined; - var off_out: i64 = undefined; - const off_in_ptr: ?*i64 = switch (file_reader.mode) { - .positional_reading, .streaming_reading => return error.Unimplemented, - .positional => p: { - off_in = @intCast(file_reader.pos); - break :p &off_in; - }, - .streaming => null, - .failure => return error.WriteFailed, - }; - const off_out_ptr: ?*i64 = switch (w.mode) { - .positional_reading, .streaming_reading => return error.Unimplemented, - .positional => p: { - off_out = @intCast(w.pos); - break :p &off_out; - }, - .streaming => null, - .failure => return error.WriteFailed, - }; - const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| { - w.copy_file_range_err = err; - return 0; - }; - if (n == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - file_reader.pos += n; - w.pos += n; - return n; - } - - if (builtin.os.tag.isDarwin()) fcf: { - if (w.fcopyfile_err != null) break :fcf; - if (file_reader.pos != 0) break :fcf; - if (w.pos != 0) break :fcf; - if (limit != .unlimited) break :fcf; - const size = file_reader.getSize() catch break :fcf; - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); - switch (posix.errno(rc)) { - .SUCCESS => {}, - .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { - w.fcopyfile_err = error.Unexpected; - return 0; - }, - .NOMEM => { - w.fcopyfile_err = error.OutOfMemory; - return 0; - }, - .OPNOTSUPP => { - w.fcopyfile_err = error.OperationNotSupported; - return 0; - }, - else => |err| { - w.fcopyfile_err = posix.unexpectedErrno(err); - return 0; - }, - } - file_reader.pos = size; - w.pos = size; - return size; - } - - return error.Unimplemented; + w.pos += n; + return w.interface.consume(n); } -fn sendFileBuffered( - io_w: *Io.Writer, - file_reader: *Io.File.Reader, - reader_buffered: []const u8, -) Io.Writer.FileError!usize { - const n = try drain(io_w, &.{reader_buffered}, 1); - file_reader.seekBy(@intCast(n)) catch return error.ReadFailed; - return n; +fn sendFileStreaming(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { + const io = w.io; + const header = w.interface.buffered(); + const n = io.vtable.fileSendFileStreaming(io.userdata, w.file, header, file_reader, limit) catch |err| switch (err) { + error.Canceled => { + w.err = error.Canceled; + return error.WriteFailed; + }, + else => |e| { + w.write_file_err = e; + return error.WriteFailed; + }, + }; + w.pos += n; + return w.interface.consume(n); } -pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void { +pub fn seekTo(w: *Writer, offset: u64) (SeekError || Io.Writer.Error)!void { try w.interface.flush(); try seekToUnbuffered(w, offset); } /// Asserts that no data is currently buffered. -pub fn seekToUnbuffered(w: *Writer, offset: u64) Writer.SeekError!void { +pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void { assert(w.interface.buffered().len == 0); + const io = w.io; switch (w.mode) { .positional, .positional_reading => { w.pos = offset; }, .streaming, .streaming_reading => { if (w.seek_err) |err| return err; - posix.lseek_SET(w.file.handle, offset) catch |err| { + io.vtable.fileSeekTo(io.userdata, w.file, offset) catch |err| { w.seek_err = err; return err; }; @@ -553,7 +215,7 @@ pub fn end(w: *Writer) EndError!void { switch (w.mode) { .positional, .positional_reading, - => w.file.setEndPos(w.pos) catch |err| switch (err) { + => w.file.setLength(w.pos) catch |err| switch (err) { error.NonResizable => return, else => |e| return e, }, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 7a8fb69a3fc788b0d8ead7fa52bd501b85f6d0ec..9a743847ad545a0b58ecde9bf0f06f06b3e5d2a4 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3,6 +3,7 @@ const Threaded = @This(); const builtin = @import("builtin"); const native_os = builtin.os.tag; const is_windows = native_os == .windows; +const is_darwin = native_os.isDarwin(); const windows = std.os.windows; const ws2_32 = std.os.windows.ws2_32; const is_debug = builtin.mode == .Debug; @@ -70,6 +71,10 @@ have_signal_handler: bool, old_sig_io: if (have_sig_io) posix.Sigaction else void, old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, +use_sendfile: UseSendfile = .default, +use_copy_file_range: UseCopyFileRange = .default, +use_fcopyfile: UseFcopyfile = .default, + pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { enabled, disabled, @@ -82,6 +87,33 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) { _, } else enum(u0) { unknown = 0 }; +pub const UseSendfile = if (have_sendfile) enum { + enabled, + disabled, + pub const default: UseSendfile = .enabled; +} else enum { + disabled, + pub const default: UseSendfile = .disabled; +}; + +pub const UseCopyFileRange = if (have_copy_file_range) enum { + enabled, + disabled, + pub const default: UseCopyFileRange = .enabled; +} else enum { + disabled, + pub const default: UseCopyFileRange = .disabled; +}; + +pub const UseFcopyfile = if (have_fcopyfile) enum { + enabled, + disabled, + pub const default: UseFcopyfile = .enabled; +} else enum { + disabled, + pub const default: UseFcopyfile = .disabled; +}; + const Thread = struct { /// The value that needs to be passed to pthread_kill or tgkill in order to /// send a signal. @@ -677,6 +709,8 @@ pub fn io(t: *Threaded) Io { .fileClose = fileClose, .fileWriteStreaming = fileWriteStreaming, .fileWritePositional = fileWritePositional, + .fileWriteFileStreaming = fileWriteFileStreaming, + .fileWriteFilePositional = fileWriteFilePositional, .fileReadStreaming = fileReadStreaming, .fileReadPositional = fileReadPositional, .fileSeekBy = fileSeekBy, @@ -797,6 +831,8 @@ pub fn ioBasic(t: *Threaded) Io { .fileClose = fileClose, .fileWriteStreaming = fileWriteStreaming, .fileWritePositional = fileWritePositional, + .fileWriteFileStreaming = fileWriteFileStreaming, + .fileWriteFilePositional = fileWriteFilePositional, .fileReadStreaming = fileReadStreaming, .fileReadPositional = fileReadPositional, .fileSeekBy = fileSeekBy, @@ -837,7 +873,7 @@ pub fn ioBasic(t: *Threaded) Io { }; } -pub const socket_flags_unsupported = native_os.isDarwin() or native_os == .haiku; +pub const socket_flags_unsupported = is_darwin or native_os == .haiku; const have_accept4 = !socket_flags_unsupported; const have_flock_open_flags = @hasField(posix.O, "EXLOCK"); const have_networking = native_os != .wasi; @@ -853,6 +889,12 @@ const have_preadv = switch (native_os) { }; const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO"); const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE"); +const have_sendfile = if (builtin.link_libc) @TypeOf(std.c.sendfile) != void else native_os == .linux; +const have_copy_file_range = switch (native_os) { + .linux, .freebsd => true, + else => false, +}; +const have_fcopyfile = is_darwin; const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; @@ -860,6 +902,18 @@ const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.syste const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek; const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv; const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate; +const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev; +const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile; +const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ + .major = 34, + .minor = 0, + .patch = 0, +} else .{ + .major = 2, + .minor = 27, + .patch = 0, +}); +const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux; /// Trailing data: /// 1. context @@ -5896,27 +5950,828 @@ fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelf fn fileWritePositional( userdata: ?*anyopaque, file: Io.File, - buffer: [][]const u8, + header: []const u8, + data: []const []const u8, + splat: usize, offset: u64, ) Io.File.WritePositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; + const current_thread = Thread.getCurrent(t); + + if (is_windows) @panic("TODO"); + + var iovecs: [max_iovecs_len]posix.iovec_const = undefined; + var iovlen: iovlen_t = 0; + addBuf(&iovecs, &iovlen, header); + for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes); + const pattern = data[data.len - 1]; + if (iovecs.len - iovlen != 0) switch (splat) { + 0 => {}, + 1 => addBuf(&iovecs, &iovlen, pattern), + else => switch (pattern.len) { + 0 => {}, + 1 => { + var backup_buffer: [splat_buffer_size]u8 = undefined; + const splat_buffer = &backup_buffer; + const memset_len = @min(splat_buffer.len, splat); + const buf = splat_buffer[0..memset_len]; + @memset(buf, pattern[0]); + addBuf(&iovecs, &iovlen, buf); + var remaining_splat = splat - buf.len; + while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) { + assert(buf.len == splat_buffer.len); + addBuf(&iovecs, &iovlen, splat_buffer); + remaining_splat -= splat_buffer.len; + } + addBuf(&iovecs, &iovlen, splat_buffer[0..remaining_splat]); + }, + else => for (0..@min(splat, iovecs.len - iovlen)) |_| { + addBuf(&iovecs, &iovlen, pattern); + }, + }, + }; + + if (native_os == .wasi and !builtin.link_libc) { + var n_written: usize = undefined; + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) { + .SUCCESS => { + current_thread.endSyscall(); + return n_written; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .BADF => return error.NotOpenForWriting, // can be a race condition. + .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .NOTCAPABLE => return error.AccessDenied, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + + try current_thread.beginSyscall(); while (true) { - _ = file; - _ = buffer; - _ = offset; - @panic("TODO implement fileWritePositional"); + const rc = pwritev_sym(file.handle, &iovecs, iovlen, @bitCast(offset)); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + return @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .INVAL => return error.InvalidArgument, + .FAULT => |err| return errnoBug(err), + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForWriting, // Usually a race condition. + .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .CONNRESET => return error.ConnectionResetByPeer, + .BUSY => return error.DeviceBusy, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } + }, + } } } -fn fileWriteStreaming(userdata: ?*anyopaque, file: Io.File, buffer: [][]const u8) Io.File.WriteStreamingError!usize { +fn fileWriteStreaming( + userdata: ?*anyopaque, + file: Io.File, + header: []const u8, + data: []const []const u8, + splat: usize, +) Io.File.WriteStreamingError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; + const current_thread = Thread.getCurrent(t); + + if (is_windows) @panic("TODO"); + + var iovecs: [max_iovecs_len]posix.iovec_const = undefined; + var iovlen: iovlen_t = 0; + addBuf(&iovecs, &iovlen, header); + for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes); + const pattern = data[data.len - 1]; + if (iovecs.len - iovlen != 0) switch (splat) { + 0 => {}, + 1 => addBuf(&iovecs, &iovlen, pattern), + else => switch (pattern.len) { + 0 => {}, + 1 => { + var backup_buffer: [splat_buffer_size]u8 = undefined; + const splat_buffer = &backup_buffer; + const memset_len = @min(splat_buffer.len, splat); + const buf = splat_buffer[0..memset_len]; + @memset(buf, pattern[0]); + addBuf(&iovecs, &iovlen, buf); + var remaining_splat = splat - buf.len; + while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) { + assert(buf.len == splat_buffer.len); + addBuf(&iovecs, &iovlen, splat_buffer); + remaining_splat -= splat_buffer.len; + } + addBuf(&iovecs, &iovlen, splat_buffer[0..remaining_splat]); + }, + else => for (0..@min(splat, iovecs.len - iovlen)) |_| { + addBuf(&iovecs, &iovlen, pattern); + }, + }, + }; + + if (native_os == .wasi and !builtin.link_libc) { + var n_written: usize = undefined; + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) { + .SUCCESS => { + current_thread.endSyscall(); + return n_written; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .BADF => return error.NotOpenForWriting, // can be a race condition. + .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + + try current_thread.beginSyscall(); while (true) { - _ = file; - _ = buffer; - @panic("TODO implement fileWriteStreaming"); + const rc = posix.system.writev(file.handle, &iovecs, iovlen); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + return @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .INVAL => return error.InvalidArgument, + .FAULT => |err| return errnoBug(err), + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForWriting, // Can be a race condition. + .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .CONNRESET => return error.ConnectionResetByPeer, + .BUSY => return error.DeviceBusy, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn fileWriteFileStreaming( + userdata: ?*anyopaque, + file: Io.File, + header: []const u8, + file_reader: *Io.File.Reader, + limit: Io.Limit, +) Io.File.WriteFileStreamingError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const reader_buffered = file_reader.interface.buffered(); + if (reader_buffered.len >= @intFromEnum(limit)) { + const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + const file_limit = @intFromEnum(limit) - reader_buffered.len; + const out_fd = file.handle; + const in_fd = file_reader.file.handle; + + if (file_reader.size) |size| { + if (size - file_reader.pos == 0) { + if (reader_buffered.len != 0) { + const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } else { + return error.EndOfStream; + } + } + } + + if (native_os == .freebsd) sf: { + // Try using sendfile on FreeBSD. + if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; + const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; + var hdtr_data: std.c.sf_hdtr = undefined; + var headers: [2]posix.iovec_const = undefined; + var headers_i: u8 = 0; + if (header.len != 0) { + headers[headers_i] = .{ .base = header.ptr, .len = header.len }; + headers_i += 1; + } + if (reader_buffered.len != 0) { + headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; + headers_i += 1; + } + const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { + hdtr_data = .{ + .headers = &headers, + .hdr_cnt = headers_i, + .trailers = null, + .trl_cnt = 0, + }; + break :b &hdtr_data; + }; + var sbytes: std.c.off_t = 0; + const nbytes: usize = @min(file_limit, std.math.maxInt(usize)); + const flags = 0; + + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + .INTR, .BUSY => { + if (sbytes == 0) { + try current_thread.checkCancel(); + continue; + } else { + // Even if we are being canceled, there have been side + // effects, so it is better to report those side + // effects to the caller. + current_thread.endSyscall(); + break; + } + }, + .AGAIN => { + current_thread.endSyscall(); + if (sbytes == 0) return error.WouldBlock; + break; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .NOTCONN => return error.BrokenPipe, + .IO => return error.InputOutput, + .PIPE => return error.BrokenPipe, + .NOBUFS => return error.SystemResources, + .BADF => |err| errnoBug(err), + .FAULT => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + // Give calling code chance to observe the error before trying + // something else. + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + } + } + if (sbytes == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + const ubytes: usize = @intCast(sbytes); + file_reader.seekBy(ubytes -| header.len) catch return error.ReadFailed; + return ubytes; } + + if (is_darwin) sf: { + // Try using sendfile on macOS. + if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; + const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; + var hdtr_data: std.c.sf_hdtr = undefined; + var headers: [2]posix.iovec_const = undefined; + var headers_i: u8 = 0; + if (header.len != 0) { + headers[headers_i] = .{ .base = header.ptr, .len = header.len }; + headers_i += 1; + } + if (reader_buffered.len != 0) { + headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; + headers_i += 1; + } + const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { + hdtr_data = .{ + .headers = &headers, + .hdr_cnt = headers_i, + .trailers = null, + .trl_cnt = 0, + }; + break :b &hdtr_data; + }; + const max_count = std.math.maxInt(i32); // Avoid EINVAL. + var len: std.c.off_t = @min(file_limit, max_count); + const flags = 0; + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .OPNOTSUPP, .NOTSOCK, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + .INTR => { + if (len == 0) { + try current_thread.checkCancel(); + continue; + } else { + // Even if we are being canceled, there have been side + // effects, so it is better to report those side + // effects to the caller. + current_thread.endSyscall(); + break; + } + }, + .AGAIN => { + current_thread.endSyscall(); + if (len == 0) return error.WouldBlock; + break; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .NOTCONN => return error.BrokenPipe, + .IO => return error.InputOutput, + .PIPE => return error.BrokenPipe, + .BADF => |err| errnoBug(err), + .FAULT => |err| errnoBug(err), + .INVAL => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + // Give calling code chance to observe the error before trying + // something else. + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + } + } + if (len == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + const u_len: usize = @bitCast(len); + file_reader.seekBy(u_len -| header.len) catch return error.ReadFailed; + return u_len; + } + + if (native_os == .linux) sf: { + // Try using sendfile on Linux. + if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; + // Linux sendfile does not support headers. + if (header.len != 0 or reader_buffered.len != 0) { + const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + const max_count = 0x7ffff000; // Avoid EINVAL. + var off: std.os.linux.off_t = undefined; + const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { + .positional => o: { + const size = file_reader.getSize() catch return 0; + off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; + break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; + }, + .streaming => .{ null, limit.minInt(max_count) }, + .streaming_reading, .positional_reading => break :sf, + .failure => return error.ReadFailed, + }; + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); + const n: usize = while (true) { + const rc = sendfile_sym(out_fd, in_fd, off_ptr, count); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .NOSYS, .INVAL => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket + .AGAIN => return error.WouldBlock, + .IO => return error.InputOutput, + .PIPE => return error.BrokenPipe, + .NOMEM => return error.SystemResources, + .NXIO, .SPIPE => { + file_reader.mode = file_reader.mode.toStreaming(); + const pos = file_reader.pos; + if (pos != 0) { + file_reader.pos = 0; + file_reader.seekBy(@intCast(pos)) catch { + file_reader.mode = .failure; + return error.ReadFailed; + }; + } + return 0; + }, + .BADF => |err| errnoBug(err), // Always a race condition. + .FAULT => |err| errnoBug(err), // Segmentation fault. + .OVERFLOW => |err| errnoBug(err), // We avoid passing too large of a `count`. + else => |err| posix.unexpectedErrno(err), + }); + // Give calling code chance to observe the error before trying + // something else. + @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); + return 0; + }, + } + }; + if (n == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + file_reader.pos += n; + return n; + } + + if (have_copy_file_range) cfr: { + if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; + if (header.len != 0 or reader_buffered.len != 0) { + const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + var off_in: i64 = undefined; + const off_in_ptr: ?*i64 = switch (file_reader.mode) { + .positional_reading, .streaming_reading => return error.Unimplemented, + .positional => p: { + off_in = @intCast(file_reader.pos); + break :p &off_in; + }, + .streaming => null, + .failure => return error.WriteFailed, + }; + const current_thread = Thread.getCurrent(t); + const n: usize = switch (native_os) { + .linux => n: { + try current_thread.beginSyscall(); + while (true) { + const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0); + switch (linux_copy_file_range_sys.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break :n @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + .OPNOTSUPP, .INVAL, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .OVERFLOW => return error.Overflow, + .PERM => return error.PermissionDenied, + .TXTBSY => return error.SwapFile, + .XDEV => return error.NotSameFileSystem, + .BADF => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + } + } + }, + .freebsd => n: { + try current_thread.beginSyscall(); + while (true) { + const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0); + switch (std.c.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break :n @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .OPNOTSUPP, .INVAL, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .INTEGRITY => return error.CorruptedData, + .ISDIR => return error.IsDir, + .NOSPC => return error.NoSpaceLeft, + .BADF => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + } + } + }, + else => comptime unreachable, + }; + if (n == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + file_reader.pos += n; + return n; + } + + return error.Unimplemented; +} + +fn fileWriteFilePositional( + userdata: ?*anyopaque, + file: Io.File, + header: []const u8, + file_reader: *Io.File.Reader, + limit: Io.Limit, + offset: u64, +) Io.File.WriteFilePositionalError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const reader_buffered = file_reader.interface.buffered(); + if (reader_buffered.len >= @intFromEnum(limit)) { + const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + const out_fd = file.handle; + const in_fd = file_reader.file.handle; + + if (file_reader.size) |size| { + if (size - file_reader.pos == 0) { + if (reader_buffered.len != 0) { + const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } else { + return error.EndOfStream; + } + } + } + + if (have_copy_file_range) cfr: { + if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; + if (header.len != 0 or reader_buffered.len != 0) { + const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + var off_in: i64 = undefined; + const off_in_ptr: ?*i64 = switch (file_reader.mode) { + .positional_reading, .streaming_reading => return error.Unimplemented, + .positional => p: { + off_in = @intCast(file_reader.pos); + break :p &off_in; + }, + .streaming => null, + .failure => return error.WriteFailed, + }; + var off_out: i64 = @intCast(offset); + const current_thread = Thread.getCurrent(t); + const n: usize = switch (native_os) { + .linux => n: { + try current_thread.beginSyscall(); + while (true) { + const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0); + switch (linux_copy_file_range_sys.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break :n @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + .OPNOTSUPP, .INVAL, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .OVERFLOW => return error.Unseekable, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .PERM => return error.PermissionDenied, + .TXTBSY => return error.SwapFile, + .XDEV => return error.NotSameFileSystem, + .BADF => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + } + } + }, + .freebsd => n: { + try current_thread.beginSyscall(); + while (true) { + const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0); + switch (std.c.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break :n @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .OPNOTSUPP, .INVAL, .NOSYS => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .INTEGRITY => return error.CorruptedData, + .ISDIR => return error.IsDir, + .NOSPC => return error.NoSpaceLeft, + .OVERFLOW => return error.Unseekable, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .BADF => |err| errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); + return 0; + }, + } + } + }, + else => comptime unreachable, + }; + if (n == 0) { + file_reader.size = file_reader.pos; + return error.EndOfStream; + } + file_reader.pos += n; + return n; + } + + if (is_darwin) fcf: { + if (@atomicLoad(UseFcopyfile, &t.use_fcopyfile, .monotonic) == .disabled) break :fcf; + if (file_reader.pos != 0) break :fcf; + if (offset != 0) break :fcf; + if (limit != .unlimited) break :fcf; + const size = file_reader.getSize() catch break :fcf; + if (header.len != 0 or reader_buffered.len != 0) { + const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); + file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + return n; + } + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); + while (true) { + const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .OPNOTSUPP => { + // Give calling code chance to observe before trying + // something else. + current_thread.endSyscall(); + @atomicStore(UseFcopyfile, &t.use_fcopyfile, .disabled, .monotonic); + return 0; + }, + else => |e| { + current_thread.endSyscall(); + assert(error.Unexpected == switch (e) { + .NOMEM => return error.SystemResources, + .INVAL => |err| posix.errnoBug(err), + else => |err| posix.unexpectedErrno(err), + }); + return 0; + }, + } + } + file_reader.pos = size; + return size; + } + + return error.Unimplemented; } fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { @@ -7847,6 +8702,7 @@ fn netWritePosix( }, }; const flags = posix.MSG.NOSIGNAL; + try current_thread.beginSyscall(); while (true) { const rc = posix.system.sendmsg(fd, &msg, flags); @@ -8010,7 +8866,11 @@ fn netWriteUnavailable( return error.NetworkDown; } -fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void { +/// This is either usize or u32. Since, either is fine, let's use the same +/// `addBuf` function for both writing to a file and sending network messages. +const iovlen_t = @FieldType(posix.msghdr_const, "iovlen"); + +fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void { // OS checks ptr addr before length so zero length vectors must be omitted. if (bytes.len == 0) return; if (v.len - i.* == 0) return; @@ -8336,7 +9196,7 @@ fn netLookupFallible( // TODO use dnsres_getaddrinfo } - if (native_os.isDarwin()) { + if (is_darwin) { // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution } diff --git a/lib/std/os.zig b/lib/std/os.zig index fdd5ab2480a4a1e4f32ab59ece17115ac97ce372..77a3833c2bbb28e475d298bd84588ff7e2428700 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -30,7 +30,6 @@ pub const uefi = @import("os/uefi.zig"); pub const wasi = @import("os/wasi.zig"); pub const emscripten = @import("os/emscripten.zig"); pub const windows = @import("os/windows.zig"); -pub const freebsd = @import("os/freebsd.zig"); test { _ = linux; diff --git a/lib/std/os/freebsd.zig b/lib/std/os/freebsd.zig deleted file mode 100644 index 2d082bf0cdf8e2882a218cf12037d8f261817584..0000000000000000000000000000000000000000 --- a/lib/std/os/freebsd.zig +++ /dev/null @@ -1,50 +0,0 @@ -const std = @import("../std.zig"); -const fd_t = std.c.fd_t; -const off_t = std.c.off_t; -const unexpectedErrno = std.posix.unexpectedErrno; -const errno = std.posix.errno; -const builtin = @import("builtin"); - -pub const CopyFileRangeError = std.posix.UnexpectedError || error{ - /// If infd is not open for reading or outfd is not open for writing, or - /// opened for writing with O_APPEND, or if infd and outfd refer to the - /// same file. - BadFileFlags, - /// If the copy exceeds the process's file size limit or the maximum - /// file size for the file system outfd re- sides on. - FileTooBig, - /// A signal interrupted the system call before it could be completed. - /// This may happen for files on some NFS mounts. When this happens, - /// the values pointed to by inoffp and outoffp are reset to the - /// initial values for the system call. - Interrupted, - /// One of: - /// * infd and outfd refer to the same file and the byte ranges overlap. - /// * The flags argument is not zero. - /// * Either infd or outfd refers to a file object that is not a regular file. - InvalidArguments, - /// An I/O error occurred while reading/writing the files. - InputOutput, - /// Corrupted data was detected while reading from a file system. - CorruptedData, - /// Either infd or outfd refers to a directory. - IsDir, - /// File system that stores outfd is full. - NoSpaceLeft, -}; - -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 { - const rc = std.c.copy_file_range(fd_in, off_in, fd_out, off_out, len, flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return error.BadFileFlags, - .FBIG => return error.FileTooBig, - .INTR => return error.Interrupted, - .INVAL => return error.InvalidArguments, - .IO => return error.InputOutput, - .INTEGRITY => return error.CorruptedData, - .ISDIR => return error.IsDir, - .NOSPC => return error.NoSpaceLeft, - else => |err| return unexpectedErrno(err), - } -} diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index d0cd278224456c61f3c28bf596e5e8bd3f624df6..a12654ee443daeb6821b122cba5ed9092f1c02e8 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -9872,165 +9872,3 @@ pub const cmsghdr = extern struct { level: i32, type: i32, }; - -/// The syscalls, but with Zig error sets, going through libc if linking libc, -/// and with some footguns eliminated. -pub const wrapped = struct { - pub const lfs64_abi = builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid()); - const system = if (builtin.link_libc) std.c else std.os.linux; - - pub const SendfileError = std.posix.UnexpectedError || error{ - /// `out_fd` is an unconnected socket, or out_fd closed its read end. - BrokenPipe, - /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd. - UnsupportedOperation, - /// Nonblocking I/O has been selected but the write would block. - WouldBlock, - /// Unspecified error while reading from in_fd. - InputOutput, - /// Insufficient kernel memory to read from in_fd. - SystemResources, - /// `offset` is not `null` but the input file is not seekable. - Unseekable, - }; - - pub fn sendfile( - out_fd: fd_t, - in_fd: fd_t, - in_offset: ?*off_t, - in_len: usize, - ) SendfileError!usize { - const adjusted_len = @min(in_len, 0x7ffff000); // Prevents EOVERFLOW. - const sendfileSymbol = if (lfs64_abi) system.sendfile64 else system.sendfile; - const rc = sendfileSymbol(out_fd, in_fd, in_offset, adjusted_len); - switch (system.errno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return invalidApiUsage(), // Always a race condition. - .FAULT => return invalidApiUsage(), // Segmentation fault. - .OVERFLOW => return unexpectedErrno(.OVERFLOW), // We avoid passing too large of a `count`. - .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket - .INVAL => return error.UnsupportedOperation, - .AGAIN => return error.WouldBlock, - .IO => return error.InputOutput, - .PIPE => return error.BrokenPipe, - .NOMEM => return error.SystemResources, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - - pub const CopyFileRangeError = std.posix.UnexpectedError || error{ - /// One of: - /// * One or more file descriptors are not valid. - /// * fd_in is not open for reading; or fd_out is not open for writing. - /// * The O_APPEND flag is set for the open file description referred - /// to by the file descriptor fd_out. - BadFileFlags, - /// One of: - /// * An attempt was made to write at a position past the maximum file - /// offset the kernel supports. - /// * An attempt was made to write a range that exceeds the allowed - /// maximum file size. The maximum file size differs between - /// filesystem implementations and can be different from the maximum - /// allowed file offset. - /// * An attempt was made to write beyond the process's file size - /// resource limit. This may also result in the process receiving a - /// SIGXFSZ signal. - FileTooBig, - /// One of: - /// * either fd_in or fd_out is not a regular file - /// * flags argument is not zero - /// * fd_in and fd_out refer to the same file and the source and target ranges overlap. - InvalidArguments, - /// A low-level I/O error occurred while copying. - InputOutput, - /// Either fd_in or fd_out refers to a directory. - IsDir, - OutOfMemory, - /// There is not enough space on the target filesystem to complete the copy. - NoSpaceLeft, - /// (since Linux 5.19) the filesystem does not support this operation. - OperationNotSupported, - /// The requested source or destination range is too large to represent - /// in the specified data types. - Overflow, - /// fd_out refers to an immutable file. - PermissionDenied, - /// Either fd_in or fd_out refers to an active swap file. - SwapFile, - /// The files referred to by fd_in and fd_out are not on the same - /// filesystem, and the source and target filesystems are not of the - /// same type, or do not support cross-filesystem copy. - NotSameFileSystem, - }; - - 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 { - const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 34, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 27, .patch = 0 }); - const sys = if (use_c) std.c else std.os.linux; - const rc = sys.copy_file_range(fd_in, off_in, fd_out, off_out, len, flags); - switch (sys.errno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return error.BadFileFlags, - .FBIG => return error.FileTooBig, - .INVAL => return error.InvalidArguments, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOMEM => return error.OutOfMemory, - .NOSPC => return error.NoSpaceLeft, - .OPNOTSUPP => return error.OperationNotSupported, - .OVERFLOW => return error.Overflow, - .PERM => return error.PermissionDenied, - .TXTBSY => return error.SwapFile, - .XDEV => return error.NotSameFileSystem, - else => |err| return unexpectedErrno(err), - } - } - - pub const StatxError = std.posix.UnexpectedError || error{ - /// Search permission is denied for one of the directories in `path`. - AccessDenied, - /// Too many symbolic links were encountered traversing `path`. - SymLinkLoop, - /// `path` is too long. - NameTooLong, - /// One of: - /// - A component of `path` does not exist. - /// - A component of `path` is not a directory. - /// - `path` is a relative and `dirfd` is not a directory file descriptor. - FileNotFound, - /// Insufficient memory is available. - SystemResources, - }; - - pub fn statx(dirfd: fd_t, path: [*:0]const u8, flags: u32, mask: STATX) StatxError!Statx { - const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) - .{ .major = 30, .minor = 0, .patch = 0 } - else - .{ .major = 2, .minor = 28, .patch = 0 }); - const sys = if (use_c) std.c else std.os.linux; - - var stx = std.mem.zeroes(Statx); - const rc = sys.statx(dirfd, path, flags, mask, &stx); - return switch (sys.errno(rc)) { - .SUCCESS => stx, - .ACCES => error.AccessDenied, - .BADF => invalidApiUsage(), - .FAULT => invalidApiUsage(), - .INVAL => invalidApiUsage(), - .LOOP => error.SymLinkLoop, - .NAMETOOLONG => error.NameTooLong, - .NOENT => error.FileNotFound, - .NOTDIR => error.FileNotFound, - .NOMEM => error.SystemResources, - else => |err| unexpectedErrno(err), - }; - } - - const unexpectedErrno = std.posix.unexpectedErrno; - - fn invalidApiUsage() error{Unexpected} { - if (builtin.mode == .Debug) @panic("invalid API usage"); - return error.Unexpected; - } -}; diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index 500c3f0baea3f9ad26ce51e0e1179daced0fb422..7399907477641340acb55a51f8a3b95eaa50f426 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -401,14 +401,6 @@ test "futex2_requeue" { try expectEqual(0, rc); } -test "copy_file_range error" { - const fds = try std.posix.pipe(); - defer std.posix.close(fds[0]); - defer std.posix.close(fds[1]); - - try std.testing.expectError(error.InvalidArguments, linux.wrapped.copy_file_range(fds[0], null, fds[1], null, 1, 0)); -} - test { _ = linux.IoUring; } diff --git a/lib/std/posix.zig b/lib/std/posix.zig index e3809ee9208d8dfcae4c0024f1e14c431dcdda1d..a35f3e52dc887a790c5c23ef4d1726e21b49f2f9 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1157,80 +1157,6 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { } } -/// Write multiple buffers to a file descriptor. -/// Retries when interrupted by a signal. -/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero. -/// -/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can -/// occur for various reasons; for example, because there was insufficient space on the disk -/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or -/// similar was interrupted by a signal handler after it had transferred some, but before it had -/// transferred all of the requested bytes. In the event of a partial write, the caller can make -/// another write() call to transfer the remaining bytes. The subsequent call will either -/// transfer further bytes or may result in an error (e.g., if the disk is now full). -/// -/// For POSIX systems, if `fd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are -/// used to perform the I/O. `error.WouldBlock` is not possible on Windows. -/// -/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur. -/// -/// This function assumes that all vectors, including zero-length vectors, have -/// a pointer within the address space of the application. -pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { - if (native_os == .windows) { - // TODO improve this to use WriteFileScatter - if (iov.len == 0) return 0; - const first = iov[0]; - return write(fd, first.base[0..first.len]); - } - if (native_os == .wasi and !builtin.link_libc) { - var nwritten: usize = undefined; - switch (wasi.fd_write(fd, iov.ptr, iov.len, &nwritten)) { - .SUCCESS => return nwritten, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .BADF => return error.NotOpenForWriting, // can be a race condition. - .DESTADDRREQ => unreachable, // `connect` was never called. - .DQUOT => return error.DiskQuota, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .PERM => return error.PermissionDenied, - .PIPE => return error.BrokenPipe, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - while (true) { - const rc = system.writev(fd, iov.ptr, @min(iov.len, IOV_MAX)); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .INVAL => return error.InvalidArgument, - .FAULT => unreachable, - .SRCH => return error.ProcessNotFound, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForWriting, // Can be a race condition. - .DESTADDRREQ => unreachable, // `connect` was never called. - .DQUOT => return error.DiskQuota, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .PERM => return error.PermissionDenied, - .PIPE => return error.BrokenPipe, - .CONNRESET => return error.ConnectionResetByPeer, - .BUSY => return error.DeviceBusy, - .CANCELED => return error.Canceled, - else => |err| return unexpectedErrno(err), - } - } -} - pub const PWriteError = WriteError || error{Unseekable}; /// Write to a file descriptor, with a position offset. @@ -4384,98 +4310,6 @@ pub fn send( }; } -pub const CopyFileRangeError = error{ - FileTooBig, - InputOutput, - /// `fd_in` is not open for reading; or `fd_out` is not open for writing; - /// or the `APPEND` flag is set for `fd_out`. - FilesOpenedWithWrongFlags, - IsDir, - OutOfMemory, - NoSpaceLeft, - Unseekable, - PermissionDenied, - SwapFile, - CorruptedData, -} || PReadError || PWriteError || UnexpectedError; - -/// Transfer data between file descriptors at specified offsets. -/// -/// Returns the number of bytes written, which can less than requested. -/// -/// The `copy_file_range` call copies `len` bytes from one file descriptor to another. When possible, -/// this is done within the operating system kernel, which can provide better performance -/// characteristics than transferring data from kernel to user space and back, such as with -/// `pread` and `pwrite` calls. -/// -/// `fd_in` must be a file descriptor opened for reading, and `fd_out` must be a file descriptor -/// opened for writing. They may be any kind of file descriptor; however, if `fd_in` is not a regular -/// file system file, it may cause this function to fall back to calling `pread` and `pwrite`, in which case -/// atomicity guarantees no longer apply. -/// -/// If `fd_in` and `fd_out` are the same, source and target ranges must not overlap. -/// The file descriptor seek positions are ignored and not updated. -/// When `off_in` is past the end of the input file, it successfully reads 0 bytes. -/// -/// `flags` has different meanings per operating system; refer to the respective man pages. -/// -/// These systems support in-kernel data copying: -/// * Linux (cross-filesystem from version 5.3) -/// * FreeBSD 13.0 -/// -/// Other systems fall back to calling `pread` / `pwrite`. -/// -/// Maximum offsets on Linux and FreeBSD are `maxInt(i64)`. -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 { - if (builtin.os.tag == .freebsd or builtin.os.tag == .linux) { - const use_c = native_os != .linux or - std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 34, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 27, .patch = 0 }); - const sys = if (use_c) std.c else linux; - - var off_in_copy: i64 = @bitCast(off_in); - var off_out_copy: i64 = @bitCast(off_out); - - while (true) { - const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags); - if (native_os == .freebsd) { - switch (sys.errno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return error.FilesOpenedWithWrongFlags, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOSPC => return error.NoSpaceLeft, - .INVAL => break, // these may not be regular files, try fallback - .INTEGRITY => return error.CorruptedData, - .INTR => continue, - else => |err| return unexpectedErrno(err), - } - } else { // assume linux - switch (sys.errno(rc)) { - .SUCCESS => return @intCast(rc), - .BADF => return error.FilesOpenedWithWrongFlags, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOSPC => return error.NoSpaceLeft, - .INVAL => break, // these may not be regular files, try fallback - .NOMEM => return error.OutOfMemory, - .OVERFLOW => return error.Unseekable, - .PERM => return error.PermissionDenied, - .TXTBSY => return error.SwapFile, - .XDEV => break, // support for cross-filesystem copy added in Linux 5.3, use fallback - else => |err| return unexpectedErrno(err), - } - } - } - } - - var buf: [8 * 4096]u8 = undefined; - const amt_read = try pread(fd_in, buf[0..@min(buf.len, len)], off_in); - if (amt_read == 0) return 0; - return pwrite(fd_out, buf[0..amt_read], off_out); -} - pub const PollError = error{ /// The network subsystem has failed. NetworkDown, -- 2.54.0 From 72b76077ab4e5042b13ea311063f2c7e867e00ba Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 5 Dec 2025 17:41:27 -0800 Subject: [PATCH 006/195] std: fix some surface level compilation errors And boldly remove preadv, pwritev, readv, writev, pread, pwrite from std.posix. --- lib/std/Io.zig | 6 +- lib/std/Io/File.zig | 4 +- lib/std/Io/File/Writer.zig | 25 ++- lib/std/Io/Threaded.zig | 16 ++ lib/std/Io/net.zig | 15 +- lib/std/Io/tty.zig | 4 +- lib/std/Progress.zig | 4 +- lib/std/posix.zig | 397 ------------------------------------- 8 files changed, 64 insertions(+), 407 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index acca9f700c6ec4d74ca40a179f856d1b7571f335..0727e3efd331ffac356be6785a9b302a9db12204 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -705,8 +705,8 @@ pub const VTable = struct { fileSetLength: *const fn (?*anyopaque, File, u64) File.SetLengthError!void, fileSetOwner: *const fn (?*anyopaque, File, ?File.Uid, ?File.Gid) File.SetOwnerError!void, fileSetPermissions: *const fn (?*anyopaque, File, File.Permissions) File.SetPermissionsError!void, - fileSetTimestamps: *const fn (?*anyopaque, File, last_accessed: Timestamp, last_modified: Timestamp, File.SetTimestampsOptions) File.SetTimestampsError!void, - fileSetTimestampsNow: *const fn (?*anyopaque, File, File.SetTimestampsOptions) File.SetTimestampsError!void, + fileSetTimestamps: *const fn (?*anyopaque, File, last_accessed: Timestamp, last_modified: Timestamp) File.SetTimestampsError!void, + fileSetTimestampsNow: *const fn (?*anyopaque, File) File.SetTimestampsError!void, fileLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!void, fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool, fileUnlock: *const fn (?*anyopaque, File) void, @@ -726,7 +726,7 @@ pub const VTable = struct { /// Returns 0 on end of stream. netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize, netWrite: *const fn (?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize, - netWriteFile: *const fn (?*anyopaque, net.Socket.Handle, header: []const u8, *Io.File.Reader, Io.Limit) net.Stream.WriteFileError!usize, + netWriteFile: *const fn (?*anyopaque, net.Socket.Handle, header: []const u8, *Io.File.Reader, Io.Limit) net.Stream.Writer.WriteFileError!usize, netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void, netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 46235d73f2bcbc84ae5f22856f7634b23fc1d974..a0349498c5f5027b47a1c364bcbc9546159b311f 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -274,7 +274,7 @@ pub fn sync(file: File, io: Io) SyncError!void { /// Test whether the file refers to a terminal. /// /// See also: -/// * `getOrEnableAnsiEscapeSupport` +/// * `enableAnsiEscapeCodes` /// * `supportsAnsiEscapeCodes`. pub fn isTty(file: File, io: Io) bool { return io.vtable.fileIsTty(io.userdata, file); @@ -282,7 +282,7 @@ pub fn isTty(file: File, io: Io) bool { pub const EnableAnsiEscapeCodesError = error{} || Io.Cancelable || Io.UnexpectedError; -pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError { +pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError!void { return io.vtable.fileEnableAnsiEscapeCodes(io.userdata, file); } diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index bb141f9ef6d4b16017111cabb883b0f84af8ba8d..e511e62e195159910f416b6855ebfb18b3086c48 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -7,7 +7,7 @@ const assert = std.debug.assert; io: Io, file: File, -err: ?File.WriteError = null, +err: ?Error = null, mode: Mode = .positional, /// Tracks the true seek position in the file. To obtain the logical /// position, add the buffer size to this value. @@ -18,6 +18,29 @@ interface: Io.Writer, pub const Mode = File.Reader.Mode; +pub const Error = error{ + DiskQuota, + FileTooBig, + InputOutput, + NoSpaceLeft, + DeviceBusy, + InvalidArgument, + /// File descriptor does not hold the required rights to write to it. + AccessDenied, + PermissionDenied, + BrokenPipe, + SystemResources, + NotOpenForWriting, + /// The process cannot access the file because another process has locked + /// a portion of the file. Windows-only. + LockViolation, + /// Non-blocking has been enabled and this operation would block. + WouldBlock, + /// This error occurs when a device gets disconnected before or mid-flush + /// while it's being written to - errno(6): No such device or address. + NoDevice, +} || Io.Cancelable || Io.UnexpectedError; + pub const WriteFileError = error{ /// `out_fd` is an unconnected socket, or out_fd closed its read end. BrokenPipe, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 9a743847ad545a0b58ecde9bf0f06f06b3e5d2a4..bc6c1c468823c23789f3c05a3bfec1813e00fef9 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -6581,6 +6581,22 @@ fn fileWriteFileStreaming( return error.Unimplemented; } +fn netWriteFile( + userdata: ?*anyopaque, + socket_handle: net.Socket.Handle, + header: []const u8, + file_reader: *Io.File.Reader, + limit: Io.Limit, +) net.Stream.WriteFileError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + _ = socket_handle; + _ = header; + _ = file_reader; + _ = limit; + return error.Unimplemented; // TODO +} + fn fileWriteFilePositional( userdata: ?*anyopaque, file: Io.File, diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 65d2dfd5e4682f8e3531c523c6dcd74d0846c066..2a56f3a4ab748cc2147a7f315e893cb786e1a34e 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -1256,6 +1256,7 @@ pub const Stream = struct { interface: Io.Writer, stream: Stream, err: ?Error = null, + write_file_err: ?WriteFileError = null, pub const Error = error{ /// Another TCP Fast Open is already in progress. @@ -1285,12 +1286,17 @@ pub const Stream = struct { SocketNotBound, } || Io.UnexpectedError || Io.Cancelable; + pub const WriteFileError = error{} || Io.Cancelable || Io.UnexpectedError; + pub fn init(stream: Stream, io: Io, buffer: []u8) Writer { return .{ .io = io, .stream = stream, .interface = .{ - .vtable = &.{ .drain = drain }, + .vtable = &.{ + .drain = drain, + .sendFile = sendFile, + }, .buffer = buffer, }, }; @@ -1307,6 +1313,13 @@ pub const Stream = struct { }; return io_w.consume(n); } + + fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { + _ = io_w; + _ = file_reader; + _ = limit; + return error.Unimplemented; // TODO + } }; pub fn reader(stream: Stream, io: Io, buffer: []u8) Reader { diff --git a/lib/std/Io/tty.zig b/lib/std/Io/tty.zig index 3e2bb0969da181cdc2aaa122ed73d3448a0d35d9..08e0bd71f0716d82ba9f24645db6beedd582fb72 100644 --- a/lib/std/Io/tty.zig +++ b/lib/std/Io/tty.zig @@ -50,7 +50,9 @@ pub const Config = union(enum) { if (force_color == false) return .no_color; - if (file.getOrEnableAnsiEscapeSupport()) return .escape_codes; + if (file.enableAnsiEscapeCodes()) |_| { + return .escape_codes; + } else |_| {} if (native_os == .windows and file.isTty()) { var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index d1ab503661416aaea2dd8acd3288e43ebeedaba8..467d4754ff3c84cf885cd538810ffccf45f2a6b6 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -474,9 +474,9 @@ pub fn start(options: Options) Node { } const stderr: std.fs.File = .stderr(); global_progress.terminal = stderr; - if (stderr.getOrEnableAnsiEscapeSupport()) { + if (stderr.enableAnsiEscapeCodes()) |_| { global_progress.terminal_mode = .ansi_escape_codes; - } else if (is_windows and stderr.isTty()) { + } else |_| if (is_windows and stderr.isTty()) { global_progress.terminal_mode = TerminalMode{ .windows_api = .{ .code_page = windows.kernel32.GetConsoleOutputCP(), } }; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index a35f3e52dc887a790c5c23ef4d1726e21b49f2f9..392987ec50e3725dd84dc4b3e28a973e31c58c06 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -813,226 +813,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { } } -/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. -/// -/// For POSIX systems, if `fd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are -/// used to perform the I/O. `error.WouldBlock` is not possible on Windows. -/// -/// This operation is non-atomic on the following systems: -/// * Windows -/// On these systems, the read races with concurrent writes to the same file descriptor. -/// -/// This function assumes that all vectors, including zero-length vectors, have -/// a pointer within the address space of the application. -pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { - if (native_os == .windows) { - if (iov.len == 0) return 0; - const first = iov[0]; - return read(fd, first.base[0..first.len]); - } - if (native_os == .wasi and !builtin.link_libc) { - var nread: usize = undefined; - switch (wasi.fd_read(fd, iov.ptr, iov.len, &nread)) { - .SUCCESS => return nread, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, // currently not support in WASI - .BADF => return error.NotOpenForReading, // can be a race condition - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - while (true) { - const rc = system.readv(fd, iov.ptr, @min(iov.len, IOV_MAX)); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .INVAL => unreachable, - .FAULT => unreachable, - .SRCH => return error.ProcessNotFound, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForReading, // can be a race condition - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - else => |err| return unexpectedErrno(err), - } - } -} - -pub const PReadError = std.Io.File.ReadPositionalError; - -/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. -/// -/// Retries when interrupted by a signal. -/// -/// For POSIX systems, if `fd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are -/// used to perform the I/O. `error.WouldBlock` is not possible on Windows. -/// -/// Linux has a limit on how many bytes may be transferred in one `pread` call, which is `0x7ffff000` -/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as -/// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page. -/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. -/// The corresponding POSIX limit is `maxInt(isize)`. -pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { - if (buf.len == 0) return 0; - if (native_os == .windows) { - return windows.ReadFile(fd, buf, offset); - } - if (native_os == .wasi and !builtin.link_libc) { - const iovs = [1]iovec{iovec{ - .base = buf.ptr, - .len = buf.len, - }}; - - var nread: usize = undefined; - switch (wasi.fd_pread(fd, &iovs, iovs.len, offset, &nread)) { - .SUCCESS => return nread, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .BADF => return error.NotOpenForReading, // Can be a race condition. - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - // Prevent EINVAL. - const max_count = switch (native_os) { - .linux => 0x7ffff000, - .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => maxInt(i32), - else => maxInt(isize), - }; - - const pread_sym = if (lfs64_abi) system.pread64 else system.pread; - while (true) { - const rc = pread_sym(fd, buf.ptr, @min(buf.len, max_count), @bitCast(offset)); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .INVAL => unreachable, - .FAULT => unreachable, - .SRCH => return error.ProcessNotFound, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForReading, // Can be a race condition. - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } -} - -/// Number of bytes read is returned. Upon reading end-of-file, zero is returned. -/// -/// Retries when interrupted by a signal. -/// -/// For POSIX systems, if `fd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are -/// used to perform the I/O. `error.WouldBlock` is not possible on Windows. -/// -/// This operation is non-atomic on the following systems: -/// * Darwin -/// * Windows -/// On these systems, the read races with concurrent writes to the same file descriptor. -pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { - const have_pread_but_not_preadv = switch (native_os) { - .windows, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .haiku => true, - else => false, - }; - if (have_pread_but_not_preadv) { - // We could loop here; but proper usage of `preadv` must handle partial reads anyway. - // So we simply read into the first vector only. - if (iov.len == 0) return 0; - const first = iov[0]; - return pread(fd, first.base[0..first.len], offset); - } - if (native_os == .wasi and !builtin.link_libc) { - var nread: usize = undefined; - switch (wasi.fd_pread(fd, iov.ptr, iov.len, offset, &nread)) { - .SUCCESS => return nread, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .BADF => return error.NotOpenForReading, // can be a race condition - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - const preadv_sym = if (lfs64_abi) system.preadv64 else system.preadv; - while (true) { - const rc = preadv_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset)); - switch (errno(rc)) { - .SUCCESS => return @bitCast(rc), - .INTR => continue, - .INVAL => unreachable, - .FAULT => unreachable, - .SRCH => return error.ProcessNotFound, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForReading, // can be a race condition - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } -} - pub const WriteError = error{ DiskQuota, FileTooBig, @@ -1157,183 +937,6 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { } } -pub const PWriteError = WriteError || error{Unseekable}; - -/// Write to a file descriptor, with a position offset. -/// Retries when interrupted by a signal. -/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero. -/// -/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can -/// occur for various reasons; for example, because there was insufficient space on the disk -/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or -/// similar was interrupted by a signal handler after it had transferred some, but before it had -/// transferred all of the requested bytes. In the event of a partial write, the caller can make -/// another write() call to transfer the remaining bytes. The subsequent call will either -/// transfer further bytes or may result in an error (e.g., if the disk is now full). -/// -/// For POSIX systems, if `fd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are -/// used to perform the I/O. `error.WouldBlock` is not possible on Windows. -/// -/// Linux has a limit on how many bytes may be transferred in one `pwrite` call, which is `0x7ffff000` -/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as -/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page. -/// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL. -/// The corresponding POSIX limit is `maxInt(isize)`. -pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { - if (bytes.len == 0) return 0; - if (native_os == .windows) { - return windows.WriteFile(fd, bytes, offset); - } - if (native_os == .wasi and !builtin.link_libc) { - const ciovs = [1]iovec_const{iovec_const{ - .base = bytes.ptr, - .len = bytes.len, - }}; - - var nwritten: usize = undefined; - switch (wasi.fd_pwrite(fd, &ciovs, ciovs.len, offset, &nwritten)) { - .SUCCESS => return nwritten, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .BADF => return error.NotOpenForWriting, // can be a race condition. - .DESTADDRREQ => unreachable, // `connect` was never called. - .DQUOT => return error.DiskQuota, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .PERM => return error.PermissionDenied, - .PIPE => return error.BrokenPipe, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - // Prevent EINVAL. - const max_count = switch (native_os) { - .linux => 0x7ffff000, - .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => maxInt(i32), - else => maxInt(isize), - }; - - const pwrite_sym = if (lfs64_abi) system.pwrite64 else system.pwrite; - while (true) { - const rc = pwrite_sym(fd, bytes.ptr, @min(bytes.len, max_count), @bitCast(offset)); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .INVAL => return error.InvalidArgument, - .FAULT => unreachable, - .SRCH => return error.ProcessNotFound, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForWriting, // Can be a race condition. - .DESTADDRREQ => unreachable, // `connect` was never called. - .DQUOT => return error.DiskQuota, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .PERM => return error.PermissionDenied, - .PIPE => return error.BrokenPipe, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .BUSY => return error.DeviceBusy, - else => |err| return unexpectedErrno(err), - } - } -} - -/// Write multiple buffers to a file descriptor, with a position offset. -/// Retries when interrupted by a signal. -/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero. -/// -/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can -/// occur for various reasons; for example, because there was insufficient space on the disk -/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or -/// similar was interrupted by a signal handler after it had transferred some, but before it had -/// transferred all of the requested bytes. In the event of a partial write, the caller can make -/// another write() call to transfer the remaining bytes. The subsequent call will either -/// transfer further bytes or may result in an error (e.g., if the disk is now full). -/// -/// If `fd` is opened in non blocking mode, the function will -/// return error.WouldBlock when EAGAIN is received. -/// -/// The following systems do not have this syscall, and will return partial writes if more than one -/// vector is provided: -/// * Darwin -/// * Windows -/// -/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur. -pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize { - const have_pwrite_but_not_pwritev = switch (native_os) { - .windows, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .haiku => true, - else => false, - }; - - if (have_pwrite_but_not_pwritev) { - // We could loop here; but proper usage of `pwritev` must handle partial writes anyway. - // So we simply write the first vector only. - if (iov.len == 0) return 0; - const first = iov[0]; - return pwrite(fd, first.base[0..first.len], offset); - } - if (native_os == .wasi and !builtin.link_libc) { - var nwritten: usize = undefined; - switch (wasi.fd_pwrite(fd, iov.ptr, iov.len, offset, &nwritten)) { - .SUCCESS => return nwritten, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .BADF => return error.NotOpenForWriting, // Can be a race condition. - .DESTADDRREQ => unreachable, // `connect` was never called. - .DQUOT => return error.DiskQuota, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .PERM => return error.PermissionDenied, - .PIPE => return error.BrokenPipe, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - const pwritev_sym = if (lfs64_abi) system.pwritev64 else system.pwritev; - while (true) { - const rc = pwritev_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset)); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .INVAL => return error.InvalidArgument, - .FAULT => unreachable, - .SRCH => return error.ProcessNotFound, - .AGAIN => return error.WouldBlock, - .BADF => return error.NotOpenForWriting, // Can be a race condition. - .DESTADDRREQ => unreachable, // `connect` was never called. - .DQUOT => return error.DiskQuota, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .PERM => return error.PermissionDenied, - .PIPE => return error.BrokenPipe, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .BUSY => return error.DeviceBusy, - else => |err| return unexpectedErrno(err), - } - } -} - pub const OpenError = std.Io.File.OpenError || error{WouldBlock}; /// Open and possibly create a file. Keeps trying if it gets interrupted. -- 2.54.0 From eab354b2f5d7242c036523394023e9824be7eca9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 5 Dec 2025 17:46:35 -0800 Subject: [PATCH 007/195] std.Io.Threaded: import File and Dir --- lib/std/Io/Threaded.zig | 328 ++++++++++++++++++++-------------------- 1 file changed, 165 insertions(+), 163 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index bc6c1c468823c23789f3c05a3bfec1813e00fef9..43424e348b0e4a7c3816307011d43193d77fc7f0 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -11,6 +11,8 @@ const is_debug = builtin.mode == .Debug; const std = @import("../std.zig"); const Io = std.Io; const net = std.Io.net; +const File = std.Io.File; +const Dir = std.Dir; const HostName = std.Io.net.HostName; const IpAddress = std.Io.net.IpAddress; const Allocator = std.mem.Allocator; @@ -1444,7 +1446,7 @@ const dirMake = switch (native_os) { else => dirMakePosix, }; -fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { +fn dirMakePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -1490,7 +1492,7 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: } } -fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { +fn dirMakeWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void { if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, mode); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -1532,7 +1534,7 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I } } -fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { +fn dirMakeWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); try current_thread.checkCancel(); @@ -1560,14 +1562,14 @@ fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode fn dirMakePath( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - mode: Io.Dir.Mode, -) Io.Dir.MakePathError!Io.Dir.MakePathStatus { + mode: Dir.Mode, +) Dir.MakePathError!Dir.MakePathStatus { const t: *Threaded = @ptrCast(@alignCast(userdata)); var it = std.fs.path.componentIterator(sub_path); - var status: Io.Dir.MakePathStatus = .existed; + var status: Dir.MakePathStatus = .existed; var component = it.last() orelse return error.BadPathName; while (true) { if (dirMake(t, dir, component.path, mode)) |_| { @@ -1604,10 +1606,10 @@ const dirMakeOpenPath = switch (native_os) { fn dirMakeOpenPathPosix( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.OpenOptions, -) Io.Dir.MakeOpenPathError!Io.Dir { + options: Dir.OpenOptions, +) Dir.MakeOpenPathError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); const t_io = ioBasic(t); return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) { @@ -1621,10 +1623,10 @@ fn dirMakeOpenPathPosix( fn dirMakeOpenPathWindows( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.OpenOptions, -) Io.Dir.MakeOpenPathError!Io.Dir { + options: Dir.OpenOptions, +) Dir.MakeOpenPathError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const w = windows; @@ -1644,7 +1646,7 @@ fn dirMakeOpenPathWindows( const is_last = it.peekNext() == null; const create_disposition: w.FILE.CREATE_DISPOSITION = if (is_last) .OPEN_IF else .CREATE; - var result: Io.Dir = .{ .handle = undefined }; + var result: Dir = .{ .handle = undefined }; const path_len_bytes: u16 = @intCast(sub_path_w.len * 2); var nt_name: w.UNICODE_STRING = .{ @@ -1736,10 +1738,10 @@ fn dirMakeOpenPathWindows( fn dirMakeOpenPathWasi( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.OpenOptions, -) Io.Dir.MakeOpenPathError!Io.Dir { + options: Dir.OpenOptions, +) Dir.MakeOpenPathError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); const t_io = ioBasic(t); return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) { @@ -1751,9 +1753,9 @@ fn dirMakeOpenPathWasi( }; } -fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat { +fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); - const file: Io.File = .{ .handle = dir.handle }; + const file: File = .{ .handle = dir.handle }; return fileStat(t, file); } @@ -1766,10 +1768,10 @@ const dirStatPath = switch (native_os) { fn dirStatPathLinux( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.StatPathOptions, -) Io.Dir.StatPathError!Io.File.Stat { + options: Dir.StatPathOptions, +) Dir.StatPathError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const linux = std.os.linux; @@ -1828,10 +1830,10 @@ fn dirStatPathLinux( fn dirStatPathPosix( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.StatPathOptions, -) Io.Dir.StatPathError!Io.File.Stat { + options: Dir.StatPathOptions, +) Dir.StatPathError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -1876,10 +1878,10 @@ fn dirStatPathPosix( fn dirStatPathWindows( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.StatPathOptions, -) Io.Dir.StatPathError!Io.File.Stat { + options: Dir.StatPathOptions, +) Dir.StatPathError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); const file = try dirOpenFileWindows(t, dir, sub_path, .{ .follow_symlinks = options.follow_symlinks, @@ -1890,10 +1892,10 @@ fn dirStatPathWindows( fn dirStatPathWasi( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.StatPathOptions, -) Io.Dir.StatPathError!Io.File.Stat { + options: Dir.StatPathOptions, +) Dir.StatPathError!File.Stat { if (builtin.link_libc) return dirStatPathPosix(userdata, dir, sub_path, options); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -1941,7 +1943,7 @@ const fileStat = switch (native_os) { else => fileStatPosix, }; -fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { +fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -1974,7 +1976,7 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File } } -fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { +fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const linux = std.os.linux; @@ -2025,7 +2027,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File } } -fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { +fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); try current_thread.checkCancel(); @@ -2071,7 +2073,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi }; } -fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { +fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { if (builtin.link_libc) return fileStatPosix(userdata, file); const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -2113,10 +2115,10 @@ const dirAccess = switch (native_os) { fn dirAccessPosix( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.AccessOptions, -) Io.Dir.AccessError!void { + options: Dir.AccessOptions, +) Dir.AccessError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -2167,10 +2169,10 @@ fn dirAccessPosix( fn dirAccessWasi( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.AccessOptions, -) Io.Dir.AccessError!void { + options: Dir.AccessOptions, +) Dir.AccessError!void { if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -2240,10 +2242,10 @@ fn dirAccessWasi( fn dirAccessWindows( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.AccessOptions, -) Io.Dir.AccessError!void { + options: Dir.AccessOptions, +) Dir.AccessError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); try current_thread.checkCancel(); @@ -2292,10 +2294,10 @@ const dirCreateFile = switch (native_os) { fn dirCreateFilePosix( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - flags: Io.File.CreateFlags, -) Io.File.OpenError!Io.File { + flags: File.CreateFlags, +) File.OpenError!File { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -2455,10 +2457,10 @@ fn dirCreateFilePosix( fn dirCreateFileWindows( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - flags: Io.File.CreateFlags, -) Io.File.OpenError!Io.File { + flags: File.CreateFlags, +) File.OpenError!File { const w = windows; const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -2509,10 +2511,10 @@ fn dirCreateFileWindows( fn dirCreateFileWasi( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - flags: Io.File.CreateFlags, -) Io.File.OpenError!Io.File { + flags: File.CreateFlags, +) File.OpenError!File { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; @@ -2593,10 +2595,10 @@ const dirOpenFile = switch (native_os) { fn dirOpenFilePosix( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - flags: Io.File.OpenFlags, -) Io.File.OpenError!Io.File { + flags: File.OpenFlags, +) File.OpenError!File { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -2765,10 +2767,10 @@ fn dirOpenFilePosix( fn dirOpenFileWindows( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - flags: Io.File.OpenFlags, -) Io.File.OpenError!Io.File { + flags: File.OpenFlags, +) File.OpenError!File { const t: *Threaded = @ptrCast(@alignCast(userdata)); const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); const sub_path_w = sub_path_w_array.span(); @@ -2780,8 +2782,8 @@ pub fn dirOpenFileWtf16( t: *Threaded, dir_handle: ?windows.HANDLE, sub_path_w: [:0]const u16, - flags: Io.File.OpenFlags, -) Io.File.OpenError!Io.File { + flags: File.OpenFlags, +) File.OpenError!File { if (std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; @@ -2904,10 +2906,10 @@ pub fn dirOpenFileWtf16( fn dirOpenFileWasi( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - flags: Io.File.OpenFlags, -) Io.File.OpenError!Io.File { + flags: File.OpenFlags, +) File.OpenError!File { if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -2991,10 +2993,10 @@ const dirOpenDir = switch (native_os) { /// This function is also used for WASI when libc is linked. fn dirOpenDirPosix( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.OpenOptions, -) Io.Dir.OpenError!Io.Dir { + options: Dir.OpenOptions, +) Dir.OpenError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (is_windows) { @@ -3065,10 +3067,10 @@ fn dirOpenDirPosix( fn dirOpenDirHaiku( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.OpenOptions, -) Io.Dir.OpenError!Io.Dir { + options: Dir.OpenOptions, +) Dir.OpenError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -3116,10 +3118,10 @@ fn dirOpenDirHaiku( pub fn dirOpenDirWindows( t: *Io.Threaded, - dir: Io.Dir, + dir: Dir, sub_path_w: [:0]const u16, - options: Io.Dir.OpenOptions, -) Io.Dir.OpenError!Io.Dir { + options: Dir.OpenOptions, +) Dir.OpenError!Dir { const current_thread = Thread.getCurrent(t); const w = windows; @@ -3130,7 +3132,7 @@ pub fn dirOpenDirWindows( .Buffer = @constCast(sub_path_w.ptr), }; var io_status_block: w.IO_STATUS_BLOCK = undefined; - var result: Io.Dir = .{ .handle = undefined }; + var result: Dir = .{ .handle = undefined }; try current_thread.checkCancel(); const rc = w.ntdll.NtCreateFile( &result.handle, @@ -3190,7 +3192,7 @@ const MakeOpenDirAccessMaskWOptions = struct { create_disposition: u32, }; -fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void { +fn dirClose(userdata: ?*anyopaque, dir: Dir) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; posix.close(dir.handle); @@ -3201,7 +3203,7 @@ const dirRealPath = switch (native_os) { else => dirRealPathPosix, }; -fn dirRealPathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, out_buffer: []u8) Io.Dir.RealPathError!usize { +fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const w = windows; const current_thread = Thread.getCurrent(t); @@ -3237,7 +3239,7 @@ fn dirRealPathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); } -fn dirRealPathPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, out_buffer: []u8) Io.Dir.RealPathError!usize { +fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathError!usize { if (native_os == .wasi) @compileError("unsupported operating system"); const max_path_bytes = std.fs.max_path_bytes; @@ -3434,11 +3436,11 @@ const dirDeleteFile = switch (native_os) { else => dirDeleteFilePosix, }; -fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void { +fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { return dirDeleteWindows(userdata, dir, sub_path, false); } -fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void { +fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { if (builtin.link_libc) return dirDeleteFilePosix(userdata, dir, sub_path); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -3482,7 +3484,7 @@ fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) I } } -fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void { +fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -3567,11 +3569,11 @@ const dirDeleteDir = switch (native_os) { else => dirDeleteDirPosix, }; -fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void { +fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { return dirDeleteWindows(userdata, dir, sub_path, true); } -fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, remove_dir: bool) Io.Dir.DeleteFileError!void { +fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remove_dir: bool) Dir.DeleteFileError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const w = windows; @@ -3712,7 +3714,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, re } } -fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void { +fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { if (builtin.link_libc) return dirDeleteDirPosix(userdata, dir, sub_path); const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -3758,7 +3760,7 @@ fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io } } -fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void { +fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -3812,11 +3814,11 @@ const dirRename = switch (native_os) { fn dirRenameWindows( userdata: ?*anyopaque, - old_dir: Io.Dir, + old_dir: Dir, old_sub_path: []const u8, - new_dir: Io.Dir, + new_dir: Dir, new_sub_path: []const u8, -) Io.Dir.RenameError!void { +) Dir.RenameError!void { const w = windows; const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -3935,11 +3937,11 @@ fn dirRenameWindows( fn dirRenameWasi( userdata: ?*anyopaque, - old_dir: Io.Dir, + old_dir: Dir, old_sub_path: []const u8, - new_dir: Io.Dir, + new_dir: Dir, new_sub_path: []const u8, -) Io.Dir.RenameError!void { +) Dir.RenameError!void { if (builtin.link_libc) return dirRenamePosix(userdata, old_dir, old_sub_path, new_dir, new_sub_path); const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -3986,11 +3988,11 @@ fn dirRenameWasi( fn dirRenamePosix( userdata: ?*anyopaque, - old_dir: Io.Dir, + old_dir: Dir, old_sub_path: []const u8, - new_dir: Io.Dir, + new_dir: Dir, new_sub_path: []const u8, -) Io.Dir.RenameError!void { +) Dir.RenameError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -4046,11 +4048,11 @@ const dirSymLink = switch (native_os) { fn dirSymLinkWindows( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, target_path: []const u8, sym_link_path: []const u8, - flags: Io.Dir.SymLinkFlags, -) Io.Dir.SymLinkError!void { + flags: Dir.SymLinkFlags, +) Dir.SymLinkError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const w = windows; @@ -4161,11 +4163,11 @@ fn dirSymLinkWindows( fn dirSymLinkWasi( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, target_path: []const u8, sym_link_path: []const u8, - flags: Io.Dir.SymLinkFlags, -) Io.Dir.SymLinkError!void { + flags: Dir.SymLinkFlags, +) Dir.SymLinkError!void { if (builtin.link_libc) return dirSymLinkPosix(dir, target_path, sym_link_path, flags); const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -4209,11 +4211,11 @@ fn dirSymLinkWasi( fn dirSymLinkPosix( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, target_path: []const u8, sym_link_path: []const u8, - flags: Io.Dir.SymLinkFlags, -) Io.Dir.SymLinkError!void { + flags: Dir.SymLinkFlags, +) Dir.SymLinkError!void { _ = flags; const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -4264,7 +4266,7 @@ const dirReadLink = switch (native_os) { else => dirReadLinkPosix, }; -fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize { +fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const w = windows; @@ -4322,7 +4324,7 @@ fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, return std.unicode.wtf16LeToWtf8(buffer, wide_result); } -fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize { +fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { if (builtin.link_libc) return dirReadLinkPosix(userdata, dir, sub_path, buffer); const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -4362,7 +4364,7 @@ fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buf } } -fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize { +fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -4408,7 +4410,7 @@ const dirSetPermissions = switch (native_os) { else => dirSetPermissionsPosix, }; -fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Io.Dir, permissions: Io.Dir.Permissions) Io.Dir.SetPermissionsError!void { +fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { // TODO I think we can actually set permissions on a dir on windows? _ = userdata; _ = dir; @@ -4416,7 +4418,7 @@ fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Io.Dir, permissions: Io. return error.Unexpected; } -fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Io.Dir, permissions: Io.Dir.Permissions) Io.Dir.SetPermissionsError!void { +fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); return setPermissionsPosix(current_thread, dir.handle, permissions.toMode()); @@ -4427,7 +4429,7 @@ const dirSetOwner = switch (native_os) { else => dirSetOwnerPosix, }; -fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void { +fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void { _ = userdata; _ = dir; _ = owner; @@ -4435,7 +4437,7 @@ fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Ui return error.Unexpected; } -fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void { +fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const uid = owner orelse ~@as(posix.uid_t, 0); @@ -4443,7 +4445,7 @@ fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, gro return setOwnerPosix(current_thread, dir.handle, uid, gid); } -fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) Io.File.SetOwnerError!void { +fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void { try current_thread.beginSyscall(); while (true) { switch (posix.errno(posix.system.fchown(fd, uid, gid))) { @@ -4456,7 +4458,7 @@ fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: else => |e| { current_thread.endSyscall(); switch (e) { - .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Io.Dir.OpenOptions.iterate` + .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Dir.OpenOptions.iterate` .FAULT => |err| return errnoBug(err), .INVAL => |err| return errnoBug(err), .ACCES => return error.AccessDenied, @@ -4479,7 +4481,7 @@ const fileSync = switch (native_os) { else => fileSyncPosix, }; -fn fileSyncWindows(userdata: ?*anyopaque, file: Io.File) Io.File.SyncError!void { +fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -4497,7 +4499,7 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: Io.File) Io.File.SyncError!void } } -fn fileSyncPosix(userdata: ?*anyopaque, file: Io.File) Io.File.SyncError!void { +fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); try current_thread.beginSyscall(); @@ -4525,13 +4527,13 @@ fn fileSyncPosix(userdata: ?*anyopaque, file: Io.File) Io.File.SyncError!void { } } -fn fileIsTty(userdata: ?*anyopaque, file: Io.File) Io.Cancelable!bool { +fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); return isTty(current_thread, file); } -fn isTty(current_thread: *Thread, file: Io.File) Io.Cancelable!bool { +fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool { if (is_windows) { if (try isCygwinPty(current_thread, file)) return true; try current_thread.checkCancel(); @@ -4604,7 +4606,7 @@ fn isTty(current_thread: *Thread, file: Io.File) Io.Cancelable!bool { @compileError("unimplemented"); } -fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: Io.File) Io.File.EnableAnsiEscapeCodesError!void { +fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -4639,13 +4641,13 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: Io.File) Io.File.Enabl return error.NotTerminalDevice; } -fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: Io.File) Io.Cancelable!bool { +fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); return supportsAnsiEscapeCodes(current_thread, file); } -fn supportsAnsiEscapeCodes(current_thread: *Thread, file: Io.File) Io.Cancelable!bool { +fn supportsAnsiEscapeCodes(current_thread: *Thread, file: File) Io.Cancelable!bool { if (is_windows) { try current_thread.checkCancel(); var console_mode: windows.DWORD = 0; @@ -4667,7 +4669,7 @@ fn supportsAnsiEscapeCodes(current_thread: *Thread, file: Io.File) Io.Cancelable return false; } -fn isCygwinPty(current_thread: *Thread, file: Io.File) Io.Cancelable!bool { +fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool { if (!is_windows) return false; const handle = file.handle; @@ -4721,7 +4723,7 @@ fn isCygwinPty(current_thread: *Thread, file: Io.File) Io.Cancelable!bool { std.mem.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null; } -fn fileSetLength(userdata: ?*anyopaque, file: Io.File, length: u64) Io.File.SetLengthError!void { +fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -4805,7 +4807,7 @@ fn fileSetLength(userdata: ?*anyopaque, file: Io.File, length: u64) Io.File.SetL } } -fn fileSetOwner(userdata: ?*anyopaque, file: Io.File, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.File.SetOwnerError!void { +fn fileSetOwner(userdata: ?*anyopaque, file: File, owner: ?File.Uid, group: ?File.Gid) File.SetOwnerError!void { switch (native_os) { .windows, .wasi => return error.Unexpected, else => {}, @@ -4817,7 +4819,7 @@ fn fileSetOwner(userdata: ?*anyopaque, file: Io.File, owner: ?Io.File.Uid, group return setOwnerPosix(current_thread, file.handle, uid, gid); } -fn fileSetPermissions(userdata: ?*anyopaque, file: Io.File, permissions: Io.File.Permissions) Io.File.SetPermissionsError!void { +fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permissions) File.SetPermissionsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); switch (native_os) { @@ -4850,7 +4852,7 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: Io.File, permissions: Io.File } } -fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode_t) Io.File.SetPermissionsError!void { +fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode_t) File.SetPermissionsError!void { try current_thread.beginSyscall(); while (true) { switch (posix.errno(posix.system.fchmod(fd, mode))) { @@ -4883,12 +4885,12 @@ fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode fn dirSetTimestamps( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, last_accessed: Io.Timestamp, last_modified: Io.Timestamp, - options: Io.File.SetTimestampsOptions, -) Io.File.SetTimestampsError!void { + options: File.SetTimestampsOptions, +) File.SetTimestampsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -4937,10 +4939,10 @@ fn dirSetTimestamps( fn dirSetTimestampsNow( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.File.SetTimestampsOptions, -) Io.File.SetTimestampsError!void { + options: File.SetTimestampsOptions, +) File.SetTimestampsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -4984,10 +4986,10 @@ fn dirSetTimestampsNow( fn fileSetTimestamps( userdata: ?*anyopaque, - file: Io.File, + file: File, last_accessed: Io.Timestamp, last_modified: Io.Timestamp, -) Io.File.SetTimestampsError!void { +) File.SetTimestampsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5068,7 +5070,7 @@ fn fileSetTimestamps( } } -fn fileSetTimestampsNow(userdata: ?*anyopaque, file: Io.File) Io.File.SetTimestampsError!void { +fn fileSetTimestampsNow(userdata: ?*anyopaque, file: File) File.SetTimestampsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5133,7 +5135,7 @@ fn fileSetTimestampsNow(userdata: ?*anyopaque, file: Io.File) Io.File.SetTimesta const windows_lock_range_off: windows.LARGE_INTEGER = 0; const windows_lock_range_len: windows.LARGE_INTEGER = 1; -fn fileLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File.LockError!void { +fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5195,7 +5197,7 @@ fn fileLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File.Lo } } -fn fileTryLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File.LockError!bool { +fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5263,7 +5265,7 @@ fn fileTryLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File } } -fn fileUnlock(userdata: ?*anyopaque, file: Io.File) void { +fn fileUnlock(userdata: ?*anyopaque, file: File) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5311,7 +5313,7 @@ fn fileUnlock(userdata: ?*anyopaque, file: Io.File) void { } } -fn fileDowngradeLock(userdata: ?*anyopaque, file: Io.File) Io.File.DowngradeLockError!void { +fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5388,10 +5390,10 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: Io.File) Io.File.DowngradeLock fn dirOpenDirWasi( userdata: ?*anyopaque, - dir: Io.Dir, + dir: Dir, sub_path: []const u8, - options: Io.Dir.OpenOptions, -) Io.Dir.OpenError!Io.Dir { + options: Dir.OpenOptions, +) Dir.OpenError!Dir { if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5462,7 +5464,7 @@ fn dirOpenDirWasi( } } -fn fileClose(userdata: ?*anyopaque, file: Io.File) void { +fn fileClose(userdata: ?*anyopaque, file: File) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; posix.close(file.handle); @@ -5473,7 +5475,7 @@ const fileReadStreaming = switch (native_os) { else => fileReadStreamingPosix, }; -fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize { +fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: [][]u8) File.Reader.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5562,7 +5564,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io } } -fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize { +fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: [][]u8) File.Reader.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5591,7 +5593,7 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) } } -fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize { +fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5694,7 +5696,7 @@ const fileReadPositional = switch (native_os) { else => fileReadPositionalPosix, }; -fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize { +fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5736,7 +5738,7 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, } } -fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void { +fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const fd = file.handle; @@ -5834,7 +5836,7 @@ fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekErr } } -fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void { +fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const fd = file.handle; @@ -5930,7 +5932,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr } } -fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelfExeError!Io.File { +fn openSelfExe(userdata: ?*anyopaque, flags: File.OpenFlags) File.OpenSelfExeError!File { const t: *Threaded = @ptrCast(@alignCast(userdata)); switch (native_os) { .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags), @@ -5949,12 +5951,12 @@ fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelf fn fileWritePositional( userdata: ?*anyopaque, - file: Io.File, + file: File, header: []const u8, data: []const []const u8, splat: usize, offset: u64, -) Io.File.WritePositionalError!usize { +) File.WritePositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -6071,11 +6073,11 @@ fn fileWritePositional( fn fileWriteStreaming( userdata: ?*anyopaque, - file: Io.File, + file: File, header: []const u8, data: []const []const u8, splat: usize, -) Io.File.WriteStreamingError!usize { +) File.WriteStreamingError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -6187,11 +6189,11 @@ fn fileWriteStreaming( fn fileWriteFileStreaming( userdata: ?*anyopaque, - file: Io.File, + file: File, header: []const u8, - file_reader: *Io.File.Reader, + file_reader: *File.Reader, limit: Io.Limit, -) Io.File.WriteFileStreamingError!usize { +) File.WriteFileStreamingError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const reader_buffered = file_reader.interface.buffered(); if (reader_buffered.len >= @intFromEnum(limit)) { @@ -6585,7 +6587,7 @@ fn netWriteFile( userdata: ?*anyopaque, socket_handle: net.Socket.Handle, header: []const u8, - file_reader: *Io.File.Reader, + file_reader: *File.Reader, limit: Io.Limit, ) net.Stream.WriteFileError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -6599,12 +6601,12 @@ fn netWriteFile( fn fileWriteFilePositional( userdata: ?*anyopaque, - file: Io.File, + file: File, header: []const u8, - file_reader: *Io.File.Reader, + file_reader: *File.Reader, limit: Io.Limit, offset: u64, -) Io.File.WriteFilePositionalError!usize { +) File.WriteFilePositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const reader_buffered = file_reader.interface.buffered(); if (reader_buffered.len >= @intFromEnum(limit)) { @@ -9488,7 +9490,7 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t { }; } -fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat { +fn statFromLinux(stx: *const std.os.linux.Statx) File.Stat { const atime = stx.atime; const mtime = stx.mtime; const ctime = stx.ctime; @@ -9512,7 +9514,7 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat { }; } -fn statFromPosix(st: *const posix.Stat) Io.File.Stat { +fn statFromPosix(st: *const posix.Stat) File.Stat { const atime = st.atime(); const mtime = st.mtime(); const ctime = st.ctime(); @@ -9546,7 +9548,7 @@ fn statFromPosix(st: *const posix.Stat) Io.File.Stat { }; } -fn statFromWasi(st: *const std.os.wasi.filestat_t) Io.File.Stat { +fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat { return .{ .inode = st.ino, .size = @bitCast(st.size), @@ -9577,7 +9579,7 @@ fn timestampToPosix(nanoseconds: i96) posix.timespec { }; } -fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNameError![:0]u8 { +fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 { if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName; // >= rather than > to make room for the null byte if (file_path.len >= buffer.len) return error.NameTooLong; @@ -9824,7 +9826,7 @@ fn lookupHosts( options: HostName.LookupOptions, ) !void { const t_io = io(t); - const file = Io.File.openAbsolute(t_io, "/etc/hosts", .{}) catch |err| switch (err) { + const file = File.openAbsolute(t_io, "/etc/hosts", .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.AccessDenied, -- 2.54.0 From aafddc2ea13e40a8262d9378aeca2e097a37ac03 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 5 Dec 2025 19:08:37 -0800 Subject: [PATCH 008/195] update all occurrences of close() to close(io) --- build.zig | 4 +- lib/compiler/aro/aro/Compilation.zig | 8 +- lib/compiler/aro/aro/Driver.zig | 12 +- lib/compiler/aro/aro/Driver/Filesystem.zig | 28 ++- lib/compiler/aro/aro/Toolchain.zig | 3 +- lib/compiler/objcopy.zig | 4 +- lib/compiler/resinator/cli.zig | 4 +- lib/compiler/resinator/compile.zig | 34 +-- lib/compiler/resinator/errors.zig | 4 +- lib/compiler/resinator/main.zig | 14 +- lib/compiler/resinator/utils.zig | 9 +- lib/compiler/std-docs.zig | 33 ++- lib/compiler/translate-c/main.zig | 5 +- lib/std/Build/Cache/Directory.zig | 4 +- lib/std/Build/Fuzz.zig | 4 +- lib/std/Build/Step.zig | 3 +- lib/std/Build/Step/InstallArtifact.zig | 3 +- lib/std/Build/Step/InstallDir.zig | 2 +- lib/std/Build/Step/Run.zig | 20 +- lib/std/Build/Step/WriteFile.zig | 10 +- lib/std/Build/Watch/FsEvents.zig | 9 +- lib/std/Build/WebServer.zig | 7 +- lib/std/Io/Dir.zig | 41 ++-- lib/std/Io/Writer.zig | 6 +- lib/std/Io/net/test.zig | 10 +- lib/std/Io/test.zig | 16 +- lib/std/Thread.zig | 7 +- lib/std/crypto/Certificate/Bundle.zig | 6 +- lib/std/crypto/codecs/asn1/test.zig | 2 +- lib/std/debug.zig | 8 +- lib/std/debug/ElfFile.zig | 26 +- lib/std/debug/Info.zig | 21 +- lib/std/debug/MachOFile.zig | 18 +- lib/std/debug/SelfInfo/Elf.zig | 10 +- lib/std/debug/SelfInfo/MachO.zig | 4 +- lib/std/debug/SelfInfo/Windows.zig | 6 +- lib/std/dynamic_library.zig | 34 +-- lib/std/fs.zig | 4 +- lib/std/fs/test.zig | 270 +++++++++++++-------- lib/std/http/Client.zig | 4 +- lib/std/os/linux/IoUring.zig | 85 +++++-- lib/std/os/linux/test.zig | 12 +- lib/std/posix/test.zig | 54 +++-- lib/std/process.zig | 12 +- lib/std/process/Child.zig | 43 ++-- lib/std/tar.zig | 71 +++--- lib/std/tar/test.zig | 8 +- lib/std/testing.zig | 6 +- lib/std/zig/LibCInstallation.zig | 71 +++--- lib/std/zig/WindowsSdk.zig | 67 ++--- lib/std/zig/llvm/Builder.zig | 19 +- lib/std/zig/system.zig | 4 +- lib/std/zig/system/linux.zig | 2 +- lib/std/zip.zig | 6 +- src/Compilation.zig | 64 ++--- src/Package/Fetch.zig | 72 +++--- src/Package/Fetch/git.zig | 26 +- src/Zcu.zig | 10 +- src/Zcu/PerThread.zig | 6 +- src/codegen/llvm.zig | 5 +- src/fmt.zig | 10 +- src/introspect.zig | 28 ++- src/libs/freebsd.zig | 4 +- src/libs/glibc.zig | 4 +- src/libs/mingw.zig | 6 +- src/libs/netbsd.zig | 4 +- src/link.zig | 83 ++++--- src/link/C.zig | 6 +- src/link/Elf.zig | 6 +- src/link/Lld.zig | 2 +- src/link/MachO.zig | 16 +- src/link/MachO/DebugSymbols.zig | 50 ++-- src/link/Wasm.zig | 4 +- src/main.zig | 135 ++++++----- src/print_targets.zig | 3 +- 75 files changed, 1014 insertions(+), 707 deletions(-) diff --git a/build.zig b/build.zig index ade482592728c13caed619d6338593a792d341d2..7ea515de3e8e98221d7f8b8086bd7b66609d55eb 100644 --- a/build.zig +++ b/build.zig @@ -1604,12 +1604,12 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath { b.build_root, @errorName(err), }); }; - defer dir.close(); + defer dir.close(io); var wf = b.addWriteFiles(); var it = dir.iterateAssumeFirstIteration(); - while (it.next() catch @panic("failed to read dir")) |entry| { + while (it.next(io) catch @panic("failed to read dir")) |entry| { if (std.mem.startsWith(u8, entry.name, ".") or entry.kind != .file) continue; diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index d5f4ebe2d94888da857563bc4356c22e4832af81..9fb8123146b669b56a4b10ccaf70caf4c237f9ab 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -1639,8 +1639,10 @@ fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kin return error.FileNotFound; } + const io = comp.io; + const file = try comp.cwd.openFile(path, .{}); - defer file.close(); + defer file.close(io); return comp.addSourceFromFile(file, path, kind); } @@ -1971,8 +1973,10 @@ fn getPathContents(comp: *Compilation, path: []const u8, limit: Io.Limit) ![]u8 return error.FileNotFound; } + const io = comp.io; + const file = try comp.cwd.openFile(path, .{}); - defer file.close(); + defer file.close(io); return comp.getFileContents(file, limit); } diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig index 6a399ece399d9411ed1d6d2a80f52f7a4d7bfcca..888ade2be4dbdba676440333b4824a5ea21ebd99 100644 --- a/lib/compiler/aro/aro/Driver.zig +++ b/lib/compiler/aro/aro/Driver.zig @@ -1286,6 +1286,8 @@ fn processSource( d.comp.generated_buf.items.len = 0; const prev_total = d.diagnostics.errors; + const io = d.comp.io; + var pp = try Preprocessor.initDefault(d.comp); defer pp.deinit(); @@ -1328,7 +1330,7 @@ fn processSource( return d.fatal("unable to create dependency file '{s}': {s}", .{ path, errorDescription(er) }) else std.fs.File.stdout(); - defer if (dep_file_name != null) file.close(); + defer if (dep_file_name != null) file.close(io); var file_writer = file.writer(&writer_buf); dep_file.write(&file_writer.interface) catch @@ -1353,7 +1355,7 @@ fn processSource( return d.fatal("unable to create output file '{s}': {s}", .{ some, errorDescription(er) }) else std.fs.File.stdout(); - defer if (d.output_name != null) file.close(); + defer if (d.output_name != null) file.close(io); var file_writer = file.writer(&writer_buf); pp.prettyPrintTokens(&file_writer.interface, dump_mode) catch @@ -1404,7 +1406,7 @@ fn processSource( if (d.only_preprocess_and_compile) { const out_file = d.comp.cwd.createFile(out_file_name, .{}) catch |er| return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) }); - defer out_file.close(); + defer out_file.close(io); assembly.writeToFile(out_file) catch |er| return d.fatal("unable to write to output file '{s}': {s}", .{ out_file_name, errorDescription(er) }); @@ -1418,7 +1420,7 @@ fn processSource( const assembly_out_file_name = try d.getRandomFilename(&assembly_name_buf, ".s"); const out_file = d.comp.cwd.createFile(assembly_out_file_name, .{}) catch |er| return d.fatal("unable to create output file '{s}': {s}", .{ assembly_out_file_name, errorDescription(er) }); - defer out_file.close(); + defer out_file.close(io); assembly.writeToFile(out_file) catch |er| return d.fatal("unable to write to output file '{s}': {s}", .{ assembly_out_file_name, errorDescription(er) }); try d.invokeAssembler(tc, assembly_out_file_name, out_file_name); @@ -1454,7 +1456,7 @@ fn processSource( const out_file = d.comp.cwd.createFile(out_file_name, .{}) catch |er| return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) }); - defer out_file.close(); + defer out_file.close(io); var file_writer = out_file.writer(&writer_buf); obj.finish(&file_writer.interface) catch diff --git a/lib/compiler/aro/aro/Driver/Filesystem.zig b/lib/compiler/aro/aro/Driver/Filesystem.zig index 87092cb2351374174f7c0387da9e4791e3b4ee39..19ac9bfe41431a508886c2c6fb889293dee8dd5a 100644 --- a/lib/compiler/aro/aro/Driver/Filesystem.zig +++ b/lib/compiler/aro/aro/Driver/Filesystem.zig @@ -1,8 +1,10 @@ -const std = @import("std"); -const mem = std.mem; const builtin = @import("builtin"); const is_windows = builtin.os.tag == .windows; +const std = @import("std"); +const Io = std.Io; +const mem = std.std.mem; + fn readFileFake(entries: []const Filesystem.Entry, path: []const u8, buf: []u8) ?[]const u8 { @branchHint(.cold); for (entries) |entry| { @@ -96,7 +98,7 @@ fn findProgramByNamePosix(name: []const u8, path: ?[]const u8, buf: []u8) ?[]con } pub const Filesystem = union(enum) { - real: std.fs.Dir, + real: std.Io.Dir, fake: []const Entry, const Entry = struct { @@ -121,7 +123,7 @@ pub const Filesystem = union(enum) { base: []const u8, i: usize = 0, - fn next(self: *@This()) !?std.fs.Dir.Entry { + fn next(self: *@This()) !?std.Io.Dir.Entry { while (self.i < self.entries.len) { const entry = self.entries[self.i]; self.i += 1; @@ -130,7 +132,7 @@ pub const Filesystem = union(enum) { const remaining = entry.path[self.base.len + 1 ..]; if (std.mem.indexOfScalar(u8, remaining, std.fs.path.sep) != null) continue; const extension = std.fs.path.extension(remaining); - const kind: std.fs.Dir.Entry.Kind = if (extension.len == 0) .directory else .file; + const kind: std.Io.Dir.Entry.Kind = if (extension.len == 0) .directory else .file; return .{ .name = remaining, .kind = kind }; } } @@ -140,7 +142,7 @@ pub const Filesystem = union(enum) { }; const Dir = union(enum) { - dir: std.fs.Dir, + dir: std.Io.Dir, fake: FakeDir, pub fn iterate(self: Dir) Iterator { @@ -150,19 +152,19 @@ pub const Filesystem = union(enum) { }; } - pub fn close(self: *Dir) void { + pub fn close(self: *Dir, io: Io) void { switch (self.*) { - .dir => |*d| d.close(), + .dir => |*d| d.close(io), .fake => {}, } } }; const Iterator = union(enum) { - iterator: std.fs.Dir.Iterator, + iterator: std.Io.Dir.Iterator, fake: FakeDir.Iterator, - pub fn next(self: *Iterator) std.fs.Dir.Iterator.Error!?std.fs.Dir.Entry { + pub fn next(self: *Iterator) std.Io.Dir.Iterator.Error!?std.Io.Dir.Entry { return switch (self.*) { .iterator => |*it| it.next(), .fake => |*it| it.next(), @@ -208,11 +210,11 @@ pub const Filesystem = union(enum) { /// Read the file at `path` into `buf`. /// Returns null if any errors are encountered /// Otherwise returns a slice of `buf`. If the file is larger than `buf` partial contents are returned - pub fn readFile(fs: Filesystem, path: []const u8, buf: []u8) ?[]const u8 { + pub fn readFile(fs: Filesystem, io: Io, path: []const u8, buf: []u8) ?[]const u8 { return switch (fs) { .real => |cwd| { const file = cwd.openFile(path, .{}) catch return null; - defer file.close(); + defer file.close(io); const bytes_read = file.readAll(buf) catch return null; return buf[0..bytes_read]; @@ -221,7 +223,7 @@ pub const Filesystem = union(enum) { }; } - pub fn openDir(fs: Filesystem, dir_name: []const u8) std.fs.Dir.OpenError!Dir { + pub fn openDir(fs: Filesystem, dir_name: []const u8) std.Io.Dir.OpenError!Dir { return switch (fs) { .real => |cwd| .{ .dir = try cwd.openDir(dir_name, .{ .access_sub_paths = false, .iterate = true }) }, .fake => |entries| .{ .fake = .{ .entries = entries, .path = dir_name } }, diff --git a/lib/compiler/aro/aro/Toolchain.zig b/lib/compiler/aro/aro/Toolchain.zig index 326278cc383293b10b7dbc56a6ae9306206a01fe..ae8436920504d355fbe7017bde34f2465f97d99b 100644 --- a/lib/compiler/aro/aro/Toolchain.zig +++ b/lib/compiler/aro/aro/Toolchain.zig @@ -497,6 +497,7 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void { const comp = d.comp; const gpa = comp.gpa; const arena = comp.arena; + const io = comp.io; try d.includes.ensureUnusedCapacity(gpa, 1); if (d.resource_dir) |resource_dir| { const path = try std.fs.path.join(arena, &.{ resource_dir, "include" }); @@ -509,7 +510,7 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void { var search_path = d.aro_name; while (std.fs.path.dirname(search_path)) |dirname| : (search_path = dirname) { var base_dir = d.comp.cwd.openDir(dirname, .{}) catch continue; - defer base_dir.close(); + defer base_dir.close(io); base_dir.access("include/stddef.h", .{}) catch continue; const path = try std.fs.path.join(arena, &.{ dirname, "include" }); diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 7cf0f14e42bea3d5850eb33da06ec39205ff38ed..1608c121b15383cef17374f58d5f48224b390ba8 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -152,7 +152,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void const io = threaded.io(); const input_file = fs.cwd().openFile(input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err }); - defer input_file.close(); + defer input_file.close(io); const stat = input_file.stat() catch |err| fatal("failed to stat {s}: {t}", .{ input, err }); @@ -180,7 +180,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void const mode = if (out_fmt != .elf or only_keep_debug) fs.File.default_mode else stat.mode; var output_file = try fs.cwd().createFile(output, .{ .mode = mode }); - defer output_file.close(); + defer output_file.close(io); var out = output_file.writer(&output_buffer); diff --git a/lib/compiler/resinator/cli.zig b/lib/compiler/resinator/cli.zig index 59568e9cef47464586185d2e16b1f9b905a9d48e..ffaa62e7ca5ad25ed491aea528baafd6e21b78ff 100644 --- a/lib/compiler/resinator/cli.zig +++ b/lib/compiler/resinator/cli.zig @@ -1991,6 +1991,8 @@ test "parse: input and output formats" { } test "maybeAppendRC" { + const io = std.testing.io; + var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); @@ -2001,7 +2003,7 @@ test "maybeAppendRC" { // Create the file so that it's found. In this scenario, .rc should not get // appended. var file = try tmp.dir.createFile("foo", .{}); - file.close(); + file.close(io); try options.maybeAppendRC(tmp.dir); try std.testing.expectEqualStrings("foo", options.input_source.filename); diff --git a/lib/compiler/resinator/compile.zig b/lib/compiler/resinator/compile.zig index 08e161e505a058f7644c8686063261530a585bfa..7dc77e5ee1b3fc738a5bcc670d2381395872975e 100644 --- a/lib/compiler/resinator/compile.zig +++ b/lib/compiler/resinator/compile.zig @@ -34,7 +34,7 @@ const code_pages = @import("code_pages.zig"); const errors = @import("errors.zig"); pub const CompileOptions = struct { - cwd: std.fs.Dir, + cwd: std.Io.Dir, diagnostics: *Diagnostics, source_mappings: ?*SourceMappings = null, /// List of paths (absolute or relative to `cwd`) for every file that the resources within the .rc file depend on. @@ -107,7 +107,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io // the cwd so we don't need to add it as a distinct search path. if (std.fs.path.dirname(root_path)) |root_dir_path| { var root_dir = try options.cwd.openDir(root_dir_path, .{}); - errdefer root_dir.close(); + errdefer root_dir.close(io); try search_dirs.append(allocator, .{ .dir = root_dir, .path = try allocator.dupe(u8, root_dir_path) }); } } @@ -136,7 +136,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io // TODO: maybe a warning that the search path is skipped? continue; }; - errdefer dir.close(); + errdefer dir.close(io); try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, extra_include_path) }); } for (options.system_include_paths) |system_include_path| { @@ -144,7 +144,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io // TODO: maybe a warning that the search path is skipped? continue; }; - errdefer dir.close(); + errdefer dir.close(io); try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, system_include_path) }); } if (!options.ignore_include_env_var) { @@ -160,7 +160,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io var it = std.mem.tokenizeScalar(u8, INCLUDE, delimiter); while (it.next()) |search_path| { var dir = openSearchPathDir(options.cwd, search_path) catch continue; - errdefer dir.close(); + errdefer dir.close(io); try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, search_path) }); } } @@ -196,7 +196,7 @@ pub const Compiler = struct { arena: Allocator, allocator: Allocator, io: Io, - cwd: std.fs.Dir, + cwd: std.Io.Dir, state: State = .{}, diagnostics: *Diagnostics, dependencies: ?*Dependencies, @@ -388,7 +388,9 @@ pub const Compiler = struct { /// matching file is invalid. That is, it does not do the `cmd` PATH searching /// thing of continuing to look for matching files until it finds a valid /// one if a matching file is invalid. - fn searchForFile(self: *Compiler, path: []const u8) !std.fs.File { + fn searchForFile(self: *Compiler, path: []const u8) !std.Io.File { + const io = self.io; + // If the path is absolute, then it is not resolved relative to any search // paths, so there's no point in checking them. // @@ -405,7 +407,7 @@ pub const Compiler = struct { // an absolute path. if (std.fs.path.isAbsolute(path)) { const file = try utils.openFileNotDir(std.fs.cwd(), path, .{}); - errdefer file.close(); + errdefer file.close(io); if (self.dependencies) |dependencies| { const duped_path = try dependencies.allocator.dupe(u8, path); @@ -414,10 +416,10 @@ pub const Compiler = struct { } } - var first_error: ?(std.fs.File.OpenError || std.fs.File.StatError) = null; + var first_error: ?(std.Io.File.OpenError || std.Io.File.StatError) = null; for (self.search_dirs) |search_dir| { if (utils.openFileNotDir(search_dir.dir, path, .{})) |file| { - errdefer file.close(); + errdefer file.close(io); if (self.dependencies) |dependencies| { const searched_file_path = try std.fs.path.join(dependencies.allocator, &.{ @@ -587,7 +589,7 @@ pub const Compiler = struct { }); }, }; - defer file_handle.close(); + defer file_handle.close(io); var file_buffer: [2048]u8 = undefined; var file_reader = file_handle.reader(io, &file_buffer); @@ -2892,9 +2894,9 @@ pub const Compiler = struct { } }; -pub const OpenSearchPathError = std.fs.Dir.OpenError; +pub const OpenSearchPathError = std.Io.Dir.OpenError; -fn openSearchPathDir(dir: std.fs.Dir, path: []const u8) OpenSearchPathError!std.fs.Dir { +fn openSearchPathDir(dir: std.Io.Dir, path: []const u8) OpenSearchPathError!std.Io.Dir { // Validate the search path to avoid possible unreachable on invalid paths, // see https://github.com/ziglang/zig/issues/15607 for why this is currently necessary. try validateSearchPath(path); @@ -2927,11 +2929,11 @@ fn validateSearchPath(path: []const u8) error{BadPathName}!void { } pub const SearchDir = struct { - dir: std.fs.Dir, + dir: std.Io.Dir, path: ?[]const u8, - pub fn deinit(self: *SearchDir, allocator: Allocator) void { - self.dir.close(); + pub fn deinit(self: *SearchDir, allocator: Allocator, io: Io) void { + self.dir.close(io); if (self.path) |path| { allocator.free(path); } diff --git a/lib/compiler/resinator/errors.zig b/lib/compiler/resinator/errors.zig index 0060990ab62790b8d7823bbca338c74cff86d40e..8509aa610fb1fdf00e9d3f05664fbd4742e6bc24 100644 --- a/lib/compiler/resinator/errors.zig +++ b/lib/compiler/resinator/errors.zig @@ -1221,8 +1221,8 @@ const CorrespondingLines = struct { }; } - pub fn deinit(self: *CorrespondingLines) void { - self.file.close(); + pub fn deinit(self: *CorrespondingLines, io: Io) void { + self.file.close(io); } }; diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 6d6819f45ae3fdf02e4533349fb24953addf7431..42308a8987f287474507605ed2977d231367609b 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -296,7 +296,7 @@ pub fn main() !void { error.ParseError, error.CompileError => { try error_handler.emitDiagnostics(gpa, std.fs.cwd(), final_input, &diagnostics, mapping_results.mappings); // Delete the output file on error - res_stream.cleanupAfterError(); + res_stream.cleanupAfterError(io); std.process.exit(1); }, else => |e| return e, @@ -315,7 +315,7 @@ pub fn main() !void { try error_handler.emitMessage(gpa, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) }); std.process.exit(1); }; - defer depfile.close(); + defer depfile.close(io); var depfile_buffer: [1024]u8 = undefined; var depfile_writer = depfile.writer(&depfile_buffer); @@ -402,7 +402,7 @@ pub fn main() !void { }, } // Delete the output file on error - coff_stream.cleanupAfterError(); + coff_stream.cleanupAfterError(io); std.process.exit(1); }; @@ -434,11 +434,11 @@ const IoStream = struct { self.source.deinit(allocator); } - pub fn cleanupAfterError(self: *IoStream) void { + pub fn cleanupAfterError(self: *IoStream, io: Io) void { switch (self.source) { .file => |file| { // Delete the output file on error - file.close(); + file.close(io); // Failing to delete is not really a big deal, so swallow any errors std.fs.cwd().deleteFile(self.name) catch {}; }, @@ -465,9 +465,9 @@ const IoStream = struct { } } - pub fn deinit(self: *Source, allocator: Allocator) void { + pub fn deinit(self: *Source, allocator: Allocator, io: Io) void { switch (self.*) { - .file => |file| file.close(), + .file => |file| file.close(io), .stdio => {}, .memory => |*list| list.deinit(allocator), .closed => {}, diff --git a/lib/compiler/resinator/utils.zig b/lib/compiler/resinator/utils.zig index 021b8cf4de1f1ba70976c86c7da030d9e53c572e..f8080539cb31ddb56579a25281dc04d8c14c00bb 100644 --- a/lib/compiler/resinator/utils.zig +++ b/lib/compiler/resinator/utils.zig @@ -1,6 +1,8 @@ -const std = @import("std"); const builtin = @import("builtin"); +const std = @import("std"); +const Io = std.Io; + pub const UncheckedSliceWriter = struct { const Self = @This(); @@ -28,11 +30,12 @@ pub const UncheckedSliceWriter = struct { /// TODO: Remove once https://github.com/ziglang/zig/issues/5732 is addressed. pub fn openFileNotDir( cwd: std.fs.Dir, + io: Io, path: []const u8, flags: std.fs.File.OpenFlags, ) (std.fs.File.OpenError || std.fs.File.StatError)!std.fs.File { - const file = try cwd.openFile(path, flags); - errdefer file.close(); + const file = try cwd.openFile(io, path, flags); + errdefer file.close(io); // https://github.com/ziglang/zig/issues/5732 if (builtin.os.tag != .windows) { const stat = try file.stat(); diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index 12825146a2b688c528beca75d0da33822d5a1c67..15c17ee59b8b3040e6af07c962745a7dc5b018e6 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -1,12 +1,14 @@ const builtin = @import("builtin"); + const std = @import("std"); +const Io = std.Io; const mem = std.mem; const Allocator = std.mem.Allocator; const assert = std.debug.assert; const Cache = std.Build.Cache; fn usage() noreturn { - std.fs.File.stdout().writeAll( + std.Io.File.stdout().writeAll( \\Usage: zig std [options] \\ \\Options: @@ -27,6 +29,10 @@ pub fn main() !void { var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; const gpa = general_purpose_allocator.allocator(); + var threaded: std.Io.Threaded = .init(gpa); + defer threaded.deinit(); + const io = threaded.io(); + var argv = try std.process.argsWithAllocator(arena); defer argv.deinit(); assert(argv.skip()); @@ -35,7 +41,7 @@ pub fn main() !void { const global_cache_path = argv.next().?; var lib_dir = try std.fs.cwd().openDir(zig_lib_directory, .{}); - defer lib_dir.close(); + defer lib_dir.close(io); var listen_port: u16 = 0; var force_open_browser: ?bool = null; @@ -64,7 +70,7 @@ pub fn main() !void { }); const port = http_server.listen_address.in.getPort(); const url_with_newline = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port}); - std.fs.File.stdout().writeAll(url_with_newline) catch {}; + std.Io.File.stdout().writeAll(url_with_newline) catch {}; if (should_open_browser) { openBrowserTab(gpa, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| { std.log.err("unable to open browser: {s}", .{@errorName(err)}); @@ -73,6 +79,7 @@ pub fn main() !void { var context: Context = .{ .gpa = gpa, + .io = io, .zig_exe_path = zig_exe_path, .global_cache_path = global_cache_path, .lib_dir = lib_dir, @@ -83,14 +90,15 @@ pub fn main() !void { const connection = try http_server.accept(); _ = std.Thread.spawn(.{}, accept, .{ &context, connection }) catch |err| { std.log.err("unable to accept connection: {s}", .{@errorName(err)}); - connection.stream.close(); + connection.stream.close(io); continue; }; } } fn accept(context: *Context, connection: std.net.Server.Connection) void { - defer connection.stream.close(); + const io = context.io; + defer connection.stream.close(io); var recv_buffer: [4000]u8 = undefined; var send_buffer: [4000]u8 = undefined; @@ -124,6 +132,7 @@ fn accept(context: *Context, connection: std.net.Server.Connection) void { const Context = struct { gpa: Allocator, + io: Io, lib_dir: std.fs.Dir, zig_lib_directory: []const u8, zig_exe_path: []const u8, @@ -185,6 +194,7 @@ fn serveDocsFile( fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { const gpa = context.gpa; + const io = context.io; var send_buffer: [0x4000]u8 = undefined; var response = try request.respondStreaming(&send_buffer, .{ @@ -197,7 +207,7 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { }); var std_dir = try context.lib_dir.openDir("std", .{ .iterate = true }); - defer std_dir.close(); + defer std_dir.close(io); var walker = try std_dir.walk(gpa); defer walker.deinit(); @@ -216,11 +226,11 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { else => continue, } var file = try entry.dir.openFile(entry.basename, .{}); - defer file.close(); + defer file.close(io); const stat = try file.stat(); - var file_reader: std.fs.File.Reader = .{ + var file_reader: std.Io.File.Reader = .{ .file = file, - .interface = std.fs.File.Reader.initInterface(&.{}), + .interface = std.Io.File.Reader.initInterface(&.{}), .size = stat.size, }; try archiver.writeFile(entry.path, &file_reader, stat.mtime); @@ -283,6 +293,7 @@ fn buildWasmBinary( optimize_mode: std.builtin.OptimizeMode, ) !Cache.Path { const gpa = context.gpa; + const io = context.io; var argv: std.ArrayList([]const u8) = .empty; @@ -371,7 +382,7 @@ fn buildWasmBinary( } // Send EOF to stdin. - child.stdin.?.close(); + child.stdin.?.close(io); child.stdin = null; switch (try child.wait()) { @@ -410,7 +421,7 @@ fn buildWasmBinary( }; } -fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { +fn sendMessage(file: std.Io.File, tag: std.zig.Client.Message.Tag) !void { const header: std.zig.Client.Message.Header = .{ .tag = tag, .bytes_len = 0, diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index b0d7a5d9bd51bc97c20555f9c90aaf4d1cb34104..0c72298b30d8c95675fda610d1871594fbcc5af1 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -121,6 +121,7 @@ pub const usage = fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration: bool) !void { const gpa = d.comp.gpa; + const io = d.comp.io; const aro_args = args: { var i: usize = 0; @@ -228,7 +229,7 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration return d.fatal("unable to create dependency file '{s}': {s}", .{ path, aro.Driver.errorDescription(er) }) else std.fs.File.stdout(); - defer if (dep_file_name != null) file.close(); + defer if (dep_file_name != null) file.close(io); var file_writer = file.writer(&out_buf); dep_file.write(&file_writer.interface) catch @@ -246,7 +247,7 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration var close_out_file = false; var out_file_path: []const u8 = ""; var out_file: std.fs.File = .stdout(); - defer if (close_out_file) out_file.close(); + defer if (close_out_file) out_file.close(io); if (d.output_name) |path| blk: { if (std.mem.eql(u8, path, "-")) break :blk; diff --git a/lib/std/Build/Cache/Directory.zig b/lib/std/Build/Cache/Directory.zig index 305ef253614d49280b21243c7c582497d7d99875..ce5f5b02bbc5be0163575a2d5f3a71cf78f4d2a1 100644 --- a/lib/std/Build/Cache/Directory.zig +++ b/lib/std/Build/Cache/Directory.zig @@ -52,8 +52,8 @@ pub fn joinZ(self: Directory, allocator: Allocator, paths: []const []const u8) ! /// Whether or not the handle should be closed, or the path should be freed /// is determined by usage, however this function is provided for convenience /// if it happens to be what the caller needs. -pub fn closeAndFree(self: *Directory, gpa: Allocator) void { - self.handle.close(); +pub fn closeAndFree(self: *Directory, gpa: Allocator, io: Io) void { + self.handle.close(io); if (self.path) |p| gpa.free(p); self.* = undefined; } diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index 2897b29969f1e851bf3a9fb91c956442dd7d4b10..db83f393fd4c8a04794e45b39cd2b984c9c7e2da 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -411,7 +411,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO }); return error.AlreadyReported; }; - defer coverage_file.close(); + defer coverage_file.close(io); const file_size = coverage_file.getEndPos() catch |err| { log.err("unable to check len of coverage file '{f}': {t}", .{ coverage_file_path, err }); @@ -533,7 +533,7 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void { cov.run.step.name, coverage_file_path, err, }); }; - defer coverage_file.close(); + defer coverage_file.close(io); const fuzz_abi = std.Build.abi.fuzz; var rbuf: [0x1000]u8 = undefined; diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 33fe755c2b81b494384a1610116eadbbf4d3d2c3..acde47071ddb46551e272c04857362148d885b6e 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -441,6 +441,7 @@ pub fn evalZigProcess( assert(argv.len != 0); const b = s.owner; const arena = b.allocator; + const io = b.graph.io; try handleChildProcUnsupported(s); try handleVerbose(s.owner, null, argv); @@ -474,7 +475,7 @@ pub fn evalZigProcess( if (!watch) { // Send EOF to stdin. - zp.child.stdin.?.close(); + zp.child.stdin.?.close(io); zp.child.stdin = null; const term = zp.child.wait() catch |err| { diff --git a/lib/std/Build/Step/InstallArtifact.zig b/lib/std/Build/Step/InstallArtifact.zig index c203ae924b96f9ba64c756ff2e131bf61ec77afe..1cdb2327706ad3e8c93209e74aa36f2306cae88d 100644 --- a/lib/std/Build/Step/InstallArtifact.zig +++ b/lib/std/Build/Step/InstallArtifact.zig @@ -119,6 +119,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { _ = options; const install_artifact: *InstallArtifact = @fieldParentPtr("step", step); const b = step.owner; + const io = b.graph.io; var all_cached = true; @@ -168,7 +169,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { src_dir_path, @errorName(err), }); }; - defer src_dir.close(); + defer src_dir.close(io); var it = try src_dir.walk(b.allocator); next_entry: while (try it.next()) |entry| { diff --git a/lib/std/Build/Step/InstallDir.zig b/lib/std/Build/Step/InstallDir.zig index ecb0959cc7d883801fae4ee2633a660b919d1875..788d5565a7437d32a809a8500762b5ef01e3ab20 100644 --- a/lib/std/Build/Step/InstallDir.zig +++ b/lib/std/Build/Step/InstallDir.zig @@ -68,7 +68,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { return step.fail("unable to open source directory '{f}': {t}", .{ src_dir_path, err }); }; - defer src_dir.close(); + defer src_dir.close(io); var it = try src_dir.walk(arena); var all_cached = true; next_entry: while (try it.next()) |entry| { diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 28c09e1faf73d9759b9e2870f0e016fe0ffcd745..e66e30cc790ad5b2bf7083504981f6f50ea7bd9e 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -851,7 +851,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .{ file_path, err }, ); }; - defer file.close(); + defer file.close(io); var buf: [1024]u8 = undefined; var file_reader = file.reader(io, &buf); @@ -1111,7 +1111,7 @@ pub fn rerunInFuzzMode( result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory; const file = try file_path.root_dir.handle.openFile(file_path.subPathOrDot(), .{}); - defer file.close(); + defer file.close(io); var buf: [1024]u8 = undefined; var file_reader = file.reader(io, &buf); @@ -1671,8 +1671,10 @@ fn evalZigTest( options: Step.MakeOptions, fuzz_context: ?FuzzContext, ) !EvalZigTestResult { - const gpa = run.step.owner.allocator; - const arena = run.step.owner.allocator; + const step_owner = run.step.owner; + const gpa = step_owner.allocator; + const arena = step_owner.allocator; + const io = step_owner.graph.io; // We will update this every time a child runs. run.step.result_peak_rss = 0; @@ -1724,7 +1726,7 @@ fn evalZigTest( run.step.result_stderr = try arena.dupe(u8, poller.reader(.stderr).buffered()); // Clean up everything and wait for the child to exit. - child.stdin.?.close(); + child.stdin.?.close(io); child.stdin = null; poller.deinit(); child_killed = true; @@ -1744,7 +1746,7 @@ fn evalZigTest( poller.reader(.stderr).tossBuffered(); // Clean up everything and wait for the child to exit. - child.stdin.?.close(); + child.stdin.?.close(io); child.stdin = null; poller.deinit(); child_killed = true; @@ -2177,7 +2179,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { child.stdin.?.writeAll(bytes) catch |err| { return run.step.fail("unable to write stdin: {s}", .{@errorName(err)}); }; - child.stdin.?.close(); + child.stdin.?.close(io); child.stdin = null; }, .lazy_path => |lazy_path| { @@ -2185,7 +2187,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { const file = path.root_dir.handle.openFile(path.subPathOrDot(), .{}) catch |err| { return run.step.fail("unable to open stdin file: {s}", .{@errorName(err)}); }; - defer file.close(); + defer file.close(io); // TODO https://github.com/ziglang/zig/issues/23955 var read_buffer: [1024]u8 = undefined; var file_reader = file.reader(io, &read_buffer); @@ -2204,7 +2206,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { stdin_writer.err.?, }), }; - child.stdin.?.close(); + child.stdin.?.close(io); child.stdin = null; }, .none => {}, diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index 030c7c6811be9f54b93f9a485dd55bbe3db4e7a9..201b132271866564b47a5a585c703b899f5736f9 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -206,7 +206,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { } } - const open_dir_cache = try arena.alloc(fs.Dir, write_file.directories.items.len); + const open_dir_cache = try arena.alloc(Io.Dir, write_file.directories.items.len); var open_dirs_count: usize = 0; defer closeDirs(open_dir_cache[0..open_dirs_count]); @@ -264,7 +264,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { b.cache_root, cache_path, @errorName(err), }); }; - defer cache_dir.close(); + defer cache_dir.close(io); for (write_file.files.items) |file| { if (fs.path.dirname(file.sub_path)) |dirname| { @@ -342,6 +342,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void { try step.writeManifest(&man); } -fn closeDirs(dirs: []fs.Dir) void { - for (dirs) |*d| d.close(); +fn closeDirs(io: Io, dirs: []Io.Dir) void { + var group: Io.Group = .init; + defer group.wait(); + for (dirs) |d| group.async(Io.Dir.close, .{ d, io }); } diff --git a/lib/std/Build/Watch/FsEvents.zig b/lib/std/Build/Watch/FsEvents.zig index 6131663993b025cca4d066a2ce7d640ca6e1c6fb..59238c8725f8f1de161db4f9bd6bc976ba0f972b 100644 --- a/lib/std/Build/Watch/FsEvents.zig +++ b/lib/std/Build/Watch/FsEvents.zig @@ -78,10 +78,10 @@ const ResolvedSymbols = struct { kCFAllocatorUseContext: *const CFAllocatorRef, }; -pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents { +pub fn init(io: Io) error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents { var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch return error.OpenFrameworkFailed; - errdefer core_services.close(); + errdefer core_services.close(io); var resolved_symbols: ResolvedSymbols = undefined; inline for (@typeInfo(ResolvedSymbols).@"struct".fields) |f| { @@ -102,10 +102,10 @@ pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents { }; } -pub fn deinit(fse: *FsEvents, gpa: Allocator) void { +pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void { dispatch_release(fse.waiting_semaphore); dispatch_release(fse.dispatch_queue); - fse.core_services.close(); + fse.core_services.close(io); gpa.free(fse.watch_roots); fse.watch_paths.deinit(gpa); @@ -487,6 +487,7 @@ const FSEventStreamEventFlags = packed struct(u32) { }; const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const Allocator = std.mem.Allocator; const watch_log = std.log.scoped(.watch); diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 2c865a888976c7d441c013668d7af43dde27fbc1..f91075b44412a7663c8bbaae3b82bb45d32e064b 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -129,6 +129,7 @@ pub fn init(opts: Options) WebServer { } pub fn deinit(ws: *WebServer) void { const gpa = ws.gpa; + const io = ws.graph.io; gpa.free(ws.step_names_trailing); gpa.free(ws.step_status_bits); @@ -139,7 +140,7 @@ pub fn deinit(ws: *WebServer) void { gpa.free(ws.time_report_update_times); if (ws.serve_thread) |t| { - if (ws.tcp_server) |*s| s.stream.close(); + if (ws.tcp_server) |*s| s.stream.close(io); t.join(); } if (ws.tcp_server) |*s| s.deinit(); @@ -507,7 +508,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons log.err("failed to open '{f}': {s}", .{ path, @errorName(err) }); continue; }; - defer file.close(); + defer file.close(io); const stat = try file.stat(); var read_buffer: [1024]u8 = undefined; var file_reader: Io.File.Reader = .initSize(file.adaptToNewApi(), io, &read_buffer, stat.size); @@ -634,7 +635,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim } // Send EOF to stdin. - child.stdin.?.close(); + child.stdin.?.close(io); child.stdin = null; switch (try child.wait()) { diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 348950e20a3071d5e640b08683a7a20c86246087..5a74c8ca722d6db8a605c76bcb8fbbb745f26539 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -131,7 +131,7 @@ pub const SelectiveWalker = struct { /// After each call to this function, and on deinit(), the memory returned /// from this function becomes invalid. A copy must be made in order to keep /// a reference to the path. - pub fn next(self: *SelectiveWalker) Error!?Walker.Entry { + pub fn next(self: *SelectiveWalker, io: Io) Error!?Walker.Entry { while (self.stack.items.len > 0) { const top = &self.stack.items[self.stack.items.len - 1]; var dirname_len = top.dirname_len; @@ -142,7 +142,7 @@ pub const SelectiveWalker = struct { // likely just fail with the same error. var item = self.stack.pop().?; if (self.stack.items.len != 0) { - item.iter.dir.close(); + item.iter.dir.close(io); } return err; }) |entry| { @@ -164,7 +164,7 @@ pub const SelectiveWalker = struct { } else { var item = self.stack.pop().?; if (self.stack.items.len != 0) { - item.iter.dir.close(); + item.iter.dir.close(io); } } } @@ -172,7 +172,7 @@ pub const SelectiveWalker = struct { } /// Traverses into the directory, continuing walking one level down. - pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void { + pub fn enter(self: *SelectiveWalker, io: Io, entry: Walker.Entry) !void { if (entry.kind != .directory) { @branchHint(.cold); return; @@ -184,7 +184,7 @@ pub const SelectiveWalker = struct { else => |e| return e, } }; - errdefer new_dir.close(); + errdefer new_dir.close(io); try self.stack.append(self.allocator, .{ .iter = new_dir.iterateAssumeFirstIteration(), @@ -200,11 +200,11 @@ pub const SelectiveWalker = struct { /// Leaves the current directory, continuing walking one level up. /// If the current entry is a directory entry, then the "current directory" /// will pertain to that entry if `enter` is called before `leave`. - pub fn leave(self: *SelectiveWalker) void { + pub fn leave(self: *SelectiveWalker, io: Io) void { var item = self.stack.pop().?; if (self.stack.items.len != 0) { @branchHint(.likely); - item.iter.dir.close(); + item.iter.dir.close(io); } } }; @@ -558,7 +558,8 @@ pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) pub const MakePathError = MakeError || StatPathError; -/// Creates parent directories as necessary to ensure `sub_path` exists as a directory. +/// Creates parent directories with default permissions as necessary to ensure +/// `sub_path` exists as a directory. /// /// Returns success if the path already exists and is a directory. /// @@ -579,8 +580,11 @@ pub const MakePathError = MakeError || StatPathError; /// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`, /// meaning a `sub_path` like "first/../second" will create both a `./first` /// and a `./second` directory. -pub fn makePath(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakePathError!void { - _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, permissions); +/// +/// See also: +/// * `makePathStatus` +pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void { + _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, .default_dir); } pub const MakePathStatus = enum { existed, created }; @@ -593,6 +597,11 @@ pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permi pub const MakeOpenPathError = MakeError || OpenError || StatPathError; +pub const MakeOpenPathOptions = struct { + open_options: OpenOptions = .{}, + permissions: Permissions = .default_dir, +}; + /// Performs the equivalent of `makePath` followed by `openDir`, atomically if possible. /// /// When this operation is canceled, it may leave the file system in a @@ -601,8 +610,8 @@ pub const MakeOpenPathError = MakeError || OpenError || StatPathError; /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions, options: OpenOptions) MakeOpenPathError!Dir { - return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, permissions, options); +pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: MakeOpenPathOptions) MakeOpenPathError!Dir { + return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options.permissions, options.open_options); } pub const Stat = File.Stat; @@ -1266,10 +1275,10 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, start_over: while (true) { var dir = (try parent.deleteTreeOpenInitialSubpath(io, sub_path, kind_hint)) orelse return; var cleanup_dir_parent: ?Dir = null; - defer if (cleanup_dir_parent) |*d| d.close(); + defer if (cleanup_dir_parent) |*d| d.close(io); var cleanup_dir = true; - defer if (cleanup_dir) dir.close(); + defer if (cleanup_dir) dir.close(io); // Valid use of max_path_bytes because dir_name_buf will only // ever store a single path component that was returned from the @@ -1315,7 +1324,7 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, error.Canceled, => |e| return e, }; - if (cleanup_dir_parent) |*d| d.close(); + if (cleanup_dir_parent) |*d| d.close(io); cleanup_dir_parent = dir; dir = new_dir; const result = dir_name_buf[0..entry.name.len]; @@ -1354,7 +1363,7 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, } // Reached the end of the directory entries, which means we successfully deleted all of them. // Now to remove the directory itself. - dir.close(); + dir.close(io); cleanup_dir = false; if (cleanup_dir_parent) |d| { diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index f49ef8eb67de5fa3c7f01e1c530e9ae6dd92e7b2..5601293cfb3a0498aed9d3ff1dd7dea263537829 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -2835,7 +2835,7 @@ test "discarding sendFile" { defer tmp_dir.cleanup(); const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); - defer file.close(); + defer file.close(io); var r_buffer: [256]u8 = undefined; var file_writer: std.fs.File.Writer = .init(file, &r_buffer); try file_writer.interface.writeByte('h'); @@ -2857,7 +2857,7 @@ test "allocating sendFile" { defer tmp_dir.cleanup(); const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); - defer file.close(); + defer file.close(io); var r_buffer: [2]u8 = undefined; var file_writer: std.fs.File.Writer = .init(file, &r_buffer); try file_writer.interface.writeAll("abcd"); @@ -2881,7 +2881,7 @@ test sendFileReading { defer tmp_dir.cleanup(); const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); - defer file.close(); + defer file.close(io); var r_buffer: [2]u8 = undefined; var file_writer: std.fs.File.Writer = .init(file, &r_buffer); try file_writer.interface.writeAll("abcd"); diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig index e234a9eddef4078adccab15d9554ba8bd31c2e58..5818f6c3f77f25ea6f84e6b65d1926b7ad46b839 100644 --- a/lib/std/Io/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -232,8 +232,10 @@ test "listen on an in use port" { fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void { if (builtin.os.tag == .wasi) return error.SkipZigTest; + const io = testing.io; + const connection = try net.tcpConnectToHost(allocator, name, port); - defer connection.close(); + defer connection.close(io); var buf: [100]u8 = undefined; const len = try connection.read(&buf); @@ -244,8 +246,10 @@ fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyer fn testClient(addr: net.IpAddress) anyerror!void { if (builtin.os.tag == .wasi) return error.SkipZigTest; + const io = testing.io; + const socket_file = try net.tcpConnectToAddress(addr); - defer socket_file.close(); + defer socket_file.close(io); var buf: [100]u8 = undefined; const len = try socket_file.read(&buf); @@ -330,7 +334,7 @@ test "non-blocking tcp server" { try testing.expectError(error.WouldBlock, accept_err); const socket_file = try net.tcpConnectToAddress(server.socket.address); - defer socket_file.close(); + defer socket_file.close(io); var stream = try server.accept(io); defer stream.close(io); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index f7965ed14efb7d1e4ec950430f43455e1130de9e..9ea2d48ee5a894bb96b6523f32b052cf7fc24b88 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -28,7 +28,7 @@ test "write a file, read it, then delete it" { const tmp_file_name = "temp_test_file.txt"; { var file = try tmp.dir.createFile(tmp_file_name, .{}); - defer file.close(); + defer file.close(io); var file_writer = file.writer(&.{}); const st = &file_writer.interface; @@ -45,7 +45,7 @@ test "write a file, read it, then delete it" { { var file = try tmp.dir.openFile(tmp_file_name, .{}); - defer file.close(); + defer file.close(io); const file_size = try file.getEndPos(); const expected_file_size: u64 = "begin".len + data.len + "end".len; @@ -67,9 +67,11 @@ test "File seek ops" { var tmp = tmpDir(.{}); defer tmp.cleanup(); + const io = testing.io; + const tmp_file_name = "temp_test_file.txt"; var file = try tmp.dir.createFile(tmp_file_name, .{}); - defer file.close(); + defer file.close(io); try file.writeAll(&([_]u8{0x55} ** 8192)); @@ -88,12 +90,14 @@ test "File seek ops" { } test "setEndPos" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); const tmp_file_name = "temp_test_file.txt"; var file = try tmp.dir.createFile(tmp_file_name, .{}); - defer file.close(); + defer file.close(io); // Verify that the file size changes and the file offset is not moved try expect((try file.getEndPos()) == 0); @@ -111,12 +115,14 @@ test "setEndPos" { } test "updateTimes" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); const tmp_file_name = "just_a_temporary_file.txt"; var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true }); - defer file.close(); + defer file.close(io); const stat_old = try file.stat(); // Set atime and mtime to 5s before diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 35b268b3495e7c9907efa1cd2ebfb22178ef333c..9f532c3bec981a169eea9b85af52071f5be0edfc 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -7,6 +7,7 @@ const target = builtin.target; const native_os = builtin.os.tag; const std = @import("std.zig"); +const Io = std.Io; const math = std.math; const assert = std.debug.assert; const posix = std.posix; @@ -176,7 +177,7 @@ pub const SetNameError = error{ InvalidWtf8, } || posix.PrctlError || posix.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError; -pub fn setName(self: Thread, name: []const u8) SetNameError!void { +pub fn setName(self: Thread, io: Io, name: []const u8) SetNameError!void { if (name.len > max_name_len) return error.NameTooLong; const name_with_terminator = blk: { @@ -208,7 +209,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); const file = try std.fs.cwd().openFile(path, .{ .mode = .write_only }); - defer file.close(); + defer file.close(io); try file.writeAll(name); return; @@ -325,7 +326,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co const io = threaded.ioBasic(); const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); + defer file.close(io); var file_reader = file.readerStreaming(io, &.{}); const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) { diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index 53fb638250a8c35852aab5457ff7160b93d202e2..9541e01db56fcd51ef3f605ae8db83f10c0c0385 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -181,7 +181,7 @@ pub fn addCertsFromDirPath( sub_dir_path: []const u8, ) AddCertsFromDirPathError!void { var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true }); - defer iterable_dir.close(); + defer iterable_dir.close(io); return addCertsFromDir(cb, gpa, io, iterable_dir); } @@ -194,7 +194,7 @@ pub fn addCertsFromDirPathAbsolute( ) AddCertsFromDirPathError!void { assert(fs.path.isAbsolute(abs_dir_path)); var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true }); - defer iterable_dir.close(); + defer iterable_dir.close(io); return addCertsFromDir(cb, gpa, io, now, iterable_dir); } @@ -222,7 +222,7 @@ pub fn addCertsFromFilePathAbsolute( abs_file_path: []const u8, ) AddCertsFromFilePathError!void { var file = try fs.openFileAbsolute(abs_file_path, .{}); - defer file.close(); + defer file.close(io); var file_reader = file.reader(io, &.{}); return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds()); } diff --git a/lib/std/crypto/codecs/asn1/test.zig b/lib/std/crypto/codecs/asn1/test.zig index fe12cba81985254bcb08ad53216f201271e3293e..ff854fcbde69bac83f7af998bff6c9fd4eefc73c 100644 --- a/lib/std/crypto/codecs/asn1/test.zig +++ b/lib/std/crypto/codecs/asn1/test.zig @@ -75,6 +75,6 @@ test AllTypes { // Use this to update test file. // const dir = try std.fs.cwd().openDir("lib/std/crypto/asn1", .{}); // var file = try dir.createFile(path, .{}); - // defer file.close(); + // defer file.close(io); // try file.writeAll(buf); } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 7c5993dfcf00c943532bb8cf09b790283399b793..0cb96ed593bc7df89716e52543053f7aa1b63479 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1298,7 +1298,7 @@ test printLineFromFile { } { const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{}); - defer file.close(); + defer file.close(io); const path = try fs.path.join(gpa, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); defer gpa.free(path); @@ -1317,7 +1317,7 @@ test printLineFromFile { } { const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{}); - defer file.close(); + defer file.close(io); const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); defer gpa.free(path); @@ -1331,7 +1331,7 @@ test printLineFromFile { } { const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{}); - defer file.close(); + defer file.close(io); const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); defer gpa.free(path); @@ -1357,7 +1357,7 @@ test printLineFromFile { } { const file = try test_dir.dir.createFile("file_of_newlines.zig", .{}); - defer file.close(); + defer file.close(io); const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" }); defer gpa.free(path); diff --git a/lib/std/debug/ElfFile.zig b/lib/std/debug/ElfFile.zig index e81943ab498007f0e1d1f4017b83a29e8188b9c2..92bcca1bcf3a977d05899c0a6cb7a5b15648fde2 100644 --- a/lib/std/debug/ElfFile.zig +++ b/lib/std/debug/ElfFile.zig @@ -1,5 +1,13 @@ //! A helper type for loading an ELF file and collecting its DWARF debug information, unwind //! information, and symbol table. +const ElfFile = @This(); + +const std = @import("std"); +const Io = std.Io; +const Endian = std.builtin.Endian; +const Dwarf = std.debug.Dwarf; +const Allocator = std.mem.Allocator; +const elf = std.elf; is_64: bool, endian: Endian, @@ -358,10 +366,17 @@ const Section = struct { const Array = std.enums.EnumArray(Section.Id, ?Section); }; -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 { +fn loadSeparateDebugFile( + arena: Allocator, + io: Io, + main_loaded: *LoadInnerResult, + opt_crc: ?u32, + comptime fmt: []const u8, + args: anytype, +) Allocator.Error!?[]align(std.heap.page_size_min) const u8 { const path = try std.fmt.allocPrint(arena, fmt, args); const elf_file = std.fs.cwd().openFile(path, .{}) catch return null; - defer elf_file.close(); + defer elf_file.close(io); const result = loadInner(arena, elf_file, opt_crc) catch |err| switch (err) { error.OutOfMemory => |e| return e, @@ -529,10 +544,3 @@ fn loadInner( .mapped_mem = mapped_mem, }; } - -const std = @import("std"); -const Endian = std.builtin.Endian; -const Dwarf = std.debug.Dwarf; -const ElfFile = @This(); -const Allocator = std.mem.Allocator; -const elf = std.elf; diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index 921cd36ab8116261f23ef11cbb9718b3f63fd734..9268ca0247eddcbfd62252e9704c50eda9e1c91e 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -5,19 +5,18 @@ //! Unlike `std.debug.SelfInfo`, this API does not assume the debug information //! in question happens to match the host CPU architecture, OS, or other target //! properties. +const Info = @This(); const std = @import("../std.zig"); +const Io = std.Io; const Allocator = std.mem.Allocator; const Path = std.Build.Cache.Path; const assert = std.debug.assert; const Coverage = std.debug.Coverage; const SourceLocation = std.debug.Coverage.SourceLocation; - const ElfFile = std.debug.ElfFile; const MachOFile = std.debug.MachOFile; -const Info = @This(); - impl: union(enum) { elf: ElfFile, macho: MachOFile, @@ -25,13 +24,23 @@ impl: union(enum) { /// Externally managed, outlives this `Info` instance. coverage: *Coverage, -pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || MachOFile.Error || std.debug.Dwarf.ScanError || error{ MissingDebugInfo, UnsupportedDebugInfo }; +pub const LoadError = error{ + MissingDebugInfo, + UnsupportedDebugInfo, +} || std.fs.File.OpenError || ElfFile.LoadError || MachOFile.Error || std.debug.Dwarf.ScanError; -pub fn load(gpa: Allocator, path: Path, coverage: *Coverage, format: std.Target.ObjectFormat, arch: std.Target.Cpu.Arch) LoadError!Info { +pub fn load( + gpa: Allocator, + io: Io, + path: Path, + coverage: *Coverage, + format: std.Target.ObjectFormat, + arch: std.Target.Cpu.Arch, +) LoadError!Info { switch (format) { .elf => { var file = try path.root_dir.handle.openFile(path.sub_path, .{}); - defer file.close(); + defer file.close(io); var elf_file: ElfFile = try .load(gpa, file, null, &.none); errdefer elf_file.deinit(gpa); diff --git a/lib/std/debug/MachOFile.zig b/lib/std/debug/MachOFile.zig index 3be1b1daff8ff54c2354a0e41e4a30acfebe3841..3f0f620a90488ed39dfca1cac68fabba236f88d8 100644 --- a/lib/std/debug/MachOFile.zig +++ b/lib/std/debug/MachOFile.zig @@ -27,13 +27,13 @@ pub fn deinit(mf: *MachOFile, gpa: Allocator) void { posix.munmap(mf.mapped_memory); } -pub fn load(gpa: Allocator, path: []const u8, arch: std.Target.Cpu.Arch) Error!MachOFile { +pub fn load(gpa: Allocator, io: Io, path: []const u8, arch: std.Target.Cpu.Arch) Error!MachOFile { switch (arch) { .x86_64, .aarch64 => {}, else => unreachable, } - const all_mapped_memory = try mapDebugInfoFile(path); + const all_mapped_memory = try mapDebugInfoFile(io, path); errdefer posix.munmap(all_mapped_memory); // 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 .text_vmaddr = text_vmaddr, }; } -pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, vaddr: u64) !struct { *Dwarf, u64 } { +pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, io: Io, vaddr: u64) !struct { *Dwarf, u64 } { const symbol = Symbol.find(mf.symbols, vaddr) orelse return error.MissingDebugInfo; if (symbol.ofile == Symbol.unknown_ofile) return error.MissingDebugInfo; @@ -254,7 +254,7 @@ pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, vaddr: u64) !struct { const gop = try mf.ofiles.getOrPut(gpa, symbol.ofile); if (!gop.found_existing) { const name = mem.sliceTo(mf.strings[symbol.ofile..], 0); - gop.value_ptr.* = loadOFile(gpa, name); + gop.value_ptr.* = loadOFile(gpa, io, name); } const of = &(gop.value_ptr.* catch |err| return err); @@ -356,7 +356,7 @@ test { _ = Symbol; } -fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile { +fn loadOFile(gpa: Allocator, io: Io, o_file_name: []const u8) !OFile { const all_mapped_memory, const mapped_ofile = map: { const open_paren = paren: { if (std.mem.endsWith(u8, o_file_name, ")")) { @@ -365,7 +365,7 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile { } } // Not an archive, just a normal path to a .o file - const m = try mapDebugInfoFile(o_file_name); + const m = try mapDebugInfoFile(io, o_file_name); break :map .{ m, m }; }; @@ -373,7 +373,7 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile { const archive_path = o_file_name[0..open_paren]; const target_name_in_archive = o_file_name[open_paren + 1 .. o_file_name.len - 1]; - const mapped_archive = try mapDebugInfoFile(archive_path); + const mapped_archive = try mapDebugInfoFile(io, archive_path); errdefer posix.munmap(mapped_archive); var ar_reader: Io.Reader = .fixed(mapped_archive); @@ -511,12 +511,12 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile { } /// Uses `mmap` to map the file at `path` into memory. -fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 { +fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 { const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { error.FileNotFound => return error.MissingDebugInfo, else => return error.ReadFailed, }; - defer file.close(); + defer file.close(io); const file_len = std.math.cast( usize, diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index 59c0b4245173ebc97c26831737dc2fc37827f327..155dac6fb8e4b7940325830cf69e8cacbd5250cf 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -319,14 +319,14 @@ const Module = struct { } /// Assumes we already hold an exclusive lock. - fn getLoadedElf(mod: *Module, gpa: Allocator) Error!*LoadedElf { - if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa); + fn getLoadedElf(mod: *Module, gpa: Allocator, io: Io) Error!*LoadedElf { + if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io); return if (mod.loaded_elf.?) |*elf| elf else |err| err; } - fn loadElf(mod: *Module, gpa: Allocator) Error!LoadedElf { + fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf { const load_result = if (mod.name.len > 0) res: { var file = std.fs.cwd().openFile(mod.name, .{}) catch return error.MissingDebugInfo; - defer file.close(); + defer file.close(io); break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name)); } else res: { const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) { @@ -335,7 +335,7 @@ const Module = struct { }; defer gpa.free(path); var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo; - defer file.close(); + defer file.close(io); break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path)); }; diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig index dd11b4c8bf473d7be8a1f222ff3c1812d505a07d..2491cf416c4659062f14b318ed3e5cfd561ef15f 100644 --- a/lib/std/debug/SelfInfo/MachO.zig +++ b/lib/std/debug/SelfInfo/MachO.zig @@ -615,12 +615,12 @@ test { } /// Uses `mmap` to map the file at `path` into memory. -fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 { +fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 { const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { error.FileNotFound => return error.MissingDebugInfo, else => return error.ReadFailed, }; - defer file.close(); + defer file.close(io); const file_end_pos = file.getEndPos() catch |err| switch (err) { error.Unexpected => |e| return e, diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index 0e923a0f1679077e07c78375cb05588e4da11f59..557f3901eb58ab21f892c215fede4725a216ed29 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -207,11 +207,11 @@ const Module = struct { file: fs.File, section_handle: windows.HANDLE, section_view: []const u8, - fn deinit(mf: *const MappedFile) void { + fn deinit(mf: *const MappedFile, io: Io) void { const process_handle = windows.GetCurrentProcess(); assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mf.section_view.ptr)) == .SUCCESS); windows.CloseHandle(mf.section_handle); - mf.file.close(); + mf.file.close(io); } }; @@ -447,7 +447,7 @@ const Module = struct { error.FileNotFound, error.IsDir => break :pdb null, else => return error.ReadFailed, }; - errdefer pdb_file.close(); + errdefer pdb_file.close(io); const pdb_reader = try arena.create(Io.File.Reader); pdb_reader.* = pdb_file.reader(io, try arena.alloc(u8, 4096)); diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index a3490ed7dbde68a7a3cc2ecb96d9932e2c48fe11..c91056b0abb613938ab7e1f58424891496c8142e 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -1,10 +1,12 @@ -const std = @import("std.zig"); const builtin = @import("builtin"); +const native_os = builtin.os.tag; + +const std = @import("std.zig"); +const Io = std.Io; const mem = std.mem; const testing = std.testing; const elf = std.elf; const windows = std.os.windows; -const native_os = builtin.os.tag; const posix = std.posix; /// Cross-platform dynamic library loading and symbol lookup. @@ -38,8 +40,8 @@ pub const DynLib = struct { } /// Trusts the file. - pub fn close(self: *DynLib) void { - return self.inner.close(); + pub fn close(self: *DynLib, io: Io) void { + return self.inner.close(io); } pub fn lookup(self: *DynLib, comptime T: type, name: [:0]const u8) ?T { @@ -155,23 +157,23 @@ pub const ElfDynLib = struct { dt_gnu_hash: *elf.gnu_hash.Header, }; - fn openPath(path: []const u8) !std.fs.Dir { + fn openPath(path: []const u8, io: Io) !std.fs.Dir { if (path.len == 0) return error.NotDir; var parts = std.mem.tokenizeScalar(u8, path, '/'); var parent = if (path[0] == '/') try std.fs.cwd().openDir("/", .{}) else std.fs.cwd(); while (parts.next()) |part| { const child = try parent.openDir(part, .{}); - parent.close(); + parent.close(io); parent = child; } return parent; } - fn resolveFromSearchPath(search_path: []const u8, file_name: []const u8, delim: u8) ?posix.fd_t { + fn resolveFromSearchPath(io: Io, search_path: []const u8, file_name: []const u8, delim: u8) ?posix.fd_t { var paths = std.mem.tokenizeScalar(u8, search_path, delim); while (paths.next()) |p| { var dir = openPath(p) catch continue; - defer dir.close(); + defer dir.close(io); const fd = posix.openat(dir.fd, file_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true, @@ -181,9 +183,9 @@ pub const ElfDynLib = struct { return null; } - fn resolveFromParent(dir_path: []const u8, file_name: []const u8) ?posix.fd_t { + fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?posix.fd_t { var dir = std.fs.cwd().openDir(dir_path, .{}) catch return null; - defer dir.close(); + defer dir.close(io); return posix.openat(dir.fd, file_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true, @@ -195,7 +197,7 @@ pub const ElfDynLib = struct { // - DT_RPATH of the calling binary is not used as a search path // - DT_RUNPATH of the calling binary is not used as a search path // - /etc/ld.so.cache is not read - fn resolveFromName(path_or_name: []const u8) !posix.fd_t { + fn resolveFromName(io: Io, path_or_name: []const u8) !posix.fd_t { // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| { return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); @@ -206,21 +208,21 @@ pub const ElfDynLib = struct { std.os.linux.getegid() == std.os.linux.getgid()) { if (posix.getenvZ("LD_LIBRARY_PATH")) |ld_library_path| { - if (resolveFromSearchPath(ld_library_path, path_or_name, ':')) |fd| { + if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |fd| { return fd; } } } // Lastly the directories /lib and /usr/lib are searched (in this exact order) - if (resolveFromParent("/lib", path_or_name)) |fd| return fd; - if (resolveFromParent("/usr/lib", path_or_name)) |fd| return fd; + if (resolveFromParent(io, "/lib", path_or_name)) |fd| return fd; + if (resolveFromParent(io, "/usr/lib", path_or_name)) |fd| return fd; return error.FileNotFound; } /// Trusts the file. Malicious file will be able to execute arbitrary code. - pub fn open(path: []const u8) Error!ElfDynLib { - const fd = try resolveFromName(path); + pub fn open(io: Io, path: []const u8) Error!ElfDynLib { + const fd = try resolveFromName(io, path); defer posix.close(fd); const file: std.fs.File = .{ .handle = fd }; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 72fab9c7c24ccfd8e19f85d646911c7343751b71..9472e5d2a5f86d3081d81de97fc4997bec54700c 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -227,7 +227,7 @@ pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void { /// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `absolute_path` should be encoded as valid UTF-8. /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. -pub fn deleteTreeAbsolute(absolute_path: []const u8) !void { +pub fn deleteTreeAbsolute(io: Io, absolute_path: []const u8) !void { assert(path.isAbsolute(absolute_path)); const dirname = path.dirname(absolute_path) orelse return error{ /// Attempt to remove the root file system path. @@ -236,7 +236,7 @@ pub fn deleteTreeAbsolute(absolute_path: []const u8) !void { }.CannotDeleteRootDirectory; var dir = try cwd().openDir(dirname, .{}); - defer dir.close(); + defer dir.close(io); return dir.deleteTree(path.basename(absolute_path)); } diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 52baa0699d773a07e2aa7e65e415e09f0b24fe06..15b8e9b558f55548057822ef86690f21fd25be5d 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -178,6 +178,8 @@ fn setupSymlinkAbsolute(target: []const u8, link: []const u8, flags: SymLinkFlag } test "Dir.readLink" { + const io = testing.io; + try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { // Create some targets @@ -208,7 +210,7 @@ test "Dir.readLink" { const parent_file = ".." ++ fs.path.sep_str ++ "target.txt"; const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file); var subdir = try ctx.dir.makeOpenPath("subdir", .{}); - defer subdir.close(); + defer subdir.close(io); try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{}); try testReadLink(subdir, canonical_parent_file, "relative-link.txt"); if (builtin.os.tag == .windows) { @@ -268,6 +270,8 @@ fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void } test "File.stat on a File that is a symlink returns Kind.sym_link" { + const io = testing.io; + // This test requires getting a file descriptor of a symlink which // is not possible on all targets switch (builtin.target.os.tag) { @@ -302,7 +306,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { .SecurityDescriptor = null, .SecurityQualityOfService = null, }; - var io: windows.IO_STATUS_BLOCK = undefined; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; const rc = windows.ntdll.NtCreateFile( &handle, .{ @@ -317,7 +321,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { }, }, &attr, - &io, + &io_status_block, null, .{ .NORMAL = true }, .VALID_FLAGS, @@ -352,7 +356,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { }, else => unreachable, }; - defer symlink.close(); + defer symlink.close(io); const stat = try symlink.stat(); try testing.expectEqual(File.Kind.sym_link, stat.kind); @@ -361,6 +365,8 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { } test "openDir" { + const io = testing.io; + try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { const allocator = ctx.arena.allocator(); @@ -370,7 +376,7 @@ test "openDir" { for ([_][]const u8{ "", ".", ".." }) |sub_path| { const dir_path = try fs.path.join(allocator, &.{ subdir_path, sub_path }); var dir = try ctx.dir.openDir(dir_path, .{}); - defer dir.close(); + defer dir.close(io); } } }.impl); @@ -393,6 +399,8 @@ test "openDirAbsolute" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .openbsd) return error.SkipZigTest; + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -404,7 +412,7 @@ test "openDirAbsolute" { // Can open sub_path var tmp_sub = try fs.openDirAbsolute(sub_path, .{}); - defer tmp_sub.close(); + defer tmp_sub.close(io); const sub_ino = (try tmp_sub.stat()).inode; @@ -414,7 +422,7 @@ test "openDirAbsolute" { defer testing.allocator.free(dir_path); var dir = try fs.openDirAbsolute(dir_path, .{}); - defer dir.close(); + defer dir.close(io); const ino = (try dir.stat()).inode; try testing.expectEqual(tmp_ino, ino); @@ -426,7 +434,7 @@ test "openDirAbsolute" { defer testing.allocator.free(dir_path); var dir = try fs.openDirAbsolute(dir_path, .{}); - defer dir.close(); + defer dir.close(io); const ino = (try dir.stat()).inode; try testing.expectEqual(sub_ino, ino); @@ -438,7 +446,7 @@ test "openDirAbsolute" { defer testing.allocator.free(dir_path); var dir = try fs.openDirAbsolute(dir_path, .{}); - defer dir.close(); + defer dir.close(io); const ino = (try dir.stat()).inode; try testing.expectEqual(tmp_ino, ino); @@ -446,13 +454,15 @@ test "openDirAbsolute" { } test "openDir cwd parent '..'" { + const io = testing.io; + var dir = fs.cwd().openDir("..", .{}) catch |err| { if (native_os == .wasi and err == error.PermissionDenied) { return; // This is okay. WASI disallows escaping from the fs sandbox } return err; }; - defer dir.close(); + defer dir.close(io); } test "openDir non-cwd parent '..'" { @@ -461,14 +471,16 @@ test "openDir non-cwd parent '..'" { else => {}, } + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); var subdir = try tmp.dir.makeOpenPath("subdir", .{}); - defer subdir.close(); + defer subdir.close(io); var dir = try subdir.openDir("..", .{}); - defer dir.close(); + defer dir.close(io); const expected_path = try tmp.dir.realpathAlloc(testing.allocator, "."); defer testing.allocator.free(expected_path); @@ -516,12 +528,14 @@ test "readLinkAbsolute" { } test "Dir.Iterator" { + const io = testing.io; + var tmp_dir = tmpDir(.{ .iterate = true }); defer tmp_dir.cleanup(); // First, create a couple of entries to iterate over. const file = try tmp_dir.dir.createFile("some_file", .{}); - file.close(); + file.close(io); try tmp_dir.dir.makeDir("some_dir"); @@ -546,6 +560,8 @@ test "Dir.Iterator" { } test "Dir.Iterator many entries" { + const io = testing.io; + var tmp_dir = tmpDir(.{ .iterate = true }); defer tmp_dir.cleanup(); @@ -555,7 +571,7 @@ test "Dir.Iterator many entries" { while (i < num) : (i += 1) { const name = try std.fmt.bufPrint(&buf, "{}", .{i}); const file = try tmp_dir.dir.createFile(name, .{}); - file.close(); + file.close(io); } var arena = ArenaAllocator.init(testing.allocator); @@ -581,12 +597,14 @@ test "Dir.Iterator many entries" { } test "Dir.Iterator twice" { + const io = testing.io; + var tmp_dir = tmpDir(.{ .iterate = true }); defer tmp_dir.cleanup(); // First, create a couple of entries to iterate over. const file = try tmp_dir.dir.createFile("some_file", .{}); - file.close(); + file.close(io); try tmp_dir.dir.makeDir("some_dir"); @@ -614,12 +632,14 @@ test "Dir.Iterator twice" { } test "Dir.Iterator reset" { + const io = testing.io; + var tmp_dir = tmpDir(.{ .iterate = true }); defer tmp_dir.cleanup(); // First, create a couple of entries to iterate over. const file = try tmp_dir.dir.createFile("some_file", .{}); - file.close(); + file.close(io); try tmp_dir.dir.makeDir("some_dir"); @@ -650,12 +670,14 @@ test "Dir.Iterator reset" { } test "Dir.Iterator but dir is deleted during iteration" { + const io = testing.io; + var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); // Create directory and setup an iterator for it var subdir = try tmp.dir.makeOpenPath("subdir", .{ .iterate = true }); - defer subdir.close(); + defer subdir.close(io); var iterator = subdir.iterate(); @@ -742,11 +764,13 @@ test "Dir.realpath smoke test" { } test "readFileAlloc" { + const io = testing.io; + var tmp_dir = tmpDir(.{}); defer tmp_dir.cleanup(); var file = try tmp_dir.dir.createFile("test_file", .{ .read = true }); - defer file.close(); + defer file.close(io); const buf1 = try tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(1024)); defer testing.allocator.free(buf1); @@ -815,10 +839,12 @@ test "statFile on dangling symlink" { test "directory operations on files" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; + const test_file_name = try ctx.transformPath("test_file"); var file = try ctx.dir.createFile(test_file_name, .{ .read = true }); - file.close(); + file.close(io); try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name)); try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{})); @@ -833,7 +859,7 @@ test "directory operations on files" { file = try ctx.dir.openFile(test_file_name, .{}); const stat = try file.stat(); try testing.expectEqual(File.Kind.file, stat.kind); - file.close(); + file.close(io); } }.impl); } @@ -842,6 +868,8 @@ test "file operations on directories" { // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759 if (native_os == .freebsd) return error.SkipZigTest; + const io = testing.io; + try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { const test_dir_name = try ctx.transformPath("test_dir"); @@ -869,7 +897,7 @@ test "file operations on directories" { if (native_os == .wasi and builtin.link_libc) { // wasmtime unexpectedly succeeds here, see https://github.com/ziglang/zig/issues/20747 const handle = try ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }); - handle.close(); + handle.close(io); } else { // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms. // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732 @@ -883,21 +911,23 @@ test "file operations on directories" { // ensure the directory still exists as a sanity check var dir = try ctx.dir.openDir(test_dir_name, .{}); - dir.close(); + dir.close(io); } }.impl); } test "makeOpenPath parent dirs do not exist" { + const io = testing.io; + var tmp_dir = tmpDir(.{}); defer tmp_dir.cleanup(); var dir = try tmp_dir.dir.makeOpenPath("root_dir/parent_dir/some_dir", .{}); - dir.close(); + dir.close(io); // double check that the full directory structure was created var dir_verification = try tmp_dir.dir.openDir("root_dir/parent_dir/some_dir", .{}); - dir_verification.close(); + dir_verification.close(io); } test "deleteDir" { @@ -924,6 +954,7 @@ test "deleteDir" { test "Dir.rename files" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; // Rename on Windows can hit intermittent AccessDenied errors // when certain conditions are true about the host system. // For now, skip this test when the path type is UNC to avoid them. @@ -939,13 +970,13 @@ test "Dir.rename files" { const test_file_name = try ctx.transformPath("test_file"); const renamed_test_file_name = try ctx.transformPath("test_file_renamed"); var file = try ctx.dir.createFile(test_file_name, .{ .read = true }); - file.close(); + file.close(io); try ctx.dir.rename(test_file_name, renamed_test_file_name); // Ensure the file was renamed try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{})); file = try ctx.dir.openFile(renamed_test_file_name, .{}); - file.close(); + file.close(io); // Rename to self succeeds try ctx.dir.rename(renamed_test_file_name, renamed_test_file_name); @@ -953,12 +984,12 @@ test "Dir.rename files" { // Rename to existing file succeeds const existing_file_path = try ctx.transformPath("existing_file"); var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true }); - existing_file.close(); + existing_file.close(io); try ctx.dir.rename(renamed_test_file_name, existing_file_path); try testing.expectError(error.FileNotFound, ctx.dir.openFile(renamed_test_file_name, .{})); file = try ctx.dir.openFile(existing_file_path, .{}); - file.close(); + file.close(io); } }.impl); } @@ -966,6 +997,8 @@ test "Dir.rename files" { test "Dir.rename directories" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; + // Rename on Windows can hit intermittent AccessDenied errors // when certain conditions are true about the host system. // For now, skip this test when the path type is UNC to avoid them. @@ -985,8 +1018,8 @@ test "Dir.rename directories" { // Put a file in the directory var file = try dir.createFile("test_file", .{ .read = true }); - file.close(); - dir.close(); + file.close(io); + dir.close(io); const test_dir_renamed_again_path = try ctx.transformPath("test_dir_renamed_again"); try ctx.dir.rename(test_dir_renamed_path, test_dir_renamed_again_path); @@ -995,8 +1028,8 @@ test "Dir.rename directories" { try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{})); dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{}); file = try dir.openFile("test_file", .{}); - file.close(); - dir.close(); + file.close(io); + dir.close(io); } }.impl); } @@ -1007,6 +1040,8 @@ test "Dir.rename directory onto empty dir" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; + const test_dir_path = try ctx.transformPath("test_dir"); const target_dir_path = try ctx.transformPath("target_dir_path"); @@ -1017,7 +1052,7 @@ test "Dir.rename directory onto empty dir" { // Ensure the directory was renamed try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{})); var dir = try ctx.dir.openDir(target_dir_path, .{}); - dir.close(); + dir.close(io); } }.impl); } @@ -1028,6 +1063,7 @@ test "Dir.rename directory onto non-empty dir" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const test_dir_path = try ctx.transformPath("test_dir"); const target_dir_path = try ctx.transformPath("target_dir_path"); @@ -1035,15 +1071,15 @@ test "Dir.rename directory onto non-empty dir" { var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{}); var file = try target_dir.createFile("test_file", .{ .read = true }); - file.close(); - target_dir.close(); + file.close(io); + target_dir.close(io); // Rename should fail with PathAlreadyExists if target_dir is non-empty try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, target_dir_path)); // Ensure the directory was not renamed var dir = try ctx.dir.openDir(test_dir_path, .{}); - dir.close(); + dir.close(io); } }.impl); } @@ -1054,11 +1090,12 @@ test "Dir.rename file <-> dir" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const test_file_path = try ctx.transformPath("test_file"); const test_dir_path = try ctx.transformPath("test_dir"); var file = try ctx.dir.createFile(test_file_path, .{ .read = true }); - file.close(); + file.close(io); try ctx.dir.makeDir(test_dir_path); try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path)); try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path)); @@ -1067,6 +1104,8 @@ test "Dir.rename file <-> dir" { } test "rename" { + const io = testing.io; + var tmp_dir1 = tmpDir(.{}); defer tmp_dir1.cleanup(); @@ -1077,19 +1116,21 @@ test "rename" { const test_file_name = "test_file"; const renamed_test_file_name = "test_file_renamed"; var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true }); - file.close(); + file.close(io); try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name); // ensure the file was renamed try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{})); file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{}); - file.close(); + file.close(io); } test "renameAbsolute" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .openbsd) return error.SkipZigTest; + const io = testing.io; + var tmp_dir = tmpDir(.{}); defer tmp_dir.cleanup(); @@ -1109,7 +1150,7 @@ test "renameAbsolute" { const test_file_name = "test_file"; const renamed_test_file_name = "test_file_renamed"; var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); - file.close(); + file.close(io); try fs.renameAbsolute( try fs.path.join(allocator, &.{ base_path, test_file_name }), try fs.path.join(allocator, &.{ base_path, renamed_test_file_name }), @@ -1120,7 +1161,7 @@ test "renameAbsolute" { file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); const stat = try file.stat(); try testing.expectEqual(File.Kind.file, stat.kind); - file.close(); + file.close(io); // Renaming directories const test_dir_name = "test_dir"; @@ -1134,14 +1175,16 @@ test "renameAbsolute" { // ensure the directory was renamed try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{})); var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{}); - dir.close(); + dir.close(io); } test "openSelfExe" { if (native_os == .wasi) return error.SkipZigTest; + const io = testing.io; + const self_exe_file = try std.fs.openSelfExe(.{}); - self_exe_file.close(); + self_exe_file.close(io); } test "selfExePath" { @@ -1155,13 +1198,15 @@ test "selfExePath" { } test "deleteTree does not follow symlinks" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); try tmp.dir.makePath("b"); { var a = try tmp.dir.makeOpenPath("a", .{}); - defer a.close(); + defer a.close(io); try setupSymlink(a, "../b", "b", .{ .is_directory = true }); } @@ -1257,27 +1302,31 @@ test "makePath but sub_path contains pre-existing file" { try testing.expectError(error.NotDir, tmp.dir.makePath("foo/bar/baz")); } -fn expectDir(dir: Dir, path: []const u8) !void { +fn expectDir(io: Io, dir: Dir, path: []const u8) !void { var d = try dir.openDir(path, .{}); - d.close(); + d.close(io); } test "makepath existing directories" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); try tmp.dir.makeDir("A"); var tmpA = try tmp.dir.openDir("A", .{}); - defer tmpA.close(); + defer tmpA.close(io); try tmpA.makeDir("B"); const testPath = "A" ++ fs.path.sep_str ++ "B" ++ fs.path.sep_str ++ "C"; try tmp.dir.makePath(testPath); - try expectDir(tmp.dir, testPath); + try expectDir(io, tmp.dir, testPath); } test "makepath through existing valid symlink" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1286,10 +1335,12 @@ test "makepath through existing valid symlink" { try tmp.dir.makePath("working-symlink" ++ fs.path.sep_str ++ "in-realfolder"); - try expectDir(tmp.dir, "realfolder" ++ fs.path.sep_str ++ "in-realfolder"); + try expectDir(io, tmp.dir, "realfolder" ++ fs.path.sep_str ++ "in-realfolder"); } test "makepath relative walks" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1305,21 +1356,23 @@ test "makepath relative walks" { .windows => { // On Windows, .. is resolved before passing the path to NtCreateFile, // meaning everything except `first/C` drops out. - try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C"); + try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "C"); try testing.expectError(error.FileNotFound, tmp.dir.access("second", .{})); try testing.expectError(error.FileNotFound, tmp.dir.access("third", .{})); }, else => { - try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "A"); - try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "B"); - try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C"); - try expectDir(tmp.dir, "second"); - try expectDir(tmp.dir, "third"); + try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "A"); + try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "B"); + try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "C"); + try expectDir(io, tmp.dir, "second"); + try expectDir(io, tmp.dir, "third"); }, } } test "makepath ignores '.'" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1337,14 +1390,14 @@ test "makepath ignores '.'" { try tmp.dir.makePath(dotPath); - try expectDir(tmp.dir, expectedPath); + try expectDir(io, tmp.dir, expectedPath); } -fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void { +fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8) !void { // setup, create a dir and a nested file both with maxed filenames, and walk the dir { var maxed_dir = try iterable_dir.makeOpenPath(maxed_filename, .{}); - defer maxed_dir.close(); + defer maxed_dir.close(io); try maxed_dir.writeFile(.{ .sub_path = maxed_filename, .data = "" }); @@ -1364,6 +1417,8 @@ fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void { } test "max file name component lengths" { + const io = testing.io; + var tmp = tmpDir(.{ .iterate = true }); defer tmp.cleanup(); @@ -1371,16 +1426,16 @@ test "max file name component lengths" { // U+FFFF is the character with the largest code point that is encoded as a single // UTF-16 code unit, so Windows allows for NAME_MAX of them. const maxed_windows_filename = ("\u{FFFF}".*) ** windows.NAME_MAX; - try testFilenameLimits(tmp.dir, &maxed_windows_filename); + try testFilenameLimits(io, tmp.dir, &maxed_windows_filename); } else if (native_os == .wasi) { // On WASI, the maxed filename depends on the host OS, so in order for this test to // work on any host, we need to use a length that will work for all platforms // (i.e. the minimum max_name_bytes of all supported platforms). const maxed_wasi_filename = [_]u8{'1'} ** 255; - try testFilenameLimits(tmp.dir, &maxed_wasi_filename); + try testFilenameLimits(io, tmp.dir, &maxed_wasi_filename); } else { const maxed_ascii_filename = [_]u8{'1'} ** std.fs.max_name_bytes; - try testFilenameLimits(tmp.dir, &maxed_ascii_filename); + try testFilenameLimits(io, tmp.dir, &maxed_ascii_filename); } } @@ -1399,7 +1454,7 @@ test "writev, readv" { var read_vecs: [2][]u8 = .{ &buf2, &buf1 }; var src_file = try tmp.dir.createFile("test.txt", .{ .read = true }); - defer src_file.close(); + defer src_file.close(io); var writer = src_file.writerStreaming(&.{}); @@ -1429,7 +1484,7 @@ test "pwritev, preadv" { var read_vecs: [2][]u8 = .{ &buf2, &buf1 }; var src_file = try tmp.dir.createFile("test.txt", .{ .read = true }); - defer src_file.close(); + defer src_file.close(io); var writer = src_file.writer(&.{}); @@ -1459,7 +1514,7 @@ test "setEndPos" { const file_name = "afile.txt"; try tmp.dir.writeFile(.{ .sub_path = file_name, .data = "ninebytes" }); const f = try tmp.dir.openFile(file_name, .{ .mode = .read_write }); - defer f.close(); + defer f.close(io); const initial_size = try f.getEndPos(); var buffer: [32]u8 = undefined; @@ -1522,21 +1577,21 @@ test "sendfile" { try tmp.dir.makePath("os_test_tmp"); var dir = try tmp.dir.openDir("os_test_tmp", .{}); - defer dir.close(); + defer dir.close(io); const line1 = "line1\n"; const line2 = "second line\n"; var vecs = [_][]const u8{ line1, line2 }; var src_file = try dir.createFile("sendfile1.txt", .{ .read = true }); - defer src_file.close(); + defer src_file.close(io); { var fw = src_file.writer(&.{}); try fw.interface.writeVecAll(&vecs); } var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true }); - defer dest_file.close(); + defer dest_file.close(io); const header1 = "header1\n"; const header2 = "second header\n"; @@ -1569,15 +1624,15 @@ test "sendfile with buffered data" { try tmp.dir.makePath("os_test_tmp"); var dir = try tmp.dir.openDir("os_test_tmp", .{}); - defer dir.close(); + defer dir.close(io); var src_file = try dir.createFile("sendfile1.txt", .{ .read = true }); - defer src_file.close(); + defer src_file.close(io); try src_file.writeAll("AAAABBBB"); var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true }); - defer dest_file.close(); + defer dest_file.close(io); var src_buffer: [32]u8 = undefined; var file_reader = src_file.reader(io, &src_buffer); @@ -1659,10 +1714,11 @@ test "open file with exclusive nonblocking lock twice" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); - defer file1.close(); + defer file1.close(io); const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); try testing.expectError(error.WouldBlock, file2); @@ -1675,10 +1731,11 @@ test "open file with shared and exclusive nonblocking lock" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true }); - defer file1.close(); + defer file1.close(io); const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); try testing.expectError(error.WouldBlock, file2); @@ -1691,10 +1748,11 @@ test "open file with exclusive and shared nonblocking lock" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); - defer file1.close(); + defer file1.close(io); const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true }); try testing.expectError(error.WouldBlock, file2); @@ -1707,10 +1765,11 @@ test "open file with exclusive lock twice, make sure second lock waits" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const filename = try ctx.transformPath("file_lock_test.txt"); const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive }); - errdefer file.close(); + errdefer file.close(io); const S = struct { fn checkFn(dir: *fs.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { @@ -1718,7 +1777,7 @@ test "open file with exclusive lock twice, make sure second lock waits" { const file1 = try dir.createFile(path, .{ .lock = .exclusive }); locked.set(); - file1.close(); + file1.close(io); } }; @@ -1739,7 +1798,7 @@ test "open file with exclusive lock twice, make sure second lock waits" { try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms)); // Release the file lock which should unlock the thread to lock it and set the locked event. - file.close(); + file.close(io); locked.wait(); } }.impl); @@ -1748,6 +1807,8 @@ test "open file with exclusive lock twice, make sure second lock waits" { test "open file with exclusive nonblocking lock twice (absolute paths)" { if (native_os == .wasi) return error.SkipZigTest; + const io = testing.io; + var random_bytes: [12]u8 = undefined; std.crypto.random.bytes(&random_bytes); @@ -1774,18 +1835,19 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { .lock = .exclusive, .lock_nonblocking = true, }); - file1.close(); + file1.close(io); try testing.expectError(error.WouldBlock, file2); } test "read from locked file" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const filename = try ctx.transformPath("read_lock_file_test.txt"); { const f = try ctx.dir.createFile(filename, .{ .read = true }); - defer f.close(); + defer f.close(io); var buffer: [1]u8 = undefined; _ = try f.read(&buffer); } @@ -1794,9 +1856,9 @@ test "read from locked file" { .read = true, .lock = .exclusive, }); - defer f.close(); + defer f.close(io); const f2 = try ctx.dir.openFile(filename, .{}); - defer f2.close(); + defer f2.close(io); var buffer: [1]u8 = undefined; if (builtin.os.tag == .windows) { try std.testing.expectError(error.LockViolation, f2.read(&buffer)); @@ -1809,6 +1871,8 @@ test "read from locked file" { } test "walker" { + const io = testing.io; + var tmp = tmpDir(.{ .iterate = true }); defer tmp.cleanup(); @@ -1857,13 +1921,15 @@ test "walker" { }; // make sure that the entry.dir is the containing dir var entry_dir = try entry.dir.openDir(entry.basename, .{}); - defer entry_dir.close(); + defer entry_dir.close(io); num_walked += 1; } try testing.expectEqual(expected_paths.kvs.len, num_walked); } test "selective walker, skip entries that start with ." { + const io = testing.io; + var tmp = tmpDir(.{ .iterate = true }); defer tmp.cleanup(); @@ -1923,7 +1989,7 @@ test "selective walker, skip entries that start with ." { // make sure that the entry.dir is the containing dir var entry_dir = try entry.dir.openDir(entry.basename, .{}); - defer entry_dir.close(); + defer entry_dir.close(io); num_walked += 1; } try testing.expectEqual(expected_paths.kvs.len, num_walked); @@ -1968,16 +2034,16 @@ test "'.' and '..' in fs.Dir functions" { try ctx.dir.makeDir(subdir_path); try ctx.dir.access(subdir_path, .{}); var created_subdir = try ctx.dir.openDir(subdir_path, .{}); - created_subdir.close(); + created_subdir.close(io); const created_file = try ctx.dir.createFile(file_path, .{}); - created_file.close(); + created_file.close(io); try ctx.dir.access(file_path, .{}); try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{}); try ctx.dir.rename(copy_path, rename_path); const renamed_file = try ctx.dir.openFile(rename_path, .{}); - renamed_file.close(); + renamed_file.close(io); try ctx.dir.deleteFile(rename_path); try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" }); @@ -1994,6 +2060,8 @@ test "'.' and '..' in absolute functions" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .openbsd) return error.SkipZigTest; + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -2007,11 +2075,11 @@ test "'.' and '..' in absolute functions" { try fs.makeDirAbsolute(subdir_path); try fs.accessAbsolute(subdir_path, .{}); var created_subdir = try fs.openDirAbsolute(subdir_path, .{}); - created_subdir.close(); + created_subdir.close(io); const created_file_path = try fs.path.join(allocator, &.{ subdir_path, "../file" }); const created_file = try fs.createFileAbsolute(created_file_path, .{}); - created_file.close(); + created_file.close(io); try fs.accessAbsolute(created_file_path, .{}); const copied_file_path = try fs.path.join(allocator, &.{ subdir_path, "../copy" }); @@ -2019,7 +2087,7 @@ test "'.' and '..' in absolute functions" { const renamed_file_path = try fs.path.join(allocator, &.{ subdir_path, "../rename" }); try fs.renameAbsolute(copied_file_path, renamed_file_path); const renamed_file = try fs.openFileAbsolute(renamed_file_path, .{}); - renamed_file.close(); + renamed_file.close(io); try fs.deleteFileAbsolute(renamed_file_path); try fs.deleteDirAbsolute(subdir_path); @@ -2029,11 +2097,13 @@ test "chmod" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); const file = try tmp.dir.createFile("test_file", .{ .mode = 0o600 }); - defer file.close(); + defer file.close(io); try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat()).mode & 0o7777); try file.chmod(0o644); @@ -2041,7 +2111,7 @@ test "chmod" { try tmp.dir.makeDir("test_dir"); var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true }); - defer dir.close(); + defer dir.close(io); try dir.chmod(0o700); try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat()).mode & 0o7777); @@ -2051,17 +2121,19 @@ test "chown" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); const file = try tmp.dir.createFile("test_file", .{}); - defer file.close(); + defer file.close(io); try file.chown(null, null); try tmp.dir.makeDir("test_dir"); var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true }); - defer dir.close(); + defer dir.close(io); try dir.chown(null, null); } @@ -2157,7 +2229,7 @@ test "read file non vectored" { const contents = "hello, world!\n"; const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); - defer file.close(); + defer file.close(io); { var file_writer: std.fs.File.Writer = .init(file, &.{}); try file_writer.interface.writeAll(contents); @@ -2189,7 +2261,7 @@ test "seek keeping partial buffer" { const contents = "0123456789"; const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); - defer file.close(); + defer file.close(io); { var file_writer: std.fs.File.Writer = .init(file, &.{}); try file_writer.interface.writeAll(contents); @@ -2231,7 +2303,7 @@ test "seekBy" { try tmp_dir.dir.writeFile(.{ .sub_path = "blah.txt", .data = "let's test seekBy" }); const f = try tmp_dir.dir.openFile("blah.txt", .{ .mode = .read_only }); - defer f.close(); + defer f.close(io); var reader = f.readerStreaming(io, &.{}); try reader.seekBy(2); @@ -2250,7 +2322,7 @@ test "seekTo flushes buffered data" { const contents = "data"; const file = try tmp.dir.createFile("seek.bin", .{ .read = true }); - defer file.close(); + defer file.close(io); { var buf: [16]u8 = undefined; var file_writer = std.fs.File.writer(file, &buf); @@ -2277,9 +2349,9 @@ test "File.Writer sendfile with buffered contents" { { try tmp_dir.dir.writeFile(.{ .sub_path = "a", .data = "bcd" }); const in = try tmp_dir.dir.openFile("a", .{}); - defer in.close(); + defer in.close(io); const out = try tmp_dir.dir.createFile("b", .{}); - defer out.close(); + defer out.close(io); var in_buf: [2]u8 = undefined; var in_r = in.reader(io, &in_buf); @@ -2294,7 +2366,7 @@ test "File.Writer sendfile with buffered contents" { } var check = try tmp_dir.dir.openFile("b", .{}); - defer check.close(); + defer check.close(io); var check_buf: [4]u8 = undefined; var check_r = check.reader(io, &check_buf); try testing.expectEqualStrings("abcd", try check_r.interface.take(4)); diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 10ab23f4760c04496b274520b79d163fd0cb5c0a..7f8c9827ef03ab3ba70477a0ec0d226c260df9f1 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -1473,6 +1473,8 @@ pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{N /// /// This function is threadsafe. pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connection { + const io = client.io; + if (client.connection_pool.findConnection(.{ .host = path, .port = 0, @@ -1485,7 +1487,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti conn.* = .{ .data = undefined }; const stream = try Io.net.connectUnixSocket(path); - errdefer stream.close(); + errdefer stream.close(io); conn.data = .{ .stream = stream, diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index c927dab376c57cabfb486b19b2150dc19fd1ac00..e4a5bd373805c0cf557b118d767b485a26ff7235 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -1,13 +1,16 @@ const IoUring = @This(); -const std = @import("std"); + const builtin = @import("builtin"); +const is_linux = builtin.os.tag == .linux; + +const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const mem = std.mem; const net = std.Io.net; const posix = std.posix; const linux = std.os.linux; const testing = std.testing; -const is_linux = builtin.os.tag == .linux; const page_size_min = std.heap.page_size_min; fd: linux.fd_t = -1, @@ -1975,6 +1978,8 @@ test "readv" { test "writev/fsync/readv" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(4, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -1987,7 +1992,7 @@ test "writev/fsync/readv" { const path = "test_io_uring_writev_fsync_readv"; const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true }); - defer file.close(); + defer file.close(io); const fd = file.handle; const buffer_write = [_]u8{42} ** 128; @@ -2045,6 +2050,8 @@ test "writev/fsync/readv" { test "write/read" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(2, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2056,7 +2063,7 @@ test "write/read" { defer tmp.cleanup(); const path = "test_io_uring_write_read"; const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true }); - defer file.close(); + defer file.close(io); const fd = file.handle; const buffer_write = [_]u8{97} ** 20; @@ -2092,6 +2099,8 @@ test "write/read" { test "splice/read" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(4, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2102,12 +2111,12 @@ test "splice/read" { var tmp = std.testing.tmpDir(.{}); const path_src = "test_io_uring_splice_src"; const file_src = try tmp.dir.createFile(path_src, .{ .read = true, .truncate = true }); - defer file_src.close(); + defer file_src.close(io); const fd_src = file_src.handle; const path_dst = "test_io_uring_splice_dst"; const file_dst = try tmp.dir.createFile(path_dst, .{ .read = true, .truncate = true }); - defer file_dst.close(); + defer file_dst.close(io); const fd_dst = file_dst.handle; const buffer_write = [_]u8{97} ** 20; @@ -2163,6 +2172,8 @@ test "splice/read" { test "write_fixed/read_fixed" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(2, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2175,7 +2186,7 @@ test "write_fixed/read_fixed" { const path = "test_io_uring_write_read_fixed"; const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true }); - defer file.close(); + defer file.close(io); const fd = file.handle; var raw_buffers: [2][11]u8 = undefined; @@ -2282,6 +2293,8 @@ test "openat" { test "close" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2294,7 +2307,7 @@ test "close" { const path = "test_io_uring_close"; const file = try tmp.dir.createFile(path, .{}); - errdefer file.close(); + errdefer file.close(io); const sqe_close = try ring.close(0x44444444, file.handle); try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode); @@ -2313,6 +2326,8 @@ test "close" { test "accept/connect/send/recv" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2321,7 +2336,7 @@ test "accept/connect/send/recv" { defer ring.deinit(); const socket_test_harness = try createSocketTestHarness(&ring); - defer socket_test_harness.close(); + defer socket_test_harness.close(io); const buffer_send = [_]u8{ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 }; var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 }; @@ -2573,6 +2588,8 @@ test "timeout_remove" { test "accept/connect/recv/link_timeout" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2581,7 +2598,7 @@ test "accept/connect/recv/link_timeout" { defer ring.deinit(); const socket_test_harness = try createSocketTestHarness(&ring); - defer socket_test_harness.close(); + defer socket_test_harness.close(io); var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 }; @@ -2622,6 +2639,8 @@ test "accept/connect/recv/link_timeout" { test "fallocate" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2634,7 +2653,7 @@ test "fallocate" { const path = "test_io_uring_fallocate"; const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); - defer file.close(); + defer file.close(io); try testing.expectEqual(@as(u64, 0), (try file.stat()).size); @@ -2668,6 +2687,8 @@ test "fallocate" { test "statx" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2679,7 +2700,7 @@ test "statx" { defer tmp.cleanup(); const path = "test_io_uring_statx"; const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); - defer file.close(); + defer file.close(io); try testing.expectEqual(@as(u64, 0), (try file.stat()).size); @@ -2725,6 +2746,8 @@ test "statx" { test "accept/connect/recv/cancel" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2733,7 +2756,7 @@ test "accept/connect/recv/cancel" { defer ring.deinit(); const socket_test_harness = try createSocketTestHarness(&ring); - defer socket_test_harness.close(); + defer socket_test_harness.close(io); var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 }; @@ -2929,6 +2952,8 @@ test "shutdown" { test "renameat" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2945,7 +2970,7 @@ test "renameat" { // Write old file with data const old_file = try tmp.dir.createFile(old_path, .{ .truncate = true, .mode = 0o666 }); - defer old_file.close(); + defer old_file.close(io); try old_file.writeAll("hello"); // Submit renameat @@ -2987,6 +3012,8 @@ test "renameat" { test "unlinkat" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -3002,7 +3029,7 @@ test "unlinkat" { // Write old file with data const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); - defer file.close(); + defer file.close(io); // Submit unlinkat @@ -3083,6 +3110,8 @@ test "mkdirat" { test "symlinkat" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -3097,7 +3126,7 @@ test "symlinkat" { const link_path = "test_io_uring_symlinkat_link"; const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); - defer file.close(); + defer file.close(io); // Submit symlinkat @@ -3131,6 +3160,8 @@ test "symlinkat" { test "linkat" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -3147,7 +3178,7 @@ test "linkat" { // Write file with data const first_file = try tmp.dir.createFile(first_path, .{ .truncate = true, .mode = 0o666 }); - defer first_file.close(); + defer first_file.close(io); try first_file.writeAll("hello"); // Submit linkat @@ -3407,6 +3438,8 @@ test "remove_buffers" { test "provide_buffers: accept/connect/send/recv" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -3443,7 +3476,7 @@ test "provide_buffers: accept/connect/send/recv" { } const socket_test_harness = try createSocketTestHarness(&ring); - defer socket_test_harness.close(); + defer socket_test_harness.close(io); // Do 4 send on the socket @@ -3696,6 +3729,8 @@ test "accept multishot" { test "accept/connect/send_zc/recv" { try skipKernelLessThan(.{ .major = 6, .minor = 0, .patch = 0 }); + const io = testing.io; + var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -3704,7 +3739,7 @@ test "accept/connect/send_zc/recv" { defer ring.deinit(); const socket_test_harness = try createSocketTestHarness(&ring); - defer socket_test_harness.close(); + defer socket_test_harness.close(io); const buffer_send = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe }; var buffer_recv = [_]u8{0} ** 10; @@ -4105,6 +4140,8 @@ inline fn skipKernelLessThan(required: std.SemanticVersion) !void { test BufferGroup { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + // Init IoUring var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, @@ -4132,7 +4169,7 @@ test BufferGroup { // Create client/server fds const fds = try createSocketTestHarness(&ring); - defer fds.close(); + defer fds.close(io); const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe }; // Client sends data @@ -4170,6 +4207,8 @@ test BufferGroup { test "ring mapped buffers recv" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -4196,7 +4235,7 @@ test "ring mapped buffers recv" { // create client/server fds const fds = try createSocketTestHarness(&ring); - defer fds.close(); + defer fds.close(io); // for random user_data in sqe/cqe var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed); @@ -4259,6 +4298,8 @@ test "ring mapped buffers recv" { test "ring mapped buffers multishot recv" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -4285,7 +4326,7 @@ test "ring mapped buffers multishot recv" { // create client/server fds const fds = try createSocketTestHarness(&ring); - defer fds.close(); + defer fds.close(io); // for random user_data in sqe/cqe var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed); diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index 7399907477641340acb55a51f8a3b95eaa50f426..39606ddfacc66a6544b50b4905664500247175ee 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -12,12 +12,14 @@ const fs = std.fs; test "fallocate" { if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/30220 + const io = std.testing.io; + var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const path = "test_fallocate"; const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); - defer file.close(); + defer file.close(io); try expect((try file.stat()).size == 0); @@ -77,12 +79,14 @@ test "timer" { } test "statx" { + const io = std.testing.io; + var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const tmp_file_name = "just_a_temporary_file.txt"; var file = try tmp.dir.createFile(tmp_file_name, .{}); - defer file.close(); + defer file.close(io); var buf: linux.Statx = undefined; switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .BASIC_STATS, &buf))) { @@ -111,12 +115,14 @@ test "user and group ids" { } test "fadvise" { + const io = std.testing.io; + var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const tmp_file_name = "temp_posix_fadvise.txt"; var file = try tmp.dir.createFile(tmp_file_name, .{}); - defer file.close(); + defer file.close(io); var buf: [2048]u8 = undefined; try file.writeAll(&buf); diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 3bb5e64c73a8033798df8114c960baff792b0a9d..8889e50ea344c5f1d5a6dcaa5ddbf3bbb33d738f 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -148,6 +148,8 @@ test "linkat with different directories" { else => return error.SkipZigTest, } + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -163,10 +165,10 @@ test "linkat with different directories" { try posix.linkat(tmp.dir.fd, target_name, subdir.fd, link_name, 0); const efd = try tmp.dir.openFile(target_name, .{}); - defer efd.close(); + defer efd.close(io); const nfd = try subdir.openFile(link_name, .{}); - defer nfd.close(); + defer nfd.close(io); { const eino, _ = try getLinkInfo(efd.handle); @@ -381,6 +383,8 @@ test "mmap" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -413,7 +417,7 @@ test "mmap" { // Create a file used for testing mmap() calls with a file descriptor { const file = try tmp.dir.createFile(test_out_file, .{}); - defer file.close(); + defer file.close(io); var stream = file.writer(&.{}); @@ -426,7 +430,7 @@ test "mmap" { // Map the whole file { const file = try tmp.dir.openFile(test_out_file, .{}); - defer file.close(); + defer file.close(io); const data = try posix.mmap( null, @@ -451,7 +455,7 @@ test "mmap" { // Map the upper half of the file { const file = try tmp.dir.openFile(test_out_file, .{}); - defer file.close(); + defer file.close(io); const data = try posix.mmap( null, @@ -476,13 +480,15 @@ test "fcntl" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); const test_out_file = "os_tmp_test"; const file = try tmp.dir.createFile(test_out_file, .{}); - defer file.close(); + defer file.close(io); // Note: The test assumes createFile opens the file with CLOEXEC { @@ -526,12 +532,14 @@ test "fsync" { else => return error.SkipZigTest, } + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); const test_out_file = "os_tmp_test"; const file = try tmp.dir.createFile(test_out_file, .{}); - defer file.close(); + defer file.close(io); try posix.fsync(file.handle); try posix.fdatasync(file.handle); @@ -646,22 +654,24 @@ test "dup & dup2" { else => return error.SkipZigTest, } + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); { var file = try tmp.dir.createFile("os_dup_test", .{}); - defer file.close(); + defer file.close(io); var duped = std.fs.File{ .handle = try posix.dup(file.handle) }; - defer duped.close(); + defer duped.close(io); try duped.writeAll("dup"); // Tests aren't run in parallel so using the next fd shouldn't be an issue. const new_fd = duped.handle + 1; try posix.dup2(file.handle, new_fd); var dup2ed = std.fs.File{ .handle = new_fd }; - defer dup2ed.close(); + defer dup2ed.close(io); try dup2ed.writeAll("dup2"); } @@ -687,11 +697,13 @@ test "getppid" { test "writev longer than IOV_MAX" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); var file = try tmp.dir.createFile("pwritev", .{}); - defer file.close(); + defer file.close(io); const iovecs = [_]posix.iovec_const{.{ .base = "a", .len = 1 }} ** (posix.IOV_MAX + 1); const amt = try file.writev(&iovecs); @@ -709,12 +721,14 @@ test "POSIX file locking with fcntl" { return error.SkipZigTest; } + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); // Create a temporary lock file var file = try tmp.dir.createFile("lock", .{ .read = true }); - defer file.close(); + defer file.close(io); try file.setEndPos(2); const fd = file.handle; @@ -905,21 +919,25 @@ test "timerfd" { } test "isatty" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); var file = try tmp.dir.createFile("foo", .{}); - defer file.close(); + defer file.close(io); try expectEqual(posix.isatty(file.handle), false); } test "pread with empty buffer" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); var file = try tmp.dir.createFile("pread_empty", .{ .read = true }); - defer file.close(); + defer file.close(io); const bytes = try a.alloc(u8, 0); defer a.free(bytes); @@ -929,11 +947,13 @@ test "pread with empty buffer" { } test "write with empty buffer" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); var file = try tmp.dir.createFile("write_empty", .{}); - defer file.close(); + defer file.close(io); const bytes = try a.alloc(u8, 0); defer a.free(bytes); @@ -943,11 +963,13 @@ test "write with empty buffer" { } test "pwrite with empty buffer" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); var file = try tmp.dir.createFile("pwrite_empty", .{}); - defer file.close(); + defer file.close(io); const bytes = try a.alloc(u8, 0); defer a.free(bytes); diff --git a/lib/std/process.zig b/lib/std/process.zig index a0a26c766fa25727a8563a2ff50e9df4ed42e9ba..a8dede6ad4aa52ec5750eefef3cf7ee5d32bc8f7 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -1,12 +1,14 @@ -const std = @import("std.zig"); const builtin = @import("builtin"); +const native_os = builtin.os.tag; + +const std = @import("std.zig"); +const Io = std.Io; const fs = std.fs; const mem = std.mem; const math = std.math; -const Allocator = mem.Allocator; +const Allocator = std.mem.Allocator; const assert = std.debug.assert; const testing = std.testing; -const native_os = builtin.os.tag; const posix = std.posix; const windows = std.os.windows; const unicode = std.unicode; @@ -1571,9 +1573,9 @@ pub fn getUserInfo(name: []const u8) !UserInfo { /// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else /// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`. -pub fn posixGetUserInfo(name: []const u8) !UserInfo { +pub fn posixGetUserInfo(io: Io, name: []const u8) !UserInfo { const file = try std.fs.openFileAbsolute("/etc/passwd", .{}); - defer file.close(); + defer file.close(io); var buffer: [4096]u8 = undefined; var file_reader = file.reader(&buffer); return posixGetUserInfoPasswdStream(name, &file_reader.interface) catch |err| switch (err) { diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 6dad72df44287d91cf8b4393672108523b3113a4..be3026ff1000542b1b561a73a89d135890f21239 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -4,6 +4,7 @@ const builtin = @import("builtin"); const native_os = builtin.os.tag; const std = @import("../std.zig"); +const Io = std.Io; const unicode = std.unicode; const fs = std.fs; const process = std.process; @@ -277,17 +278,17 @@ pub fn spawnAndWait(self: *ChildProcess) SpawnError!Term { } /// Forcibly terminates child process and then cleans up all resources. -pub fn kill(self: *ChildProcess) !Term { +pub fn kill(self: *ChildProcess, io: Io) !Term { if (native_os == .windows) { - return self.killWindows(1); + return self.killWindows(io, 1); } else { - return self.killPosix(); + return self.killPosix(io); } } -pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term { +pub fn killWindows(self: *ChildProcess, io: Io, exit_code: windows.UINT) !Term { if (self.term) |term| { - self.cleanupStreams(); + self.cleanupStreams(io); return term; } @@ -303,20 +304,20 @@ pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term { }, else => return err, }; - try self.waitUnwrappedWindows(); + try self.waitUnwrappedWindows(io); return self.term.?; } -pub fn killPosix(self: *ChildProcess) !Term { +pub fn killPosix(self: *ChildProcess, io: Io) !Term { if (self.term) |term| { - self.cleanupStreams(); + self.cleanupStreams(io); return term; } posix.kill(self.id, posix.SIG.TERM) catch |err| switch (err) { error.ProcessNotFound => return error.AlreadyTerminated, else => return err, }; - self.waitUnwrappedPosix(); + self.waitUnwrappedPosix(io); return self.term.?; } @@ -354,15 +355,15 @@ pub fn waitForSpawn(self: *ChildProcess) SpawnError!void { } /// Blocks until child process terminates and then cleans up all resources. -pub fn wait(self: *ChildProcess) WaitError!Term { +pub fn wait(self: *ChildProcess, io: Io) WaitError!Term { try self.waitForSpawn(); // report spawn errors if (self.term) |term| { - self.cleanupStreams(); + self.cleanupStreams(io); return term; } switch (native_os) { - .windows => try self.waitUnwrappedWindows(), - else => self.waitUnwrappedPosix(), + .windows => try self.waitUnwrappedWindows(io), + else => self.waitUnwrappedPosix(io), } self.id = undefined; return self.term.?; @@ -474,7 +475,7 @@ pub fn run(args: struct { }; } -fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void { +fn waitUnwrappedWindows(self: *ChildProcess, io: Io) WaitError!void { const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false); self.term = @as(SpawnError!Term, x: { @@ -492,11 +493,11 @@ fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void { posix.close(self.id); posix.close(self.thread_handle); - self.cleanupStreams(); + self.cleanupStreams(io); return result; } -fn waitUnwrappedPosix(self: *ChildProcess) void { +fn waitUnwrappedPosix(self: *ChildProcess, io: Io) void { const res: posix.WaitPidResult = res: { if (self.request_resource_usage_statistics) { switch (native_os) { @@ -527,7 +528,7 @@ fn waitUnwrappedPosix(self: *ChildProcess) void { break :res posix.waitpid(self.id, 0); }; const status = res.status; - self.cleanupStreams(); + self.cleanupStreams(io); self.handleWaitResult(status); } @@ -535,17 +536,17 @@ fn handleWaitResult(self: *ChildProcess, status: u32) void { self.term = statusToTerm(status); } -fn cleanupStreams(self: *ChildProcess) void { +fn cleanupStreams(self: *ChildProcess, io: Io) void { if (self.stdin) |*stdin| { - stdin.close(); + stdin.close(io); self.stdin = null; } if (self.stdout) |*stdout| { - stdout.close(); + stdout.close(io); self.stdout = null; } if (self.stderr) |*stderr| { - stderr.close(); + stderr.close(io); self.stderr = null; } } diff --git a/lib/std/tar.zig b/lib/std/tar.zig index bf96aed35c832588c6f5586e9b87b703ba9a06d8..d861314fecb3768254d293b97391d18e49a63c9a 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -16,6 +16,7 @@ //! pax reference: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13 const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const testing = std.testing; @@ -302,7 +303,7 @@ pub const FileKind = enum { /// Iterator over entries in the tar file represented by reader. pub const Iterator = struct { - reader: *std.Io.Reader, + reader: *Io.Reader, diagnostics: ?*Diagnostics = null, // buffers for heeader and file attributes @@ -328,7 +329,7 @@ pub const Iterator = struct { /// Iterates over files in tar archive. /// `next` returns each file in tar archive. - pub fn init(reader: *std.Io.Reader, options: Options) Iterator { + pub fn init(reader: *Io.Reader, options: Options) Iterator { return .{ .reader = reader, .diagnostics = options.diagnostics, @@ -473,7 +474,7 @@ pub const Iterator = struct { return null; } - pub fn streamRemaining(it: *Iterator, file: File, w: *std.Io.Writer) std.Io.Reader.StreamError!void { + pub fn streamRemaining(it: *Iterator, file: File, w: *Io.Writer) Io.Reader.StreamError!void { try it.reader.streamExact64(w, file.size); it.unread_file_bytes = 0; } @@ -499,14 +500,14 @@ const pax_max_size_attr_len = 64; pub const PaxIterator = struct { size: usize, // cumulative size of all pax attributes - reader: *std.Io.Reader, + reader: *Io.Reader, const Self = @This(); const Attribute = struct { kind: PaxAttributeKind, len: usize, // length of the attribute value - reader: *std.Io.Reader, // reader positioned at value start + reader: *Io.Reader, // reader positioned at value start // Copies pax attribute value into destination buffer. // Must be called with destination buffer of size at least Attribute.len. @@ -573,13 +574,13 @@ pub const PaxIterator = struct { } // Checks that each record ends with new line. - fn validateAttributeEnding(reader: *std.Io.Reader) !void { + fn validateAttributeEnding(reader: *Io.Reader) !void { if (try reader.takeByte() != '\n') return error.PaxInvalidAttributeEnd; } }; /// Saves tar file content to the file systems. -pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOptions) !void { +pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOptions) !void { var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; var file_contents_buffer: [1024]u8 = undefined; @@ -610,7 +611,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp }, .file => { if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| { - defer fs_file.close(); + defer fs_file.close(io); var file_writer = fs_file.writer(&file_contents_buffer); try it.streamRemaining(file, &file_writer.interface); try file_writer.interface.flush(); @@ -637,7 +638,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp } } -fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mode) !std.fs.File { +fn createDirAndFile(dir: Io.Dir, file_name: []const u8, mode: Io.File.Mode) !Io.File { const fs_file = dir.createFile(file_name, .{ .exclusive = true, .mode = mode }) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { @@ -651,7 +652,7 @@ fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mo } // Creates a symbolic link at path `file_name` which points to `link_name`. -fn createDirAndSymlink(dir: std.fs.Dir, link_name: []const u8, file_name: []const u8) !void { +fn createDirAndSymlink(dir: Io.Dir, link_name: []const u8, file_name: []const u8) !void { dir.symLink(link_name, file_name, .{}) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { @@ -783,7 +784,7 @@ test PaxIterator { var buffer: [1024]u8 = undefined; outer: for (cases) |case| { - var reader: std.Io.Reader = .fixed(case.data); + var reader: Io.Reader = .fixed(case.data); var it: PaxIterator = .{ .size = case.data.len, .reader = &reader, @@ -874,13 +875,15 @@ test "header parse mode" { } test "create file and symlink" { + const io = testing.io; + var root = testing.tmpDir(.{}); defer root.cleanup(); var file = try createDirAndFile(root.dir, "file1", default_mode); - file.close(); + file.close(io); file = try createDirAndFile(root.dir, "a/b/c/file2", default_mode); - file.close(); + file.close(io); createDirAndSymlink(root.dir, "a/b/c/file2", "symlink1") catch |err| { // On Windows when developer mode is not enabled @@ -892,7 +895,7 @@ test "create file and symlink" { // Danglink symlnik, file created later try createDirAndSymlink(root.dir, "../../../g/h/i/file4", "j/k/l/symlink3"); file = try createDirAndFile(root.dir, "g/h/i/file4", default_mode); - file.close(); + file.close(io); } test Iterator { @@ -916,7 +919,7 @@ test Iterator { // example/empty/ const data = @embedFile("tar/testdata/example.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); // User provided buffers to the iterator var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; @@ -942,7 +945,7 @@ test Iterator { .file => { try testing.expectEqualStrings("example/a/file", file.name); var buf: [16]u8 = undefined; - var w: std.Io.Writer = .fixed(&buf); + var w: Io.Writer = .fixed(&buf); try it.streamRemaining(file, &w); try testing.expectEqualStrings("content\n", w.buffered()); }, @@ -955,6 +958,7 @@ test Iterator { } test pipeToFileSystem { + const io = testing.io; // Example tar file is created from this tree structure: // $ tree example // example @@ -975,14 +979,14 @@ test pipeToFileSystem { // example/empty/ const data = @embedFile("tar/testdata/example.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); var tmp = testing.tmpDir(.{ .follow_symlinks = false }); defer tmp.cleanup(); const dir = tmp.dir; // Save tar from reader to the file system `dir` - pipeToFileSystem(dir, &reader, .{ + pipeToFileSystem(io, dir, &reader, .{ .mode_mode = .ignore, .strip_components = 1, .exclude_empty_directories = true, @@ -1005,8 +1009,9 @@ test pipeToFileSystem { } test "pipeToFileSystem root_dir" { + const io = testing.io; const data = @embedFile("tar/testdata/example.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); // with strip_components = 1 { @@ -1015,7 +1020,7 @@ test "pipeToFileSystem root_dir" { var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); - pipeToFileSystem(tmp.dir, &reader, .{ + pipeToFileSystem(io, tmp.dir, &reader, .{ .strip_components = 1, .diagnostics = &diagnostics, }) catch |err| { @@ -1037,7 +1042,7 @@ test "pipeToFileSystem root_dir" { var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); - pipeToFileSystem(tmp.dir, &reader, .{ + pipeToFileSystem(io, tmp.dir, &reader, .{ .strip_components = 0, .diagnostics = &diagnostics, }) catch |err| { @@ -1053,43 +1058,46 @@ test "pipeToFileSystem root_dir" { } test "findRoot with single file archive" { + const io = testing.io; const data = @embedFile("tar/testdata/22752.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); - try pipeToFileSystem(tmp.dir, &reader, .{ .diagnostics = &diagnostics }); + try pipeToFileSystem(io, tmp.dir, &reader, .{ .diagnostics = &diagnostics }); try testing.expectEqualStrings("", diagnostics.root_dir); } test "findRoot without explicit root dir" { + const io = testing.io; const data = @embedFile("tar/testdata/19820.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); - try pipeToFileSystem(tmp.dir, &reader, .{ .diagnostics = &diagnostics }); + try pipeToFileSystem(io, tmp.dir, &reader, .{ .diagnostics = &diagnostics }); try testing.expectEqualStrings("root", diagnostics.root_dir); } test "pipeToFileSystem strip_components" { + const io = testing.io; const data = @embedFile("tar/testdata/example.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); var tmp = testing.tmpDir(.{ .follow_symlinks = false }); defer tmp.cleanup(); var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); - pipeToFileSystem(tmp.dir, &reader, .{ + pipeToFileSystem(io, tmp.dir, &reader, .{ .strip_components = 3, .diagnostics = &diagnostics, }) catch |err| { @@ -1110,10 +1118,10 @@ fn normalizePath(bytes: []u8) []u8 { return bytes; } -const default_mode = std.fs.File.default_mode; +const default_mode = Io.File.default_mode; // File system mode based on tar header mode and mode_mode options. -fn fileMode(mode: u32, options: PipeOptions) std.fs.File.Mode { +fn fileMode(mode: u32, options: PipeOptions) Io.File.Mode { if (!std.fs.has_executable_bit or options.mode_mode == .ignore) return default_mode; @@ -1139,16 +1147,17 @@ test fileMode { test "executable bit" { if (!std.fs.has_executable_bit) return error.SkipZigTest; + const io = testing.io; const S = std.posix.S; const data = @embedFile("tar/testdata/example.tar"); for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| { - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); var tmp = testing.tmpDir(.{ .follow_symlinks = false }); //defer tmp.cleanup(); - pipeToFileSystem(tmp.dir, &reader, .{ + pipeToFileSystem(io, tmp.dir, &reader, .{ .strip_components = 1, .exclude_empty_directories = true, .mode_mode = opt, diff --git a/lib/std/tar/test.zig b/lib/std/tar/test.zig index 780e2b844cb13fc09074d928c174c2983000c13e..b3fbd8f4b3cce2f1c887346f716f8e1840e9cee0 100644 --- a/lib/std/tar/test.zig +++ b/lib/std/tar/test.zig @@ -424,6 +424,7 @@ test "insufficient buffer in Header name filed" { } test "should not overwrite existing file" { + const io = testing.io; // Starting from this folder structure: // $ tree root // root @@ -469,17 +470,18 @@ test "should not overwrite existing file" { defer root.cleanup(); try testing.expectError( error.PathAlreadyExists, - tar.pipeToFileSystem(root.dir, &r, .{ .mode_mode = .ignore, .strip_components = 1 }), + tar.pipeToFileSystem(io, root.dir, &r, .{ .mode_mode = .ignore, .strip_components = 1 }), ); // Unpack with strip_components = 0 should pass r = .fixed(data); var root2 = std.testing.tmpDir(.{}); defer root2.cleanup(); - try tar.pipeToFileSystem(root2.dir, &r, .{ .mode_mode = .ignore, .strip_components = 0 }); + try tar.pipeToFileSystem(io, root2.dir, &r, .{ .mode_mode = .ignore, .strip_components = 0 }); } test "case sensitivity" { + const io = testing.io; // Mimicking issue #18089, this tar contains, same file name in two case // sensitive name version. Should fail on case insensitive file systems. // @@ -495,7 +497,7 @@ test "case sensitivity" { var root = std.testing.tmpDir(.{}); defer root.cleanup(); - tar.pipeToFileSystem(root.dir, &r, .{ .mode_mode = .ignore, .strip_components = 1 }) catch |err| { + tar.pipeToFileSystem(io, root.dir, &r, .{ .mode_mode = .ignore, .strip_components = 1 }) catch |err| { // on case insensitive fs we fail on overwrite existing file try testing.expectEqual(error.PathAlreadyExists, err); return; diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 186cafad59599a5924050ed88f609c7dbfd08b38..63131d771c4f644603840ed2e7e60376ebc2bfeb 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -613,9 +613,9 @@ pub const TmpDir = struct { const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); pub fn cleanup(self: *TmpDir) void { - self.dir.close(); + self.dir.close(io); self.parent_dir.deleteTree(&self.sub_path) catch {}; - self.parent_dir.close(); + self.parent_dir.close(io); self.* = undefined; } }; @@ -629,7 +629,7 @@ pub fn tmpDir(opts: std.fs.Dir.OpenOptions) TmpDir { const cwd = std.fs.cwd(); var cache_dir = cwd.makeOpenPath(".zig-cache", .{}) catch @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); - defer cache_dir.close(); + defer cache_dir.close(io); const parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); const dir = parent_dir.makeOpenPath(&sub_path, opts) catch diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index 2ab4e48570cb5725453b16159efc29b1384a2af3..c8bde2ab02e227c26c3c35df7b2dbf8d6e325afc 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -1,4 +1,18 @@ //! See the render function implementation for documentation of the fields. +const LibCInstallation = @This(); + +const builtin = @import("builtin"); +const is_darwin = builtin.target.os.tag.isDarwin(); +const is_windows = builtin.target.os.tag == .windows; +const is_haiku = builtin.target.os.tag == .haiku; + +const std = @import("std"); +const Io = std.Io; +const Target = std.Target; +const fs = std.fs; +const Allocator = std.mem.Allocator; +const Path = std.Build.Cache.Path; +const log = std.log.scoped(.libc_installation); include_dir: ?[]const u8 = null, sys_include_dir: ?[]const u8 = null, @@ -157,6 +171,7 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void { pub const FindNativeOptions = struct { allocator: Allocator, + io: Io, target: *const std.Target, /// If enabled, will print human-friendly errors to stderr. @@ -165,29 +180,32 @@ pub const FindNativeOptions = struct { /// Finds the default, native libc. pub fn findNative(args: FindNativeOptions) FindError!LibCInstallation { + const gpa = args.allocator; + const io = args.io; + var self: LibCInstallation = .{}; if (is_darwin and args.target.os.tag.isDarwin()) { - if (!std.zig.system.darwin.isSdkInstalled(args.allocator)) + if (!std.zig.system.darwin.isSdkInstalled(gpa)) return error.DarwinSdkNotFound; - const sdk = std.zig.system.darwin.getSdk(args.allocator, args.target) orelse + const sdk = std.zig.system.darwin.getSdk(gpa, args.target) orelse return error.DarwinSdkNotFound; - defer args.allocator.free(sdk); + defer gpa.free(sdk); - self.include_dir = try fs.path.join(args.allocator, &.{ + self.include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include", }); - self.sys_include_dir = try fs.path.join(args.allocator, &.{ + self.sys_include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include", }); return self; } else if (is_windows) { - const sdk = std.zig.WindowsSdk.find(args.allocator, args.target.cpu.arch) catch |err| switch (err) { + const sdk = std.zig.WindowsSdk.find(gpa, io, args.target.cpu.arch) catch |err| switch (err) { error.NotFound => return error.WindowsSdkNotFound, error.PathTooLong => return error.WindowsSdkNotFound, error.OutOfMemory => return error.OutOfMemory, }; - defer sdk.free(args.allocator); + defer sdk.free(gpa); try self.findNativeMsvcIncludeDir(args, sdk); try self.findNativeMsvcLibDir(args, sdk); @@ -197,16 +215,16 @@ pub fn findNative(args: FindNativeOptions) FindError!LibCInstallation { } else if (is_haiku) { try self.findNativeIncludeDirPosix(args); try self.findNativeGccDirHaiku(args); - self.crt_dir = try args.allocator.dupeZ(u8, "/system/develop/lib"); + self.crt_dir = try gpa.dupeZ(u8, "/system/develop/lib"); } else if (builtin.target.os.tag == .illumos) { // There is only one libc, and its headers/libraries are always in the same spot. - self.include_dir = try args.allocator.dupeZ(u8, "/usr/include"); - self.sys_include_dir = try args.allocator.dupeZ(u8, "/usr/include"); - self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib/64"); + self.include_dir = try gpa.dupeZ(u8, "/usr/include"); + self.sys_include_dir = try gpa.dupeZ(u8, "/usr/include"); + self.crt_dir = try gpa.dupeZ(u8, "/usr/lib/64"); } else if (std.process.can_spawn) { try self.findNativeIncludeDirPosix(args); switch (builtin.target.os.tag) { - .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib"), + .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try gpa.dupeZ(u8, "/usr/lib"), .linux => try self.findNativeCrtDirPosix(args), else => {}, } @@ -229,6 +247,7 @@ pub fn deinit(self: *LibCInstallation, allocator: Allocator) void { fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void { const allocator = args.allocator; + const io = args.io; // Detect infinite loops. var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { @@ -326,7 +345,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F else => return error.FileSystem, }; - defer search_dir.close(); + defer search_dir.close(io); if (self.include_dir == null) { if (search_dir.access(include_dir_example_file, .{})) |_| { @@ -361,6 +380,7 @@ fn findNativeIncludeDirWindows( sdk: std.zig.WindowsSdk, ) FindError!void { const allocator = args.allocator; + const io = args.io; var install_buf: [2]std.zig.WindowsSdk.Installation = undefined; const installs = fillInstallations(&install_buf, sdk); @@ -380,7 +400,7 @@ fn findNativeIncludeDirWindows( else => return error.FileSystem, }; - defer dir.close(); + defer dir.close(io); dir.access("stdlib.h", .{}) catch |err| switch (err) { error.FileNotFound => continue, @@ -400,6 +420,7 @@ fn findNativeCrtDirWindows( sdk: std.zig.WindowsSdk, ) FindError!void { const allocator = args.allocator; + const io = args.io; var install_buf: [2]std.zig.WindowsSdk.Installation = undefined; const installs = fillInstallations(&install_buf, sdk); @@ -427,7 +448,7 @@ fn findNativeCrtDirWindows( else => return error.FileSystem, }; - defer dir.close(); + defer dir.close(io); dir.access("ucrt.lib", .{}) catch |err| switch (err) { error.FileNotFound => continue, @@ -467,6 +488,7 @@ fn findNativeKernel32LibDir( sdk: std.zig.WindowsSdk, ) FindError!void { const allocator = args.allocator; + const io = args.io; var install_buf: [2]std.zig.WindowsSdk.Installation = undefined; const installs = fillInstallations(&install_buf, sdk); @@ -494,7 +516,7 @@ fn findNativeKernel32LibDir( else => return error.FileSystem, }; - defer dir.close(); + defer dir.close(io); dir.access("kernel32.lib", .{}) catch |err| switch (err) { error.FileNotFound => continue, @@ -513,6 +535,7 @@ fn findNativeMsvcIncludeDir( sdk: std.zig.WindowsSdk, ) FindError!void { const allocator = args.allocator; + const io = args.io; const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCStdLibHeaderNotFound; const up1 = fs.path.dirname(msvc_lib_dir) orelse return error.LibCStdLibHeaderNotFound; @@ -529,7 +552,7 @@ fn findNativeMsvcIncludeDir( else => return error.FileSystem, }; - defer dir.close(); + defer dir.close(io); dir.access("vcruntime.h", .{}) catch |err| switch (err) { error.FileNotFound => return error.LibCStdLibHeaderNotFound, @@ -1015,17 +1038,3 @@ pub fn resolveCrtPaths( }, } } - -const LibCInstallation = @This(); -const std = @import("std"); -const builtin = @import("builtin"); -const Target = std.Target; -const fs = std.fs; -const Allocator = std.mem.Allocator; -const Path = std.Build.Cache.Path; - -const is_darwin = builtin.target.os.tag.isDarwin(); -const is_windows = builtin.target.os.tag == .windows; -const is_haiku = builtin.target.os.tag == .haiku; - -const log = std.log.scoped(.libc_installation); diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index 89d608633c7db98a7151e5f50d0e890d78af83dc..a7b16e1bed193818abd686bdad18eb8862a53196 100644 --- a/lib/std/zig/WindowsSdk.zig +++ b/lib/std/zig/WindowsSdk.zig @@ -1,7 +1,10 @@ const WindowsSdk = @This(); const builtin = @import("builtin"); + const std = @import("std"); +const Io = std.Io; const Writer = std.Io.Writer; +const Allocator = std.mem.Allocator; windows10sdk: ?Installation, windows81sdk: ?Installation, @@ -20,7 +23,7 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len /// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory. /// Caller owns the result's fields. /// After finishing work, call `free(allocator)`. -pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { +pub fn find(allocator: Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { if (builtin.os.tag != .windows) return error.NotFound; //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed @@ -58,7 +61,7 @@ pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutO }; } -pub fn free(sdk: WindowsSdk, allocator: std.mem.Allocator) void { +pub fn free(sdk: WindowsSdk, allocator: Allocator) void { if (sdk.windows10sdk) |*w10sdk| { w10sdk.free(allocator); } @@ -75,7 +78,7 @@ pub fn free(sdk: WindowsSdk, allocator: std.mem.Allocator) void { /// Caller owns result. fn iterateAndFilterByVersion( iterator: *std.fs.Dir.Iterator, - allocator: std.mem.Allocator, + allocator: Allocator, prefix: []const u8, ) error{OutOfMemory}![][]const u8 { const Version = struct { @@ -174,7 +177,7 @@ const RegistryWtf8 = struct { /// Get string from registry. /// Caller owns result. - pub fn getString(reg: RegistryWtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 { + pub fn getString(reg: RegistryWtf8, allocator: Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 { const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: { var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable; @@ -282,7 +285,7 @@ const RegistryWtf16Le = struct { } /// Get string ([:0]const u16) from registry. - 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 { + fn getString(reg: RegistryWtf16Le, allocator: Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 { var actual_type: windows.ULONG = undefined; // Calculating length to allocate @@ -416,7 +419,7 @@ pub const Installation = struct { /// Caller owns the result's fields. /// After finishing work, call `free(allocator)`. fn find( - allocator: std.mem.Allocator, + allocator: Allocator, roots_key: RegistryWtf8, roots_subkey: []const u8, prefix: []const u8, @@ -437,7 +440,8 @@ pub const Installation = struct { } fn findFromRoot( - allocator: std.mem.Allocator, + allocator: Allocator, + io: Io, roots_key: RegistryWtf8, roots_subkey: []const u8, prefix: []const u8, @@ -478,7 +482,7 @@ pub const Installation = struct { error.NameTooLong => return error.PathTooLong, else => return error.InstallationNotFound, }; - defer sdk_lib_dir.close(); + defer sdk_lib_dir.close(io); var iterator = sdk_lib_dir.iterate(); const versions = try iterateAndFilterByVersion(&iterator, allocator, prefix); @@ -495,7 +499,7 @@ pub const Installation = struct { } fn findFromInstallationFolder( - allocator: std.mem.Allocator, + allocator: Allocator, version_key_name: []const u8, ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined; @@ -597,14 +601,14 @@ pub const Installation = struct { return (reg_value == 1); } - fn free(install: Installation, allocator: std.mem.Allocator) void { + fn free(install: Installation, allocator: Allocator) void { allocator.free(install.path); allocator.free(install.version); } }; const MsvcLibDir = struct { - fn findInstancesDirViaSetup(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { + fn findInstancesDirViaSetup(allocator: Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { const vs_setup_key_path = "SOFTWARE\\Microsoft\\VisualStudio\\Setup"; const vs_setup_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, vs_setup_key_path, .{}) catch |err| switch (err) { error.KeyNotFound => return error.PathNotFound, @@ -629,7 +633,7 @@ const MsvcLibDir = struct { return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return error.PathNotFound; } - fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { + fn findInstancesDirViaCLSID(allocator: Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}"; const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid, .{}) catch |err| switch (err) { error.KeyNotFound => return error.PathNotFound, @@ -665,7 +669,7 @@ const MsvcLibDir = struct { return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return error.PathNotFound; } - fn findInstancesDir(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { + fn findInstancesDir(allocator: Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { // First, try getting the packages cache path from the registry. // This only seems to exist when the path is different from the default. method1: { @@ -748,13 +752,13 @@ const MsvcLibDir = struct { /// /// The logic in this function is intended to match what ISetupConfiguration does /// under-the-hood, as verified using Procmon. - fn findViaCOM(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { + fn findViaCOM(allocator: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances` // This will contain directories with names of instance IDs like 80a758ca, // which will contain `state.json` files that have the version and // installation directory. var instances_dir = try findInstancesDir(allocator); - defer instances_dir.close(); + defer instances_dir.close(io); var state_subpath_buf: [std.fs.max_name_bytes + 32]u8 = undefined; var latest_version_lib_dir: std.ArrayList(u8) = .empty; @@ -791,7 +795,7 @@ const MsvcLibDir = struct { const installation_path = parsed.value.object.get("installationPath") orelse continue; if (installation_path != .string) continue; - const lib_dir_path = libDirFromInstallationPath(allocator, installation_path.string, arch) catch |err| switch (err) { + const lib_dir_path = libDirFromInstallationPath(allocator, io, installation_path.string, arch) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.PathNotFound => continue, }; @@ -806,7 +810,12 @@ const MsvcLibDir = struct { return latest_version_lib_dir.toOwnedSlice(allocator); } - fn libDirFromInstallationPath(allocator: std.mem.Allocator, installation_path: []const u8, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { + fn libDirFromInstallationPath( + allocator: Allocator, + io: Io, + installation_path: []const u8, + arch: std.Target.Cpu.Arch, + ) error{ OutOfMemory, PathNotFound }![]const u8 { var lib_dir_buf = try std.array_list.Managed(u8).initCapacity(allocator, installation_path.len + 64); errdefer lib_dir_buf.deinit(); @@ -837,7 +846,7 @@ const MsvcLibDir = struct { else => unreachable, }); - if (!verifyLibDir(lib_dir_buf.items)) { + if (!verifyLibDir(io, lib_dir_buf.items)) { return error.PathNotFound; } @@ -845,7 +854,7 @@ const MsvcLibDir = struct { } // 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 - fn findViaRegistry(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { + fn findViaRegistry(allocator: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { // %localappdata%\Microsoft\VisualStudio\ // %appdata%\Local\Microsoft\VisualStudio\ @@ -859,7 +868,7 @@ const MsvcLibDir = struct { var visualstudio_folder = std.fs.openDirAbsolute(visualstudio_folder_path, .{ .iterate = true, }) catch return error.PathNotFound; - defer visualstudio_folder.close(); + defer visualstudio_folder.close(io); var iterator = visualstudio_folder.iterate(); break :vs_versions try iterateAndFilterByVersion(&iterator, allocator, ""); @@ -926,14 +935,14 @@ const MsvcLibDir = struct { }; errdefer allocator.free(msvc_dir); - if (!verifyLibDir(msvc_dir)) { + if (!verifyLibDir(io, msvc_dir)) { return error.PathNotFound; } return msvc_dir; } - fn findViaVs7Key(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { + fn findViaVs7Key(allocator: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { var base_path: std.array_list.Managed(u8) = base_path: { try_env: { var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { @@ -989,7 +998,7 @@ const MsvcLibDir = struct { else => unreachable, }); - if (!verifyLibDir(base_path.items)) { + if (!verifyLibDir(io, base_path.items)) { return error.PathNotFound; } @@ -997,11 +1006,11 @@ const MsvcLibDir = struct { return full_path; } - fn verifyLibDir(lib_dir_path: []const u8) bool { + fn verifyLibDir(io: Io, lib_dir_path: []const u8) bool { std.debug.assert(std.fs.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*` var dir = std.fs.openDirAbsolute(lib_dir_path, .{}) catch return false; - defer dir.close(); + defer dir.close(io); const stat = dir.statFile("vcruntime.lib") catch return false; if (stat.kind != .file) @@ -1012,12 +1021,12 @@ const MsvcLibDir = struct { /// Find path to MSVC's `lib/` directory. /// Caller owns the result. - pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { - const full_path = MsvcLibDir.findViaCOM(allocator, arch) catch |err1| switch (err1) { + pub fn find(allocator: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { + const full_path = MsvcLibDir.findViaCOM(allocator, io, arch) catch |err1| switch (err1) { error.OutOfMemory => return error.OutOfMemory, - error.PathNotFound => MsvcLibDir.findViaRegistry(allocator, arch) catch |err2| switch (err2) { + error.PathNotFound => MsvcLibDir.findViaRegistry(allocator, io, arch) catch |err2| switch (err2) { error.OutOfMemory => return error.OutOfMemory, - error.PathNotFound => MsvcLibDir.findViaVs7Key(allocator, arch) catch |err3| switch (err3) { + error.PathNotFound => MsvcLibDir.findViaVs7Key(allocator, io, arch) catch |err3| switch (err3) { error.OutOfMemory => return error.OutOfMemory, error.PathNotFound => return error.MsvcLibDirNotFound, }, diff --git a/lib/std/zig/llvm/Builder.zig b/lib/std/zig/llvm/Builder.zig index 587ac82c70ff11928c0c3f37bd47730801da1493..9a52ae2c81c797d71c97f613d3c6225887631b68 100644 --- a/lib/std/zig/llvm/Builder.zig +++ b/lib/std/zig/llvm/Builder.zig @@ -1,14 +1,17 @@ +const builtin = @import("builtin"); +const Builder = @This(); + const std = @import("../../std.zig"); +const Io = std.Io; const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const bitcode_writer = @import("bitcode_writer.zig"); -const Builder = @This(); -const builtin = @import("builtin"); const DW = std.dwarf; -const ir = @import("ir.zig"); const log = std.log.scoped(.llvm); const Writer = std.Io.Writer; +const bitcode_writer = @import("bitcode_writer.zig"); +const ir = @import("ir.zig"); + gpa: Allocator, strip: bool, @@ -9579,11 +9582,11 @@ pub fn dump(b: *Builder) void { b.printToFile(stderr, &buffer) catch {}; } -pub fn printToFilePath(b: *Builder, dir: std.fs.Dir, path: []const u8) !void { +pub fn printToFilePath(b: *Builder, io: Io, dir: std.fs.Dir, path: []const u8) !void { var buffer: [4000]u8 = undefined; - const file = try dir.createFile(path, .{}); - defer file.close(); - try b.printToFile(file, &buffer); + const file = try dir.createFile(io, path, .{}); + defer file.close(io); + try b.printToFile(io, file, &buffer); } pub fn printToFile(b: *Builder, file: std.fs.File, buffer: []u8) !void { diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 290cd8d38892002945315c5c459eec1772a5ab76..4f0c11797d7965feb64205e9dfe179b6cf732141 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -847,7 +847,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { error.Unexpected => |e| return e, error.Canceled => |e| return e, }; - defer file.close(); + defer file.close(io); // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system. var buffer: [8000]u8 = undefined; @@ -1051,7 +1051,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ else => |e| return e, }; var is_elf_file = false; - defer if (!is_elf_file) file.close(); + defer if (!is_elf_file) file.close(io); file_reader = .initAdapted(file, io, &file_reader_buffer); file_name = undefined; // it aliases file_reader_buffer diff --git a/lib/std/zig/system/linux.zig b/lib/std/zig/system/linux.zig index 6b4f0cf6f9e2ef3f3abf078578b50e44a03130f5..668e5a1d9989797da0714deb60853d1d522ae421 100644 --- a/lib/std/zig/system/linux.zig +++ b/lib/std/zig/system/linux.zig @@ -447,7 +447,7 @@ pub fn detectNativeCpuAndFeatures(io: Io) ?Target.Cpu { var file = fs.openFileAbsolute("/proc/cpuinfo", .{}) catch |err| switch (err) { else => return null, }; - defer file.close(); + defer file.close(io); var buffer: [4096]u8 = undefined; // "flags" lines can get pretty long. var file_reader = file.reader(io, &buffer); diff --git a/lib/std/zip.zig b/lib/std/zip.zig index 583377e00adf0c41a3f3cf24bf3c1c38f850e7b4..c2dbaf5b810b594b6159c207537bf160cfd34169 100644 --- a/lib/std/zip.zig +++ b/lib/std/zip.zig @@ -554,17 +554,19 @@ pub const Iterator = struct { return; } + const io = stream.io; + const out_file = blk: { if (std.fs.path.dirname(filename)) |dirname| { var parent_dir = try dest.makeOpenPath(dirname, .{}); - defer parent_dir.close(); + defer parent_dir.close(io); const basename = std.fs.path.basename(filename); break :blk try parent_dir.createFile(basename, .{ .exclusive = true }); } break :blk try dest.createFile(filename, .{ .exclusive = true }); }; - defer out_file.close(); + defer out_file.close(io); var out_file_buffer: [1024]u8 = undefined; var file_writer = out_file.writer(&out_file_buffer); const local_data_file_offset: u64 = diff --git a/src/Compilation.zig b/src/Compilation.zig index 931a0b2d142c76b59ab3452ebbc1e170613317b5..df64dee19fbfae1540a91bbd7af5b3bfd8e7bde2 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -721,13 +721,13 @@ pub const Directories = struct { /// This may be the same as `global_cache`. local_cache: Cache.Directory, - pub fn deinit(dirs: *Directories) void { + pub fn deinit(dirs: *Directories, io: Io) void { // The local and global caches could be the same. const close_local = dirs.local_cache.handle.fd != dirs.global_cache.handle.fd; - dirs.global_cache.handle.close(); - if (close_local) dirs.local_cache.handle.close(); - dirs.zig_lib.handle.close(); + dirs.global_cache.handle.close(io); + if (close_local) dirs.local_cache.handle.close(io); + dirs.zig_lib.handle.close(io); } /// Returns a `Directories` where `local_cache` is replaced with `global_cache`, intended for @@ -1105,7 +1105,7 @@ pub const CObject = struct { if (diag.src_loc.offset == 0 or diag.src_loc.column == 0) break :source_line 0; const file = fs.cwd().openFile(file_name, .{}) catch break :source_line 0; - defer file.close(); + defer file.close(io); var buffer: [1024]u8 = undefined; var file_reader = file.reader(io, &buffer); file_reader.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0; @@ -1180,7 +1180,7 @@ pub const CObject = struct { var buffer: [1024]u8 = undefined; const file = try fs.cwd().openFile(path, .{}); - defer file.close(); + defer file.close(io); var file_reader = file.reader(io, &buffer); var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = &file_reader.interface }); defer bc.deinit(); @@ -1617,13 +1617,13 @@ const CacheUse = union(CacheMode) { } }; - fn deinit(cu: CacheUse) void { + fn deinit(cu: CacheUse, io: Io) void { switch (cu) { .none => |none| { assert(none.tmp_artifact_directory == null); }, .incremental => |incremental| { - incremental.artifact_directory.handle.close(); + incremental.artifact_directory.handle.close(io); }, .whole => |whole| { assert(whole.tmp_artifact_directory == null); @@ -2113,7 +2113,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, cache.addPrefix(options.dirs.zig_lib); cache.addPrefix(options.dirs.local_cache); cache.addPrefix(options.dirs.global_cache); - errdefer cache.manifest_dir.close(); + errdefer cache.manifest_dir.close(io); // This is shared hasher state common to zig source and all C source files. cache.hash.addBytes(build_options.version); @@ -2157,7 +2157,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, var local_zir_dir = options.dirs.local_cache.handle.makeOpenPath(zir_sub_dir, .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = zir_sub_dir, .err = err } }); }; - errdefer local_zir_dir.close(); + errdefer local_zir_dir.close(io); const local_zir_cache: Cache.Directory = .{ .handle = local_zir_dir, .path = try options.dirs.local_cache.join(arena, &.{zir_sub_dir}), @@ -2165,7 +2165,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, var global_zir_dir = options.dirs.global_cache.handle.makeOpenPath(zir_sub_dir, .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .global, .sub = zir_sub_dir, .err = err } }); }; - errdefer global_zir_dir.close(); + errdefer global_zir_dir.close(io); const global_zir_cache: Cache.Directory = .{ .handle = global_zir_dir, .path = try options.dirs.global_cache.join(arena, &.{zir_sub_dir}), @@ -2436,7 +2436,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, var artifact_dir = options.dirs.local_cache.handle.makeOpenPath(artifact_sub_dir, .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = artifact_sub_dir, .err = err } }); }; - errdefer artifact_dir.close(); + errdefer artifact_dir.close(io); const artifact_directory: Cache.Directory = .{ .handle = artifact_dir, .path = try options.dirs.local_cache.join(arena, &.{artifact_sub_dir}), @@ -2689,6 +2689,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, pub fn destroy(comp: *Compilation) void { const gpa = comp.gpa; + const io = comp.io; if (comp.bin_file) |lf| lf.destroy(); if (comp.zcu) |zcu| zcu.deinit(); @@ -2760,7 +2761,7 @@ pub fn destroy(comp: *Compilation) void { comp.clearMiscFailures(); - comp.cache_parent.manifest_dir.close(); + comp.cache_parent.manifest_dir.close(io); } pub fn clearMiscFailures(comp: *Compilation) void { @@ -2791,10 +2792,12 @@ pub fn hotCodeSwap( } fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { + const io = comp.io; + switch (comp.cache_use) { .none => |none| { if (none.tmp_artifact_directory) |*tmp_dir| { - tmp_dir.handle.close(); + tmp_dir.handle.close(io); none.tmp_artifact_directory = null; if (dev.env == .bootstrap) { // zig1 uses `CacheMode.none`, but it doesn't need to know how to delete @@ -2834,7 +2837,7 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { comp.bin_file = null; } if (whole.tmp_artifact_directory) |*tmp_dir| { - tmp_dir.handle.close(); + tmp_dir.handle.close(io); whole.tmp_artifact_directory = null; const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| { @@ -3152,7 +3155,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE // the file handle and re-open it in the follow up call to // `makeWritable`. if (lf.file) |f| { - f.close(); + f.close(io); lf.file = null; if (lf.closeDebugInfo()) break :w .lf_and_debug; @@ -3165,7 +3168,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE // Rename the temporary directory into place. // Close tmp dir and link.File to avoid open handle during rename. - whole.tmp_artifact_directory.?.handle.close(); + whole.tmp_artifact_directory.?.handle.close(io); whole.tmp_artifact_directory = null; const s = fs.path.sep_str; const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); @@ -5258,6 +5261,7 @@ fn workerDocsCopy(comp: *Compilation) void { fn docsCopyFallible(comp: *Compilation) anyerror!void { const zcu = comp.zcu orelse return comp.lockAndSetMiscFailure(.docs_copy, "no Zig code to document", .{}); + const io = comp.io; const docs_path = comp.resolveEmitPath(comp.emit_docs.?); var out_dir = docs_path.root_dir.handle.makeOpenPath(docs_path.sub_path, .{}) catch |err| { @@ -5267,7 +5271,7 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { .{ docs_path, @errorName(err) }, ); }; - defer out_dir.close(); + defer out_dir.close(io); for (&[_][]const u8{ "docs/main.js", "docs/index.html" }) |sub_path| { const basename = fs.path.basename(sub_path); @@ -5287,7 +5291,7 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { .{ docs_path, @errorName(err) }, ); }; - defer tar_file.close(); + defer tar_file.close(io); var buffer: [1024]u8 = undefined; var tar_file_writer = tar_file.writer(&buffer); @@ -5331,7 +5335,7 @@ fn docsCopyModule( } catch |err| { return comp.lockAndSetMiscFailure(.docs_copy, "unable to open directory '{f}': {t}", .{ root.fmt(comp), err }); }; - defer mod_dir.close(); + defer mod_dir.close(io); var walker = try mod_dir.walk(comp.gpa); defer walker.deinit(); @@ -5355,7 +5359,7 @@ fn docsCopyModule( root.fmt(comp), entry.path, err, }); }; - defer file.close(); + defer file.close(io); const stat = try file.stat(); var file_reader: fs.File.Reader = .initSize(file.adaptToNewApi(), io, &buffer, stat.size); @@ -5510,7 +5514,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU ); return error.AlreadyReported; }; - defer out_dir.close(); + defer out_dir.close(io); crt_file.full_object_path.root_dir.handle.copyFile( crt_file.full_object_path.sub_path, @@ -5693,7 +5697,7 @@ pub fn translateC( const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename; const cache_dir = comp.dirs.local_cache.handle; var cache_tmp_dir = try cache_dir.makeOpenPath(tmp_sub_path, .{}); - defer cache_tmp_dir.close(); + defer cache_tmp_dir.close(io); const translated_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, translated_basename }); const source_path = switch (source) { @@ -6268,7 +6272,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // so we need a temporary filename. const out_obj_path = try comp.tmpFilePath(arena, o_basename); var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{}); - defer zig_cache_tmp_dir.close(); + defer zig_cache_tmp_dir.close(io); const out_diag_path = if (comp.clang_passthrough_mode or !ext.clangSupportsDiagnostics()) null @@ -6433,7 +6437,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr const digest = man.final(); const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest }); var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); - defer o_dir.close(); + defer o_dir.close(io); const tmp_basename = fs.path.basename(out_obj_path); try fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename); break :blk digest; @@ -6477,8 +6481,6 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 const tracy_trace = trace(@src()); defer tracy_trace.end(); - const io = comp.io; - const src_path = switch (win32_resource.src) { .rc => |rc_src| rc_src.src_path, .manifest => |src_path| src_path, @@ -6487,6 +6489,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 log.debug("updating win32 resource: {s}", .{src_path}); + const io = comp.io; + var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); @@ -6522,7 +6526,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 const o_sub_path = try fs.path.join(arena, &.{ "o", &digest }); var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); - defer o_dir.close(); + defer o_dir.close(io); const in_rc_path = try comp.dirs.local_cache.join(comp.gpa, &.{ o_sub_path, rc_basename, @@ -6610,7 +6614,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 const digest = if (try man.hit()) man.final() else blk: { var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{}); - defer zig_cache_tmp_dir.close(); + defer zig_cache_tmp_dir.close(io); const res_filename = try std.fmt.allocPrint(arena, "{s}.res", .{rc_basename_noext}); @@ -6681,7 +6685,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 const digest = man.final(); const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest }); var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); - defer o_dir.close(); + defer o_dir.close(io); const tmp_basename = fs.path.basename(out_res_path); try fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename); break :blk digest; diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 9b7c83cc391546c6f27f72a6eb9288f2266af306..58f970abe5444a071efaad57aa308a54dfa25407 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -513,7 +513,7 @@ fn runResource( break :handle dir; }, }; - defer tmp_directory.handle.close(); + defer tmp_directory.handle.close(io); // Fetch and unpack a resource into a temporary directory. var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory); @@ -523,7 +523,7 @@ fn runResource( // Apply btrfs workaround if needed. Reopen tmp_directory. if (native_os == .linux and f.job_queue.work_around_btrfs_bug) { // https://github.com/ziglang/zig/issues/17095 - pkg_path.root_dir.handle.close(); + pkg_path.root_dir.handle.close(io); pkg_path.root_dir.handle = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{ .iterate = true, }) catch @panic("btrfs workaround failed"); @@ -885,7 +885,7 @@ const Resource = union(enum) { file: fs.File.Reader, http_request: HttpRequest, git: Git, - dir: fs.Dir, + dir: Io.Dir, const Git = struct { session: git.Session, @@ -908,7 +908,7 @@ const Resource = union(enum) { .git => |*git_resource| { git_resource.fetch_stream.deinit(); }, - .dir => |*dir| dir.close(), + .dir => |*dir| dir.close(io), } resource.* = undefined; } @@ -1247,13 +1247,14 @@ fn unpackResource( } } -fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) RunError!UnpackResult { +fn unpackTarball(f: *Fetch, out_dir: Io.Dir, reader: *Io.Reader) RunError!UnpackResult { const eb = &f.error_bundle; const arena = f.arena.allocator(); + const io = f.job_queue.io; var diagnostics: std.tar.Diagnostics = .{ .allocator = arena }; - std.tar.pipeToFileSystem(out_dir, reader, .{ + std.tar.pipeToFileSystem(io, out_dir, reader, .{ .diagnostics = &diagnostics, .strip_components = 0, .mode_mode = .ignore, @@ -1280,7 +1281,7 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) RunError!Unpack fn unzip( f: *Fetch, - out_dir: fs.Dir, + out_dir: Io.Dir, reader: *Io.Reader, ) error{ ReadFailed, OutOfMemory, Canceled, FetchFailed }!UnpackResult { // We write the entire contents to a file first because zip files @@ -1314,7 +1315,7 @@ fn unzip( ), }; }; - defer zip_file.close(); + defer zip_file.close(io); var zip_file_buffer: [4096]u8 = undefined; var zip_file_reader = b: { var zip_file_writer = zip_file.writer(&zip_file_buffer); @@ -1349,7 +1350,7 @@ fn unzip( return .{ .root_dir = diagnostics.root_dir }; } -fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!UnpackResult { +fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!UnpackResult { const io = f.job_queue.io; const arena = f.arena.allocator(); // TODO don't try to get a gpa from an arena. expose this dependency higher up @@ -1363,9 +1364,9 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U // directory, since that isn't relevant for fetching a package. { var pack_dir = try out_dir.makeOpenPath(".git", .{}); - defer pack_dir.close(); + defer pack_dir.close(io); var pack_file = try pack_dir.createFile("pkg.pack", .{ .read = true }); - defer pack_file.close(); + defer pack_file.close(io); var pack_file_buffer: [4096]u8 = undefined; var pack_file_reader = b: { var pack_file_writer = pack_file.writer(&pack_file_buffer); @@ -1376,7 +1377,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U }; var index_file = try pack_dir.createFile("pkg.idx", .{ .read = true }); - defer index_file.close(); + defer index_file.close(io); var index_file_buffer: [2000]u8 = undefined; var index_file_writer = index_file.writer(&index_file_buffer); { @@ -1393,7 +1394,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U try repository.init(gpa, object_format, &pack_file_reader, &index_file_reader); defer repository.deinit(); var diagnostics: git.Diagnostics = .{ .allocator = arena }; - try repository.checkout(out_dir, resource.want_oid, &diagnostics); + try repository.checkout(io, out_dir, resource.want_oid, &diagnostics); if (diagnostics.errors.items.len > 0) { try res.allocErrors(arena, diagnostics.errors.items.len, "unable to unpack packfile"); @@ -1411,7 +1412,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U return res; } -fn recursiveDirectoryCopy(f: *Fetch, dir: fs.Dir, tmp_dir: fs.Dir) anyerror!void { +fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void { const gpa = f.arena.child_allocator; // Recursive directory copy. var it = try dir.walk(gpa); @@ -1451,7 +1452,7 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: fs.Dir, tmp_dir: fs.Dir) anyerror!void } } -pub fn renameTmpIntoCache(cache_dir: fs.Dir, tmp_dir_sub_path: []const u8, dest_dir_sub_path: []const u8) !void { +pub fn renameTmpIntoCache(cache_dir: Io.Dir, tmp_dir_sub_path: []const u8, dest_dir_sub_path: []const u8) !void { assert(dest_dir_sub_path[1] == fs.path.sep); var handled_missing_dir = false; while (true) { @@ -1660,15 +1661,15 @@ fn dumpHashInfo(all_files: []const *const HashedFile) !void { try w.flush(); } -fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile) void { +fn workerHashFile(dir: Io.Dir, hashed_file: *HashedFile) void { hashed_file.failure = hashFileFallible(dir, hashed_file); } -fn workerDeleteFile(dir: fs.Dir, deleted_file: *DeletedFile) void { +fn workerDeleteFile(dir: Io.Dir, deleted_file: *DeletedFile) void { deleted_file.failure = deleteFileFallible(dir, deleted_file); } -fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void { +fn hashFileFallible(io: Io, dir: Io.Dir, hashed_file: *HashedFile) HashedFile.Error!void { var buf: [8000]u8 = undefined; var hasher = Package.Hash.Algo.init(.{}); hasher.update(hashed_file.normalized_path); @@ -1677,7 +1678,7 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void switch (hashed_file.kind) { .file => { var file = try dir.openFile(hashed_file.fs_path, .{}); - defer file.close(); + defer file.close(io); // Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463 hasher.update(&.{ 0, 0 }); var file_header: FileHeader = .{}; @@ -1707,7 +1708,7 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void hashed_file.size = file_size; } -fn deleteFileFallible(dir: fs.Dir, deleted_file: *DeletedFile) DeletedFile.Error!void { +fn deleteFileFallible(dir: Io.Dir, deleted_file: *DeletedFile) DeletedFile.Error!void { try dir.deleteFile(deleted_file.fs_path); } @@ -1724,8 +1725,8 @@ const DeletedFile = struct { failure: Error!void, const Error = - fs.Dir.DeleteFileError || - fs.Dir.DeleteDirError; + Io.Dir.DeleteFileError || + Io.Dir.DeleteDirError; }; const HashedFile = struct { @@ -1741,7 +1742,7 @@ const HashedFile = struct { fs.File.ReadError || fs.File.StatError || fs.File.ChmodError || - fs.Dir.ReadLinkError; + Io.Dir.ReadLinkError; const Kind = enum { file, link }; @@ -2074,7 +2075,7 @@ test "tarball with duplicate paths" { defer tmp.cleanup(); const tarball_name = "duplicate_paths.tar.gz"; - try saveEmbedFile(tarball_name, tmp.dir); + try saveEmbedFile(io, tarball_name, tmp.dir); const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); defer gpa.free(tarball_path); @@ -2107,7 +2108,7 @@ test "tarball with excluded duplicate paths" { defer tmp.cleanup(); const tarball_name = "duplicate_paths_excluded.tar.gz"; - try saveEmbedFile(tarball_name, tmp.dir); + try saveEmbedFile(io, tarball_name, tmp.dir); const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); defer gpa.free(tarball_path); @@ -2153,7 +2154,7 @@ test "tarball without root folder" { defer tmp.cleanup(); const tarball_name = "no_root.tar.gz"; - try saveEmbedFile(tarball_name, tmp.dir); + try saveEmbedFile(io, tarball_name, tmp.dir); const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); defer gpa.free(tarball_path); @@ -2186,7 +2187,7 @@ test "set executable bit based on file content" { defer tmp.cleanup(); const tarball_name = "executables.tar.gz"; - try saveEmbedFile(tarball_name, tmp.dir); + try saveEmbedFile(io, tarball_name, tmp.dir); const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); defer gpa.free(tarball_path); @@ -2210,7 +2211,7 @@ test "set executable bit based on file content" { ); var out = try fb.packageDir(); - defer out.close(); + defer out.close(io); const S = std.posix.S; // expect executable bit not set try std.testing.expect((try out.statFile("file1")).mode & S.IXUSR == 0); @@ -2231,11 +2232,11 @@ test "set executable bit based on file content" { // -rwxrwxr-x 1 17 Apr script_with_shebang_without_exec_bit } -fn saveEmbedFile(comptime tarball_name: []const u8, dir: fs.Dir) !void { +fn saveEmbedFile(io: Io, comptime tarball_name: []const u8, dir: Io.Dir) !void { //const tarball_name = "duplicate_paths_excluded.tar.gz"; const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name); var tmp_file = try dir.createFile(tarball_name, .{}); - defer tmp_file.close(); + defer tmp_file.close(io); try tmp_file.writeAll(tarball_content); } @@ -2250,7 +2251,7 @@ const TestFetchBuilder = struct { self: *TestFetchBuilder, allocator: std.mem.Allocator, io: Io, - cache_parent_dir: std.fs.Dir, + cache_parent_dir: std.Io.Dir, path_or_url: []const u8, ) !*Fetch { const cache_dir = try cache_parent_dir.makeOpenPath("zig-global-cache", .{}); @@ -2301,14 +2302,15 @@ const TestFetchBuilder = struct { } fn deinit(self: *TestFetchBuilder) void { + const io = self.job_queue.io; self.fetch.deinit(); self.job_queue.deinit(); self.fetch.prog_node.end(); - self.global_cache_directory.handle.close(); + self.global_cache_directory.handle.close(io); self.http_client.deinit(); } - fn packageDir(self: *TestFetchBuilder) !fs.Dir { + fn packageDir(self: *TestFetchBuilder) !Io.Dir { const root = self.fetch.package_root; return try root.root_dir.handle.openDir(root.sub_path, .{ .iterate = true }); } @@ -2316,8 +2318,10 @@ const TestFetchBuilder = struct { // Test helper, asserts thet package dir constains expected_files. // expected_files must be sorted. fn expectPackageFiles(self: *TestFetchBuilder, expected_files: []const []const u8) !void { + const io = self.job_queue.io; + var package_dir = try self.packageDir(); - defer package_dir.close(); + defer package_dir.close(io); var actual_files: std.ArrayList([]u8) = .empty; defer actual_files.deinit(std.testing.allocator); diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index a2ea870c3f9ff0295f6055fe69ef196de58b8fb5..864865bd1939da867ccb424b54c65c4279303e6b 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -213,6 +213,7 @@ pub const Repository = struct { /// Checks out the repository at `commit_oid` to `worktree`. pub fn checkout( repository: *Repository, + io: Io, worktree: std.fs.Dir, commit_oid: Oid, diagnostics: *Diagnostics, @@ -223,12 +224,13 @@ pub const Repository = struct { if (commit_object.type != .commit) return error.NotACommit; break :tree_oid try getCommitTree(repository.odb.format, commit_object.data); }; - try repository.checkoutTree(worktree, tree_oid, "", diagnostics); + try repository.checkoutTree(io, worktree, tree_oid, "", diagnostics); } /// Checks out the tree at `tree_oid` to `worktree`. fn checkoutTree( repository: *Repository, + io: Io, dir: std.fs.Dir, tree_oid: Oid, current_path: []const u8, @@ -253,10 +255,10 @@ pub const Repository = struct { .directory => { try dir.makeDir(entry.name); var subdir = try dir.openDir(entry.name, .{}); - defer subdir.close(); + defer subdir.close(io); const sub_path = try std.fs.path.join(repository.odb.allocator, &.{ current_path, entry.name }); defer repository.odb.allocator.free(sub_path); - try repository.checkoutTree(subdir, entry.oid, sub_path, diagnostics); + try repository.checkoutTree(io, subdir, entry.oid, sub_path, diagnostics); }, .file => { try repository.odb.seekOid(entry.oid); @@ -271,7 +273,7 @@ pub const Repository = struct { } }); continue; }; - defer file.close(); + defer file.close(io); try file.writeAll(file_object.data); }, .symlink => { @@ -1583,14 +1585,14 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u var git_dir = testing.tmpDir(.{}); defer git_dir.cleanup(); var pack_file = try git_dir.dir.createFile("testrepo.pack", .{ .read = true }); - defer pack_file.close(); + defer pack_file.close(io); try pack_file.writeAll(testrepo_pack); var pack_file_buffer: [2000]u8 = undefined; var pack_file_reader = pack_file.reader(io, &pack_file_buffer); var index_file = try git_dir.dir.createFile("testrepo.idx", .{ .read = true }); - defer index_file.close(); + defer index_file.close(io); var index_file_buffer: [2000]u8 = undefined; var index_file_writer = index_file.writer(&index_file_buffer); try indexPack(testing.allocator, format, &pack_file_reader, &index_file_writer); @@ -1621,7 +1623,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); - try repository.checkout(worktree.dir, commit_id, &diagnostics); + try repository.checkout(io, worktree.dir, commit_id, &diagnostics); try testing.expect(diagnostics.errors.items.len == 0); const expected_files: []const []const u8 = &.{ @@ -1713,20 +1715,20 @@ pub fn main() !void { const format = std.meta.stringToEnum(Oid.Format, args[1]) orelse return error.InvalidFormat; var pack_file = try std.fs.cwd().openFile(args[2], .{}); - defer pack_file.close(); + defer pack_file.close(io); var pack_file_buffer: [4096]u8 = undefined; var pack_file_reader = pack_file.reader(io, &pack_file_buffer); const commit = try Oid.parse(format, args[3]); var worktree = try std.fs.cwd().makeOpenPath(args[4], .{}); - defer worktree.close(); + defer worktree.close(io); var git_dir = try worktree.makeOpenPath(".git", .{}); - defer git_dir.close(); + defer git_dir.close(io); std.debug.print("Starting index...\n", .{}); var index_file = try git_dir.createFile("idx", .{ .read = true }); - defer index_file.close(); + defer index_file.close(io); var index_file_buffer: [4096]u8 = undefined; var index_file_writer = index_file.writer(&index_file_buffer); try indexPack(allocator, format, &pack_file_reader, &index_file_writer); @@ -1738,7 +1740,7 @@ pub fn main() !void { defer repository.deinit(); var diagnostics: Diagnostics = .{ .allocator = allocator }; defer diagnostics.deinit(); - try repository.checkout(worktree, commit, &diagnostics); + try repository.checkout(io, worktree, commit, &diagnostics); for (diagnostics.errors.items) |err| { std.debug.print("Diagnostic: {}\n", .{err}); diff --git a/src/Zcu.zig b/src/Zcu.zig index 137b4d8b5990dae75c3664c792ffc22f5fdf92b7..58d884afe3aebebf11a264ee2469a594ce851bd1 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -1078,7 +1078,7 @@ pub const File = struct { const dir, const sub_path = file.path.openInfo(zcu.comp.dirs); break :f try dir.openFile(sub_path, .{}); }; - defer f.close(); + defer f.close(io); const stat = f.stat() catch |err| switch (err) { error.Streaming => { @@ -2813,8 +2813,8 @@ pub fn init(zcu: *Zcu, gpa: Allocator, io: Io, thread_count: usize) !void { pub fn deinit(zcu: *Zcu) void { const comp = zcu.comp; - const gpa = comp.gpa; const io = comp.io; + const gpa = zcu.gpa; { const pt: Zcu.PerThread = .activate(zcu, .main); defer pt.deactivate(); @@ -2835,8 +2835,8 @@ pub fn deinit(zcu: *Zcu) void { } zcu.embed_table.deinit(gpa); - zcu.local_zir_cache.handle.close(); - zcu.global_zir_cache.handle.close(); + zcu.local_zir_cache.handle.close(io); + zcu.global_zir_cache.handle.close(io); for (zcu.failed_analysis.values()) |value| value.destroy(gpa); for (zcu.failed_codegen.values()) |value| value.destroy(gpa); @@ -2900,7 +2900,7 @@ pub fn deinit(zcu: *Zcu) void { if (zcu.resolved_references) |*r| r.deinit(gpa); - if (zcu.comp.debugIncremental()) { + if (comp.debugIncremental()) { zcu.incremental_debug_state.deinit(gpa); } } diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 2ad5bac01ce442c3341d79abb5ed0564e3e65eff..d2ca004058f5dcebd3ef389319baf080baa5d484 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -96,7 +96,7 @@ pub fn updateFile( const dir, const sub_path = file.path.openInfo(comp.dirs); break :f try dir.openFile(sub_path, .{}); }; - defer source_file.close(); + defer source_file.close(io); const stat = try source_file.stat(); @@ -215,7 +215,7 @@ pub fn updateFile( else => |e| return e, // Retryable errors are handled at callsite. }; }; - defer cache_file.close(); + defer cache_file.close(io); // Under `--time-report`, ignore cache hits; do the work anyway for those juicy numbers. const ignore_hit = comp.time_report != null; @@ -2468,7 +2468,7 @@ fn updateEmbedFileInner( const dir, const sub_path = ef.path.openInfo(zcu.comp.dirs); break :f try dir.openFile(sub_path, .{}); }; - defer file.close(); + defer file.close(io); const stat: Cache.File.Stat = .fromFs(try file.stat()); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 5dc55b74f6709ed5bd304f0a92dc5bbd6263ca7a..cb4fe0459f70b511fb04dbd501d5bd48e04936f0 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -799,6 +799,7 @@ pub const Object = struct { pub fn emit(o: *Object, pt: Zcu.PerThread, options: EmitOptions) error{ LinkFailure, OutOfMemory }!void { const zcu = pt.zcu; const comp = zcu.comp; + const io = comp.io; const diags = &comp.link_diags; { @@ -979,7 +980,7 @@ pub const Object = struct { if (options.pre_bc_path) |path| { var file = std.fs.cwd().createFile(path, .{}) catch |err| return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) }); - defer file.close(); + defer file.close(io); const ptr: [*]const u8 = @ptrCast(bitcode.ptr); file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err| @@ -992,7 +993,7 @@ pub const Object = struct { if (options.post_bc_path) |path| { var file = std.fs.cwd().createFile(path, .{}) catch |err| return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) }); - defer file.close(); + defer file.close(io); const ptr: [*]const u8 = @ptrCast(bitcode.ptr); file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err| diff --git a/src/fmt.zig b/src/fmt.zig index 80925200d62551b14c2a399f7872e1c8c131549f..907c7885adf262fb9407096645be73dcce2534eb 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -187,7 +187,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! // On Windows, statFile does not work for directories error.IsDir => dir: { var dir = try fs.cwd().openDir(file_path, .{}); - defer dir.close(); + defer dir.close(io); break :dir try dir.stat(); }, else => |e| return e, @@ -222,8 +222,10 @@ fn fmtPathDir( parent_dir: fs.Dir, parent_sub_path: []const u8, ) !void { + const io = fmt.io; + var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true }); - defer dir.close(); + defer dir.close(io); const stat = try dir.stat(); if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; @@ -262,7 +264,7 @@ fn fmtPathFile( const source_file = try dir.openFile(sub_path, .{}); var file_closed = false; - errdefer if (!file_closed) source_file.close(); + errdefer if (!file_closed) source_file.close(io); const stat = try source_file.stat(); @@ -280,7 +282,7 @@ fn fmtPathFile( }; defer gpa.free(source_code); - source_file.close(); + source_file.close(io); file_closed = true; // Add to set after no longer possible to get error.IsDir. diff --git a/src/introspect.zig b/src/introspect.zig index 8467b566c6eb1290c57fda2c967b8ddd3ce5ffb2..9b6797e7d82b7430d0c1006f8e9b935dd49ff026 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -1,18 +1,21 @@ -const std = @import("std"); const builtin = @import("builtin"); +const build_options = @import("build_options"); + +const std = @import("std"); +const Io = std.Io; const mem = std.mem; -const Allocator = mem.Allocator; +const Allocator = std.mem.Allocator; const os = std.os; const fs = std.fs; const Cache = std.Build.Cache; + const Compilation = @import("Compilation.zig"); const Package = @import("Package.zig"); -const build_options = @import("build_options"); /// Returns the sub_path that worked, or `null` if none did. /// The path of the returned Directory is relative to `base`. /// The handle of the returned Directory is open. -fn testZigInstallPrefix(base_dir: fs.Dir) ?Cache.Directory { +fn testZigInstallPrefix(io: Io, base_dir: Io.Dir) ?Cache.Directory { const test_index_file = "std" ++ fs.path.sep_str ++ "std.zig"; zig_dir: { @@ -20,31 +23,31 @@ fn testZigInstallPrefix(base_dir: fs.Dir) ?Cache.Directory { const lib_zig = "lib" ++ fs.path.sep_str ++ "zig"; var test_zig_dir = base_dir.openDir(lib_zig, .{}) catch break :zig_dir; const file = test_zig_dir.openFile(test_index_file, .{}) catch { - test_zig_dir.close(); + test_zig_dir.close(io); break :zig_dir; }; - file.close(); + file.close(io); return .{ .handle = test_zig_dir, .path = lib_zig }; } // Try lib/std/std.zig var test_zig_dir = base_dir.openDir("lib", .{}) catch return null; const file = test_zig_dir.openFile(test_index_file, .{}) catch { - test_zig_dir.close(); + test_zig_dir.close(io); return null; }; - file.close(); + file.close(io); return .{ .handle = test_zig_dir, .path = "lib" }; } /// Both the directory handle and the path are newly allocated resources which the caller now owns. -pub fn findZigLibDir(gpa: Allocator) !Cache.Directory { +pub fn findZigLibDir(gpa: Allocator, io: Io) !Cache.Directory { const cwd_path = try getResolvedCwd(gpa); defer gpa.free(cwd_path); const self_exe_path = try fs.selfExePathAlloc(gpa); defer gpa.free(self_exe_path); - return findZigLibDirFromSelfExe(gpa, cwd_path, self_exe_path); + return findZigLibDirFromSelfExe(gpa, io, cwd_path, self_exe_path); } /// Like `std.process.getCwdAlloc`, but also resolves the path with `std.fs.path.resolve`. This @@ -73,6 +76,7 @@ pub fn getResolvedCwd(gpa: Allocator) error{ /// Both the directory handle and the path are newly allocated resources which the caller now owns. pub fn findZigLibDirFromSelfExe( allocator: Allocator, + io: Io, /// The return value of `getResolvedCwd`. /// Passed as an argument to avoid pointlessly repeating the call. cwd_path: []const u8, @@ -82,9 +86,9 @@ pub fn findZigLibDirFromSelfExe( var cur_path: []const u8 = self_exe_path; while (fs.path.dirname(cur_path)) |dirname| : (cur_path = dirname) { var base_dir = cwd.openDir(dirname, .{}) catch continue; - defer base_dir.close(); + defer base_dir.close(io); - const sub_directory = testZigInstallPrefix(base_dir) orelse continue; + const sub_directory = testZigInstallPrefix(io, base_dir) orelse continue; const p = try fs.path.join(allocator, &.{ dirname, sub_directory.path.? }); defer allocator.free(p); diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index afeb5b3282d760ab6b02a4970c3f4f238d24d7f5..8c5e0afe4b7bd721e175f60a4d8c42bbee3b0978 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -449,7 +449,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); cache.addPrefix(comp.dirs.zig_lib); cache.addPrefix(comp.dirs.global_cache); - defer cache.manifest_dir.close(); + defer cache.manifest_dir.close(io); var man = cache.obtain(); defer man.deinit(); @@ -480,7 +480,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .handle = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}), .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}), }; - defer o_directory.handle.close(); + defer o_directory.handle.close(io); const abilists_contents = man.files.keys()[abilists_index].contents.?; const metadata = try loadMetaData(gpa, abilists_contents); diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index 64d0fdbeac987b0d87ca28b640e1f8eacb6034cc..bec20ff3d46c18be09ca25c014480f7900ab9a11 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -684,7 +684,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); cache.addPrefix(comp.dirs.zig_lib); cache.addPrefix(comp.dirs.global_cache); - defer cache.manifest_dir.close(); + defer cache.manifest_dir.close(io); var man = cache.obtain(); defer man.deinit(); @@ -715,7 +715,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .handle = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}), .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}), }; - defer o_directory.handle.close(); + defer o_directory.handle.close(io); const abilists_contents = man.files.keys()[abilists_index].contents.?; const metadata = try loadMetaData(gpa, abilists_contents); diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index b3c018996a53cad37c8c19ca8a3c46f20b010d37..005696e1fca20a6f32f74562d5f79404d486c65b 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -262,7 +262,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() }); cache.addPrefix(comp.dirs.zig_lib); cache.addPrefix(comp.dirs.global_cache); - defer cache.manifest_dir.close(); + defer cache.manifest_dir.close(io); cache.hash.addBytes(build_options.version); cache.hash.addOptionalBytes(comp.dirs.zig_lib.path); @@ -297,7 +297,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { const digest = man.final(); const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); var o_dir = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}); - defer o_dir.close(); + defer o_dir.close(io); const aro = @import("aro"); var diagnostics: aro.Diagnostics = .{ @@ -377,7 +377,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { { const lib_final_file = try o_dir.createFile(final_lib_basename, .{ .truncate = true }); - defer lib_final_file.close(); + defer lib_final_file.close(io); var buffer: [1024]u8 = undefined; var file_writer = lib_final_file.writer(&buffer); try implib.writeCoffArchive(gpa, &file_writer.interface, members); diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index 8d35e3bd71d39dabf7415c63ca5ea2dff426e64c..67e6a2f90326a46a199340939c17daac085dd4be 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -390,7 +390,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); cache.addPrefix(comp.dirs.zig_lib); cache.addPrefix(comp.dirs.global_cache); - defer cache.manifest_dir.close(); + defer cache.manifest_dir.close(io); var man = cache.obtain(); defer man.deinit(); @@ -421,7 +421,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .handle = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}), .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}), }; - defer o_directory.handle.close(); + defer o_directory.handle.close(io); const abilists_contents = man.files.keys()[abilists_index].contents.?; const metadata = try loadMetaData(gpa, abilists_contents); diff --git a/src/link.zig b/src/link.zig index 6ac96504c7b4ca08d7f0d3f522a0e140a48662de..ef095987c9cf1170d94c7c16a3c1101abae1f6a0 100644 --- a/src/link.zig +++ b/src/link.zig @@ -687,7 +687,7 @@ pub const File = struct { .lld => assert(base.file == null), .elf => if (base.file) |f| { dev.check(.elf_linker); - f.close(); + f.close(io); base.file = null; if (base.child_pid) |pid| { @@ -701,7 +701,7 @@ pub const File = struct { }, .macho, .wasm => if (base.file) |f| { dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker }); - f.close(); + f.close(io); base.file = null; if (base.child_pid) |pid| { @@ -866,8 +866,9 @@ pub const File = struct { } pub fn destroy(base: *File) void { + const io = base.comp.io; base.releaseLock(); - if (base.file) |f| f.close(); + if (base.file) |f| f.close(io); switch (base.tag) { .plan9 => unreachable, inline else => |tag| { @@ -1060,9 +1061,10 @@ pub const File = struct { /// Opens a path as an object file and parses it into the linker. fn openLoadObject(base: *File, path: Path) anyerror!void { if (base.tag == .lld) return; + const io = base.comp.io; const diags = &base.comp.link_diags; - const input = try openObjectInput(diags, path); - errdefer input.object.file.close(); + const input = try openObjectInput(io, diags, path); + errdefer input.object.file.close(io); try loadInput(base, input); } @@ -1070,21 +1072,22 @@ pub const File = struct { /// If `query` is non-null, allows GNU ld scripts. fn openLoadArchive(base: *File, path: Path, opt_query: ?UnresolvedInput.Query) anyerror!void { if (base.tag == .lld) return; + const io = base.comp.io; if (opt_query) |query| { - const archive = try openObject(path, query.must_link, query.hidden); - errdefer archive.file.close(); + const archive = try openObject(io, path, query.must_link, query.hidden); + errdefer archive.file.close(io); loadInput(base, .{ .archive = archive }) catch |err| switch (err) { error.BadMagic, error.UnexpectedEndOfFile => { if (base.tag != .elf and base.tag != .elf2) return err; try loadGnuLdScript(base, path, query, archive.file); - archive.file.close(); + archive.file.close(io); return; }, else => return err, }; } else { - const archive = try openObject(path, false, false); - errdefer archive.file.close(); + const archive = try openObject(io, path, false, false); + errdefer archive.file.close(io); try loadInput(base, .{ .archive = archive }); } } @@ -1093,13 +1096,14 @@ pub const File = struct { /// Handles GNU ld scripts. fn openLoadDso(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void { if (base.tag == .lld) return; - const dso = try openDso(path, query.needed, query.weak, query.reexport); - errdefer dso.file.close(); + const io = base.comp.io; + const dso = try openDso(io, path, query.needed, query.weak, query.reexport); + errdefer dso.file.close(io); loadInput(base, .{ .dso = dso }) catch |err| switch (err) { error.BadMagic, error.UnexpectedEndOfFile => { if (base.tag != .elf and base.tag != .elf2) return err; try loadGnuLdScript(base, path, query, dso.file); - dso.file.close(); + dso.file.close(io); return; }, else => return err, @@ -1735,6 +1739,7 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void { pub fn resolveInputs( gpa: Allocator, arena: Allocator, + io: Io, target: *const std.Target, /// This function mutates this array but does not take ownership. /// Allocated with `gpa`. @@ -1784,6 +1789,7 @@ pub fn resolveInputs( for (lib_directories) |lib_directory| switch (try resolveLibInput( gpa, arena, + io, unresolved_inputs, resolved_inputs, &checked_paths, @@ -1810,6 +1816,7 @@ pub fn resolveInputs( for (lib_directories) |lib_directory| switch (try resolveLibInput( gpa, arena, + io, unresolved_inputs, resolved_inputs, &checked_paths, @@ -1837,6 +1844,7 @@ pub fn resolveInputs( switch (try resolveLibInput( gpa, arena, + io, unresolved_inputs, resolved_inputs, &checked_paths, @@ -1855,6 +1863,7 @@ pub fn resolveInputs( switch (try resolveLibInput( gpa, arena, + io, unresolved_inputs, resolved_inputs, &checked_paths, @@ -1886,6 +1895,7 @@ pub fn resolveInputs( if (try resolvePathInput( gpa, arena, + io, unresolved_inputs, resolved_inputs, &ld_script_bytes, @@ -1903,6 +1913,7 @@ pub fn resolveInputs( switch ((try resolvePathInput( gpa, arena, + io, unresolved_inputs, resolved_inputs, &ld_script_bytes, @@ -1930,6 +1941,7 @@ pub fn resolveInputs( if (try resolvePathInput( gpa, arena, + io, unresolved_inputs, resolved_inputs, &ld_script_bytes, @@ -1969,6 +1981,7 @@ const fatal = std.process.fatal; fn resolveLibInput( gpa: Allocator, arena: Allocator, + io: Io, /// Allocated via `gpa`. unresolved_inputs: *std.ArrayList(UnresolvedInput), /// Allocated via `gpa`. @@ -1998,7 +2011,7 @@ fn resolveLibInput( error.FileNotFound => break :tbd, else => |e| fatal("unable to search for tbd library '{f}': {s}", .{ test_path, @errorName(e) }), }; - errdefer file.close(); + errdefer file.close(io); return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query); } @@ -2013,7 +2026,7 @@ fn resolveLibInput( }), }; try checked_paths.print(gpa, "\n {f}", .{test_path}); - switch (try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, .{ + switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, .{ .path = test_path, .query = name_query.query, }, link_mode, color)) { @@ -2036,7 +2049,7 @@ fn resolveLibInput( test_path, @errorName(e), }), }; - errdefer file.close(); + errdefer file.close(io); return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query); } @@ -2052,7 +2065,7 @@ fn resolveLibInput( error.FileNotFound => break :mingw, else => |e| fatal("unable to search for static library '{f}': {s}", .{ test_path, @errorName(e) }), }; - errdefer file.close(); + errdefer file.close(io); return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query); } @@ -2087,6 +2100,7 @@ fn finishResolveLibInput( fn resolvePathInput( gpa: Allocator, arena: Allocator, + io: Io, /// Allocated with `gpa`. unresolved_inputs: *std.ArrayList(UnresolvedInput), /// Allocated with `gpa`. @@ -2098,12 +2112,12 @@ fn resolvePathInput( color: std.zig.Color, ) Allocator.Error!?ResolveLibInputResult { switch (Compilation.classifyFileExt(pq.path.sub_path)) { - .static_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color), - .shared_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color), + .static_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color), + .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color), .object => { var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err| fatal("failed to open object {f}: {s}", .{ pq.path, @errorName(err) }); - errdefer file.close(); + errdefer file.close(io); try resolved_inputs.append(gpa, .{ .object = .{ .path = pq.path, .file = file, @@ -2115,7 +2129,7 @@ fn resolvePathInput( .res => { var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err| fatal("failed to open windows resource {f}: {s}", .{ pq.path, @errorName(err) }); - errdefer file.close(); + errdefer file.close(io); try resolved_inputs.append(gpa, .{ .res = .{ .path = pq.path, .file = file, @@ -2129,6 +2143,7 @@ fn resolvePathInput( fn resolvePathInputLib( gpa: Allocator, arena: Allocator, + io: Io, /// Allocated with `gpa`. unresolved_inputs: *std.ArrayList(UnresolvedInput), /// Allocated with `gpa`. @@ -2155,7 +2170,7 @@ fn resolvePathInputLib( @tagName(link_mode), std.fmt.alt(test_path, .formatEscapeChar), @errorName(e), }), }; - errdefer file.close(); + errdefer file.close(io); try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len)); const n = file.preadAll(ld_script_bytes.items, 0) catch |err| fatal("failed to read '{f}': {s}", .{ std.fmt.alt(test_path, .formatEscapeChar), @errorName(err), @@ -2223,7 +2238,7 @@ fn resolvePathInputLib( } }); } } - file.close(); + file.close(io); return .ok; } @@ -2233,13 +2248,13 @@ fn resolvePathInputLib( @tagName(link_mode), test_path, @errorName(e), }), }; - errdefer file.close(); + errdefer file.close(io); return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query); } -pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object { +pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object { var file = try path.root_dir.handle.openFile(path.sub_path, .{}); - errdefer file.close(); + errdefer file.close(io); return .{ .path = path, .file = file, @@ -2248,9 +2263,9 @@ pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object { }; } -pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso { +pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso { var file = try path.root_dir.handle.openFile(path.sub_path, .{}); - errdefer file.close(); + errdefer file.close(io); return .{ .path = path, .file = file, @@ -2260,20 +2275,20 @@ pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso }; } -pub fn openObjectInput(diags: *Diags, path: Path) error{LinkFailure}!Input { - return .{ .object = openObject(path, false, false) catch |err| { +pub fn openObjectInput(io: Io, diags: *Diags, path: Path) error{LinkFailure}!Input { + return .{ .object = openObject(io, path, false, false) catch |err| { return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) }); } }; } -pub fn openArchiveInput(diags: *Diags, path: Path, must_link: bool, hidden: bool) error{LinkFailure}!Input { - return .{ .archive = openObject(path, must_link, hidden) catch |err| { +pub fn openArchiveInput(io: Io, diags: *Diags, path: Path, must_link: bool, hidden: bool) error{LinkFailure}!Input { + return .{ .archive = openObject(io, path, must_link, hidden) catch |err| { return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) }); } }; } -pub fn openDsoInput(diags: *Diags, path: Path, needed: bool, weak: bool, reexport: bool) error{LinkFailure}!Input { - return .{ .dso = openDso(path, needed, weak, reexport) catch |err| { +pub fn openDsoInput(io: Io, diags: *Diags, path: Path, needed: bool, weak: bool, reexport: bool) error{LinkFailure}!Input { + return .{ .dso = openDso(io, path, needed, weak, reexport) catch |err| { return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) }); } }; } diff --git a/src/link/C.zig b/src/link/C.zig index ce48e85851b1d2bd909386f5eaf3f9990cb643a2..04c92443e5d2f2da848d6eff78495ab922b1fe26 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -124,6 +124,7 @@ pub fn createEmpty( emit: Path, options: link.File.OpenOptions, ) !*C { + const io = comp.io; const target = &comp.root_mod.resolved_target.result; assert(target.ofmt == .c); const optimize_mode = comp.root_mod.optimize_mode; @@ -139,7 +140,7 @@ pub fn createEmpty( // Truncation is done on `flush`. .truncate = false, }); - errdefer file.close(); + errdefer file.close(io); const c_file = try arena.create(C); @@ -763,6 +764,7 @@ pub fn flushEmitH(zcu: *Zcu) !void { if (true) return; // emit-h is regressed const emit_h = zcu.emit_h orelse return; + const io = zcu.comp.io; // We collect a list of buffers to write, and write them all at once with pwritev 😎 const num_buffers = emit_h.decl_table.count() + 1; @@ -795,7 +797,7 @@ pub fn flushEmitH(zcu: *Zcu) !void { // make it easier on the file system by doing 1 reallocation instead of two. .truncate = false, }); - defer file.close(); + defer file.close(io); try file.setEndPos(file_size); try file.pwritevAll(all_buffers.items, 0); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 69acbe034bde484da71a332835a5ff653b021216..ae7d631f094b9a4af2d2c94b3fc96aabbc8e75a5 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -406,10 +406,12 @@ pub fn open( } pub fn deinit(self: *Elf) void { - const gpa = self.base.comp.gpa; + const comp = self.base.comp; + const gpa = comp.gpa; + const io = comp.io; for (self.file_handles.items) |fh| { - fh.close(); + fh.close(io); } self.file_handles.deinit(gpa); diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 234509048212a6f21d6d7983b2236c2bdb49a8f4..66b032e0a91b5c83478e95c2794c649028382812 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1628,7 +1628,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err| log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) }); { - defer rsp_file.close(); + defer rsp_file.close(io); var rsp_file_buffer: [1024]u8 = undefined; var rsp_file_writer = rsp_file.writer(&rsp_file_buffer); const rsp_writer = &rsp_file_writer.interface; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 2c4ffd6632ca084ed60dcbd5243000419941a41a..471465cea1411b5e1d1f65ea4f42b3bba71e30a6 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -267,14 +267,16 @@ pub fn open( } pub fn deinit(self: *MachO) void { - const gpa = self.base.comp.gpa; + const comp = self.base.comp; + const gpa = comp.gpa; + const io = comp.io; if (self.d_sym) |*d_sym| { d_sym.deinit(); } for (self.file_handles.items) |handle| { - handle.close(); + handle.close(io); } self.file_handles.deinit(gpa); @@ -3257,8 +3259,10 @@ const InitMetadataOptions = struct { }; pub fn closeDebugInfo(self: *MachO) bool { + const comp = self.base.comp; + const io = comp.io; const d_sym = &(self.d_sym orelse return false); - d_sym.file.?.close(); + d_sym.file.?.close(io); d_sym.file = null; return true; } @@ -3269,7 +3273,9 @@ pub fn reopenDebugInfo(self: *MachO) !void { assert(!self.base.comp.config.use_llvm); assert(self.base.comp.config.debug_format == .dwarf); - const gpa = self.base.comp.gpa; + const comp = self.base.comp; + const io = comp.io; + const gpa = comp.gpa; const sep = fs.path.sep_str; const d_sym_path = try std.fmt.allocPrint( gpa, @@ -3279,7 +3285,7 @@ pub fn reopenDebugInfo(self: *MachO) !void { defer gpa.free(d_sym_path); var d_sym_bundle = try self.base.emit.root_dir.handle.makeOpenPath(d_sym_path, .{}); - defer d_sym_bundle.close(); + defer d_sym_bundle.close(io); self.d_sym.?.file = try d_sym_bundle.createFile(fs.path.basename(self.base.emit.sub_path), .{ .truncate = false, diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 5d7b9b88c3fe37b9f0101db9c5c2fdb8f9e1ed9c..8a97f2844f2c41efc819bd8d6ce0760870414224 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -1,5 +1,28 @@ +const DebugSymbols = @This(); + +const std = @import("std"); +const Io = std.Io; +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.link_dsym); +const macho = std.macho; +const makeStaticString = MachO.makeStaticString; +const math = std.math; +const mem = std.mem; +const Writer = std.Io.Writer; +const Allocator = std.mem.Allocator; + +const link = @import("../../link.zig"); +const MachO = @import("../MachO.zig"); +const StringTable = @import("../StringTable.zig"); +const Type = @import("../../Type.zig"); +const trace = @import("../../tracy.zig").trace; +const load_commands = @import("load_commands.zig"); +const padToIdeal = MachO.padToIdeal; + +io: Io, allocator: Allocator, -file: ?fs.File, +file: ?Io.File, symtab_cmd: macho.symtab_command = .{}, uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 }, @@ -208,7 +231,8 @@ pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void { pub fn deinit(self: *DebugSymbols) void { const gpa = self.allocator; - if (self.file) |file| file.close(); + const io = self.io; + if (self.file) |file| file.close(io); self.segments.deinit(gpa); self.sections.deinit(gpa); self.relocs.deinit(gpa); @@ -443,25 +467,3 @@ pub fn getSection(self: DebugSymbols, sect: u8) macho.section_64 { assert(sect < self.sections.items.len); return self.sections.items[sect]; } - -const DebugSymbols = @This(); - -const std = @import("std"); -const build_options = @import("build_options"); -const assert = std.debug.assert; -const fs = std.fs; -const link = @import("../../link.zig"); -const load_commands = @import("load_commands.zig"); -const log = std.log.scoped(.link_dsym); -const macho = std.macho; -const makeStaticString = MachO.makeStaticString; -const math = std.math; -const mem = std.mem; -const padToIdeal = MachO.padToIdeal; -const trace = @import("../../tracy.zig").trace; -const Writer = std.Io.Writer; - -const Allocator = mem.Allocator; -const MachO = @import("../MachO.zig"); -const StringTable = @import("../StringTable.zig"); -const Type = @import("../../Type.zig"); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 92307ec40c36d894d0d0e97689582fb6e0f83664..160e6cdcc6c95832b70386a8fe87159fe10fbcec 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3032,7 +3032,7 @@ fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void { const io = wasm.base.comp.io; const gc_sections = wasm.base.gc_sections; - defer obj.file.close(); + defer obj.file.close(io); var file_reader = obj.file.reader(io, &.{}); @@ -3060,7 +3060,7 @@ fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void { const io = wasm.base.comp.io; const gc_sections = wasm.base.gc_sections; - defer obj.file.close(); + defer obj.file.close(io); var file_reader = obj.file.reader(io, &.{}); diff --git a/src/main.zig b/src/main.zig index a897f2a84771162e5f3b96a98e7383c41cada0b9..3ca64881f8bcda4c732c7a3748b7e6c8217fe128 100644 --- a/src/main.zig +++ b/src/main.zig @@ -328,21 +328,21 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { .prepend_global_cache_path = true, }); } else if (mem.eql(u8, cmd, "init")) { - return cmdInit(gpa, arena, cmd_args); + return cmdInit(gpa, arena, io, cmd_args); } else if (mem.eql(u8, cmd, "targets")) { dev.check(.targets_command); const host = std.zig.resolveTargetQueryOrFatal(io, .{}); - var stdout_writer = fs.File.stdout().writer(&stdout_buffer); - try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + try @import("print_targets.zig").cmdTargets(arena, io, cmd_args, &stdout_writer.interface, &host); return stdout_writer.interface.flush(); } else if (mem.eql(u8, cmd, "version")) { dev.check(.version_command); - try fs.File.stdout().writeAll(build_options.version ++ "\n"); + try Io.File.stdout().writeAll(build_options.version ++ "\n"); return; } else if (mem.eql(u8, cmd, "env")) { dev.check(.env_command); const host = std.zig.resolveTargetQueryOrFatal(io, .{}); - var stdout_writer = fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); try @import("print_env.zig").cmdEnv( arena, &stdout_writer.interface, @@ -358,10 +358,10 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { }); } else if (mem.eql(u8, cmd, "zen")) { dev.check(.zen_command); - return fs.File.stdout().writeAll(info_zen); + return Io.File.stdout().writeAll(info_zen); } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) { dev.check(.help_command); - return fs.File.stdout().writeAll(usage); + return Io.File.stdout().writeAll(usage); } else if (mem.eql(u8, cmd, "ast-check")) { return cmdAstCheck(arena, io, cmd_args); } else if (mem.eql(u8, cmd, "detect-cpu")) { @@ -698,7 +698,7 @@ const Emit = union(enum) { yes: []const u8, const OutputToCacheReason = enum { listen, @"zig run", @"zig test" }; - fn resolve(emit: Emit, default_basename: []const u8, output_to_cache: ?OutputToCacheReason) Compilation.CreateOptions.Emit { + fn resolve(io: Io, emit: Emit, default_basename: []const u8, output_to_cache: ?OutputToCacheReason) Compilation.CreateOptions.Emit { return switch (emit) { .no => .no, .yes_default_path => if (output_to_cache != null) .yes_cache else .{ .yes_path = default_basename }, @@ -716,7 +716,7 @@ const Emit = union(enum) { var dir = fs.cwd().openDir(dir_path, .{}) catch |err| { fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) }); }; - dir.close(); + dir.close(io); } break :e .{ .yes_path = path }; }, @@ -1034,7 +1034,7 @@ fn buildOutputType( }; } else if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try fs.File.stdout().writeAll(usage_build_generic); + try Io.File.stdout().writeAll(usage_build_generic); return cleanExit(); } else if (mem.eql(u8, arg, "--")) { if (arg_mode == .run) { @@ -2834,9 +2834,9 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-V")) { warn("ignoring request for supported emulations: unimplemented", .{}); } else if (mem.eql(u8, arg, "-v")) { - try fs.File.stdout().writeAll("zig ld " ++ build_options.version ++ "\n"); + try Io.File.stdout().writeAll("zig ld " ++ build_options.version ++ "\n"); } else if (mem.eql(u8, arg, "--version")) { - try fs.File.stdout().writeAll("zig ld " ++ build_options.version ++ "\n"); + try Io.File.stdout().writeAll("zig ld " ++ build_options.version ++ "\n"); process.exit(0); } else { fatal("unsupported linker arg: {s}", .{arg}); @@ -3251,8 +3251,8 @@ fn buildOutputType( } } - var cleanup_emit_bin_dir: ?fs.Dir = null; - defer if (cleanup_emit_bin_dir) |*dir| dir.close(); + var cleanup_emit_bin_dir: ?Io.Dir = null; + defer if (cleanup_emit_bin_dir) |*dir| dir.close(io); // For `zig run` and `zig test`, we don't want to put the binary in the cwd by default. So, if // the binary is requested with no explicit path (as is the default), we emit to the cache. @@ -3307,7 +3307,7 @@ fn buildOutputType( var dir = fs.cwd().openDir(dir_path, .{}) catch |err| { fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) }); }; - dir.close(); + dir.close(io); } break :emit .{ .yes_path = path }; }, @@ -3390,7 +3390,7 @@ fn buildOutputType( // will be a hash of its contents — so multiple invocations of // `zig cc -` will result in the same temp file name. var f = try dirs.local_cache.handle.createFile(dump_path, .{}); - defer f.close(); + defer f.close(io); // Re-using the hasher from Cache, since the functional requirements // for the hashing algorithm here and in the cache are the same. @@ -3399,7 +3399,7 @@ fn buildOutputType( var file_writer = f.writer(&.{}); var buffer: [1000]u8 = undefined; var hasher = file_writer.interface.hashed(Cache.Hasher.init("0123456789abcdef"), &buffer); - var stdin_reader = fs.File.stdin().readerStreaming(io, &.{}); + var stdin_reader = Io.File.stdin().readerStreaming(io, &.{}); _ = hasher.writer.sendFileAll(&stdin_reader, .unlimited) catch |err| switch (err) { error.WriteFailed => fatal("failed to write {s}: {t}", .{ dump_path, file_writer.err.? }), else => fatal("failed to pipe stdin to {s}: {t}", .{ dump_path, err }), @@ -3630,13 +3630,13 @@ fn buildOutputType( if (show_builtin) { const builtin_opts = comp.root_mod.getBuiltinOptions(comp.config); const source = try builtin_opts.generate(arena); - return fs.File.stdout().writeAll(source); + return Io.File.stdout().writeAll(source); } switch (listen) { .none => {}, .stdio => { - var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer); - var stdout_writer = fs.File.stdout().writer(&stdout_buffer); + var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); try serve( comp, &stdin_reader.interface, @@ -4034,6 +4034,7 @@ fn createModule( link.resolveInputs( gpa, arena, + io, target, &unresolved_link_inputs, &create_module.link_inputs, @@ -4689,8 +4690,8 @@ fn cmdTranslateC( @errorName(err), }); }; - defer zig_file.close(); - var stdout_writer = fs.File.stdout().writer(&stdout_buffer); + defer zig_file.close(io); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); var file_reader = zig_file.reader(io, &.{}); _ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited); try stdout_writer.interface.flush(); @@ -4728,7 +4729,7 @@ const usage_init = \\ ; -fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { +fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void { dev.check(.init_command); var template: enum { example, minimal } = .example; @@ -4740,7 +4741,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { if (mem.eql(u8, arg, "-m") or mem.eql(u8, arg, "--minimal")) { template = .minimal; } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try fs.File.stdout().writeAll(usage_init); + try Io.File.stdout().writeAll(usage_init); return cleanExit(); } else { fatal("unrecognized parameter: '{s}'", .{arg}); @@ -4759,7 +4760,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { switch (template) { .example => { - var templates = findTemplates(gpa, arena); + var templates = findTemplates(gpa, arena, io); defer templates.deinit(); const s = fs.path.sep_str; @@ -4789,7 +4790,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { return cleanExit(); }, .minimal => { - writeSimpleTemplateFile(Package.Manifest.basename, + writeSimpleTemplateFile(io, Package.Manifest.basename, \\.{{ \\ .name = .{s}, \\ .version = "0.0.1", @@ -4806,7 +4807,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { else => fatal("failed to create '{s}': {s}", .{ Package.Manifest.basename, @errorName(err) }), error.PathAlreadyExists => fatal("refusing to overwrite '{s}'", .{Package.Manifest.basename}), }; - writeSimpleTemplateFile(Package.build_zig_basename, + writeSimpleTemplateFile(io, Package.build_zig_basename, \\const std = @import("std"); \\ \\pub fn build(b: *std.Build) void {{ @@ -5203,8 +5204,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) .parent = root_mod, }); - var cleanup_build_dir: ?fs.Dir = null; - defer if (cleanup_build_dir) |*dir| dir.close(); + var cleanup_build_dir: ?Io.Dir = null; + defer if (cleanup_build_dir) |*dir| dir.close(io); if (dev.env.supports(.fetch_command)) { const fetch_prog_node = root_prog_node.start("Fetch Packages", 0); @@ -5296,6 +5297,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) try job_queue.createDependenciesSource(&source_buf); const deps_mod = try createDependenciesModule( arena, + io, source_buf.items, root_mod, dirs, @@ -5357,6 +5359,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) } } else try createEmptyDependenciesModule( arena, + io, root_mod, dirs, config, @@ -5623,7 +5626,7 @@ fn jitCmd( defer comp.destroy(); if (options.server) { - var stdout_writer = fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); var server: std.zig.Server = .{ .out = &stdout_writer.interface, .in = undefined, // won't be receiving messages @@ -6156,7 +6159,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try fs.File.stdout().writeAll(usage_ast_check); + try Io.File.stdout().writeAll(usage_ast_check); return cleanExit(); } else if (mem.eql(u8, arg, "-t")) { want_output_text = true; @@ -6187,9 +6190,9 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { break :file fs.cwd().openFile(p, .{}) catch |err| { fatal("unable to open file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) }); }; - } else fs.File.stdin(); - defer if (zig_source_path != null) f.close(); - var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer); + } else Io.File.stdin(); + defer if (zig_source_path != null) f.close(io); + var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer); break :s std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| { fatal("unable to load file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) }); }; @@ -6207,7 +6210,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { const tree = try Ast.parse(arena, source, mode); - var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); const stdout_bw = &stdout_writer.interface; switch (mode) { .zig => { @@ -6330,7 +6333,7 @@ fn cmdDetectCpu(io: Io, args: []const []const u8) !void { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try fs.File.stdout().writeAll(detect_cpu_usage); + try Io.File.stdout().writeAll(detect_cpu_usage); return cleanExit(); } else if (mem.eql(u8, arg, "--llvm")) { use_llvm = true; @@ -6422,7 +6425,7 @@ fn detectNativeCpuWithLLVM( } fn printCpu(cpu: std.Target.Cpu) !void { - var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); const stdout_bw = &stdout_writer.interface; if (cpu.model.llvm_name) |llvm_name| { @@ -6471,7 +6474,7 @@ fn cmdDumpLlvmInts( const dl = tm.createTargetDataLayout(); const context = llvm.Context.create(); - var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); const stdout_bw = &stdout_writer.interface; for ([_]u16{ 1, 8, 16, 32, 64, 128, 256 }) |bits| { const int_type = context.intType(bits); @@ -6494,10 +6497,10 @@ fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void { var f = fs.cwd().openFile(cache_file, .{}) catch |err| { fatal("unable to open zir cache file for dumping '{s}': {s}", .{ cache_file, @errorName(err) }); }; - defer f.close(); + defer f.close(io); const zir = try Zcu.loadZirCache(arena, io, f); - var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); const stdout_bw = &stdout_writer.interface; { const instruction_bytes = zir.instructions.len * @@ -6540,16 +6543,16 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { const old_source = source: { var f = fs.cwd().openFile(old_source_path, .{}) catch |err| fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); - defer f.close(); - var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer); + defer f.close(io); + var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer); break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); }; const new_source = source: { var f = fs.cwd().openFile(new_source_path, .{}) catch |err| fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) }); - defer f.close(); - var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer); + defer f.close(io); + var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer); break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| fatal("unable to read new source file '{s}': {s}", .{ new_source_path, @errorName(err) }); }; @@ -6581,7 +6584,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .empty; try Zcu.mapOldZirToNew(arena, old_zir, new_zir, &inst_map); - var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); const stdout_bw = &stdout_writer.interface; { try stdout_bw.print("Instruction mappings:\n", .{}); @@ -6912,7 +6915,7 @@ fn cmdFetch( const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try fs.File.stdout().writeAll(usage_fetch); + try Io.File.stdout().writeAll(usage_fetch); return cleanExit(); } else if (mem.eql(u8, arg, "--global-cache-dir")) { if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); @@ -6958,7 +6961,7 @@ fn cmdFetch( .path = p, }; }; - defer global_cache_directory.handle.close(); + defer global_cache_directory.handle.close(io); var job_queue: Package.Fetch.JobQueue = .{ .io = io, @@ -7021,7 +7024,7 @@ fn cmdFetch( const name = switch (save) { .no => { - var stdout = fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout = Io.File.stdout().writerStreaming(&stdout_buffer); try stdout.interface.print("{s}\n", .{package_hash_slice}); try stdout.interface.flush(); return cleanExit(); @@ -7043,7 +7046,7 @@ fn cmdFetch( // The name to use in case the manifest file needs to be created now. const init_root_name = fs.path.basename(build_root.directory.path orelse cwd_path); - var manifest, var ast = try loadManifest(gpa, arena, .{ + var manifest, var ast = try loadManifest(gpa, arena, io, .{ .root_name = try sanitizeExampleName(arena, init_root_name), .dir = build_root.directory.handle, .color = color, @@ -7168,6 +7171,7 @@ fn cmdFetch( fn createEmptyDependenciesModule( arena: Allocator, + io: Io, main_mod: *Package.Module, dirs: Compilation.Directories, global_options: Compilation.Config, @@ -7176,6 +7180,7 @@ fn createEmptyDependenciesModule( try Package.Fetch.JobQueue.createEmptyDependenciesSource(&source); _ = try createDependenciesModule( arena, + io, source.items, main_mod, dirs, @@ -7187,6 +7192,7 @@ fn createEmptyDependenciesModule( /// build runner to obtain via `@import("@dependencies")`. fn createDependenciesModule( arena: Allocator, + io: Io, source: []const u8, main_mod: *Package.Module, dirs: Compilation.Directories, @@ -7198,7 +7204,7 @@ fn createDependenciesModule( const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); { var tmp_dir = try dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}); - defer tmp_dir.close(); + defer tmp_dir.close(io); try tmp_dir.writeFile(.{ .sub_path = basename, .data = source }); } @@ -7232,10 +7238,10 @@ fn createDependenciesModule( const BuildRoot = struct { directory: Cache.Directory, build_zig_basename: []const u8, - cleanup_build_dir: ?fs.Dir, + cleanup_build_dir: ?Io.Dir, - fn deinit(br: *BuildRoot) void { - if (br.cleanup_build_dir) |*dir| dir.close(); + fn deinit(br: *BuildRoot, io: Io) void { + if (br.cleanup_build_dir) |*dir| dir.close(io); br.* = undefined; } }; @@ -7304,13 +7310,14 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { const LoadManifestOptions = struct { root_name: []const u8, - dir: fs.Dir, + dir: Io.Dir, color: Color, }; fn loadManifest( gpa: Allocator, arena: Allocator, + io: Io, options: LoadManifestOptions, ) !struct { Package.Manifest, Ast } { const manifest_bytes = while (true) { @@ -7322,7 +7329,7 @@ fn loadManifest( 0, ) catch |err| switch (err) { error.FileNotFound => { - writeSimpleTemplateFile(Package.Manifest.basename, + writeSimpleTemplateFile(io, Package.Manifest.basename, \\.{{ \\ .name = .{s}, \\ .version = "{s}", @@ -7374,12 +7381,12 @@ fn loadManifest( const Templates = struct { zig_lib_directory: Cache.Directory, - dir: fs.Dir, + dir: Io.Dir, buffer: std.array_list.Managed(u8), - fn deinit(templates: *Templates) void { - templates.zig_lib_directory.handle.close(); - templates.dir.close(); + fn deinit(templates: *Templates, io: Io) void { + templates.zig_lib_directory.handle.close(io); + templates.dir.close(io); templates.buffer.deinit(); templates.* = undefined; } @@ -7387,7 +7394,7 @@ const Templates = struct { fn write( templates: *Templates, arena: Allocator, - out_dir: fs.Dir, + out_dir: Io.Dir, root_name: []const u8, template_path: []const u8, fingerprint: Package.Fingerprint, @@ -7435,23 +7442,23 @@ const Templates = struct { }); } }; -fn writeSimpleTemplateFile(file_name: []const u8, comptime fmt: []const u8, args: anytype) !void { +fn writeSimpleTemplateFile(io: Io, file_name: []const u8, comptime fmt: []const u8, args: anytype) !void { const f = try fs.cwd().createFile(file_name, .{ .exclusive = true }); - defer f.close(); + defer f.close(io); var buf: [4096]u8 = undefined; var fw = f.writer(&buf); try fw.interface.print(fmt, args); try fw.interface.flush(); } -fn findTemplates(gpa: Allocator, arena: Allocator) Templates { +fn findTemplates(gpa: Allocator, arena: Allocator, io: Io) Templates { const cwd_path = introspect.getResolvedCwd(arena) catch |err| { fatal("unable to get cwd: {s}", .{@errorName(err)}); }; const self_exe_path = fs.selfExePathAlloc(arena) catch |err| { fatal("unable to find self exe path: {s}", .{@errorName(err)}); }; - var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, cwd_path, self_exe_path) catch |err| { + var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, io, cwd_path, self_exe_path) catch |err| { fatal("unable to find zig installation directory '{s}': {s}", .{ self_exe_path, @errorName(err) }); }; diff --git a/src/print_targets.zig b/src/print_targets.zig index d9118b901b598cf8497885064cd4bba9df787750..2f80187de1931a0678c29ad8cd2ce553bb1f1863 100644 --- a/src/print_targets.zig +++ b/src/print_targets.zig @@ -12,6 +12,7 @@ const introspect = @import("introspect.zig"); pub fn cmdTargets( allocator: Allocator, + io: Io, args: []const []const u8, out: *std.Io.Writer, native_target: *const Target, @@ -20,7 +21,7 @@ pub fn cmdTargets( var zig_lib_directory = introspect.findZigLibDir(allocator) catch |err| { fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)}); }; - defer zig_lib_directory.handle.close(); + defer zig_lib_directory.handle.close(io); defer allocator.free(zig_lib_directory.path.?); const abilists_contents = zig_lib_directory.handle.readFileAlloc( -- 2.54.0 From 1b1fb7fab623e40f4ddc24d7b5ef7e48949e8a17 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 6 Dec 2025 16:47:19 -0800 Subject: [PATCH 009/195] std.crypo.tls: delete unused import --- lib/std/crypto/tls.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/std/crypto/tls.zig b/lib/std/crypto/tls.zig index c24283469c68af5e0496c6f54e5ac40edfe580c1..252defcb2d5dcc955fb4b102146b16ecf380b48e 100644 --- a/lib/std/crypto/tls.zig +++ b/lib/std/crypto/tls.zig @@ -32,7 +32,6 @@ const std = @import("../std.zig"); const Tls = @This(); -const net = std.net; const mem = std.mem; const crypto = std.crypto; const assert = std.debug.assert; -- 2.54.0 From 3204fb756980c19b7a95534acdd7a1bba837fbc3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 6 Dec 2025 17:23:07 -0800 Subject: [PATCH 010/195] update all occurrences of std.fs.File to std.Io.File --- lib/compiler/aro/aro/Compilation.zig | 4 +- lib/compiler/aro/aro/Driver.zig | 15 ++--- lib/compiler/aro/aro/Preprocessor.zig | 3 +- lib/compiler/aro/backend/Assembly.zig | 3 +- lib/compiler/aro/main.zig | 3 +- lib/compiler/build_runner.zig | 6 +- lib/compiler/libc.zig | 3 +- lib/compiler/objcopy.zig | 13 +++-- lib/compiler/reduce.zig | 3 +- lib/compiler/resinator/cli.zig | 3 +- lib/compiler/resinator/errors.zig | 8 +-- lib/compiler/resinator/main.zig | 10 ++-- lib/compiler/resinator/utils.zig | 4 +- lib/compiler/test_runner.zig | 10 ++-- lib/compiler/translate-c/main.zig | 13 +++-- lib/std/Build.zig | 6 +- lib/std/Build/Step.zig | 2 +- lib/std/Build/Step/ObjCopy.zig | 2 +- lib/std/Build/Step/Run.zig | 21 +++---- lib/std/Io.zig | 2 +- lib/std/Io/Writer.zig | 9 +-- lib/std/Io/test.zig | 2 +- lib/std/Io/tty.zig | 7 ++- lib/std/Progress.zig | 18 +++--- lib/std/Random/benchmark.zig | 6 +- lib/std/Thread.zig | 4 +- lib/std/crypto/Certificate/Bundle.zig | 8 +-- lib/std/crypto/Certificate/Bundle/macos.zig | 2 +- lib/std/crypto/benchmark.zig | 8 ++- lib/std/debug.zig | 6 +- lib/std/debug/ElfFile.zig | 4 +- lib/std/debug/Info.zig | 2 +- lib/std/debug/Pdb.zig | 2 +- lib/std/debug/SelfInfo/Windows.zig | 2 +- lib/std/debug/simple_panic.zig | 2 +- lib/std/dynamic_library.zig | 2 +- lib/std/fs/test.zig | 8 +-- lib/std/hash/benchmark.zig | 7 ++- lib/std/http.zig | 2 +- lib/std/pdb.zig | 2 +- lib/std/posix/test.zig | 18 +++--- lib/std/process/Child.zig | 4 +- lib/std/unicode/throughput_test.zig | 6 +- lib/std/zig/Zir.zig | 10 ++-- lib/std/zig/Zoir.zig | 15 ++--- lib/std/zig/llvm/Builder.zig | 4 +- lib/std/zig/perf_test.zig | 3 +- lib/std/zip.zig | 5 +- src/Compilation.zig | 4 +- src/Package/Fetch.zig | 16 ++--- src/Package/Fetch/git.zig | 22 +++---- src/Zcu.zig | 8 +-- src/Zcu/PerThread.zig | 6 +- src/fmt.zig | 16 ++--- src/link.zig | 20 +++---- src/link/Dwarf.zig | 53 +++++++++-------- src/link/Elf.zig | 5 +- src/link/Elf/Object.zig | 65 +++++++++++---------- src/link/Elf/SharedObject.zig | 37 ++++++------ src/link/Elf/file.zig | 35 +++++------ src/link/Elf2.zig | 46 ++++++++------- src/link/MachO.zig | 23 ++++---- src/link/MachO/CodeSignature.zig | 8 ++- src/link/MachO/fat.zig | 12 ++-- src/link/MachO/file.zig | 3 +- src/link/MachO/hasher.zig | 22 ++++--- src/link/MachO/uuid.zig | 19 +++--- src/link/MappedFile.zig | 25 ++++---- src/link/Wasm.zig | 5 +- src/link/tapi.zig | 10 ++-- 70 files changed, 401 insertions(+), 361 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index 9fb8123146b669b56a4b10ccaf70caf4c237f9ab..06b27c33bbed7a3abb614aede310a880ce059a14 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -1646,7 +1646,7 @@ fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kin return comp.addSourceFromFile(file, path, kind); } -pub fn addSourceFromFile(comp: *Compilation, file: std.fs.File, path: []const u8, kind: Source.Kind) !Source { +pub fn addSourceFromFile(comp: *Compilation, file: Io.File, path: []const u8, kind: Source.Kind) !Source { const contents = try comp.getFileContents(file, .unlimited); errdefer comp.gpa.free(contents); return comp.addSourceFromOwnedBuffer(path, contents, kind); @@ -1980,7 +1980,7 @@ fn getPathContents(comp: *Compilation, path: []const u8, limit: Io.Limit) ![]u8 return comp.getFileContents(file, limit); } -fn getFileContents(comp: *Compilation, file: std.fs.File, limit: Io.Limit) ![]u8 { +fn getFileContents(comp: *Compilation, file: Io.File, limit: Io.Limit) ![]u8 { var file_buf: [4096]u8 = undefined; var file_reader = file.reader(comp.io, &file_buf); diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig index 888ade2be4dbdba676440333b4824a5ea21ebd99..f933e3ce52f61d42607d26472ee7d0dd94ebe158 100644 --- a/lib/compiler/aro/aro/Driver.zig +++ b/lib/compiler/aro/aro/Driver.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const mem = std.mem; const Allocator = mem.Allocator; const process = std.process; @@ -1061,7 +1062,7 @@ pub fn printDiagnosticsStats(d: *Driver) void { } } -pub fn detectConfig(d: *Driver, file: std.fs.File) std.Io.tty.Config { +pub fn detectConfig(d: *Driver, file: Io.File) std.Io.tty.Config { if (d.diagnostics.color == false) return .no_color; const force_color = d.diagnostics.color == true; @@ -1109,7 +1110,7 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_ defer macro_buf.deinit(d.comp.gpa); var stdout_buf: [256]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&stdout_buf); + var stdout = Io.File.stdout().writer(&stdout_buf); if (parseArgs(d, &stdout.interface, ¯o_buf, args) catch |er| switch (er) { error.WriteFailed => return d.fatal("failed to write to stdout: {s}", .{errorDescription(er)}), error.OutOfMemory => return error.OutOfMemory, @@ -1329,7 +1330,7 @@ fn processSource( d.comp.cwd.createFile(path, .{}) catch |er| return d.fatal("unable to create dependency file '{s}': {s}", .{ path, errorDescription(er) }) else - std.fs.File.stdout(); + Io.File.stdout(); defer if (dep_file_name != null) file.close(io); var file_writer = file.writer(&writer_buf); @@ -1354,7 +1355,7 @@ fn processSource( d.comp.cwd.createFile(some, .{}) catch |er| return d.fatal("unable to create output file '{s}': {s}", .{ some, errorDescription(er) }) else - std.fs.File.stdout(); + Io.File.stdout(); defer if (d.output_name != null) file.close(io); var file_writer = file.writer(&writer_buf); @@ -1369,7 +1370,7 @@ fn processSource( defer tree.deinit(); if (d.verbose_ast) { - var stdout = std.fs.File.stdout().writer(&writer_buf); + var stdout = Io.File.stdout().writer(&writer_buf); tree.dump(d.detectConfig(stdout.file), &stdout.interface) catch {}; } @@ -1433,7 +1434,7 @@ fn processSource( defer ir.deinit(gpa); if (d.verbose_ir) { - var stdout = std.fs.File.stdout().writer(&writer_buf); + var stdout = Io.File.stdout().writer(&writer_buf); ir.dump(gpa, d.detectConfig(stdout.file), &stdout.interface) catch {}; } @@ -1499,7 +1500,7 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compil if (d.verbose_linker_args) { var stdout_buf: [4096]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&stdout_buf); + var stdout = Io.File.stdout().writer(&stdout_buf); dumpLinkerArgs(&stdout.interface, argv.items) catch { return d.fatal("unable to dump linker args: {s}", .{errorDescription(stdout.err.?)}); }; diff --git a/lib/compiler/aro/aro/Preprocessor.zig b/lib/compiler/aro/aro/Preprocessor.zig index 6bd1206aff5e880bd93376d257fbf9bbff0e40a8..d47727cbf0a94c25291b7d76aa11c12d80a04e7e 100644 --- a/lib/compiler/aro/aro/Preprocessor.zig +++ b/lib/compiler/aro/aro/Preprocessor.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const mem = std.mem; const Allocator = mem.Allocator; const assert = std.debug.assert; @@ -1068,7 +1069,7 @@ fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start }); var stderr_buf: [4096]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&stderr_buf); + var stderr = Io.File.stderr().writer(&stderr_buf); const w = &stderr.interface; w.print("{s}:{d}:{d}: ", .{ source.path, line_col.line_no, line_col.col }) catch return; diff --git a/lib/compiler/aro/backend/Assembly.zig b/lib/compiler/aro/backend/Assembly.zig index d0d14bdd77d1b9d1bc78b8eb85ee6b2b2abbd770..80143bf97f91b6fa79426c1e19cfbe1729586484 100644 --- a/lib/compiler/aro/backend/Assembly.zig +++ b/lib/compiler/aro/backend/Assembly.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; data: []const u8, @@ -11,7 +12,7 @@ pub fn deinit(self: *const Assembly, gpa: Allocator) void { gpa.free(self.text); } -pub fn writeToFile(self: Assembly, file: std.fs.File) !void { +pub fn writeToFile(self: Assembly, file: Io.File) !void { var file_writer = file.writer(&.{}); var buffers = [_][]const u8{ self.data, self.text }; diff --git a/lib/compiler/aro/main.zig b/lib/compiler/aro/main.zig index d3655e43da9205f180db0c443e1b95bab2cf553d..66c8add4a32343fd38e5e71b90348da604dca273 100644 --- a/lib/compiler/aro/main.zig +++ b/lib/compiler/aro/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const Allocator = mem.Allocator; const mem = std.mem; const process = std.process; @@ -50,7 +51,7 @@ pub fn main() u8 { defer gpa.free(aro_name); var stderr_buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&stderr_buf); + var stderr = Io.File.stderr().writer(&stderr_buf); var diagnostics: Diagnostics = .{ .output = .{ .to_writer = .{ .color = .detect(stderr.file), diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 2c8d71e3e2b83d487a07d0ad72301a6a0c503a5d..6e0e2d8ecaaa793c37b4fa23df7edba67d7e8534 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -7,7 +7,7 @@ const assert = std.debug.assert; const fmt = std.fmt; const mem = std.mem; const process = std.process; -const File = std.fs.File; +const File = std.Io.File; const Step = std.Build.Step; const Watch = std.Build.Watch; const WebServer = std.Build.WebServer; @@ -1845,9 +1845,9 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void { } var stdio_buffer_allocation: [256]u8 = undefined; -var stdout_writer_allocation: std.fs.File.Writer = undefined; +var stdout_writer_allocation: Io.File.Writer = undefined; fn initStdoutWriter() *Writer { - stdout_writer_allocation = std.fs.File.stdout().writerStreaming(&stdio_buffer_allocation); + stdout_writer_allocation = Io.File.stdout().writerStreaming(&stdio_buffer_allocation); return &stdout_writer_allocation.interface; } diff --git a/lib/compiler/libc.zig b/lib/compiler/libc.zig index a18a7a0e06ff2ef5f5a4812b60369d62a7486f7d..142b87062eece7b5015ec2c8ff7666a9546fce7c 100644 --- a/lib/compiler/libc.zig +++ b/lib/compiler/libc.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const mem = std.mem; const LibCInstallation = std.zig.LibCInstallation; @@ -39,7 +40,7 @@ pub fn main() !void { var input_file: ?[]const u8 = null; var target_arch_os_abi: []const u8 = "native"; var print_includes: bool = false; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); const stdout = &stdout_writer.interface; { var i: usize = 2; diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 1608c121b15383cef17374f58d5f48224b390ba8..c360ea8df09da5ba9e609ff5bcbdfbe494cf747b 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -1,12 +1,13 @@ const builtin = @import("builtin"); + const std = @import("std"); +const Io = std.Io; const mem = std.mem; const fs = std.fs; const elf = std.elf; const Allocator = std.mem.Allocator; -const File = std.fs.File; +const File = std.Io.File; const assert = std.debug.assert; - const fatal = std.process.fatal; const Server = std.zig.Server; @@ -56,7 +57,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void fatal("unexpected positional argument: '{s}'", .{arg}); } } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - return std.fs.File.stdout().writeAll(usage); + return Io.File.stdout().writeAll(usage); } else if (mem.eql(u8, arg, "-O") or mem.eql(u8, arg, "--output-target")) { i += 1; if (i >= args.len) fatal("expected another argument after '{s}'", .{arg}); @@ -177,7 +178,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void } }; - const mode = if (out_fmt != .elf or only_keep_debug) fs.File.default_mode else stat.mode; + const mode = if (out_fmt != .elf or only_keep_debug) Io.File.default_mode else stat.mode; var output_file = try fs.cwd().createFile(output, .{ .mode = mode }); defer output_file.close(io); @@ -221,8 +222,8 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void try out.end(); if (listen) { - var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer); - var stdout_writer = fs.File.stdout().writer(&stdout_buffer); + var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); var server = try Server.init(.{ .in = &stdin_reader.interface, .out = &stdout_writer.interface, diff --git a/lib/compiler/reduce.zig b/lib/compiler/reduce.zig index 28305e801b7b339e0cf63ef8e51b2d6050fe1db5..bbd3d172b4833acd79774e9c427b167774c89171 100644 --- a/lib/compiler/reduce.zig +++ b/lib/compiler/reduce.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const mem = std.mem; const Allocator = std.mem.Allocator; const assert = std.debug.assert; @@ -68,7 +69,7 @@ pub fn main() !void { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - const stdout = std.fs.File.stdout(); + const stdout = Io.File.stdout(); try stdout.writeAll(usage); return std.process.cleanExit(); } else if (mem.eql(u8, arg, "--")) { diff --git a/lib/compiler/resinator/cli.zig b/lib/compiler/resinator/cli.zig index ffaa62e7ca5ad25ed491aea528baafd6e21b78ff..17da5c7b75d112799d4d86c9d4daa75dfbada517 100644 --- a/lib/compiler/resinator/cli.zig +++ b/lib/compiler/resinator/cli.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const code_pages = @import("code_pages.zig"); const SupportedCodePage = code_pages.SupportedCodePage; const lang = @import("lang.zig"); @@ -169,7 +170,7 @@ pub const Options = struct { coff_options: cvtres.CoffOptions = .{}, pub const IoSource = union(enum) { - stdio: std.fs.File, + stdio: Io.File, filename: []const u8, }; pub const AutoIncludes = enum { any, msvc, gnu, none }; diff --git a/lib/compiler/resinator/errors.zig b/lib/compiler/resinator/errors.zig index 8509aa610fb1fdf00e9d3f05664fbd4742e6bc24..61fd00e683b0a1381f0215fe2e300ac6ddeb8935 100644 --- a/lib/compiler/resinator/errors.zig +++ b/lib/compiler/resinator/errors.zig @@ -169,9 +169,9 @@ pub const ErrorDetails = struct { filename_string_index: FilenameStringIndex, pub const FilenameStringIndex = std.meta.Int(.unsigned, 32 - @bitSizeOf(FileOpenErrorEnum)); - pub const FileOpenErrorEnum = std.meta.FieldEnum(std.fs.File.OpenError || std.fs.File.StatError); + pub const FileOpenErrorEnum = std.meta.FieldEnum(Io.File.OpenError || Io.File.StatError); - pub fn enumFromError(err: (std.fs.File.OpenError || std.fs.File.StatError)) FileOpenErrorEnum { + pub fn enumFromError(err: (Io.File.OpenError || Io.File.StatError)) FileOpenErrorEnum { return switch (err) { inline else => |e| @field(ErrorDetails.FileOpenError.FileOpenErrorEnum, @errorName(e)), }; @@ -1094,8 +1094,8 @@ const CorrespondingLines = struct { last_byte: u8 = 0, at_eof: bool = false, span: SourceMappings.CorrespondingSpan, - file: std.fs.File, - file_reader: std.fs.File.Reader, + file: Io.File, + file_reader: Io.File.Reader, code_page: SupportedCodePage, pub fn init( diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 42308a8987f287474507605ed2977d231367609b..e171d8199c0b7b6da502b894027e50a77e71cd13 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -45,7 +45,7 @@ pub fn main() !void { } var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); const stdout = &stdout_writer.interface; var error_handler: ErrorHandler = switch (zig_integration) { true => .{ @@ -447,8 +447,8 @@ const IoStream = struct { } pub const Source = union(enum) { - file: std.fs.File, - stdio: std.fs.File, + file: Io.File, + stdio: Io.File, memory: std.ArrayList(u8), /// The source has been closed and any usage of the Source in this state is illegal (except deinit). closed: void, @@ -500,10 +500,10 @@ const IoStream = struct { } pub const Writer = union(enum) { - file: std.fs.File.Writer, + file: Io.File.Writer, allocating: std.Io.Writer.Allocating, - pub const Error = Allocator.Error || std.fs.File.WriteError; + pub const Error = Allocator.Error || Io.File.WriteError; pub fn interface(this: *@This()) *std.Io.Writer { return switch (this.*) { diff --git a/lib/compiler/resinator/utils.zig b/lib/compiler/resinator/utils.zig index f8080539cb31ddb56579a25281dc04d8c14c00bb..e8565f3af904192fe8e9f598612fd93ccce61ba9 100644 --- a/lib/compiler/resinator/utils.zig +++ b/lib/compiler/resinator/utils.zig @@ -32,8 +32,8 @@ pub fn openFileNotDir( cwd: std.fs.Dir, io: Io, path: []const u8, - flags: std.fs.File.OpenFlags, -) (std.fs.File.OpenError || std.fs.File.StatError)!std.fs.File { + flags: Io.File.OpenFlags, +) (Io.File.OpenError || Io.File.StatError)!Io.File { const file = try cwd.openFile(io, path, flags); errdefer file.close(io); // https://github.com/ziglang/zig/issues/5732 diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 72ed3e76776dfe672f58492a0656b585c59823d0..07a6724ec090bb65307eb584d09b7945c2bad7b4 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -74,8 +74,8 @@ pub fn main() void { fn mainServer() !void { @disableInstrumentation(); - var stdin_reader = std.fs.File.stdin().readerStreaming(runner_threaded_io.io(), &stdin_buffer); - var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer); + var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io.io(), &stdin_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); var server = try std.zig.Server.init(.{ .in = &stdin_reader.interface, .out = &stdout_writer.interface, @@ -228,7 +228,7 @@ fn mainTerminal() void { .root_name = "Test", .estimated_total_items = test_fn_list.len, }); - const have_tty = std.fs.File.stderr().isTty(); + const have_tty = Io.File.stderr().isTty(); var leaks: usize = 0; for (test_fn_list, 0..) |test_fn, i| { @@ -318,7 +318,7 @@ pub fn log( /// work-in-progress backends can handle it. pub fn mainSimple() anyerror!void { @disableInstrumentation(); - // is the backend capable of calling `std.fs.File.writeAll`? + // is the backend capable of calling `Io.File.writeAll`? const enable_write = switch (builtin.zig_backend) { .stage2_aarch64, .stage2_riscv64 => true, else => false, @@ -334,7 +334,7 @@ pub fn mainSimple() anyerror!void { var failed: u64 = 0; // we don't want to bring in File and Writer if the backend doesn't support it - const stdout = if (enable_write) std.fs.File.stdout() else {}; + const stdout = if (enable_write) Io.File.stdout() else {}; for (builtin.test_functions) |test_fn| { if (enable_write) { diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index 0c72298b30d8c95675fda610d1871594fbcc5af1..830c70e4246b4b1dc9860fcd7bfade50ab372269 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const mem = std.mem; const process = std.process; @@ -34,7 +35,7 @@ pub fn main() u8 { } var stderr_buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&stderr_buf); + var stderr = Io.File.stderr().writer(&stderr_buf); var diagnostics: aro.Diagnostics = switch (zig_integration) { false => .{ .output = .{ .to_writer = .{ .color = .detect(stderr.file), @@ -99,7 +100,7 @@ fn serveErrorBundle(arena: std.mem.Allocator, diagnostics: *const aro.Diagnostic "translation failure", ); var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); var server: std.zig.Server = .{ .out = &stdout_writer.interface, .in = undefined, @@ -129,13 +130,13 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration args[i] = arg; if (mem.eql(u8, arg, "--help")) { var stdout_buf: [512]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&stdout_buf); + var stdout = Io.File.stdout().writer(&stdout_buf); try stdout.interface.print(usage, .{args[0]}); try stdout.interface.flush(); return; } else if (mem.eql(u8, arg, "--version")) { var stdout_buf: [512]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&stdout_buf); + var stdout = Io.File.stdout().writer(&stdout_buf); // TODO add version try stdout.interface.writeAll("0.0.0-dev\n"); try stdout.interface.flush(); @@ -228,7 +229,7 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration d.comp.cwd.createFile(path, .{}) catch |er| return d.fatal("unable to create dependency file '{s}': {s}", .{ path, aro.Driver.errorDescription(er) }) else - std.fs.File.stdout(); + Io.File.stdout(); defer if (dep_file_name != null) file.close(io); var file_writer = file.writer(&out_buf); @@ -246,7 +247,7 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration var close_out_file = false; var out_file_path: []const u8 = ""; - var out_file: std.fs.File = .stdout(); + var out_file: Io.File = .stdout(); defer if (close_out_file) out_file.close(io); if (d.output_name) |path| blk: { diff --git a/lib/std/Build.zig b/lib/std/Build.zig index fcd94ce1345cd629d0d240952bd65a668e02cee4..2755b895c241679d52950914330e0c6b669a4679 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1,3 +1,4 @@ +const Build = @This(); const builtin = @import("builtin"); const std = @import("std.zig"); @@ -9,13 +10,12 @@ const panic = std.debug.panic; const assert = debug.assert; const log = std.log; const StringHashMap = std.StringHashMap; -const Allocator = mem.Allocator; +const Allocator = std.mem.Allocator; const Target = std.Target; const process = std.process; const EnvMap = std.process.EnvMap; -const File = fs.File; +const File = std.Io.File; const Sha256 = std.crypto.hash.sha2.Sha256; -const Build = @This(); const ArrayList = std.ArrayList; pub const Cache = @import("Build/Cache.zig"); diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index acde47071ddb46551e272c04857362148d885b6e..2b1e6d8ace5f86412636d7472f12a995ff31fbd7 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -667,7 +667,7 @@ fn clearZigProcess(s: *Step, gpa: Allocator) void { } } -fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { +fn sendMessage(file: Io.File, tag: std.zig.Client.Message.Tag) !void { const header: std.zig.Client.Message.Header = .{ .tag = tag, .bytes_len = 0, diff --git a/lib/std/Build/Step/ObjCopy.zig b/lib/std/Build/Step/ObjCopy.zig index b5f058ddfc00c328bd1ef58730feddd87141af16..4aa1c0a9dca73359cb6fdbe2c0b0d6bb69316d6f 100644 --- a/lib/std/Build/Step/ObjCopy.zig +++ b/lib/std/Build/Step/ObjCopy.zig @@ -3,7 +3,7 @@ const ObjCopy = @This(); const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; -const File = std.fs.File; +const File = std.Io.File; const InstallDir = std.Build.InstallDir; const Step = std.Build.Step; const elf = std.elf; diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index e66e30cc790ad5b2bf7083504981f6f50ea7bd9e..1df6f42a353fd87489332e49c3e1bd94b3580f78 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1,15 +1,16 @@ -const std = @import("std"); +const Run = @This(); const builtin = @import("builtin"); + +const std = @import("std"); +const Io = std.Io; const Build = std.Build; -const Step = Build.Step; +const Step = std.Build.Step; const fs = std.fs; const mem = std.mem; const process = std.process; -const EnvMap = process.EnvMap; +const EnvMap = std.process.EnvMap; const assert = std.debug.assert; -const Path = Build.Cache.Path; - -const Run = @This(); +const Path = std.Build.Cache.Path; pub const base_id: Step.Id = .run; @@ -2095,7 +2096,7 @@ pub const CachedTestMetadata = struct { } }; -fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { +fn requestNextTest(in: Io.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { while (metadata.next_index < metadata.names.len) { const i = metadata.next_index; metadata.next_index += 1; @@ -2114,7 +2115,7 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr } } -fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { +fn sendMessage(file: Io.File, tag: std.zig.Client.Message.Tag) !void { const header: std.zig.Client.Message.Header = .{ .tag = tag, .bytes_len = 0, @@ -2125,7 +2126,7 @@ fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { }; } -fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index: u32) !void { +fn sendRunTestMessage(file: Io.File, tag: std.zig.Client.Message.Tag, index: u32) !void { const header: std.zig.Client.Message.Header = .{ .tag = tag, .bytes_len = 4, @@ -2140,7 +2141,7 @@ fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index: } fn sendRunFuzzTestMessage( - file: std.fs.File, + file: Io.File, index: u32, kind: std.Build.abi.fuzz.LimitKind, amount_or_instance: u64, diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 0727e3efd331ffac356be6785a9b302a9db12204..f783718cef16597326ecb0d912fb527ed9187013 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -528,7 +528,7 @@ pub fn Poller(comptime StreamEnum: type) type { /// Given an enum, returns a struct with fields of that enum, each field /// representing an I/O stream for polling. pub fn PollFiles(comptime StreamEnum: type) type { - return @Struct(.auto, null, std.meta.fieldNames(StreamEnum), &@splat(std.fs.File), &@splat(.{})); + return @Struct(.auto, null, std.meta.fieldNames(StreamEnum), &@splat(Io.File), &@splat(.{})); } test { diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 5601293cfb3a0498aed9d3ff1dd7dea263537829..3f25bc6a26ce01151a2309ba49bbc880214dfcab 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -1,7 +1,8 @@ +const Writer = @This(); + const builtin = @import("builtin"); const native_endian = builtin.target.cpu.arch.endian(); -const Writer = @This(); const std = @import("../std.zig"); const assert = std.debug.assert; const Limit = std.Io.Limit; @@ -2837,7 +2838,7 @@ test "discarding sendFile" { const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); defer file.close(io); var r_buffer: [256]u8 = undefined; - var file_writer: std.fs.File.Writer = .init(file, &r_buffer); + var file_writer: File.Writer = .init(file, &r_buffer); try file_writer.interface.writeByte('h'); try file_writer.interface.flush(); @@ -2859,7 +2860,7 @@ test "allocating sendFile" { const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); defer file.close(io); var r_buffer: [2]u8 = undefined; - var file_writer: std.fs.File.Writer = .init(file, &r_buffer); + var file_writer: File.Writer = .init(file, &r_buffer); try file_writer.interface.writeAll("abcd"); try file_writer.interface.flush(); @@ -2883,7 +2884,7 @@ test sendFileReading { const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); defer file.close(io); var r_buffer: [2]u8 = undefined; - var file_writer: std.fs.File.Writer = .init(file, &r_buffer); + var file_writer: File.Writer = .init(file, &r_buffer); try file_writer.interface.writeAll("abcd"); try file_writer.interface.flush(); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 9ea2d48ee5a894bb96b6523f32b052cf7fc24b88..b922acc3332608d191a6b7eb527c4ad32736dd6e 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -10,7 +10,7 @@ const expectError = std.testing.expectError; const DefaultPrng = std.Random.DefaultPrng; const mem = std.mem; const fs = std.fs; -const File = std.fs.File; +const File = std.Io.File; const assert = std.debug.assert; const tmpDir = std.testing.tmpDir; diff --git a/lib/std/Io/tty.zig b/lib/std/Io/tty.zig index 08e0bd71f0716d82ba9f24645db6beedd582fb72..d1f8b576c22015f8e69e5a2599c21c24fc1ac0e4 100644 --- a/lib/std/Io/tty.zig +++ b/lib/std/Io/tty.zig @@ -1,9 +1,10 @@ -const std = @import("std"); const builtin = @import("builtin"); -const File = std.fs.File; +const native_os = builtin.os.tag; + +const std = @import("std"); +const File = std.Io.File; const process = std.process; const windows = std.os.windows; -const native_os = builtin.os.tag; pub const Color = enum { black, diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 467d4754ff3c84cf885cd538810ffccf45f2a6b6..d8b22c2db0f3a3819fce9f6186f1b42ef722f783 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -1,19 +1,21 @@ //! This API is non-allocating, non-fallible, thread-safe, and lock-free. +const Progress = @This(); -const std = @import("std"); const builtin = @import("builtin"); +const is_big_endian = builtin.cpu.arch.endian() == .big; +const is_windows = builtin.os.tag == .windows; + +const std = @import("std"); +const Io = std.Io; const windows = std.os.windows; const testing = std.testing; const assert = std.debug.assert; -const Progress = @This(); const posix = std.posix; -const is_big_endian = builtin.cpu.arch.endian() == .big; -const is_windows = builtin.os.tag == .windows; const Writer = std.Io.Writer; /// `null` if the current node (and its children) should /// not print on update() -terminal: std.fs.File, +terminal: Io.File, terminal_mode: TerminalMode, @@ -472,7 +474,7 @@ pub fn start(options: Options) Node { if (options.disable_printing) { return Node.none; } - const stderr: std.fs.File = .stderr(); + const stderr: Io.File = .stderr(); global_progress.terminal = stderr; if (stderr.enableAnsiEscapeCodes()) |_| { global_progress.terminal_mode = .ansi_escape_codes; @@ -633,8 +635,8 @@ pub fn unlockStdErr() void { /// Protected by `stderr_mutex`. const stderr_writer: *Writer = &stderr_file_writer.interface; /// Protected by `stderr_mutex`. -var stderr_file_writer: std.fs.File.Writer = .{ - .interface = std.fs.File.Writer.initInterface(&.{}), +var stderr_file_writer: Io.File.Writer = .{ + .interface = Io.File.Writer.initInterface(&.{}), .file = if (is_windows) undefined else .stderr(), .mode = .streaming, }; diff --git a/lib/std/Random/benchmark.zig b/lib/std/Random/benchmark.zig index 57dc69051e6df5198455d1bdb30a21c325edca05..97afe23b95ef45a6468e84b9f72ce00031da6380 100644 --- a/lib/std/Random/benchmark.zig +++ b/lib/std/Random/benchmark.zig @@ -1,7 +1,9 @@ // zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig -const std = @import("std"); const builtin = @import("builtin"); + +const std = @import("std"); +const Io = std.Io; const time = std.time; const Timer = time.Timer; const Random = std.Random; @@ -123,7 +125,7 @@ fn mode(comptime x: comptime_int) comptime_int { pub fn main() !void { var stdout_buffer: [0x100]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); const stdout = &stdout_writer.interface; var buffer: [1024]u8 = undefined; diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 9f532c3bec981a169eea9b85af52071f5be0edfc..8d8e5979df19ca443431b7b4598ccd2454cbca40 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -175,7 +175,7 @@ pub const SetNameError = error{ Unsupported, Unexpected, InvalidWtf8, -} || posix.PrctlError || posix.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError; +} || posix.PrctlError || posix.WriteError || Io.File.OpenError || std.fmt.BufPrintError; pub fn setName(self: Thread, io: Io, name: []const u8) SetNameError!void { if (name.len > max_name_len) return error.NameTooLong; @@ -293,7 +293,7 @@ pub fn setName(self: Thread, io: Io, name: []const u8) SetNameError!void { pub const GetNameError = error{ Unsupported, Unexpected, -} || posix.PrctlError || posix.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError; +} || posix.PrctlError || posix.ReadError || Io.File.OpenError || std.fmt.BufPrintError; /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index 9541e01db56fcd51ef3f605ae8db83f10c0c0385..1d21918b5c3337d31e4bd1e25e17c3279dab8aa0 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -171,7 +171,7 @@ fn rescanWindows(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanW cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len); } -pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError; +pub const AddCertsFromDirPathError = Io.File.OpenError || AddCertsFromDirError; pub fn addCertsFromDirPath( cb: *Bundle, @@ -212,7 +212,7 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, i } } -pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError || Io.Clock.Error; +pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError || Io.Clock.Error; pub fn addCertsFromFilePathAbsolute( cb: *Bundle, @@ -242,8 +242,8 @@ pub fn addCertsFromFilePath( } pub const AddCertsFromFileError = Allocator.Error || - fs.File.GetSeekPosError || - fs.File.ReadError || + Io.File.GetSeekPosError || + Io.File.ReadError || ParseCertError || std.base64.Error || error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker, Streaming }; diff --git a/lib/std/crypto/Certificate/Bundle/macos.zig b/lib/std/crypto/Certificate/Bundle/macos.zig index d32f1be8e0c3b2f56f970bf77c7b43b7410a2ba6..473505ac51f6d283cf32667e96a9565288f4962e 100644 --- a/lib/std/crypto/Certificate/Bundle/macos.zig +++ b/lib/std/crypto/Certificate/Bundle/macos.zig @@ -6,7 +6,7 @@ const mem = std.mem; const Allocator = std.mem.Allocator; const Bundle = @import("../Bundle.zig"); -pub const RescanMacError = Allocator.Error || fs.File.OpenError || fs.File.ReadError || fs.File.SeekError || Bundle.ParseCertError || error{EndOfStream}; +pub const RescanMacError = Allocator.Error || Io.File.OpenError || Io.File.ReadError || Io.File.SeekError || Bundle.ParseCertError || error{EndOfStream}; pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanMacError!void { cb.bytes.clearRetainingCapacity(); diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 54024f070ec6dfb0e743de7e714afc49e5ed9b09..1b71110be52623ba22d58e438a695736cc03e3a8 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -1,10 +1,12 @@ // zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig -const std = @import("std"); const builtin = @import("builtin"); + +const std = @import("std"); +const Io = std.Io; const mem = std.mem; const time = std.time; -const Timer = time.Timer; +const Timer = std.time.Timer; const crypto = std.crypto; const KiB = 1024; @@ -504,7 +506,7 @@ fn mode(comptime x: comptime_int) comptime_int { pub fn main() !void { // Size of buffer is about size of printed message. var stdout_buffer: [0x100]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); const stdout = &stdout_writer.interface; var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 0cb96ed593bc7df89716e52543053f7aa1b63479..97741ecb40598edc97beafae430951b0adbcc51d 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -8,7 +8,7 @@ const posix = std.posix; const fs = std.fs; const testing = std.testing; const Allocator = mem.Allocator; -const File = std.fs.File; +const File = std.Io.File; const windows = std.os.windows; const builtin = @import("builtin"); @@ -575,7 +575,7 @@ pub fn defaultPanic( // A panic happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). - fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {}; + File.stderr().writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. } @@ -1596,7 +1596,7 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex // A segfault happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). - fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {}; + File.stderr().writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. } diff --git a/lib/std/debug/ElfFile.zig b/lib/std/debug/ElfFile.zig index 92bcca1bcf3a977d05899c0a6cb7a5b15648fde2..5dbae181308792727d1df7f0e375cf115e0e887a 100644 --- a/lib/std/debug/ElfFile.zig +++ b/lib/std/debug/ElfFile.zig @@ -123,7 +123,7 @@ pub const LoadError = error{ pub fn load( gpa: Allocator, - elf_file: std.fs.File, + elf_file: Io.File, opt_build_id: ?[]const u8, di_search_paths: *const DebugInfoSearchPaths, ) LoadError!ElfFile { @@ -423,7 +423,7 @@ const LoadInnerResult = struct { }; fn loadInner( arena: Allocator, - elf_file: std.fs.File, + elf_file: Io.File, opt_crc: ?u32, ) (LoadError || error{ CrcMismatch, Streaming, Canceled })!LoadInnerResult { const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: { diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index 9268ca0247eddcbfd62252e9704c50eda9e1c91e..da7656e62646adf66c086dbfef9db32f5173bb9a 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -27,7 +27,7 @@ coverage: *Coverage, pub const LoadError = error{ MissingDebugInfo, UnsupportedDebugInfo, -} || std.fs.File.OpenError || ElfFile.LoadError || MachOFile.Error || std.debug.Dwarf.ScanError; +} || Io.File.OpenError || ElfFile.LoadError || MachOFile.Error || std.debug.Dwarf.ScanError; pub fn load( gpa: Allocator, diff --git a/lib/std/debug/Pdb.zig b/lib/std/debug/Pdb.zig index c10b361f72934cc71e958e54491c260285b97311..3ecfd1b3637713954751dd2bf4835819683bd88a 100644 --- a/lib/std/debug/Pdb.zig +++ b/lib/std/debug/Pdb.zig @@ -1,5 +1,5 @@ const std = @import("../std.zig"); -const File = std.fs.File; +const File = std.Io.File; const Allocator = std.mem.Allocator; const pdb = std.pdb; const assert = std.debug.assert; diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index 557f3901eb58ab21f892c215fede4725a216ed29..3af7223293aaf7ac9b71c76012fd60d253ccb955 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -204,7 +204,7 @@ const Module = struct { coff_section_headers: []coff.SectionHeader, const MappedFile = struct { - file: fs.File, + file: Io.File, section_handle: windows.HANDLE, section_view: []const u8, fn deinit(mf: *const MappedFile, io: Io) void { diff --git a/lib/std/debug/simple_panic.zig b/lib/std/debug/simple_panic.zig index 45e97777c4ba9d9389df95e6f211bd4387a4bc20..f6ff77e04f561a2f699f2867dcf7bc664c559148 100644 --- a/lib/std/debug/simple_panic.zig +++ b/lib/std/debug/simple_panic.zig @@ -15,7 +15,7 @@ pub fn call(msg: []const u8, ra: ?usize) noreturn { @branchHint(.cold); _ = ra; std.debug.lockStdErr(); - const stderr: std.fs.File = .stderr(); + const stderr: std.Io.File = .stderr(); stderr.writeAll(msg) catch {}; @trap(); } diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index c91056b0abb613938ab7e1f58424891496c8142e..ca36d5cbb94131e808edcca23130d5a8d2e5255a 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -225,7 +225,7 @@ pub const ElfDynLib = struct { const fd = try resolveFromName(io, path); defer posix.close(fd); - const file: std.fs.File = .{ .handle = fd }; + const file: Io.File = .{ .handle = fd }; const stat = try file.stat(); const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 15b8e9b558f55548057822ef86690f21fd25be5d..f770ddd30edb7efd678c16f17311218ff90e71f0 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -12,7 +12,7 @@ const posix = std.posix; const ArenaAllocator = std.heap.ArenaAllocator; const Dir = std.fs.Dir; -const File = std.fs.File; +const File = std.Io.File; const tmpDir = testing.tmpDir; const SymLinkFlags = std.fs.Dir.SymLinkFlags; @@ -2231,7 +2231,7 @@ test "read file non vectored" { const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); defer file.close(io); { - var file_writer: std.fs.File.Writer = .init(file, &.{}); + var file_writer: File.Writer = .init(file, &.{}); try file_writer.interface.writeAll(contents); try file_writer.interface.flush(); } @@ -2263,7 +2263,7 @@ test "seek keeping partial buffer" { const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); defer file.close(io); { - var file_writer: std.fs.File.Writer = .init(file, &.{}); + var file_writer: File.Writer = .init(file, &.{}); try file_writer.interface.writeAll(contents); try file_writer.interface.flush(); } @@ -2325,7 +2325,7 @@ test "seekTo flushes buffered data" { defer file.close(io); { var buf: [16]u8 = undefined; - var file_writer = std.fs.File.writer(file, &buf); + var file_writer = File.writer(file, &buf); try file_writer.interface.writeAll(contents); try file_writer.seekTo(8); diff --git a/lib/std/hash/benchmark.zig b/lib/std/hash/benchmark.zig index a21d6e9ada27f760ab18e7d0d396e7fff65839cc..6744b87facaed5a65d6e1a987766af3e638931e7 100644 --- a/lib/std/hash/benchmark.zig +++ b/lib/std/hash/benchmark.zig @@ -1,7 +1,8 @@ // zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig - -const std = @import("std"); const builtin = @import("builtin"); + +const std = @import("std"); +const Io = std.Io; const time = std.time; const Timer = time.Timer; const hash = std.hash; @@ -354,7 +355,7 @@ fn mode(comptime x: comptime_int) comptime_int { pub fn main() !void { var stdout_buffer: [0x100]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); const stdout = &stdout_writer.interface; var buffer: [1024]u8 = undefined; diff --git a/lib/std/http.zig b/lib/std/http.zig index a768372eccae6c7a61f510106c05d52c1bc1daa7..291e22539b312f8a4ca497e0686ca51e17162083 100644 --- a/lib/std/http.zig +++ b/lib/std/http.zig @@ -2,7 +2,7 @@ const builtin = @import("builtin"); const std = @import("std.zig"); const assert = std.debug.assert; const Writer = std.Io.Writer; -const File = std.fs.File; +const File = std.Io.File; pub const Client = @import("http/Client.zig"); pub const Server = @import("http/Server.zig"); diff --git a/lib/std/pdb.zig b/lib/std/pdb.zig index 36b0e04e5cfdf320adbd7e0ddd62597cd3421021..7e479de8d4fe196594c6184b9721d11f31e2051d 100644 --- a/lib/std/pdb.zig +++ b/lib/std/pdb.zig @@ -12,7 +12,7 @@ const math = std.math; const mem = std.mem; const coff = std.coff; const fs = std.fs; -const File = std.fs.File; +const File = std.Io.File; const debug = std.debug; const ArrayList = std.ArrayList; diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 8889e50ea344c5f1d5a6dcaa5ddbf3bbb33d738f..82fa2c41d11a188c4b323ce6783e304663ff7a14 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -1,20 +1,20 @@ +const builtin = @import("builtin"); +const native_os = builtin.target.os.tag; + const std = @import("../std.zig"); +const Io = std.Io; const posix = std.posix; const testing = std.testing; -const expect = testing.expect; -const expectEqual = testing.expectEqual; -const expectError = testing.expectError; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectError = std.testing.expectError; const fs = std.fs; const mem = std.mem; const elf = std.elf; const linux = std.os.linux; - const a = std.testing.allocator; - -const builtin = @import("builtin"); const AtomicRmwOp = std.builtin.AtomicRmwOp; const AtomicOrder = std.builtin.AtomicOrder; -const native_os = builtin.target.os.tag; const tmpDir = std.testing.tmpDir; const AT = posix.AT; @@ -663,14 +663,14 @@ test "dup & dup2" { var file = try tmp.dir.createFile("os_dup_test", .{}); defer file.close(io); - var duped = std.fs.File{ .handle = try posix.dup(file.handle) }; + var duped = Io.File{ .handle = try posix.dup(file.handle) }; defer duped.close(io); try duped.writeAll("dup"); // Tests aren't run in parallel so using the next fd shouldn't be an issue. const new_fd = duped.handle + 1; try posix.dup2(file.handle, new_fd); - var dup2ed = std.fs.File{ .handle = new_fd }; + var dup2ed = Io.File{ .handle = new_fd }; defer dup2ed.close(io); try dup2ed.writeAll("dup2"); } diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index be3026ff1000542b1b561a73a89d135890f21239..87d2fe3ba9848c948fba2a4484765bf5b476f907 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -8,7 +8,7 @@ const Io = std.Io; const unicode = std.unicode; const fs = std.fs; const process = std.process; -const File = std.fs.File; +const File = std.Io.File; const windows = std.os.windows; const linux = std.os.linux; const posix = std.posix; @@ -1055,7 +1055,7 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { fn writeIntFd(fd: i32, value: ErrInt) !void { var buffer: [8]u8 = undefined; - var fw: std.fs.File.Writer = .initStreaming(.{ .handle = fd }, &buffer); + var fw: File.Writer = .initStreaming(.{ .handle = fd }, &buffer); fw.interface.writeInt(u64, value, .little) catch unreachable; fw.interface.flush() catch return error.SystemResources; } diff --git a/lib/std/unicode/throughput_test.zig b/lib/std/unicode/throughput_test.zig index fd3f46ec584a5ddc96dfdf37df38f2c517777080..c02f550a4a74bb9ff787bba9ac485a7d9ebc7002 100644 --- a/lib/std/unicode/throughput_test.zig +++ b/lib/std/unicode/throughput_test.zig @@ -1,8 +1,8 @@ const std = @import("std"); +const Io = std.Io; const time = std.time; const unicode = std.unicode; - -const Timer = time.Timer; +const Timer = std.time.Timer; const N = 1_000_000; @@ -41,7 +41,7 @@ fn benchmarkCodepointCount(buf: []const u8) !ResultCount { pub fn main() !void { // Size of buffer is about size of printed message. var stdout_buffer: [0x100]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); const stdout = &stdout_writer.interface; try stdout.print("short ASCII strings\n", .{}); diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 09c785613fc7c9ccbf730d3666b02fb70b1f5bf0..37ce7b4cfa120b17901a0600810ac27acdf6a406 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -11,9 +11,11 @@ //! * In the future, possibly inline assembly, which needs to get parsed and //! handled by the codegen backend, and errors reported there. However for now, //! inline assembly is not an exception. - -const std = @import("std"); +const Zir = @This(); const builtin = @import("builtin"); + +const std = @import("std"); +const Io = std.Io; const mem = std.mem; const Allocator = std.mem.Allocator; const assert = std.debug.assert; @@ -21,8 +23,6 @@ const BigIntConst = std.math.big.int.Const; const BigIntMutable = std.math.big.int.Mutable; const Ast = std.zig.Ast; -const Zir = @This(); - instructions: std.MultiArrayList(Inst).Slice, /// In order to store references to strings in fewer bytes, we copy all /// string bytes into here. String bytes can be null. It is up to whomever @@ -45,7 +45,7 @@ pub const Header = extern struct { /// it's essentially free to have a zero field here and makes the warning go away, /// making it more likely that following Valgrind warnings will be taken seriously. unused: u32 = 0, - stat_inode: std.fs.File.INode, + stat_inode: Io.File.INode, stat_size: u64, stat_mtime: i128, }; diff --git a/lib/std/zig/Zoir.zig b/lib/std/zig/Zoir.zig index 08a7fc96396e32840065d1f430b550bc5bfc709f..d82b8f1861f3e8b645e79651f55aea3da3d8ccce 100644 --- a/lib/std/zig/Zoir.zig +++ b/lib/std/zig/Zoir.zig @@ -1,6 +1,13 @@ //! Zig Object Intermediate Representation. //! Simplified AST for the ZON (Zig Object Notation) format. //! `ZonGen` converts `Ast` to `Zoir`. +const Zoir = @This(); + +const std = @import("std"); +const Io = std.Io; +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const Ast = std.zig.Ast; nodes: std.MultiArrayList(Node.Repr).Slice, extra: []u32, @@ -25,7 +32,7 @@ pub const Header = extern struct { /// making it more likely that following Valgrind warnings will be taken seriously. unused: u64 = 0, - stat_inode: std.fs.File.INode, + stat_inode: Io.File.INode, stat_size: u64, stat_mtime: i128, @@ -254,9 +261,3 @@ pub const CompileError = extern struct { assert(std.meta.hasUniqueRepresentation(Note)); } }; - -const std = @import("std"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; -const Ast = std.zig.Ast; -const Zoir = @This(); diff --git a/lib/std/zig/llvm/Builder.zig b/lib/std/zig/llvm/Builder.zig index 9a52ae2c81c797d71c97f613d3c6225887631b68..9ca0124f4f28a970d63e23cefedef66faf6df891 100644 --- a/lib/std/zig/llvm/Builder.zig +++ b/lib/std/zig/llvm/Builder.zig @@ -9578,7 +9578,7 @@ pub fn asmValue( pub fn dump(b: *Builder) void { var buffer: [4000]u8 = undefined; - const stderr: std.fs.File = .stderr(); + const stderr: Io.File = .stderr(); b.printToFile(stderr, &buffer) catch {}; } @@ -9589,7 +9589,7 @@ pub fn printToFilePath(b: *Builder, io: Io, dir: std.fs.Dir, path: []const u8) ! try b.printToFile(io, file, &buffer); } -pub fn printToFile(b: *Builder, file: std.fs.File, buffer: []u8) !void { +pub fn printToFile(b: *Builder, file: Io.File, buffer: []u8) !void { var fw = file.writer(buffer); try print(b, &fw.interface); try fw.interface.flush(); diff --git a/lib/std/zig/perf_test.zig b/lib/std/zig/perf_test.zig index 1566a15d2dc141f99e488e3309977220ed444f6e..da3dd42f15fc3feb4f2c2347119e32b145d4430d 100644 --- a/lib/std/zig/perf_test.zig +++ b/lib/std/zig/perf_test.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const mem = std.mem; const Tokenizer = std.zig.Tokenizer; const fmtIntSizeBin = std.fmt.fmtIntSizeBin; @@ -22,7 +23,7 @@ pub fn main() !void { const bytes_per_sec = @as(u64, @intFromFloat(@floor(bytes_per_sec_float))); var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); const stdout = &stdout_writer.interface; try stdout.print("parsing speed: {Bi:.2}/s, {Bi:.2} used \n", .{ bytes_per_sec, memory_used }); try stdout.flush(); diff --git a/lib/std/zip.zig b/lib/std/zip.zig index c2dbaf5b810b594b6159c207537bf160cfd34169..9588651e7f1c1b3d71a3d1089ad00402cc62af82 100644 --- a/lib/std/zip.zig +++ b/lib/std/zip.zig @@ -4,9 +4,10 @@ //! Note that this file uses the abbreviation "cd" for "central directory" const builtin = @import("builtin"); -const std = @import("std"); -const File = std.fs.File; const is_le = builtin.target.cpu.arch.endian() == .little; + +const std = @import("std"); +const File = std.Io.File; const Writer = std.Io.Writer; const Reader = std.Io.Reader; const flate = std.compress.flate; diff --git a/src/Compilation.zig b/src/Compilation.zig index df64dee19fbfae1540a91bbd7af5b3bfd8e7bde2..582c9dff3c6d103d1e0e07004cb8e5c3d87a4c79 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -5325,7 +5325,7 @@ fn docsCopyModule( comp: *Compilation, module: *Package.Module, name: []const u8, - tar_file_writer: *fs.File.Writer, + tar_file_writer: *Io.File.Writer, ) !void { const io = comp.io; const root = module.root; @@ -5361,7 +5361,7 @@ fn docsCopyModule( }; defer file.close(io); const stat = try file.stat(); - var file_reader: fs.File.Reader = .initSize(file.adaptToNewApi(), io, &buffer, stat.size); + var file_reader: Io.File.Reader = .initSize(file.adaptToNewApi(), io, &buffer, stat.size); archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime) catch |err| { return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive {f}{s}: {t}", .{ diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 58f970abe5444a071efaad57aa308a54dfa25407..988282097bf6e6a341f5096c8c5d4d1be59692dd 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -882,7 +882,7 @@ fn fail(f: *Fetch, msg_tok: std.zig.Ast.TokenIndex, msg_str: u32) RunError { } const Resource = union(enum) { - file: fs.File.Reader, + file: Io.File.Reader, http_request: HttpRequest, git: Git, dir: Io.Dir, @@ -1653,7 +1653,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute fn dumpHashInfo(all_files: []const *const HashedFile) !void { var stdout_buffer: [1024]u8 = undefined; - var stdout_writer: fs.File.Writer = .initStreaming(.stdout(), &stdout_buffer); + var stdout_writer: Io.File.Writer = .initStreaming(.stdout(), &stdout_buffer); const w = &stdout_writer.interface; for (all_files) |hashed_file| { try w.print("{t}: {x}: {s}\n", .{ hashed_file.kind, &hashed_file.hash, hashed_file.normalized_path }); @@ -1712,11 +1712,11 @@ fn deleteFileFallible(dir: Io.Dir, deleted_file: *DeletedFile) DeletedFile.Error try dir.deleteFile(deleted_file.fs_path); } -fn setExecutable(file: fs.File) !void { +fn setExecutable(file: Io.File) !void { if (!std.fs.has_executable_bit) return; const S = std.posix.S; - const mode = fs.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH; + const mode = Io.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH; try file.chmod(mode); } @@ -1738,10 +1738,10 @@ const HashedFile = struct { size: u64, const Error = - fs.File.OpenError || - fs.File.ReadError || - fs.File.StatError || - fs.File.ChmodError || + Io.File.OpenError || + Io.File.ReadError || + Io.File.StatError || + Io.File.ChmodError || Io.Dir.ReadLinkError; const Kind = enum { file, link }; diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index 864865bd1939da867ccb424b54c65c4279303e6b..0fca3a0ee3324473984083f8b45a07b6fbc5d465 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -198,8 +198,8 @@ pub const Repository = struct { repo: *Repository, allocator: Allocator, format: Oid.Format, - pack_file: *std.fs.File.Reader, - index_file: *std.fs.File.Reader, + pack_file: *Io.File.Reader, + index_file: *Io.File.Reader, ) !void { repo.* = .{ .odb = undefined }; try repo.odb.init(allocator, format, pack_file, index_file); @@ -372,9 +372,9 @@ pub const Repository = struct { /// [pack-format](https://git-scm.com/docs/pack-format). const Odb = struct { format: Oid.Format, - pack_file: *std.fs.File.Reader, + pack_file: *Io.File.Reader, index_header: IndexHeader, - index_file: *std.fs.File.Reader, + index_file: *Io.File.Reader, cache: ObjectCache = .{}, allocator: Allocator, @@ -383,8 +383,8 @@ const Odb = struct { odb: *Odb, allocator: Allocator, format: Oid.Format, - pack_file: *std.fs.File.Reader, - index_file: *std.fs.File.Reader, + pack_file: *Io.File.Reader, + index_file: *Io.File.Reader, ) !void { try pack_file.seekTo(0); try index_file.seekTo(0); @@ -1272,8 +1272,8 @@ const IndexEntry = struct { pub fn indexPack( allocator: Allocator, format: Oid.Format, - pack: *std.fs.File.Reader, - index_writer: *std.fs.File.Writer, + pack: *Io.File.Reader, + index_writer: *Io.File.Writer, ) !void { try pack.seekTo(0); @@ -1372,7 +1372,7 @@ pub fn indexPack( fn indexPackFirstPass( allocator: Allocator, format: Oid.Format, - pack: *std.fs.File.Reader, + pack: *Io.File.Reader, index_entries: *std.AutoHashMapUnmanaged(Oid, IndexEntry), pending_deltas: *std.ArrayList(IndexEntry), ) !Oid { @@ -1425,7 +1425,7 @@ fn indexPackFirstPass( fn indexPackHashDelta( allocator: Allocator, format: Oid.Format, - pack: *std.fs.File.Reader, + pack: *Io.File.Reader, delta: IndexEntry, index_entries: std.AutoHashMapUnmanaged(Oid, IndexEntry), cache: *ObjectCache, @@ -1477,7 +1477,7 @@ fn indexPackHashDelta( fn resolveDeltaChain( allocator: Allocator, format: Oid.Format, - pack: *std.fs.File.Reader, + pack: *Io.File.Reader, base_object: Object, delta_offsets: []const u64, cache: *ObjectCache, diff --git a/src/Zcu.zig b/src/Zcu.zig index 58d884afe3aebebf11a264ee2469a594ce851bd1..cd4a8c7783e4afba17a29752a88464213b50dead 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -1200,7 +1200,7 @@ pub const EmbedFile = struct { /// `.none` means the file was not loaded, so `stat` is undefined. val: InternPool.Index, /// If this is `null` and `val` is `.none`, the file has never been loaded. - err: ?(std.fs.File.OpenError || std.fs.File.StatError || std.fs.File.ReadError || error{UnexpectedEof}), + err: ?(Io.File.OpenError || Io.File.StatError || Io.File.ReadError || error{UnexpectedEof}), stat: Cache.File.Stat, pub const Index = enum(u32) { @@ -2927,7 +2927,7 @@ comptime { } } -pub fn loadZirCache(gpa: Allocator, io: Io, cache_file: std.fs.File) !Zir { +pub fn loadZirCache(gpa: Allocator, io: Io, cache_file: Io.File) !Zir { var buffer: [2000]u8 = undefined; var file_reader = cache_file.reader(io, &buffer); return result: { @@ -2986,7 +2986,7 @@ pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_br: *Io.Reader return zir; } -pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.Stat, zir: Zir) (std.fs.File.WriteError || Allocator.Error)!void { +pub fn saveZirCache(gpa: Allocator, cache_file: Io.File, stat: Io.File.Stat, zir: Zir) (Io.File.WriteError || Allocator.Error)!void { const safety_buffer = if (data_has_safety_tag) try gpa.alloc([8]u8, zir.instructions.len) else @@ -3026,7 +3026,7 @@ pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.S }; } -pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir) std.fs.File.WriteError!void { +pub fn saveZoirCache(cache_file: Io.File, stat: Io.File.Stat, zoir: Zoir) Io.File.WriteError!void { const header: Zoir.Header = .{ .nodes_len = @intCast(zoir.nodes.len), .extra_len = @intCast(zoir.extra.len), diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index d2ca004058f5dcebd3ef389319baf080baa5d484..55d6a3861fa7f32642d10385e4e26ef2bfef6dc5 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -118,7 +118,7 @@ pub fn updateFile( const zir_dir = cache_directory.handle; // Determine whether we need to reload the file from disk and redo parsing and AstGen. - var lock: std.fs.File.Lock = switch (file.status) { + var lock: Io.File.Lock = switch (file.status) { .never_loaded, .retryable_failure => lock: { // First, load the cached ZIR code, if any. log.debug("AstGen checking cache: {f} (local={}, digest={s})", .{ @@ -346,8 +346,8 @@ pub fn updateFile( fn loadZirZoirCache( zcu: *Zcu, - cache_file: std.fs.File, - stat: std.fs.File.Stat, + cache_file: Io.File, + stat: Io.File.Stat, file: *Zcu.File, comptime mode: Ast.Mode, ) !enum { success, invalid, truncated, stale } { diff --git a/src/fmt.zig b/src/fmt.zig index 907c7885adf262fb9407096645be73dcce2534eb..7bdc24054e02ee424df0fb50ac153d2ced267c25 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -37,9 +37,9 @@ const Fmt = struct { arena: Allocator, io: Io, out_buffer: std.Io.Writer.Allocating, - stdout_writer: *fs.File.Writer, + stdout_writer: *Io.File.Writer, - const SeenMap = std.AutoHashMap(fs.File.INode, void); + const SeenMap = std.AutoHashMap(Io.File.INode, void); }; pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void { @@ -59,7 +59,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try fs.File.stdout().writeAll(usage_fmt); + try Io.File.stdout().writeAll(usage_fmt); return process.cleanExit(); } else if (mem.eql(u8, arg, "--color")) { if (i + 1 >= args.len) { @@ -99,9 +99,9 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! fatal("cannot use --stdin with positional arguments", .{}); } - const stdin: fs.File = .stdin(); + const stdin: Io.File = .stdin(); var stdio_buffer: [1024]u8 = undefined; - var file_reader: fs.File.Reader = stdin.reader(io, &stdio_buffer); + var file_reader: Io.File.Reader = stdin.reader(io, &stdio_buffer); const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| { fatal("unable to read stdin: {}", .{err}); }; @@ -154,7 +154,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! process.exit(code); } - return fs.File.stdout().writeAll(formatted); + return Io.File.stdout().writeAll(formatted); } if (input_files.items.len == 0) { @@ -162,7 +162,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! } var stdout_buffer: [4096]u8 = undefined; - var stdout_writer = fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(&stdout_buffer); var fmt: Fmt = .{ .gpa = gpa, @@ -272,7 +272,7 @@ fn fmtPathFile( return error.IsDir; var read_buffer: [1024]u8 = undefined; - var file_reader: fs.File.Reader = source_file.reader(io, &read_buffer); + var file_reader: Io.File.Reader = source_file.reader(io, &read_buffer); file_reader.size = stat.size; const gpa = fmt.gpa; diff --git a/src/link.zig b/src/link.zig index ef095987c9cf1170d94c7c16a3c1101abae1f6a0..d5daf6fca733f19ae83b744b0f72acafd67004c6 100644 --- a/src/link.zig +++ b/src/link.zig @@ -393,7 +393,7 @@ pub const File = struct { comp: *Compilation, emit: Path, - file: ?fs.File, + file: ?Io.File, /// When using the LLVM backend, the emitted object is written to a file with this name. This /// object file then becomes a normal link input to LLD or a self-hosted linker. /// @@ -1110,7 +1110,7 @@ pub const File = struct { }; } - fn loadGnuLdScript(base: *File, path: Path, parent_query: UnresolvedInput.Query, file: fs.File) anyerror!void { + fn loadGnuLdScript(base: *File, path: Path, parent_query: UnresolvedInput.Query, file: Io.File) anyerror!void { const comp = base.comp; const diags = &comp.link_diags; const gpa = comp.gpa; @@ -1238,7 +1238,7 @@ pub const File = struct { pub fn determineMode( output_mode: std.builtin.OutputMode, link_mode: std.builtin.LinkMode, - ) fs.File.Mode { + ) Io.File.Mode { // On common systems with a 0o022 umask, 0o777 will still result in a file created // with 0o755 permissions, but it works appropriately if the system is configured // more leniently. As another data point, C's fopen seems to open files with the @@ -1247,10 +1247,10 @@ pub const File = struct { switch (output_mode) { .Lib => return switch (link_mode) { .dynamic => executable_mode, - .static => fs.File.default_mode, + .static => Io.File.default_mode, }, .Exe => return executable_mode, - .Obj => return fs.File.default_mode, + .Obj => return Io.File.default_mode, } } @@ -1660,19 +1660,19 @@ pub const Input = union(enum) { pub const Object = struct { path: Path, - file: fs.File, + file: Io.File, must_link: bool, hidden: bool, }; pub const Res = struct { path: Path, - file: fs.File, + file: Io.File, }; pub const Dso = struct { path: Path, - file: fs.File, + file: Io.File, needed: bool, weak: bool, reexport: bool, @@ -1694,7 +1694,7 @@ pub const Input = union(enum) { } /// Returns `null` in the case of `dso_exact`. - pub fn pathAndFile(input: Input) ?struct { Path, fs.File } { + pub fn pathAndFile(input: Input) ?struct { Path, Io.File } { return switch (input) { .object, .archive => |obj| .{ obj.path, obj.file }, inline .res, .dso => |x| .{ x.path, x.file }, @@ -2075,7 +2075,7 @@ fn resolveLibInput( fn finishResolveLibInput( resolved_inputs: *std.ArrayList(Input), path: Path, - file: std.fs.File, + file: Io.File, link_mode: std.builtin.LinkMode, query: UnresolvedInput.Query, ) ResolveLibInputResult { diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 95f4ca8bbd4b8ba4fae23515fefe3a527941e31e..cfb02fba38b4e4c5da9b5fcf6675d335b5b36246 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -1,3 +1,24 @@ +const Dwarf = @This(); + +const std = @import("std"); +const Io = std.Io; +const Allocator = std.mem.Allocator; +const DW = std.dwarf; +const Zir = std.zig.Zir; +const assert = std.debug.assert; +const log = std.log.scoped(.dwarf); +const Writer = std.Io.Writer; + +const InternPool = @import("../InternPool.zig"); +const Module = @import("../Package.zig").Module; +const Type = @import("../Type.zig"); +const Value = @import("../Value.zig"); +const Zcu = @import("../Zcu.zig"); +const codegen = @import("../codegen.zig"); +const dev = @import("../dev.zig"); +const link = @import("../link.zig"); +const target_info = @import("../target.zig"); + gpa: Allocator, bin_file: *link.File, format: DW.Format, @@ -29,16 +50,16 @@ pub const UpdateError = error{ UnexpectedEndOfFile, } || codegen.GenerateSymbolError || - std.fs.File.OpenError || - std.fs.File.SetEndPosError || - std.fs.File.CopyRangeError || - std.fs.File.PReadError || - std.fs.File.PWriteError; + Io.File.OpenError || + Io.File.SetEndPosError || + Io.File.CopyRangeError || + Io.File.PReadError || + Io.File.PWriteError; pub const FlushError = UpdateError; pub const RelocError = - std.fs.File.PWriteError; + Io.File.PWriteError; pub const AddressSize = enum(u8) { @"32" = 4, @@ -6350,7 +6371,7 @@ const AbbrevCode = enum { }); }; -fn getFile(dwarf: *Dwarf) ?std.fs.File { +fn getFile(dwarf: *Dwarf) ?Io.File { if (dwarf.bin_file.cast(.macho)) |macho_file| if (macho_file.d_sym) |*d_sym| return d_sym.file; return dwarf.bin_file.file; } @@ -6429,21 +6450,3 @@ const force_incremental = false; inline fn incremental(dwarf: Dwarf) bool { return force_incremental or dwarf.bin_file.comp.config.incremental; } - -const Allocator = std.mem.Allocator; -const DW = std.dwarf; -const Dwarf = @This(); -const InternPool = @import("../InternPool.zig"); -const Module = @import("../Package.zig").Module; -const Type = @import("../Type.zig"); -const Value = @import("../Value.zig"); -const Zcu = @import("../Zcu.zig"); -const Zir = std.zig.Zir; -const assert = std.debug.assert; -const codegen = @import("../codegen.zig"); -const dev = @import("../dev.zig"); -const link = @import("../link.zig"); -const log = std.log.scoped(.dwarf); -const std = @import("std"); -const target_info = @import("../target.zig"); -const Writer = std.Io.Writer; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index ae7d631f094b9a4af2d2c94b3fc96aabbc8e75a5..584a50c7f2da10c4fe71ffdb05aa28054c261ae0 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -3651,7 +3651,7 @@ fn fileLookup(files: std.MultiArrayList(File.Entry), index: File.Index, zig_obje pub fn addFileHandle( gpa: Allocator, file_handles: *std.ArrayList(File.Handle), - handle: fs.File, + handle: Io.File, ) Allocator.Error!File.HandleIndex { try file_handles.append(gpa, handle); return @intCast(file_handles.items.len - 1); @@ -4068,7 +4068,7 @@ fn fmtDumpState(self: *Elf, writer: *std.Io.Writer) std.Io.Writer.Error!void { } /// Caller owns the memory. -pub fn preadAllAlloc(allocator: Allocator, handle: fs.File, offset: u64, size: u64) ![]u8 { +pub fn preadAllAlloc(allocator: Allocator, handle: Io.File, offset: u64, size: u64) ![]u8 { const buffer = try allocator.alloc(u8, math.cast(usize, size) orelse return error.Overflow); errdefer allocator.free(buffer); const amt = try handle.preadAll(buffer, offset); @@ -4460,6 +4460,7 @@ pub fn cast(elf_file: *Elf, comptime T: type, x: anytype) error{LinkFailure}!T { } const std = @import("std"); +const Io = std.Io; const build_options = @import("build_options"); const builtin = @import("builtin"); const assert = std.debug.assert; diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index d51a82b26607847028dee8058b85ef388baf4860..c0dde4176ad41b7fdaa0778c50a7d3cde093230a 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -1,3 +1,30 @@ +const Object = @This(); + +const std = @import("std"); +const Io = std.Io; +const assert = std.debug.assert; +const eh_frame = @import("eh_frame.zig"); +const elf = std.elf; +const fs = std.fs; +const log = std.log.scoped(.link); +const math = std.math; +const mem = std.mem; +const Path = std.Build.Cache.Path; +const Allocator = std.mem.Allocator; + +const Diags = @import("../../link.zig").Diags; +const Archive = @import("Archive.zig"); +const Atom = @import("Atom.zig"); +const AtomList = @import("AtomList.zig"); +const Cie = eh_frame.Cie; +const Elf = @import("../Elf.zig"); +const Fde = eh_frame.Fde; +const File = @import("file.zig").File; +const Merge = @import("Merge.zig"); +const Symbol = @import("Symbol.zig"); +const Alignment = Atom.Alignment; +const riscv = @import("../riscv.zig"); + archive: ?InArchive = null, /// Archive files cannot contain subdirectories, so only the basename is needed /// for output. However, the full path is kept for error reporting. @@ -68,7 +95,7 @@ pub fn parse( diags: *Diags, /// For error reporting purposes only. path: Path, - handle: fs.File, + handle: Io.File, target: *const std.Target, debug_fmt_strip: bool, default_sym_version: elf.Versym, @@ -97,7 +124,7 @@ pub fn parseCommon( gpa: Allocator, diags: *Diags, path: Path, - handle: fs.File, + handle: Io.File, target: *const std.Target, ) !void { const offset = if (self.archive) |ar| ar.offset else 0; @@ -264,7 +291,7 @@ fn initAtoms( gpa: Allocator, diags: *Diags, path: Path, - handle: fs.File, + handle: Io.File, debug_fmt_strip: bool, target: *const std.Target, ) !void { @@ -421,7 +448,7 @@ fn initSymbols( fn parseEhFrame( self: *Object, gpa: Allocator, - handle: fs.File, + handle: Io.File, shndx: u32, target: *const std.Target, ) !void { @@ -1310,7 +1337,7 @@ fn addString(self: *Object, gpa: Allocator, str: []const u8) !u32 { } /// Caller owns the memory. -fn preadShdrContentsAlloc(self: Object, gpa: Allocator, handle: fs.File, index: u32) ![]u8 { +fn preadShdrContentsAlloc(self: Object, gpa: Allocator, handle: Io.File, index: u32) ![]u8 { assert(index < self.shdrs.items.len); const offset = if (self.archive) |ar| ar.offset else 0; const shdr = self.shdrs.items[index]; @@ -1320,7 +1347,7 @@ fn preadShdrContentsAlloc(self: Object, gpa: Allocator, handle: fs.File, index: } /// Caller owns the memory. -fn preadRelocsAlloc(self: Object, gpa: Allocator, handle: fs.File, shndx: u32) ![]align(1) const elf.Elf64_Rela { +fn preadRelocsAlloc(self: Object, gpa: Allocator, handle: Io.File, shndx: u32) ![]align(1) const elf.Elf64_Rela { const raw = try self.preadShdrContentsAlloc(gpa, handle, shndx); const num = @divExact(raw.len, @sizeOf(elf.Elf64_Rela)); return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num]; @@ -1552,29 +1579,3 @@ const InArchive = struct { offset: u64, size: u32, }; - -const Object = @This(); - -const std = @import("std"); -const assert = std.debug.assert; -const eh_frame = @import("eh_frame.zig"); -const elf = std.elf; -const fs = std.fs; -const log = std.log.scoped(.link); -const math = std.math; -const mem = std.mem; -const Path = std.Build.Cache.Path; -const Allocator = std.mem.Allocator; - -const Diags = @import("../../link.zig").Diags; -const Archive = @import("Archive.zig"); -const Atom = @import("Atom.zig"); -const AtomList = @import("AtomList.zig"); -const Cie = eh_frame.Cie; -const Elf = @import("../Elf.zig"); -const Fde = eh_frame.Fde; -const File = @import("file.zig").File; -const Merge = @import("Merge.zig"); -const Symbol = @import("Symbol.zig"); -const Alignment = Atom.Alignment; -const riscv = @import("../riscv.zig"); diff --git a/src/link/Elf/SharedObject.zig b/src/link/Elf/SharedObject.zig index 1e17aa34a873e18dffeb3ba8d4617eb3b36110b2..3720fe53d6ff128a9bbacd1badcda27ed0841da1 100644 --- a/src/link/Elf/SharedObject.zig +++ b/src/link/Elf/SharedObject.zig @@ -1,3 +1,20 @@ +const SharedObject = @This(); + +const std = @import("std"); +const Io = std.Io; +const assert = std.debug.assert; +const elf = std.elf; +const log = std.log.scoped(.elf); +const mem = std.mem; +const Path = std.Build.Cache.Path; +const Stat = std.Build.Cache.File.Stat; +const Allocator = mem.Allocator; + +const Elf = @import("../Elf.zig"); +const File = @import("file.zig").File; +const Symbol = @import("Symbol.zig"); +const Diags = @import("../../link.zig").Diags; + path: Path, index: File.Index, @@ -94,7 +111,7 @@ pub fn parseHeader( gpa: Allocator, diags: *Diags, file_path: Path, - fs_file: std.fs.File, + fs_file: Io.File, stat: Stat, target: *const std.Target, ) !Header { @@ -192,7 +209,7 @@ pub fn parse( gpa: Allocator, /// Moves resources from header. Caller may unconditionally deinit. header: *Header, - fs_file: std.fs.File, + fs_file: Io.File, ) !Parsed { const symtab = if (header.dynsym_sect_index) |index| st: { const shdr = header.sections[index]; @@ -534,19 +551,3 @@ const Format = struct { } } }; - -const SharedObject = @This(); - -const std = @import("std"); -const assert = std.debug.assert; -const elf = std.elf; -const log = std.log.scoped(.elf); -const mem = std.mem; -const Path = std.Build.Cache.Path; -const Stat = std.Build.Cache.File.Stat; -const Allocator = mem.Allocator; - -const Elf = @import("../Elf.zig"); -const File = @import("file.zig").File; -const Symbol = @import("Symbol.zig"); -const Diags = @import("../../link.zig").Diags; diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig index 50f5159d18bb8e61c27e87e7fd64aa2f22417921..52d3c6e6f0eebdc0702756b97ec0a96fb0fea663 100644 --- a/src/link/Elf/file.zig +++ b/src/link/Elf/file.zig @@ -1,3 +1,20 @@ +const std = @import("std"); +const Io = std.Io; +const elf = std.elf; +const log = std.log.scoped(.link); +const Path = std.Build.Cache.Path; +const Allocator = std.mem.Allocator; + +const Archive = @import("Archive.zig"); +const Atom = @import("Atom.zig"); +const Cie = @import("eh_frame.zig").Cie; +const Elf = @import("../Elf.zig"); +const LinkerDefined = @import("LinkerDefined.zig"); +const Object = @import("Object.zig"); +const SharedObject = @import("SharedObject.zig"); +const Symbol = @import("Symbol.zig"); +const ZigObject = @import("ZigObject.zig"); + pub const File = union(enum) { zig_object: *ZigObject, linker_defined: *LinkerDefined, @@ -279,22 +296,6 @@ pub const File = union(enum) { shared_object: SharedObject, }; - pub const Handle = std.fs.File; + pub const Handle = Io.File; pub const HandleIndex = Index; }; - -const std = @import("std"); -const elf = std.elf; -const log = std.log.scoped(.link); -const Path = std.Build.Cache.Path; -const Allocator = std.mem.Allocator; - -const Archive = @import("Archive.zig"); -const Atom = @import("Atom.zig"); -const Cie = @import("eh_frame.zig").Cie; -const Elf = @import("../Elf.zig"); -const LinkerDefined = @import("LinkerDefined.zig"); -const Object = @import("Object.zig"); -const SharedObject = @import("SharedObject.zig"); -const Symbol = @import("Symbol.zig"); -const ZigObject = @import("ZigObject.zig"); diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 7d12ccedb2c10d65339745d3b5a3627272f4f33a..e35444bc0201d4b83976613a85d3d5130315d8f5 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -1,3 +1,23 @@ +const Elf = @This(); + +const builtin = @import("builtin"); +const native_endian = builtin.cpu.arch.endian(); + +const std = @import("std"); +const Io = std.Io; +const assert = std.debug.assert; +const log = std.log.scoped(.link); + +const codegen = @import("../codegen.zig"); +const Compilation = @import("../Compilation.zig"); +const InternPool = @import("../InternPool.zig"); +const link = @import("../link.zig"); +const MappedFile = @import("MappedFile.zig"); +const target_util = @import("../target.zig"); +const Type = @import("../Type.zig"); +const Value = @import("../Value.zig"); +const Zcu = @import("../Zcu.zig"); + base: link.File, options: link.File.OpenOptions, mf: MappedFile, @@ -1973,8 +1993,8 @@ pub fn lazySymbol(elf: *Elf, lazy: link.File.LazySymbol) !Symbol.Index { return lazy_gop.value_ptr.*; } -pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError || - std.Io.File.Reader.Error || MappedFile.Error || error{ EndOfStream, BadMagic, LinkFailure })!void { +pub fn loadInput(elf: *Elf, input: link.Input) (Io.File.Reader.SizeError || + Io.File.Reader.Error || MappedFile.Error || error{ EndOfStream, BadMagic, LinkFailure })!void { const io = elf.base.comp.io; var buf: [4096]u8 = undefined; switch (input) { @@ -2007,7 +2027,7 @@ pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError || .dso_exact => |dso_exact| try elf.loadDsoExact(dso_exact.name), } } -fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *std.Io.File.Reader) !void { +fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { const comp = elf.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -2067,7 +2087,7 @@ fn loadObject( elf: *Elf, path: std.Build.Cache.Path, member: ?[]const u8, - fr: *std.Io.File.Reader, + fr: *Io.File.Reader, fl: MappedFile.Node.FileLocation, ) !void { const comp = elf.base.comp; @@ -2310,7 +2330,7 @@ fn loadObject( }, } } -fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *std.Io.File.Reader) !void { +fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { const comp = elf.base.comp; const diags = &comp.link_diags; const r = &fr.interface; @@ -3822,19 +3842,3 @@ pub fn printNode( try w.writeByte('\n'); } } - -const assert = std.debug.assert; -const builtin = @import("builtin"); -const codegen = @import("../codegen.zig"); -const Compilation = @import("../Compilation.zig"); -const Elf = @This(); -const InternPool = @import("../InternPool.zig"); -const link = @import("../link.zig"); -const log = std.log.scoped(.link); -const MappedFile = @import("MappedFile.zig"); -const native_endian = builtin.cpu.arch.endian(); -const std = @import("std"); -const target_util = @import("../target.zig"); -const Type = @import("../Type.zig"); -const Value = @import("../Value.zig"); -const Zcu = @import("../Zcu.zig"); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 471465cea1411b5e1d1f65ea4f42b3bba71e30a6..72a49c0c9e15517bb644aa614b3d155c0e25f3d4 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -890,7 +890,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { _ = try self.addTbd(.fromLinkInput(input), true, fh); } -fn parseFatFile(self: *MachO, file: std.fs.File, path: Path) !?fat.Arch { +fn parseFatFile(self: *MachO, file: Io.File, path: Path) !?fat.Arch { const diags = &self.base.comp.link_diags; const fat_h = fat.readFatHeader(file) catch return null; if (fat_h.magic != macho.FAT_MAGIC and fat_h.magic != macho.FAT_MAGIC_64) return null; @@ -903,7 +903,7 @@ fn parseFatFile(self: *MachO, file: std.fs.File, path: Path) !?fat.Arch { return diags.failParse(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)}); } -pub fn readMachHeader(file: std.fs.File, offset: usize) !macho.mach_header_64 { +pub fn readMachHeader(file: Io.File, offset: usize) !macho.mach_header_64 { var buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined; const nread = try file.preadAll(&buffer, offset); if (nread != buffer.len) return error.InputOutput; @@ -911,7 +911,7 @@ pub fn readMachHeader(file: std.fs.File, offset: usize) !macho.mach_header_64 { return hdr; } -pub fn readArMagic(file: std.fs.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 { +pub fn readArMagic(file: Io.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 { const nread = try file.preadAll(buffer, offset); if (nread != buffer.len) return error.InputOutput; return buffer[0..Archive.SARMAG]; @@ -3768,7 +3768,7 @@ pub fn getInternalObject(self: *MachO) ?*InternalObject { return self.getFile(index).?.internal; } -pub fn addFileHandle(self: *MachO, file: fs.File) !File.HandleIndex { +pub fn addFileHandle(self: *MachO, file: Io.File) !File.HandleIndex { const gpa = self.base.comp.gpa; const index: File.HandleIndex = @intCast(self.file_handles.items.len); const fh = try self.file_handles.addOne(gpa); @@ -5373,10 +5373,11 @@ const max_distance = (1 << (jump_bits - 1)); const max_allowed_distance = max_distance - 0x500_000; const MachO = @This(); - -const std = @import("std"); const build_options = @import("build_options"); const builtin = @import("builtin"); + +const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const fs = std.fs; const log = std.log.scoped(.link); @@ -5386,6 +5387,11 @@ const math = std.math; const mem = std.mem; const meta = std.meta; const Writer = std.Io.Writer; +const AtomicBool = std.atomic.Value(bool); +const Cache = std.Build.Cache; +const Hash = std.hash.Wyhash; +const Md5 = std.crypto.hash.Md5; +const Allocator = std.mem.Allocator; const aarch64 = codegen.aarch64.encoding; const bind = @import("MachO/dyld_info/bind.zig"); @@ -5403,11 +5409,8 @@ const trace = @import("../tracy.zig").trace; const synthetic = @import("MachO/synthetic.zig"); const Alignment = Atom.Alignment; -const Allocator = mem.Allocator; const Archive = @import("MachO/Archive.zig"); -const AtomicBool = std.atomic.Value(bool); const Bind = bind.Bind; -const Cache = std.Build.Cache; const CodeSignature = @import("MachO/CodeSignature.zig"); const Compilation = @import("../Compilation.zig"); const DataInCode = synthetic.DataInCode; @@ -5417,14 +5420,12 @@ const ExportTrie = @import("MachO/dyld_info/Trie.zig"); const Path = Cache.Path; const File = @import("MachO/file.zig").File; const GotSection = synthetic.GotSection; -const Hash = std.hash.Wyhash; const Indsymtab = synthetic.Indsymtab; const InternalObject = @import("MachO/InternalObject.zig"); const ObjcStubsSection = synthetic.ObjcStubsSection; const Object = @import("MachO/Object.zig"); const LazyBind = bind.LazyBind; const LaSymbolPtrSection = synthetic.LaSymbolPtrSection; -const Md5 = std.crypto.hash.Md5; const Zcu = @import("../Zcu.zig"); const InternPool = @import("../InternPool.zig"); const Rebase = @import("MachO/dyld_info/Rebase.zig"); diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 5bded3b9e3148fc28f3b9df17cf1c9731ba1217a..5f9a9ecac9d995b8ef88fb473c0230d5259655e7 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -1,17 +1,19 @@ const CodeSignature = @This(); const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const fs = std.fs; const log = std.log.scoped(.link); const macho = std.macho; const mem = std.mem; const testing = std.testing; +const Sha256 = std.crypto.hash.sha2.Sha256; +const Allocator = std.mem.Allocator; + const trace = @import("../../tracy.zig").trace; -const Allocator = mem.Allocator; const Hasher = @import("hasher.zig").ParallelHasher; const MachO = @import("../MachO.zig"); -const Sha256 = std.crypto.hash.sha2.Sha256; const hash_size = Sha256.digest_length; @@ -250,7 +252,7 @@ pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const } pub const WriteOpts = struct { - file: fs.File, + file: Io.File, exec_seg_base: u64, exec_seg_limit: u64, file_size: u32, diff --git a/src/link/MachO/fat.zig b/src/link/MachO/fat.zig index 7772f7a4de89a51983e8c71b27051739c5fc59e9..fd9a3025318f2439bf3fd1f3705e592715ffdb9a 100644 --- a/src/link/MachO/fat.zig +++ b/src/link/MachO/fat.zig @@ -1,18 +1,20 @@ +const builtin = @import("builtin"); +const native_endian = builtin.target.cpu.arch.endian(); + const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; -const builtin = @import("builtin"); const log = std.log.scoped(.macho); const macho = std.macho; const mem = std.mem; -const native_endian = builtin.target.cpu.arch.endian(); const MachO = @import("../MachO.zig"); -pub fn readFatHeader(file: std.fs.File) !macho.fat_header { +pub fn readFatHeader(file: Io.File) !macho.fat_header { return readFatHeaderGeneric(macho.fat_header, file, 0); } -fn readFatHeaderGeneric(comptime Hdr: type, file: std.fs.File, offset: usize) !Hdr { +fn readFatHeaderGeneric(comptime Hdr: type, file: Io.File, offset: usize) !Hdr { var buffer: [@sizeOf(Hdr)]u8 = undefined; const nread = try file.preadAll(&buffer, offset); if (nread != buffer.len) return error.InputOutput; @@ -27,7 +29,7 @@ pub const Arch = struct { size: u32, }; -pub fn parseArchs(file: std.fs.File, fat_header: macho.fat_header, out: *[2]Arch) ![]const Arch { +pub fn parseArchs(file: Io.File, fat_header: macho.fat_header, out: *[2]Arch) ![]const Arch { var count: usize = 0; var fat_arch_index: u32 = 0; while (fat_arch_index < fat_header.nfat_arch and count < out.len) : (fat_arch_index += 1) { diff --git a/src/link/MachO/file.zig b/src/link/MachO/file.zig index 05b43de1818350513a3a4081368f9d4dc33be6af..cd687a4941b2e73915a7daafe31b8d996cee37a5 100644 --- a/src/link/MachO/file.zig +++ b/src/link/MachO/file.zig @@ -355,11 +355,12 @@ pub const File = union(enum) { dylib: Dylib, }; - pub const Handle = std.fs.File; + pub const Handle = Io.File; pub const HandleIndex = Index; }; const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const log = std.log.scoped(.link); const macho = std.macho; diff --git a/src/link/MachO/hasher.zig b/src/link/MachO/hasher.zig index 78cd847c409dc13585602657f569b45c4c565679..8cf53071c811a787d5f6b867110828ebd294d5a4 100644 --- a/src/link/MachO/hasher.zig +++ b/src/link/MachO/hasher.zig @@ -1,3 +1,9 @@ +const std = @import("std"); +const Io = std.Io; +const Allocator = std.mem.Allocator; + +const trace = @import("../../tracy.zig").trace; + pub fn ParallelHasher(comptime Hasher: type) type { const hash_size = Hasher.digest_length; @@ -5,7 +11,7 @@ pub fn ParallelHasher(comptime Hasher: type) type { allocator: Allocator, io: std.Io, - pub fn hash(self: Self, file: fs.File, out: [][hash_size]u8, opts: struct { + pub fn hash(self: Self, file: Io.File, out: [][hash_size]u8, opts: struct { chunk_size: u64 = 0x4000, max_file_size: ?u64 = null, }) !void { @@ -23,7 +29,7 @@ pub fn ParallelHasher(comptime Hasher: type) type { const buffer = try self.allocator.alloc(u8, chunk_size * out.len); defer self.allocator.free(buffer); - const results = try self.allocator.alloc(fs.File.PReadError!usize, out.len); + const results = try self.allocator.alloc(Io.File.PReadError!usize, out.len); defer self.allocator.free(results); { @@ -51,11 +57,11 @@ pub fn ParallelHasher(comptime Hasher: type) type { } fn worker( - file: fs.File, + file: Io.File, fstart: usize, buffer: []u8, out: *[hash_size]u8, - err: *fs.File.PReadError!usize, + err: *Io.File.PReadError!usize, ) void { const tracy = trace(@src()); defer tracy.end(); @@ -66,11 +72,3 @@ pub fn ParallelHasher(comptime Hasher: type) type { const Self = @This(); }; } - -const assert = std.debug.assert; -const fs = std.fs; -const mem = std.mem; -const std = @import("std"); -const trace = @import("../../tracy.zig").trace; - -const Allocator = mem.Allocator; diff --git a/src/link/MachO/uuid.zig b/src/link/MachO/uuid.zig index d08ac0c5b8ec18909816c324d8d6073e6fba95eb..4d8eac7523ba3e34cf61fa5a47a32b13a6f4852a 100644 --- a/src/link/MachO/uuid.zig +++ b/src/link/MachO/uuid.zig @@ -1,10 +1,18 @@ +const std = @import("std"); +const Io = std.Io; +const Md5 = std.crypto.hash.Md5; + +const trace = @import("../../tracy.zig").trace; +const Compilation = @import("../../Compilation.zig"); +const Hasher = @import("hasher.zig").ParallelHasher; + /// Calculates Md5 hash of each chunk in parallel and then hashes all Md5 hashes to produce /// the final digest. /// While this is NOT a correct MD5 hash of the contents, this methodology is used by LLVM/LLD /// and we will use it too as it seems accepted by Apple OSes. /// TODO LLD also hashes the output filename to disambiguate between same builds with different /// output files. Should we also do that? -pub fn calcUuid(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void { +pub fn calcUuid(comp: *const Compilation, file: Io.File, file_size: u64, out: *[Md5.digest_length]u8) !void { const tracy = trace(@src()); defer tracy.end(); @@ -37,12 +45,3 @@ inline fn conform(out: *[Md5.digest_length]u8) void { out[6] = (out[6] & 0x0F) | (3 << 4); out[8] = (out[8] & 0x3F) | 0x80; } - -const fs = std.fs; -const mem = std.mem; -const std = @import("std"); -const trace = @import("../../tracy.zig").trace; - -const Compilation = @import("../../Compilation.zig"); -const Md5 = std.crypto.hash.Md5; -const Hasher = @import("hasher.zig").ParallelHasher; diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 975b94578bbd4935e222f2e293b57d2b35d6785c..7d4134ccaf1e595a25d4a9fc65383de48a601f42 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -1,3 +1,15 @@ +const MappedFile = @This(); + +const builtin = @import("builtin"); +const is_linux = builtin.os.tag == .linux; +const is_windows = builtin.os.tag == .windows; + +const std = @import("std"); +const Io = std.Io; +const assert = std.debug.assert; +const linux = std.os.linux; +const windows = std.os.windows; + file: std.Io.File, flags: packed struct { block_size: std.mem.Alignment, @@ -16,7 +28,7 @@ writers: std.SinglyLinkedList, pub const growth_factor = 4; -pub const Error = std.posix.MMapError || std.posix.MRemapError || std.fs.File.SetEndPosError || error{ +pub const Error = std.posix.MMapError || std.posix.MRemapError || Io.File.SetEndPosError || error{ NotFile, SystemResources, IsDir, @@ -618,7 +630,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested // Resize the entire file if (ni == Node.Index.root) { try mf.ensureCapacityForSetLocation(gpa); - try std.fs.File.adaptFromNewApi(mf.file).setEndPos(new_size); + try Io.File.adaptFromNewApi(mf.file).setEndPos(new_size); try mf.ensureTotalCapacity(@intCast(new_size)); ni.setLocationAssumeCapacity(mf, old_offset, new_size); return; @@ -1059,12 +1071,3 @@ fn verifyNode(mf: *MappedFile, parent_ni: Node.Index) void { ni = node.next; } } - -const assert = std.debug.assert; -const builtin = @import("builtin"); -const is_linux = builtin.os.tag == .linux; -const is_windows = builtin.os.tag == .windows; -const linux = std.os.linux; -const MappedFile = @This(); -const std = @import("std"); -const windows = std.os.windows; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 160e6cdcc6c95832b70386a8fe87159fe10fbcec..7ab1e0eb4b0c3866fb0663c34c7e662327d6b431 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -20,6 +20,7 @@ const native_endian = builtin.cpu.arch.endian(); const build_options = @import("build_options"); const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; const Path = Cache.Path; @@ -3001,9 +3002,9 @@ pub fn createEmpty( .read = true, .mode = if (fs.has_executable_bit) if (target.os.tag == .wasi and output_mode == .Exe) - fs.File.default_mode | 0b001_000_000 + Io.File.default_mode | 0b001_000_000 else - fs.File.default_mode + Io.File.default_mode else 0, }); diff --git a/src/link/tapi.zig b/src/link/tapi.zig index 4c1471a6b4712142b5a549e9b17c1169b715e14f..fff25b75447336c9c756a0421fbf9a88e23f3e00 100644 --- a/src/link/tapi.zig +++ b/src/link/tapi.zig @@ -1,10 +1,10 @@ const std = @import("std"); -const fs = std.fs; +const Io = std.Io; const mem = std.mem; const log = std.log.scoped(.tapi); +const Allocator = std.mem.Allocator; + const yaml = @import("tapi/yaml.zig"); - -const Allocator = mem.Allocator; const Yaml = yaml.Yaml; const VersionField = union(enum) { @@ -130,7 +130,7 @@ pub const Tbd = union(enum) { pub const TapiError = error{ NotLibStub, InputOutput, -} || yaml.YamlError || std.fs.File.PReadError; +} || yaml.YamlError || Io.File.PReadError; pub const LibStub = struct { /// Underlying memory for stub's contents. @@ -139,7 +139,7 @@ pub const LibStub = struct { /// Typed contents of the tbd file. inner: []Tbd, - pub fn loadFromFile(allocator: Allocator, file: fs.File) TapiError!LibStub { + pub fn loadFromFile(allocator: Allocator, file: Io.File) TapiError!LibStub { const filesize = blk: { const stat = file.stat() catch break :blk std.math.maxInt(u32); break :blk @min(stat.size, std.math.maxInt(u32)); -- 2.54.0 From dd1d15b72aa3bae4b38e2337609758ffb7a7b55a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 6 Dec 2025 17:32:19 -0800 Subject: [PATCH 011/195] update all occurrences of std.fs.Dir to std.Io.Dir --- lib/compiler/aro/aro/Compilation.zig | 6 +-- lib/compiler/resinator/cli.zig | 2 +- lib/compiler/resinator/errors.zig | 6 +-- lib/compiler/resinator/main.zig | 2 +- lib/compiler/resinator/utils.zig | 4 +- lib/compiler/std-docs.zig | 2 +- lib/std/Build.zig | 2 +- lib/std/Build/Step.zig | 6 +-- lib/std/Build/Watch.zig | 2 +- lib/std/Io/Dir.zig | 2 +- lib/std/crypto/Certificate/Bundle.zig | 4 +- lib/std/dynamic_library.zig | 2 +- lib/std/fs/path.zig | 4 +- lib/std/fs/test.zig | 13 +++--- lib/std/process/Child.zig | 4 +- lib/std/testing.zig | 60 ++++++++++++++------------- lib/std/zig/WindowsSdk.zig | 8 ++-- lib/std/zig/llvm/Builder.zig | 2 +- lib/std/zip.zig | 5 ++- src/Compilation.zig | 4 +- src/Package/Fetch/git.zig | 4 +- src/fmt.zig | 6 +-- src/link/Lld.zig | 7 ++-- src/link/MachO.zig | 2 +- 24 files changed, 81 insertions(+), 78 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index 06b27c33bbed7a3abb614aede310a880ce059a14..c31caefb0f2e31b0be5d63bfc053db6b6a34b3cb 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -154,7 +154,7 @@ gpa: Allocator, /// Allocations in this arena live all the way until `Compilation.deinit`. arena: Allocator, io: Io, -cwd: std.fs.Dir, +cwd: Io.Dir, diagnostics: *Diagnostics, sources: std.StringArrayHashMapUnmanaged(Source) = .empty, @@ -181,7 +181,7 @@ pragma_handlers: std.StringArrayHashMapUnmanaged(*Pragma) = .empty, /// Used by MS extensions which allow searching for includes relative to the directory of the main source file. ms_cwd_source_id: ?Source.Id = null, -pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilation { +pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: Io.Dir) Compilation { return .{ .gpa = gpa, .arena = arena, @@ -193,7 +193,7 @@ pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, /// Initialize Compilation with default environment, /// pragma handlers and emulation mode set to target. -pub fn initDefault(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: std.fs.Dir) !Compilation { +pub fn initDefault(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: Io.Dir) !Compilation { var comp: Compilation = .{ .gpa = gpa, .arena = arena, diff --git a/lib/compiler/resinator/cli.zig b/lib/compiler/resinator/cli.zig index 17da5c7b75d112799d4d86c9d4daa75dfbada517..bb54289e3e966ed23561db14602bcb44c37faca5 100644 --- a/lib/compiler/resinator/cli.zig +++ b/lib/compiler/resinator/cli.zig @@ -250,7 +250,7 @@ pub const Options = struct { /// worlds' situation where we'll be compatible with most use-cases /// of the .rc extension being omitted from the CLI args, but still /// work fine if the file itself does not have an extension. - pub fn maybeAppendRC(options: *Options, cwd: std.fs.Dir) !void { + pub fn maybeAppendRC(options: *Options, cwd: Io.Dir) !void { switch (options.input_source) { .stdio => return, .filename => {}, diff --git a/lib/compiler/resinator/errors.zig b/lib/compiler/resinator/errors.zig index 61fd00e683b0a1381f0215fe2e300ac6ddeb8935..14ff28a697355960dd3e20a314344c42eef84d67 100644 --- a/lib/compiler/resinator/errors.zig +++ b/lib/compiler/resinator/errors.zig @@ -67,7 +67,7 @@ pub const Diagnostics = struct { return @intCast(index); } - pub fn renderToStdErr(self: *Diagnostics, cwd: std.fs.Dir, source: []const u8, source_mappings: ?SourceMappings) void { + pub fn renderToStdErr(self: *Diagnostics, cwd: Io.Dir, source: []const u8, source_mappings: ?SourceMappings) void { const io = self.io; const stderr, const ttyconf = std.debug.lockStderrWriter(&.{}); defer std.debug.unlockStderrWriter(); @@ -903,7 +903,7 @@ pub fn renderErrorMessage( io: Io, writer: *std.Io.Writer, tty_config: std.Io.tty.Config, - cwd: std.fs.Dir, + cwd: Io.Dir, err_details: ErrorDetails, source: []const u8, strings: []const []const u8, @@ -1100,7 +1100,7 @@ const CorrespondingLines = struct { pub fn init( io: Io, - cwd: std.fs.Dir, + cwd: Io.Dir, err_details: ErrorDetails, line_for_comparison: []const u8, corresponding_span: SourceMappings.CorrespondingSpan, diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index e171d8199c0b7b6da502b894027e50a77e71cd13..c726a06cf43382c9632930502ef1acff038b7651 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -707,7 +707,7 @@ const ErrorHandler = union(enum) { pub fn emitDiagnostics( self: *ErrorHandler, allocator: Allocator, - cwd: std.fs.Dir, + cwd: Io.Dir, source: []const u8, diagnostics: *Diagnostics, mappings: SourceMappings, diff --git a/lib/compiler/resinator/utils.zig b/lib/compiler/resinator/utils.zig index e8565f3af904192fe8e9f598612fd93ccce61ba9..3f03b73c54cc57b17e8d70f9042d8df37a73f883 100644 --- a/lib/compiler/resinator/utils.zig +++ b/lib/compiler/resinator/utils.zig @@ -25,11 +25,11 @@ pub const UncheckedSliceWriter = struct { } }; -/// Cross-platform 'std.fs.Dir.openFile' wrapper that will always return IsDir if +/// Cross-platform 'Io.Dir.openFile' wrapper that will always return IsDir if /// a directory is attempted to be opened. /// TODO: Remove once https://github.com/ziglang/zig/issues/5732 is addressed. pub fn openFileNotDir( - cwd: std.fs.Dir, + cwd: Io.Dir, io: Io, path: []const u8, flags: Io.File.OpenFlags, diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index 15c17ee59b8b3040e6af07c962745a7dc5b018e6..f1ca7fb5bbea7831cd0e6750c0b39d759c76f5f2 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -133,7 +133,7 @@ fn accept(context: *Context, connection: std.net.Server.Connection) void { const Context = struct { gpa: Allocator, io: Io, - lib_dir: std.fs.Dir, + lib_dir: Io.Dir, zig_lib_directory: []const u8, zig_exe_path: []const u8, global_cache_path: []const u8, diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 2755b895c241679d52950914330e0c6b669a4679..7bfdbb6449c36dbebd4bc7f273165a6ce905dfe2 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1699,7 +1699,7 @@ pub fn addCheckFile( return Step.CheckFile.create(b, file_source, options); } -pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Dir.StatFileError)!void { +pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.MakeError || Io.Dir.StatFileError)!void { const io = b.graph.io; if (b.verbose) log.info("truncate {s}", .{dest_path}); const cwd = fs.cwd(); diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 2b1e6d8ace5f86412636d7472f12a995ff31fbd7..56ef32e8d865044eefe7128d6652d16bb1dcac65 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -505,7 +505,7 @@ pub fn evalZigProcess( return result; } -/// Wrapper around `std.fs.Dir.updateFile` that handles verbose and error output. +/// Wrapper around `Io.Dir.updateFile` that handles verbose and error output. pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !Io.Dir.PrevStatus { const b = s.owner; const io = b.graph.io; @@ -515,8 +515,8 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err }); } -/// Wrapper around `std.fs.Dir.makePathStatus` that handles verbose and error output. -pub fn installDir(s: *Step, dest_path: []const u8) !std.fs.Dir.MakePathStatus { +/// Wrapper around `Io.Dir.makePathStatus` that handles verbose and error output. +pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.MakePathStatus { const b = s.owner; try handleVerbose(b, null, &.{ "install", "-d", dest_path }); return std.fs.cwd().makePathStatus(dest_path) catch |err| diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index ca01376e02620c9c711381571adeea05f0c436e8..ff06ad3ff351df01759846cbffa96b4417d04355 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -350,7 +350,7 @@ const Os = switch (builtin.os.tag) { } fn init(gpa: Allocator, path: Cache.Path) !*@This() { - // The following code is a drawn out NtCreateFile call. (mostly adapted from std.fs.Dir.makeOpenDirAccessMaskW) + // The following code is a drawn out NtCreateFile call. (mostly adapted from Io.Dir.makeOpenDirAccessMaskW) // It's necessary in order to get the specific flags that are required when calling ReadDirectoryChangesW. var dir_handle: windows.HANDLE = undefined; const root_fd = path.root_dir.handle.fd; diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 5a74c8ca722d6db8a605c76bcb8fbbb745f26539..1c28c2f9b3bcdebeeabf08fed3c579f7f5ac1ca9 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -481,7 +481,7 @@ pub fn updateFile( } var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available. - var atomic_file = try std.fs.Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{ + var atomic_file = try Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{ .permissions = actual_permissions, .write_buffer = &buffer, }); diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index 1d21918b5c3337d31e4bd1e25e17c3279dab8aa0..df671e6f636ef240c04f563921f6e35dde0b7947 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -177,7 +177,7 @@ pub fn addCertsFromDirPath( cb: *Bundle, gpa: Allocator, io: Io, - dir: fs.Dir, + dir: Io.Dir, sub_dir_path: []const u8, ) AddCertsFromDirPathError!void { var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true }); @@ -200,7 +200,7 @@ pub fn addCertsFromDirPathAbsolute( pub const AddCertsFromDirError = AddCertsFromFilePathError; -pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, iterable_dir: fs.Dir) AddCertsFromDirError!void { +pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, iterable_dir: Io.Dir) AddCertsFromDirError!void { var it = iterable_dir.iterate(); while (try it.next()) |entry| { switch (entry.kind) { diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index ca36d5cbb94131e808edcca23130d5a8d2e5255a..7db177ad703ed2f47c2aabbb2db6270c92ba933c 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -157,7 +157,7 @@ pub const ElfDynLib = struct { dt_gnu_hash: *elf.gnu_hash.Header, }; - fn openPath(path: []const u8, io: Io) !std.fs.Dir { + fn openPath(path: []const u8, io: Io) !Io.Dir { if (path.len == 0) return error.NotDir; var parts = std.mem.tokenizeScalar(u8, path, '/'); var parent = if (path[0] == '/') try std.fs.cwd().openDir("/", .{}) else std.fs.cwd(); diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index f00007756f563dcdf8717e859b204584ff25a645..a8465f52d4148d10aea014b10a330b3d0578f371 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -872,7 +872,7 @@ pub fn resolve(allocator: Allocator, paths: []const []const u8) Allocator.Error! /// This function is like a series of `cd` statements executed one after another. /// It resolves "." and ".." to the best of its ability, but will not convert relative paths to -/// an absolute path, use std.fs.Dir.realpath instead. +/// an absolute path, use Io.Dir.realpath instead. /// ".." components may persist in the resolved path if the resolved path is relative or drive-relative. /// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters. /// @@ -1095,7 +1095,7 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator /// This function is like a series of `cd` statements executed one after another. /// It resolves "." and ".." to the best of its ability, but will not convert relative paths to -/// an absolute path, use std.fs.Dir.realpath instead. +/// an absolute path, use Io.Dir.realpath instead. /// ".." components may persist in the resolved path if the resolved path is relative. /// The result does not have a trailing path separator. /// This function does not perform any syscalls. Executing this series of path diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index f770ddd30edb7efd678c16f17311218ff90e71f0..36ccd3a6be2fd1fe185fe83e7c0a46af2d28e41d 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -9,12 +9,11 @@ const mem = std.mem; const wasi = std.os.wasi; const windows = std.os.windows; const posix = std.posix; - const ArenaAllocator = std.heap.ArenaAllocator; -const Dir = std.fs.Dir; +const Dir = std.Io.Dir; const File = std.Io.File; -const tmpDir = testing.tmpDir; -const SymLinkFlags = std.fs.Dir.SymLinkFlags; +const tmpDir = std.testing.tmpDir; +const SymLinkFlags = std.Io.Dir.SymLinkFlags; const PathType = enum { relative, @@ -80,7 +79,7 @@ const TestContext = struct { path_sep: u8, arena: ArenaAllocator, tmp: testing.TmpDir, - dir: std.fs.Dir, + dir: Io.Dir, transform_fn: *const PathType.TransformFn, pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext { @@ -1772,7 +1771,7 @@ test "open file with exclusive lock twice, make sure second lock waits" { errdefer file.close(io); const S = struct { - fn checkFn(dir: *fs.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { + fn checkFn(dir: *Io.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { started.set(); const file1 = try dir.createFile(path, .{ .lock = .exclusive }); @@ -2016,7 +2015,7 @@ test "walker without fully iterating" { try testing.expectEqual(@as(usize, 1), num_walked); } -test "'.' and '..' in fs.Dir functions" { +test "'.' and '..' in Io.Dir functions" { if (native_os == .windows and builtin.cpu.arch == .aarch64) { // https://github.com/ziglang/zig/issues/17134 return error.SkipZigTest; diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 87d2fe3ba9848c948fba2a4484765bf5b476f907..b7743039010a4c1fe01515822d35d6d73d2acc06 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -77,7 +77,7 @@ cwd: ?[]const u8, /// Set to change the current working directory when spawning the child process. /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190 /// Once that is done, `cwd` will be deprecated in favor of this field. -cwd_dir: ?fs.Dir = null, +cwd_dir: ?Io.Dir = null, err_pipe: if (native_os == .windows) void else ?posix.fd_t, @@ -439,7 +439,7 @@ pub fn run(args: struct { allocator: mem.Allocator, argv: []const []const u8, cwd: ?[]const u8 = null, - cwd_dir: ?fs.Dir = null, + cwd_dir: ?Io.Dir = null, /// Required if unable to access the current env map (e.g. building a /// library on some platforms). env_map: ?*const EnvMap = null, diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 63131d771c4f644603840ed2e7e60376ebc2bfeb..19038543a6674717b3c6e64a91591c5d9200b100 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -1,5 +1,7 @@ -const std = @import("std.zig"); const builtin = @import("builtin"); + +const std = @import("std.zig"); +const Io = std.Io; const assert = std.debug.assert; const math = std.math; @@ -28,7 +30,7 @@ pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{ break :b .init; }; -pub var io_instance: std.Io.Threaded = undefined; +pub var io_instance: Io.Threaded = undefined; pub const io = io_instance.io(); /// TODO https://github.com/ziglang/zig/issues/5738 @@ -355,7 +357,7 @@ test expectApproxEqRel { /// This function is intended to be used only in tests. When the two slices are not /// equal, prints diagnostics to stderr to show exactly how they are not equal (with /// the differences highlighted in red), then returns a test failure error. -/// The colorized output is optional and controlled by the return of `std.Io.tty.Config.detect`. +/// The colorized output is optional and controlled by the return of `Io.tty.Config.detect`. /// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead. pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void { const diff_index: usize = diff_index: { @@ -378,8 +380,8 @@ fn failEqualSlices( expected: []const T, actual: []const T, diff_index: usize, - w: *std.Io.Writer, - ttyconf: std.Io.tty.Config, + w: *Io.Writer, + ttyconf: Io.tty.Config, ) !void { try w.print("slices differ. first difference occurs at index {d} (0x{X})\n", .{ diff_index, diff_index }); @@ -464,11 +466,11 @@ fn SliceDiffer(comptime T: type) type { start_index: usize, expected: []const T, actual: []const T, - ttyconf: std.Io.tty.Config, + ttyconf: Io.tty.Config, const Self = @This(); - pub fn write(self: Self, writer: *std.Io.Writer) !void { + pub fn write(self: Self, writer: *Io.Writer) !void { for (self.expected, 0..) |value, i| { const full_index = self.start_index + i; const diff = if (i < self.actual.len) !std.meta.eql(self.actual[i], value) else true; @@ -487,9 +489,9 @@ fn SliceDiffer(comptime T: type) type { const BytesDiffer = struct { expected: []const u8, actual: []const u8, - ttyconf: std.Io.tty.Config, + ttyconf: Io.tty.Config, - pub fn write(self: BytesDiffer, writer: *std.Io.Writer) !void { + pub fn write(self: BytesDiffer, writer: *Io.Writer) !void { var expected_iterator = std.mem.window(u8, self.expected, 16, 16); var row: usize = 0; while (expected_iterator.next()) |chunk| { @@ -535,7 +537,7 @@ const BytesDiffer = struct { } } - fn writeDiff(self: BytesDiffer, writer: *std.Io.Writer, comptime fmt: []const u8, args: anytype, diff: bool) !void { + fn writeDiff(self: BytesDiffer, writer: *Io.Writer, comptime fmt: []const u8, args: anytype, diff: bool) !void { if (diff) try self.ttyconf.setColor(writer, .red); try writer.print(fmt, args); if (diff) try self.ttyconf.setColor(writer, .reset); @@ -605,8 +607,8 @@ pub fn expect(ok: bool) !void { } pub const TmpDir = struct { - dir: std.fs.Dir, - parent_dir: std.fs.Dir, + dir: Io.Dir, + parent_dir: Io.Dir, sub_path: [sub_path_len]u8, const random_bytes_count = 12; @@ -620,7 +622,7 @@ pub const TmpDir = struct { } }; -pub fn tmpDir(opts: std.fs.Dir.OpenOptions) TmpDir { +pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir { var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; std.crypto.random.bytes(&random_bytes); var sub_path: [TmpDir.sub_path_len]u8 = undefined; @@ -929,7 +931,7 @@ test "expectEqualDeep primitive type" { a, b, - pub fn format(self: @This(), writer: *std.Io.Writer) !void { + pub fn format(self: @This(), writer: *Io.Writer) !void { try writer.writeAll(@tagName(self)); } }; @@ -1160,7 +1162,7 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime } else |err| switch (err) { error.OutOfMemory => { if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) { - const tty_config: std.Io.tty.Config = .detect(.stderr()); + const tty_config: Io.tty.Config = .detect(.stderr()); print( "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}", .{ @@ -1220,14 +1222,14 @@ pub inline fn fuzz( return @import("root").fuzz(context, testOne, options); } -/// A `std.Io.Reader` that writes a predetermined list of buffers during `stream`. +/// A `Io.Reader` that writes a predetermined list of buffers during `stream`. pub const Reader = struct { calls: []const Call, - interface: std.Io.Reader, + interface: Io.Reader, next_call_index: usize, next_offset: usize, /// Further reduces how many bytes are written in each `stream` call. - artificial_limit: std.Io.Limit = .unlimited, + artificial_limit: Io.Limit = .unlimited, pub const Call = struct { buffer: []const u8, @@ -1247,7 +1249,7 @@ pub const Reader = struct { }; } - fn stream(io_r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize { + fn stream(io_r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); if (r.calls.len - r.next_call_index == 0) return error.EndOfStream; const call = r.calls[r.next_call_index]; @@ -1262,13 +1264,13 @@ pub const Reader = struct { } }; -/// A `std.Io.Reader` that gets its data from another `std.Io.Reader`, and always +/// A `Io.Reader` that gets its data from another `Io.Reader`, and always /// writes to its own buffer (and returns 0) during `stream` and `readVec`. pub const ReaderIndirect = struct { - in: *std.Io.Reader, - interface: std.Io.Reader, + in: *Io.Reader, + interface: Io.Reader, - pub fn init(in: *std.Io.Reader, buffer: []u8) ReaderIndirect { + pub fn init(in: *Io.Reader, buffer: []u8) ReaderIndirect { return .{ .in = in, .interface = .{ @@ -1283,17 +1285,17 @@ pub const ReaderIndirect = struct { }; } - fn readVec(r: *std.Io.Reader, _: [][]u8) std.Io.Reader.Error!usize { + fn readVec(r: *Io.Reader, _: [][]u8) Io.Reader.Error!usize { try streamInner(r); return 0; } - fn stream(r: *std.Io.Reader, _: *std.Io.Writer, _: std.Io.Limit) std.Io.Reader.StreamError!usize { + fn stream(r: *Io.Reader, _: *Io.Writer, _: Io.Limit) Io.Reader.StreamError!usize { try streamInner(r); return 0; } - fn streamInner(r: *std.Io.Reader) std.Io.Reader.Error!void { + fn streamInner(r: *Io.Reader) Io.Reader.Error!void { const r_indirect: *ReaderIndirect = @alignCast(@fieldParentPtr("interface", r)); // If there's no room remaining in the buffer at all, make room. @@ -1301,12 +1303,12 @@ pub const ReaderIndirect = struct { try r.rebase(r.buffer.len); } - var writer: std.Io.Writer = .{ + var writer: Io.Writer = .{ .buffer = r.buffer, .end = r.end, .vtable = &.{ - .drain = std.Io.Writer.unreachableDrain, - .rebase = std.Io.Writer.unreachableRebase, + .drain = Io.Writer.unreachableDrain, + .rebase = Io.Writer.unreachableRebase, }, }; defer r.end = writer.end; diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index a7b16e1bed193818abd686bdad18eb8862a53196..6b6e4fa9f7881b4ac8a375f4d1e08d9e3846e462 100644 --- a/lib/std/zig/WindowsSdk.zig +++ b/lib/std/zig/WindowsSdk.zig @@ -77,7 +77,7 @@ pub fn free(sdk: WindowsSdk, allocator: Allocator) void { /// and a version. Returns slice of version strings sorted in descending order. /// Caller owns result. fn iterateAndFilterByVersion( - iterator: *std.fs.Dir.Iterator, + iterator: *Io.Dir.Iterator, allocator: Allocator, prefix: []const u8, ) error{OutOfMemory}![][]const u8 { @@ -608,7 +608,7 @@ pub const Installation = struct { }; const MsvcLibDir = struct { - fn findInstancesDirViaSetup(allocator: Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { + fn findInstancesDirViaSetup(allocator: Allocator) error{ OutOfMemory, PathNotFound }!Io.Dir { const vs_setup_key_path = "SOFTWARE\\Microsoft\\VisualStudio\\Setup"; const vs_setup_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, vs_setup_key_path, .{}) catch |err| switch (err) { error.KeyNotFound => return error.PathNotFound, @@ -633,7 +633,7 @@ const MsvcLibDir = struct { return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return error.PathNotFound; } - fn findInstancesDirViaCLSID(allocator: Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { + fn findInstancesDirViaCLSID(allocator: Allocator) error{ OutOfMemory, PathNotFound }!Io.Dir { const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}"; const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid, .{}) catch |err| switch (err) { error.KeyNotFound => return error.PathNotFound, @@ -669,7 +669,7 @@ const MsvcLibDir = struct { return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return error.PathNotFound; } - fn findInstancesDir(allocator: Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { + fn findInstancesDir(allocator: Allocator) error{ OutOfMemory, PathNotFound }!Io.Dir { // First, try getting the packages cache path from the registry. // This only seems to exist when the path is different from the default. method1: { diff --git a/lib/std/zig/llvm/Builder.zig b/lib/std/zig/llvm/Builder.zig index 9ca0124f4f28a970d63e23cefedef66faf6df891..443bdcc391210ae1267a2981a03768b4618ed2bf 100644 --- a/lib/std/zig/llvm/Builder.zig +++ b/lib/std/zig/llvm/Builder.zig @@ -9582,7 +9582,7 @@ pub fn dump(b: *Builder) void { b.printToFile(stderr, &buffer) catch {}; } -pub fn printToFilePath(b: *Builder, io: Io, dir: std.fs.Dir, path: []const u8) !void { +pub fn printToFilePath(b: *Builder, io: Io, dir: Io.Dir, path: []const u8) !void { var buffer: [4000]u8 = undefined; const file = try dir.createFile(io, path, .{}); defer file.close(io); diff --git a/lib/std/zip.zig b/lib/std/zip.zig index 9588651e7f1c1b3d71a3d1089ad00402cc62af82..0ca77c98a191e57d054f00a67108ae02b44b7a38 100644 --- a/lib/std/zip.zig +++ b/lib/std/zip.zig @@ -7,6 +7,7 @@ const builtin = @import("builtin"); const is_le = builtin.target.cpu.arch.endian() == .little; const std = @import("std"); +const Io = std.Io; const File = std.Io.File; const Writer = std.Io.Writer; const Reader = std.Io.Reader; @@ -461,7 +462,7 @@ pub const Iterator = struct { stream: *File.Reader, options: ExtractOptions, filename_buf: []u8, - dest: std.fs.Dir, + dest: Io.Dir, ) !void { if (filename_buf.len < self.filename_len) return error.ZipInsufficientBuffer; @@ -650,7 +651,7 @@ pub const ExtractOptions = struct { }; /// Extract the zipped files to the given `dest` directory. -pub fn extract(dest: std.fs.Dir, fr: *File.Reader, options: ExtractOptions) !void { +pub fn extract(dest: Io.Dir, fr: *File.Reader, options: ExtractOptions) !void { if (options.verify_checksums) @panic("TODO unimplemented"); var iter = try Iterator.init(fr); diff --git a/src/Compilation.zig b/src/Compilation.zig index 582c9dff3c6d103d1e0e07004cb8e5c3d87a4c79..d75cba5a11770b4d4478587014b3c134e83aef3c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -446,7 +446,7 @@ pub const Path = struct { } /// Given a `Path`, returns the directory handle and sub path to be used to open the path. - pub fn openInfo(p: Path, dirs: Directories) struct { fs.Dir, []const u8 } { + pub fn openInfo(p: Path, dirs: Directories) struct { Io.Dir, []const u8 } { const dir = switch (p.root) { .none => { const cwd_sub_path = absToCwdRelative(p.sub_path, dirs.cwd); @@ -1872,7 +1872,7 @@ pub const CreateDiagnostic = union(enum) { pub const CreateCachePath = struct { which: enum { local, global }, sub: []const u8, - err: (fs.Dir.MakeError || fs.Dir.OpenError || fs.Dir.StatFileError), + err: (Io.Dir.MakeError || Io.Dir.OpenError || Io.Dir.StatFileError), }; pub fn format(diag: CreateDiagnostic, w: *Writer) Writer.Error!void { switch (diag) { diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index 0fca3a0ee3324473984083f8b45a07b6fbc5d465..abaa8fef730582c37eed4f453e977e44008870b3 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -214,7 +214,7 @@ pub const Repository = struct { pub fn checkout( repository: *Repository, io: Io, - worktree: std.fs.Dir, + worktree: Io.Dir, commit_oid: Oid, diagnostics: *Diagnostics, ) !void { @@ -231,7 +231,7 @@ pub const Repository = struct { fn checkoutTree( repository: *Repository, io: Io, - dir: std.fs.Dir, + dir: Io.Dir, tree_oid: Oid, current_path: []const u8, diagnostics: *Diagnostics, diff --git a/src/fmt.zig b/src/fmt.zig index 7bdc24054e02ee424df0fb50ac153d2ced267c25..663d09e9cbb8152d97faeac2c553237740361afd 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -204,7 +204,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! } } -fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) !void { +fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: Io.Dir, sub_path: []const u8) !void { fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) { error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path), else => { @@ -219,7 +219,7 @@ fn fmtPathDir( fmt: *Fmt, file_path: []const u8, check_mode: bool, - parent_dir: fs.Dir, + parent_dir: Io.Dir, parent_sub_path: []const u8, ) !void { const io = fmt.io; @@ -257,7 +257,7 @@ fn fmtPathFile( fmt: *Fmt, file_path: []const u8, check_mode: bool, - dir: fs.Dir, + dir: Io.Dir, sub_path: []const u8, ) !void { const io = fmt.io; diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 66b032e0a91b5c83478e95c2794c649028382812..49f6d3f7c726794390d95dd788953f31af688b72 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -400,7 +400,7 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { // regarding eliding redundant object -> object transformations. return error.NoObjectsToLink; }; - try std.fs.Dir.copyFile( + try Io.Dir.copyFile( the_object_path.root_dir.handle, the_object_path.sub_path, directory.handle, @@ -816,7 +816,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { // regarding eliding redundant object -> object transformations. return error.NoObjectsToLink; }; - try std.fs.Dir.copyFile( + try Io.Dir.copyFile( the_object_path.root_dir.handle, the_object_path.sub_path, directory.handle, @@ -1371,7 +1371,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { // regarding eliding redundant object -> object transformations. return error.NoObjectsToLink; }; - try fs.Dir.copyFile( + try Io.Dir.copyFile( the_object_path.root_dir.handle, the_object_path.sub_path, directory.handle, @@ -1692,6 +1692,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi } const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; const allocPrint = std.fmt.allocPrint; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 72a49c0c9e15517bb644aa614b3d155c0e25f3d4..93adc633ce6a06c485438bf49302fab8c3ce8291 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3573,7 +3573,7 @@ pub fn getTarget(self: *const MachO) *const std.Target { /// into a new inode, remove the original file, and rename the copy to match /// the original file. This is super messy, but there doesn't seem any other /// way to please the XNU. -pub fn invalidateKernelCache(dir: fs.Dir, sub_path: []const u8) !void { +pub fn invalidateKernelCache(dir: Io.Dir, sub_path: []const u8) !void { const tracy = trace(@src()); defer tracy.end(); if (builtin.target.os.tag.isDarwin() and builtin.target.cpu.arch == .aarch64) { -- 2.54.0 From 8328de24f13e21e325207b19288a143854df50df Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 6 Dec 2025 17:52:57 -0800 Subject: [PATCH 012/195] update all occurrences of openFile to receive an io instance --- lib/compiler/aro/aro/Compilation.zig | 4 +- lib/compiler/aro/aro/Driver/Filesystem.zig | 2 +- lib/compiler/aro/aro/Toolchain.zig | 6 +-- lib/compiler/objcopy.zig | 2 +- lib/compiler/std-docs.zig | 2 +- lib/std/Build/Cache.zig | 8 ++-- lib/std/Build/Cache/Path.zig | 8 +--- lib/std/Build/Fuzz.zig | 4 +- lib/std/Build/Step/Run.zig | 6 +-- lib/std/Build/Step/UpdateSourceFiles.zig | 2 +- lib/std/Build/Step/WriteFile.zig | 6 +-- lib/std/Build/WebServer.zig | 4 +- lib/std/Io/Dir.zig | 2 +- lib/std/Io/test.zig | 2 +- lib/std/Thread.zig | 4 +- lib/std/crypto/Certificate/Bundle.zig | 2 +- lib/std/debug/ElfFile.zig | 2 +- lib/std/debug/Info.zig | 2 +- lib/std/debug/MachOFile.zig | 2 +- lib/std/debug/SelfInfo/Elf.zig | 4 +- lib/std/debug/SelfInfo/MachO.zig | 2 +- lib/std/debug/SelfInfo/Windows.zig | 6 +-- lib/std/fs.zig | 17 -------- lib/std/fs/test.zig | 46 +++++++++++----------- lib/std/os/linux/IoUring.zig | 6 +-- lib/std/posix/test.zig | 8 ++-- lib/std/zig/system.zig | 6 +-- src/Compilation.zig | 8 ++-- src/Package/Fetch.zig | 6 +-- src/Package/Fetch/git.zig | 2 +- src/Zcu.zig | 2 +- src/Zcu/PerThread.zig | 4 +- src/fmt.zig | 2 +- src/introspect.zig | 4 +- src/link.zig | 24 +++++------ src/link/Coff.zig | 4 +- src/link/Elf2.zig | 14 ++++--- src/link/MachO.zig | 8 ++-- src/link/MachO/relocatable.zig | 7 ++-- src/link/MappedFile.zig | 2 +- src/main.zig | 10 ++--- 41 files changed, 124 insertions(+), 138 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index c31caefb0f2e31b0be5d63bfc053db6b6a34b3cb..09e4861d133e9affa44263241f568f58980419ba 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -1641,7 +1641,7 @@ fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kin const io = comp.io; - const file = try comp.cwd.openFile(path, .{}); + const file = try comp.cwd.openFile(io, path, .{}); defer file.close(io); return comp.addSourceFromFile(file, path, kind); } @@ -1975,7 +1975,7 @@ fn getPathContents(comp: *Compilation, path: []const u8, limit: Io.Limit) ![]u8 const io = comp.io; - const file = try comp.cwd.openFile(path, .{}); + const file = try comp.cwd.openFile(io, path, .{}); defer file.close(io); return comp.getFileContents(file, limit); } diff --git a/lib/compiler/aro/aro/Driver/Filesystem.zig b/lib/compiler/aro/aro/Driver/Filesystem.zig index 19ac9bfe41431a508886c2c6fb889293dee8dd5a..b0bdbb7e2185e65a2f00780bd71ce03bc00e0346 100644 --- a/lib/compiler/aro/aro/Driver/Filesystem.zig +++ b/lib/compiler/aro/aro/Driver/Filesystem.zig @@ -213,7 +213,7 @@ pub const Filesystem = union(enum) { pub fn readFile(fs: Filesystem, io: Io, path: []const u8, buf: []u8) ?[]const u8 { return switch (fs) { .real => |cwd| { - const file = cwd.openFile(path, .{}) catch return null; + const file = cwd.openFile(io, path, .{}) catch return null; defer file.close(io); const bytes_read = file.readAll(buf) catch return null; diff --git a/lib/compiler/aro/aro/Toolchain.zig b/lib/compiler/aro/aro/Toolchain.zig index ae8436920504d355fbe7017bde34f2465f97d99b..95a8baba7776fe077960d7332e046585248d0dae 100644 --- a/lib/compiler/aro/aro/Toolchain.zig +++ b/lib/compiler/aro/aro/Toolchain.zig @@ -524,12 +524,12 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void { /// Otherwise returns a slice of `buf`. If the file is larger than `buf` partial contents are returned pub fn readFile(tc: *const Toolchain, path: []const u8, buf: []u8) ?[]const u8 { const comp = tc.driver.comp; - return comp.cwd.adaptToNewApi().readFile(comp.io, path, buf) catch null; + return comp.cwd.readFile(comp.io, path, buf) catch null; } pub fn exists(tc: *const Toolchain, path: []const u8) bool { const comp = tc.driver.comp; - comp.cwd.adaptToNewApi().access(comp.io, path, .{}) catch return false; + comp.cwd.access(comp.io, path, .{}) catch return false; return true; } @@ -547,7 +547,7 @@ pub fn canExecute(tc: *const Toolchain, path: []const u8) bool { } const comp = tc.driver.comp; - comp.cwd.adaptToNewApi().access(comp.io, path, .{ .execute = true }) catch return false; + comp.cwd.access(comp.io, path, .{ .execute = true }) catch return false; // Todo: ensure path is not a directory return true; } diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index c360ea8df09da5ba9e609ff5bcbdfbe494cf747b..485e644daaf7f3966a0e52b46e0ff7925a28bd0a 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -157,7 +157,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void const stat = input_file.stat() catch |err| fatal("failed to stat {s}: {t}", .{ input, err }); - var in: File.Reader = .initSize(input_file.adaptToNewApi(), io, &input_buffer, stat.size); + var in: File.Reader = .initSize(input_file, io, &input_buffer, stat.size); const elf_hdr = std.elf.Header.read(&in.interface) catch |err| switch (err) { error.ReadFailed => fatal("unable to read {s}: {t}", .{ input, in.err.? }), diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index f1ca7fb5bbea7831cd0e6750c0b39d759c76f5f2..e4efac28cdb7385f2a6f76d8f93fc9d10334698e 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -225,7 +225,7 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { }, else => continue, } - var file = try entry.dir.openFile(entry.basename, .{}); + var file = try entry.dir.openFile(io, entry.basename, .{}); defer file.close(io); const stat = try file.stat(); var file_reader: std.Io.File.Reader = .{ diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index b06547dc537bea3f485c14fe5303b9c49e797298..42459c033d90d5534632581ac5fc8378b9809e9a 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -502,6 +502,8 @@ pub const Manifest = struct { @memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest); manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*; + const io = self.cache.io; + // We'll try to open the cache with an exclusive lock, but if that would block // and `want_shared_lock` is set, a shared lock might be sufficient, so we'll // open with a shared lock instead. @@ -517,7 +519,7 @@ pub const Manifest = struct { break; } else |err| switch (err) { error.WouldBlock => { - self.manifest_file = self.cache.manifest_dir.openFile(&manifest_file_path, .{ + self.manifest_file = self.cache.manifest_dir.openFile(io, &manifest_file_path, .{ .mode = .read_write, .lock = .shared, }) catch |e| { @@ -757,7 +759,7 @@ pub const Manifest = struct { const pp = cache_hash_file.prefixed_path; const dir = self.cache.prefixes()[pp.prefix].handle; - const this_file = dir.openFile(pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) { + const this_file = dir.openFile(io, pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) { error.FileNotFound => { // Every digest before this one has been populated successfully. return .{ .miss = .{ .file_digests_populated = idx } }; @@ -900,7 +902,7 @@ pub const Manifest = struct { } else { const pp = ch_file.prefixed_path; const dir = self.cache.prefixes()[pp.prefix].handle; - const handle = try dir.openFile(pp.sub_path, .{}); + const handle = try dir.openFile(io, pp.sub_path, .{}); defer handle.close(io); return populateFileHashHandle(self, ch_file, handle); } diff --git a/lib/std/Build/Cache/Path.zig b/lib/std/Build/Cache/Path.zig index f6f76c1e8f6bf6eb1451207acb7ce3358ea2115a..60211670ded38352091d04ccd50bbdbd8c58a0f6 100644 --- a/lib/std/Build/Cache/Path.zig +++ b/lib/std/Build/Cache/Path.zig @@ -59,18 +59,14 @@ pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Erro return p.root_dir.joinZ(gpa, parts); } -pub fn openFile( - p: Path, - sub_path: []const u8, - flags: Io.File.OpenFlags, -) !Io.File { +pub fn openFile(p: Path, io: Io, sub_path: []const u8, flags: Io.File.OpenFlags) !Io.File { var buf: [fs.max_path_bytes]u8 = undefined; const joined_path = if (p.sub_path.len == 0) sub_path else p: { break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ p.sub_path, sub_path, }) catch return error.NameTooLong; }; - return p.root_dir.handle.openFile(joined_path, flags); + return p.root_dir.handle.openFile(io, joined_path, flags); } pub fn openDir( diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index db83f393fd4c8a04794e45b39cd2b984c9c7e2da..c95c9bd354b591639faa6bb85a75029f8ea4c565 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -405,7 +405,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO .root_dir = run_step.step.owner.cache_root, .sub_path = "v/" ++ std.fmt.hex(coverage_id), }; - var coverage_file = coverage_file_path.root_dir.handle.openFile(coverage_file_path.sub_path, .{}) catch |err| { + var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| { log.err("step '{s}': failed to load coverage file '{f}': {t}", .{ run_step.step.name, coverage_file_path, err, }); @@ -528,7 +528,7 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void { .root_dir = cov.run.step.owner.cache_root, .sub_path = "v/" ++ std.fmt.hex(cov.id), }; - var coverage_file = coverage_file_path.root_dir.handle.openFile(coverage_file_path.sub_path, .{}) catch |err| { + var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| { fatal("step '{s}': failed to load coverage file '{f}': {t}", .{ cov.run.step.name, coverage_file_path, err, }); diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 1df6f42a353fd87489332e49c3e1bd94b3580f78..7c54c8048e8da6dbb79050977d15e6a49342beda 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -846,7 +846,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { errdefer result.deinit(); result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory; - const file = file_path.root_dir.handle.openFile(file_path.subPathOrDot(), .{}) catch |err| { + const file = file_path.root_dir.handle.openFile(io, file_path.subPathOrDot(), .{}) catch |err| { return step.fail( "unable to open input file '{f}': {t}", .{ file_path, err }, @@ -1111,7 +1111,7 @@ pub fn rerunInFuzzMode( errdefer result.deinit(); result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory; - const file = try file_path.root_dir.handle.openFile(file_path.subPathOrDot(), .{}); + const file = try file_path.root_dir.handle.openFile(io, file_path.subPathOrDot(), .{}); defer file.close(io); var buf: [1024]u8 = undefined; @@ -2185,7 +2185,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { }, .lazy_path => |lazy_path| { const path = lazy_path.getPath3(b, &run.step); - const file = path.root_dir.handle.openFile(path.subPathOrDot(), .{}) catch |err| { + const file = path.root_dir.handle.openFile(io, path.subPathOrDot(), .{}) catch |err| { return run.step.fail("unable to open stdin file: {s}", .{@errorName(err)}); }; defer file.close(io); diff --git a/lib/std/Build/Step/UpdateSourceFiles.zig b/lib/std/Build/Step/UpdateSourceFiles.zig index 7cdb521d21a2c7047ca0e35a876e587abdd435b7..f5d95182e925deb3782b6a59cc0879554856ffbb 100644 --- a/lib/std/Build/Step/UpdateSourceFiles.zig +++ b/lib/std/Build/Step/UpdateSourceFiles.zig @@ -99,7 +99,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .cwd(), io, source_path, - b.build_root.handle.adaptToNewApi(), + b.build_root.handle, output_source_file.sub_path, .{}, ) catch |err| { diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index 201b132271866564b47a5a585c703b899f5736f9..2834f18564f1b91217d1deb5f5496fdfd15ae075 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -284,7 +284,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { }, .copy => |file_source| { const source_path = file_source.getPath2(b, step); - const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir.adaptToNewApi(), file.sub_path, .{}) catch |err| { + const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir, file.sub_path, .{}) catch |err| { return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {t}", .{ source_path, b.cache_root, cache_path, fs.path.sep, file.sub_path, err, }); @@ -321,10 +321,10 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .directory => try cache_dir.makePath(dest_path), .file => { const prev_status = Io.Dir.updateFile( - src_entry_path.root_dir.handle.adaptToNewApi(), + src_entry_path.root_dir.handle, io, src_entry_path.sub_path, - cache_dir.adaptToNewApi(), + cache_dir, dest_path, .{}, ) catch |err| { diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index f91075b44412a7663c8bbaae3b82bb45d32e064b..9938d5e1b027cfc574ed4852b2d85319cdc3b5c2 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -504,14 +504,14 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer }; for (paths) |path| { - var file = path.root_dir.handle.openFile(path.sub_path, .{}) catch |err| { + var file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| { log.err("failed to open '{f}': {s}", .{ path, @errorName(err) }); continue; }; defer file.close(io); const stat = try file.stat(); var read_buffer: [1024]u8 = undefined; - var file_reader: Io.File.Reader = .initSize(file.adaptToNewApi(), io, &read_buffer, stat.size); + var file_reader: Io.File.Reader = .initSize(file, io, &read_buffer, stat.size); // TODO: this logic is completely bogus -- obviously so, because `path.root_dir.path` can // be cwd-relative. This is also related to why linkification doesn't work in the fuzzer UI: diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 1c28c2f9b3bcdebeeabf08fed3c579f7f5ac1ca9..9ae636d4a614e6427363ca16e9f375df85250d28 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -481,7 +481,7 @@ pub fn updateFile( } var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available. - var atomic_file = try Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{ + var atomic_file = try Dir.atomicFile(dest_dir, dest_path, .{ .permissions = actual_permissions, .write_buffer = &buffer, }); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index b922acc3332608d191a6b7eb527c4ad32736dd6e..9f21fe50e7aae388643bf1b9dd38741b705a6133 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -44,7 +44,7 @@ test "write a file, read it, then delete it" { } { - var file = try tmp.dir.openFile(tmp_file_name, .{}); + var file = try tmp.dir.openFile(io, tmp_file_name, .{}); defer file.close(io); const file_size = try file.getEndPos(); diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 8d8e5979df19ca443431b7b4598ccd2454cbca40..102bb59415d56fa78f3f7300e671bb742d050b8b 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -208,7 +208,7 @@ pub fn setName(self: Thread, io: Io, name: []const u8) SetNameError!void { var buf: [32]u8 = undefined; const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); - const file = try std.fs.cwd().openFile(path, .{ .mode = .write_only }); + const file = try std.fs.cwd().openFile(io, path, .{ .mode = .write_only }); defer file.close(io); try file.writeAll(name); @@ -325,7 +325,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co var threaded: std.Io.Threaded = .init_single_threaded; const io = threaded.ioBasic(); - const file = try std.fs.cwd().openFile(path, .{}); + const file = try std.fs.cwd().openFile(io, path, .{}); defer file.close(io); var file_reader = file.readerStreaming(io, &.{}); diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index df671e6f636ef240c04f563921f6e35dde0b7947..eb60ad37a8144726c13f42fe579841e4dd582c9c 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -208,7 +208,7 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, i else => continue, } - try addCertsFromFilePath(cb, gpa, io, now, iterable_dir.adaptToNewApi(), entry.name); + try addCertsFromFilePath(cb, gpa, io, now, iterable_dir, entry.name); } } diff --git a/lib/std/debug/ElfFile.zig b/lib/std/debug/ElfFile.zig index 5dbae181308792727d1df7f0e375cf115e0e887a..a0f1188adeb3cc7645df469e025ad1b94eda08b1 100644 --- a/lib/std/debug/ElfFile.zig +++ b/lib/std/debug/ElfFile.zig @@ -375,7 +375,7 @@ fn loadSeparateDebugFile( args: anytype, ) Allocator.Error!?[]align(std.heap.page_size_min) const u8 { const path = try std.fmt.allocPrint(arena, fmt, args); - const elf_file = std.fs.cwd().openFile(path, .{}) catch return null; + const elf_file = std.fs.cwd().openFile(io, path, .{}) catch return null; defer elf_file.close(io); const result = loadInner(arena, elf_file, opt_crc) catch |err| switch (err) { diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index da7656e62646adf66c086dbfef9db32f5173bb9a..6b31f03f72ad366bb73e60ea71953b65cce557af 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -39,7 +39,7 @@ pub fn load( ) LoadError!Info { switch (format) { .elf => { - var file = try path.root_dir.handle.openFile(path.sub_path, .{}); + var file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); defer file.close(io); var elf_file: ElfFile = try .load(gpa, file, null, &.none); diff --git a/lib/std/debug/MachOFile.zig b/lib/std/debug/MachOFile.zig index 3f0f620a90488ed39dfca1cac68fabba236f88d8..ae904c0aec6d52ccb556e22f826d89d6ee0dde07 100644 --- a/lib/std/debug/MachOFile.zig +++ b/lib/std/debug/MachOFile.zig @@ -512,7 +512,7 @@ fn loadOFile(gpa: Allocator, io: Io, o_file_name: []const u8) !OFile { /// Uses `mmap` to map the file at `path` into memory. fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 { - const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { + const file = std.fs.cwd().openFile(io, path, .{}) catch |err| switch (err) { error.FileNotFound => return error.MissingDebugInfo, else => return error.ReadFailed, }; diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index 155dac6fb8e4b7940325830cf69e8cacbd5250cf..124768687c8e8026fa3411e4433f3b2119105aa0 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -325,7 +325,7 @@ const Module = struct { } fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf { const load_result = if (mod.name.len > 0) res: { - var file = std.fs.cwd().openFile(mod.name, .{}) catch return error.MissingDebugInfo; + var file = std.fs.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo; defer file.close(io); break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name)); } else res: { @@ -334,7 +334,7 @@ const Module = struct { else => return error.ReadFailed, }; defer gpa.free(path); - var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo; + var file = std.fs.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo; defer file.close(io); break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path)); }; diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig index 2491cf416c4659062f14b318ed3e5cfd561ef15f..15da616f3b6274905428cfb2b2ddd7ea81514b0b 100644 --- a/lib/std/debug/SelfInfo/MachO.zig +++ b/lib/std/debug/SelfInfo/MachO.zig @@ -616,7 +616,7 @@ test { /// Uses `mmap` to map the file at `path` into memory. fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 { - const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { + const file = std.fs.cwd().openFile(io, path, .{}) catch |err| switch (err) { error.FileNotFound => return error.MissingDebugInfo, else => return error.ReadFailed, }; diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index 3af7223293aaf7ac9b71c76012fd60d253ccb955..c7f9d8c352a012a00421ea1057cddcbeda2e3802 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -387,7 +387,7 @@ const Module = struct { const section_view = section_view_ptr.?[0..coff_len]; coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo; break :mapped .{ - .file = .adaptFromNewApi(coff_file), + .file = coff_file, .section_handle = section_handle, .section_view = section_view, }; @@ -432,7 +432,7 @@ const Module = struct { break :pdb null; }; const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: { - break :res std.fs.cwd().openFile(path, .{}); + break :res std.fs.cwd().openFile(io, path, .{}); } else res: { const self_dir = fs.selfExeDirPathAlloc(gpa) catch |err| switch (err) { error.OutOfMemory, error.Unexpected => |e| return e, @@ -441,7 +441,7 @@ const Module = struct { defer gpa.free(self_dir); const abs_path = try fs.path.join(gpa, &.{ self_dir, path }); defer gpa.free(abs_path); - break :res std.fs.cwd().openFile(abs_path, .{}); + break :res std.fs.cwd().openFile(io, abs_path, .{}); }; const pdb_file = pdb_file_open_result catch |err| switch (err) { error.FileNotFound, error.IsDir => break :pdb null, diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 9472e5d2a5f86d3081d81de97fc4997bec54700c..cb4daf7c50dc30a7eaf7488162985bfcf2ae39c1 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -287,23 +287,6 @@ pub fn symLinkAbsoluteW( return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory); } -pub const OpenSelfExeError = Io.File.OpenSelfExeError; - -/// Deprecated in favor of `Io.File.openSelfExe`. -pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { - if (native_os == .linux or native_os == .serenity or native_os == .windows) { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return .adaptFromNewApi(try Io.File.openSelfExe(io, flags)); - } - // Use of max_path_bytes here is valid as the resulting path is immediately - // opened with no modification. - var buf: [max_path_bytes]u8 = undefined; - const self_exe_path = try selfExePath(&buf); - buf[self_exe_path.len] = 0; - return openFileAbsolute(buf[0..self_exe_path.len :0], flags); -} - // This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded pub const SelfExePathError = error{ FileNotFound, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 36ccd3a6be2fd1fe185fe83e7c0a46af2d28e41d..7d566da0e95ec6d1f3a70eb9c10e877da8347421 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -855,7 +855,7 @@ test "directory operations on files" { } // ensure the file still exists and is a file as a sanity check - file = try ctx.dir.openFile(test_file_name, .{}); + file = try ctx.dir.openFile(io, test_file_name, .{}); const stat = try file.stat(); try testing.expectEqual(File.Kind.file, stat.kind); file.close(io); @@ -895,12 +895,12 @@ test "file operations on directories" { if (native_os == .wasi and builtin.link_libc) { // wasmtime unexpectedly succeeds here, see https://github.com/ziglang/zig/issues/20747 - const handle = try ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }); + const handle = try ctx.dir.openFile(io, test_dir_name, .{ .mode = .read_write }); handle.close(io); } else { // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms. // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732 - try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write })); + try testing.expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .mode = .read_write })); } if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { @@ -973,8 +973,8 @@ test "Dir.rename files" { try ctx.dir.rename(test_file_name, renamed_test_file_name); // Ensure the file was renamed - try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{})); - file = try ctx.dir.openFile(renamed_test_file_name, .{}); + try testing.expectError(error.FileNotFound, ctx.dir.openFile(io, test_file_name, .{})); + file = try ctx.dir.openFile(io, renamed_test_file_name, .{}); file.close(io); // Rename to self succeeds @@ -986,8 +986,8 @@ test "Dir.rename files" { existing_file.close(io); try ctx.dir.rename(renamed_test_file_name, existing_file_path); - try testing.expectError(error.FileNotFound, ctx.dir.openFile(renamed_test_file_name, .{})); - file = try ctx.dir.openFile(existing_file_path, .{}); + try testing.expectError(error.FileNotFound, ctx.dir.openFile(io, renamed_test_file_name, .{})); + file = try ctx.dir.openFile(io, existing_file_path, .{}); file.close(io); } }.impl); @@ -1026,7 +1026,7 @@ test "Dir.rename directories" { // Ensure the directory was renamed and the file still exists in it try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{})); dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{}); - file = try dir.openFile("test_file", .{}); + file = try dir.openFile(io, "test_file", .{}); file.close(io); dir.close(io); } @@ -1119,8 +1119,8 @@ test "rename" { try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name); // ensure the file was renamed - try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{})); - file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{}); + try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(io, test_file_name, .{})); + file = try tmp_dir2.dir.openFile(io, renamed_test_file_name, .{}); file.close(io); } @@ -1156,8 +1156,8 @@ test "renameAbsolute" { ); // ensure the file was renamed - try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); - file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); + try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(io, test_file_name, .{})); + file = try tmp_dir.dir.openFile(io, renamed_test_file_name, .{}); const stat = try file.stat(); try testing.expectEqual(File.Kind.file, stat.kind); file.close(io); @@ -1512,7 +1512,7 @@ test "setEndPos" { const file_name = "afile.txt"; try tmp.dir.writeFile(.{ .sub_path = file_name, .data = "ninebytes" }); - const f = try tmp.dir.openFile(file_name, .{ .mode = .read_write }); + const f = try tmp.dir.openFile(io, file_name, .{ .mode = .read_write }); defer f.close(io); const initial_size = try f.getEndPos(); @@ -1856,7 +1856,7 @@ test "read from locked file" { .lock = .exclusive, }); defer f.close(io); - const f2 = try ctx.dir.openFile(filename, .{}); + const f2 = try ctx.dir.openFile(io, filename, .{}); defer f2.close(io); var buffer: [1]u8 = undefined; if (builtin.os.tag == .windows) { @@ -2041,12 +2041,12 @@ test "'.' and '..' in Io.Dir functions" { try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{}); try ctx.dir.rename(copy_path, rename_path); - const renamed_file = try ctx.dir.openFile(rename_path, .{}); + const renamed_file = try ctx.dir.openFile(io, rename_path, .{}); renamed_file.close(io); try ctx.dir.deleteFile(rename_path); try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" }); - var dir = ctx.dir.adaptToNewApi(); + var dir = ctx.dir; const prev_status = try dir.updateFile(io, file_path, dir, update_path, .{}); try testing.expectEqual(Io.Dir.PrevStatus.stale, prev_status); @@ -2186,7 +2186,7 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{})); - var dir = ctx.dir.adaptToNewApi(); + var dir = ctx.dir; try testing.expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{})); try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{})); @@ -2235,7 +2235,7 @@ test "read file non vectored" { try file_writer.interface.flush(); } - var file_reader: std.Io.File.Reader = .initAdapted(file, io, &.{}); + var file_reader: std.Io.File.Reader = .init(file, io, &.{}); var write_buffer: [100]u8 = undefined; var w: std.Io.Writer = .fixed(&write_buffer); @@ -2268,7 +2268,7 @@ test "seek keeping partial buffer" { } var read_buffer: [3]u8 = undefined; - var file_reader: Io.File.Reader = .initAdapted(file, io, &read_buffer); + var file_reader: Io.File.Reader = .init(file, io, &read_buffer); try testing.expectEqual(0, file_reader.logicalPos()); @@ -2301,7 +2301,7 @@ test "seekBy" { defer tmp_dir.cleanup(); try tmp_dir.dir.writeFile(.{ .sub_path = "blah.txt", .data = "let's test seekBy" }); - const f = try tmp_dir.dir.openFile("blah.txt", .{ .mode = .read_only }); + const f = try tmp_dir.dir.openFile(io, "blah.txt", .{ .mode = .read_only }); defer f.close(io); var reader = f.readerStreaming(io, &.{}); try reader.seekBy(2); @@ -2332,7 +2332,7 @@ test "seekTo flushes buffered data" { } var read_buffer: [16]u8 = undefined; - var file_reader: std.Io.File.Reader = .initAdapted(file, io, &read_buffer); + var file_reader: std.Io.File.Reader = .init(file, io, &read_buffer); var buf: [4]u8 = undefined; try file_reader.interface.readSliceAll(&buf); @@ -2347,7 +2347,7 @@ test "File.Writer sendfile with buffered contents" { { try tmp_dir.dir.writeFile(.{ .sub_path = "a", .data = "bcd" }); - const in = try tmp_dir.dir.openFile("a", .{}); + const in = try tmp_dir.dir.openFile(io, "a", .{}); defer in.close(io); const out = try tmp_dir.dir.createFile("b", .{}); defer out.close(io); @@ -2364,7 +2364,7 @@ test "File.Writer sendfile with buffered contents" { try out_w.interface.flush(); } - var check = try tmp_dir.dir.openFile("b", .{}); + var check = try tmp_dir.dir.openFile(io, "b", .{}); defer check.close(io); var check_buf: [4]u8 = undefined; var check_r = check.reader(io, &check_buf); diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index e4a5bd373805c0cf557b118d767b485a26ff7235..c7d3f35d40143444f271826a53bc7f6110ac5fdb 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -3002,7 +3002,7 @@ test "renameat" { }, cqe); // Validate that the old file doesn't exist anymore - try testing.expectError(error.FileNotFound, tmp.dir.openFile(old_path, .{})); + try testing.expectError(error.FileNotFound, tmp.dir.openFile(io, old_path, .{})); // Validate that the new file exists with the proper content var new_file_data: [16]u8 = undefined; @@ -3057,7 +3057,7 @@ test "unlinkat" { }, cqe); // Validate that the file doesn't exist anymore - _ = tmp.dir.openFile(path, .{}) catch |err| switch (err) { + _ = tmp.dir.openFile(io, path, .{}) catch |err| switch (err) { error.FileNotFound => {}, else => std.debug.panic("unexpected error: {}", .{err}), }; @@ -3154,7 +3154,7 @@ test "symlinkat" { }, cqe); // Validate that the symlink exist - _ = try tmp.dir.openFile(link_path, .{}); + _ = try tmp.dir.openFile(io, link_path, .{}); } test "linkat" { diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 82fa2c41d11a188c4b323ce6783e304663ff7a14..19313e3ff79031c2113f86ed7be304ec95b6a93b 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -164,10 +164,10 @@ test "linkat with different directories" { // Test 1: link from file in subdir back up to target in parent directory try posix.linkat(tmp.dir.fd, target_name, subdir.fd, link_name, 0); - const efd = try tmp.dir.openFile(target_name, .{}); + const efd = try tmp.dir.openFile(io, target_name, .{}); defer efd.close(io); - const nfd = try subdir.openFile(link_name, .{}); + const nfd = try subdir.openFile(io, link_name, .{}); defer nfd.close(io); { @@ -429,7 +429,7 @@ test "mmap" { // Map the whole file { - const file = try tmp.dir.openFile(test_out_file, .{}); + const file = try tmp.dir.openFile(io, test_out_file, .{}); defer file.close(io); const data = try posix.mmap( @@ -454,7 +454,7 @@ test "mmap" { // Map the upper half of the file { - const file = try tmp.dir.openFile(test_out_file, .{}); + const file = try tmp.dir.openFile(io, test_out_file, .{}); defer file.close(io); const data = try posix.mmap( diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 4f0c11797d7965feb64205e9dfe179b6cf732141..cc74da956af50b3a3b45f5327d29d437e5544c8d 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -817,7 +817,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { // .dynstr section, and finding the max version number of symbols // that start with "GLIBC_2.". const glibc_so_basename = "libc.so.6"; - var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) { + var file = dir.openFile(io, glibc_so_basename, .{}) catch |err| switch (err) { error.NameTooLong => return error.Unexpected, error.BadPathName => return error.Unexpected, error.PipeBusy => return error.Unexpected, // Windows-only @@ -851,7 +851,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system. var buffer: [8000]u8 = undefined; - var file_reader: Io.File.Reader = .initAdapted(file, io, &buffer); + var file_reader: Io.File.Reader = .init(file, io, &buffer); return glibcVerFromSoFile(&file_reader) catch |err| switch (err) { error.InvalidElfMagic, @@ -1053,7 +1053,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ var is_elf_file = false; defer if (!is_elf_file) file.close(io); - file_reader = .initAdapted(file, io, &file_reader_buffer); + file_reader = .init(file, io, &file_reader_buffer); file_name = undefined; // it aliases file_reader_buffer const header = elf.Header.read(&file_reader.interface) catch |hdr_err| switch (hdr_err) { diff --git a/src/Compilation.zig b/src/Compilation.zig index d75cba5a11770b4d4478587014b3c134e83aef3c..24b994f608696f2a3dca0eafb888ca6428ed9c38 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1104,7 +1104,7 @@ pub const CObject = struct { const source_line = source_line: { if (diag.src_loc.offset == 0 or diag.src_loc.column == 0) break :source_line 0; - const file = fs.cwd().openFile(file_name, .{}) catch break :source_line 0; + const file = fs.cwd().openFile(io, file_name, .{}) catch break :source_line 0; defer file.close(io); var buffer: [1024]u8 = undefined; var file_reader = file.reader(io, &buffer); @@ -1179,7 +1179,7 @@ pub const CObject = struct { }; var buffer: [1024]u8 = undefined; - const file = try fs.cwd().openFile(path, .{}); + const file = try fs.cwd().openFile(io, path, .{}); defer file.close(io); var file_reader = file.reader(io, &buffer); var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = &file_reader.interface }); @@ -5354,14 +5354,14 @@ fn docsCopyModule( }, else => continue, } - var file = mod_dir.openFile(entry.path, .{}) catch |err| { + var file = mod_dir.openFile(io, entry.path, .{}) catch |err| { return comp.lockAndSetMiscFailure(.docs_copy, "unable to open {f}{s}: {t}", .{ root.fmt(comp), entry.path, err, }); }; defer file.close(io); const stat = try file.stat(); - var file_reader: Io.File.Reader = .initSize(file.adaptToNewApi(), io, &buffer, stat.size); + var file_reader: Io.File.Reader = .initSize(file, io, &buffer, stat.size); archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime) catch |err| { return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive {f}{s}: {t}", .{ diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 988282097bf6e6a341f5096c8c5d4d1be59692dd..3bd05120ff456a134b46e3cdf2ac6813e093bb76 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -390,7 +390,7 @@ pub fn run(f: *Fetch) RunError!void { var server_header_buffer: [init_resource_buffer_size]u8 = undefined; const file_err = if (dir_err == error.NotDir) e: { - if (fs.cwd().openFile(path_or_url, .{})) |file| { + if (fs.cwd().openFile(io, path_or_url, .{})) |file| { var resource: Resource = .{ .file = file.reader(io, &server_header_buffer) }; return f.runResource(path_or_url, &resource, null); } else |err| break :e err; @@ -995,7 +995,7 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u if (ascii.eqlIgnoreCase(uri.scheme, "file")) { const path = try uri.path.toRawMaybeAlloc(arena); - const file = f.parent_package_root.openFile(path, .{}) catch |err| { + const file = f.parent_package_root.openFile(io, path, .{}) catch |err| { return f.fail(f.location_tok, try eb.printString("unable to open '{f}{s}': {t}", .{ f.parent_package_root, path, err, })); @@ -1677,7 +1677,7 @@ fn hashFileFallible(io: Io, dir: Io.Dir, hashed_file: *HashedFile) HashedFile.Er switch (hashed_file.kind) { .file => { - var file = try dir.openFile(hashed_file.fs_path, .{}); + var file = try dir.openFile(io, hashed_file.fs_path, .{}); defer file.close(io); // Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463 hasher.update(&.{ 0, 0 }); diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index abaa8fef730582c37eed4f453e977e44008870b3..ccae9440e21f8f222ab209c82be5fb9c26fdfdbc 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -1714,7 +1714,7 @@ pub fn main() !void { const format = std.meta.stringToEnum(Oid.Format, args[1]) orelse return error.InvalidFormat; - var pack_file = try std.fs.cwd().openFile(args[2], .{}); + var pack_file = try std.fs.cwd().openFile(io, args[2], .{}); defer pack_file.close(io); var pack_file_buffer: [4096]u8 = undefined; var pack_file_reader = pack_file.reader(io, &pack_file_buffer); diff --git a/src/Zcu.zig b/src/Zcu.zig index cd4a8c7783e4afba17a29752a88464213b50dead..d2634a8962553b774989c37ffe6b5e2f986a0219 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -1076,7 +1076,7 @@ pub const File = struct { var f = f: { const dir, const sub_path = file.path.openInfo(zcu.comp.dirs); - break :f try dir.openFile(sub_path, .{}); + break :f try dir.openFile(io, sub_path, .{}); }; defer f.close(io); diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 55d6a3861fa7f32642d10385e4e26ef2bfef6dc5..45b130213881e8ed30eab6fc46d0037fd0a0066d 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -94,7 +94,7 @@ pub fn updateFile( // In any case we need to examine the stat of the file to determine the course of action. var source_file = f: { const dir, const sub_path = file.path.openInfo(comp.dirs); - break :f try dir.openFile(sub_path, .{}); + break :f try dir.openFile(io, sub_path, .{}); }; defer source_file.close(io); @@ -2466,7 +2466,7 @@ fn updateEmbedFileInner( var file = f: { const dir, const sub_path = ef.path.openInfo(zcu.comp.dirs); - break :f try dir.openFile(sub_path, .{}); + break :f try dir.openFile(io, sub_path, .{}); }; defer file.close(io); diff --git a/src/fmt.zig b/src/fmt.zig index 663d09e9cbb8152d97faeac2c553237740361afd..ce8a31fa4c86836d8580bc3e05ecbf61ca72f148 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -262,7 +262,7 @@ fn fmtPathFile( ) !void { const io = fmt.io; - const source_file = try dir.openFile(sub_path, .{}); + const source_file = try dir.openFile(io, sub_path, .{}); var file_closed = false; errdefer if (!file_closed) source_file.close(io); diff --git a/src/introspect.zig b/src/introspect.zig index 9b6797e7d82b7430d0c1006f8e9b935dd49ff026..d2faa9a55cd2ef73390ad4162ff11792ed5c6c7b 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -22,7 +22,7 @@ fn testZigInstallPrefix(io: Io, base_dir: Io.Dir) ?Cache.Directory { // Try lib/zig/std/std.zig const lib_zig = "lib" ++ fs.path.sep_str ++ "zig"; var test_zig_dir = base_dir.openDir(lib_zig, .{}) catch break :zig_dir; - const file = test_zig_dir.openFile(test_index_file, .{}) catch { + const file = test_zig_dir.openFile(io, test_index_file, .{}) catch { test_zig_dir.close(io); break :zig_dir; }; @@ -32,7 +32,7 @@ fn testZigInstallPrefix(io: Io, base_dir: Io.Dir) ?Cache.Directory { // Try lib/std/std.zig var test_zig_dir = base_dir.openDir("lib", .{}) catch return null; - const file = test_zig_dir.openFile(test_index_file, .{}) catch { + const file = test_zig_dir.openFile(io, test_index_file, .{}) catch { test_zig_dir.close(io); return null; }; diff --git a/src/link.zig b/src/link.zig index d5daf6fca733f19ae83b744b0f72acafd67004c6..073ec632c6eeb9f9ebcaba0739daece26195b936 100644 --- a/src/link.zig +++ b/src/link.zig @@ -637,7 +637,7 @@ pub const File = struct { } } } - base.file = try emit.root_dir.handle.openFile(emit.sub_path, .{ .mode = .read_write }); + base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write }); }, .elf2, .coff2 => if (base.file == null) { const mf = if (base.cast(.elf2)) |elf| @@ -646,10 +646,10 @@ pub const File = struct { &coff.mf else unreachable; - mf.file = try base.emit.root_dir.handle.adaptToNewApi().openFile(io, base.emit.sub_path, .{ + mf.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{ .mode = .read_write, }); - base.file = .adaptFromNewApi(mf.file); + base.file = mf.file; try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1])); }, .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), @@ -2007,7 +2007,7 @@ fn resolveLibInput( .sub_path = try std.fmt.allocPrint(arena, "lib{s}.tbd", .{lib_name}), }; try checked_paths.print(gpa, "\n {f}", .{test_path}); - var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) { + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { error.FileNotFound => break :tbd, else => |e| fatal("unable to search for tbd library '{f}': {s}", .{ test_path, @errorName(e) }), }; @@ -2043,7 +2043,7 @@ fn resolveLibInput( .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}), }; try checked_paths.print(gpa, "\n {f}", .{test_path}); - var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) { + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { error.FileNotFound => break :so, else => |e| fatal("unable to search for so library '{f}': {s}", .{ test_path, @errorName(e), @@ -2061,7 +2061,7 @@ fn resolveLibInput( .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}), }; try checked_paths.print(gpa, "\n {f}", .{test_path}); - var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) { + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { error.FileNotFound => break :mingw, else => |e| fatal("unable to search for static library '{f}': {s}", .{ test_path, @errorName(e) }), }; @@ -2115,7 +2115,7 @@ fn resolvePathInput( .static_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color), .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color), .object => { - var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err| + var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err| fatal("failed to open object {f}: {s}", .{ pq.path, @errorName(err) }); errdefer file.close(io); try resolved_inputs.append(gpa, .{ .object = .{ @@ -2127,7 +2127,7 @@ fn resolvePathInput( return null; }, .res => { - var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err| + var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err| fatal("failed to open windows resource {f}: {s}", .{ pq.path, @errorName(err) }); errdefer file.close(io); try resolved_inputs.append(gpa, .{ .res = .{ @@ -2164,7 +2164,7 @@ fn resolvePathInputLib( .static_library, .shared_library => true, else => false, }) { - var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) { + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { error.FileNotFound => return .no_match, else => |e| fatal("unable to search for {s} library '{f}': {s}", .{ @tagName(link_mode), std.fmt.alt(test_path, .formatEscapeChar), @errorName(e), @@ -2242,7 +2242,7 @@ fn resolvePathInputLib( return .ok; } - var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) { + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { error.FileNotFound => return .no_match, else => |e| fatal("unable to search for {s} library {f}: {s}", .{ @tagName(link_mode), test_path, @errorName(e), @@ -2253,7 +2253,7 @@ fn resolvePathInputLib( } pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object { - var file = try path.root_dir.handle.openFile(path.sub_path, .{}); + var file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); errdefer file.close(io); return .{ .path = path, @@ -2264,7 +2264,7 @@ pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Obje } pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso { - var file = try path.root_dir.handle.openFile(path.sub_path, .{}); + var file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); errdefer file.close(io); return .{ .path = path, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index f33e0ccdea8e2d4758c381c59b82b90460a7a2a0..e1d52fb7c4f07ee55b74da8ca278a8c73a0f1d94 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -632,7 +632,7 @@ fn create( }; const coff = try arena.create(Coff); - const file = try path.root_dir.handle.adaptToNewApi().createFile(comp.io, path.sub_path, .{ + const file = try path.root_dir.handle.createFile(comp.io, path.sub_path, .{ .read = true, .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode), }); @@ -644,7 +644,7 @@ fn create( .comp = comp, .emit = path, - .file = .adaptFromNewApi(file), + .file = file, .gc_sections = false, .print_gc_sections = false, .build_id = .none, diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index e35444bc0201d4b83976613a85d3d5130315d8f5..72fdb244a47084d4dec6d2c4d8c7426dfeb52b9b 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -928,6 +928,7 @@ fn create( path: std.Build.Cache.Path, options: link.File.OpenOptions, ) !*Elf { + const io = comp.io; const target = &comp.root_mod.resolved_target.result; assert(target.ofmt == .elf); const class: std.elf.CLASS = switch (target.ptrBitWidth()) { @@ -973,11 +974,11 @@ fn create( }; const elf = try arena.create(Elf); - const file = try path.root_dir.handle.adaptToNewApi().createFile(comp.io, path.sub_path, .{ + const file = try path.root_dir.handle.createFile(io, path.sub_path, .{ .read = true, .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode), }); - errdefer file.close(comp.io); + errdefer file.close(io); elf.* = .{ .base = .{ .tag = .elf2, @@ -985,7 +986,7 @@ fn create( .comp = comp, .emit = path, - .file = .adaptFromNewApi(file), + .file = file, .gc_sections = false, .print_gc_sections = false, .build_id = .none, @@ -3325,12 +3326,13 @@ fn flushInputSection(elf: *Elf, isi: Node.InputSectionIndex) !void { const file_loc = isi.fileLocation(elf); if (file_loc.size == 0) return; const comp = elf.base.comp; + const io = comp.io; const gpa = comp.gpa; const ii = isi.input(elf); const path = ii.path(elf); - const file = try path.root_dir.handle.adaptToNewApi().openFile(comp.io, path.sub_path, .{}); - defer file.close(comp.io); - var fr = file.reader(comp.io, &.{}); + const file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); + defer file.close(io); + var fr = file.reader(io, &.{}); try fr.seekTo(file_loc.offset); var nw: MappedFile.Node.Writer = undefined; const si = isi.symbol(elf); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 93adc633ce6a06c485438bf49302fab8c3ce8291..e837cc853a24dc8624b0824aaaee162828ca679d 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1126,7 +1126,9 @@ fn parseDependentDylibs(self: *MachO) !void { if (self.dylibs.items.len == 0) return; - const gpa = self.base.comp.gpa; + const comp = self.base.comp; + const gpa = comp.gpa; + const io = comp.io; const framework_dirs = self.framework_dirs; // TODO delete this, directories must instead be resolved by the frontend @@ -1183,7 +1185,7 @@ fn parseDependentDylibs(self: *MachO) !void { const path = if (existing_ext.len > 0) id.name[0 .. id.name.len - existing_ext.len] else id.name; for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| { test_path.clearRetainingCapacity(); - if (self.base.comp.sysroot) |root| { + if (comp.sysroot) |root| { try test_path.print("{s}" ++ fs.path.sep_str ++ "{s}{s}", .{ root, path, ext }); } else { try test_path.print("{s}{s}", .{ path, ext }); @@ -1235,7 +1237,7 @@ fn parseDependentDylibs(self: *MachO) !void { .path = Path.initCwd(full_path), .weak = is_weak, }; - const file = try lib.path.root_dir.handle.openFile(lib.path.sub_path, .{}); + const file = try lib.path.root_dir.handle.openFile(io, lib.path.sub_path, .{}); const fh = try self.addFileHandle(file); const fat_arch = try self.parseFatFile(file, lib.path); const offset = if (fat_arch) |fa| fa.offset else 0; diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index d2a6c2a3abd581248313c17868a350c248149989..0f42442640368c0ac6902c493f01776a74c446f5 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -1,6 +1,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void { - const gpa = macho_file.base.comp.gpa; - const diags = &macho_file.base.comp.link_diags; + const gpa = comp.gpa; + const io = comp.io; + const diags = &comp.link_diags; // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list. var positionals = std.array_list.Managed(link.Input).init(gpa); @@ -19,7 +20,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat // debug info segments/sections (this is apparently by design by Apple), we copy // the *only* input file over. const path = positionals.items[0].path().?; - const in_file = path.root_dir.handle.openFile(path.sub_path, .{}) catch |err| + const in_file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) }); const stat = in_file.stat() catch |err| return diags.fail("failed to stat {f}: {s}", .{ path, @errorName(err) }); diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 7d4134ccaf1e595a25d4a9fc65383de48a601f42..a61c6e764cb6495c63d66d9433aa6b16a4c05b64 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -630,7 +630,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested // Resize the entire file if (ni == Node.Index.root) { try mf.ensureCapacityForSetLocation(gpa); - try Io.File.adaptFromNewApi(mf.file).setEndPos(new_size); + try mf.file.setEndPos(new_size); try mf.ensureTotalCapacity(@intCast(new_size)); ni.setLocationAssumeCapacity(mf, old_offset, new_size); return; diff --git a/src/main.zig b/src/main.zig index 3ca64881f8bcda4c732c7a3748b7e6c8217fe128..b040b6c8efa00c42b769fe0a8dfc48e9cc5e3655 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4681,7 +4681,7 @@ fn cmdTranslateC( } else { const hex_digest = Cache.binToHex(result.digest); const out_zig_path = try fs.path.join(arena, &.{ "o", &hex_digest, translated_basename }); - const zig_file = comp.dirs.local_cache.handle.openFile(out_zig_path, .{}) catch |err| { + const zig_file = comp.dirs.local_cache.handle.openFile(io, out_zig_path, .{}) catch |err| { const path = comp.dirs.local_cache.path orelse "."; fatal("unable to open cached translated zig file '{s}{s}{s}': {s}", .{ path, @@ -6187,7 +6187,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { const display_path = zig_source_path orelse ""; const source: [:0]const u8 = s: { var f = if (zig_source_path) |p| file: { - break :file fs.cwd().openFile(p, .{}) catch |err| { + break :file fs.cwd().openFile(io, p, .{}) catch |err| { fatal("unable to open file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) }); }; } else Io.File.stdin(); @@ -6494,7 +6494,7 @@ fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void { const cache_file = args[0]; - var f = fs.cwd().openFile(cache_file, .{}) catch |err| { + var f = fs.cwd().openFile(io, cache_file, .{}) catch |err| { fatal("unable to open zir cache file for dumping '{s}': {s}", .{ cache_file, @errorName(err) }); }; defer f.close(io); @@ -6541,7 +6541,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { const new_source_path = args[1]; const old_source = source: { - var f = fs.cwd().openFile(old_source_path, .{}) catch |err| + var f = fs.cwd().openFile(io, old_source_path, .{}) catch |err| fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); defer f.close(io); var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer); @@ -6549,7 +6549,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); }; const new_source = source: { - var f = fs.cwd().openFile(new_source_path, .{}) catch |err| + var f = fs.cwd().openFile(io, new_source_path, .{}) catch |err| fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) }); defer f.close(io); var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer); -- 2.54.0 From 877032ec6a0007316f42658d12042f1473de4856 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 6 Dec 2025 20:43:57 -0800 Subject: [PATCH 013/195] std: reorganize realpath and process executable APIs --- lib/std/Io.zig | 4 +- lib/std/Io/Dir.zig | 39 +++-- lib/std/Io/File.zig | 6 - lib/std/Io/Kqueue.zig | 4 +- lib/std/Io/Threaded.zig | 152 ++++++++++++++++++- lib/std/debug/SelfInfo/Elf.zig | 2 +- lib/std/debug/SelfInfo/Windows.zig | 2 +- lib/std/fs.zig | 232 ----------------------------- lib/std/fs/test.zig | 55 ++++++- lib/std/posix/test.zig | 43 ------ lib/std/process.zig | 98 +++++++++++- lib/std/zig/system.zig | 11 +- 12 files changed, 333 insertions(+), 315 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index f783718cef16597326ecb0d912fb527ed9187013..17fb75fe5412a9b233711e15e1c8e24401c65d90 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -697,7 +697,6 @@ pub const VTable = struct { fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize, fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void, fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void, - openSelfExe: *const fn (?*anyopaque, File.OpenFlags) File.OpenSelfExeError!File, fileSync: *const fn (?*anyopaque, File) File.SyncError!void, fileIsTty: *const fn (?*anyopaque, File) Cancelable!bool, fileEnableAnsiEscapeCodes: *const fn (?*anyopaque, File) File.EnableAnsiEscapeCodesError!void, @@ -712,6 +711,9 @@ pub const VTable = struct { fileUnlock: *const fn (?*anyopaque, File) void, fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void, + processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File, + processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize, + now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, sleep: *const fn (?*anyopaque, Timeout) SleepError!void, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 9ae636d4a614e6427363ca16e9f375df85250d28..71ea3b465a23d504b11a0b9af2abb865e23b0272 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -694,24 +694,29 @@ pub const RealPathError = error{ /// supported hosts are: Linux, macOS, and Windows. /// /// See also: -/// * `realpathAlloc`. +/// * `realPathAlloc`. pub fn realPath(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathError!usize { return io.vtable.dirRealPath(io.userdata, dir, sub_path, out_buffer); } pub const RealPathAllocError = RealPathError || Allocator.Error; -/// Same as `Dir.realpath` except caller must free the returned memory. -/// See also `Dir.realpath`. -pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]u8 { - // Use of max_path_bytes here is valid as the realpath function does not - // have a variant that takes an arbitrary-size buffer. - // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008 - // NULL out parameter (GNU's canonicalize_file_name) to handle overelong - // paths. musl supports passing NULL but restricts the output to PATH_MAX - // anyway. - var buf: [std.fs.max_path_bytes]u8 = undefined; - return allocator.dupe(u8, try self.realpath(pathname, &buf)); +/// Same as `realPath` except allocates result. +pub fn realPathAlloc(dir: Dir, io: Io, sub_path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 { + var buffer: [std.fs.max_path_bytes]u8 = undefined; + const n = try realPath(dir, io, sub_path, &buffer); + return allocator.dupeZ(u8, buffer[0..n]); +} + +pub fn realPathAbsolute(io: Io, path: []const u8, out_buffer: []u8) RealPathError!usize { + return io.vtable.dirRealPath(io.userdata, .cwd(), path, out_buffer); +} + +/// Same as `realPathAbsolute` except allocates result. +pub fn realPathAbsoluteAlloc(io: Io, path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 { + var buffer: [std.fs.max_path_bytes]u8 = undefined; + const n = try realPathAbsolute(io, path, &buffer); + return allocator.dupeZ(u8, buffer[0..n]); } pub const DeleteFileError = error{ @@ -975,6 +980,16 @@ pub fn readLink(dir: Dir, io: Io, sub_path: []const u8, buffer: []u8) ReadLinkEr return io.vtable.dirReadLink(io.userdata, dir, sub_path, buffer); } +/// Same as `readLink`, except it asserts the path is absolute. +/// +/// On Windows, `path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, `path` should be encoded as valid UTF-8. +/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding. +pub fn readLinkAbsolute(io: Io, path: []const u8, buffer: []u8) ReadLinkError!usize { + assert(std.fs.path.isAbsolute(path)); + return io.vtable.dirReadLink(io.userdata, .cwd(), path, buffer); +} + pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{ /// File size reached or exceeded the provided limit. StreamTooLong, diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index a0349498c5f5027b47a1c364bcbc9546159b311f..b24c0b5100e896889104e2996551cbe774be273d 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -464,12 +464,6 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { return io.vtable.fileSetTimestampsNow(io.userdata, file); } -pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || LockError; - -pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File { - return io.vtable.openSelfExe(io.userdata, flags); -} - pub const ReadPositionalError = Reader.Error || error{Unseekable}; pub fn readPositional(file: File, io: Io, buffer: [][]u8, offset: u64) ReadPositionalError!usize { diff --git a/lib/std/Io/Kqueue.zig b/lib/std/Io/Kqueue.zig index 5b4f71da08cb7715177603696f8121c14adde000..e34c862ae99ec017aae3247e3d7037ed264c26fe 100644 --- a/lib/std/Io/Kqueue.zig +++ b/lib/std/Io/Kqueue.zig @@ -888,7 +888,7 @@ pub fn io(k: *Kqueue) Io { .fileReadPositional = fileReadPositional, .fileSeekBy = fileSeekBy, .fileSeekTo = fileSeekTo, - .openSelfExe = openSelfExe, + .openExecutable = openExecutable, .now = now, .sleep = sleep, @@ -1246,7 +1246,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, absolute_offset: u64) File.Seek _ = absolute_offset; @panic("TODO"); } -fn openSelfExe(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenSelfExeError!File { +fn openExecutable(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenExecutableError!File { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = file; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 43424e348b0e4a7c3816307011d43193d77fc7f0..fb76002201428ff080c6e553d24a28e73d5a7bd3 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -717,7 +717,6 @@ pub fn io(t: *Threaded) Io { .fileReadPositional = fileReadPositional, .fileSeekBy = fileSeekBy, .fileSeekTo = fileSeekTo, - .openSelfExe = openSelfExe, .fileSync = fileSync, .fileIsTty = fileIsTty, .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, @@ -732,6 +731,9 @@ pub fn io(t: *Threaded) Io { .fileUnlock = fileUnlock, .fileDowngradeLock = fileDowngradeLock, + .processExecutableOpen = processExecutableOpen, + .processExecutablePath = processExecutablePath, + .now = now, .sleep = sleep, @@ -839,7 +841,6 @@ pub fn ioBasic(t: *Threaded) Io { .fileReadPositional = fileReadPositional, .fileSeekBy = fileSeekBy, .fileSeekTo = fileSeekTo, - .openSelfExe = openSelfExe, .fileSync = fileSync, .fileIsTty = fileIsTty, .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, @@ -854,6 +855,9 @@ pub fn ioBasic(t: *Threaded) Io { .fileUnlock = fileUnlock, .fileDowngradeLock = fileDowngradeLock, + .processExecutableOpen = processExecutableOpen, + .processExecutablePath = processExecutablePath, + .now = now, .sleep = sleep, @@ -5932,7 +5936,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi } } -fn openSelfExe(userdata: ?*anyopaque, flags: File.OpenFlags) File.OpenSelfExeError!File { +fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.process.OpenExecutableError!File { const t: *Threaded = @ptrCast(@alignCast(userdata)); switch (native_os) { .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags), @@ -5945,7 +5949,147 @@ fn openSelfExe(userdata: ?*anyopaque, flags: File.OpenFlags) File.OpenSelfExeErr const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name); return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags); }, - else => @panic("TODO implement openSelfExe"), + else => @panic("TODO implement processExecutableOpen"), + } +} + +fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.ExecutablePathError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const max_path_bytes = std.fs.max_path_bytes; + + switch (native_os) { + .driverkit, + .ios, + .maccatalyst, + .macos, + .tvos, + .visionos, + .watchos, + => { + // Note that _NSGetExecutablePath() will return "a path" to + // the executable not a "real path" to the executable. + var symlink_path_buf: [max_path_bytes:0]u8 = undefined; + var u32_len: u32 = max_path_bytes + 1; // include the sentinel + const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len); + if (rc != 0) return error.NameTooLong; + + var real_path_buf: [max_path_bytes]u8 = undefined; + const real_path = Io.Dir.realPathAbsolute(ioBasic(t), &symlink_path_buf, &real_path_buf) catch |err| switch (err) { + error.NetworkNotFound => unreachable, // Windows-only + else => |e| return e, + }; + if (real_path.len > out_buffer.len) return error.NameTooLong; + const result = out_buffer[0..real_path.len]; + @memcpy(result, real_path); + return result.len; + }, + .linux, .serenity => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/exe", out_buffer) catch |err| switch (err) { + error.UnsupportedReparsePointType => unreachable, // Windows-only + error.NetworkNotFound => unreachable, // Windows-only + else => |e| return e, + }, + .illumos => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/path/a.out", out_buffer) catch |err| switch (err) { + error.UnsupportedReparsePointType => unreachable, // Windows-only + error.NetworkNotFound => unreachable, // Windows-only + else => |e| return e, + }, + .freebsd, .dragonfly => { + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); + var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 }; + var out_len: usize = out_buffer.len; + try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); + return out_len; + }, + .netbsd => { + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); + var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME }; + var out_len: usize = out_buffer.len; + try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); + return out_len; + }, + .openbsd, .haiku => { + // OpenBSD doesn't support getting the path of a running process, so try to guess it + if (std.os.argv.len == 0) + return error.FileNotFound; + + const argv0 = std.mem.span(std.os.argv[0]); + if (std.mem.indexOf(u8, argv0, "/") != null) { + // argv[0] is a path (relative or absolute): use realpath(3) directly + var real_path_buf: [max_path_bytes]u8 = undefined; + const real_path = Io.Dir.realPathAbsolute(ioBasic(t), std.os.argv[0], &real_path_buf) catch |err| switch (err) { + error.NetworkNotFound => unreachable, // Windows-only + else => |e| return e, + }; + if (real_path.len > out_buffer.len) + return error.NameTooLong; + const result = out_buffer[0..real_path.len]; + @memcpy(result, real_path); + return result.len; + } else if (argv0.len != 0) { + // argv[0] is not empty (and not a path): search it inside PATH + const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; + var path_it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter); + while (path_it.next()) |a_path| { + var resolved_path_buf: [max_path_bytes - 1:0]u8 = undefined; + const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ + a_path, std.os.argv[0], + }, 0) catch continue; + + var real_path_buf: [max_path_bytes]u8 = undefined; + if (Io.Dir.realPathAbsolute(ioBasic(t), resolved_path, &real_path_buf)) |real_path| { + // found a file, and hope it is the right file + if (real_path.len > out_buffer.len) + return error.NameTooLong; + const result = out_buffer[0..real_path.len]; + @memcpy(result, real_path); + return result.len; + } else |_| continue; + } + } + return error.FileNotFound; + }, + .windows => { + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); + const w = windows; + const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName; + const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; + + // If ImagePathName is a symlink, then it will contain the path of the + // symlink, not the path that the symlink points to. We want the path + // that the symlink points to, though, so we need to get the realpath. + var path_name_w = try w.wToPrefixedFileW(null, image_path_name); + + const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; + const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE; + const creation = w.FILE_OPEN; + const h_file = blk: { + const res = w.OpenFile(path_name_w.span(), .{ + .dir = null, + .access_mask = access_mask, + .share_access = share_access, + .creation = creation, + .filter = .any, + }) catch |err| switch (err) { + error.WouldBlock => unreachable, + else => |e| return e, + }; + break :blk res; + }; + defer w.CloseHandle(h_file); + + const wide_slice = w.GetFinalPathNameByHandle(h_file, .{}, out_buffer); + + const len = std.unicode.calcWtf8Len(wide_slice); + if (len > out_buffer.len) + return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return end_index; + }, + else => @compileError("unsupported OS"), } } diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index 124768687c8e8026fa3411e4433f3b2119105aa0..213389bf04c2c592b41e9fa9b3020bf035de294a 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -329,7 +329,7 @@ const Module = struct { defer file.close(io); break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name)); } else res: { - const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) { + const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) { error.OutOfMemory => |e| return e, else => return error.ReadFailed, }; diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index c7f9d8c352a012a00421ea1057cddcbeda2e3802..f0ac30cca22229ec8f26a07c3910876deb5e5676 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -434,7 +434,7 @@ const Module = struct { const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: { break :res std.fs.cwd().openFile(io, path, .{}); } else res: { - const self_dir = fs.selfExeDirPathAlloc(gpa) catch |err| switch (err) { + const self_dir = std.process.executableDirPathAlloc(io, gpa) catch |err| switch (err) { error.OutOfMemory, error.Unexpected => |e| return e, else => return error.ReadFailed, }; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index cb4daf7c50dc30a7eaf7488162985bfcf2ae39c1..78c924f8d915195844f414f1a81a45a1dd16f0c0 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -25,13 +25,6 @@ pub const File = std.Io.File; pub const path = @import("fs/path.zig"); pub const wasi = @import("fs/wasi.zig"); -// TODO audit these APIs with respect to Dir and absolute paths - -pub const realpath = posix.realpath; -pub const realpathZ = posix.realpathZ; -pub const realpathW = posix.realpathW; -pub const realpathW2 = posix.realpathW2; - pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; @@ -241,15 +234,6 @@ pub fn deleteTreeAbsolute(io: Io, absolute_path: []const u8) !void { return dir.deleteTree(path.basename(absolute_path)); } -/// Same as `Dir.readLink`, except it asserts the path is absolute. -/// On Windows, `pathname` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `pathname` should be encoded as valid UTF-8. -/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. -pub fn readLinkAbsolute(pathname: []const u8, buffer: *[max_path_bytes]u8) ![]u8 { - assert(path.isAbsolute(pathname)); - return posix.readlink(pathname, buffer); -} - /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent /// one; the latter case is known as a dangling link. @@ -287,222 +271,6 @@ pub fn symLinkAbsoluteW( return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory); } -// This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded -pub const SelfExePathError = error{ - FileNotFound, - AccessDenied, - NameTooLong, - NotSupported, - NotDir, - SymLinkLoop, - InputOutput, - FileTooBig, - IsDir, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - NoDevice, - SystemResources, - NoSpaceLeft, - FileSystem, - BadPathName, - DeviceBusy, - SharingViolation, - PipeBusy, - NotLink, - PathAlreadyExists, - - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, - ProcessNotFound, - - /// On Windows, antivirus software is enabled by default. It can be - /// disabled, but Windows Update sometimes ignores the user's preference - /// and re-enables it. When enabled, antivirus software on Windows - /// intercepts file system operations and makes them significantly slower - /// in addition to possibly failing with this error code. - AntivirusInterference, - - /// On Windows, the volume does not contain a recognized file system. File - /// system drivers might not be loaded, or the volume may be corrupt. - UnrecognizedVolume, - - Canceled, -} || posix.SysCtlError; - -/// `selfExePath` except allocates the result on the heap. -/// Caller owns returned memory. -pub fn selfExePathAlloc(allocator: Allocator) ![]u8 { - // Use of max_path_bytes here is justified as, at least on one tested Linux - // system, readlink will completely fail to return a result larger than - // PATH_MAX even if given a sufficiently large buffer. This makes it - // fundamentally impossible to get the selfExePath of a program running in - // a very deeply nested directory chain in this way. - // TODO(#4812): Investigate other systems and whether it is possible to get - // this path by trying larger and larger buffers until one succeeds. - var buf: [max_path_bytes]u8 = undefined; - return allocator.dupe(u8, try selfExePath(&buf)); -} - -/// Get the path to the current executable. Follows symlinks. -/// If you only need the directory, use selfExeDirPath. -/// If you only want an open file handle, use openSelfExe. -/// This function may return an error if the current executable -/// was deleted after spawning. -/// Returned value is a slice of out_buffer. -/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -/// -/// On Linux, depends on procfs being mounted. If the currently executing binary has -/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`. -/// TODO make the return type of this a null terminated pointer -pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { - if (is_darwin) { - // Note that _NSGetExecutablePath() will return "a path" to - // the executable not a "real path" to the executable. - var symlink_path_buf: [max_path_bytes:0]u8 = undefined; - var u32_len: u32 = max_path_bytes + 1; // include the sentinel - const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len); - if (rc != 0) return error.NameTooLong; - - var real_path_buf: [max_path_bytes]u8 = undefined; - const real_path = std.posix.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) { - error.NetworkNotFound => unreachable, // Windows-only - else => |e| return e, - }; - if (real_path.len > out_buffer.len) return error.NameTooLong; - const result = out_buffer[0..real_path.len]; - @memcpy(result, real_path); - return result; - } - switch (native_os) { - .linux, .serenity => return posix.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) { - error.UnsupportedReparsePointType => unreachable, // Windows-only - error.NetworkNotFound => unreachable, // Windows-only - else => |e| return e, - }, - .illumos => return posix.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) { - error.UnsupportedReparsePointType => unreachable, // Windows-only - error.NetworkNotFound => unreachable, // Windows-only - else => |e| return e, - }, - .freebsd, .dragonfly => { - var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 }; - var out_len: usize = out_buffer.len; - try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); - // TODO could this slice from 0 to out_len instead? - return mem.sliceTo(out_buffer, 0); - }, - .netbsd => { - var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME }; - var out_len: usize = out_buffer.len; - try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); - // TODO could this slice from 0 to out_len instead? - return mem.sliceTo(out_buffer, 0); - }, - .openbsd, .haiku => { - // OpenBSD doesn't support getting the path of a running process, so try to guess it - if (std.os.argv.len == 0) - return error.FileNotFound; - - const argv0 = mem.span(std.os.argv[0]); - if (mem.find(u8, argv0, "/") != null) { - // argv[0] is a path (relative or absolute): use realpath(3) directly - var real_path_buf: [max_path_bytes]u8 = undefined; - const real_path = posix.realpathZ(std.os.argv[0], &real_path_buf) catch |err| switch (err) { - error.NetworkNotFound => unreachable, // Windows-only - else => |e| return e, - }; - if (real_path.len > out_buffer.len) - return error.NameTooLong; - const result = out_buffer[0..real_path.len]; - @memcpy(result, real_path); - return result; - } else if (argv0.len != 0) { - // argv[0] is not empty (and not a path): search it inside PATH - const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; - var path_it = mem.tokenizeScalar(u8, PATH, path.delimiter); - while (path_it.next()) |a_path| { - var resolved_path_buf: [max_path_bytes - 1:0]u8 = undefined; - const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ - a_path, - std.os.argv[0], - }, 0) catch continue; - - var real_path_buf: [max_path_bytes]u8 = undefined; - if (posix.realpathZ(resolved_path, &real_path_buf)) |real_path| { - // found a file, and hope it is the right file - if (real_path.len > out_buffer.len) - return error.NameTooLong; - const result = out_buffer[0..real_path.len]; - @memcpy(result, real_path); - return result; - } else |_| continue; - } - } - return error.FileNotFound; - }, - .windows => { - const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName; - const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; - - // If ImagePathName is a symlink, then it will contain the path of the - // symlink, not the path that the symlink points to. We want the path - // that the symlink points to, though, so we need to get the realpath. - var pathname_w = try windows.wToPrefixedFileW(null, image_path_name); - - const wide_slice = try std.fs.cwd().realpathW2(pathname_w.span(), &pathname_w.data); - - const len = std.unicode.calcWtf8Len(wide_slice); - if (len > out_buffer.len) - return error.NameTooLong; - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); - return out_buffer[0..end_index]; - }, - else => @compileError("std.fs.selfExePath not supported for this target"), - } -} - -/// `selfExeDirPath` except allocates the result on the heap. -/// Caller owns returned memory. -pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 { - // Use of max_path_bytes here is justified as, at least on one tested Linux - // system, readlink will completely fail to return a result larger than - // PATH_MAX even if given a sufficiently large buffer. This makes it - // fundamentally impossible to get the selfExeDirPath of a program running - // in a very deeply nested directory chain in this way. - // TODO(#4812): Investigate other systems and whether it is possible to get - // this path by trying larger and larger buffers until one succeeds. - var buf: [max_path_bytes]u8 = undefined; - return allocator.dupe(u8, try selfExeDirPath(&buf)); -} - -/// Get the directory path that contains the current executable. -/// Returned value is a slice of out_buffer. -/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 { - const self_exe_path = try selfExePath(out_buffer); - // Assume that the OS APIs return absolute paths, and therefore dirname - // will not return null. - return path.dirname(self_exe_path).?; -} - -/// `realpath`, except caller must free the returned memory. -/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -/// See also `Dir.realpath`. -pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 { - // Use of max_path_bytes here is valid as the realpath function does not - // have a variant that takes an arbitrary-size buffer. - // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008 - // NULL out parameter (GNU's canonicalize_file_name) to handle overelong - // paths. musl supports passing NULL but restricts the output to PATH_MAX - // anyway. - var buf: [max_path_bytes]u8 = undefined; - return allocator.dupe(u8, try posix.realpath(pathname, &buf)); -} - test { _ = AtomicFile; _ = Dir; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 7d566da0e95ec6d1f3a70eb9c10e877da8347421..1a600fb82cc5f4fe436f5a3d3ab5f79c5874b02c 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -4,6 +4,7 @@ const native_os = builtin.os.tag; const std = @import("../std.zig"); const Io = std.Io; const testing = std.testing; +const expect = std.testing.expect; const fs = std.fs; const mem = std.mem; const wasi = std.os.wasi; @@ -1177,21 +1178,22 @@ test "renameAbsolute" { dir.close(io); } -test "openSelfExe" { +test "openExecutable" { if (native_os == .wasi) return error.SkipZigTest; const io = testing.io; - const self_exe_file = try std.fs.openSelfExe(.{}); + const self_exe_file = try std.fs.openExecutable(.{}); self_exe_file.close(io); } -test "selfExePath" { +test "executablePath" { if (native_os == .wasi) return error.SkipZigTest; + const io = testing.io; var buf: [fs.max_path_bytes]u8 = undefined; - const buf_self_exe_path = try std.fs.selfExePath(&buf); - const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator); + const buf_self_exe_path = try std.process.executablePath(io, &buf); + const alloc_self_exe_path = try std.process.executablePathAlloc(io, testing.allocator); defer testing.allocator.free(alloc_self_exe_path); try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path); } @@ -2371,3 +2373,46 @@ test "File.Writer sendfile with buffered contents" { try testing.expectEqualStrings("abcd", try check_r.interface.take(4)); try testing.expectError(error.EndOfStream, check_r.interface.takeByte()); } + +test "readlink on Windows" { + if (native_os != .windows) return error.SkipZigTest; + + try testReadlink("C:\\ProgramData", "C:\\Users\\All Users"); + try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User"); + try testReadlink("C:\\Users", "C:\\Documents and Settings"); +} + +fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { + var buffer: [fs.max_path_bytes]u8 = undefined; + const given = try Dir.readLinkAbsolute(symlink_path, buffer[0..]); + try expect(mem.eql(u8, target_path, given)); +} + +test "readlinkat" { + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + // create file + try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" }); + + // create a symbolic link + if (native_os == .windows) { + std.os.windows.CreateSymbolicLink( + tmp.dir.fd, + &[_]u16{ 'l', 'i', 'n', 'k' }, + &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, + false, + ) catch |err| switch (err) { + // Symlink requires admin privileges on windows, so this test can legitimately fail. + error.AccessDenied => return error.SkipZigTest, + else => return err, + }; + } else { + try posix.symlinkat("file.txt", tmp.dir.fd, "link"); + } + + // read the link + var buffer: [fs.max_path_bytes]u8 = undefined; + const read_link = try tmp.dir.readLink("link", &buffer); + try expect(mem.eql(u8, "file.txt", read_link)); +} diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 19313e3ff79031c2113f86ed7be304ec95b6a93b..0071a72a26dfd5d301666b18e890c84e3a1df79f 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -111,20 +111,6 @@ test "open smoke test" { } } -test "readlink on Windows" { - if (native_os != .windows) return error.SkipZigTest; - - try testReadlink("C:\\ProgramData", "C:\\Users\\All Users"); - try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User"); - try testReadlink("C:\\Users", "C:\\Documents and Settings"); -} - -fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { - var buffer: [fs.max_path_bytes]u8 = undefined; - const given = try posix.readlink(symlink_path, buffer[0..]); - try expect(mem.eql(u8, target_path, given)); -} - fn getLinkInfo(fd: posix.fd_t) !struct { posix.ino_t, posix.nlink_t } { if (native_os == .linux) { const stx = try linux.wrapped.statx( @@ -216,35 +202,6 @@ test "fstatat" { // try expectEqual(stat.blocks, statat.blocks); } -test "readlinkat" { - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - // create file - try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" }); - - // create a symbolic link - if (native_os == .windows) { - std.os.windows.CreateSymbolicLink( - tmp.dir.fd, - &[_]u16{ 'l', 'i', 'n', 'k' }, - &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, - false, - ) catch |err| switch (err) { - // Symlink requires admin privileges on windows, so this test can legitimately fail. - error.AccessDenied => return error.SkipZigTest, - else => return err, - }; - } else { - try posix.symlinkat("file.txt", tmp.dir.fd, "link"); - } - - // read the link - var buffer: [fs.max_path_bytes]u8 = undefined; - const read_link = try posix.readlinkat(tmp.dir.fd, "link", buffer[0..]); - try expect(mem.eql(u8, "file.txt", read_link)); -} - test "getrandom" { var buf_a: [50]u8 = undefined; var buf_b: [50]u8 = undefined; diff --git a/lib/std/process.zig b/lib/std/process.zig index a8dede6ad4aa52ec5750eefef3cf7ee5d32bc8f7..77f91587fef16fd3680c6ec5a6c653b50282e6f7 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -3,6 +3,7 @@ const native_os = builtin.os.tag; const std = @import("std.zig"); const Io = std.Io; +const File = std.Io.File; const fs = std.fs; const mem = std.mem; const math = std.math; @@ -12,6 +13,7 @@ const testing = std.testing; const posix = std.posix; const windows = std.os.windows; const unicode = std.unicode; +const max_path_bytes = std.fs.max_path_bytes; pub const Child = @import("process/Child.zig"); pub const abort = posix.abort; @@ -37,7 +39,7 @@ pub const GetCwdAllocError = Allocator.Error || error{CurrentWorkingDirectoryUnl pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 { // The use of max_path_bytes here is just a heuristic: most paths will fit // in stack_buf, avoiding an extra allocation in the common case. - var stack_buf: [fs.max_path_bytes]u8 = undefined; + var stack_buf: [max_path_bytes]u8 = undefined; var heap_buf: ?[]u8 = null; defer if (heap_buf) |buf| allocator.free(buf); @@ -2112,3 +2114,97 @@ pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn { std.log.err(format, format_arguments); exit(1); } + +pub const ExecutablePathBaseError = error{ + FileNotFound, + AccessDenied, + NotSupported, + NotDir, + SymLinkLoop, + InputOutput, + FileTooBig, + IsDir, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + NoSpaceLeft, + FileSystem, + BadPathName, + DeviceBusy, + SharingViolation, + PipeBusy, + NotLink, + PathAlreadyExists, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, + ProcessNotFound, + /// On Windows, antivirus software is enabled by default. It can be + /// disabled, but Windows Update sometimes ignores the user's preference + /// and re-enables it. When enabled, antivirus software on Windows + /// intercepts file system operations and makes them significantly slower + /// in addition to possibly failing with this error code. + AntivirusInterference, + /// On Windows, the volume does not contain a recognized file system. File + /// system drivers might not be loaded, or the volume may be corrupt. + UnrecognizedVolume, + PermissionDenied, +} || Io.Cancelable || Io.UnexpectedError; + +pub const ExecutablePathAllocError = ExecutablePathBaseError || Allocator.Error; + +pub fn executablePathAlloc(io: Io, allocator: Allocator) ExecutablePathAllocError![:0]u8 { + var buffer: [max_path_bytes]u8 = undefined; + const n = executablePath(io, &buffer) catch |err| switch (err) { + error.NameTooLong => unreachable, + else => |e| return e, + }; + return allocator.dupeZ(u8, buffer[0..n]); +} + +pub const ExecutablePathError = ExecutablePathBaseError || error{NameTooLong}; + +/// Get the path to the current executable, following symlinks. +/// +/// This function may return an error if the current executable +/// was deleted after spawning. +/// +/// Returned value is a slice of out_buffer. +/// +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. +/// +/// On Linux, depends on procfs being mounted. If the currently executing binary has +/// been deleted, the file path looks something like "/a/b/c/exe (deleted)". +/// +/// See also: +/// * `executableDirPath` - to obtain only the directory +/// * `openExecutable` - to obtain only an open file handle +pub fn executablePath(io: Io, out_buffer: []u8) ExecutablePathError!usize { + return io.vtable.processExecutablePath(io.userdata, out_buffer); +} + +/// Get the directory path that contains the current executable. +/// +/// Returns index into `out_buffer`. +/// +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. +pub fn executableDirPath(out_buffer: []u8) ExecutablePathError!usize { + const n = try executablePath(out_buffer); + // Assert that the OS APIs return absolute paths, and therefore dirname + // will not return null. + return std.fs.path.dirname(out_buffer[0..n]).?; +} + +/// Same as `executableDirPath` except allocates the result. +pub fn executableDirPathAlloc(allocator: Allocator) ![]u8 { + var buffer: [max_path_bytes]u8 = undefined; + return allocator.dupe(u8, try executableDirPath(&buffer)); +} + +pub const OpenExecutableError = File.OpenError || ExecutablePathError || File.LockError; + +pub fn openExecutable(io: Io, flags: File.OpenFlags) OpenExecutableError!File { + return io.vtable.processExecutableOpen(io.userdata, flags); +} diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index cc74da956af50b3a3b45f5327d29d437e5544c8d..5c110a576d5d54871c02a956fbd1082e291681d9 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -209,7 +209,6 @@ pub const DetectError = error{ DeviceBusy, OSVersionDetectionFail, Unexpected, - ProcessNotFound, } || Io.Cancelable; /// Given a `Target.Query`, which specifies in detail which parts of the @@ -422,7 +421,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { error.SocketUnconnected => return error.Unexpected, error.AccessDenied, - error.ProcessNotFound, error.SymLinkLoop, error.ProcessFdQuotaExceeded, error.SystemFdQuotaExceeded, @@ -553,7 +551,6 @@ pub const AbiAndDynamicLinkerFromFileError = error{ SystemResources, ProcessFdQuotaExceeded, SystemFdQuotaExceeded, - ProcessNotFound, IsDir, WouldBlock, InputOutput, @@ -693,8 +690,10 @@ fn abiAndDynamicLinkerFromFile( // So far, no luck. Next we try to see if the information is // present in the symlink data for the dynamic linker path. - var link_buf: [posix.PATH_MAX]u8 = undefined; - const link_name = posix.readlink(dl_path, &link_buf) catch |err| switch (err) { + var link_buffer: [posix.PATH_MAX]u8 = undefined; + const link_name = if (Io.Dir.readLinkAbsolute(io, dl_path, &link_buffer)) |n| + link_buffer[0..n] + else |err| switch (err) { error.NameTooLong => unreachable, error.BadPathName => unreachable, // Windows only error.UnsupportedReparsePointType => unreachable, // Windows only @@ -839,7 +838,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { error.NotDir => return error.GLibCNotFound, error.IsDir => return error.GLibCNotFound, - error.ProcessNotFound => |e| return e, error.ProcessFdQuotaExceeded => |e| return e, error.SystemFdQuotaExceeded => |e| return e, error.SystemResources => |e| return e, @@ -1103,7 +1101,6 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ error.SymLinkLoop, error.ProcessFdQuotaExceeded, error.SystemFdQuotaExceeded, - error.ProcessNotFound, error.Canceled, => |e| return e, -- 2.54.0 From 916998315967f73c91e682e9ea05dd3232818654 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 6 Dec 2025 21:06:30 -0800 Subject: [PATCH 014/195] std.fs: migrate most of the API elsewhere --- lib/std/Io/Dir.zig | 173 +++++++++++++++-- lib/std/Io/File.zig | 22 +-- lib/std/crypto/Certificate/Bundle.zig | 2 +- lib/std/fs.zig | 266 +------------------------- lib/std/fs/test.zig | 29 ++- lib/std/os.zig | 5 + lib/std/process.zig | 2 +- lib/std/std.zig | 2 +- lib/std/zig/system.zig | 2 +- lib/std/zig/system/linux.zig | 2 +- 10 files changed, 193 insertions(+), 312 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 71ea3b465a23d504b11a0b9af2abb865e23b0272..3b552a556a7780d4bcab936b543bba3e2a1e8986 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -1,4 +1,5 @@ const Dir = @This(); +const root = @import("root"); const builtin = @import("builtin"); const native_os = builtin.os.tag; @@ -11,6 +12,61 @@ const Allocator = std.mem.Allocator; handle: Handle, +pub const path = std.fs.path; + +/// The maximum length of a file path that the operating system will accept. +/// +/// Paths, including those returned from file system operations, may be longer +/// than this length, but such paths cannot be successfully passed back in +/// other file system operations. However, all path components returned by file +/// system operations are assumed to fit into a `u8` array of this length. +/// +/// The byte count includes room for a null sentinel byte. +/// +/// * On Windows, `[]u8` file paths are encoded as +/// [WTF-8](https://wtf-8.codeberg.page/). +/// * On WASI, `[]u8` file paths are encoded as valid UTF-8. +/// * On other platforms, `[]u8` file paths are opaque sequences of bytes with +/// no particular encoding. +pub const max_path_bytes = switch (native_os) { + .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .illumos, .plan9, .emscripten, .wasi, .serenity => std.posix.PATH_MAX, + // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes. + // If it would require 4 WTF-8 bytes, then there would be a surrogate + // pair in the WTF-16LE, and we (over)account 3 bytes for it that way. + // +1 for the null byte at the end, which can be encoded in 1 byte. + .windows => std.os.windows.PATH_MAX_WIDE * 3 + 1, + else => if (@hasDecl(root, "os") and @hasDecl(root.os, "PATH_MAX")) + root.os.PATH_MAX + else + @compileError("PATH_MAX not implemented for " ++ @tagName(native_os)), +}; + +/// This represents the maximum size of a `[]u8` file name component that +/// the platform's common file systems support. File name components returned by file system +/// operations are likely to fit into a `u8` array of this length, but +/// (depending on the platform) this assumption may not hold for every configuration. +/// The byte count does not include a null sentinel byte. +/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, file name components are encoded as valid UTF-8. +/// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding. +pub const max_name_bytes = switch (native_os) { + .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .illumos, .serenity => std.posix.NAME_MAX, + // Haiku's NAME_MAX includes the null terminator, so subtract one. + .haiku => std.posix.NAME_MAX - 1, + // Each WTF-16LE character may be expanded to 3 WTF-8 bytes. + // If it would require 4 WTF-8 bytes, then there would be a surrogate + // pair in the WTF-16LE, and we (over)account 3 bytes for it that way. + .windows => std.os.windows.NAME_MAX * 3, + // For WASI, the MAX_NAME will depend on the host OS, so it needs to be + // as large as the largest max_name_bytes (Windows) in order to work on any host OS. + // TODO determine if this is a reasonable approach + .wasi => std.os.windows.NAME_MAX * 3, + else => if (@hasDecl(root, "os") and @hasDecl(root.os, "NAME_MAX")) + root.os.NAME_MAX + else + @compileError("NAME_MAX not implemented for " ++ @tagName(native_os)), +}; + pub const Entry = struct { name: []const u8, kind: File.Kind, @@ -148,7 +204,7 @@ pub const SelectiveWalker = struct { }) |entry| { self.name_buffer.shrinkRetainingCapacity(dirname_len); if (self.name_buffer.items.len != 0) { - try self.name_buffer.append(self.allocator, std.fs.path.sep); + try self.name_buffer.append(self.allocator, path.sep); dirname_len += 1; } try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1); @@ -252,7 +308,7 @@ pub const Walker = struct { /// Returns 1 for a direct child of the initial directory, 2 for an entry /// within a direct child of the initial directory, etc. pub fn depth(self: Walker.Entry) usize { - return std.mem.countScalar(u8, self.path, std.fs.path.sep) + 1; + return std.mem.countScalar(u8, self.path, path.sep) + 1; } }; @@ -340,6 +396,11 @@ pub fn access(dir: Dir, io: Io, sub_path: []const u8, options: AccessOptions) Ac return io.vtable.dirAccess(io.userdata, dir, sub_path, options); } +pub fn accessAbsolute(io: Io, absolute_path: []const u8, options: AccessOptions) AccessError!void { + assert(path.isAbsolute(absolute_path)); + return access(.cwd(), io, absolute_path, options); +} + pub const OpenError = error{ FileNotFound, NotDir, @@ -379,6 +440,11 @@ pub fn openDir(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) Ope return io.vtable.dirOpenDir(io.userdata, dir, sub_path, options); } +pub fn openDirAbsolute(io: Io, absolute_path: []const u8, options: OpenOptions) OpenError!Dir { + assert(path.isAbsolute(absolute_path)); + return openDir(.cwd(), io, absolute_path, options); +} + pub fn close(dir: Dir, io: Io) void { return io.vtable.dirClose(io.userdata, dir); } @@ -396,6 +462,11 @@ pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) F return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags); } +pub fn openFileAbsolute(io: Io, absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File { + assert(path.isAbsolute(absolute_path)); + return openFile(.cwd(), io, absolute_path, flags); +} + /// Creates, opens, or overwrites a file with write access. /// /// Allocates a resource to be dellocated with `File.close`. @@ -407,6 +478,10 @@ pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlag return io.vtable.dirCreateFile(io.userdata, dir, sub_path, flags); } +pub fn createFileAbsolute(io: Io, absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File { + return createFile(.cwd(), io, absolute_path, flags); +} + pub const WriteFileOptions = struct { /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. @@ -476,7 +551,7 @@ pub fn updateFile( } } - if (std.fs.path.dirname(dest_path)) |dirname| { + if (path.dirname(dest_path)) |dirname| { try dest_dir.makePath(io, dirname, .default_dir); } @@ -556,6 +631,21 @@ pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) return io.vtable.dirMake(io.userdata, dir, sub_path, permissions); } +/// Create a new directory, based on an absolute path. +/// +/// Asserts that the path is absolute. See `makeDir` for a function that +/// operates on both absolute and relative paths. +/// +/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, `absolute_path` should be encoded as valid UTF-8. +/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. +pub fn makeDirAbsolute(io: Io, absolute_path: []const u8, permissions: Permissions) MakeError!void { + assert(path.isAbsolute(absolute_path)); + return makeDir(.cwd(), io, absolute_path, permissions); +} + +test makeDirAbsolute {} + pub const MakePathError = MakeError || StatPathError; /// Creates parent directories with default permissions as necessary to ensure @@ -703,19 +793,20 @@ pub const RealPathAllocError = RealPathError || Allocator.Error; /// Same as `realPath` except allocates result. pub fn realPathAlloc(dir: Dir, io: Io, sub_path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 { - var buffer: [std.fs.max_path_bytes]u8 = undefined; + var buffer: [max_path_bytes]u8 = undefined; const n = try realPath(dir, io, sub_path, &buffer); return allocator.dupeZ(u8, buffer[0..n]); } -pub fn realPathAbsolute(io: Io, path: []const u8, out_buffer: []u8) RealPathError!usize { - return io.vtable.dirRealPath(io.userdata, .cwd(), path, out_buffer); +pub fn realPathAbsolute(io: Io, absolute_path: []const u8, out_buffer: []u8) RealPathError!usize { + assert(path.isAbsolute(absolute_path)); + return io.vtable.dirRealPath(io.userdata, .cwd(), absolute_path, out_buffer); } /// Same as `realPathAbsolute` except allocates result. -pub fn realPathAbsoluteAlloc(io: Io, path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 { - var buffer: [std.fs.max_path_bytes]u8 = undefined; - const n = try realPathAbsolute(io, path, &buffer); +pub fn realPathAbsoluteAlloc(io: Io, absolute_path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 { + var buffer: [max_path_bytes]u8 = undefined; + const n = try realPathAbsolute(io, absolute_path, &buffer); return allocator.dupeZ(u8, buffer[0..n]); } @@ -754,6 +845,13 @@ pub fn deleteFile(dir: Dir, io: Io, sub_path: []const u8) DeleteFileError!void { return io.vtable.dirDeleteFile(io.userdata, dir, sub_path); } +pub fn deleteFileAbsolute(io: Io, absolute_path: []const u8) DeleteFileError!void { + assert(path.isAbsolute(absolute_path)); + return deleteFile(.cwd(), io, absolute_path); +} + +test deleteFileAbsolute {} + pub const DeleteDirError = error{ DirNotEmpty, FileNotFound, @@ -785,6 +883,16 @@ pub fn deleteDir(dir: Dir, io: Io, sub_path: []const u8) DeleteDirError!void { return io.vtable.dirDeleteDir(io.userdata, dir, sub_path); } +/// Same as `deleteDir` except the path is absolute. +/// +/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, `dir_path` should be encoded as valid UTF-8. +/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. +pub fn deleteDirAbsolute(io: Io, absolute_path: []const u8) DeleteDirError!void { + assert(path.isAbsolute(absolute_path)); + return deleteDir(.cwd(), io, absolute_path); +} + pub const RenameError = error{ /// In WASI, this error may occur when the file descriptor does /// not hold the required rights to rename a resource by path relative to it. @@ -893,6 +1001,17 @@ pub fn symLink( return io.vtable.dirSymLink(io.userdata, dir, target_path, sym_link_path, flags); } +pub fn symLinkAbsolute( + io: Io, + target_path: []const u8, + sym_link_path: []const u8, + flags: SymLinkFlags, +) SymLinkError!void { + assert(path.isAbsolute(target_path)); + assert(path.isAbsolute(sym_link_path)); + return symLink(.cwd(), io, target_path, sym_link_path, flags); +} + /// Same as `symLink`, except tries to create the symbolic link until it /// succeeds or encounters an error other than `error.PathAlreadyExists`. /// @@ -913,15 +1032,15 @@ pub fn symLinkAtomic( else => |e| return e, } - const dirname = std.fs.path.dirname(sym_link_path) orelse "."; + const dirname = path.dirname(sym_link_path) orelse "."; const rand_len = @sizeOf(u64) * 2; const temp_path_len = dirname.len + 1 + rand_len; - var temp_path_buf: [std.fs.max_path_bytes]u8 = undefined; + var temp_path_buf: [max_path_bytes]u8 = undefined; if (temp_path_len > temp_path_buf.len) return error.NameTooLong; @memcpy(temp_path_buf[0..dirname.len], dirname); - temp_path_buf[dirname.len] = std.fs.path.sep; + temp_path_buf[dirname.len] = path.sep; const temp_path = temp_path_buf[0..temp_path_len]; @@ -985,8 +1104,8 @@ pub fn readLink(dir: Dir, io: Io, sub_path: []const u8, buffer: []u8) ReadLinkEr /// On Windows, `path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `path` should be encoded as valid UTF-8. /// On other platforms, `path` is an opaque sequence of bytes with no particular encoding. -pub fn readLinkAbsolute(io: Io, path: []const u8, buffer: []u8) ReadLinkError!usize { - assert(std.fs.path.isAbsolute(path)); +pub fn readLinkAbsolute(io: Io, absolute_path: []const u8, buffer: []u8) ReadLinkError!usize { + assert(path.isAbsolute(absolute_path)); return io.vtable.dirReadLink(io.userdata, .cwd(), path, buffer); } @@ -1298,7 +1417,7 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, // Valid use of max_path_bytes because dir_name_buf will only // ever store a single path component that was returned from the // filesystem. - var dir_name_buf: [std.fs.max_path_bytes]u8 = undefined; + var dir_name_buf: [max_path_bytes]u8 = undefined; var dir_name: []const u8 = sub_path; // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function. @@ -1521,6 +1640,26 @@ pub fn copyFile( try atomic_file.finish(); } +/// Same as `copyFile`, except asserts that both `source_path` and `dest_path` +/// are absolute. +/// +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On WASI, both paths should be encoded as valid UTF-8. +/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +pub fn copyFileAbsolute( + source_path: []const u8, + dest_path: []const u8, + io: Io, + options: CopyFileOptions, +) !void { + assert(path.isAbsolute(source_path)); + assert(path.isAbsolute(dest_path)); + const my_cwd = cwd(); + return copyFile(my_cwd, source_path, my_cwd, dest_path, io, options); +} + +test copyFileAbsolute {} + pub const AtomicFileOptions = struct { permissions: File.Permissions = .default_file, make_path: bool = false, @@ -1538,13 +1677,13 @@ pub const AtomicFileOptions = struct { /// On WASI, `dest_path` should be encoded as valid UTF-8. /// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding. pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFileOptions) !File.Atomic { - if (std.fs.path.dirname(dest_path)) |dirname| { + if (path.dirname(dest_path)) |dirname| { const dir = if (options.make_path) try parent.makeOpenPath(io, dirname, .{}) else try parent.openDir(io, dirname, .{}); - return .init(std.fs.path.basename(dest_path), options.permissions, dir, true, options.write_buffer); + return .init(path.basename(dest_path), options.permissions, dir, true, options.write_buffer); } else { return .init(dest_path, options.permissions, parent, false, options.write_buffer); } diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index b24c0b5100e896889104e2996551cbe774be273d..8e71f648e294d6807bf86c91ecf5bb1845fc3e29 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -489,22 +489,6 @@ pub const WriteFileStreamingError = error{ SystemResources, } || Io.Cancelable || Io.UnexpectedError; -/// Opens a file for reading or writing, without attempting to create a new -/// file, based on an absolute path. -/// -/// Returns an open resource to be released with `close`. -/// -/// Asserts that the path is absolute. See `Dir.openFile` for a function that -/// operates on both absolute and relative paths. -/// -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `absolute_path` should be encoded as valid UTF-8. -/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. -pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File { - assert(std.fs.path.isAbsolute(absolute_path)); - return Io.Dir.cwd().openFile(io, absolute_path, flags); -} - pub const SeekError = error{ Unseekable, /// The file descriptor does not hold the required rights to seek on it. @@ -579,3 +563,9 @@ pub const DowngradeLockError = Io.Cancelable || Io.UnexpectedError; pub fn downgradeLock(file: File, io: Io) LockError!void { return io.vtable.fileDowngradeLock(io.userdata, file); } + +test { + _ = Reader; + _ = Writer; + _ = Atomic; +} diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index eb60ad37a8144726c13f42fe579841e4dd582c9c..d4a9f1e757d10f1b1cde732d8af9f4ab15a7d161 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -221,7 +221,7 @@ pub fn addCertsFromFilePathAbsolute( now: Io.Timestamp, abs_file_path: []const u8, ) AddCertsFromFilePathError!void { - var file = try fs.openFileAbsolute(abs_file_path, .{}); + var file = try Io.Dir.openFileAbsolute(io, abs_file_path, .{}); defer file.close(io); var file_reader = file.reader(io, &.{}); return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds()); diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 78c924f8d915195844f414f1a81a45a1dd16f0c0..5f2d36323ac0a4a92ccb797c728440baf8a584bc 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1,280 +1,28 @@ //! File System. -const builtin = @import("builtin"); -const native_os = builtin.os.tag; const std = @import("std.zig"); -const Io = std.Io; -const root = @import("root"); -const mem = std.mem; -const base64 = std.base64; -const crypto = std.crypto; -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; -const posix = std.posix; -const windows = std.os.windows; - -const is_darwin = native_os.isDarwin(); - -/// Deprecated. -pub const AtomicFile = std.Io.File.Atomic; -/// Deprecated. -pub const Dir = std.Io.Dir; -/// Deprecated. -pub const File = std.Io.File; +/// Deprecated, use `std.Io.Dir.path`. pub const path = @import("fs/path.zig"); pub const wasi = @import("fs/wasi.zig"); pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; -/// The maximum length of a file path that the operating system will accept. -/// -/// Paths, including those returned from file system operations, may be longer -/// than this length, but such paths cannot be successfully passed back in -/// other file system operations. However, all path components returned by file -/// system operations are assumed to fit into a `u8` array of this length. -/// -/// The byte count includes room for a null sentinel byte. -/// -/// * On Windows, `[]u8` file paths are encoded as -/// [WTF-8](https://wtf-8.codeberg.page/). -/// * On WASI, `[]u8` file paths are encoded as valid UTF-8. -/// * On other platforms, `[]u8` file paths are opaque sequences of bytes with -/// no particular encoding. -pub const max_path_bytes = switch (native_os) { - .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .illumos, .plan9, .emscripten, .wasi, .serenity => posix.PATH_MAX, - // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes. - // If it would require 4 WTF-8 bytes, then there would be a surrogate - // pair in the WTF-16LE, and we (over)account 3 bytes for it that way. - // +1 for the null byte at the end, which can be encoded in 1 byte. - .windows => windows.PATH_MAX_WIDE * 3 + 1, - else => if (@hasDecl(root, "os") and @hasDecl(root.os, "PATH_MAX")) - root.os.PATH_MAX - else - @compileError("PATH_MAX not implemented for " ++ @tagName(native_os)), -}; - -/// This represents the maximum size of a `[]u8` file name component that -/// the platform's common file systems support. File name components returned by file system -/// operations are likely to fit into a `u8` array of this length, but -/// (depending on the platform) this assumption may not hold for every configuration. -/// The byte count does not include a null sentinel byte. -/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, file name components are encoded as valid UTF-8. -/// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding. -pub const max_name_bytes = switch (native_os) { - .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .illumos, .serenity => posix.NAME_MAX, - // Haiku's NAME_MAX includes the null terminator, so subtract one. - .haiku => posix.NAME_MAX - 1, - // Each WTF-16LE character may be expanded to 3 WTF-8 bytes. - // If it would require 4 WTF-8 bytes, then there would be a surrogate - // pair in the WTF-16LE, and we (over)account 3 bytes for it that way. - .windows => windows.NAME_MAX * 3, - // For WASI, the MAX_NAME will depend on the host OS, so it needs to be - // as large as the largest max_name_bytes (Windows) in order to work on any host OS. - // TODO determine if this is a reasonable approach - .wasi => windows.NAME_MAX * 3, - else => if (@hasDecl(root, "os") and @hasDecl(root.os, "NAME_MAX")) - root.os.NAME_MAX - else - @compileError("NAME_MAX not implemented for " ++ @tagName(native_os)), -}; - pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; /// Base64 encoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem. -pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null); +pub const base64_encoder = std.base64.Base64Encoder.init(base64_alphabet, null); /// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem. -pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null); +pub const base64_decoder = std.base64.Base64Decoder.init(base64_alphabet, null); -/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path` -/// are absolute. See `Dir.copyFile` for a function that operates on both -/// absolute and relative paths. -/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn copyFileAbsolute( - source_path: []const u8, - dest_path: []const u8, - args: Dir.CopyFileOptions, -) !void { - assert(path.isAbsolute(source_path)); - assert(path.isAbsolute(dest_path)); - const my_cwd = cwd(); - return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args); -} - -test copyFileAbsolute {} - -/// Create a new directory, based on an absolute path. -/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates -/// on both absolute and relative paths. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `absolute_path` should be encoded as valid UTF-8. -/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. -pub fn makeDirAbsolute(absolute_path: []const u8) !void { - assert(path.isAbsolute(absolute_path)); - return posix.mkdir(absolute_path, Dir.default_mode); -} - -test makeDirAbsolute {} - -/// Same as `makeDirAbsolute` except the parameter is null-terminated. -pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void { - assert(path.isAbsoluteZ(absolute_path_z)); - return posix.mkdirZ(absolute_path_z, Dir.default_mode); -} - -test makeDirAbsoluteZ {} - -/// Same as `Dir.deleteDir` except the path is absolute. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn deleteDirAbsolute(dir_path: []const u8) !void { - assert(path.isAbsolute(dir_path)); - return posix.rmdir(dir_path); -} - -/// Same as `deleteDirAbsolute` except the path parameter is null-terminated. -pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void { - assert(path.isAbsoluteZ(dir_path)); - return posix.rmdirZ(dir_path); -} - -/// Deprecated in favor of `Io.Dir.cwd`. -pub fn cwd() Io.Dir { - return .cwd(); -} - -pub fn defaultWasiCwd() std.os.wasi.fd_t { - // Expect the first preopen to be current working directory. - return 3; -} - -/// Opens a directory at the given path. The directory is a system resource that remains -/// open until `close` is called on the result. -/// See `openDirAbsoluteZ` for a function that accepts a null-terminated path. -/// -/// Asserts that the path parameter has no null bytes. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `absolute_path` should be encoded as valid UTF-8. -/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. -pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenOptions) File.OpenError!Dir { - assert(path.isAbsolute(absolute_path)); - return cwd().openDir(absolute_path, flags); -} - -/// Deprecated in favor of `Io.File.openAbsolute`. -pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Io.File.OpenError!Io.File { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return Io.File.openAbsolute(io, absolute_path, flags); -} - -/// Test accessing `path`. -/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function. -/// For example, instead of testing if a file exists and then opening it, just -/// open it and handle the error for file not found. -/// See `accessAbsoluteZ` for a function that accepts a null-terminated path. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `absolute_path` should be encoded as valid UTF-8. -/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. -pub fn accessAbsolute(absolute_path: []const u8, flags: Io.Dir.AccessOptions) Dir.AccessError!void { - assert(path.isAbsolute(absolute_path)); - try cwd().access(absolute_path, flags); -} -/// Creates, opens, or overwrites a file with write access, based on an absolute path. -/// Call `File.close` to release the resource. -/// Asserts that the path is absolute. See `Dir.createFile` for a function that -/// operates on both absolute and relative paths. -/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function -/// that accepts a null-terminated path. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `absolute_path` should be encoded as valid UTF-8. -/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. -pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File { - assert(path.isAbsolute(absolute_path)); - return cwd().createFile(absolute_path, flags); -} - -/// Delete a file name and possibly the file it refers to, based on an absolute path. -/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that -/// operates on both absolute and relative paths. -/// Asserts that the path parameter has no null bytes. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `absolute_path` should be encoded as valid UTF-8. -/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. -pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void { - assert(path.isAbsolute(absolute_path)); - return cwd().deleteFile(absolute_path); -} - -/// Removes a symlink, file, or directory. -/// This is equivalent to `Dir.deleteTree` with the base directory. -/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that -/// operates on both absolute and relative paths. -/// Asserts that the path parameter has no null bytes. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `absolute_path` should be encoded as valid UTF-8. -/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. -pub fn deleteTreeAbsolute(io: Io, absolute_path: []const u8) !void { - assert(path.isAbsolute(absolute_path)); - const dirname = path.dirname(absolute_path) orelse return error{ - /// Attempt to remove the root file system path. - /// This error is unreachable if `absolute_path` is relative. - CannotDeleteRootDirectory, - }.CannotDeleteRootDirectory; - - var dir = try cwd().openDir(dirname, .{}); - defer dir.close(io); - - return dir.deleteTree(path.basename(absolute_path)); -} - -/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. -/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent -/// one; the latter case is known as a dangling link. -/// If `sym_link_path` exists, it will not be overwritten. -/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`. -/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn symLinkAbsolute( - target_path: []const u8, - sym_link_path: []const u8, - flags: Dir.SymLinkFlags, -) !void { - assert(path.isAbsolute(target_path)); - assert(path.isAbsolute(sym_link_path)); - if (native_os == .windows) { - const target_path_w = try windows.sliceToPrefixedFileW(null, target_path); - const sym_link_path_w = try windows.sliceToPrefixedFileW(null, sym_link_path); - return windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory); - } - return posix.symlink(target_path, sym_link_path); -} - -/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 LE encoded. -/// Note that this function will by default try creating a symbolic link to a file. If you would -/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`. -/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`. -pub fn symLinkAbsoluteW( - target_path_w: [*:0]const u16, - sym_link_path_w: [*:0]const u16, - flags: Dir.SymLinkFlags, -) !void { - assert(path.isAbsoluteWindowsW(target_path_w)); - assert(path.isAbsoluteWindowsW(sym_link_path_w)); - return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory); -} +/// Deprecated, use `std.Io.Dir.max_path_bytes`. +pub const max_path_bytes = std.Io.Dir.max_path_bytes; +/// Deprecated, use `std.Io.Dir.max_name_bytes`. +pub const max_name_bytes = std.Io.Dir.max_name_bytes; test { - _ = AtomicFile; - _ = Dir; - _ = File; _ = path; _ = @import("fs/test.zig"); _ = @import("fs/get_app_data_dir.zig"); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 1a600fb82cc5f4fe436f5a3d3ab5f79c5874b02c..f4bdecf89de155c28c4d94acaa8be778a22d1875 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -2087,7 +2087,7 @@ test "'.' and '..' in absolute functions" { try fs.copyFileAbsolute(created_file_path, copied_file_path, .{}); const renamed_file_path = try fs.path.join(allocator, &.{ subdir_path, "../rename" }); try fs.renameAbsolute(copied_file_path, renamed_file_path); - const renamed_file = try fs.openFileAbsolute(renamed_file_path, .{}); + const renamed_file = try Dir.openFileAbsolute(renamed_file_path, .{}); renamed_file.close(io); try fs.deleteFileAbsolute(renamed_file_path); @@ -2202,20 +2202,19 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path)); if (native_os != .wasi and ctx.path_type != .relative) { - try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{})); - try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path)); - try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path)); - try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path)); - try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{})); - try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{})); - try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{})); - try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{})); - try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path)); - try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path)); - var readlink_buf: [fs.max_path_bytes]u8 = undefined; - try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf)); - try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{})); - try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path)); + try testing.expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, .{})); + try testing.expectError(expected_err, Dir.makeDirAbsolute(invalid_path)); + try testing.expectError(expected_err, Dir.deleteDirAbsolute(invalid_path)); + try testing.expectError(expected_err, Dir.renameAbsolute(invalid_path, invalid_path)); + try testing.expectError(expected_err, Dir.openDirAbsolute(invalid_path, .{})); + try testing.expectError(expected_err, Dir.openFileAbsolute(invalid_path, .{})); + try testing.expectError(expected_err, Dir.accessAbsolute(invalid_path, .{})); + try testing.expectError(expected_err, Dir.createFileAbsolute(invalid_path, .{})); + try testing.expectError(expected_err, Dir.deleteFileAbsolute(invalid_path)); + var readlink_buf: [Dir.max_path_bytes]u8 = undefined; + try testing.expectError(expected_err, Dir.readLinkAbsolute(invalid_path, &readlink_buf)); + try testing.expectError(expected_err, Dir.symLinkAbsolute(invalid_path, invalid_path, .{})); + try testing.expectError(expected_err, Dir.realpathAlloc(testing.allocator, invalid_path)); } } }.impl); diff --git a/lib/std/os.zig b/lib/std/os.zig index 77a3833c2bbb28e475d298bd84588ff7e2428700..667d743f3d89abdd6ec1cac7d9877bd319530db5 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -72,3 +72,8 @@ pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t { else => |err| return posix.unexpectedErrno(err), } } + +pub fn defaultWasiCwd() std.os.wasi.fd_t { + // Expect the first preopen to be current working directory. + return 3; +} diff --git a/lib/std/process.zig b/lib/std/process.zig index 77f91587fef16fd3680c6ec5a6c653b50282e6f7..5d60190c2b743a885908da39e740a919f02a6148 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -1576,7 +1576,7 @@ pub fn getUserInfo(name: []const u8) !UserInfo { /// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else /// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`. pub fn posixGetUserInfo(io: Io, name: []const u8) !UserInfo { - const file = try std.fs.openFileAbsolute("/etc/passwd", .{}); + const file = try Io.Dir.openFileAbsolute(io, "/etc/passwd", .{}); defer file.close(io); var buffer: [4096]u8 = undefined; var file_reader = file.reader(&buffer); diff --git a/lib/std/std.zig b/lib/std/std.zig index 84e402f52bbdfd465c6022bf65c2ef7c5e3675f9..106811859b88f6532ee22212fec92875d5529fa0 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -115,7 +115,7 @@ pub const Options = struct { enable_segfault_handler: bool = debug.default_enable_segfault_handler, /// Function used to implement `std.fs.cwd` for WASI. - wasiCwd: fn () os.wasi.fd_t = fs.defaultWasiCwd, + wasiCwd: fn () os.wasi.fd_t = os.defaultWasiCwd, /// The current log level. log_level: log.Level = log.default_level, diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 5c110a576d5d54871c02a956fbd1082e291681d9..d3bafc16f2028e409fbfc68cd29716ddff8ba5d9 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -1024,7 +1024,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ }; while (true) { - const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) { + const file = Io.Dir.openFileAbsolute(io, file_name, .{}) catch |err| switch (err) { error.NoSpaceLeft => return error.Unexpected, error.NameTooLong => return error.Unexpected, error.PathAlreadyExists => return error.Unexpected, diff --git a/lib/std/zig/system/linux.zig b/lib/std/zig/system/linux.zig index 668e5a1d9989797da0714deb60853d1d522ae421..60f4d7bfec86233b578bca8a1a437d9207140b83 100644 --- a/lib/std/zig/system/linux.zig +++ b/lib/std/zig/system/linux.zig @@ -444,7 +444,7 @@ inline fn getAArch64CpuFeature(comptime feat_reg: []const u8) u64 { } pub fn detectNativeCpuAndFeatures(io: Io) ?Target.Cpu { - var file = fs.openFileAbsolute("/proc/cpuinfo", .{}) catch |err| switch (err) { + var file = Io.Dir.openFileAbsolute(io, "/proc/cpuinfo", .{}) catch |err| switch (err) { else => return null, }; defer file.close(io); -- 2.54.0 From f53248a40936ebc9aaf75ddbd16e67ebec05ab84 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 13:39:09 -0800 Subject: [PATCH 015/195] update all std.fs.cwd() to std.Io.Dir.cwd() --- lib/compiler/aro/aro/Compilation.zig | 8 +- lib/compiler/aro/aro/Driver.zig | 10 +- lib/compiler/aro/aro/Parser.zig | 27 ++--- lib/compiler/aro/aro/Preprocessor.zig | 6 +- lib/compiler/aro/aro/Tokenizer.zig | 5 +- lib/compiler/aro/aro/Value.zig | 11 +- lib/compiler/aro/main.zig | 2 +- lib/compiler/objcopy.zig | 4 +- lib/compiler/reduce.zig | 6 +- lib/compiler/resinator/cli.zig | 2 +- lib/compiler/resinator/compile.zig | 4 +- lib/compiler/resinator/main.zig | 22 ++-- lib/compiler/std-docs.zig | 2 +- lib/compiler/translate-c/main.zig | 8 +- lib/std/Build.zig | 10 +- lib/std/Build/Cache.zig | 8 +- lib/std/Build/Step.zig | 17 +-- lib/std/Build/Step/CheckFile.zig | 4 +- lib/std/Build/Step/ConfigHeader.zig | 8 +- lib/std/Build/Step/Options.zig | 15 +-- lib/std/Build/Step/Run.zig | 2 +- lib/std/Build/Watch.zig | 16 +-- lib/std/Io/File.zig | 8 ++ lib/std/Io/Threaded.zig | 9 +- lib/std/Io/Writer.zig | 6 +- lib/std/Io/net/test.zig | 2 +- lib/std/Io/test.zig | 10 +- lib/std/Thread.zig | 4 +- lib/std/crypto/Certificate/Bundle/macos.zig | 2 +- lib/std/crypto/codecs/asn1/test.zig | 4 +- lib/std/debug.zig | 20 ++-- lib/std/debug/ElfFile.zig | 2 +- lib/std/debug/MachOFile.zig | 2 +- lib/std/debug/SelfInfo/Elf.zig | 19 ++-- lib/std/debug/SelfInfo/MachO.zig | 2 +- lib/std/debug/SelfInfo/Windows.zig | 4 +- lib/std/dynamic_library.zig | 8 +- lib/std/fs/test.zig | 116 +++++++++----------- lib/std/os/linux/IoUring.zig | 24 ++-- lib/std/os/linux/test.zig | 6 +- lib/std/os/windows.zig | 4 +- lib/std/posix.zig | 14 +-- lib/std/posix/test.zig | 52 ++++----- lib/std/process/Child.zig | 2 +- lib/std/std.zig | 2 +- lib/std/tar.zig | 14 +-- lib/std/testing.zig | 2 +- lib/std/zig/LibCInstallation.zig | 12 +- lib/std/zig/WindowsSdk.zig | 2 +- lib/std/zig/system.zig | 6 +- lib/std/zig/system/darwin/macos.zig | 7 +- lib/std/zip.zig | 4 +- src/Compilation.zig | 20 ++-- src/Package/Fetch.zig | 12 +- src/Package/Fetch/git.zig | 12 +- src/Zcu/PerThread.zig | 4 +- src/codegen/llvm.zig | 17 +-- src/fmt.zig | 6 +- src/introspect.zig | 4 +- src/libs/freebsd.zig | 12 +- src/libs/glibc.zig | 16 +-- src/libs/mingw.zig | 17 +-- src/libs/netbsd.zig | 12 +- src/link/C.zig | 4 +- src/link/Coff.zig | 6 +- src/link/Elf.zig | 4 +- src/link/Lld.zig | 4 +- src/link/MachO.zig | 14 ++- src/link/MachO/CodeSignature.zig | 2 +- src/link/SpirV.zig | 3 +- src/link/Wasm.zig | 4 +- src/main.zig | 36 +++--- 72 files changed, 398 insertions(+), 377 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index 09e4861d133e9affa44263241f568f58980419ba..b3e4d5544da9f245174a9e6e6f72d664c0a95ce7 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -2253,7 +2253,7 @@ test "addSourceFromBuffer" { var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, &diagnostics, Io.Dir.cwd()); defer comp.deinit(); const source = try comp.addSourceFromBuffer("path", str); @@ -2267,7 +2267,7 @@ test "addSourceFromBuffer" { var arena: std.heap.ArenaAllocator = .init(allocator); defer arena.deinit(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, arena.allocator(), std.testing.io, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(allocator, arena.allocator(), std.testing.io, &diagnostics, Io.Dir.cwd()); defer comp.deinit(); _ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n"); @@ -2313,7 +2313,7 @@ test "addSourceFromBuffer - exhaustive check for carriage return elimination" { var buf: [alphabet.len]u8 = @splat(alphabet[0]); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, &diagnostics, Io.Dir.cwd()); defer comp.deinit(); var source_count: u32 = 0; @@ -2341,7 +2341,7 @@ test "ignore BOM at beginning of file" { const Test = struct { fn run(arena: Allocator, buf: []const u8) !void { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, &diagnostics, Io.Dir.cwd()); defer comp.deinit(); const source = try comp.addSourceFromBuffer("file.c", buf); diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig index f933e3ce52f61d42607d26472ee7d0dd94ebe158..fec3cea0f877f5c7b3ffa2820971c02e174f0d65 100644 --- a/lib/compiler/aro/aro/Driver.zig +++ b/lib/compiler/aro/aro/Driver.zig @@ -1327,7 +1327,7 @@ fn processSource( const dep_file_name = try d.getDepFileName(source, writer_buf[0..std.fs.max_name_bytes]); const file = if (dep_file_name) |path| - d.comp.cwd.createFile(path, .{}) catch |er| + d.comp.cwd.createFile(io, path, .{}) catch |er| return d.fatal("unable to create dependency file '{s}': {s}", .{ path, errorDescription(er) }) else Io.File.stdout(); @@ -1352,7 +1352,7 @@ fn processSource( } const file = if (d.output_name) |some| - d.comp.cwd.createFile(some, .{}) catch |er| + d.comp.cwd.createFile(io, some, .{}) catch |er| return d.fatal("unable to create output file '{s}': {s}", .{ some, errorDescription(er) }) else Io.File.stdout(); @@ -1405,7 +1405,7 @@ fn processSource( defer assembly.deinit(gpa); if (d.only_preprocess_and_compile) { - const out_file = d.comp.cwd.createFile(out_file_name, .{}) catch |er| + const out_file = d.comp.cwd.createFile(io, out_file_name, .{}) catch |er| return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) }); defer out_file.close(io); @@ -1419,7 +1419,7 @@ fn processSource( // then assemble to out_file_name var assembly_name_buf: [std.fs.max_name_bytes]u8 = undefined; const assembly_out_file_name = try d.getRandomFilename(&assembly_name_buf, ".s"); - const out_file = d.comp.cwd.createFile(assembly_out_file_name, .{}) catch |er| + const out_file = d.comp.cwd.createFile(io, assembly_out_file_name, .{}) catch |er| return d.fatal("unable to create output file '{s}': {s}", .{ assembly_out_file_name, errorDescription(er) }); defer out_file.close(io); assembly.writeToFile(out_file) catch |er| @@ -1455,7 +1455,7 @@ fn processSource( }; defer obj.deinit(); - const out_file = d.comp.cwd.createFile(out_file_name, .{}) catch |er| + const out_file = d.comp.cwd.createFile(io, out_file_name, .{}) catch |er| return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) }); defer out_file.close(io); diff --git a/lib/compiler/aro/aro/Parser.zig b/lib/compiler/aro/aro/Parser.zig index 4a89e0d4607762335795ed2f098ac1072993f0c5..fc21ee4d0b5f25f8f28b08c157d80a59b83adda8 100644 --- a/lib/compiler/aro/aro/Parser.zig +++ b/lib/compiler/aro/aro/Parser.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const mem = std.mem; const Allocator = mem.Allocator; const assert = std.debug.assert; @@ -211,7 +212,7 @@ fn checkIdentifierCodepointWarnings(p: *Parser, codepoint: u21, loc: Source.Loca const prev_total = p.diagnostics.total; var sf = std.heap.stackFallback(1024, p.comp.gpa); - var allocating: std.Io.Writer.Allocating = .init(sf.get()); + var allocating: Io.Writer.Allocating = .init(sf.get()); defer allocating.deinit(); if (!char_info.isC99IdChar(codepoint)) { @@ -425,7 +426,7 @@ pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype) if (p.diagnostics.effectiveKind(diagnostic) == .off) return; var sf = std.heap.stackFallback(1024, p.comp.gpa); - var allocating: std.Io.Writer.Allocating = .init(sf.get()); + var allocating: Io.Writer.Allocating = .init(sf.get()); defer allocating.deinit(); 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) }, p.pp.expansionSlice(tok_i), true); } -fn formatArgs(p: *Parser, w: *std.Io.Writer, fmt: []const u8, args: anytype) !void { +fn formatArgs(p: *Parser, w: *Io.Writer, fmt: []const u8, args: anytype) !void { var i: usize = 0; inline for (std.meta.fields(@TypeOf(args))) |arg_info| { 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 try w.writeAll(fmt[i..]); } -fn formatTokenId(w: *std.Io.Writer, fmt: []const u8, tok_id: Tree.Token.Id) !usize { +fn formatTokenId(w: *Io.Writer, fmt: []const u8, tok_id: Tree.Token.Id) !usize { const i = Diagnostics.templateIndex(w, fmt, "{tok_id}"); try w.writeAll(tok_id.symbol()); return i; } -fn formatQualType(p: *Parser, w: *std.Io.Writer, fmt: []const u8, qt: QualType) !usize { +fn formatQualType(p: *Parser, w: *Io.Writer, fmt: []const u8, qt: QualType) !usize { const i = Diagnostics.templateIndex(w, fmt, "{qt}"); try w.writeByte('\''); try qt.print(p.comp, w); @@ -501,7 +502,7 @@ fn formatQualType(p: *Parser, w: *std.Io.Writer, fmt: []const u8, qt: QualType) return i; } -fn formatResult(p: *Parser, w: *std.Io.Writer, fmt: []const u8, res: Result) !usize { +fn formatResult(p: *Parser, w: *Io.Writer, fmt: []const u8, res: Result) !usize { const i = Diagnostics.templateIndex(w, fmt, "{value}"); switch (res.val.opt_ref) { .none => try w.writeAll("(none)"), @@ -524,7 +525,7 @@ const Normalized = struct { return .{ .str = str }; } - pub fn format(ctx: Normalized, w: *std.Io.Writer, fmt: []const u8) !usize { + pub fn format(ctx: Normalized, w: *Io.Writer, fmt: []const u8) !usize { const i = Diagnostics.templateIndex(w, fmt, "{normalized}"); var it: std.unicode.Utf8Iterator = .{ .bytes = ctx.str, @@ -558,7 +559,7 @@ const Codepoint = struct { return .{ .codepoint = codepoint }; } - pub fn format(ctx: Codepoint, w: *std.Io.Writer, fmt: []const u8) !usize { + pub fn format(ctx: Codepoint, w: *Io.Writer, fmt: []const u8) !usize { const i = Diagnostics.templateIndex(w, fmt, "{codepoint}"); try w.print("{X:0>4}", .{ctx.codepoint}); return i; @@ -572,7 +573,7 @@ const Escaped = struct { return .{ .str = str }; } - pub fn format(ctx: Escaped, w: *std.Io.Writer, fmt: []const u8) !usize { + pub fn format(ctx: Escaped, w: *Io.Writer, fmt: []const u8) !usize { const i = Diagnostics.templateIndex(w, fmt, "{s}"); try std.zig.stringEscape(ctx.str, w); return i; @@ -1453,7 +1454,7 @@ fn decl(p: *Parser) Error!bool { return true; } -fn staticAssertMessage(p: *Parser, cond_node: Node.Index, maybe_message: ?Result, allocating: *std.Io.Writer.Allocating) !?[]const u8 { +fn staticAssertMessage(p: *Parser, cond_node: Node.Index, maybe_message: ?Result, allocating: *Io.Writer.Allocating) !?[]const u8 { const w = &allocating.writer; const cond = cond_node.get(&p.tree); @@ -1526,7 +1527,7 @@ fn staticAssert(p: *Parser) Error!bool { } else { if (!res.val.toBool(p.comp)) { var sf = std.heap.stackFallback(1024, gpa); - var allocating: std.Io.Writer.Allocating = .init(sf.get()); + var allocating: Io.Writer.Allocating = .init(sf.get()); defer allocating.deinit(); if (p.staticAssertMessage(res_node, str, &allocating) catch return error.OutOfMemory) |message| { @@ -9719,7 +9720,7 @@ fn primaryExpr(p: *Parser) Error!?Result { qt = some.qt; } else if (p.func.qt) |func_qt| { var sf = std.heap.stackFallback(1024, gpa); - var allocating: std.Io.Writer.Allocating = .init(sf.get()); + var allocating: Io.Writer.Allocating = .init(sf.get()); defer allocating.deinit(); func_qt.printNamed(p.tokSlice(p.func.name), p.comp, &allocating.writer) catch return error.OutOfMemory; @@ -10608,7 +10609,7 @@ test "Node locations" { const arena = arena_state.allocator(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, &diagnostics, Io.Dir.cwd()); defer comp.deinit(); const file = try comp.addSourceFromBuffer("file.c", diff --git a/lib/compiler/aro/aro/Preprocessor.zig b/lib/compiler/aro/aro/Preprocessor.zig index d47727cbf0a94c25291b7d76aa11c12d80a04e7e..e8343dc83a0d0a16a2fc4b44f83d6ca4ffa9552c 100644 --- a/lib/compiler/aro/aro/Preprocessor.zig +++ b/lib/compiler/aro/aro/Preprocessor.zig @@ -3900,7 +3900,7 @@ test "Preserve pragma tokens sometimes" { defer arena.deinit(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(gpa, arena.allocator(), std.testing.io, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(gpa, arena.allocator(), std.testing.io, &diagnostics, Io.Dir.cwd()); defer comp.deinit(); try comp.addDefaultPragmaHandlers(); @@ -3967,7 +3967,7 @@ test "destringify" { var arena: std.heap.ArenaAllocator = .init(gpa); defer arena.deinit(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(gpa, arena.allocator(), std.testing.io, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(gpa, arena.allocator(), std.testing.io, &diagnostics, Io.Dir.cwd()); defer comp.deinit(); var pp = Preprocessor.init(&comp, .default); defer pp.deinit(); @@ -4030,7 +4030,7 @@ test "Include guards" { const arena = arena_state.allocator(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(gpa, arena, std.testing.io, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(gpa, arena, std.testing.io, &diagnostics, Io.Dir.cwd()); defer comp.deinit(); var pp = Preprocessor.init(&comp, .default); defer pp.deinit(); diff --git a/lib/compiler/aro/aro/Tokenizer.zig b/lib/compiler/aro/aro/Tokenizer.zig index c497c5ce828e93a0aa6b80787119fe84428de6d9..198d49364afecd434caede2440e424237ed8f65e 100644 --- a/lib/compiler/aro/aro/Tokenizer.zig +++ b/lib/compiler/aro/aro/Tokenizer.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const Compilation = @import("Compilation.zig"); @@ -2326,7 +2327,7 @@ test "Tokenizer fuzz test" { fn testOne(_: @This(), input_bytes: []const u8) anyerror!void { var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); - var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, undefined, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, undefined, Io.Dir.cwd()); defer comp.deinit(); const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes); @@ -2351,7 +2352,7 @@ test "Tokenizer fuzz test" { fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, langopts: ?LangOpts) !void { var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); - var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, undefined, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, undefined, Io.Dir.cwd()); defer comp.deinit(); if (langopts) |provided| { comp.langopts = provided; diff --git a/lib/compiler/aro/aro/Value.zig b/lib/compiler/aro/aro/Value.zig index 25a2d1824f7e4df3812698c4315253ec08aebc26..14949ce03bbb3ade7c548b88894433213cb1a591 100644 --- a/lib/compiler/aro/aro/Value.zig +++ b/lib/compiler/aro/aro/Value.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const BigIntConst = std.math.big.int.Const; const BigIntMutable = std.math.big.int.Mutable; @@ -80,7 +81,7 @@ test "minUnsignedBits" { defer arena_state.deinit(); const arena = arena_state.allocator(); - var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, undefined, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, undefined, Io.Dir.cwd()); defer comp.deinit(); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); comp.target = .fromZigTarget(try std.zig.system.resolveTargetQuery(std.testing.io, target_query)); @@ -119,7 +120,7 @@ test "minSignedBits" { defer arena_state.deinit(); const arena = arena_state.allocator(); - var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, undefined, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, undefined, Io.Dir.cwd()); defer comp.deinit(); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); comp.target = .fromZigTarget(try std.zig.system.resolveTargetQuery(std.testing.io, target_query)); @@ -1080,7 +1081,7 @@ const NestedPrint = union(enum) { }, }; -pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!void { +pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w: *Io.Writer) Io.Writer.Error!void { try w.writeByte('&'); try w.writeAll(base); if (!offset.isZero(comp)) { @@ -1089,7 +1090,7 @@ pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w } } -pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!?NestedPrint { +pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *Io.Writer) Io.Writer.Error!?NestedPrint { if (qt.is(comp, .bool)) { try w.writeAll(if (v.isZero(comp)) "false" else "true"); return null; @@ -1116,7 +1117,7 @@ pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *std.Io.Writer return null; } -pub fn printString(bytes: []const u8, qt: QualType, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!void { +pub fn printString(bytes: []const u8, qt: QualType, comp: *const Compilation, w: *Io.Writer) Io.Writer.Error!void { const size: Compilation.CharUnitSize = @enumFromInt(qt.childType(comp).sizeof(comp)); const without_null = bytes[0 .. bytes.len - @intFromEnum(size)]; try w.writeByte('"'); diff --git a/lib/compiler/aro/main.zig b/lib/compiler/aro/main.zig index 66c8add4a32343fd38e5e71b90348da604dca273..d1be1dbb21ab01bc5d95ab5894f54b08485fcdf3 100644 --- a/lib/compiler/aro/main.zig +++ b/lib/compiler/aro/main.zig @@ -59,7 +59,7 @@ pub fn main() u8 { } }, }; - var comp = Compilation.initDefault(gpa, arena, io, &diagnostics, std.fs.cwd()) catch |er| switch (er) { + var comp = Compilation.initDefault(gpa, arena, io, &diagnostics, Io.Dir.cwd()) catch |er| switch (er) { error.OutOfMemory => { std.debug.print("out of memory\n", .{}); if (fast_exit) process.exit(1); diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 485e644daaf7f3966a0e52b46e0ff7925a28bd0a..e48f76a6a61a68bcb003bf68f53290200505abcd 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -152,7 +152,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void defer threaded.deinit(); const io = threaded.io(); - const input_file = fs.cwd().openFile(input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err }); + const input_file = Io.Dir.cwd().openFile(input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err }); defer input_file.close(io); const stat = input_file.stat() catch |err| fatal("failed to stat {s}: {t}", .{ input, err }); @@ -180,7 +180,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void const mode = if (out_fmt != .elf or only_keep_debug) Io.File.default_mode else stat.mode; - var output_file = try fs.cwd().createFile(output, .{ .mode = mode }); + var output_file = try Io.Dir.cwd().createFile(io, output, .{ .mode = mode }); defer output_file.close(io); var out = output_file.writer(&output_buffer); diff --git a/lib/compiler/reduce.zig b/lib/compiler/reduce.zig index bbd3d172b4833acd79774e9c427b167774c89171..d3f33ad81af2ca2dcb9b14f711bb94cf8d22f9e5 100644 --- a/lib/compiler/reduce.zig +++ b/lib/compiler/reduce.zig @@ -233,7 +233,7 @@ pub fn main() !void { } } - try std.fs.cwd().writeFile(.{ .sub_path = root_source_file_path, .data = rendered.written() }); + try Io.Dir.cwd().writeFile(.{ .sub_path = root_source_file_path, .data = rendered.written() }); // std.debug.print("trying this code:\n{s}\n", .{rendered.items}); const interestingness = try runCheck(arena, interestingness_argv.items); @@ -274,7 +274,7 @@ pub fn main() !void { fixups.clearRetainingCapacity(); rendered.clearRetainingCapacity(); try tree.render(gpa, &rendered.writer, fixups); - try std.fs.cwd().writeFile(.{ .sub_path = root_source_file_path, .data = rendered.written() }); + try Io.Dir.cwd().writeFile(.{ .sub_path = root_source_file_path, .data = rendered.written() }); return std.process.cleanExit(); } @@ -398,7 +398,7 @@ fn transformationsToFixups( } fn parse(gpa: Allocator, file_path: []const u8) !Ast { - const source_code = std.fs.cwd().readFileAllocOptions( + const source_code = Io.Dir.cwd().readFileAllocOptions( file_path, gpa, .limited(std.math.maxInt(u32)), diff --git a/lib/compiler/resinator/cli.zig b/lib/compiler/resinator/cli.zig index bb54289e3e966ed23561db14602bcb44c37faca5..ae4ece29688d38d2a6de5d22a90a2a8b4ce2fc81 100644 --- a/lib/compiler/resinator/cli.zig +++ b/lib/compiler/resinator/cli.zig @@ -2003,7 +2003,7 @@ test "maybeAppendRC" { // Create the file so that it's found. In this scenario, .rc should not get // appended. - var file = try tmp.dir.createFile("foo", .{}); + var file = try tmp.dir.createFile(io, "foo", .{}); file.close(io); try options.maybeAppendRC(tmp.dir); try std.testing.expectEqualStrings("foo", options.input_source.filename); diff --git a/lib/compiler/resinator/compile.zig b/lib/compiler/resinator/compile.zig index 7dc77e5ee1b3fc738a5bcc670d2381395872975e..3e046a10c14f61ee22b8d4c88310fd15db3cf11a 100644 --- a/lib/compiler/resinator/compile.zig +++ b/lib/compiler/resinator/compile.zig @@ -111,7 +111,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io try search_dirs.append(allocator, .{ .dir = root_dir, .path = try allocator.dupe(u8, root_dir_path) }); } } - // Re-open the passed in cwd since we want to be able to close it (std.fs.cwd() shouldn't be closed) + // Re-open the passed in cwd since we want to be able to close it (Io.Dir.cwd() shouldn't be closed) const cwd_dir = options.cwd.openDir(".", .{}) catch |err| { try options.diagnostics.append(.{ .err = .failed_to_open_cwd, @@ -406,7 +406,7 @@ pub const Compiler = struct { // `/test.bin` relative to include paths and instead only treats it as // an absolute path. if (std.fs.path.isAbsolute(path)) { - const file = try utils.openFileNotDir(std.fs.cwd(), path, .{}); + const file = try utils.openFileNotDir(Io.Dir.cwd(), path, .{}); errdefer file.close(io); if (self.dependencies) |dependencies| { diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index c726a06cf43382c9632930502ef1acff038b7651..416abc2ab742ad065804e2696589cf756b722a41 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -67,7 +67,7 @@ pub fn main() !void { }, else => |e| return e, }; - try options.maybeAppendRC(std.fs.cwd()); + try options.maybeAppendRC(Io.Dir.cwd()); if (!zig_integration) { // print any warnings/notes @@ -141,7 +141,7 @@ pub fn main() !void { if (!zig_integration) std.debug.unlockStderrWriter(); } - var comp = aro.Compilation.init(aro_arena, aro_arena, io, &diagnostics, std.fs.cwd()); + var comp = aro.Compilation.init(aro_arena, aro_arena, io, &diagnostics, Io.Dir.cwd()); defer comp.deinit(); var argv: std.ArrayList([]const u8) = .empty; @@ -196,7 +196,7 @@ pub fn main() !void { }; }, .filename => |input_filename| { - break :full_input std.fs.cwd().readFileAlloc(input_filename, gpa, .unlimited) catch |err| { + break :full_input Io.Dir.cwd().readFileAlloc(input_filename, gpa, .unlimited) catch |err| { try error_handler.emitMessage(gpa, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) }); std.process.exit(1); }; @@ -212,7 +212,7 @@ pub fn main() !void { try output_file.writeAll(full_input); }, .filename => |output_filename| { - try std.fs.cwd().writeFile(.{ .sub_path = output_filename, .data = full_input }); + try Io.Dir.cwd().writeFile(.{ .sub_path = output_filename, .data = full_input }); }, } return; @@ -277,7 +277,7 @@ pub fn main() !void { const output_buffered_stream = res_stream_writer.interface(); compile(gpa, io, final_input, output_buffered_stream, .{ - .cwd = std.fs.cwd(), + .cwd = Io.Dir.cwd(), .diagnostics = &diagnostics, .source_mappings = &mapping_results.mappings, .dependencies = maybe_dependencies, @@ -294,7 +294,7 @@ pub fn main() !void { .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page, }) catch |err| switch (err) { error.ParseError, error.CompileError => { - try error_handler.emitDiagnostics(gpa, std.fs.cwd(), final_input, &diagnostics, mapping_results.mappings); + try error_handler.emitDiagnostics(gpa, Io.Dir.cwd(), final_input, &diagnostics, mapping_results.mappings); // Delete the output file on error res_stream.cleanupAfterError(io); std.process.exit(1); @@ -306,12 +306,12 @@ pub fn main() !void { // print any warnings/notes if (!zig_integration) { - diagnostics.renderToStdErr(std.fs.cwd(), final_input, mapping_results.mappings); + diagnostics.renderToStdErr(Io.Dir.cwd(), final_input, mapping_results.mappings); } // write the depfile if (options.depfile_path) |depfile_path| { - var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| { + var depfile = Io.Dir.cwd().createFile(io, depfile_path, .{}) catch |err| { try error_handler.emitMessage(gpa, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) }); std.process.exit(1); }; @@ -440,7 +440,7 @@ const IoStream = struct { // Delete the output file on error file.close(io); // Failing to delete is not really a big deal, so swallow any errors - std.fs.cwd().deleteFile(self.name) catch {}; + Io.Dir.cwd().deleteFile(self.name) catch {}; }, .stdio, .memory, .closed => return, } @@ -457,8 +457,8 @@ const IoStream = struct { switch (source) { .filename => |filename| return .{ .file = switch (io) { - .input => try openFileNotDir(std.fs.cwd(), filename, .{}), - .output => try std.fs.cwd().createFile(filename, .{}), + .input => try openFileNotDir(Io.Dir.cwd(), filename, .{}), + .output => try Io.Dir.cwd().createFile(io, filename, .{}), }, }, .stdio => |file| return .{ .stdio = file }, diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index e4efac28cdb7385f2a6f76d8f93fc9d10334698e..87c4da9faaf83c8d51cb078ed2c868692776e2df 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -40,7 +40,7 @@ pub fn main() !void { const zig_exe_path = argv.next().?; const global_cache_path = argv.next().?; - var lib_dir = try std.fs.cwd().openDir(zig_lib_directory, .{}); + var lib_dir = try Io.Dir.cwd().openDir(zig_lib_directory, .{}); defer lib_dir.close(io); var listen_port: u16 = 0; diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index 830c70e4246b4b1dc9860fcd7bfade50ab372269..d0a873fd785b8d0a4758c8ca78fee9c3999d1f37 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -47,7 +47,7 @@ pub fn main() u8 { }; defer diagnostics.deinit(); - var comp = aro.Compilation.initDefault(gpa, arena, io, &diagnostics, std.fs.cwd()) catch |err| switch (err) { + var comp = aro.Compilation.initDefault(gpa, arena, io, &diagnostics, Io.Dir.cwd()) catch |err| switch (err) { error.OutOfMemory => { std.debug.print("ran out of memory initializing C compilation\n", .{}); if (fast_exit) process.exit(1); @@ -226,7 +226,7 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration const dep_file_name = try d.getDepFileName(source, out_buf[0..std.fs.max_name_bytes]); const file = if (dep_file_name) |path| - d.comp.cwd.createFile(path, .{}) catch |er| + d.comp.cwd.createFile(io, path, .{}) catch |er| return d.fatal("unable to create dependency file '{s}': {s}", .{ path, aro.Driver.errorDescription(er) }) else Io.File.stdout(); @@ -253,10 +253,10 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration if (d.output_name) |path| blk: { if (std.mem.eql(u8, path, "-")) break :blk; if (std.fs.path.dirname(path)) |dirname| { - std.fs.cwd().makePath(dirname) catch |err| + Io.Dir.cwd().makePath(dirname) catch |err| return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); } - out_file = std.fs.cwd().createFile(path, .{}) catch |err| { + out_file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| { return d.fatal("failed to create output file '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); }; close_out_file = true; diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 7bfdbb6449c36dbebd4bc7f273165a6ce905dfe2..cc2f70fd2f067dee8f32d3c71fa8a25cfef74872 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1702,13 +1702,13 @@ pub fn addCheckFile( pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.MakeError || Io.Dir.StatFileError)!void { const io = b.graph.io; if (b.verbose) log.info("truncate {s}", .{dest_path}); - const cwd = fs.cwd(); - var src_file = cwd.createFile(dest_path, .{}) catch |err| switch (err) { + const cwd = Io.Dir.cwd(); + var src_file = cwd.createFile(io, dest_path, .{}) catch |err| switch (err) { error.FileNotFound => blk: { if (fs.path.dirname(dest_path)) |dirname| { try cwd.makePath(dirname); } - break :blk try cwd.createFile(dest_path, .{}); + break :blk try cwd.createFile(io, dest_path, .{}); }, else => |e| return e, }; @@ -1846,7 +1846,7 @@ pub fn runAllowFail( }; errdefer b.allocator.free(stdout); - const term = try child.wait(); + const term = try child.wait(io); switch (term) { .Exited => |code| { if (code != 0) { @@ -2193,7 +2193,7 @@ fn dependencyInner( const build_root: std.Build.Cache.Directory = .{ .path = build_root_string, - .handle = fs.cwd().openDir(build_root_string, .{}) catch |err| { + .handle = Io.Dir.cwd().openDir(build_root_string, .{}) catch |err| { std.debug.print("unable to open '{s}': {s}\n", .{ build_root_string, @errorName(err), }); diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 42459c033d90d5534632581ac5fc8378b9809e9a..fdcb2ab714ca4a28ceee69957b1cddaad8363f69 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -508,7 +508,7 @@ pub const Manifest = struct { // and `want_shared_lock` is set, a shared lock might be sufficient, so we'll // open with a shared lock instead. while (true) { - if (self.cache.manifest_dir.createFile(&manifest_file_path, .{ + if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{ .read = true, .truncate = false, .lock = .exclusive, @@ -543,7 +543,7 @@ pub const Manifest = struct { return error.CacheCheckFailed; } - if (self.cache.manifest_dir.createFile(&manifest_file_path, .{ + if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{ .read = true, .truncate = false, .lock = .exclusive, @@ -873,7 +873,7 @@ pub const Manifest = struct { if (man.want_refresh_timestamp) { man.want_refresh_timestamp = false; - var file = man.cache.manifest_dir.createFile("timestamp", .{ + var file = man.cache.manifest_dir.createFile(io, "timestamp", .{ .read = true, .truncate = true, }) catch |err| switch (err) { @@ -1324,7 +1324,7 @@ fn hashFile(file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.PReadErro fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp { const test_out_file = "test-filetimestamp.tmp"; - var file = try dir.createFile(test_out_file, .{ + var file = try dir.createFile(io, test_out_file, .{ .read = true, .truncate = true, }); diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 56ef32e8d865044eefe7128d6652d16bb1dcac65..2ec1c0ef31337da0629f0809004de0aa1bee7ba0 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -401,6 +401,9 @@ pub fn evalZigProcess( web_server: ?*Build.WebServer, gpa: Allocator, ) !?Path { + const b = s.owner; + const io = b.graph.io; + // If an error occurs, it's happened in this command: assert(s.result_failed_command == null); s.result_failed_command = try allocPrintCmd(gpa, null, argv); @@ -411,7 +414,7 @@ pub fn evalZigProcess( const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) { error.BrokenPipe => { // Process restart required. - const term = zp.child.wait() catch |e| { + const term = zp.child.wait(io) catch |e| { return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); }; _ = term; @@ -427,7 +430,7 @@ pub fn evalZigProcess( if (s.result_error_msgs.items.len > 0 and result == null) { // Crash detected. - const term = zp.child.wait() catch |e| { + const term = zp.child.wait(io) catch |e| { return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); }; s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; @@ -439,9 +442,7 @@ pub fn evalZigProcess( return result; } assert(argv.len != 0); - const b = s.owner; const arena = b.allocator; - const io = b.graph.io; try handleChildProcUnsupported(s); try handleVerbose(s.owner, null, argv); @@ -478,7 +479,7 @@ pub fn evalZigProcess( zp.child.stdin.?.close(io); zp.child.stdin = null; - const term = zp.child.wait() catch |err| { + const term = zp.child.wait(io) catch |err| { return s.fail("unable to wait for {s}: {t}", .{ argv[0], err }); }; s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; @@ -519,7 +520,7 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.MakePathStatus { const b = s.owner; try handleVerbose(b, null, &.{ "install", "-d", dest_path }); - return std.fs.cwd().makePathStatus(dest_path) catch |err| + return Io.Dir.cwd().makePathStatus(dest_path) catch |err| return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err }); } @@ -895,7 +896,7 @@ pub fn addWatchInput(step: *Step, lazy_file: Build.LazyPath) Allocator.Error!voi try addWatchInputFromPath(step, .{ .root_dir = .{ .path = null, - .handle = std.fs.cwd(), + .handle = Io.Dir.cwd(), }, .sub_path = std.fs.path.dirname(path_string) orelse "", }, std.fs.path.basename(path_string)); @@ -920,7 +921,7 @@ pub fn addDirectoryWatchInput(step: *Step, lazy_directory: Build.LazyPath) Alloc try addDirectoryWatchInputFromPath(step, .{ .root_dir = .{ .path = null, - .handle = std.fs.cwd(), + .handle = Io.Dir.cwd(), }, .sub_path = path_string, }); diff --git a/lib/std/Build/Step/CheckFile.zig b/lib/std/Build/Step/CheckFile.zig index efeedc8b80ae36795e57a5993c8a6b170cb15efd..560b6ad0507752ec9af281ae27369c39afb0aa10 100644 --- a/lib/std/Build/Step/CheckFile.zig +++ b/lib/std/Build/Step/CheckFile.zig @@ -3,7 +3,9 @@ //! TODO: generalize the code in std.testing.expectEqualStrings and make this //! CheckFile step produce those helpful diagnostics when there is not a match. const CheckFile = @This(); + const std = @import("std"); +const Io = std.Io; const Step = std.Build.Step; const fs = std.fs; const mem = std.mem; @@ -53,7 +55,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { try step.singleUnchangingWatchInput(check_file.source); const src_path = check_file.source.getPath2(b, step); - const contents = fs.cwd().readFileAlloc(src_path, b.allocator, .limited(check_file.max_bytes)) catch |err| { + const contents = Io.Dir.cwd().readFileAlloc(src_path, b.allocator, .limited(check_file.max_bytes)) catch |err| { return step.fail("unable to read '{s}': {s}", .{ src_path, @errorName(err), }); diff --git a/lib/std/Build/Step/ConfigHeader.zig b/lib/std/Build/Step/ConfigHeader.zig index df2419764d048c60df9e10e0f48b4b55db96aeea..ea7d9d99ff4e404475a5852b15a0b2d6cb207e9b 100644 --- a/lib/std/Build/Step/ConfigHeader.zig +++ b/lib/std/Build/Step/ConfigHeader.zig @@ -1,5 +1,7 @@ -const std = @import("std"); const ConfigHeader = @This(); + +const std = @import("std"); +const Io = std.Io; const Step = std.Build.Step; const Allocator = std.mem.Allocator; const Writer = std.Io.Writer; @@ -205,7 +207,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .autoconf_undef, .autoconf_at => |file_source| { try bw.writeAll(c_generated_line); const src_path = file_source.getPath2(b, step); - const contents = std.fs.cwd().readFileAlloc(src_path, arena, .limited(config_header.max_bytes)) catch |err| { + const contents = Io.Dir.cwd().readFileAlloc(src_path, arena, .limited(config_header.max_bytes)) catch |err| { return step.fail("unable to read autoconf input file '{s}': {s}", .{ src_path, @errorName(err), }); @@ -219,7 +221,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .cmake => |file_source| { try bw.writeAll(c_generated_line); const src_path = file_source.getPath2(b, step); - const contents = std.fs.cwd().readFileAlloc(src_path, arena, .limited(config_header.max_bytes)) catch |err| { + const contents = Io.Dir.cwd().readFileAlloc(src_path, arena, .limited(config_header.max_bytes)) catch |err| { return step.fail("unable to read cmake input file '{s}': {s}", .{ src_path, @errorName(err), }); diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 9f5665e93a30399c3ea19f7102b425279a1e5cfb..1416e0e916236ac7353c632b1af66c8c7a56aa33 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -1,12 +1,13 @@ -const std = @import("std"); +const Options = @This(); const builtin = @import("builtin"); + +const std = @import("std"); +const Io = std.Io; const fs = std.fs; const Step = std.Build.Step; const GeneratedFile = std.Build.GeneratedFile; const LazyPath = std.Build.LazyPath; -const Options = @This(); - pub const base_id: Step.Id = .options; step: Step, @@ -542,11 +543,11 @@ test Options { .cache = .{ .io = io, .gpa = arena.allocator(), - .manifest_dir = std.fs.cwd(), + .manifest_dir = Io.Dir.cwd(), }, .zig_exe = "test", .env_map = std.process.EnvMap.init(arena.allocator()), - .global_cache_root = .{ .path = "test", .handle = std.fs.cwd() }, + .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() }, .host = .{ .query = .{}, .result = try std.zig.system.resolveTargetQuery(io, .{}), @@ -557,8 +558,8 @@ test Options { var builder = try std.Build.create( &graph, - .{ .path = "test", .handle = std.fs.cwd() }, - .{ .path = "test", .handle = std.fs.cwd() }, + .{ .path = "test", .handle = Io.Dir.cwd() }, + .{ .path = "test", .handle = Io.Dir.cwd() }, &.{}, ); diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 7c54c8048e8da6dbb79050977d15e6a49342beda..af6bc20438d1681636489c2356996d26d44db8d8 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1023,7 +1023,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, options, null); - const dep_file_dir = std.fs.cwd(); + const dep_file_dir = Io.Dir.cwd(); const dep_file_basename = dep_output_file.generated_file.getPath2(b, step); if (has_side_effects) try man.addDepFile(dep_file_dir, dep_file_basename) diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index ff06ad3ff351df01759846cbffa96b4417d04355..f7ac47961e28999ed44fdcfb7034e0f8aa5d2ff1 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -122,7 +122,7 @@ const Os = switch (builtin.os.tag) { }) catch return error.NameTooLong; const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer); stack_ptr.handle_bytes = file_handle_buffer.len - @sizeOf(std.os.linux.file_handle); - try posix.name_to_handle_at(path.root_dir.handle.fd, adjusted_path, stack_ptr, mount_id, std.os.linux.AT.HANDLE_FID); + try posix.name_to_handle_at(path.root_dir.handle.handle, adjusted_path, stack_ptr, mount_id, std.os.linux.AT.HANDLE_FID); const stack_lfh: FileHandle = .{ .handle = stack_ptr }; return stack_lfh.clone(gpa); } @@ -222,7 +222,7 @@ const Os = switch (builtin.os.tag) { posix.fanotify_mark(fan_fd, .{ .ADD = true, .ONLYDIR = true, - }, fan_mask, path.root_dir.handle.fd, path.subPathOrDot()) catch |err| { + }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err| { fatal("unable to watch {f}: {s}", .{ path, @errorName(err) }); }; } @@ -275,7 +275,7 @@ const Os = switch (builtin.os.tag) { posix.fanotify_mark(fan_fd, .{ .REMOVE = true, .ONLYDIR = true, - }, fan_mask, path.root_dir.handle.fd, path.subPathOrDot()) catch |err| switch (err) { + }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err| switch (err) { error.FileNotFound => {}, // Expected, harmless. else => |e| std.log.warn("unable to unwatch '{f}': {s}", .{ path, @errorName(e) }), }; @@ -353,7 +353,7 @@ const Os = switch (builtin.os.tag) { // The following code is a drawn out NtCreateFile call. (mostly adapted from Io.Dir.makeOpenDirAccessMaskW) // It's necessary in order to get the specific flags that are required when calling ReadDirectoryChangesW. var dir_handle: windows.HANDLE = undefined; - const root_fd = path.root_dir.handle.fd; + const root_fd = path.root_dir.handle.handle; const sub_path = path.subPathOrDot(); const sub_path_w = try windows.sliceToPrefixedFileW(root_fd, sub_path); const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; @@ -681,9 +681,9 @@ const Os = switch (builtin.os.tag) { if (!gop.found_existing) { const skip_open_dir = path.sub_path.len == 0; const dir_fd = if (skip_open_dir) - path.root_dir.handle.fd + path.root_dir.handle.handle else - posix.openat(path.root_dir.handle.fd, path.sub_path, dir_open_flags, 0) catch |err| { + posix.openat(path.root_dir.handle.handle, path.sub_path, dir_open_flags, 0) catch |err| { fatal("failed to open directory {f}: {s}", .{ path, @errorName(err) }); }; // Empirically the dir has to stay open or else no events are triggered. @@ -750,7 +750,7 @@ const Os = switch (builtin.os.tag) { // to access that data via the dir_fd field. const path = w.dir_table.keys()[i]; const dir_fd = if (path.sub_path.len == 0) - path.root_dir.handle.fd + path.root_dir.handle.handle else handles.items(.dir_fd)[i]; assert(dir_fd != -1); @@ -761,7 +761,7 @@ const Os = switch (builtin.os.tag) { const last_dir_fd = fd: { const last_path = w.dir_table.keys()[handles.len - 1]; const last_dir_fd = if (last_path.sub_path.len == 0) - last_path.root_dir.handle.fd + last_path.root_dir.handle.handle else handles.items(.dir_fd)[handles.len - 1]; assert(last_dir_fd != -1); diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 8e71f648e294d6807bf86c91ecf5bb1845fc3e29..a9b4775772d796ef9e798fd57e47ae53a5479d65 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -527,6 +527,14 @@ pub fn writerStreaming(file: File, io: Io, buffer: []u8) Writer { return .initStreaming(file, io, buffer); } +/// Equivalent to creating a streaming writer, writing `bytes`, and then flushing. +pub fn writeStreamingAll(file: File, io: Io, bytes: []const u8) Writer.Error!void { + var index: usize = 0; + while (index < bytes.len) { + index += try io.vtable.fileWriteStreaming(io.userdata, file, &.{}, &.{bytes[index..]}, 1); + } +} + pub const LockError = error{ SystemResources, FileLocksUnsupported, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index fb76002201428ff080c6e553d24a28e73d5a7bd3..124f886515a1dccca1310c4ca38d14babfd632cc 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2361,7 +2361,7 @@ fn dirCreateFilePosix( .NFILE => return error.SystemFdQuotaExceeded, .NODEV => return error.NoDevice, .NOENT => return error.FileNotFound, - .SRCH => return error.ProcessNotFound, + .SRCH => return error.FileNotFound, // Linux when accessing procfs. .NOMEM => return error.SystemResources, .NOSPC => return error.NoSpaceLeft, .NOTDIR => return error.NotDir, @@ -2670,7 +2670,7 @@ fn dirOpenFilePosix( .NFILE => return error.SystemFdQuotaExceeded, .NODEV => return error.NoDevice, .NOENT => return error.FileNotFound, - .SRCH => return error.ProcessNotFound, + .SRCH => return error.FileNotFound, // Linux when opening procfs files. .NOMEM => return error.SystemResources, .NOSPC => return error.NoSpaceLeft, .NOTDIR => return error.NotDir, @@ -3287,7 +3287,7 @@ fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_b .NFILE => return error.SystemFdQuotaExceeded, .NODEV => return error.NoDevice, .NOENT => return error.FileNotFound, - .SRCH => return error.ProcessNotFound, + .SRCH => return error.FileNotFound, // Linux when accessing procfs. .NOMEM => return error.SystemResources, .NOSPC => return error.NoSpaceLeft, .NOTDIR => return error.NotDir, @@ -5548,7 +5548,6 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: [][]u8) File. switch (e) { .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), - .SRCH => return error.ProcessNotFound, .AGAIN => return error.WouldBlock, .BADF => |err| { if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. @@ -5672,7 +5671,6 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: [][]u8, offs switch (e) { .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), - .SRCH => return error.ProcessNotFound, .AGAIN => return error.WouldBlock, .BADF => |err| { if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. @@ -6312,7 +6310,6 @@ fn fileWriteStreaming( switch (e) { .INVAL => return error.InvalidArgument, .FAULT => |err| return errnoBug(err), - .SRCH => return error.ProcessNotFound, .AGAIN => return error.WouldBlock, .BADF => return error.NotOpenForWriting, // Can be a race condition. .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 3f25bc6a26ce01151a2309ba49bbc880214dfcab..63ae6d93a0eceee110b5887113381ea4c59106c9 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -2835,7 +2835,7 @@ test "discarding sendFile" { var tmp_dir = testing.tmpDir(.{}); defer tmp_dir.cleanup(); - const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); + const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); defer file.close(io); var r_buffer: [256]u8 = undefined; var file_writer: File.Writer = .init(file, &r_buffer); @@ -2857,7 +2857,7 @@ test "allocating sendFile" { var tmp_dir = testing.tmpDir(.{}); defer tmp_dir.cleanup(); - const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); + const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); defer file.close(io); var r_buffer: [2]u8 = undefined; var file_writer: File.Writer = .init(file, &r_buffer); @@ -2881,7 +2881,7 @@ test sendFileReading { var tmp_dir = testing.tmpDir(.{}); defer tmp_dir.cleanup(); - const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); + const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); defer file.close(io); var r_buffer: [2]u8 = undefined; var file_writer: File.Writer = .init(file, &r_buffer); diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig index 5818f6c3f77f25ea6f84e6b65d1926b7ad46b839..c9ed0d32847852e259c021aa9af0a718ee40c8c9 100644 --- a/lib/std/Io/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -278,7 +278,7 @@ test "listen on a unix socket, send bytes, receive bytes" { defer testing.allocator.free(socket_path); const socket_addr = try net.UnixAddress.init(socket_path); - defer std.fs.cwd().deleteFile(socket_path) catch {}; + defer Io.Dir.cwd().deleteFile(socket_path) catch {}; var server = try socket_addr.listen(io, .{}); defer server.socket.close(io); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 9f21fe50e7aae388643bf1b9dd38741b705a6133..e731dc18d78354a4c45052ff48b64f7639c3755e 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -27,7 +27,7 @@ test "write a file, read it, then delete it" { random.bytes(data[0..]); const tmp_file_name = "temp_test_file.txt"; { - var file = try tmp.dir.createFile(tmp_file_name, .{}); + var file = try tmp.dir.createFile(io, tmp_file_name, .{}); defer file.close(io); var file_writer = file.writer(&.{}); @@ -40,7 +40,7 @@ test "write a file, read it, then delete it" { { // Make sure the exclusive flag is honored. - try expectError(File.OpenError.PathAlreadyExists, tmp.dir.createFile(tmp_file_name, .{ .exclusive = true })); + try expectError(File.OpenError.PathAlreadyExists, tmp.dir.createFile(io, tmp_file_name, .{ .exclusive = true })); } { @@ -70,7 +70,7 @@ test "File seek ops" { const io = testing.io; const tmp_file_name = "temp_test_file.txt"; - var file = try tmp.dir.createFile(tmp_file_name, .{}); + var file = try tmp.dir.createFile(io, tmp_file_name, .{}); defer file.close(io); try file.writeAll(&([_]u8{0x55} ** 8192)); @@ -96,7 +96,7 @@ test "setEndPos" { defer tmp.cleanup(); const tmp_file_name = "temp_test_file.txt"; - var file = try tmp.dir.createFile(tmp_file_name, .{}); + var file = try tmp.dir.createFile(io, tmp_file_name, .{}); defer file.close(io); // Verify that the file size changes and the file offset is not moved @@ -121,7 +121,7 @@ test "updateTimes" { defer tmp.cleanup(); const tmp_file_name = "just_a_temporary_file.txt"; - var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true }); + var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true }); defer file.close(io); const stat_old = try file.stat(); diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 102bb59415d56fa78f3f7300e671bb742d050b8b..8453bc4c8122da017e5a54c81869fe9cd0fca414 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -208,7 +208,7 @@ pub fn setName(self: Thread, io: Io, name: []const u8) SetNameError!void { var buf: [32]u8 = undefined; const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); - const file = try std.fs.cwd().openFile(io, path, .{ .mode = .write_only }); + const file = try Io.Dir.cwd().openFile(io, path, .{ .mode = .write_only }); defer file.close(io); try file.writeAll(name); @@ -325,7 +325,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co var threaded: std.Io.Threaded = .init_single_threaded; const io = threaded.ioBasic(); - const file = try std.fs.cwd().openFile(io, path, .{}); + const file = try Io.Dir.cwd().openFile(io, path, .{}); defer file.close(io); var file_reader = file.readerStreaming(io, &.{}); diff --git a/lib/std/crypto/Certificate/Bundle/macos.zig b/lib/std/crypto/Certificate/Bundle/macos.zig index 473505ac51f6d283cf32667e96a9565288f4962e..444d8da67519309684ebe873c101849616f52e96 100644 --- a/lib/std/crypto/Certificate/Bundle/macos.zig +++ b/lib/std/crypto/Certificate/Bundle/macos.zig @@ -19,7 +19,7 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanM _ = io; // TODO migrate file system to use std.Io for (keychain_paths) |keychain_path| { - const bytes = std.fs.cwd().readFileAlloc(keychain_path, gpa, .limited(std.math.maxInt(u32))) catch |err| switch (err) { + const bytes = Io.Dir.cwd().readFileAlloc(keychain_path, gpa, .limited(std.math.maxInt(u32))) catch |err| switch (err) { error.StreamTooLong => return error.FileTooBig, else => |e| return e, }; diff --git a/lib/std/crypto/codecs/asn1/test.zig b/lib/std/crypto/codecs/asn1/test.zig index ff854fcbde69bac83f7af998bff6c9fd4eefc73c..3dbedb9f80a24b59beb83a1dd034e8849b15159e 100644 --- a/lib/std/crypto/codecs/asn1/test.zig +++ b/lib/std/crypto/codecs/asn1/test.zig @@ -73,8 +73,8 @@ test AllTypes { try std.testing.expectEqualSlices(u8, encoded, buf); // Use this to update test file. - // const dir = try std.fs.cwd().openDir("lib/std/crypto/asn1", .{}); - // var file = try dir.createFile(path, .{}); + // const dir = try Io.Dir.cwd().openDir("lib/std/crypto/asn1", .{}); + // var file = try dir.createFile(io, path, .{}); // defer file.close(io); // try file.writeAll(buf); } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 97741ecb40598edc97beafae430951b0adbcc51d..5df0eef2d560ed86b3020df96841f32bf9f7332e 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -60,7 +60,7 @@ pub const cpu_context = @import("debug/cpu_context.zig"); /// }; /// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's /// /// return address, or 0 if the end of the stack has been reached. -/// pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) SelfInfoError!usize; +/// pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) SelfInfoError!usize; /// ``` pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo")) root.debug.SelfInfo @@ -558,9 +558,9 @@ pub fn defaultPanic( stderr.print("{s}\n", .{msg}) catch break :trace; if (@errorReturnTrace()) |t| if (t.index > 0) { - stderr.writeAll("error return context:\n") catch break :trace; + stderr.writeStreamingAll("error return context:\n") catch break :trace; writeStackTrace(t, stderr, tty_config) catch break :trace; - stderr.writeAll("\nstack trace:\n") catch break :trace; + stderr.writeStreamingAll("\nstack trace:\n") catch break :trace; }; writeCurrentStackTrace(.{ .first_address = first_trace_addr orelse @returnAddress(), @@ -575,7 +575,7 @@ pub fn defaultPanic( // A panic happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). - File.stderr().writeAll("aborting due to recursive panic\n") catch {}; + File.stderr().writeStreamingAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. } @@ -960,7 +960,7 @@ const StackIterator = union(enum) { }, }; - fn next(it: *StackIterator) Result { + fn next(it: *StackIterator, io: Io) Result { switch (it.*) { .ctx_first => |context_ptr| { // After the first frame, start actually unwinding. @@ -976,7 +976,7 @@ const StackIterator = union(enum) { .di => |*unwind_context| { const di = getSelfDebugInfo() catch unreachable; const di_gpa = getDebugInfoAllocator(); - const ret_addr = di.unwindFrame(di_gpa, unwind_context) catch |err| { + const ret_addr = di.unwindFrame(di_gpa, io, unwind_context) catch |err| { const pc = unwind_context.pc; const fp = unwind_context.getFp(); it.* = .{ .fp = fp }; @@ -1297,7 +1297,7 @@ test printLineFromFile { aw.clearRetainingCapacity(); } { - const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{}); + const file = try test_dir.dir.createFile(io, "line_overlaps_page_boundary.zig", .{}); defer file.close(io); const path = try fs.path.join(gpa, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); defer gpa.free(path); @@ -1316,7 +1316,7 @@ test printLineFromFile { aw.clearRetainingCapacity(); } { - const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{}); + const file = try test_dir.dir.createFile(io, "file_ends_on_page_boundary.zig", .{}); defer file.close(io); const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); defer gpa.free(path); @@ -1330,7 +1330,7 @@ test printLineFromFile { aw.clearRetainingCapacity(); } { - const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{}); + const file = try test_dir.dir.createFile(io, "very_long_first_line_spanning_multiple_pages.zig", .{}); defer file.close(io); const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); defer gpa.free(path); @@ -1356,7 +1356,7 @@ test printLineFromFile { aw.clearRetainingCapacity(); } { - const file = try test_dir.dir.createFile("file_of_newlines.zig", .{}); + const file = try test_dir.dir.createFile(io, "file_of_newlines.zig", .{}); defer file.close(io); const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" }); defer gpa.free(path); diff --git a/lib/std/debug/ElfFile.zig b/lib/std/debug/ElfFile.zig index a0f1188adeb3cc7645df469e025ad1b94eda08b1..203ee8effb1b74c19cc4f247ef70878e4d29c5d6 100644 --- a/lib/std/debug/ElfFile.zig +++ b/lib/std/debug/ElfFile.zig @@ -375,7 +375,7 @@ fn loadSeparateDebugFile( args: anytype, ) Allocator.Error!?[]align(std.heap.page_size_min) const u8 { const path = try std.fmt.allocPrint(arena, fmt, args); - const elf_file = std.fs.cwd().openFile(io, path, .{}) catch return null; + const elf_file = Io.Dir.cwd().openFile(io, path, .{}) catch return null; defer elf_file.close(io); const result = loadInner(arena, elf_file, opt_crc) catch |err| switch (err) { diff --git a/lib/std/debug/MachOFile.zig b/lib/std/debug/MachOFile.zig index ae904c0aec6d52ccb556e22f826d89d6ee0dde07..18126a1c297171fe3f95e97fdf6d1d7dbc675c06 100644 --- a/lib/std/debug/MachOFile.zig +++ b/lib/std/debug/MachOFile.zig @@ -512,7 +512,7 @@ fn loadOFile(gpa: Allocator, io: Io, o_file_name: []const u8) !OFile { /// Uses `mmap` to map the file at `path` into memory. fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 { - const file = std.fs.cwd().openFile(io, path, .{}) catch |err| switch (err) { + const file = Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) { error.FileNotFound => return error.MissingDebugInfo, else => return error.ReadFailed, }; diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index 213389bf04c2c592b41e9fa9b3020bf035de294a..6ed18bcb80ad08fd5201d1affe4fb1bab6826f4b 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -29,13 +29,12 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void { } pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { - _ = io; const module = try si.findModule(gpa, address, .exclusive); defer si.rwlock.unlock(); const vaddr = address - module.load_offset; - const loaded_elf = try module.getLoadedElf(gpa); + const loaded_elf = try module.getLoadedElf(gpa, io); if (loaded_elf.file.dwarf) |*dwarf| { if (!loaded_elf.scanned_dwarf) { dwarf.open(gpa, native_endian) catch |err| switch (err) { @@ -180,7 +179,7 @@ comptime { } } pub const UnwindContext = Dwarf.SelfUnwinder; -pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { +pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize { comptime assert(can_unwind); { @@ -201,7 +200,7 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error @memset(si.unwind_cache.?, .empty); } - const unwind_sections = try module.getUnwindSections(gpa); + const unwind_sections = try module.getUnwindSections(gpa, io); for (unwind_sections) |*unwind| { if (context.computeRules(gpa, unwind, module.load_offset, null)) |entry| { entry.populate(si.unwind_cache.?); @@ -261,12 +260,12 @@ const Module = struct { }; /// Assumes we already hold an exclusive lock. - fn getUnwindSections(mod: *Module, gpa: Allocator) Error![]Dwarf.Unwind { - if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa); + fn getUnwindSections(mod: *Module, gpa: Allocator, io: Io) Error![]Dwarf.Unwind { + if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa, io); const us = &(mod.unwind.? catch |err| return err); return us.buf[0..us.len]; } - fn loadUnwindSections(mod: *Module, gpa: Allocator) Error!UnwindSections { + fn loadUnwindSections(mod: *Module, gpa: Allocator, io: Io) Error!UnwindSections { var us: UnwindSections = .{ .buf = undefined, .len = 0, @@ -284,7 +283,7 @@ const Module = struct { } else { // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame` // section, but we'll have to load the binary to get at it. - const loaded = try mod.getLoadedElf(gpa); + const loaded = try mod.getLoadedElf(gpa, io); // If both are present, we can't just pick one -- the info could be split between them. // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one. if (loaded.file.debug_frame) |*debug_frame| { @@ -325,7 +324,7 @@ const Module = struct { } fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf { const load_result = if (mod.name.len > 0) res: { - var file = std.fs.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo; + var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo; defer file.close(io); break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name)); } else res: { @@ -334,7 +333,7 @@ const Module = struct { else => return error.ReadFailed, }; defer gpa.free(path); - var file = std.fs.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo; + var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo; defer file.close(io); break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path)); }; diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig index 15da616f3b6274905428cfb2b2ddd7ea81514b0b..db8e5334e65d1ba978f8a9a40f0ecb37ed2d7222 100644 --- a/lib/std/debug/SelfInfo/MachO.zig +++ b/lib/std/debug/SelfInfo/MachO.zig @@ -616,7 +616,7 @@ test { /// Uses `mmap` to map the file at `path` into memory. fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 { - const file = std.fs.cwd().openFile(io, path, .{}) catch |err| switch (err) { + const file = Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) { error.FileNotFound => return error.MissingDebugInfo, else => return error.ReadFailed, }; diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index f0ac30cca22229ec8f26a07c3910876deb5e5676..9874efd4974c8f127e3ef520fca864a9333faf17 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -432,7 +432,7 @@ const Module = struct { break :pdb null; }; const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: { - break :res std.fs.cwd().openFile(io, path, .{}); + break :res Io.Dir.cwd().openFile(io, path, .{}); } else res: { const self_dir = std.process.executableDirPathAlloc(io, gpa) catch |err| switch (err) { error.OutOfMemory, error.Unexpected => |e| return e, @@ -441,7 +441,7 @@ const Module = struct { defer gpa.free(self_dir); const abs_path = try fs.path.join(gpa, &.{ self_dir, path }); defer gpa.free(abs_path); - break :res std.fs.cwd().openFile(io, abs_path, .{}); + break :res Io.Dir.cwd().openFile(io, abs_path, .{}); }; const pdb_file = pdb_file_open_result catch |err| switch (err) { error.FileNotFound, error.IsDir => break :pdb null, diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 7db177ad703ed2f47c2aabbb2db6270c92ba933c..a1801d00d05122d53962263bbe1a21664320c453 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -160,7 +160,7 @@ pub const ElfDynLib = struct { fn openPath(path: []const u8, io: Io) !Io.Dir { if (path.len == 0) return error.NotDir; var parts = std.mem.tokenizeScalar(u8, path, '/'); - var parent = if (path[0] == '/') try std.fs.cwd().openDir("/", .{}) else std.fs.cwd(); + var parent = if (path[0] == '/') try Io.Dir.cwd().openDir("/", .{}) else Io.Dir.cwd(); while (parts.next()) |part| { const child = try parent.openDir(part, .{}); parent.close(io); @@ -174,7 +174,7 @@ pub const ElfDynLib = struct { while (paths.next()) |p| { var dir = openPath(p) catch continue; defer dir.close(io); - const fd = posix.openat(dir.fd, file_name, .{ + const fd = posix.openat(dir.handle, file_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true, }, 0) catch continue; @@ -184,9 +184,9 @@ pub const ElfDynLib = struct { } fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?posix.fd_t { - var dir = std.fs.cwd().openDir(dir_path, .{}) catch return null; + var dir = Io.Dir.cwd().openDir(dir_path, .{}) catch return null; defer dir.close(io); - return posix.openat(dir.fd, file_name, .{ + return posix.openat(dir.handle, file_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true, }, 0) catch null; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index f4bdecf89de155c28c4d94acaa8be778a22d1875..aab86d40a62c1da7e85de57678032edef84654de 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -46,7 +46,7 @@ const PathType = enum { // The final path may not actually exist which would cause realpath to fail. // So instead, we get the path of the dir and join it with the relative path. var fd_path_buf: [fs.max_path_bytes]u8 = undefined; - const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf); + const dir_path = try std.os.getFdPath(dir.handle, &fd_path_buf); return fs.path.joinZ(allocator, &.{ dir_path, relative_path }); } }.transform, @@ -55,7 +55,7 @@ const PathType = enum { // Any drive absolute path (C:\foo) can be converted into a UNC path by // using '127.0.0.1' as the server name and '$' as the share name. var fd_path_buf: [fs.max_path_bytes]u8 = undefined; - const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf); + const dir_path = try std.os.getFdPath(dir.handle, &fd_path_buf); const windows_path_type = windows.getWin32PathType(u8, dir_path); switch (windows_path_type) { .unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }), @@ -256,7 +256,7 @@ fn testReadLinkW(allocator: mem.Allocator, dir: Dir, target_path: []const u8, sy const target_path_w = try std.unicode.wtf8ToWtf16LeAlloc(allocator, target_path); defer allocator.free(target_path_w); // Calling the W functions directly requires the path to be NT-prefixed - const symlink_path_w = try std.os.windows.sliceToPrefixedFileW(dir.fd, symlink_path); + const symlink_path_w = try std.os.windows.sliceToPrefixedFileW(dir.handle, symlink_path); const wtf16_buffer = try allocator.alloc(u16, target_path_w.len); defer allocator.free(wtf16_buffer); const actual = try dir.readLinkW(symlink_path_w.span(), wtf16_buffer); @@ -288,9 +288,11 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { var symlink: Dir = switch (builtin.target.os.tag) { .windows => windows_symlink: { - const sub_path_w = try windows.cStrToPrefixedFileW(ctx.dir.fd, "symlink"); + const sub_path_w = try windows.cStrToPrefixedFileW(ctx.dir.handle, "symlink"); - var handle: windows.HANDLE = undefined; + var result: Dir = .{ + .handle = undefined, + }; const path_len_bytes = @as(u16, @intCast(sub_path_w.span().len * 2)); var nt_name = windows.UNICODE_STRING{ @@ -300,26 +302,16 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { }; var attr: windows.OBJECT_ATTRIBUTES = .{ .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), - .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else ctx.dir.fd, - .Attributes = .{}, + .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else ctx.dir.handle, + .Attributes = 0, .ObjectName = &nt_name, .SecurityDescriptor = null, .SecurityQualityOfService = null, }; var io_status_block: windows.IO_STATUS_BLOCK = undefined; const rc = windows.ntdll.NtCreateFile( - &handle, - .{ - .SPECIFIC = .{ .FILE_DIRECTORY = .{ - .READ_EA = true, - .TRAVERSE = true, - .READ_ATTRIBUTES = true, - } }, - .STANDARD = .{ - .RIGHTS = .READ, - .SYNCHRONIZE = true, - }, - }, + &result.handle, + windows.STANDARD_RIGHTS_READ | windows.FILE_READ_ATTRIBUTES | windows.FILE_READ_EA | windows.SYNCHRONIZE | windows.FILE_TRAVERSE, &attr, &io_status_block, null, @@ -337,7 +329,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { ); switch (rc) { - .SUCCESS => break :windows_symlink .{ .fd = handle }, + .SUCCESS => break :windows_symlink .{ .fd = result.handle }, else => return windows.unexpectedStatus(rc), } }, @@ -351,8 +343,8 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { .ACCMODE = .RDONLY, .CLOEXEC = true, }; - const fd = try posix.openatZ(ctx.dir.fd, &sub_path_c, flags, 0); - break :linux_symlink Dir{ .fd = fd }; + const fd = try posix.openatZ(ctx.dir.handle, &sub_path_c, flags, 0); + break :linux_symlink .{ .handle = fd }; }, else => unreachable, }; @@ -456,7 +448,7 @@ test "openDirAbsolute" { test "openDir cwd parent '..'" { const io = testing.io; - var dir = fs.cwd().openDir("..", .{}) catch |err| { + var dir = Io.Dir.cwd().openDir("..", .{}) catch |err| { if (native_os == .wasi and err == error.PermissionDenied) { return; // This is okay. WASI disallows escaping from the fs sandbox } @@ -534,7 +526,7 @@ test "Dir.Iterator" { defer tmp_dir.cleanup(); // First, create a couple of entries to iterate over. - const file = try tmp_dir.dir.createFile("some_file", .{}); + const file = try tmp_dir.dir.createFile(io, "some_file", .{}); file.close(io); try tmp_dir.dir.makeDir("some_dir"); @@ -570,7 +562,7 @@ test "Dir.Iterator many entries" { var buf: [4]u8 = undefined; // Enough to store "1024". while (i < num) : (i += 1) { const name = try std.fmt.bufPrint(&buf, "{}", .{i}); - const file = try tmp_dir.dir.createFile(name, .{}); + const file = try tmp_dir.dir.createFile(io, name, .{}); file.close(io); } @@ -603,7 +595,7 @@ test "Dir.Iterator twice" { defer tmp_dir.cleanup(); // First, create a couple of entries to iterate over. - const file = try tmp_dir.dir.createFile("some_file", .{}); + const file = try tmp_dir.dir.createFile(io, "some_file", .{}); file.close(io); try tmp_dir.dir.makeDir("some_dir"); @@ -638,7 +630,7 @@ test "Dir.Iterator reset" { defer tmp_dir.cleanup(); // First, create a couple of entries to iterate over. - const file = try tmp_dir.dir.createFile("some_file", .{}); + const file = try tmp_dir.dir.createFile(io, "some_file", .{}); file.close(io); try tmp_dir.dir.makeDir("some_dir"); @@ -769,7 +761,7 @@ test "readFileAlloc" { var tmp_dir = tmpDir(.{}); defer tmp_dir.cleanup(); - var file = try tmp_dir.dir.createFile("test_file", .{ .read = true }); + var file = try tmp_dir.dir.createFile(io, "test_file", .{ .read = true }); defer file.close(io); const buf1 = try tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(1024)); @@ -843,7 +835,7 @@ test "directory operations on files" { const test_file_name = try ctx.transformPath("test_file"); - var file = try ctx.dir.createFile(test_file_name, .{ .read = true }); + var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true }); file.close(io); try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name)); @@ -876,7 +868,7 @@ test "file operations on directories" { try ctx.dir.makeDir(test_dir_name); - try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{})); + try testing.expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{})); try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name)); switch (native_os) { .dragonfly, .netbsd => { @@ -969,7 +961,7 @@ test "Dir.rename files" { // Renaming files const test_file_name = try ctx.transformPath("test_file"); const renamed_test_file_name = try ctx.transformPath("test_file_renamed"); - var file = try ctx.dir.createFile(test_file_name, .{ .read = true }); + var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true }); file.close(io); try ctx.dir.rename(test_file_name, renamed_test_file_name); @@ -983,7 +975,7 @@ test "Dir.rename files" { // Rename to existing file succeeds const existing_file_path = try ctx.transformPath("existing_file"); - var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true }); + var existing_file = try ctx.dir.createFile(io, existing_file_path, .{ .read = true }); existing_file.close(io); try ctx.dir.rename(renamed_test_file_name, existing_file_path); @@ -1017,7 +1009,7 @@ test "Dir.rename directories" { var dir = try ctx.dir.openDir(test_dir_renamed_path, .{}); // Put a file in the directory - var file = try dir.createFile("test_file", .{ .read = true }); + var file = try dir.createFile(io, "test_file", .{ .read = true }); file.close(io); dir.close(io); @@ -1070,7 +1062,7 @@ test "Dir.rename directory onto non-empty dir" { try ctx.dir.makeDir(test_dir_path); var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{}); - var file = try target_dir.createFile("test_file", .{ .read = true }); + var file = try target_dir.createFile(io, "test_file", .{ .read = true }); file.close(io); target_dir.close(io); @@ -1094,7 +1086,7 @@ test "Dir.rename file <-> dir" { const test_file_path = try ctx.transformPath("test_file"); const test_dir_path = try ctx.transformPath("test_dir"); - var file = try ctx.dir.createFile(test_file_path, .{ .read = true }); + var file = try ctx.dir.createFile(io, test_file_path, .{ .read = true }); file.close(io); try ctx.dir.makeDir(test_dir_path); try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path)); @@ -1115,7 +1107,7 @@ test "rename" { // Renaming files const test_file_name = "test_file"; const renamed_test_file_name = "test_file_renamed"; - var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true }); + var file = try tmp_dir1.dir.createFile(io, test_file_name, .{ .read = true }); file.close(io); try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name); @@ -1149,7 +1141,7 @@ test "renameAbsolute" { // Renaming files const test_file_name = "test_file"; const renamed_test_file_name = "test_file_renamed"; - var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); + var file = try tmp_dir.dir.createFile(io, test_file_name, .{ .read = true }); file.close(io); try fs.renameAbsolute( try fs.path.join(allocator, &.{ base_path, test_file_name }), @@ -1454,7 +1446,7 @@ test "writev, readv" { var write_vecs: [2][]const u8 = .{ line1, line2 }; var read_vecs: [2][]u8 = .{ &buf2, &buf1 }; - var src_file = try tmp.dir.createFile("test.txt", .{ .read = true }); + var src_file = try tmp.dir.createFile(io, "test.txt", .{ .read = true }); defer src_file.close(io); var writer = src_file.writerStreaming(&.{}); @@ -1484,7 +1476,7 @@ test "pwritev, preadv" { var buf2: [line2.len]u8 = undefined; var read_vecs: [2][]u8 = .{ &buf2, &buf1 }; - var src_file = try tmp.dir.createFile("test.txt", .{ .read = true }); + var src_file = try tmp.dir.createFile(io, "test.txt", .{ .read = true }); defer src_file.close(io); var writer = src_file.writer(&.{}); @@ -1584,14 +1576,14 @@ test "sendfile" { const line2 = "second line\n"; var vecs = [_][]const u8{ line1, line2 }; - var src_file = try dir.createFile("sendfile1.txt", .{ .read = true }); + var src_file = try dir.createFile(io, "sendfile1.txt", .{ .read = true }); defer src_file.close(io); { var fw = src_file.writer(&.{}); try fw.interface.writeVecAll(&vecs); } - var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true }); + var dest_file = try dir.createFile(io, "sendfile2.txt", .{ .read = true }); defer dest_file.close(io); const header1 = "header1\n"; @@ -1627,12 +1619,12 @@ test "sendfile with buffered data" { var dir = try tmp.dir.openDir("os_test_tmp", .{}); defer dir.close(io); - var src_file = try dir.createFile("sendfile1.txt", .{ .read = true }); + var src_file = try dir.createFile(io, "sendfile1.txt", .{ .read = true }); defer src_file.close(io); try src_file.writeAll("AAAABBBB"); - var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true }); + var dest_file = try dir.createFile(io, "sendfile2.txt", .{ .read = true }); defer dest_file.close(io); var src_buffer: [32]u8 = undefined; @@ -1718,10 +1710,10 @@ test "open file with exclusive nonblocking lock twice" { const io = ctx.io; const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); - const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); + const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true }); defer file1.close(io); - const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); + const file2 = ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true }); try testing.expectError(error.WouldBlock, file2); } }.impl); @@ -1735,10 +1727,10 @@ test "open file with shared and exclusive nonblocking lock" { const io = ctx.io; const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); - const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true }); + const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .shared, .lock_nonblocking = true }); defer file1.close(io); - const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); + const file2 = ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true }); try testing.expectError(error.WouldBlock, file2); } }.impl); @@ -1752,10 +1744,10 @@ test "open file with exclusive and shared nonblocking lock" { const io = ctx.io; const filename = try ctx.transformPath("file_nonblocking_lock_test.txt"); - const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); + const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true }); defer file1.close(io); - const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true }); + const file2 = ctx.dir.createFile(io, filename, .{ .lock = .shared, .lock_nonblocking = true }); try testing.expectError(error.WouldBlock, file2); } }.impl); @@ -1769,13 +1761,13 @@ test "open file with exclusive lock twice, make sure second lock waits" { const io = ctx.io; const filename = try ctx.transformPath("file_lock_test.txt"); - const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive }); + const file = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive }); errdefer file.close(io); const S = struct { fn checkFn(dir: *Io.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { started.set(); - const file1 = try dir.createFile(path, .{ .lock = .exclusive }); + const file1 = try dir.createFile(io, path, .{ .lock = .exclusive }); locked.set(); file1.close(io); @@ -1847,13 +1839,13 @@ test "read from locked file" { const filename = try ctx.transformPath("read_lock_file_test.txt"); { - const f = try ctx.dir.createFile(filename, .{ .read = true }); + const f = try ctx.dir.createFile(io, filename, .{ .read = true }); defer f.close(io); var buffer: [1]u8 = undefined; _ = try f.read(&buffer); } { - const f = try ctx.dir.createFile(filename, .{ + const f = try ctx.dir.createFile(io, filename, .{ .read = true, .lock = .exclusive, }); @@ -2037,7 +2029,7 @@ test "'.' and '..' in Io.Dir functions" { var created_subdir = try ctx.dir.openDir(subdir_path, .{}); created_subdir.close(io); - const created_file = try ctx.dir.createFile(file_path, .{}); + const created_file = try ctx.dir.createFile(io, file_path, .{}); created_file.close(io); try ctx.dir.access(file_path, .{}); @@ -2103,7 +2095,7 @@ test "chmod" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - const file = try tmp.dir.createFile("test_file", .{ .mode = 0o600 }); + const file = try tmp.dir.createFile(io, "test_file", .{ .mode = 0o600 }); defer file.close(io); try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat()).mode & 0o7777); @@ -2127,7 +2119,7 @@ test "chown" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - const file = try tmp.dir.createFile("test_file", .{}); + const file = try tmp.dir.createFile(io, "test_file", .{}); defer file.close(io); try file.chown(null, null); @@ -2228,7 +2220,7 @@ test "read file non vectored" { const contents = "hello, world!\n"; - const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); + const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); defer file.close(io); { var file_writer: File.Writer = .init(file, &.{}); @@ -2260,7 +2252,7 @@ test "seek keeping partial buffer" { const contents = "0123456789"; - const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); + const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); defer file.close(io); { var file_writer: File.Writer = .init(file, &.{}); @@ -2321,7 +2313,7 @@ test "seekTo flushes buffered data" { const contents = "data"; - const file = try tmp.dir.createFile("seek.bin", .{ .read = true }); + const file = try tmp.dir.createFile(io, "seek.bin", .{ .read = true }); defer file.close(io); { var buf: [16]u8 = undefined; @@ -2350,7 +2342,7 @@ test "File.Writer sendfile with buffered contents" { try tmp_dir.dir.writeFile(.{ .sub_path = "a", .data = "bcd" }); const in = try tmp_dir.dir.openFile(io, "a", .{}); defer in.close(io); - const out = try tmp_dir.dir.createFile("b", .{}); + const out = try tmp_dir.dir.createFile(io, "b", .{}); defer out.close(io); var in_buf: [2]u8 = undefined; @@ -2397,7 +2389,7 @@ test "readlinkat" { // create a symbolic link if (native_os == .windows) { std.os.windows.CreateSymbolicLink( - tmp.dir.fd, + tmp.dir.handle, &[_]u16{ 'l', 'i', 'n', 'k' }, &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false, @@ -2407,7 +2399,7 @@ test "readlinkat" { else => return err, }; } else { - try posix.symlinkat("file.txt", tmp.dir.fd, "link"); + try posix.symlinkat("file.txt", tmp.dir.handle, "link"); } // read the link diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index c7d3f35d40143444f271826a53bc7f6110ac5fdb..0972a302da6f7ec8dcbc6cb4e1b8eab2571947a3 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -1991,7 +1991,7 @@ test "writev/fsync/readv" { defer tmp.cleanup(); const path = "test_io_uring_writev_fsync_readv"; - const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true }); + const file = try tmp.dir.createFile(io, path, .{ .read = true, .truncate = true }); defer file.close(io); const fd = file.handle; @@ -2062,7 +2062,7 @@ test "write/read" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const path = "test_io_uring_write_read"; - const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true }); + const file = try tmp.dir.createFile(io, path, .{ .read = true, .truncate = true }); defer file.close(io); const fd = file.handle; @@ -2110,12 +2110,12 @@ test "splice/read" { var tmp = std.testing.tmpDir(.{}); const path_src = "test_io_uring_splice_src"; - const file_src = try tmp.dir.createFile(path_src, .{ .read = true, .truncate = true }); + const file_src = try tmp.dir.createFile(io, path_src, .{ .read = true, .truncate = true }); defer file_src.close(io); const fd_src = file_src.handle; const path_dst = "test_io_uring_splice_dst"; - const file_dst = try tmp.dir.createFile(path_dst, .{ .read = true, .truncate = true }); + const file_dst = try tmp.dir.createFile(io, path_dst, .{ .read = true, .truncate = true }); defer file_dst.close(io); const fd_dst = file_dst.handle; @@ -2185,7 +2185,7 @@ test "write_fixed/read_fixed" { defer tmp.cleanup(); const path = "test_io_uring_write_read_fixed"; - const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true }); + const file = try tmp.dir.createFile(io, path, .{ .read = true, .truncate = true }); defer file.close(io); const fd = file.handle; @@ -2306,7 +2306,7 @@ test "close" { defer tmp.cleanup(); const path = "test_io_uring_close"; - const file = try tmp.dir.createFile(path, .{}); + const file = try tmp.dir.createFile(io, path, .{}); errdefer file.close(io); const sqe_close = try ring.close(0x44444444, file.handle); @@ -2652,7 +2652,7 @@ test "fallocate" { defer tmp.cleanup(); const path = "test_io_uring_fallocate"; - const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); + const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); defer file.close(io); try testing.expectEqual(@as(u64, 0), (try file.stat()).size); @@ -2699,7 +2699,7 @@ test "statx" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const path = "test_io_uring_statx"; - const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); + const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); defer file.close(io); try testing.expectEqual(@as(u64, 0), (try file.stat()).size); @@ -2969,7 +2969,7 @@ test "renameat" { // Write old file with data - const old_file = try tmp.dir.createFile(old_path, .{ .truncate = true, .mode = 0o666 }); + const old_file = try tmp.dir.createFile(io, old_path, .{ .truncate = true, .mode = 0o666 }); defer old_file.close(io); try old_file.writeAll("hello"); @@ -3028,7 +3028,7 @@ test "unlinkat" { // Write old file with data - const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); + const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); defer file.close(io); // Submit unlinkat @@ -3125,7 +3125,7 @@ test "symlinkat" { const path = "test_io_uring_symlinkat"; const link_path = "test_io_uring_symlinkat_link"; - const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); + const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); defer file.close(io); // Submit symlinkat @@ -3177,7 +3177,7 @@ test "linkat" { // Write file with data - const first_file = try tmp.dir.createFile(first_path, .{ .truncate = true, .mode = 0o666 }); + const first_file = try tmp.dir.createFile(io, first_path, .{ .truncate = true, .mode = 0o666 }); defer first_file.close(io); try first_file.writeAll("hello"); diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index 39606ddfacc66a6544b50b4905664500247175ee..d7cfb4e1380997b84f00a3df85b68533048ad35d 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -18,7 +18,7 @@ test "fallocate" { defer tmp.cleanup(); const path = "test_fallocate"; - const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); + const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); defer file.close(io); try expect((try file.stat()).size == 0); @@ -85,7 +85,7 @@ test "statx" { defer tmp.cleanup(); const tmp_file_name = "just_a_temporary_file.txt"; - var file = try tmp.dir.createFile(tmp_file_name, .{}); + var file = try tmp.dir.createFile(io, tmp_file_name, .{}); defer file.close(io); var buf: linux.Statx = undefined; @@ -121,7 +121,7 @@ test "fadvise" { defer tmp.cleanup(); const tmp_file_name = "temp_posix_fadvise.txt"; - var file = try tmp.dir.createFile(tmp_file_name, .{}); + var file = try tmp.dir.createFile(io, tmp_file_name, .{}); defer file.close(io); var buf: [2048]u8 = undefined; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 6b5b678b20d802560ce9f8d1d40fe2d0d9f1b40d..ba5282256fd97eebf6257a8ee68b8be006139a7a 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -4639,8 +4639,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE break :path_to_get path; } // We can also skip GetFinalPathNameByHandle if the handle matches - // the handle returned by fs.cwd() - if (dir.? == std.fs.cwd().fd) { + // the handle returned by Io.Dir.cwd() + if (dir.? == Io.Dir.cwd().fd) { break :path_to_get path; } // At this point, we know we have a relative path that had too many diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 392987ec50e3725dd84dc4b3e28a973e31c58c06..f4aa970413345a03470ec7a2dc30193d493e895d 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -15,15 +15,16 @@ //! deal with the exception. const builtin = @import("builtin"); -const root = @import("root"); +const native_os = builtin.os.tag; + const std = @import("std.zig"); +const Io = std.Io; const mem = std.mem; const fs = std.fs; -const max_path_bytes = fs.max_path_bytes; +const max_path_bytes = std.fs.max_path_bytes; const maxInt = std.math.maxInt; const cast = std.math.cast; const assert = std.debug.assert; -const native_os = builtin.os.tag; const page_size_min = std.heap.page_size_min; test { @@ -797,7 +798,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { .INTR => continue, .INVAL => unreachable, .FAULT => unreachable, - .SRCH => return error.ProcessNotFound, .AGAIN => return error.WouldBlock, .CANCELED => return error.Canceled, .BADF => return error.NotOpenForReading, // Can be a race condition. @@ -917,7 +917,6 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { .INTR => continue, .INVAL => return error.InvalidArgument, .FAULT => unreachable, - .SRCH => return error.ProcessNotFound, .AGAIN => return error.WouldBlock, .BADF => return error.NotOpenForWriting, // can be a race condition. .DESTADDRREQ => unreachable, // `connect` was never called. @@ -985,7 +984,8 @@ pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t { .NFILE => return error.SystemFdQuotaExceeded, .NODEV => return error.NoDevice, .NOENT => return error.FileNotFound, - .SRCH => return error.ProcessNotFound, + // Can happen on Linux when opening procfs files. + .SRCH => return error.FileNotFound, .NOMEM => return error.SystemResources, .NOSPC => return error.NoSpaceLeft, .NOTDIR => return error.NotDir, @@ -1560,7 +1560,7 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void { pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void { _ = mode; const sub_dir_handle = windows.OpenFile(dir_path_w, .{ - .dir = fs.cwd().fd, + .dir = Io.Dir.cwd().handle, .access_mask = .{ .STANDARD = .{ .SYNCHRONIZE = true }, .GENERIC = .{ .READ = true }, diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 0071a72a26dfd5d301666b18e890c84e3a1df79f..dc63be6e14ab496ee997a3b2609dcbd5ef69f370 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -148,7 +148,7 @@ test "linkat with different directories" { try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" }); // Test 1: link from file in subdir back up to target in parent directory - try posix.linkat(tmp.dir.fd, target_name, subdir.fd, link_name, 0); + try posix.linkat(tmp.dir.handle, target_name, subdir.handle, link_name, 0); const efd = try tmp.dir.openFile(io, target_name, .{}); defer efd.close(io); @@ -164,7 +164,7 @@ test "linkat with different directories" { } // Test 2: remove link - try posix.unlinkat(subdir.fd, link_name, 0); + try posix.unlinkat(subdir.handle, link_name, 0); _, const elink = try getLinkInfo(efd.handle); try testing.expectEqual(@as(posix.nlink_t, 1), elink); } @@ -373,7 +373,7 @@ test "mmap" { // Create a file used for testing mmap() calls with a file descriptor { - const file = try tmp.dir.createFile(test_out_file, .{}); + const file = try tmp.dir.createFile(io, test_out_file, .{}); defer file.close(io); var stream = file.writer(&.{}); @@ -444,7 +444,7 @@ test "fcntl" { const test_out_file = "os_tmp_test"; - const file = try tmp.dir.createFile(test_out_file, .{}); + const file = try tmp.dir.createFile(io, test_out_file, .{}); defer file.close(io); // Note: The test assumes createFile opens the file with CLOEXEC @@ -495,7 +495,7 @@ test "fsync" { defer tmp.cleanup(); const test_out_file = "os_tmp_test"; - const file = try tmp.dir.createFile(test_out_file, .{}); + const file = try tmp.dir.createFile(io, test_out_file, .{}); defer file.close(io); try posix.fsync(file.handle); @@ -617,7 +617,7 @@ test "dup & dup2" { defer tmp.cleanup(); { - var file = try tmp.dir.createFile("os_dup_test", .{}); + var file = try tmp.dir.createFile(io, "os_dup_test", .{}); defer file.close(io); var duped = Io.File{ .handle = try posix.dup(file.handle) }; @@ -659,7 +659,7 @@ test "writev longer than IOV_MAX" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - var file = try tmp.dir.createFile("pwritev", .{}); + var file = try tmp.dir.createFile(io, "pwritev", .{}); defer file.close(io); const iovecs = [_]posix.iovec_const{.{ .base = "a", .len = 1 }} ** (posix.IOV_MAX + 1); @@ -684,7 +684,7 @@ test "POSIX file locking with fcntl" { defer tmp.cleanup(); // Create a temporary lock file - var file = try tmp.dir.createFile("lock", .{ .read = true }); + var file = try tmp.dir.createFile(io, "lock", .{ .read = true }); defer file.close(io); try file.setEndPos(2); const fd = file.handle; @@ -881,7 +881,7 @@ test "isatty" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - var file = try tmp.dir.createFile("foo", .{}); + var file = try tmp.dir.createFile(io, "foo", .{}); defer file.close(io); try expectEqual(posix.isatty(file.handle), false); @@ -893,7 +893,7 @@ test "pread with empty buffer" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - var file = try tmp.dir.createFile("pread_empty", .{ .read = true }); + var file = try tmp.dir.createFile(io, "pread_empty", .{ .read = true }); defer file.close(io); const bytes = try a.alloc(u8, 0); @@ -909,7 +909,7 @@ test "write with empty buffer" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - var file = try tmp.dir.createFile("write_empty", .{}); + var file = try tmp.dir.createFile(io, "write_empty", .{}); defer file.close(io); const bytes = try a.alloc(u8, 0); @@ -925,7 +925,7 @@ test "pwrite with empty buffer" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - var file = try tmp.dir.createFile("pwrite_empty", .{}); + var file = try tmp.dir.createFile(io, "pwrite_empty", .{}); defer file.close(io); const bytes = try a.alloc(u8, 0); @@ -965,35 +965,35 @@ test "fchmodat smoke test" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try expectError(error.FileNotFound, posix.fchmodat(tmp.dir.fd, "regfile", 0o666, 0)); + try expectError(error.FileNotFound, posix.fchmodat(tmp.dir.handle, "regfile", 0o666, 0)); const fd = try posix.openat( - tmp.dir.fd, + tmp.dir.handle, "regfile", .{ .ACCMODE = .WRONLY, .CREAT = true, .EXCL = true, .TRUNC = true }, 0o644, ); posix.close(fd); - try posix.symlinkat("regfile", tmp.dir.fd, "symlink"); - const sym_mode = try getFileMode(tmp.dir.fd, "symlink"); + try posix.symlinkat("regfile", tmp.dir.handle, "symlink"); + const sym_mode = try getFileMode(tmp.dir.handle, "symlink"); - try posix.fchmodat(tmp.dir.fd, "regfile", 0o640, 0); - try expectMode(tmp.dir.fd, "regfile", 0o640); - try posix.fchmodat(tmp.dir.fd, "regfile", 0o600, posix.AT.SYMLINK_NOFOLLOW); - try expectMode(tmp.dir.fd, "regfile", 0o600); + try posix.fchmodat(tmp.dir.handle, "regfile", 0o640, 0); + try expectMode(tmp.dir.handle, "regfile", 0o640); + try posix.fchmodat(tmp.dir.handle, "regfile", 0o600, posix.AT.SYMLINK_NOFOLLOW); + try expectMode(tmp.dir.handle, "regfile", 0o600); - try posix.fchmodat(tmp.dir.fd, "symlink", 0o640, 0); - try expectMode(tmp.dir.fd, "regfile", 0o640); - try expectMode(tmp.dir.fd, "symlink", sym_mode); + try posix.fchmodat(tmp.dir.handle, "symlink", 0o640, 0); + try expectMode(tmp.dir.handle, "regfile", 0o640); + try expectMode(tmp.dir.handle, "symlink", sym_mode); var test_link = true; - posix.fchmodat(tmp.dir.fd, "symlink", 0o600, posix.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) { + posix.fchmodat(tmp.dir.handle, "symlink", 0o600, posix.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) { error.OperationNotSupported => test_link = false, else => |e| return e, }; if (test_link) - try expectMode(tmp.dir.fd, "symlink", 0o600); - try expectMode(tmp.dir.fd, "regfile", 0o640); + try expectMode(tmp.dir.handle, "symlink", 0o600); + try expectMode(tmp.dir.handle, "regfile", 0o640); } const CommonOpenFlags = packed struct { diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index b7743039010a4c1fe01515822d35d6d73d2acc06..33faeef0615cff9a1a173e4dbbcee7af0fd9fcf0 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -677,7 +677,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); if (self.cwd_dir) |cwd| { - posix.fchdir(cwd.fd) catch |err| forkChildErrReport(err_pipe[1], err); + posix.fchdir(cwd.handle) catch |err| forkChildErrReport(err_pipe[1], err); } else if (self.cwd) |cwd| { posix.chdir(cwd) catch |err| forkChildErrReport(err_pipe[1], err); } diff --git a/lib/std/std.zig b/lib/std/std.zig index 106811859b88f6532ee22212fec92875d5529fa0..1690c0575c6c5396758300cb2b23a522cdbd993e 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -114,7 +114,7 @@ pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options pub const Options = struct { enable_segfault_handler: bool = debug.default_enable_segfault_handler, - /// Function used to implement `std.fs.cwd` for WASI. + /// Function used to implement `std.Io.Dir.cwd` for WASI. wasiCwd: fn () os.wasi.fd_t = os.defaultWasiCwd, /// The current log level. diff --git a/lib/std/tar.zig b/lib/std/tar.zig index d861314fecb3768254d293b97391d18e49a63c9a..8a0bbb342f5fdd5b7b413042a0326bad36f70ab5 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -610,7 +610,7 @@ pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOp } }, .file => { - if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| { + if (createDirAndFile(io, dir, file_name, fileMode(file.mode, options))) |fs_file| { defer fs_file.close(io); var file_writer = fs_file.writer(&file_contents_buffer); try it.streamRemaining(file, &file_writer.interface); @@ -638,12 +638,12 @@ pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOp } } -fn createDirAndFile(dir: Io.Dir, file_name: []const u8, mode: Io.File.Mode) !Io.File { - const fs_file = dir.createFile(file_name, .{ .exclusive = true, .mode = mode }) catch |err| { +fn createDirAndFile(io: Io, dir: Io.Dir, file_name: []const u8, mode: Io.File.Mode) !Io.File { + const fs_file = dir.createFile(io, file_name, .{ .exclusive = true, .mode = mode }) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { try dir.makePath(dir_name); - return try dir.createFile(file_name, .{ .exclusive = true, .mode = mode }); + return try dir.createFile(io, file_name, .{ .exclusive = true, .mode = mode }); } } return err; @@ -880,9 +880,9 @@ test "create file and symlink" { var root = testing.tmpDir(.{}); defer root.cleanup(); - var file = try createDirAndFile(root.dir, "file1", default_mode); + var file = try createDirAndFile(io, root.dir, "file1", default_mode); file.close(io); - file = try createDirAndFile(root.dir, "a/b/c/file2", default_mode); + file = try createDirAndFile(io, root.dir, "a/b/c/file2", default_mode); file.close(io); createDirAndSymlink(root.dir, "a/b/c/file2", "symlink1") catch |err| { @@ -894,7 +894,7 @@ test "create file and symlink" { // Danglink symlnik, file created later try createDirAndSymlink(root.dir, "../../../g/h/i/file4", "j/k/l/symlink3"); - file = try createDirAndFile(root.dir, "g/h/i/file4", default_mode); + file = try createDirAndFile(io, root.dir, "g/h/i/file4", default_mode); file.close(io); } diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 19038543a6674717b3c6e64a91591c5d9200b100..99d67ec1322fa8633a71bf25124f1cb2df5f0845 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -628,7 +628,7 @@ pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir { var sub_path: [TmpDir.sub_path_len]u8 = undefined; _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); - const cwd = std.fs.cwd(); + const cwd = Io.Dir.cwd(); var cache_dir = cwd.makeOpenPath(".zig-cache", .{}) catch @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); defer cache_dir.close(io); diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index c8bde2ab02e227c26c3c35df7b2dbf8d6e325afc..80317850df95748894b9509be038480a60a0ec1c 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -57,7 +57,7 @@ pub fn parse( } } - const contents = try std.fs.cwd().readFileAlloc(libc_file, allocator, .limited(std.math.maxInt(usize))); + const contents = try Io.Dir.cwd().readFileAlloc(libc_file, allocator, .limited(std.math.maxInt(usize))); defer allocator.free(contents); var it = std.mem.tokenizeScalar(u8, contents, '\n'); @@ -337,7 +337,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F // search in reverse order const search_path_untrimmed = search_paths.items[search_paths.items.len - path_i - 1]; const search_path = std.mem.trimStart(u8, search_path_untrimmed, " "); - var search_dir = fs.cwd().openDir(search_path, .{}) catch |err| switch (err) { + var search_dir = Io.Dir.cwd().openDir(search_path, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.NoDevice, @@ -392,7 +392,7 @@ fn findNativeIncludeDirWindows( result_buf.shrinkAndFree(0); try result_buf.print("{s}\\Include\\{s}\\ucrt", .{ install.path, install.version }); - var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { + var dir = Io.Dir.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.NoDevice, @@ -440,7 +440,7 @@ fn findNativeCrtDirWindows( result_buf.shrinkAndFree(0); try result_buf.print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ install.path, install.version, arch_sub_dir }); - var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { + var dir = Io.Dir.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.NoDevice, @@ -508,7 +508,7 @@ fn findNativeKernel32LibDir( result_buf.shrinkAndFree(0); try result_buf.print("{s}\\Lib\\{s}\\um\\{s}", .{ install.path, install.version, arch_sub_dir }); - var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { + var dir = Io.Dir.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.NoDevice, @@ -544,7 +544,7 @@ fn findNativeMsvcIncludeDir( const dir_path = try fs.path.join(allocator, &[_][]const u8{ up2, "include" }); errdefer allocator.free(dir_path); - var dir = fs.cwd().openDir(dir_path, .{}) catch |err| switch (err) { + var dir = Io.Dir.cwd().openDir(dir_path, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.NoDevice, diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index 6b6e4fa9f7881b4ac8a375f4d1e08d9e3846e462..dca474020a32f2c9bb00b6cb21d415bec0ba53ac 100644 --- a/lib/std/zig/WindowsSdk.zig +++ b/lib/std/zig/WindowsSdk.zig @@ -828,7 +828,7 @@ const MsvcLibDir = struct { try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); var default_tools_version_buf: [512]u8 = undefined; - const default_tools_version_contents = std.fs.cwd().readFile(lib_dir_buf.items, &default_tools_version_buf) catch { + const default_tools_version_contents = Io.Dir.cwd().readFile(lib_dir_buf.items, &default_tools_version_buf) catch { return error.PathNotFound; }; var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n"); diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index d3bafc16f2028e409fbfc68cd29716ddff8ba5d9..9fa0546c3b7427c03214c12e275b28267094263a 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -1,11 +1,12 @@ const builtin = @import("builtin"); +const native_endian = builtin.cpu.arch.endian(); + const std = @import("../std.zig"); const mem = std.mem; const elf = std.elf; const fs = std.fs; const assert = std.debug.assert; const Target = std.Target; -const native_endian = builtin.cpu.arch.endian(); const posix = std.posix; const Io = std.Io; @@ -69,7 +70,7 @@ pub fn getExternalExecutor( if (os_match and cpu_ok) native: { if (options.link_libc) { if (candidate.dynamic_linker.get()) |candidate_dl| { - fs.cwd().access(candidate_dl, .{}) catch { + Io.Dir.cwd().access(candidate_dl, .{}) catch { bad_result = .{ .bad_dl = candidate_dl }; break :native; }; @@ -710,6 +711,7 @@ fn abiAndDynamicLinkerFromFile( error.SystemResources, error.FileSystem, error.SymLinkLoop, + error.Canceled, error.Unexpected, => |e| return e, }; diff --git a/lib/std/zig/system/darwin/macos.zig b/lib/std/zig/system/darwin/macos.zig index 9bb4e34e3bc337961ffd3caceb69cc2e63a93938..4ff6846a099e752ffeef831e24a7c42bc1a44ff8 100644 --- a/lib/std/zig/system/darwin/macos.zig +++ b/lib/std/zig/system/darwin/macos.zig @@ -1,9 +1,10 @@ -const std = @import("std"); const builtin = @import("builtin"); + +const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const mem = std.mem; const testing = std.testing; - const Target = std.Target; /// Detect macOS version. @@ -54,7 +55,7 @@ pub fn detect(target_os: *Target.Os) !void { // approx. 4 times historical file size var buf: [2048]u8 = undefined; - if (std.fs.cwd().readFile(path, &buf)) |bytes| { + if (Io.Dir.cwd().readFile(path, &buf)) |bytes| { if (parseSystemVersion(bytes)) |ver| { // never return non-canonical `10.(16+)` if (!(ver.major == 10 and ver.minor >= 16)) { diff --git a/lib/std/zip.zig b/lib/std/zip.zig index 0ca77c98a191e57d054f00a67108ae02b44b7a38..9d0884709236d22ae85d0a47b0ab95ad53e2aded 100644 --- a/lib/std/zip.zig +++ b/lib/std/zip.zig @@ -564,9 +564,9 @@ pub const Iterator = struct { defer parent_dir.close(io); const basename = std.fs.path.basename(filename); - break :blk try parent_dir.createFile(basename, .{ .exclusive = true }); + break :blk try parent_dir.createFile(io, basename, .{ .exclusive = true }); } - break :blk try dest.createFile(filename, .{ .exclusive = true }); + break :blk try dest.createFile(io, filename, .{ .exclusive = true }); }; defer out_file.close(io); var out_file_buffer: [1024]u8 = undefined; diff --git a/src/Compilation.zig b/src/Compilation.zig index 24b994f608696f2a3dca0eafb888ca6428ed9c38..5f15ef5f743667b21ec397865ec6c99bdc89f465 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -450,7 +450,7 @@ pub const Path = struct { const dir = switch (p.root) { .none => { const cwd_sub_path = absToCwdRelative(p.sub_path, dirs.cwd); - return .{ fs.cwd(), cwd_sub_path }; + return .{ Io.Dir.cwd(), cwd_sub_path }; }, .zig_lib => dirs.zig_lib.handle, .global_cache => dirs.global_cache.handle, @@ -723,7 +723,7 @@ pub const Directories = struct { pub fn deinit(dirs: *Directories, io: Io) void { // The local and global caches could be the same. - const close_local = dirs.local_cache.handle.fd != dirs.global_cache.handle.fd; + const close_local = dirs.local_cache.handle.handle != dirs.global_cache.handle.handle; dirs.global_cache.handle.close(io); if (close_local) dirs.local_cache.handle.close(io); @@ -814,7 +814,7 @@ pub const Directories = struct { return .{ .path = if (std.mem.eql(u8, name, ".")) null else name, .handle = .{ - .fd = preopens.find(name) orelse fatal("WASI preopen not found: '{s}'", .{name}), + .handle = preopens.find(name) orelse fatal("WASI preopen not found: '{s}'", .{name}), }, }; } @@ -824,8 +824,8 @@ pub const Directories = struct { }; const nonempty_path = if (path.len == 0) "." else path; const handle_or_err = switch (thing) { - .@"zig lib" => fs.cwd().openDir(nonempty_path, .{}), - .@"global cache", .@"local cache" => fs.cwd().makeOpenPath(nonempty_path, .{}), + .@"zig lib" => Io.Dir.cwd().openDir(nonempty_path, .{}), + .@"global cache", .@"local cache" => Io.Dir.cwd().makeOpenPath(nonempty_path, .{}), }; return .{ .path = if (path.len == 0) null else path, @@ -1104,7 +1104,7 @@ pub const CObject = struct { const source_line = source_line: { if (diag.src_loc.offset == 0 or diag.src_loc.column == 0) break :source_line 0; - const file = fs.cwd().openFile(io, file_name, .{}) catch break :source_line 0; + const file = Io.Dir.cwd().openFile(io, file_name, .{}) catch break :source_line 0; defer file.close(io); var buffer: [1024]u8 = undefined; var file_reader = file.reader(io, &buffer); @@ -1179,7 +1179,7 @@ pub const CObject = struct { }; var buffer: [1024]u8 = undefined; - const file = try fs.cwd().openFile(io, path, .{}); + const file = try Io.Dir.cwd().openFile(io, path, .{}); defer file.close(io); var file_reader = file.reader(io, &buffer); var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = &file_reader.interface }); @@ -2109,7 +2109,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, }, }; // These correspond to std.zig.Server.Message.PathPrefix. - cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); + cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(options.dirs.zig_lib); cache.addPrefix(options.dirs.local_cache); cache.addPrefix(options.dirs.global_cache); @@ -5220,7 +5220,7 @@ fn createDepFile( binfile: Cache.Path, ) anyerror!void { var buf: [4096]u8 = undefined; - var af = try std.fs.cwd().atomicFile(depfile, .{ .write_buffer = &buf }); + var af = try Io.Dir.cwd().atomicFile(depfile, .{ .write_buffer = &buf }); defer af.deinit(); comp.writeDepFile(binfile, &af.file_writer.interface) catch return af.file_writer.err.?; @@ -5284,7 +5284,7 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { }; } - var tar_file = out_dir.createFile("sources.tar", .{}) catch |err| { + var tar_file = out_dir.createFile(io, "sources.tar", .{}) catch |err| { return comp.lockAndSetMiscFailure( .docs_copy, "unable to create '{f}/sources.tar': {s}", diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 3bd05120ff456a134b46e3cdf2ac6813e093bb76..8a30529bc579953f44c816e77afc883cb312afef 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -383,14 +383,14 @@ pub fn run(f: *Fetch) RunError!void { }, .remote => |remote| remote, .path_or_url => |path_or_url| { - if (fs.cwd().openDir(path_or_url, .{ .iterate = true })) |dir| { + if (Io.Dir.cwd().openDir(path_or_url, .{ .iterate = true })) |dir| { var resource: Resource = .{ .dir = dir }; return f.runResource(path_or_url, &resource, null); } else |dir_err| { var server_header_buffer: [init_resource_buffer_size]u8 = undefined; const file_err = if (dir_err == error.NotDir) e: { - if (fs.cwd().openFile(io, path_or_url, .{})) |file| { + if (Io.Dir.cwd().openFile(io, path_or_url, .{})) |file| { var resource: Resource = .{ .file = file.reader(io, &server_header_buffer) }; return f.runResource(path_or_url, &resource, null); } else |err| break :e err; @@ -1303,7 +1303,7 @@ fn unzip( const random_integer = std.crypto.random.int(u64); zip_path[prefix.len..][0..random_len].* = std.fmt.hex(random_integer); - break cache_root.handle.createFile(&zip_path, .{ + break cache_root.handle.createFile(io, &zip_path, .{ .exclusive = true, .read = true, }) catch |err| switch (err) { @@ -1365,7 +1365,7 @@ fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!U { var pack_dir = try out_dir.makeOpenPath(".git", .{}); defer pack_dir.close(io); - var pack_file = try pack_dir.createFile("pkg.pack", .{ .read = true }); + var pack_file = try pack_dir.createFile(io, "pkg.pack", .{ .read = true }); defer pack_file.close(io); var pack_file_buffer: [4096]u8 = undefined; var pack_file_reader = b: { @@ -1376,7 +1376,7 @@ fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!U break :b pack_file_writer.moveToReader(io); }; - var index_file = try pack_dir.createFile("pkg.idx", .{ .read = true }); + var index_file = try pack_dir.createFile(io, "pkg.idx", .{ .read = true }); defer index_file.close(io); var index_file_buffer: [2000]u8 = undefined; var index_file_writer = index_file.writer(&index_file_buffer); @@ -2235,7 +2235,7 @@ test "set executable bit based on file content" { fn saveEmbedFile(io: Io, comptime tarball_name: []const u8, dir: Io.Dir) !void { //const tarball_name = "duplicate_paths_excluded.tar.gz"; const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name); - var tmp_file = try dir.createFile(tarball_name, .{}); + var tmp_file = try dir.createFile(io, tarball_name, .{}); defer tmp_file.close(io); try tmp_file.writeAll(tarball_content); } diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index ccae9440e21f8f222ab209c82be5fb9c26fdfdbc..7b08a89cae19a257337cba8a2e9cb35d3576842b 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -264,7 +264,7 @@ pub const Repository = struct { try repository.odb.seekOid(entry.oid); const file_object = try repository.odb.readObject(); if (file_object.type != .blob) return error.InvalidFile; - var file = dir.createFile(entry.name, .{ .exclusive = true }) catch |e| { + var file = dir.createFile(io, entry.name, .{ .exclusive = true }) catch |e| { const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name }); errdefer diagnostics.allocator.free(file_name); try diagnostics.errors.append(diagnostics.allocator, .{ .unable_to_create_file = .{ @@ -1584,14 +1584,14 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u var git_dir = testing.tmpDir(.{}); defer git_dir.cleanup(); - var pack_file = try git_dir.dir.createFile("testrepo.pack", .{ .read = true }); + var pack_file = try git_dir.dir.createFile(io, "testrepo.pack", .{ .read = true }); defer pack_file.close(io); try pack_file.writeAll(testrepo_pack); var pack_file_buffer: [2000]u8 = undefined; var pack_file_reader = pack_file.reader(io, &pack_file_buffer); - var index_file = try git_dir.dir.createFile("testrepo.idx", .{ .read = true }); + var index_file = try git_dir.dir.createFile(io, "testrepo.idx", .{ .read = true }); defer index_file.close(io); var index_file_buffer: [2000]u8 = undefined; var index_file_writer = index_file.writer(&index_file_buffer); @@ -1714,20 +1714,20 @@ pub fn main() !void { const format = std.meta.stringToEnum(Oid.Format, args[1]) orelse return error.InvalidFormat; - var pack_file = try std.fs.cwd().openFile(io, args[2], .{}); + var pack_file = try Io.Dir.cwd().openFile(io, args[2], .{}); defer pack_file.close(io); var pack_file_buffer: [4096]u8 = undefined; var pack_file_reader = pack_file.reader(io, &pack_file_buffer); const commit = try Oid.parse(format, args[3]); - var worktree = try std.fs.cwd().makeOpenPath(args[4], .{}); + var worktree = try Io.Dir.cwd().makeOpenPath(args[4], .{}); defer worktree.close(io); var git_dir = try worktree.makeOpenPath(".git", .{}); defer git_dir.close(io); std.debug.print("Starting index...\n", .{}); - var index_file = try git_dir.createFile("idx", .{ .read = true }); + var index_file = try git_dir.createFile(io, "idx", .{ .read = true }); defer index_file.close(io); var index_file_buffer: [4096]u8 = undefined; var index_file_writer = index_file.writer(&index_file_buffer); diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 45b130213881e8ed30eab6fc46d0037fd0a0066d..9a75b2096ef15c5a6703615e8aed764532fe4020 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -170,7 +170,7 @@ pub fn updateFile( // version. Likewise if we're working on AstGen and another process asks for // the cached file, they'll get it. const cache_file = while (true) { - break zir_dir.createFile(&hex_digest, .{ + break zir_dir.createFile(io, &hex_digest, .{ .read = true, .truncate = false, .lock = lock, @@ -196,7 +196,7 @@ pub fn updateFile( cache_directory, }); } - break zir_dir.createFile(&hex_digest, .{ + break zir_dir.createFile(io, &hex_digest, .{ .read = true, .truncate = false, .lock = lock, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index cb4fe0459f70b511fb04dbd501d5bd48e04936f0..4fc58c2c4b0d59e93d65e6a327dc5d1b29f35fd3 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1,19 +1,22 @@ -const std = @import("std"); const builtin = @import("builtin"); + +const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const Allocator = std.mem.Allocator; const log = std.log.scoped(.codegen); const math = std.math; const DW = std.dwarf; - const Builder = std.zig.llvm.Builder; + +const build_options = @import("build_options"); const llvm = if (build_options.have_llvm) @import("llvm/bindings.zig") else @compileError("LLVM unavailable"); + const link = @import("../link.zig"); const Compilation = @import("../Compilation.zig"); -const build_options = @import("build_options"); const Zcu = @import("../Zcu.zig"); const InternPool = @import("../InternPool.zig"); const Package = @import("../Package.zig"); @@ -964,7 +967,7 @@ pub const Object = struct { if (std.mem.eql(u8, path, "-")) { o.builder.dump(); } else { - o.builder.printToFilePath(std.fs.cwd(), path) catch |err| { + o.builder.printToFilePath(Io.Dir.cwd(), path) catch |err| { log.err("failed printing LLVM module to \"{s}\": {s}", .{ path, @errorName(err) }); }; } @@ -978,7 +981,7 @@ pub const Object = struct { o.builder.clearAndFree(); if (options.pre_bc_path) |path| { - var file = std.fs.cwd().createFile(path, .{}) catch |err| + var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) }); defer file.close(io); @@ -991,7 +994,7 @@ pub const Object = struct { options.post_ir_path == null and options.post_bc_path == null) return; if (options.post_bc_path) |path| { - var file = std.fs.cwd().createFile(path, .{}) catch |err| + var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) }); defer file.close(io); @@ -2711,7 +2714,7 @@ pub const Object = struct { } fn allocTypeName(o: *Object, pt: Zcu.PerThread, ty: Type) Allocator.Error![:0]const u8 { - var aw: std.Io.Writer.Allocating = .init(o.gpa); + var aw: Io.Writer.Allocating = .init(o.gpa); defer aw.deinit(); ty.print(&aw.writer, pt, null) catch |err| switch (err) { error.WriteFailed => return error.OutOfMemory, diff --git a/src/fmt.zig b/src/fmt.zig index ce8a31fa4c86836d8580bc3e05ecbf61ca72f148..36a38339860627cb7274a53cb2b9f6aa416083a2 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -182,11 +182,11 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! // Mark any excluded files/directories as already seen, // so that they are skipped later during actual processing for (excluded_files.items) |file_path| { - const stat = fs.cwd().statFile(file_path) catch |err| switch (err) { + const stat = Io.Dir.cwd().statFile(file_path) catch |err| switch (err) { error.FileNotFound => continue, // On Windows, statFile does not work for directories error.IsDir => dir: { - var dir = try fs.cwd().openDir(file_path, .{}); + var dir = try Io.Dir.cwd().openDir(file_path, .{}); defer dir.close(io); break :dir try dir.stat(); }, @@ -196,7 +196,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! } for (input_files.items) |file_path| { - try fmtPath(&fmt, file_path, check_flag, fs.cwd(), file_path); + try fmtPath(&fmt, file_path, check_flag, Io.Dir.cwd(), file_path); } try fmt.stdout_writer.interface.flush(); if (fmt.any_error) { diff --git a/src/introspect.zig b/src/introspect.zig index d2faa9a55cd2ef73390ad4162ff11792ed5c6c7b..04ddf47e8ad16df68e11298259e0f33a212adfaa 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -82,7 +82,7 @@ pub fn findZigLibDirFromSelfExe( cwd_path: []const u8, self_exe_path: []const u8, ) error{ OutOfMemory, FileNotFound }!Cache.Directory { - const cwd = fs.cwd(); + const cwd = Io.Dir.cwd(); var cur_path: []const u8 = self_exe_path; while (fs.path.dirname(cur_path)) |dirname| : (cur_path = dirname) { var base_dir = cwd.openDir(dirname, .{}) catch continue; @@ -206,7 +206,7 @@ pub fn resolveSuitableLocalCacheDir(arena: Allocator, cwd: []const u8) Allocator var cur_dir = cwd; while (true) { const joined = try fs.path.join(arena, &.{ cur_dir, Package.build_zig_basename }); - if (fs.cwd().access(joined, .{})) |_| { + if (Io.Dir.cwd().access(joined, .{})) |_| { return try fs.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename }); } else |err| switch (err) { error.FileNotFound => { diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index 8c5e0afe4b7bd721e175f60a4d8c42bbee3b0978..cfd8d5554c9ee3b3159a9857d576f827b6fe6a3d 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -1,9 +1,9 @@ const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; const mem = std.mem; const log = std.log; -const fs = std.fs; -const path = fs.path; +const path = std.Io.Dir.path; const assert = std.debug.assert; const Version = std.SemanticVersion; const Path = std.Build.Cache.Path; @@ -446,7 +446,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .io = io, .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}), }; - cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); + cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); cache.addPrefix(comp.dirs.global_cache); defer cache.manifest_dir.close(io); @@ -468,7 +468,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .lock = man.toOwnedLock(), .dir_path = .{ .root_dir = comp.dirs.global_cache, - .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest), + .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest), }, }); } @@ -986,7 +986,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .lock = man.toOwnedLock(), .dir_path = .{ .root_dir = comp.dirs.global_cache, - .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest), + .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest), }, }); } @@ -1014,7 +1014,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C const so_path: Path = .{ .root_dir = so_files.dir_path.root_dir, .sub_path = std.fmt.allocPrint(comp.arena, "{s}{c}lib{s}.so.{d}", .{ - so_files.dir_path.sub_path, fs.path.sep, lib.name, lib.getSoVersion(&target.os), + so_files.dir_path.sub_path, path.sep, lib.name, lib.getSoVersion(&target.os), }) catch return comp.setAllocFailure(), }; task_buffer[task_buffer_i] = .{ .load_dso = so_path }; diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index bec20ff3d46c18be09ca25c014480f7900ab9a11..e3d8ce1f7f021fe0da06e979c706608da9053bee 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -1,9 +1,9 @@ const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; const mem = std.mem; const log = std.log; -const fs = std.fs; -const path = fs.path; +const path = std.Io.Dir.path; const assert = std.debug.assert; const Version = std.SemanticVersion; const Path = std.Build.Cache.Path; @@ -681,7 +681,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .io = io, .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}), }; - cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); + cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); cache.addPrefix(comp.dirs.global_cache); defer cache.manifest_dir.close(io); @@ -703,7 +703,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .lock = man.toOwnedLock(), .dir_path = .{ .root_dir = comp.dirs.global_cache, - .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest), + .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest), }, }); } @@ -775,7 +775,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye try stubs_asm.appendSlice(".text\n"); var sym_i: usize = 0; - var sym_name_buf: std.Io.Writer.Allocating = .init(arena); + var sym_name_buf: Io.Writer.Allocating = .init(arena); var opt_symbol_name: ?[]const u8 = null; var versions_buffer: [32]u8 = undefined; var versions_len: usize = undefined; @@ -796,7 +796,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye // twice, which causes a "duplicate symbol" assembler error. var versions_written = std.AutoArrayHashMap(Version, void).init(arena); - var inc_reader: std.Io.Reader = .fixed(metadata.inclusions); + var inc_reader: Io.Reader = .fixed(metadata.inclusions); const fn_inclusions_len = try inc_reader.takeInt(u16, .little); @@ -1130,7 +1130,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .lock = man.toOwnedLock(), .dir_path = .{ .root_dir = comp.dirs.global_cache, - .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest), + .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest), }, }); } @@ -1156,7 +1156,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C const so_path: Path = .{ .root_dir = so_files.dir_path.root_dir, .sub_path = std.fmt.allocPrint(comp.arena, "{s}{c}lib{s}.so.{d}", .{ - so_files.dir_path.sub_path, fs.path.sep, lib.name, lib.sover, + so_files.dir_path.sub_path, path.sep, lib.name, lib.sover, }) catch return comp.setAllocFailure(), }; task_buffer[task_buffer_i] = .{ .load_dso = so_path }; diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 005696e1fca20a6f32f74562d5f79404d486c65b..b3ca51e833f296f112722ba56d2a31f9507f62a9 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -1,7 +1,8 @@ const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; const mem = std.mem; -const path = std.fs.path; +const path = std.Io.Dir.path; const assert = std.debug.assert; const log = std.log.scoped(.mingw); @@ -259,7 +260,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { .io = io, .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}), }; - cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() }); + cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); cache.addPrefix(comp.dirs.global_cache); defer cache.manifest_dir.close(io); @@ -304,7 +305,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { .output = .{ .to_list = .{ .arena = .init(gpa) } }, }; defer diagnostics.deinit(); - var aro_comp = aro.Compilation.init(gpa, arena, io, &diagnostics, std.fs.cwd()); + var aro_comp = aro.Compilation.init(gpa, arena, io, &diagnostics, Io.Dir.cwd()); defer aro_comp.deinit(); aro_comp.target = .fromZigTarget(target.*); @@ -343,7 +344,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { } const members = members: { - var aw: std.Io.Writer.Allocating = .init(gpa); + var aw: Io.Writer.Allocating = .init(gpa); errdefer aw.deinit(); try pp.prettyPrintTokens(&aw.writer, .result_only); @@ -376,7 +377,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { errdefer gpa.free(lib_final_path); { - const lib_final_file = try o_dir.createFile(final_lib_basename, .{ .truncate = true }); + const lib_final_file = try o_dir.createFile(io, final_lib_basename, .{ .truncate = true }); defer lib_final_file.close(io); var buffer: [1024]u8 = undefined; var file_writer = lib_final_file.writer(&buffer); @@ -442,7 +443,7 @@ fn findDef( } else { try override_path.print(fmt_path, .{ lib_path, lib_name }); } - if (std.fs.cwd().access(override_path.items, .{})) |_| { + if (Io.Dir.cwd().access(override_path.items, .{})) |_| { return override_path.toOwnedSlice(); } else |err| switch (err) { error.FileNotFound => {}, @@ -459,7 +460,7 @@ fn findDef( } else { try override_path.print(fmt_path, .{lib_name}); } - if (std.fs.cwd().access(override_path.items, .{})) |_| { + if (Io.Dir.cwd().access(override_path.items, .{})) |_| { return override_path.toOwnedSlice(); } else |err| switch (err) { error.FileNotFound => {}, @@ -476,7 +477,7 @@ fn findDef( } else { try override_path.print(fmt_path, .{lib_name}); } - if (std.fs.cwd().access(override_path.items, .{})) |_| { + if (Io.Dir.cwd().access(override_path.items, .{})) |_| { return override_path.toOwnedSlice(); } else |err| switch (err) { error.FileNotFound => {}, diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index 67e6a2f90326a46a199340939c17daac085dd4be..cb6a80d69d338a3fe9f517c07c1e7d9f04b65a1d 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -1,9 +1,9 @@ const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; const mem = std.mem; const log = std.log; -const fs = std.fs; -const path = fs.path; +const path = std.Io.Dir.path; const assert = std.debug.assert; const Version = std.SemanticVersion; const Path = std.Build.Cache.Path; @@ -387,7 +387,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .io = io, .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}), }; - cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); + cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); cache.addPrefix(comp.dirs.global_cache); defer cache.manifest_dir.close(io); @@ -409,7 +409,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .lock = man.toOwnedLock(), .dir_path = .{ .root_dir = comp.dirs.global_cache, - .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest), + .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest), }, }); } @@ -640,7 +640,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .lock = man.toOwnedLock(), .dir_path = .{ .root_dir = comp.dirs.global_cache, - .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest), + .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest), }, }); } @@ -661,7 +661,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C const so_path: Path = .{ .root_dir = so_files.dir_path.root_dir, .sub_path = std.fmt.allocPrint(comp.arena, "{s}{c}lib{s}.so.{d}", .{ - so_files.dir_path.sub_path, fs.path.sep, lib.name, lib.sover, + so_files.dir_path.sub_path, path.sep, lib.name, lib.sover, }) catch return comp.setAllocFailure(), }; task_buffer[task_buffer_i] = .{ .load_dso = so_path }; diff --git a/src/link/C.zig b/src/link/C.zig index 04c92443e5d2f2da848d6eff78495ab922b1fe26..a001f8fdd9f30f291ec35b3f942458ff1401377d 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -136,7 +136,7 @@ pub fn createEmpty( assert(!use_lld); assert(!use_llvm); - const file = try emit.root_dir.handle.createFile(emit.sub_path, .{ + const file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ // Truncation is done on `flush`. .truncate = false, }); @@ -792,7 +792,7 @@ pub fn flushEmitH(zcu: *Zcu) !void { } const directory = emit_h.loc.directory orelse zcu.comp.local_cache_directory; - const file = try directory.handle.createFile(emit_h.loc.basename, .{ + const file = try directory.handle.createFile(io, emit_h.loc.basename, .{ // We set the end position explicitly below; by not truncating the file, we possibly // make it easier on the file system by doing 1 reallocation instead of two. .truncate = false, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index e1d52fb7c4f07ee55b74da8ca278a8c73a0f1d94..009e59ed0d8a38dbff3ad1bf3a9609766c31e7d6 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -631,12 +631,14 @@ fn create( else => return error.UnsupportedCOFFArchitecture, }; + const io = comp.io; + const coff = try arena.create(Coff); - const file = try path.root_dir.handle.createFile(comp.io, path.sub_path, .{ + const file = try path.root_dir.handle.createFile(io, path.sub_path, .{ .read = true, .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode), }); - errdefer file.close(comp.io); + errdefer file.close(io); coff.* = .{ .base = .{ .tag = .coff2, diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 584a50c7f2da10c4fe71ffdb05aa28054c261ae0..53812a37ec473046d14212b33011997021719bac 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -313,9 +313,11 @@ pub fn createEmpty( const is_obj = output_mode == .Obj; const is_obj_or_ar = is_obj or (output_mode == .Lib and link_mode == .static); + const io = comp.io; + // What path should this ELF linker code output to? const sub_path = emit.sub_path; - self.base.file = try emit.root_dir.handle.createFile(sub_path, .{ + self.base.file = try emit.root_dir.handle.createFile(io, sub_path, .{ .truncate = true, .read = true, .mode = link.File.determineMode(output_mode, link_mode), diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 49f6d3f7c726794390d95dd788953f31af688b72..b25b9da9d99ea4406488a5407de17cef6878915c 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1572,7 +1572,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { // report a nice error here with the file path if it fails instead of // just returning the error code. // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. - std.posix.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) { + std.posix.fchmodat(Io.Dir.cwd().handle, full_out_path, 0o744, 0) catch |err| switch (err) { error.OperationNotSupported => unreachable, // Not a symlink. else => |e| return e, }; @@ -1624,7 +1624,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi const rand_int = std.crypto.random.int(u64); const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp"; - const rsp_file = try comp.dirs.local_cache.handle.createFile(rsp_path, .{}); + const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{}); defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err| log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) }); { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index e837cc853a24dc8624b0824aaaee162828ca679d..0f6127e10e89b2b6c66f9dbb96c9f103c40c8325 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -219,7 +219,9 @@ pub fn createEmpty( }; errdefer self.base.destroy(); - self.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ + const io = comp.io; + + self.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ .truncate = true, .read = true, .mode = link.File.determineMode(output_mode, link_mode), @@ -1082,7 +1084,7 @@ fn accessLibPath( test_path.clearRetainingCapacity(); try test_path.print("{s}" ++ sep ++ "lib{s}{s}", .{ search_dir, name, ext }); try checked_paths.append(try arena.dupe(u8, test_path.items)); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + Io.Dir.cwd().access(test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; @@ -1110,7 +1112,7 @@ fn accessFrameworkPath( ext, }); try checked_paths.append(try arena.dupe(u8, test_path.items)); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + Io.Dir.cwd().access(test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; @@ -1191,7 +1193,7 @@ fn parseDependentDylibs(self: *MachO) !void { try test_path.print("{s}{s}", .{ path, ext }); } try checked_paths.append(try arena.dupe(u8, test_path.items)); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + Io.Dir.cwd().access(test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; @@ -3289,7 +3291,7 @@ pub fn reopenDebugInfo(self: *MachO) !void { var d_sym_bundle = try self.base.emit.root_dir.handle.makeOpenPath(d_sym_path, .{}); defer d_sym_bundle.close(io); - self.d_sym.?.file = try d_sym_bundle.createFile(fs.path.basename(self.base.emit.sub_path), .{ + self.d_sym.?.file = try d_sym_bundle.createFile(io, fs.path.basename(self.base.emit.sub_path), .{ .truncate = false, .read = true, }); @@ -4370,7 +4372,7 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi // The file/property is also available with vendored libc. fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 { const sdk_path = try fs.path.join(arena, &.{ dir, "SDKSettings.json" }); - const contents = try fs.cwd().readFileAlloc(sdk_path, arena, .limited(std.math.maxInt(u16))); + const contents = try Io.Dir.cwd().readFileAlloc(sdk_path, arena, .limited(std.math.maxInt(u16))); const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{}); if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string; return error.SdkVersionFailure; diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 5f9a9ecac9d995b8ef88fb473c0230d5259655e7..814faf234a86ee79c8bbcf50138d9854a529342d 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -247,7 +247,7 @@ pub fn deinit(self: *CodeSignature, allocator: Allocator) void { } pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void { - const inner = try fs.cwd().readFileAlloc(path, allocator, .limited(std.math.maxInt(u32))); + const inner = try Io.Dir.cwd().readFileAlloc(path, allocator, .limited(std.math.maxInt(u32))); self.entitlements = .{ .inner = inner }; } diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 7e28dc0a8b0b70411e5b9dafee17fcf2ca98c52b..d13caaa315abe9552614c437151d124d963f2fef 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -33,6 +33,7 @@ pub fn createEmpty( options: link.File.OpenOptions, ) !*Linker { const gpa = comp.gpa; + const io = comp.io; const target = &comp.root_mod.resolved_target.result; assert(!comp.config.use_lld); // Caught by Compilation.Config.resolve @@ -78,7 +79,7 @@ pub fn createEmpty( }; errdefer linker.deinit(); - linker.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ + linker.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ .truncate = true, .read = true, }); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 7ab1e0eb4b0c3866fb0663c34c7e662327d6b431..5f89625d56122b6d4872280d5c7c02fa1589ce39 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2997,7 +2997,9 @@ pub fn createEmpty( .named => |name| (try wasm.internString(name)).toOptional(), }; - wasm.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ + const io = comp.io; + + wasm.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ .truncate = true, .read = true, .mode = if (fs.has_executable_bit) diff --git a/src/main.zig b/src/main.zig index b040b6c8efa00c42b769fe0a8dfc48e9cc5e3655..67b7384b578fcbadc5004f31f7724e7b070464ac 100644 --- a/src/main.zig +++ b/src/main.zig @@ -713,7 +713,7 @@ const Emit = union(enum) { } else e: { // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would. if (fs.path.dirname(path)) |dir_path| { - var dir = fs.cwd().openDir(dir_path, .{}) catch |err| { + var dir = Io.Dir.cwd().openDir(dir_path, .{}) catch |err| { fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) }); }; dir.close(io); @@ -3304,7 +3304,7 @@ fn buildOutputType( } else emit: { // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would. if (fs.path.dirname(path)) |dir_path| { - var dir = fs.cwd().openDir(dir_path, .{}) catch |err| { + var dir = Io.Dir.cwd().openDir(dir_path, .{}) catch |err| { fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) }); }; dir.close(io); @@ -3389,7 +3389,7 @@ fn buildOutputType( // file will not run and this temp file will be leaked. The filename // will be a hash of its contents — so multiple invocations of // `zig cc -` will result in the same temp file name. - var f = try dirs.local_cache.handle.createFile(dump_path, .{}); + var f = try dirs.local_cache.handle.createFile(io, dump_path, .{}); defer f.close(io); // Re-using the hasher from Cache, since the functional requirements @@ -4773,7 +4773,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! var ok_count: usize = 0; for (template_paths) |template_path| { - if (templates.write(arena, fs.cwd(), sanitized_root_name, template_path, fingerprint)) |_| { + if (templates.write(arena, Io.Dir.cwd(), sanitized_root_name, template_path, fingerprint)) |_| { std.log.info("created {s}", .{template_path}); ok_count += 1; } else |err| switch (err) { @@ -5227,7 +5227,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) if (system_pkg_dir_path) |p| { job_queue.global_cache = .{ .path = p, - .handle = fs.cwd().openDir(p, .{}) catch |err| { + .handle = Io.Dir.cwd().openDir(p, .{}) catch |err| { fatal("unable to open system package directory '{s}': {s}", .{ p, @errorName(err), }); @@ -5823,7 +5823,7 @@ const ArgIteratorResponseFile = process.ArgIteratorGeneral(.{ .comments = true, /// Initialize the arguments from a Response File. "*.rsp" fn initArgIteratorResponseFile(allocator: Allocator, resp_file_path: []const u8) !ArgIteratorResponseFile { const max_bytes = 10 * 1024 * 1024; // 10 MiB of command line arguments is a reasonable limit - const cmd_line = try fs.cwd().readFileAlloc(resp_file_path, allocator, .limited(max_bytes)); + const cmd_line = try Io.Dir.cwd().readFileAlloc(resp_file_path, allocator, .limited(max_bytes)); errdefer allocator.free(cmd_line); return ArgIteratorResponseFile.initTakeOwnership(allocator, cmd_line); @@ -6187,7 +6187,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { const display_path = zig_source_path orelse ""; const source: [:0]const u8 = s: { var f = if (zig_source_path) |p| file: { - break :file fs.cwd().openFile(io, p, .{}) catch |err| { + break :file Io.Dir.cwd().openFile(io, p, .{}) catch |err| { fatal("unable to open file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) }); }; } else Io.File.stdin(); @@ -6494,7 +6494,7 @@ fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void { const cache_file = args[0]; - var f = fs.cwd().openFile(io, cache_file, .{}) catch |err| { + var f = Io.Dir.cwd().openFile(io, cache_file, .{}) catch |err| { fatal("unable to open zir cache file for dumping '{s}': {s}", .{ cache_file, @errorName(err) }); }; defer f.close(io); @@ -6541,7 +6541,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { const new_source_path = args[1]; const old_source = source: { - var f = fs.cwd().openFile(io, old_source_path, .{}) catch |err| + var f = Io.Dir.cwd().openFile(io, old_source_path, .{}) catch |err| fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); defer f.close(io); var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer); @@ -6549,7 +6549,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); }; const new_source = source: { - var f = fs.cwd().openFile(io, new_source_path, .{}) catch |err| + var f = Io.Dir.cwd().openFile(io, new_source_path, .{}) catch |err| fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) }); defer f.close(io); var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer); @@ -6845,7 +6845,7 @@ fn accessFrameworkPath( framework_dir_path, framework_name, framework_name, ext, }); try checked_paths.print("\n {s}", .{test_path.items}); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + Io.Dir.cwd().access(test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| fatal("unable to search for {s} framework '{s}': {s}", .{ ext, test_path.items, @errorName(e), @@ -6957,7 +6957,7 @@ fn cmdFetch( var global_cache_directory: Directory = l: { const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena); break :l .{ - .handle = try fs.cwd().makeOpenPath(p, .{}), + .handle = try Io.Dir.cwd().makeOpenPath(p, .{}), .path = p, }; }; @@ -7260,7 +7260,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { if (options.build_file) |bf| { if (fs.path.dirname(bf)) |dirname| { - const dir = fs.cwd().openDir(dirname, .{}) catch |err| { + const dir = Io.Dir.cwd().openDir(dirname, .{}) catch |err| { fatal("unable to open directory to build file from argument 'build-file', '{s}': {s}", .{ dirname, @errorName(err) }); }; return .{ @@ -7272,7 +7272,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { return .{ .build_zig_basename = build_zig_basename, - .directory = .{ .path = null, .handle = fs.cwd() }, + .directory = .{ .path = null, .handle = Io.Dir.cwd() }, .cleanup_build_dir = null, }; } @@ -7280,8 +7280,8 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { var dirname: []const u8 = cwd_path; while (true) { const joined_path = try fs.path.join(arena, &[_][]const u8{ dirname, build_zig_basename }); - if (fs.cwd().access(joined_path, .{})) |_| { - const dir = fs.cwd().openDir(dirname, .{}) catch |err| { + if (Io.Dir.cwd().access(joined_path, .{})) |_| { + const dir = Io.Dir.cwd().openDir(dirname, .{}) catch |err| { fatal("unable to open directory while searching for build.zig file, '{s}': {s}", .{ dirname, @errorName(err) }); }; return .{ @@ -7443,7 +7443,7 @@ const Templates = struct { } }; fn writeSimpleTemplateFile(io: Io, file_name: []const u8, comptime fmt: []const u8, args: anytype) !void { - const f = try fs.cwd().createFile(file_name, .{ .exclusive = true }); + const f = try Io.Dir.cwd().createFile(io, file_name, .{ .exclusive = true }); defer f.close(io); var buf: [4096]u8 = undefined; var fw = f.writer(&buf); @@ -7591,7 +7591,7 @@ fn addLibDirectoryWarn2( ignore_not_found: bool, ) void { lib_directories.appendAssumeCapacity(.{ - .handle = fs.cwd().openDir(path, .{}) catch |err| { + .handle = Io.Dir.cwd().openDir(path, .{}) catch |err| { if (err == error.FileNotFound and ignore_not_found) return; warn("unable to open library directory '{s}': {s}", .{ path, @errorName(err) }); return; -- 2.54.0 From 264d714321d3e5f1f189af393e1fb24d101a7e91 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 13:46:29 -0800 Subject: [PATCH 016/195] update all openDir() sites to accept io instance --- lib/compiler/aro/aro/Driver/Filesystem.zig | 4 +- lib/compiler/aro/aro/Toolchain.zig | 2 +- lib/compiler/resinator/compile.zig | 14 +++--- lib/compiler/std-docs.zig | 4 +- lib/std/Build.zig | 3 +- lib/std/Build/Cache/Path.zig | 3 +- lib/std/Build/Step/InstallArtifact.zig | 2 +- lib/std/Build/Step/WriteFile.zig | 2 +- lib/std/Io/Dir.zig | 10 ++-- lib/std/crypto/Certificate/Bundle.zig | 2 +- lib/std/crypto/codecs/asn1/test.zig | 2 +- lib/std/dynamic_library.zig | 6 +-- lib/std/fs/test.zig | 54 +++++++++++----------- lib/std/os/linux/IoUring.zig | 4 +- lib/std/zig/LibCInstallation.zig | 10 ++-- src/Compilation.zig | 23 +++++---- src/Package/Fetch.zig | 5 +- src/Package/Fetch/git.zig | 2 +- src/fmt.zig | 4 +- src/introspect.zig | 6 +-- src/main.zig | 37 +++++++-------- 21 files changed, 107 insertions(+), 92 deletions(-) diff --git a/lib/compiler/aro/aro/Driver/Filesystem.zig b/lib/compiler/aro/aro/Driver/Filesystem.zig index b0bdbb7e2185e65a2f00780bd71ce03bc00e0346..001f10f27c7b5acc5f9e854adfd36ae10b58ead5 100644 --- a/lib/compiler/aro/aro/Driver/Filesystem.zig +++ b/lib/compiler/aro/aro/Driver/Filesystem.zig @@ -223,9 +223,9 @@ pub const Filesystem = union(enum) { }; } - pub fn openDir(fs: Filesystem, dir_name: []const u8) std.Io.Dir.OpenError!Dir { + pub fn openDir(fs: Filesystem, io: Io, dir_name: []const u8) std.Io.Dir.OpenError!Dir { return switch (fs) { - .real => |cwd| .{ .dir = try cwd.openDir(dir_name, .{ .access_sub_paths = false, .iterate = true }) }, + .real => |cwd| .{ .dir = try cwd.openDir(io, dir_name, .{ .access_sub_paths = false, .iterate = true }) }, .fake => |entries| .{ .fake = .{ .entries = entries, .path = dir_name } }, }; } diff --git a/lib/compiler/aro/aro/Toolchain.zig b/lib/compiler/aro/aro/Toolchain.zig index 95a8baba7776fe077960d7332e046585248d0dae..0328c264d08ad4f33c2bf3ea43e235c6d7aa7c4e 100644 --- a/lib/compiler/aro/aro/Toolchain.zig +++ b/lib/compiler/aro/aro/Toolchain.zig @@ -509,7 +509,7 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void { } var search_path = d.aro_name; while (std.fs.path.dirname(search_path)) |dirname| : (search_path = dirname) { - var base_dir = d.comp.cwd.openDir(dirname, .{}) catch continue; + var base_dir = d.comp.cwd.openDir(io, dirname, .{}) catch continue; defer base_dir.close(io); base_dir.access("include/stddef.h", .{}) catch continue; diff --git a/lib/compiler/resinator/compile.zig b/lib/compiler/resinator/compile.zig index 3e046a10c14f61ee22b8d4c88310fd15db3cf11a..2d6ea87efbce7d84f966228c79e6e861c870d535 100644 --- a/lib/compiler/resinator/compile.zig +++ b/lib/compiler/resinator/compile.zig @@ -106,13 +106,13 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io // If dirname returns null, then the root path will be the same as // the cwd so we don't need to add it as a distinct search path. if (std.fs.path.dirname(root_path)) |root_dir_path| { - var root_dir = try options.cwd.openDir(root_dir_path, .{}); + var root_dir = try options.cwd.openDir(io, root_dir_path, .{}); errdefer root_dir.close(io); try search_dirs.append(allocator, .{ .dir = root_dir, .path = try allocator.dupe(u8, root_dir_path) }); } } // Re-open the passed in cwd since we want to be able to close it (Io.Dir.cwd() shouldn't be closed) - const cwd_dir = options.cwd.openDir(".", .{}) catch |err| { + const cwd_dir = options.cwd.openDir(io, ".", .{}) catch |err| { try options.diagnostics.append(.{ .err = .failed_to_open_cwd, .token = .{ @@ -132,7 +132,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io }; try search_dirs.append(allocator, .{ .dir = cwd_dir, .path = null }); for (options.extra_include_paths) |extra_include_path| { - var dir = openSearchPathDir(options.cwd, extra_include_path) catch { + var dir = openSearchPathDir(options.cwd, io, extra_include_path) catch { // TODO: maybe a warning that the search path is skipped? continue; }; @@ -140,7 +140,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, extra_include_path) }); } for (options.system_include_paths) |system_include_path| { - var dir = openSearchPathDir(options.cwd, system_include_path) catch { + var dir = openSearchPathDir(options.cwd, io, system_include_path) catch { // TODO: maybe a warning that the search path is skipped? continue; }; @@ -159,7 +159,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io }; var it = std.mem.tokenizeScalar(u8, INCLUDE, delimiter); while (it.next()) |search_path| { - var dir = openSearchPathDir(options.cwd, search_path) catch continue; + var dir = openSearchPathDir(options.cwd, io, search_path) catch continue; errdefer dir.close(io); try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, search_path) }); } @@ -2896,11 +2896,11 @@ pub const Compiler = struct { pub const OpenSearchPathError = std.Io.Dir.OpenError; -fn openSearchPathDir(dir: std.Io.Dir, path: []const u8) OpenSearchPathError!std.Io.Dir { +fn openSearchPathDir(dir: std.Io.Dir, io: Io, path: []const u8) OpenSearchPathError!std.Io.Dir { // Validate the search path to avoid possible unreachable on invalid paths, // see https://github.com/ziglang/zig/issues/15607 for why this is currently necessary. try validateSearchPath(path); - return dir.openDir(path, .{}); + return dir.openDir(io, path, .{}); } /// Very crude attempt at validating a path. This is imperfect diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index 87c4da9faaf83c8d51cb078ed2c868692776e2df..37e72d49e501b889a81c8e174ae6c47703bcea24 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -40,7 +40,7 @@ pub fn main() !void { const zig_exe_path = argv.next().?; const global_cache_path = argv.next().?; - var lib_dir = try Io.Dir.cwd().openDir(zig_lib_directory, .{}); + var lib_dir = try Io.Dir.cwd().openDir(io, zig_lib_directory, .{}); defer lib_dir.close(io); var listen_port: u16 = 0; @@ -206,7 +206,7 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { }, }); - var std_dir = try context.lib_dir.openDir("std", .{ .iterate = true }); + var std_dir = try context.lib_dir.openDir(io, "std", .{ .iterate = true }); defer std_dir.close(io); var walker = try std_dir.walk(gpa); diff --git a/lib/std/Build.zig b/lib/std/Build.zig index cc2f70fd2f067dee8f32d3c71fa8a25cfef74872..3d0e8b8dbc219c27243ad0df6882728d3d56f0f1 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -2184,6 +2184,7 @@ fn dependencyInner( pkg_deps: AvailableDeps, args: anytype, ) *Dependency { + const io = b.graph.io; const user_input_options = userInputOptionsFromArgs(b.allocator, args); if (b.graph.dependency_cache.getContext(.{ .build_root_string = build_root_string, @@ -2193,7 +2194,7 @@ fn dependencyInner( const build_root: std.Build.Cache.Directory = .{ .path = build_root_string, - .handle = Io.Dir.cwd().openDir(build_root_string, .{}) catch |err| { + .handle = Io.Dir.cwd().openDir(io, build_root_string, .{}) catch |err| { std.debug.print("unable to open '{s}': {s}\n", .{ build_root_string, @errorName(err), }); diff --git a/lib/std/Build/Cache/Path.zig b/lib/std/Build/Cache/Path.zig index 60211670ded38352091d04ccd50bbdbd8c58a0f6..93e0c0d7924d6cd9f68a512c064f2b0b4d5610f3 100644 --- a/lib/std/Build/Cache/Path.zig +++ b/lib/std/Build/Cache/Path.zig @@ -71,6 +71,7 @@ pub fn openFile(p: Path, io: Io, sub_path: []const u8, flags: Io.File.OpenFlags) pub fn openDir( p: Path, + io: Io, sub_path: []const u8, args: Io.Dir.OpenOptions, ) Io.Dir.OpenError!Io.Dir { @@ -80,7 +81,7 @@ pub fn openDir( p.sub_path, sub_path, }) catch return error.NameTooLong; }; - return p.root_dir.handle.openDir(joined_path, args); + return p.root_dir.handle.openDir(io, joined_path, args); } pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io.Dir { diff --git a/lib/std/Build/Step/InstallArtifact.zig b/lib/std/Build/Step/InstallArtifact.zig index 1cdb2327706ad3e8c93209e74aa36f2306cae88d..4c1506aa335d43503571d902438b4bc2ba8b4a98 100644 --- a/lib/std/Build/Step/InstallArtifact.zig +++ b/lib/std/Build/Step/InstallArtifact.zig @@ -164,7 +164,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const src_dir_path = dir.source.getPath3(b, step); const full_h_prefix = b.getInstallPath(h_dir, dir.dest_rel_path); - var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { + var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { return step.fail("unable to open source directory '{f}': {s}", .{ src_dir_path, @errorName(err), }); diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index 2834f18564f1b91217d1deb5f5496fdfd15ae075..353c85fecfd360079850f6394bf7442e2c2b1f1d 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -218,7 +218,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const need_derived_inputs = try step.addDirectoryWatchInput(dir.source); const src_dir_path = dir.source.getPath3(b, step); - var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { + var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { return step.fail("unable to open source directory '{f}': {s}", .{ src_dir_path, @errorName(err), }); diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 3b552a556a7780d4bcab936b543bba3e2a1e8986..9189a4c60939a4c9d3c15cd09354d143d617b65f 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -234,7 +234,7 @@ pub const SelectiveWalker = struct { return; } - var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| { + var new_dir = entry.dir.openDir(io, entry.basename, .{ .iterate = true }) catch |err| { switch (err) { error.NameTooLong => unreachable, else => |e| return e, @@ -1326,7 +1326,7 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { var treat_as_dir = true; handle_entry: while (true) { if (treat_as_dir) { - break :iterable_dir parent_dir.openDir(name, .{ + break :iterable_dir parent_dir.openDir(io, name, .{ .follow_symlinks = false, .iterate = true, }) catch |err| switch (err) { @@ -1430,7 +1430,7 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, var treat_as_dir = entry.kind == .directory; handle_entry: while (true) { if (treat_as_dir) { - const new_dir = dir.openDir(entry.name, .{ + const new_dir = dir.openDir(io, entry.name, .{ .follow_symlinks = false, .iterate = true, }) catch |err| switch (err) { @@ -1520,14 +1520,14 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, } /// On successful delete, returns null. -fn deleteTreeOpenInitialSubpath(dir: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir { +fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hint: File.Kind) !?Dir { return iterable_dir: { // Treat as a file by default var treat_as_dir = kind_hint == .directory; handle_entry: while (true) { if (treat_as_dir) { - break :iterable_dir dir.openDir(sub_path, .{ + break :iterable_dir dir.openDir(io, sub_path, .{ .follow_symlinks = false, .iterate = true, }) catch |err| switch (err) { diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index d4a9f1e757d10f1b1cde732d8af9f4ab15a7d161..1b87c97949be14adfaf93a5cdbbac4838f9ee488 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -180,7 +180,7 @@ pub fn addCertsFromDirPath( dir: Io.Dir, sub_dir_path: []const u8, ) AddCertsFromDirPathError!void { - var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true }); + var iterable_dir = try dir.openDir(io, sub_dir_path, .{ .iterate = true }); defer iterable_dir.close(io); return addCertsFromDir(cb, gpa, io, iterable_dir); } diff --git a/lib/std/crypto/codecs/asn1/test.zig b/lib/std/crypto/codecs/asn1/test.zig index 3dbedb9f80a24b59beb83a1dd034e8849b15159e..bdfb47e2dfb68b4e2b917419d37b06d8c3b47b16 100644 --- a/lib/std/crypto/codecs/asn1/test.zig +++ b/lib/std/crypto/codecs/asn1/test.zig @@ -73,7 +73,7 @@ test AllTypes { try std.testing.expectEqualSlices(u8, encoded, buf); // Use this to update test file. - // const dir = try Io.Dir.cwd().openDir("lib/std/crypto/asn1", .{}); + // const dir = try Io.Dir.cwd().openDir(io, "lib/std/crypto/asn1", .{}); // var file = try dir.createFile(io, path, .{}); // defer file.close(io); // try file.writeAll(buf); diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index a1801d00d05122d53962263bbe1a21664320c453..b1965b60eea9090662a01220bd3fc959835e4924 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -160,9 +160,9 @@ pub const ElfDynLib = struct { fn openPath(path: []const u8, io: Io) !Io.Dir { if (path.len == 0) return error.NotDir; var parts = std.mem.tokenizeScalar(u8, path, '/'); - var parent = if (path[0] == '/') try Io.Dir.cwd().openDir("/", .{}) else Io.Dir.cwd(); + var parent = if (path[0] == '/') try Io.Dir.cwd().openDir(io, "/", .{}) else Io.Dir.cwd(); while (parts.next()) |part| { - const child = try parent.openDir(part, .{}); + const child = try parent.openDir(io, part, .{}); parent.close(io); parent = child; } @@ -184,7 +184,7 @@ pub const ElfDynLib = struct { } fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?posix.fd_t { - var dir = Io.Dir.cwd().openDir(dir_path, .{}) catch return null; + var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch return null; defer dir.close(io); return posix.openat(dir.handle, file_name, .{ .ACCMODE = .RDONLY, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index aab86d40a62c1da7e85de57678032edef84654de..9924cafaf84350d0c98eaf45062c365351355046 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -367,7 +367,7 @@ test "openDir" { for ([_][]const u8{ "", ".", ".." }) |sub_path| { const dir_path = try fs.path.join(allocator, &.{ subdir_path, sub_path }); - var dir = try ctx.dir.openDir(dir_path, .{}); + var dir = try ctx.dir.openDir(io, dir_path, .{}); defer dir.close(io); } } @@ -448,7 +448,7 @@ test "openDirAbsolute" { test "openDir cwd parent '..'" { const io = testing.io; - var dir = Io.Dir.cwd().openDir("..", .{}) catch |err| { + var dir = Io.Dir.cwd().openDir(io, "..", .{}) catch |err| { if (native_os == .wasi and err == error.PermissionDenied) { return; // This is okay. WASI disallows escaping from the fs sandbox } @@ -471,7 +471,7 @@ test "openDir non-cwd parent '..'" { var subdir = try tmp.dir.makeOpenPath("subdir", .{}); defer subdir.close(io); - var dir = try subdir.openDir("..", .{}); + var dir = try subdir.openDir(io, "..", .{}); defer dir.close(io); const expected_path = try tmp.dir.realpathAlloc(testing.allocator, "."); @@ -839,7 +839,7 @@ test "directory operations on files" { file.close(io); try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name)); - try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{})); + try testing.expectError(error.NotDir, ctx.dir.openDir(io, test_file_name, .{})); try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name)); if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { @@ -902,7 +902,7 @@ test "file operations on directories" { } // ensure the directory still exists as a sanity check - var dir = try ctx.dir.openDir(test_dir_name, .{}); + var dir = try ctx.dir.openDir(io, test_dir_name, .{}); dir.close(io); } }.impl); @@ -918,7 +918,7 @@ test "makeOpenPath parent dirs do not exist" { dir.close(io); // double check that the full directory structure was created - var dir_verification = try tmp_dir.dir.openDir("root_dir/parent_dir/some_dir", .{}); + var dir_verification = try tmp_dir.dir.openDir(io, "root_dir/parent_dir/some_dir", .{}); dir_verification.close(io); } @@ -1005,8 +1005,8 @@ test "Dir.rename directories" { try ctx.dir.rename(test_dir_path, test_dir_renamed_path); // Ensure the directory was renamed - try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{})); - var dir = try ctx.dir.openDir(test_dir_renamed_path, .{}); + try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{})); + var dir = try ctx.dir.openDir(io, test_dir_renamed_path, .{}); // Put a file in the directory var file = try dir.createFile(io, "test_file", .{ .read = true }); @@ -1017,8 +1017,8 @@ test "Dir.rename directories" { try ctx.dir.rename(test_dir_renamed_path, test_dir_renamed_again_path); // Ensure the directory was renamed and the file still exists in it - try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{})); - dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{}); + try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_renamed_path, .{})); + dir = try ctx.dir.openDir(io, test_dir_renamed_again_path, .{}); file = try dir.openFile(io, "test_file", .{}); file.close(io); dir.close(io); @@ -1042,8 +1042,8 @@ test "Dir.rename directory onto empty dir" { try ctx.dir.rename(test_dir_path, target_dir_path); // Ensure the directory was renamed - try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{})); - var dir = try ctx.dir.openDir(target_dir_path, .{}); + try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{})); + var dir = try ctx.dir.openDir(io, target_dir_path, .{}); dir.close(io); } }.impl); @@ -1070,7 +1070,7 @@ test "Dir.rename directory onto non-empty dir" { try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, target_dir_path)); // Ensure the directory was not renamed - var dir = try ctx.dir.openDir(test_dir_path, .{}); + var dir = try ctx.dir.openDir(io, test_dir_path, .{}); dir.close(io); } }.impl); @@ -1165,8 +1165,8 @@ test "renameAbsolute" { ); // ensure the directory was renamed - try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{})); - var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{}); + try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(io, test_dir_name, .{})); + var dir = try tmp_dir.dir.openDir(io, renamed_test_dir_name, .{}); dir.close(io); } @@ -1234,6 +1234,7 @@ test "deleteTree on a symlink" { test "makePath, put some files in it, deleteTree" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const allocator = ctx.arena.allocator(); const dir_path = try ctx.transformPath("os_test_tmp"); @@ -1248,7 +1249,7 @@ test "makePath, put some files in it, deleteTree" { }); try ctx.dir.deleteTree(dir_path); - try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{})); + try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{})); } }.impl); } @@ -1256,6 +1257,7 @@ test "makePath, put some files in it, deleteTree" { test "makePath, put some files in it, deleteTreeMinStackSize" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const allocator = ctx.arena.allocator(); const dir_path = try ctx.transformPath("os_test_tmp"); @@ -1270,7 +1272,7 @@ test "makePath, put some files in it, deleteTreeMinStackSize" { }); try ctx.dir.deleteTreeMinStackSize(dir_path); - try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{})); + try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{})); } }.impl); } @@ -1296,7 +1298,7 @@ test "makePath but sub_path contains pre-existing file" { } fn expectDir(io: Io, dir: Dir, path: []const u8) !void { - var d = try dir.openDir(path, .{}); + var d = try dir.openDir(io, path, .{}); d.close(io); } @@ -1307,7 +1309,7 @@ test "makepath existing directories" { defer tmp.cleanup(); try tmp.dir.makeDir("A"); - var tmpA = try tmp.dir.openDir("A", .{}); + var tmpA = try tmp.dir.openDir(io, "A", .{}); defer tmpA.close(io); try tmpA.makeDir("B"); @@ -1569,7 +1571,7 @@ test "sendfile" { try tmp.dir.makePath("os_test_tmp"); - var dir = try tmp.dir.openDir("os_test_tmp", .{}); + var dir = try tmp.dir.openDir(io, "os_test_tmp", .{}); defer dir.close(io); const line1 = "line1\n"; @@ -1616,7 +1618,7 @@ test "sendfile with buffered data" { try tmp.dir.makePath("os_test_tmp"); - var dir = try tmp.dir.openDir("os_test_tmp", .{}); + var dir = try tmp.dir.openDir(io, "os_test_tmp", .{}); defer dir.close(io); var src_file = try dir.createFile(io, "sendfile1.txt", .{ .read = true }); @@ -1913,7 +1915,7 @@ test "walker" { return err; }; // make sure that the entry.dir is the containing dir - var entry_dir = try entry.dir.openDir(entry.basename, .{}); + var entry_dir = try entry.dir.openDir(io, entry.basename, .{}); defer entry_dir.close(io); num_walked += 1; } @@ -1981,7 +1983,7 @@ test "selective walker, skip entries that start with ." { }; // make sure that the entry.dir is the containing dir - var entry_dir = try entry.dir.openDir(entry.basename, .{}); + var entry_dir = try entry.dir.openDir(io, entry.basename, .{}); defer entry_dir.close(io); num_walked += 1; } @@ -2026,7 +2028,7 @@ test "'.' and '..' in Io.Dir functions" { try ctx.dir.makeDir(subdir_path); try ctx.dir.access(subdir_path, .{}); - var created_subdir = try ctx.dir.openDir(subdir_path, .{}); + var created_subdir = try ctx.dir.openDir(io, subdir_path, .{}); created_subdir.close(io); const created_file = try ctx.dir.createFile(io, file_path, .{}); @@ -2103,7 +2105,7 @@ test "chmod" { try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777); try tmp.dir.makeDir("test_dir"); - var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true }); + var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); defer dir.close(io); try dir.chmod(0o700); @@ -2125,7 +2127,7 @@ test "chown" { try tmp.dir.makeDir("test_dir"); - var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true }); + var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); defer dir.close(io); try dir.chown(null, null); } diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index 0972a302da6f7ec8dcbc6cb4e1b8eab2571947a3..167c6ef398af07d168f152555ab88c094e789fb0 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -3066,6 +3066,8 @@ test "unlinkat" { test "mkdirat" { if (!is_linux) return error.SkipZigTest; + const io = testing.io; + var ring = IoUring.init(1, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -3104,7 +3106,7 @@ test "mkdirat" { }, cqe); // Validate that the directory exist - _ = try tmp.dir.openDir(path, .{}); + _ = try tmp.dir.openDir(io, path, .{}); } test "symlinkat" { diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index 80317850df95748894b9509be038480a60a0ec1c..a5c55d3522a763d06ff73bf3536d6277901020d7 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -337,7 +337,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F // search in reverse order const search_path_untrimmed = search_paths.items[search_paths.items.len - path_i - 1]; const search_path = std.mem.trimStart(u8, search_path_untrimmed, " "); - var search_dir = Io.Dir.cwd().openDir(search_path, .{}) catch |err| switch (err) { + var search_dir = Io.Dir.cwd().openDir(io, search_path, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.NoDevice, @@ -392,7 +392,7 @@ fn findNativeIncludeDirWindows( result_buf.shrinkAndFree(0); try result_buf.print("{s}\\Include\\{s}\\ucrt", .{ install.path, install.version }); - var dir = Io.Dir.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { + var dir = Io.Dir.cwd().openDir(io, result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.NoDevice, @@ -440,7 +440,7 @@ fn findNativeCrtDirWindows( result_buf.shrinkAndFree(0); try result_buf.print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ install.path, install.version, arch_sub_dir }); - var dir = Io.Dir.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { + var dir = Io.Dir.cwd().openDir(io, result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.NoDevice, @@ -508,7 +508,7 @@ fn findNativeKernel32LibDir( result_buf.shrinkAndFree(0); try result_buf.print("{s}\\Lib\\{s}\\um\\{s}", .{ install.path, install.version, arch_sub_dir }); - var dir = Io.Dir.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { + var dir = Io.Dir.cwd().openDir(io, result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.NoDevice, @@ -544,7 +544,7 @@ fn findNativeMsvcIncludeDir( const dir_path = try fs.path.join(allocator, &[_][]const u8{ up2, "include" }); errdefer allocator.free(dir_path); - var dir = Io.Dir.cwd().openDir(dir_path, .{}) catch |err| switch (err) { + var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.NoDevice, diff --git a/src/Compilation.zig b/src/Compilation.zig index 5f15ef5f743667b21ec397865ec6c99bdc89f465..ef01fbb07cc5462b7e5ab327b6e50578c04ecaff 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -745,6 +745,7 @@ pub const Directories = struct { /// Uses `std.process.fatal` on error conditions. pub fn init( arena: Allocator, + io: Io, override_zig_lib: ?[]const u8, override_global_cache: ?[]const u8, local_cache_strat: union(enum) { @@ -768,7 +769,7 @@ pub const Directories = struct { }; const zig_lib: Cache.Directory = d: { - if (override_zig_lib) |path| break :d openUnresolved(arena, cwd, path, .@"zig lib"); + if (override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib"); if (wasi) break :d openWasiPreopen(wasi_preopens, "/lib"); break :d introspect.findZigLibDirFromSelfExe(arena, cwd, self_exe_path) catch |err| { fatal("unable to find zig installation directory '{s}': {s}", .{ self_exe_path, @errorName(err) }); @@ -776,22 +777,22 @@ pub const Directories = struct { }; const global_cache: Cache.Directory = d: { - if (override_global_cache) |path| break :d openUnresolved(arena, cwd, path, .@"global cache"); + if (override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache"); if (wasi) break :d openWasiPreopen(wasi_preopens, "/cache"); const path = introspect.resolveGlobalCacheDir(arena) catch |err| { fatal("unable to resolve zig cache directory: {s}", .{@errorName(err)}); }; - break :d openUnresolved(arena, cwd, path, .@"global cache"); + break :d openUnresolved(arena, io, cwd, path, .@"global cache"); }; const local_cache: Cache.Directory = switch (local_cache_strat) { - .override => |path| openUnresolved(arena, cwd, path, .@"local cache"), + .override => |path| openUnresolved(arena, io, cwd, path, .@"local cache"), .search => d: { const maybe_path = introspect.resolveSuitableLocalCacheDir(arena, cwd) catch |err| { fatal("unable to resolve zig cache directory: {s}", .{@errorName(err)}); }; const path = maybe_path orelse break :d global_cache; - break :d openUnresolved(arena, cwd, path, .@"local cache"); + break :d openUnresolved(arena, io, cwd, path, .@"local cache"); }, .global => global_cache, }; @@ -818,13 +819,19 @@ pub const Directories = struct { }, }; } - fn openUnresolved(arena: Allocator, cwd: []const u8, unresolved_path: []const u8, thing: enum { @"zig lib", @"global cache", @"local cache" }) Cache.Directory { + fn openUnresolved( + arena: Allocator, + io: Io, + cwd: []const u8, + unresolved_path: []const u8, + thing: enum { @"zig lib", @"global cache", @"local cache" }, + ) Cache.Directory { const path = introspect.resolvePath(arena, cwd, &.{unresolved_path}) catch |err| { fatal("unable to resolve {s} directory: {s}", .{ @tagName(thing), @errorName(err) }); }; const nonempty_path = if (path.len == 0) "." else path; const handle_or_err = switch (thing) { - .@"zig lib" => Io.Dir.cwd().openDir(nonempty_path, .{}), + .@"zig lib" => Io.Dir.cwd().openDir(io, nonempty_path, .{}), .@"global cache", .@"local cache" => Io.Dir.cwd().makeOpenPath(nonempty_path, .{}), }; return .{ @@ -5331,7 +5338,7 @@ fn docsCopyModule( const root = module.root; var mod_dir = d: { const root_dir, const sub_path = root.openInfo(comp.dirs); - break :d root_dir.openDir(sub_path, .{ .iterate = true }); + break :d root_dir.openDir(io, sub_path, .{ .iterate = true }); } catch |err| { return comp.lockAndSetMiscFailure(.docs_copy, "unable to open directory '{f}': {t}", .{ root.fmt(comp), err }); }; diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 8a30529bc579953f44c816e77afc883cb312afef..a651107bf5f8b42ccb7868a9ccdd7c709a95024a 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -383,7 +383,7 @@ pub fn run(f: *Fetch) RunError!void { }, .remote => |remote| remote, .path_or_url => |path_or_url| { - if (Io.Dir.cwd().openDir(path_or_url, .{ .iterate = true })) |dir| { + if (Io.Dir.cwd().openDir(io, path_or_url, .{ .iterate = true })) |dir| { var resource: Resource = .{ .dir = dir }; return f.runResource(path_or_url, &resource, null); } else |dir_err| { @@ -2311,8 +2311,9 @@ const TestFetchBuilder = struct { } fn packageDir(self: *TestFetchBuilder) !Io.Dir { + const io = self.job_queue.io; const root = self.fetch.package_root; - return try root.root_dir.handle.openDir(root.sub_path, .{ .iterate = true }); + return try root.root_dir.handle.openDir(io, root.sub_path, .{ .iterate = true }); } // Test helper, asserts thet package dir constains expected_files. diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index 7b08a89cae19a257337cba8a2e9cb35d3576842b..3f0ffb04cdf928fdaa92a43c05223451693c10c8 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -254,7 +254,7 @@ pub const Repository = struct { switch (entry.type) { .directory => { try dir.makeDir(entry.name); - var subdir = try dir.openDir(entry.name, .{}); + var subdir = try dir.openDir(io, entry.name, .{}); defer subdir.close(io); const sub_path = try std.fs.path.join(repository.odb.allocator, &.{ current_path, entry.name }); defer repository.odb.allocator.free(sub_path); diff --git a/src/fmt.zig b/src/fmt.zig index 36a38339860627cb7274a53cb2b9f6aa416083a2..dc67619d54675f1067862bcc7521eac197188449 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -186,7 +186,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! error.FileNotFound => continue, // On Windows, statFile does not work for directories error.IsDir => dir: { - var dir = try Io.Dir.cwd().openDir(file_path, .{}); + var dir = try Io.Dir.cwd().openDir(io, file_path, .{}); defer dir.close(io); break :dir try dir.stat(); }, @@ -224,7 +224,7 @@ fn fmtPathDir( ) !void { const io = fmt.io; - var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true }); + var dir = try parent_dir.openDir(io, parent_sub_path, .{ .iterate = true }); defer dir.close(io); const stat = try dir.stat(); diff --git a/src/introspect.zig b/src/introspect.zig index 04ddf47e8ad16df68e11298259e0f33a212adfaa..a56a214cbe793580df11f171cd1d5964a95917f5 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -21,7 +21,7 @@ fn testZigInstallPrefix(io: Io, base_dir: Io.Dir) ?Cache.Directory { zig_dir: { // Try lib/zig/std/std.zig const lib_zig = "lib" ++ fs.path.sep_str ++ "zig"; - var test_zig_dir = base_dir.openDir(lib_zig, .{}) catch break :zig_dir; + var test_zig_dir = base_dir.openDir(io, lib_zig, .{}) catch break :zig_dir; const file = test_zig_dir.openFile(io, test_index_file, .{}) catch { test_zig_dir.close(io); break :zig_dir; @@ -31,7 +31,7 @@ fn testZigInstallPrefix(io: Io, base_dir: Io.Dir) ?Cache.Directory { } // Try lib/std/std.zig - var test_zig_dir = base_dir.openDir("lib", .{}) catch return null; + var test_zig_dir = base_dir.openDir(io, "lib", .{}) catch return null; const file = test_zig_dir.openFile(io, test_index_file, .{}) catch { test_zig_dir.close(io); return null; @@ -85,7 +85,7 @@ pub fn findZigLibDirFromSelfExe( const cwd = Io.Dir.cwd(); var cur_path: []const u8 = self_exe_path; while (fs.path.dirname(cur_path)) |dirname| : (cur_path = dirname) { - var base_dir = cwd.openDir(dirname, .{}) catch continue; + var base_dir = cwd.openDir(io, dirname, .{}) catch continue; defer base_dir.close(io); const sub_directory = testZigInstallPrefix(io, base_dir) orelse continue; diff --git a/src/main.zig b/src/main.zig index 67b7384b578fcbadc5004f31f7724e7b070464ac..fe48efa2fd2d9e0d50a47f7d2321afd457ecff83 100644 --- a/src/main.zig +++ b/src/main.zig @@ -713,7 +713,7 @@ const Emit = union(enum) { } else e: { // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would. if (fs.path.dirname(path)) |dir_path| { - var dir = Io.Dir.cwd().openDir(dir_path, .{}) catch |err| { + var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch |err| { fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) }); }; dir.close(io); @@ -3304,7 +3304,7 @@ fn buildOutputType( } else emit: { // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would. if (fs.path.dirname(path)) |dir_path| { - var dir = Io.Dir.cwd().openDir(dir_path, .{}) catch |err| { + var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch |err| { fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) }); }; dir.close(io); @@ -3959,14 +3959,14 @@ fn createModule( if (fs.path.isAbsolute(lib_dir_arg)) { const stripped_dir = lib_dir_arg[fs.path.parsePath(lib_dir_arg).root.len..]; const full_path = try fs.path.join(arena, &[_][]const u8{ root, stripped_dir }); - addLibDirectoryWarn(&create_module.lib_directories, full_path); + addLibDirectoryWarn(io, &create_module.lib_directories, full_path); } else { - addLibDirectoryWarn(&create_module.lib_directories, lib_dir_arg); + addLibDirectoryWarn(io, &create_module.lib_directories, lib_dir_arg); } } } else { for (create_module.lib_dir_args.items) |lib_dir_arg| { - addLibDirectoryWarn(&create_module.lib_directories, lib_dir_arg); + addLibDirectoryWarn(io, &create_module.lib_directories, lib_dir_arg); } } create_module.lib_dir_args = undefined; // From here we use lib_directories instead. @@ -4002,7 +4002,7 @@ fn createModule( try create_module.rpath_list.appendSlice(arena, paths.rpaths.items); try create_module.lib_directories.ensureUnusedCapacity(arena, paths.lib_dirs.items.len); - for (paths.lib_dirs.items) |path| addLibDirectoryWarn2(&create_module.lib_directories, path, true); + for (paths.lib_dirs.items) |path| addLibDirectoryWarn2(io, &create_module.lib_directories, path, true); } if (create_module.libc_paths_file) |paths_file| { @@ -4026,8 +4026,8 @@ fn createModule( }; } try create_module.lib_directories.ensureUnusedCapacity(arena, 2); - addLibDirectoryWarn(&create_module.lib_directories, create_module.libc_installation.?.msvc_lib_dir.?); - addLibDirectoryWarn(&create_module.lib_directories, create_module.libc_installation.?.kernel32_lib_dir.?); + addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.msvc_lib_dir.?); + addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.kernel32_lib_dir.?); } // Destructively mutates but does not transfer ownership of `unresolved_link_inputs`. @@ -5118,7 +5118,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) process.raiseFileDescriptorLimit(); const cwd_path = try introspect.getResolvedCwd(arena); - const build_root = try findBuildRoot(arena, .{ + const build_root = try findBuildRoot(arena, io, .{ .cwd_path = cwd_path, .build_file = build_file, }); @@ -5227,7 +5227,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) if (system_pkg_dir_path) |p| { job_queue.global_cache = .{ .path = p, - .handle = Io.Dir.cwd().openDir(p, .{}) catch |err| { + .handle = Io.Dir.cwd().openDir(io, p, .{}) catch |err| { fatal("unable to open system package directory '{s}': {s}", .{ p, @errorName(err), }); @@ -7039,7 +7039,7 @@ fn cmdFetch( const cwd_path = try introspect.getResolvedCwd(arena); - var build_root = try findBuildRoot(arena, .{ + var build_root = try findBuildRoot(arena, io, .{ .cwd_path = cwd_path, }); defer build_root.deinit(); @@ -7251,7 +7251,7 @@ const FindBuildRootOptions = struct { cwd_path: ?[]const u8 = null, }; -fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { +fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !BuildRoot { const cwd_path = options.cwd_path orelse try introspect.getResolvedCwd(arena); const build_zig_basename = if (options.build_file) |bf| fs.path.basename(bf) @@ -7260,7 +7260,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { if (options.build_file) |bf| { if (fs.path.dirname(bf)) |dirname| { - const dir = Io.Dir.cwd().openDir(dirname, .{}) catch |err| { + const dir = Io.Dir.cwd().openDir(io, dirname, .{}) catch |err| { fatal("unable to open directory to build file from argument 'build-file', '{s}': {s}", .{ dirname, @errorName(err) }); }; return .{ @@ -7281,7 +7281,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot { while (true) { const joined_path = try fs.path.join(arena, &[_][]const u8{ dirname, build_zig_basename }); if (Io.Dir.cwd().access(joined_path, .{})) |_| { - const dir = Io.Dir.cwd().openDir(dirname, .{}) catch |err| { + const dir = Io.Dir.cwd().openDir(io, dirname, .{}) catch |err| { fatal("unable to open directory while searching for build.zig file, '{s}': {s}", .{ dirname, @errorName(err) }); }; return .{ @@ -7464,7 +7464,7 @@ fn findTemplates(gpa: Allocator, arena: Allocator, io: Io) Templates { const s = fs.path.sep_str; const template_sub_path = "init"; - const template_dir = zig_lib_directory.handle.openDir(template_sub_path, .{}) catch |err| { + const template_dir = zig_lib_directory.handle.openDir(io, template_sub_path, .{}) catch |err| { const path = zig_lib_directory.path orelse "."; fatal("unable to open zig project template directory '{s}{s}{s}': {s}", .{ path, s, template_sub_path, @errorName(err), @@ -7581,17 +7581,18 @@ fn anyObjectLinkInputs(link_inputs: []const link.UnresolvedInput) bool { return false; } -fn addLibDirectoryWarn(lib_directories: *std.ArrayList(Directory), path: []const u8) void { - return addLibDirectoryWarn2(lib_directories, path, false); +fn addLibDirectoryWarn(io: Io, lib_directories: *std.ArrayList(Directory), path: []const u8) void { + return addLibDirectoryWarn2(io, lib_directories, path, false); } fn addLibDirectoryWarn2( + io: Io, lib_directories: *std.ArrayList(Directory), path: []const u8, ignore_not_found: bool, ) void { lib_directories.appendAssumeCapacity(.{ - .handle = Io.Dir.cwd().openDir(path, .{}) catch |err| { + .handle = Io.Dir.cwd().openDir(io, path, .{}) catch |err| { if (err == error.FileNotFound and ignore_not_found) return; warn("unable to open library directory '{s}': {s}", .{ path, @errorName(err) }); return; -- 2.54.0 From 9f4d40b1f9bffc4137055b8a07f042ecfa398124 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 14:17:52 -0800 Subject: [PATCH 017/195] update all stat() to stat(io) --- lib/compiler/objcopy.zig | 2 +- lib/compiler/resinator/utils.zig | 2 +- lib/compiler/std-docs.zig | 2 +- lib/std/Build/Cache.zig | 9 ++++--- lib/std/Build/WebServer.zig | 2 +- lib/std/Io/test.zig | 4 +-- lib/std/dynamic_library.zig | 2 +- lib/std/fs/test.zig | 22 ++++++++-------- lib/std/os/linux/IoUring.zig | 6 ++--- lib/std/os/linux/test.zig | 4 +-- src/Compilation.zig | 2 +- src/Zcu.zig | 2 +- src/Zcu/PerThread.zig | 4 +-- src/fmt.zig | 6 ++--- src/link.zig | 6 ++--- src/link/Elf.zig | 5 ++-- src/link/Elf/Archive.zig | 45 +++++++++++++++++--------------- src/link/Elf/Object.zig | 7 +++-- src/link/MachO.zig | 3 ++- src/link/MachO/Archive.zig | 3 ++- src/link/MachO/Object.zig | 4 ++- src/link/MachO/relocatable.zig | 2 +- src/link/tapi.zig | 4 +-- 23 files changed, 80 insertions(+), 68 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index e48f76a6a61a68bcb003bf68f53290200505abcd..6473a10dd08a833846f5dc653f9bdb9400d983e8 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -155,7 +155,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void const input_file = Io.Dir.cwd().openFile(input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err }); defer input_file.close(io); - const stat = input_file.stat() catch |err| fatal("failed to stat {s}: {t}", .{ input, err }); + const stat = input_file.stat(io) catch |err| fatal("failed to stat {s}: {t}", .{ input, err }); var in: File.Reader = .initSize(input_file, io, &input_buffer, stat.size); diff --git a/lib/compiler/resinator/utils.zig b/lib/compiler/resinator/utils.zig index 3f03b73c54cc57b17e8d70f9042d8df37a73f883..285342ad74189a9f10abb3035149c1266218fe54 100644 --- a/lib/compiler/resinator/utils.zig +++ b/lib/compiler/resinator/utils.zig @@ -38,7 +38,7 @@ pub fn openFileNotDir( errdefer file.close(io); // https://github.com/ziglang/zig/issues/5732 if (builtin.os.tag != .windows) { - const stat = try file.stat(); + const stat = try file.stat(io); if (stat.kind == .directory) return error.IsDir; diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index 37e72d49e501b889a81c8e174ae6c47703bcea24..d5beab5f179d62cfa676ddfac03c924b41016774 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -227,7 +227,7 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { } var file = try entry.dir.openFile(io, entry.basename, .{}); defer file.close(io); - const stat = try file.stat(); + const stat = try file.stat(io); var file_reader: std.Io.File.Reader = .{ .file = file, .interface = std.Io.File.Reader.initInterface(&.{}), diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index fdcb2ab714ca4a28ceee69957b1cddaad8363f69..0176d71a1caba1e7512c98e4e1c0ea45a7c95489 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -775,7 +775,7 @@ pub const Manifest = struct { }; defer this_file.close(io); - const actual_stat = this_file.stat() catch |err| { + const actual_stat = this_file.stat(io) catch |err| { self.diagnostic = .{ .file_stat = .{ .file_index = idx, .err = err, @@ -883,7 +883,7 @@ pub const Manifest = struct { defer file.close(io); // Save locally and also save globally (we still hold the global lock). - const stat = file.stat() catch |err| switch (err) { + const stat = file.stat(io) catch |err| switch (err) { error.Canceled => return error.Canceled, else => return true, }; @@ -909,7 +909,8 @@ pub const Manifest = struct { } fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: Io.File) !void { - const actual_stat = try handle.stat(); + const io = self.cache.io; + const actual_stat = try handle.stat(io); ch_file.stat = .{ .size = actual_stat.size, .mtime = actual_stat.mtime, @@ -1333,7 +1334,7 @@ fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp { dir.deleteFile(test_out_file) catch {}; } - return (try file.stat()).mtime; + return (try file.stat(io)).mtime; } test "cache file and then recall it" { diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 9938d5e1b027cfc574ed4852b2d85319cdc3b5c2..472e87b05a156f73b830a9e45b53f0e7a1ca2e1f 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -509,7 +509,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons continue; }; defer file.close(io); - const stat = try file.stat(); + const stat = try file.stat(io); var read_buffer: [1024]u8 = undefined; var file_reader: Io.File.Reader = .initSize(file, io, &read_buffer, stat.size); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index e731dc18d78354a4c45052ff48b64f7639c3755e..8b9d714ee857622b8a0c253cc2de54e26d544b74 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -124,13 +124,13 @@ test "updateTimes" { var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true }); defer file.close(io); - const stat_old = try file.stat(); + const stat_old = try file.stat(io); // Set atime and mtime to 5s before try file.updateTimes( stat_old.atime.subDuration(.fromSeconds(5)), stat_old.mtime.subDuration(.fromSeconds(5)), ); - const stat_new = try file.stat(); + const stat_new = try file.stat(io); try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds); try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds); } diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index b1965b60eea9090662a01220bd3fc959835e4924..f02caa1f5ba025372dde22682bb6eb28c2ae3879 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -226,7 +226,7 @@ pub const ElfDynLib = struct { defer posix.close(fd); const file: Io.File = .{ .handle = fd }; - const stat = try file.stat(); + const stat = try file.stat(io); const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; const page_size = std.heap.pageSize(); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 9924cafaf84350d0c98eaf45062c365351355046..7b12ba42713db6fffc201679fd5fb4a87b3eac89 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -350,7 +350,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { }; defer symlink.close(io); - const stat = try symlink.stat(); + const stat = try symlink.stat(io); try testing.expectEqual(File.Kind.sym_link, stat.kind); } }.impl); @@ -396,7 +396,7 @@ test "openDirAbsolute" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - const tmp_ino = (try tmp.dir.stat()).inode; + const tmp_ino = (try tmp.dir.stat(io)).inode; try tmp.dir.makeDir("subdir"); const sub_path = try tmp.dir.realpathAlloc(testing.allocator, "subdir"); @@ -406,7 +406,7 @@ test "openDirAbsolute" { var tmp_sub = try fs.openDirAbsolute(sub_path, .{}); defer tmp_sub.close(io); - const sub_ino = (try tmp_sub.stat()).inode; + const sub_ino = (try tmp_sub.stat(io)).inode; { // Can open sub_path + ".." @@ -416,7 +416,7 @@ test "openDirAbsolute" { var dir = try fs.openDirAbsolute(dir_path, .{}); defer dir.close(io); - const ino = (try dir.stat()).inode; + const ino = (try dir.stat(io)).inode; try testing.expectEqual(tmp_ino, ino); } @@ -428,7 +428,7 @@ test "openDirAbsolute" { var dir = try fs.openDirAbsolute(dir_path, .{}); defer dir.close(io); - const ino = (try dir.stat()).inode; + const ino = (try dir.stat(io)).inode; try testing.expectEqual(sub_ino, ino); } @@ -440,7 +440,7 @@ test "openDirAbsolute" { var dir = try fs.openDirAbsolute(dir_path, .{}); defer dir.close(io); - const ino = (try dir.stat()).inode; + const ino = (try dir.stat(io)).inode; try testing.expectEqual(tmp_ino, ino); } } @@ -849,7 +849,7 @@ test "directory operations on files" { // ensure the file still exists and is a file as a sanity check file = try ctx.dir.openFile(io, test_file_name, .{}); - const stat = try file.stat(); + const stat = try file.stat(io); try testing.expectEqual(File.Kind.file, stat.kind); file.close(io); } @@ -1151,7 +1151,7 @@ test "renameAbsolute" { // ensure the file was renamed try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(io, test_file_name, .{})); file = try tmp_dir.dir.openFile(io, renamed_test_file_name, .{}); - const stat = try file.stat(); + const stat = try file.stat(io); try testing.expectEqual(File.Kind.file, stat.kind); file.close(io); @@ -2099,17 +2099,17 @@ test "chmod" { const file = try tmp.dir.createFile(io, "test_file", .{ .mode = 0o600 }); defer file.close(io); - try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat()).mode & 0o7777); + try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat(io)).mode & 0o7777); try file.chmod(0o644); - try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777); + try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat(io)).mode & 0o7777); try tmp.dir.makeDir("test_dir"); var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); defer dir.close(io); try dir.chmod(0o700); - try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat()).mode & 0o7777); + try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat(io)).mode & 0o7777); } test "chown" { diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index 167c6ef398af07d168f152555ab88c094e789fb0..3c9a313a4ac5d45a1074ffa2f20054e700dd648d 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -2655,7 +2655,7 @@ test "fallocate" { const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); defer file.close(io); - try testing.expectEqual(@as(u64, 0), (try file.stat()).size); + try testing.expectEqual(@as(u64, 0), (try file.stat(io)).size); const len: u64 = 65536; const sqe = try ring.fallocate(0xaaaaaaaa, file.handle, 0, 0, len); @@ -2681,7 +2681,7 @@ test "fallocate" { .flags = 0, }, cqe); - try testing.expectEqual(len, (try file.stat()).size); + try testing.expectEqual(len, (try file.stat(io)).size); } test "statx" { @@ -2702,7 +2702,7 @@ test "statx" { const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); defer file.close(io); - try testing.expectEqual(@as(u64, 0), (try file.stat()).size); + try testing.expectEqual(@as(u64, 0), (try file.stat(io)).size); try file.writeAll("foobar"); diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index d7cfb4e1380997b84f00a3df85b68533048ad35d..cfafebe82648a79dda36c2e1564bc6004ff6a352 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -21,7 +21,7 @@ test "fallocate" { const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); defer file.close(io); - try expect((try file.stat()).size == 0); + try expect((try file.stat(io)).size == 0); const len: i64 = 65536; switch (linux.errno(linux.fallocate(file.handle, 0, 0, len))) { @@ -31,7 +31,7 @@ test "fallocate" { else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), } - try expect((try file.stat()).size == len); + try expect((try file.stat(io)).size == len); } test "getpid" { diff --git a/src/Compilation.zig b/src/Compilation.zig index ef01fbb07cc5462b7e5ab327b6e50578c04ecaff..3a48705880a7e57bfe1cf727fd5cdaf2665cd2f6 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -5367,7 +5367,7 @@ fn docsCopyModule( }); }; defer file.close(io); - const stat = try file.stat(); + const stat = try file.stat(io); var file_reader: Io.File.Reader = .initSize(file, io, &buffer, stat.size); archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime) catch |err| { diff --git a/src/Zcu.zig b/src/Zcu.zig index d2634a8962553b774989c37ffe6b5e2f986a0219..1f2fe89236a02d4f2f83125a63a46533a4091dee 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -1080,7 +1080,7 @@ pub const File = struct { }; defer f.close(io); - const stat = f.stat() catch |err| switch (err) { + const stat = f.stat(io) catch |err| switch (err) { error.Streaming => { // Since `file.stat` is populated, this was previously a file stream; since it is // now not a file stream, it must have changed. diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 9a75b2096ef15c5a6703615e8aed764532fe4020..74196705c389fc42a52ee0dc601155ee72e8235f 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -98,7 +98,7 @@ pub fn updateFile( }; defer source_file.close(io); - const stat = try source_file.stat(); + const stat = try source_file.stat(io); const want_local_cache = switch (file.path.root) { .none, .local_cache => true, @@ -2470,7 +2470,7 @@ fn updateEmbedFileInner( }; defer file.close(io); - const stat: Cache.File.Stat = .fromFs(try file.stat()); + const stat: Cache.File.Stat = .fromFs(try file.stat(io)); if (ef.val != .none) { const old_stat = ef.stat; diff --git a/src/fmt.zig b/src/fmt.zig index dc67619d54675f1067862bcc7521eac197188449..1a1e5298e22ef43a39344f7e44ea511ce6e3b93b 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -188,7 +188,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! error.IsDir => dir: { var dir = try Io.Dir.cwd().openDir(io, file_path, .{}); defer dir.close(io); - break :dir try dir.stat(); + break :dir try dir.stat(io); }, else => |e| return e, }; @@ -227,7 +227,7 @@ fn fmtPathDir( var dir = try parent_dir.openDir(io, parent_sub_path, .{ .iterate = true }); defer dir.close(io); - const stat = try dir.stat(); + const stat = try dir.stat(io); if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; var dir_it = dir.iterate(); @@ -266,7 +266,7 @@ fn fmtPathFile( var file_closed = false; errdefer if (!file_closed) source_file.close(io); - const stat = try source_file.stat(); + const stat = try source_file.stat(io); if (stat.kind == .directory) return error.IsDir; diff --git a/src/link.zig b/src/link.zig index 073ec632c6eeb9f9ebcaba0739daece26195b936..06d18ec2d5073212ca028f68a00d024832de7166 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1112,10 +1112,10 @@ pub const File = struct { fn loadGnuLdScript(base: *File, path: Path, parent_query: UnresolvedInput.Query, file: Io.File) anyerror!void { const comp = base.comp; + const io = comp.io; const diags = &comp.link_diags; const gpa = comp.gpa; - const io = comp.io; - const stat = try file.stat(); + const stat = try file.stat(io); const size = std.math.cast(u32, stat.size) orelse return error.FileTooBig; const buf = try gpa.alloc(u8, size); defer gpa.free(buf); @@ -2180,7 +2180,7 @@ fn resolvePathInputLib( // Appears to be an ELF or archive file. return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query); } - const stat = file.stat() catch |err| + const stat = file.stat(io) catch |err| fatal("failed to stat {f}: {s}", .{ test_path, @errorName(err) }); const size = std.math.cast(u32, stat.size) orelse fatal("{f}: linker script too big", .{test_path}); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 53812a37ec473046d14212b33011997021719bac..13a624a295b753746c2ab94c8ae1b4d112ee79eb 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -742,7 +742,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { .dso_exact => @panic("TODO"), .object => |obj| try parseObject(self, obj), .archive => |obj| try parseArchive(gpa, diags, &self.file_handles, &self.files, target, debug_fmt_strip, default_sym_version, &self.objects, obj, is_static_lib), - .dso => |dso| try parseDso(gpa, diags, dso, &self.shared_objects, &self.files, target), + .dso => |dso| try parseDso(gpa, io, diags, dso, &self.shared_objects, &self.files, target), } } @@ -1136,6 +1136,7 @@ fn parseArchive( fn parseDso( gpa: Allocator, + io: Io, diags: *Diags, dso: link.Input.Dso, shared_objects: *std.StringArrayHashMapUnmanaged(File.Index), @@ -1147,7 +1148,7 @@ fn parseDso( const handle = dso.file; - const stat = Stat.fromFs(try handle.stat()); + const stat = Stat.fromFs(try handle.stat(io)); var header = try SharedObject.parseHeader(gpa, diags, dso.path, handle, stat, target); defer header.deinit(gpa); diff --git a/src/link/Elf/Archive.zig b/src/link/Elf/Archive.zig index a9961bf8f90e15f2c220c9054312f3517c5b5c8f..0f786c1a47a381f27e9ab6f03293cb539b98fb4b 100644 --- a/src/link/Elf/Archive.zig +++ b/src/link/Elf/Archive.zig @@ -1,3 +1,21 @@ +const Archive = @This(); + +const std = @import("std"); +const Io = std.Io; +const assert = std.debug.assert; +const elf = std.elf; +const fs = std.fs; +const log = std.log.scoped(.link); +const mem = std.mem; +const Path = std.Build.Cache.Path; +const Allocator = std.mem.Allocator; + +const Diags = @import("../../link.zig").Diags; +const Elf = @import("../Elf.zig"); +const File = @import("file.zig").File; +const Object = @import("Object.zig"); +const StringTable = @import("../StringTable.zig"); + objects: []const Object, /// '\n'-delimited strtab: []const u8, @@ -10,6 +28,7 @@ pub fn deinit(a: *Archive, gpa: Allocator) void { pub fn parse( gpa: Allocator, + io: Io, diags: *Diags, file_handles: *const std.ArrayList(File.Handle), path: Path, @@ -25,7 +44,7 @@ pub fn parse( pos += magic_buffer.len; } - const size = (try handle.stat()).size; + const size = (try handle.stat(io)).size; var objects: std.ArrayList(Object) = .empty; defer objects.deinit(gpa); @@ -120,7 +139,7 @@ pub fn setArHdr(opts: struct { @memset(mem.asBytes(&hdr), 0x20); { - var writer: std.Io.Writer = .fixed(&hdr.ar_name); + var writer: Io.Writer = .fixed(&hdr.ar_name); switch (opts.name) { .symtab => writer.print("{s}", .{elf.SYM64NAME}) catch unreachable, .strtab => writer.print("//", .{}) catch unreachable, @@ -133,7 +152,7 @@ pub fn setArHdr(opts: struct { hdr.ar_gid[0] = '0'; hdr.ar_mode[0] = '0'; { - var writer: std.Io.Writer = .fixed(&hdr.ar_size); + var writer: Io.Writer = .fixed(&hdr.ar_size); writer.print("{d}", .{opts.size}) catch unreachable; } hdr.ar_fmag = elf.ARFMAG.*; @@ -206,7 +225,7 @@ pub const ArSymtab = struct { ar: ArSymtab, elf_file: *Elf, - fn default(f: Format, writer: *std.Io.Writer) std.Io.Writer.Error!void { + fn default(f: Format, writer: *Io.Writer) Io.Writer.Error!void { const ar = f.ar; const elf_file = f.elf_file; for (ar.symtab.items, 0..) |entry, i| { @@ -261,7 +280,7 @@ pub const ArStrtab = struct { try writer.writeAll(ar.buffer.items); } - pub fn format(ar: ArStrtab, writer: *std.Io.Writer) std.Io.Writer.Error!void { + pub fn format(ar: ArStrtab, writer: *Io.Writer) Io.Writer.Error!void { try writer.print("{f}", .{std.ascii.hexEscape(ar.buffer.items, .lower)}); } }; @@ -277,19 +296,3 @@ pub const ArState = struct { /// Total size of the contributing object (excludes ar_hdr). size: u64 = 0, }; - -const std = @import("std"); -const assert = std.debug.assert; -const elf = std.elf; -const fs = std.fs; -const log = std.log.scoped(.link); -const mem = std.mem; -const Path = std.Build.Cache.Path; -const Allocator = std.mem.Allocator; - -const Diags = @import("../../link.zig").Diags; -const Archive = @This(); -const Elf = @import("../Elf.zig"); -const File = @import("file.zig").File; -const Object = @import("Object.zig"); -const StringTable = @import("../StringTable.zig"); diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index c0dde4176ad41b7fdaa0778c50a7d3cde093230a..7dacfb3a625e0b8449275bc29340d474ce49fe52 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -122,13 +122,14 @@ pub fn parse( pub fn parseCommon( self: *Object, gpa: Allocator, + io: Io, diags: *Diags, path: Path, handle: Io.File, target: *const std.Target, ) !void { const offset = if (self.archive) |ar| ar.offset else 0; - const file_size = (try handle.stat()).size; + const file_size = (try handle.stat(io)).size; const header_buffer = try Elf.preadAllAlloc(gpa, handle, offset, @sizeOf(elf.Elf64_Ehdr)); defer gpa.free(header_buffer); @@ -1122,9 +1123,11 @@ pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, elf_file: *Elf } pub fn updateArSize(self: *Object, elf_file: *Elf) !void { + const comp = elf_file.base.comp; + const io = comp.io; self.output_ar_state.size = if (self.archive) |ar| ar.size else size: { const handle = elf_file.fileHandle(self.file_handle); - break :size (try handle.stat()).size; + break :size (try handle.stat(io)).size; }; } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 0f6127e10e89b2b6c66f9dbb96c9f103c40c8325..78e035e2adaacc5c6ac2234cb4afdafff9f70f2b 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -925,6 +925,7 @@ fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u const comp = self.base.comp; const gpa = comp.gpa; + const io = comp.io; const abs_path = try std.fs.path.resolvePosix(gpa, &.{ comp.dirs.cwd, @@ -934,7 +935,7 @@ fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u errdefer gpa.free(abs_path); const file = self.getFileHandle(handle_index); - const stat = try file.stat(); + const stat = try file.stat(io); const mtime = stat.mtime.toSeconds(); const index: File.Index = @intCast(try self.files.addOne(gpa)); self.files.set(index, .{ .object = .{ diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index d1962412c428db30b9f30482fc6abd2ed1257f1e..122a4085339bcd21566b11267d53bb59413c3876 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -6,6 +6,7 @@ pub fn deinit(self: *Archive, allocator: Allocator) void { pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void { const comp = macho_file.base.comp; + const io = comp.io; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -14,7 +15,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File const handle = macho_file.getFileHandle(handle_index); const offset = if (fat_arch) |ar| ar.offset else 0; - const end_pos = if (fat_arch) |ar| offset + ar.size else (try handle.stat()).size; + const end_pos = if (fat_arch) |ar| offset + ar.size else (try handle.stat(io)).size; var pos: usize = offset + SARMAG; while (true) { diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 5fc77fe7634b7b9de1bcb6a37f6b8a006780e0a1..1a1799f551dc843d189080aa1a38ac777a69b4a9 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -1689,9 +1689,11 @@ pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, macho_file: *M } pub fn updateArSize(self: *Object, macho_file: *MachO) !void { + const comp = macho_file.base.comp; + const io = comp.io; self.output_ar_state.size = if (self.in_archive) |ar| ar.size else size: { const file = macho_file.getFileHandle(self.file_handle); - break :size (try file.stat()).size; + break :size (try file.stat(io)).size; }; } diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index 0f42442640368c0ac6902c493f01776a74c446f5..e9f78a8ef2fd6d04d652332cab95049c82c6a293 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -22,7 +22,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat const path = positionals.items[0].path().?; const in_file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) }); - const stat = in_file.stat() catch |err| + const stat = in_file.stat(io) catch |err| return diags.fail("failed to stat {f}: {s}", .{ path, @errorName(err) }); const amt = in_file.copyRangeAll(0, macho_file.base.file.?, 0, stat.size) catch |err| return diags.fail("failed to copy range of file {f}: {s}", .{ path, @errorName(err) }); diff --git a/src/link/tapi.zig b/src/link/tapi.zig index fff25b75447336c9c756a0421fbf9a88e23f3e00..046f43eae2cd8b012c0140a8f2eaee45e3ea7010 100644 --- a/src/link/tapi.zig +++ b/src/link/tapi.zig @@ -139,9 +139,9 @@ pub const LibStub = struct { /// Typed contents of the tbd file. inner: []Tbd, - pub fn loadFromFile(allocator: Allocator, file: Io.File) TapiError!LibStub { + pub fn loadFromFile(allocator: Allocator, io: Io, file: Io.File) TapiError!LibStub { const filesize = blk: { - const stat = file.stat() catch break :blk std.math.maxInt(u32); + const stat = file.stat(io) catch break :blk std.math.maxInt(u32); break :blk @min(stat.size, std.math.maxInt(u32)); }; const source = try allocator.alloc(u8, filesize); -- 2.54.0 From 181ac08459f8d4001c504330ee66037135e56908 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 15:22:55 -0800 Subject: [PATCH 018/195] std.Io.Threaded: add missing fileLength implementation --- lib/std/Io/Threaded.zig | 55 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 124f886515a1dccca1310c4ca38d14babfd632cc..6a650f8ccf5e8164bc3f208df538229d1d3adbba 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -708,6 +708,7 @@ pub fn io(t: *Threaded) Io { .dirSetTimestampsNow = dirSetTimestampsNow, .fileStat = fileStat, + .fileLength = fileLength, .fileClose = fileClose, .fileWriteStreaming = fileWriteStreaming, .fileWritePositional = fileWritePositional, @@ -832,6 +833,7 @@ pub fn ioBasic(t: *Threaded) Io { .dirSetTimestampsNow = dirSetTimestampsNow, .fileStat = fileStat, + .fileLength = fileLength, .fileClose = fileClose, .fileWriteStreaming = fileWriteStreaming, .fileWritePositional = fileWritePositional, @@ -1940,6 +1942,51 @@ fn dirStatPathWasi( } } +fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + + if (native_os == .linux) { + const current_thread = Thread.getCurrent(t); + const linux = std.os.linux; + + try current_thread.beginSyscall(); + while (true) { + var statx = std.mem.zeroes(linux.Statx); + switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_SIZE, &statx))) { + .SUCCESS => { + current_thread.endSyscall(); + return statx.size; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .LOOP => |err| return errnoBug(err), + .NAMETOOLONG => |err| return errnoBug(err), + .NOENT => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + .NOTDIR => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } else if (is_windows) { + // TODO call NtQueryInformationFile and ask for only the size instead of "all" + } + + const stat = try fileStat(ioBasic(t), file); + return stat.size; +} + const fileStat = switch (native_os) { .linux => fileStatLinux, .windows => fileStatWindows, @@ -5479,7 +5526,7 @@ const fileReadStreaming = switch (native_os) { else => fileReadStreamingPosix, }; -fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: [][]u8) File.Reader.Error!usize { +fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5567,7 +5614,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: [][]u8) File. } } -fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: [][]u8) File.Reader.Error!usize { +fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5596,7 +5643,7 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: [][]u8) Fil } } -fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize { +fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5698,7 +5745,7 @@ const fileReadPositional = switch (native_os) { else => fileReadPositionalPosix, }; -fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize { +fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); -- 2.54.0 From 6f46570958af8ae27308eb4a9470e05f33aaa522 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 15:23:18 -0800 Subject: [PATCH 019/195] link.MachO: update parallel hasher to std.Io --- lib/std/Build/Cache.zig | 66 ++++++++++++++------------------ lib/std/Build/WebServer.zig | 6 +-- lib/std/Io.zig | 4 +- lib/std/Io/File.zig | 34 +++++++++++++++- lib/std/fs/test.zig | 14 +++---- src/link/MachO/CodeSignature.zig | 17 ++++---- src/link/MachO/hasher.zig | 24 +++++------- src/link/MachO/uuid.zig | 16 ++++---- 8 files changed, 100 insertions(+), 81 deletions(-) diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 0176d71a1caba1e7512c98e4e1c0ea45a7c95489..dab1926f534006dcc600b1da329e3d941edb2960 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -800,7 +800,7 @@ pub const Manifest = struct { } var actual_digest: BinDigest = undefined; - hashFile(this_file, &actual_digest) catch |err| { + hashFile(io, this_file, &actual_digest) catch |err| { self.diagnostic = .{ .file_read = .{ .file_index = idx, .err = err, @@ -908,9 +908,11 @@ pub const Manifest = struct { } } - fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: Io.File) !void { + fn populateFileHashHandle(self: *Manifest, ch_file: *File, io_file: Io.File) !void { const io = self.cache.io; - const actual_stat = try handle.stat(io); + const gpa = self.cache.gpa; + + const actual_stat = try io_file.stat(io); ch_file.stat = .{ .size = actual_stat.size, .mtime = actual_stat.mtime, @@ -924,19 +926,17 @@ pub const Manifest = struct { } if (ch_file.max_file_size) |max_file_size| { - if (ch_file.stat.size > max_file_size) { - return error.FileTooBig; - } + if (ch_file.stat.size > max_file_size) return error.FileTooBig; - const contents = try self.cache.gpa.alloc(u8, @as(usize, @intCast(ch_file.stat.size))); - errdefer self.cache.gpa.free(contents); + // Hash while reading from disk, to keep the contents in the cpu + // cache while doing hashing. + const contents = try gpa.alloc(u8, @intCast(ch_file.stat.size)); + errdefer gpa.free(contents); - // Hash while reading from disk, to keep the contents in the cpu cache while - // doing hashing. var hasher = hasher_init; var off: usize = 0; while (true) { - const bytes_read = try handle.pread(contents[off..], off); + const bytes_read = try io_file.readPositional(io, &.{contents[off..]}, off); if (bytes_read == 0) break; hasher.update(contents[off..][0..bytes_read]); off += bytes_read; @@ -945,7 +945,7 @@ pub const Manifest = struct { ch_file.contents = contents; } else { - try hashFile(handle, &ch_file.bin_digest); + try hashFile(io, io_file, &ch_file.bin_digest); } self.hash.hasher.update(&ch_file.bin_digest); @@ -1169,13 +1169,11 @@ pub const Manifest = struct { fn downgradeToSharedLock(self: *Manifest) !void { if (!self.have_exclusive_lock) return; + const io = self.cache.io; - // WASI does not currently support flock, so we bypass it here. - // TODO: If/when flock is supported on WASI, this check should be removed. - // See https://github.com/WebAssembly/wasi-filesystem/issues/2 - if (builtin.os.tag != .wasi or std.process.can_spawn or !builtin.single_threaded) { + if (std.process.can_spawn or !builtin.single_threaded) { const manifest_file = self.manifest_file.?; - try manifest_file.downgradeLock(); + try manifest_file.downgradeLock(io); } self.have_exclusive_lock = false; @@ -1184,16 +1182,14 @@ pub const Manifest = struct { fn upgradeToExclusiveLock(self: *Manifest) error{CacheCheckFailed}!bool { if (self.have_exclusive_lock) return false; assert(self.manifest_file != null); + const io = self.cache.io; - // WASI does not currently support flock, so we bypass it here. - // TODO: If/when flock is supported on WASI, this check should be removed. - // See https://github.com/WebAssembly/wasi-filesystem/issues/2 - if (builtin.os.tag != .wasi or std.process.can_spawn or !builtin.single_threaded) { + if (std.process.can_spawn or !builtin.single_threaded) { const manifest_file = self.manifest_file.?; // Here we intentionally have a period where the lock is released, in case there are // other processes holding a shared lock. - manifest_file.unlock(); - manifest_file.lock(.exclusive) catch |err| { + manifest_file.unlock(io); + manifest_file.lock(io, .exclusive) catch |err| { self.diagnostic = .{ .manifest_lock = err }; return error.CacheCheckFailed; }; @@ -1206,12 +1202,8 @@ pub const Manifest = struct { /// The `Manifest` remains safe to deinit. /// Don't forget to call `writeManifest` before this! pub fn toOwnedLock(self: *Manifest) Lock { - const lock: Lock = .{ - .manifest_file = self.manifest_file.?, - }; - - self.manifest_file = null; - return lock; + defer self.manifest_file = null; + return .{ .manifest_file = self.manifest_file.? }; } /// Releases the manifest file and frees any memory the Manifest was using. @@ -1223,7 +1215,7 @@ pub const Manifest = struct { if (self.manifest_file) |file| { if (builtin.os.tag == .windows) { // See Lock.release for why this is required on Windows - file.unlock(); + file.unlock(io); } file.close(io); @@ -1308,15 +1300,15 @@ pub fn writeSmallFile(dir: Io.Dir, sub_path: []const u8, data: []const u8) !void } } -fn hashFile(file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.PReadError!void { - var buf: [1024]u8 = undefined; +fn hashFile(io: Io, file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.ReadPositionalError!void { + var buffer: [2048]u8 = undefined; var hasher = hasher_init; - var off: u64 = 0; + var offset: u64 = 0; while (true) { - const bytes_read = try file.pread(&buf, off); - if (bytes_read == 0) break; - hasher.update(buf[0..bytes_read]); - off += bytes_read; + const n = try file.readPositional(io, &.{&buffer}, offset); + if (n == 0) break; + hasher.update(buffer[0..n]); + offset += n; } hasher.final(bin_digest); } diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 472e87b05a156f73b830a9e45b53f0e7a1ca2e1f..162d17f070458cfd4134e55fcb377d90505ee2ba 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -218,9 +218,9 @@ pub fn finishBuild(ws: *WebServer, opts: struct { else => {}, } if (@bitSizeOf(usize) != 64) { - // Current implementation depends on posix.mmap()'s second parameter, `length: usize`, - // being compatible with `std.fs.getEndPos() u64`'s return value. This is not the case - // on 32-bit platforms. + // Current implementation depends on posix.mmap()'s second + // parameter, `length: usize`, being compatible with file system's + // u64 return value. This is not the case on 32-bit platforms. // Affects or affected by issues #5185, #22523, and #22464. std.process.fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); } diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 17fb75fe5412a9b233711e15e1c8e24401c65d90..9d6dcef615bf726470004240b11682711c371b7e 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -692,9 +692,9 @@ pub const VTable = struct { fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize, fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize, /// Returns 0 on end of stream. - fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.Reader.Error!usize, + fileReadStreaming: *const fn (?*anyopaque, File, data: []const []u8) File.Reader.Error!usize, /// Returns 0 on end of stream. - fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize, + fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize, fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void, fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void, fileSync: *const fn (?*anyopaque, File) File.SyncError!void, diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index a9b4775772d796ef9e798fd57e47ae53a5479d65..5e89025478cec160c1ecd168db1afd8461ad03dd 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -466,13 +466,21 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { pub const ReadPositionalError = Reader.Error || error{Unseekable}; -pub fn readPositional(file: File, io: Io, buffer: [][]u8, offset: u64) ReadPositionalError!usize { +/// Returns 0 on end of stream. +/// +/// See also: +/// * `reader` +pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) ReadPositionalError!usize { return io.vtable.fileReadPositional(io.userdata, file, buffer, offset); } pub const WritePositionalError = Writer.Error || error{Unseekable}; -pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize { +/// Returns 0 on end of stream. +/// +/// See also: +/// * `writer` +pub fn writePositional(file: File, io: Io, buffer: []const []const u8, offset: u64) WritePositionalError!usize { return io.vtable.fileWritePositional(io.userdata, file, buffer, offset); } @@ -501,13 +509,35 @@ pub const WriteFilePositionalError = Writer.WriteFileError || error{Unseekable}; /// /// Positional is more threadsafe, since the global seek position is not /// affected. +/// +/// See also: +/// * `readerStreaming` pub fn reader(file: File, io: Io, buffer: []u8) Reader { return .init(file, io, buffer); } +/// Equivalent to creating a positional reader and reading multiple times to fill `buffer`. +/// +/// Returns number of bytes read into `buffer`. If less than `buffer.len`, end of file occurred. +/// +/// See also: +/// * `reader` +pub fn readPositionalAll(file: File, io: Io, buffer: []u8, offset: u64) ReadPositionalError!usize { + var index: usize = 0; + while (index != buffer.len) { + const amt = try file.readPositional(io, &.{buffer[index..]}, offset + index); + if (amt == 0) break; + index += amt; + } + return index; +} + /// Positional is more threadsafe, since the global seek position is not /// affected, but when such syscalls are not available, preemptively /// initializing in streaming mode skips a failed syscall. +/// +/// See also: +/// * `reader` pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader { return .initStreaming(file, io, buffer); } diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 7b12ba42713db6fffc201679fd5fb4a87b3eac89..e044a97620f5608bd5a618fe919665cec32323d9 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1455,7 +1455,7 @@ test "writev, readv" { try writer.interface.writeVecAll(&write_vecs); try writer.interface.flush(); - try testing.expectEqual(@as(u64, line1.len + line2.len), try src_file.getEndPos()); + try testing.expectEqual(@as(u64, line1.len + line2.len), try src_file.length(io)); var reader = writer.moveToReader(io); try reader.seekTo(0); @@ -1486,7 +1486,7 @@ test "pwritev, preadv" { try writer.seekTo(16); try writer.interface.writeVecAll(&lines); try writer.interface.flush(); - try testing.expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.getEndPos()); + try testing.expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.length(io)); var reader = writer.moveToReader(io); try reader.seekTo(16); @@ -1511,13 +1511,13 @@ test "setEndPos" { const f = try tmp.dir.openFile(io, file_name, .{ .mode = .read_write }); defer f.close(io); - const initial_size = try f.getEndPos(); + const initial_size = try f.length(io); var buffer: [32]u8 = undefined; var reader = f.reader(io, &.{}); { try f.setEndPos(initial_size); - try testing.expectEqual(initial_size, try f.getEndPos()); + try testing.expectEqual(initial_size, try f.length(io)); try reader.seekTo(0); try testing.expectEqual(initial_size, try reader.interface.readSliceShort(&buffer)); try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]); @@ -1526,7 +1526,7 @@ test "setEndPos" { { const larger = initial_size + 4; try f.setEndPos(larger); - try testing.expectEqual(larger, try f.getEndPos()); + try testing.expectEqual(larger, try f.length(io)); try reader.seekTo(0); try testing.expectEqual(larger, try reader.interface.readSliceShort(&buffer)); try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]); @@ -1535,14 +1535,14 @@ test "setEndPos" { { const smaller = initial_size - 5; try f.setEndPos(smaller); - try testing.expectEqual(smaller, try f.getEndPos()); + try testing.expectEqual(smaller, try f.length(io)); try reader.seekTo(0); try testing.expectEqual(smaller, try reader.interface.readSliceShort(&buffer)); try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]); } try f.setEndPos(0); - try testing.expectEqual(0, try f.getEndPos()); + try testing.expectEqual(0, try f.length(io)); try reader.seekTo(0); try testing.expectEqual(0, try reader.interface.readSliceShort(&buffer)); } diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 814faf234a86ee79c8bbcf50138d9854a529342d..ec516d4af0f1f33bb5038432da06803666436ac3 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -12,7 +12,7 @@ const Sha256 = std.crypto.hash.sha2.Sha256; const Allocator = std.mem.Allocator; const trace = @import("../../tracy.zig").trace; -const Hasher = @import("hasher.zig").ParallelHasher; +const ParallelHasher = @import("hasher.zig").ParallelHasher; const MachO = @import("../MachO.zig"); const hash_size = Sha256.digest_length; @@ -268,7 +268,9 @@ pub fn writeAdhocSignature( const tracy = trace(@src()); defer tracy.end(); - const allocator = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const gpa = comp.gpa; + const io = comp.io; var header: macho.SuperBlob = .{ .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE, @@ -276,7 +278,7 @@ pub fn writeAdhocSignature( .count = 0, }; - var blobs = std.array_list.Managed(Blob).init(allocator); + var blobs = std.array_list.Managed(Blob).init(gpa); defer blobs.deinit(); self.code_directory.inner.execSegBase = opts.exec_seg_base; @@ -286,13 +288,12 @@ pub fn writeAdhocSignature( const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size)); - try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages); + try self.code_directory.code_slots.ensureTotalCapacityPrecise(gpa, total_pages); self.code_directory.code_slots.items.len = total_pages; self.code_directory.inner.nCodeSlots = total_pages; // Calculate hash for each page (in file) and write it to the buffer - var hasher = Hasher(Sha256){ .allocator = allocator, .io = macho_file.base.comp.io }; - try hasher.hash(opts.file, self.code_directory.code_slots.items, .{ + try ParallelHasher(Sha256).hash(gpa, io, opts.file, self.code_directory.code_slots.items, .{ .chunk_size = self.page_size, .max_file_size = opts.file_size, }); @@ -304,7 +305,7 @@ pub fn writeAdhocSignature( var hash: [hash_size]u8 = undefined; if (self.requirements) |*req| { - var a: std.Io.Writer.Allocating = .init(allocator); + var a: std.Io.Writer.Allocating = .init(gpa); defer a.deinit(); try req.write(&a.writer); Sha256.hash(a.written(), &hash, .{}); @@ -316,7 +317,7 @@ pub fn writeAdhocSignature( } if (self.entitlements) |*ents| { - var a: std.Io.Writer.Allocating = .init(allocator); + var a: std.Io.Writer.Allocating = .init(gpa); defer a.deinit(); try ents.write(&a.writer); Sha256.hash(a.written(), &hash, .{}); diff --git a/src/link/MachO/hasher.zig b/src/link/MachO/hasher.zig index 8cf53071c811a787d5f6b867110828ebd294d5a4..2e0129d24028280ae41e50373d64a5bb7a8ca3bd 100644 --- a/src/link/MachO/hasher.zig +++ b/src/link/MachO/hasher.zig @@ -1,5 +1,6 @@ const std = @import("std"); const Io = std.Io; +const assert = std.debug.assert; const Allocator = std.mem.Allocator; const trace = @import("../../tracy.zig").trace; @@ -8,20 +9,15 @@ pub fn ParallelHasher(comptime Hasher: type) type { const hash_size = Hasher.digest_length; return struct { - allocator: Allocator, - io: std.Io, - - pub fn hash(self: Self, file: Io.File, out: [][hash_size]u8, opts: struct { + pub fn hash(self: Self, io: Io, file: Io.File, out: [][hash_size]u8, opts: struct { chunk_size: u64 = 0x4000, max_file_size: ?u64 = null, }) !void { const tracy = trace(@src()); defer tracy.end(); - const io = self.io; - const file_size = blk: { - const file_size = opts.max_file_size orelse try file.getEndPos(); + const file_size = opts.max_file_size orelse try file.length(io); break :blk std.math.cast(usize, file_size) orelse return error.Overflow; }; const chunk_size = std.math.cast(usize, opts.chunk_size) orelse return error.Overflow; @@ -29,12 +25,12 @@ pub fn ParallelHasher(comptime Hasher: type) type { const buffer = try self.allocator.alloc(u8, chunk_size * out.len); defer self.allocator.free(buffer); - const results = try self.allocator.alloc(Io.File.PReadError!usize, out.len); + const results = try self.allocator.alloc(Io.File.ReadPositionalError!usize, out.len); defer self.allocator.free(results); { - var group: std.Io.Group = .init; - errdefer group.cancel(io); + var group: Io.Group = .init; + defer group.cancel(io); for (out, results, 0..) |*out_buf, *result, i| { const fstart = i * chunk_size; @@ -42,7 +38,7 @@ pub fn ParallelHasher(comptime Hasher: type) type { file_size - fstart else chunk_size; - group.async(io, worker, .{ + group.async(worker, .{ file, fstart, buffer[fstart..][0..fsize], @@ -61,11 +57,9 @@ pub fn ParallelHasher(comptime Hasher: type) type { fstart: usize, buffer: []u8, out: *[hash_size]u8, - err: *Io.File.PReadError!usize, + err: *Io.File.ReadPositionalError!usize, ) void { - const tracy = trace(@src()); - defer tracy.end(); - err.* = file.preadAll(buffer, fstart); + err.* = file.readPositionalAll(buffer, fstart); Hasher.hash(buffer, out, .{}); } diff --git a/src/link/MachO/uuid.zig b/src/link/MachO/uuid.zig index 4d8eac7523ba3e34cf61fa5a47a32b13a6f4852a..a75799d01ec4571de97fdd39db73f8aa1b2a75bb 100644 --- a/src/link/MachO/uuid.zig +++ b/src/link/MachO/uuid.zig @@ -4,7 +4,7 @@ const Md5 = std.crypto.hash.Md5; const trace = @import("../../tracy.zig").trace; const Compilation = @import("../../Compilation.zig"); -const Hasher = @import("hasher.zig").ParallelHasher; +const ParallelHasher = @import("hasher.zig").ParallelHasher; /// Calculates Md5 hash of each chunk in parallel and then hashes all Md5 hashes to produce /// the final digest. @@ -16,21 +16,23 @@ pub fn calcUuid(comp: *const Compilation, file: Io.File, file_size: u64, out: *[ const tracy = trace(@src()); defer tracy.end(); + const gpa = comp.gpa; + const io = comp.io; + const chunk_size: usize = 1024 * 1024; const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow; const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks; - const hashes = try comp.gpa.alloc([Md5.digest_length]u8, actual_num_chunks); - defer comp.gpa.free(hashes); + const hashes = try gpa.alloc([Md5.digest_length]u8, actual_num_chunks); + defer gpa.free(hashes); - var hasher = Hasher(Md5){ .allocator = comp.gpa, .io = comp.io }; - try hasher.hash(file, hashes, .{ + try ParallelHasher(Md5).hash(gpa, io, file, hashes, .{ .chunk_size = chunk_size, .max_file_size = file_size, }); - const final_buffer = try comp.gpa.alloc(u8, actual_num_chunks * Md5.digest_length); - defer comp.gpa.free(final_buffer); + const final_buffer = try gpa.alloc(u8, actual_num_chunks * Md5.digest_length); + defer gpa.free(final_buffer); for (hashes, 0..) |hash, i| { @memcpy(final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash); -- 2.54.0 From 7f5bb118d4d90e2b883ee66e17592ac8d7808ac8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 15:42:42 -0800 Subject: [PATCH 020/195] std.Io: make all the close functions batched --- lib/std/Build/Step/WriteFile.zig | 8 +------- lib/std/Io.zig | 6 +++--- lib/std/Io/Dir.zig | 6 +++++- lib/std/Io/File.zig | 6 +++++- lib/std/Io/Threaded.zig | 18 +++++++++--------- lib/std/Io/net.zig | 8 ++++++-- 6 files changed, 29 insertions(+), 23 deletions(-) diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index 353c85fecfd360079850f6394bf7442e2c2b1f1d..85dc9b3fa232d8f0065578d8594f84c00246682f 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -208,7 +208,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const open_dir_cache = try arena.alloc(Io.Dir, write_file.directories.items.len); var open_dirs_count: usize = 0; - defer closeDirs(open_dir_cache[0..open_dirs_count]); + defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]); for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| { man.hash.addBytes(dir.sub_path); @@ -341,9 +341,3 @@ fn make(step: *Step, options: Step.MakeOptions) !void { try step.writeManifest(&man); } - -fn closeDirs(io: Io, dirs: []Io.Dir) void { - var group: Io.Group = .init; - defer group.wait(); - for (dirs) |d| group.async(Io.Dir.close, .{ d, io }); -} diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 9d6dcef615bf726470004240b11682711c371b7e..ad0764ebe1272c57888463d67be1055cfac465ef 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -671,7 +671,7 @@ pub const VTable = struct { dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File, dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File, dirOpenDir: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.OpenError!Dir, - dirClose: *const fn (?*anyopaque, Dir) void, + dirClose: *const fn (?*anyopaque, []const Dir) void, dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize, dirRealPath: *const fn (?*anyopaque, Dir, path_name: []const u8, out_buffer: []u8) Dir.RealPathError!usize, dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void, @@ -686,7 +686,7 @@ pub const VTable = struct { fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, fileLength: *const fn (?*anyopaque, File) File.LengthError!u64, - fileClose: *const fn (?*anyopaque, File) void, + fileClose: *const fn (?*anyopaque, []const File) void, fileWriteStreaming: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize) File.Writer.Error!usize, fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize, fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize, @@ -729,7 +729,7 @@ pub const VTable = struct { netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize, netWrite: *const fn (?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize, netWriteFile: *const fn (?*anyopaque, net.Socket.Handle, header: []const u8, *Io.File.Reader, Io.Limit) net.Stream.Writer.WriteFileError!usize, - netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void, + netClose: *const fn (?*anyopaque, handle: []const net.Socket.Handle) void, netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 9189a4c60939a4c9d3c15cd09354d143d617b65f..58f81cbb90452daec55e609830a32a20373ec0c7 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -446,7 +446,11 @@ pub fn openDirAbsolute(io: Io, absolute_path: []const u8, options: OpenOptions) } pub fn close(dir: Dir, io: Io) void { - return io.vtable.dirClose(io.userdata, dir); + return io.vtable.dirClose(io.userdata, (&dir)[0..1]); +} + +pub fn closeMany(io: Io, dirs: []const Dir) void { + return io.vtable.dirClose(io.userdata, dirs); } /// Opens a file for reading or writing, without attempting to create a new file. diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 5e89025478cec160c1ecd168db1afd8461ad03dd..302d2a8ca518bbe2010c5900b9ce16f9ec152301 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -252,7 +252,11 @@ pub const OpenError = error{ } || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; pub fn close(file: File, io: Io) void { - return io.vtable.fileClose(io.userdata, file); + return io.vtable.fileClose(io.userdata, (&file)[0..1]); +} + +pub fn closeMany(io: Io, files: []const File) void { + return io.vtable.fileClose(io.userdata, files); } pub const SyncError = error{ diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 6a650f8ccf5e8164bc3f208df538229d1d3adbba..4ee1d5db53adb1af018c42e6a2fd111019be27f0 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3243,10 +3243,10 @@ const MakeOpenDirAccessMaskWOptions = struct { create_disposition: u32, }; -fn dirClose(userdata: ?*anyopaque, dir: Dir) void { +fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - posix.close(dir.handle); + for (dirs) |dir| posix.close(dir.handle); } const dirRealPath = switch (native_os) { @@ -5515,10 +5515,10 @@ fn dirOpenDirWasi( } } -fn fileClose(userdata: ?*anyopaque, file: File) void { +fn fileClose(userdata: ?*anyopaque, files: []const File) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - posix.close(file.handle); + for (files) |file| posix.close(file.handle); } const fileReadStreaming = switch (native_os) { @@ -9084,18 +9084,18 @@ fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void { i.* += 1; } -fn netClose(userdata: ?*anyopaque, handle: net.Socket.Handle) void { +fn netClose(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; switch (native_os) { - .windows => closeSocketWindows(handle), - else => posix.close(handle), + .windows => for (handles) |handle| closeSocketWindows(handle), + else => for (handles) |handle| posix.close(handle), } } -fn netCloseUnavailable(userdata: ?*anyopaque, handle: net.Socket.Handle) void { +fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void { _ = userdata; - _ = handle; + _ = handles; unreachable; // How you gonna close something that was impossible to open? } diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 2a56f3a4ab748cc2147a7f315e893cb786e1a34e..8b8728b6f107cab5dc98d56e336abb9504431716 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -1043,7 +1043,11 @@ pub const Socket = struct { /// Leaves `address` in a valid state. pub fn close(s: *const Socket, io: Io) void { - io.vtable.netClose(io.userdata, s.handle); + io.vtable.netClose(io.userdata, (&s.handle)[0..1]); + } + + pub fn closeMany(io: Io, sockets: []const Socket) void { + io.vtable.netClose(io.userdata, sockets); } pub const SendError = error{ @@ -1184,7 +1188,7 @@ pub const Stream = struct { const max_iovecs_len = 8; pub fn close(s: *const Stream, io: Io) void { - io.vtable.netClose(io.userdata, s.socket.handle); + io.vtable.netClose(io.userdata, (&s.socket.handle)[0..1]); } pub const Reader = struct { -- 2.54.0 From 9ccd68de0b79c3723bd11071fd836bc24ff25b33 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 16:13:51 -0800 Subject: [PATCH 021/195] std: move abort and exit from posix into process and delete the unit tests that called fork() no forking allowed in the std lib, including unit tests, except to implement child process spawning. --- lib/compiler/translate-c/main.zig | 2 +- lib/std/Build.zig | 9 ++- lib/std/Build/Cache/Path.zig | 4 +- lib/std/Build/Step/ConfigHeader.zig | 3 +- lib/std/Build/Step/ObjCopy.zig | 3 +- lib/std/Build/Step/Run.zig | 7 +- lib/std/Build/Step/UpdateSourceFiles.zig | 2 +- lib/std/Build/Step/WriteFile.zig | 6 +- lib/std/debug.zig | 12 ++-- lib/std/fs/test.zig | 45 +++++++----- lib/std/os/linux/IoUring.zig | 26 ------- lib/std/posix.zig | 87 ------------------------ lib/std/posix/test.zig | 67 ------------------ lib/std/process.zig | 86 ++++++++++++++++++++++- lib/std/process/Child.zig | 2 +- lib/std/start.zig | 6 +- lib/std/tar.zig | 16 ++--- lib/std/zip.zig | 6 +- src/Compilation.zig | 10 +-- src/Package/Fetch.zig | 5 +- src/main.zig | 11 +-- 21 files changed, 167 insertions(+), 248 deletions(-) diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index d0a873fd785b8d0a4758c8ca78fee9c3999d1f37..d1401450329d80fe73bd7f50549365e95633aab0 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -253,7 +253,7 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration if (d.output_name) |path| blk: { if (std.mem.eql(u8, path, "-")) break :blk; if (std.fs.path.dirname(path)) |dirname| { - Io.Dir.cwd().makePath(dirname) catch |err| + Io.Dir.cwd().makePath(io, dirname) catch |err| return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); } out_file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| { diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 3d0e8b8dbc219c27243ad0df6882728d3d56f0f1..1eff8813e5c6623b53a3efac60bf2e3cc620cc30 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1706,7 +1706,7 @@ pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.MakeError || Io.Di var src_file = cwd.createFile(io, dest_path, .{}) catch |err| switch (err) { error.FileNotFound => blk: { if (fs.path.dirname(dest_path)) |dirname| { - try cwd.makePath(dirname); + try cwd.makePath(io, dirname); } break :blk try cwd.createFile(io, dest_path, .{}); }, @@ -2634,13 +2634,12 @@ pub const InstallDir = union(enum) { /// source of API breakage in the future, so keep that in mind when using this /// function. pub fn makeTempPath(b: *Build) []const u8 { + const io = b.graph.io; const rand_int = std.crypto.random.int(u64); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM"); - b.cache_root.handle.makePath(tmp_dir_sub_path) catch |err| { - std.debug.print("unable to make tmp path '{s}': {s}\n", .{ - result_path, @errorName(err), - }); + b.cache_root.handle.makePath(io, tmp_dir_sub_path) catch |err| { + std.debug.print("unable to make tmp path '{s}': {t}\n", .{ result_path, err }); }; return result_path; } diff --git a/lib/std/Build/Cache/Path.zig b/lib/std/Build/Cache/Path.zig index 93e0c0d7924d6cd9f68a512c064f2b0b4d5610f3..941948a9cd13fa71387fde847eaab89c1f211586 100644 --- a/lib/std/Build/Cache/Path.zig +++ b/lib/std/Build/Cache/Path.zig @@ -128,14 +128,14 @@ pub fn access(p: Path, sub_path: []const u8, flags: Io.Dir.AccessOptions) !void return p.root_dir.handle.access(joined_path, flags); } -pub fn makePath(p: Path, sub_path: []const u8) !void { +pub fn makePath(p: Path, io: Io, sub_path: []const u8) !void { var buf: [fs.max_path_bytes]u8 = undefined; const joined_path = if (p.sub_path.len == 0) sub_path else p: { break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ p.sub_path, sub_path, }) catch return error.NameTooLong; }; - return p.root_dir.handle.makePath(joined_path); + return p.root_dir.handle.makePath(io, joined_path); } pub fn toString(p: Path, allocator: Allocator) Allocator.Error![]u8 { diff --git a/lib/std/Build/Step/ConfigHeader.zig b/lib/std/Build/Step/ConfigHeader.zig index ea7d9d99ff4e404475a5852b15a0b2d6cb207e9b..f377959610925ac15eea5b5b48df0dfa70064877 100644 --- a/lib/std/Build/Step/ConfigHeader.zig +++ b/lib/std/Build/Step/ConfigHeader.zig @@ -184,6 +184,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const gpa = b.allocator; const arena = b.allocator; + const io = b.graph.io; var man = b.graph.cache.obtain(); defer man.deinit(); @@ -257,7 +258,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const sub_path = b.pathJoin(&.{ "o", &digest, config_header.include_path }); const sub_path_dirname = std.fs.path.dirname(sub_path).?; - b.cache_root.handle.makePath(sub_path_dirname) catch |err| { + b.cache_root.handle.makePath(io, sub_path_dirname) catch |err| { return step.fail("unable to make path '{f}{s}': {s}", .{ b.cache_root, sub_path_dirname, @errorName(err), }); diff --git a/lib/std/Build/Step/ObjCopy.zig b/lib/std/Build/Step/ObjCopy.zig index 4aa1c0a9dca73359cb6fdbe2c0b0d6bb69316d6f..b81f59b9a1888d454b2d69c0b37c601ca55d3a84 100644 --- a/lib/std/Build/Step/ObjCopy.zig +++ b/lib/std/Build/Step/ObjCopy.zig @@ -143,6 +143,7 @@ pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath { fn make(step: *Step, options: Step.MakeOptions) !void { const prog_node = options.progress_node; const b = step.owner; + const io = b.graph.io; const objcopy: *ObjCopy = @fieldParentPtr("step", step); try step.singleUnchangingWatchInput(objcopy.input_file); @@ -176,7 +177,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const cache_path = "o" ++ fs.path.sep_str ++ digest; const full_dest_path = try b.cache_root.join(b.allocator, &.{ cache_path, objcopy.basename }); const full_dest_path_debug = try b.cache_root.join(b.allocator, &.{ cache_path, b.fmt("{s}.debug", .{objcopy.basename}) }); - b.cache_root.handle.makePath(cache_path) catch |err| { + b.cache_root.handle.makePath(io, cache_path) catch |err| { return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) }); }; diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index af6bc20438d1681636489c2356996d26d44db8d8..54e77bd6146f41f8cb28afb4ee0455cc93193ddb 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -973,7 +973,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .output_directory => output_sub_path, else => unreachable, }; - b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { + b.cache_root.handle.makePath(io, output_sub_dir_path) catch |err| { return step.fail("unable to make path '{f}{s}': {s}", .{ b.cache_root, output_sub_dir_path, @errorName(err), }); @@ -1005,7 +1005,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .output_directory => output_sub_path, else => unreachable, }; - b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { + b.cache_root.handle.makePath(io, output_sub_dir_path) catch |err| { return step.fail("unable to make path '{f}{s}': {s}", .{ b.cache_root, output_sub_dir_path, @errorName(err), }); @@ -1241,6 +1241,7 @@ fn runCommand( const b = step.owner; const arena = b.allocator; const gpa = options.gpa; + const io = b.graph.io; const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null; @@ -1470,7 +1471,7 @@ fn runCommand( const sub_path = b.pathJoin(&output_components); const sub_path_dirname = fs.path.dirname(sub_path).?; - b.cache_root.handle.makePath(sub_path_dirname) catch |err| { + b.cache_root.handle.makePath(io, sub_path_dirname) catch |err| { return step.fail("unable to make path '{f}{s}': {s}", .{ b.cache_root, sub_path_dirname, @errorName(err), }); diff --git a/lib/std/Build/Step/UpdateSourceFiles.zig b/lib/std/Build/Step/UpdateSourceFiles.zig index f5d95182e925deb3782b6a59cc0879554856ffbb..44c6ae1ed4c73d3f19b0ee76e208252dd46356c3 100644 --- a/lib/std/Build/Step/UpdateSourceFiles.zig +++ b/lib/std/Build/Step/UpdateSourceFiles.zig @@ -78,7 +78,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { var any_miss = false; for (usf.output_source_files.items) |output_source_file| { if (fs.path.dirname(output_source_file.sub_path)) |dirname| { - b.build_root.handle.makePath(dirname) catch |err| { + b.build_root.handle.makePath(io, dirname) catch |err| { return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err }); }; } diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index 85dc9b3fa232d8f0065578d8594f84c00246682f..21346959e7b78c455aadc35928394d1c1d42effe 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -268,7 +268,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { for (write_file.files.items) |file| { if (fs.path.dirname(file.sub_path)) |dirname| { - cache_dir.makePath(dirname) catch |err| { + cache_dir.makePath(io, dirname) catch |err| { return step.fail("unable to make path '{f}{s}{c}{s}': {t}", .{ b.cache_root, cache_path, fs.path.sep, dirname, err, }); @@ -303,7 +303,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const dest_dirname = dir.sub_path; if (dest_dirname.len != 0) { - cache_dir.makePath(dest_dirname) catch |err| { + cache_dir.makePath(io, dest_dirname) catch |err| { return step.fail("unable to make path '{f}{s}{c}{s}': {s}", .{ b.cache_root, cache_path, fs.path.sep, dest_dirname, @errorName(err), }); @@ -318,7 +318,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const src_entry_path = try src_dir_path.join(arena, entry.path); const dest_path = b.pathJoin(&.{ dest_dirname, entry.path }); switch (entry.kind) { - .directory => try cache_dir.makePath(dest_path), + .directory => try cache_dir.makePath(io, dest_path), .file => { const prev_status = Io.Dir.updateFile( src_entry_path.root_dir.handle, diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 5df0eef2d560ed86b3020df96841f32bf9f7332e..7ede172d86f428f886a2b1f0aac54881fd2858f0 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -522,7 +522,7 @@ pub fn defaultPanic( } @trap(); }, - .cuda, .amdhsa => std.posix.abort(), + .cuda, .amdhsa => std.process.abort(), .plan9 => { var status: [std.os.plan9.ERRMAX]u8 = undefined; const len = @min(msg.len, status.len - 1); @@ -575,12 +575,13 @@ pub fn defaultPanic( // A panic happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). - File.stderr().writeStreamingAll("aborting due to recursive panic\n") catch {}; + const stderr, _ = lockStderrWriter(&.{}); + stderr.writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. } - posix.abort(); + std.process.abort(); } /// Must be called only after adding 1 to `panicking`. There are three callsites. @@ -1596,7 +1597,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex // A segfault happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). - File.stderr().writeAll("aborting due to recursive panic\n") catch {}; + const stderr, _ = lockStderrWriter(&.{}); + stderr().writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. } @@ -1604,7 +1606,7 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex // We cannot allow the signal handler to return because when it runs the original instruction // again, the memory may be mapped and undefined behavior would occur rather than repeating // the segfault. So we simply abort here. - posix.abort(); + std.process.abort(); } pub fn dumpStackPointerAddr(prefix: []const u8) void { diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index e044a97620f5608bd5a618fe919665cec32323d9..bc845004190a85ac3f2175b13a420a19c194eb42 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -674,7 +674,7 @@ test "Dir.Iterator but dir is deleted during iteration" { var iterator = subdir.iterate(); // Create something to iterate over within the subdir - try tmp.dir.makePath("subdir" ++ fs.path.sep_str ++ "b"); + try tmp.dir.makePath(io, "subdir" ++ fs.path.sep_str ++ "b"); // Then, before iterating, delete the directory that we're iterating. // This is a contrived reproduction, but this could happen outside of the program, in another thread, etc. @@ -1196,7 +1196,7 @@ test "deleteTree does not follow symlinks" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makePath("b"); + try tmp.dir.makePath(io, "b"); { var a = try tmp.dir.makeOpenPath("a", .{}); defer a.close(io); @@ -1211,6 +1211,8 @@ test "deleteTree does not follow symlinks" { } test "deleteTree on a symlink" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1223,7 +1225,7 @@ test "deleteTree on a symlink" { try tmp.dir.access("file", .{}); // Symlink to a directory - try tmp.dir.makePath("dir"); + try tmp.dir.makePath(io, "dir"); try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true }); try tmp.dir.deleteTree("dirlink"); @@ -1238,7 +1240,7 @@ test "makePath, put some files in it, deleteTree" { const allocator = ctx.arena.allocator(); const dir_path = try ctx.transformPath("os_test_tmp"); - try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); + try ctx.dir.makePath(io, try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); try ctx.dir.writeFile(.{ .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), .data = "nonsense", @@ -1261,7 +1263,7 @@ test "makePath, put some files in it, deleteTreeMinStackSize" { const allocator = ctx.arena.allocator(); const dir_path = try ctx.transformPath("os_test_tmp"); - try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); + try ctx.dir.makePath(io, try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); try ctx.dir.writeFile(.{ .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), .data = "nonsense", @@ -1280,21 +1282,25 @@ test "makePath, put some files in it, deleteTreeMinStackSize" { test "makePath in a directory that no longer exists" { if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); try tmp.parent_dir.deleteTree(&tmp.sub_path); - try testing.expectError(error.FileNotFound, tmp.dir.makePath("sub-path")); + try testing.expectError(error.FileNotFound, tmp.dir.makePath(io, "sub-path")); } test "makePath but sub_path contains pre-existing file" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); try tmp.dir.makeDir("foo"); try tmp.dir.writeFile(.{ .sub_path = "foo/bar", .data = "" }); - try testing.expectError(error.NotDir, tmp.dir.makePath("foo/bar/baz")); + try testing.expectError(error.NotDir, tmp.dir.makePath(io, "foo/bar/baz")); } fn expectDir(io: Io, dir: Dir, path: []const u8) !void { @@ -1314,7 +1320,7 @@ test "makepath existing directories" { try tmpA.makeDir("B"); const testPath = "A" ++ fs.path.sep_str ++ "B" ++ fs.path.sep_str ++ "C"; - try tmp.dir.makePath(testPath); + try tmp.dir.makePath(io, testPath); try expectDir(io, tmp.dir, testPath); } @@ -1328,7 +1334,7 @@ test "makepath through existing valid symlink" { try tmp.dir.makeDir("realfolder"); try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{}); - try tmp.dir.makePath("working-symlink" ++ fs.path.sep_str ++ "in-realfolder"); + try tmp.dir.makePath(io, "working-symlink" ++ fs.path.sep_str ++ "in-realfolder"); try expectDir(io, tmp.dir, "realfolder" ++ fs.path.sep_str ++ "in-realfolder"); } @@ -1344,7 +1350,7 @@ test "makepath relative walks" { }); defer testing.allocator.free(relPath); - try tmp.dir.makePath(relPath); + try tmp.dir.makePath(io, relPath); // How .. is handled is different on Windows than non-Windows switch (native_os) { @@ -1383,7 +1389,7 @@ test "makepath ignores '.'" { }); defer testing.allocator.free(expectedPath); - try tmp.dir.makePath(dotPath); + try tmp.dir.makePath(io, dotPath); try expectDir(io, tmp.dir, expectedPath); } @@ -1550,10 +1556,11 @@ test "setEndPos" { test "access file" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const dir_path = try ctx.transformPath("os_test_tmp"); const file_path = try ctx.transformPath("os_test_tmp" ++ fs.path.sep_str ++ "file.txt"); - try ctx.dir.makePath(dir_path); + try ctx.dir.makePath(io, dir_path); try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{})); try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "" }); @@ -1569,7 +1576,7 @@ test "sendfile" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makePath("os_test_tmp"); + try tmp.dir.makePath(io, "os_test_tmp"); var dir = try tmp.dir.openDir(io, "os_test_tmp", .{}); defer dir.close(io); @@ -1616,7 +1623,7 @@ test "sendfile with buffered data" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makePath("os_test_tmp"); + try tmp.dir.makePath(io, "os_test_tmp"); var dir = try tmp.dir.openDir(io, "os_test_tmp", .{}); defer dir.close(io); @@ -1894,7 +1901,7 @@ test "walker" { }); for (expected_paths.keys()) |key| { - try tmp.dir.makePath(key); + try tmp.dir.makePath(io, key); } var walker = try tmp.dir.walk(testing.allocator); @@ -1956,7 +1963,7 @@ test "selective walker, skip entries that start with ." { }); for (paths_to_create) |path| { - try tmp.dir.makePath(path); + try tmp.dir.makePath(io, path); } var walker = try tmp.dir.walkSelectively(testing.allocator); @@ -1991,6 +1998,8 @@ test "selective walker, skip entries that start with ." { } test "walker without fully iterating" { + const io = testing.io; + var tmp = tmpDir(.{ .iterate = true }); defer tmp.cleanup(); @@ -2000,8 +2009,8 @@ test "walker without fully iterating" { // Create 2 directories inside the tmp directory, but then only iterate once before breaking. // This ensures that walker doesn't try to close the initial directory when not fully iterating. - try tmp.dir.makePath("a"); - try tmp.dir.makePath("b"); + try tmp.dir.makePath(io, "a"); + try tmp.dir.makePath(io, "b"); var num_walked: usize = 0; while (try walker.next()) |_| { diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index 3c9a313a4ac5d45a1074ffa2f20054e700dd648d..72b8369d681dc766572a4c041705ba656a18965e 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -4090,32 +4090,6 @@ test "openat_direct/close_direct" { try ring.unregister_files(); } -test "waitid" { - try skipKernelLessThan(.{ .major = 6, .minor = 7, .patch = 0 }); - - var ring = IoUring.init(16, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const pid = try posix.fork(); - if (pid == 0) { - posix.exit(7); - } - - var siginfo: posix.siginfo_t = undefined; - _ = try ring.waitid(0, .PID, pid, &siginfo, posix.W.EXITED, 0); - - try testing.expectEqual(1, try ring.submit()); - - const cqe_waitid = try ring.copy_cqe(); - try testing.expectEqual(0, cqe_waitid.res); - try testing.expectEqual(pid, siginfo.fields.common.first.piduid.pid); - try testing.expectEqual(7, siginfo.fields.common.second.sigchld.status); -} - /// For use in tests. Returns SkipZigTest if kernel version is less than required. inline fn skipKernelLessThan(required: std.SemanticVersion) !void { if (!is_linux) return error.SkipZigTest; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index f4aa970413345a03470ec7a2dc30193d493e895d..42429a49931a95ec9f3b07188649f252efcfaec4 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -615,66 +615,6 @@ fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void { } } -/// Causes abnormal process termination. -/// If linking against libc, this calls the abort() libc function. Otherwise -/// it raises SIGABRT followed by SIGKILL and finally lo -/// Invokes the current signal handler for SIGABRT, if any. -pub fn abort() noreturn { - @branchHint(.cold); - // MSVCRT abort() sometimes opens a popup window which is undesirable, so - // even when linking libc on Windows we use our own abort implementation. - // See https://github.com/ziglang/zig/issues/2071 for more details. - if (native_os == .windows) { - if (builtin.mode == .Debug and windows.peb().BeingDebugged != 0) { - @breakpoint(); - } - windows.ntdll.RtlExitUserProcess(3); - } - if (!builtin.link_libc and native_os == .linux) { - // The Linux man page says that the libc abort() function - // "first unblocks the SIGABRT signal", but this is a footgun - // for user-defined signal handlers that want to restore some state in - // some program sections and crash in others. - // So, the user-installed SIGABRT handler is run, if present. - raise(.ABRT) catch {}; - - // Disable all signal handlers. - const filledset = linux.sigfillset(); - sigprocmask(SIG.BLOCK, &filledset, null); - - // Only one thread may proceed to the rest of abort(). - if (!builtin.single_threaded) { - const global = struct { - var abort_entered: bool = false; - }; - while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .seq_cst, .seq_cst)) |_| {} - } - - // Install default handler so that the tkill below will terminate. - const sigact = Sigaction{ - .handler = .{ .handler = SIG.DFL }, - .mask = sigemptyset(), - .flags = 0, - }; - sigaction(.ABRT, &sigact, null); - - _ = linux.tkill(linux.gettid(), .ABRT); - - var sigabrtmask = sigemptyset(); - sigaddset(&sigabrtmask, .ABRT); - sigprocmask(SIG.UNBLOCK, &sigabrtmask, null); - - // Beyond this point should be unreachable. - @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0; - raise(.KILL) catch {}; - exit(127); // Pid 1 might not be signalled in some containers. - } - switch (native_os) { - .uefi, .wasi, .emscripten, .cuda, .amdhsa => @trap(), - else => system.abort(), - } -} - pub const RaiseError = UnexpectedError; pub fn raise(sig: SIG) RaiseError!void { @@ -715,33 +655,6 @@ pub fn kill(pid: pid_t, sig: SIG) KillError!void { } } -/// Exits all threads of the program with the specified status code. -pub fn exit(status: u8) noreturn { - if (builtin.link_libc) { - std.c.exit(status); - } - if (native_os == .windows) { - windows.ntdll.RtlExitUserProcess(status); - } - if (native_os == .wasi) { - wasi.proc_exit(status); - } - if (native_os == .linux and !builtin.single_threaded) { - linux.exit_group(status); - } - if (native_os == .uefi) { - const uefi = std.os.uefi; - // exit() is only available if exitBootServices() has not been called yet. - // This call to exit should not fail, so we catch-ignore errors. - if (uefi.system_table.boot_services) |bs| { - bs.exit(uefi.handle, @enumFromInt(status), null) catch {}; - } - // If we can't exit, reboot the system instead. - uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), null); - } - system.exit(status); -} - pub const ReadError = std.Io.File.Reader.Error; /// Returns the number of bytes that were read, which can be less than diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index dc63be6e14ab496ee997a3b2609dcbd5ef69f370..169c5a70c2bf43c33bffbe99da259f69cdabbaa7 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -667,73 +667,6 @@ test "writev longer than IOV_MAX" { try testing.expectEqual(@as(usize, posix.IOV_MAX), amt); } -test "POSIX file locking with fcntl" { - if (native_os == .windows or native_os == .wasi) { - // Not POSIX. - return error.SkipZigTest; - } - - if (true) { - // https://github.com/ziglang/zig/issues/11074 - return error.SkipZigTest; - } - - const io = testing.io; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - // Create a temporary lock file - var file = try tmp.dir.createFile(io, "lock", .{ .read = true }); - defer file.close(io); - try file.setEndPos(2); - const fd = file.handle; - - // Place an exclusive lock on the first byte, and a shared lock on the second byte: - var struct_flock = std.mem.zeroInit(posix.Flock, .{ .type = posix.F.WRLCK }); - _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)); - struct_flock.start = 1; - struct_flock.type = posix.F.RDLCK; - _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)); - - // Check the locks in a child process: - const pid = try posix.fork(); - if (pid == 0) { - // child expects be denied the exclusive lock: - struct_flock.start = 0; - struct_flock.type = posix.F.WRLCK; - try expectError(error.Locked, posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock))); - // child expects to get the shared lock: - struct_flock.start = 1; - struct_flock.type = posix.F.RDLCK; - _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)); - // child waits for the exclusive lock in order to test deadlock: - struct_flock.start = 0; - struct_flock.type = posix.F.WRLCK; - _ = try posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock)); - // child exits without continuing: - posix.exit(0); - } else { - // parent waits for child to get shared lock: - std.Thread.sleep(1 * std.time.ns_per_ms); - // parent expects deadlock when attempting to upgrade the shared lock to exclusive: - struct_flock.start = 1; - struct_flock.type = posix.F.WRLCK; - try expectError(error.DeadLock, posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock))); - // parent releases exclusive lock: - struct_flock.start = 0; - struct_flock.type = posix.F.UNLCK; - _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)); - // parent releases shared lock: - struct_flock.start = 1; - struct_flock.type = posix.F.UNLCK; - _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)); - // parent waits for child: - const result = posix.waitpid(pid, 0); - try expect(result.status == 0 * 256); - } -} - test "rename smoke test" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .windows) return error.SkipZigTest; diff --git a/lib/std/process.zig b/lib/std/process.zig index 5d60190c2b743a885908da39e740a919f02a6148..f7ecf5fdc240eeb3847b94a0b2ede6193acdaeb4 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -16,8 +16,6 @@ const unicode = std.unicode; const max_path_bytes = std.fs.max_path_bytes; pub const Child = @import("process/Child.zig"); -pub const abort = posix.abort; -pub const exit = posix.exit; pub const changeCurDir = posix.chdir; pub const changeCurDirZ = posix.chdirZ; @@ -2208,3 +2206,87 @@ pub const OpenExecutableError = File.OpenError || ExecutablePathError || File.Lo pub fn openExecutable(io: Io, flags: File.OpenFlags) OpenExecutableError!File { return io.vtable.processExecutableOpen(io.userdata, flags); } + +/// Causes abnormal process termination. +/// +/// If linking against libc, this calls `std.c.abort`. Otherwise it raises +/// SIGABRT followed by SIGKILL. +/// +/// Invokes the current signal handler for SIGABRT, if any. +pub fn abort() noreturn { + @branchHint(.cold); + // MSVCRT abort() sometimes opens a popup window which is undesirable, so + // even when linking libc on Windows we use our own abort implementation. + // See https://github.com/ziglang/zig/issues/2071 for more details. + if (native_os == .windows) { + if (builtin.mode == .Debug and windows.peb().BeingDebugged != 0) { + @breakpoint(); + } + windows.ntdll.RtlExitUserProcess(3); + } + if (!builtin.link_libc and native_os == .linux) { + // The Linux man page says that the libc abort() function + // "first unblocks the SIGABRT signal", but this is a footgun + // for user-defined signal handlers that want to restore some state in + // some program sections and crash in others. + // So, the user-installed SIGABRT handler is run, if present. + posix.raise(.ABRT) catch {}; + + // Disable all signal handlers. + const filledset = std.os.linux.sigfillset(); + posix.sigprocmask(posix.SIG.BLOCK, &filledset, null); + + // Only one thread may proceed to the rest of abort(). + if (!builtin.single_threaded) { + const global = struct { + var abort_entered: bool = false; + }; + while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .seq_cst, .seq_cst)) |_| {} + } + + // Install default handler so that the tkill below will terminate. + const sigact: posix.Sigaction = .{ + .handler = .{ .handler = posix.SIG.DFL }, + .mask = posix.sigemptyset(), + .flags = 0, + }; + posix.sigaction(.ABRT, &sigact, null); + + _ = std.os.linux.tkill(std.os.linux.gettid(), .ABRT); + + var sigabrtmask = posix.sigemptyset(); + posix.sigaddset(&sigabrtmask, .ABRT); + posix.sigprocmask(posix.SIG.UNBLOCK, &sigabrtmask, null); + + // Beyond this point should be unreachable. + @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0; + posix.raise(.KILL) catch {}; + exit(127); // Pid 1 might not be signalled in some containers. + } + switch (native_os) { + .uefi, .wasi, .emscripten, .cuda, .amdhsa => @trap(), + else => posix.system.abort(), + } +} + +/// Exits all threads of the program with the specified status code. +pub fn exit(status: u8) noreturn { + if (builtin.link_libc) { + std.c.exit(status); + } else switch (native_os) { + .windows => windows.ntdll.RtlExitUserProcess(status), + .wasi => std.os.wasi.proc_exit(status), + .linux => if (!builtin.single_threaded) std.os.linux.exit_group(status), + .uefi => { + const uefi = std.os.uefi; + // exit() is only available if exitBootServices() has not been called yet. + // This call to exit should not fail, so we catch-ignore errors. + if (uefi.system_table.boot_services) |bs| { + bs.exit(uefi.handle, @enumFromInt(status), null) catch {}; + } + // If we can't exit, reboot the system instead. + uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), null); + }, + else => posix.system.exit(status), + } +} diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 33faeef0615cff9a1a173e4dbbcee7af0fd9fcf0..4521cd511fcf83eb336985547c9d0fb5c168b86b 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -1050,7 +1050,7 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { // The _exit(2) function does nothing but make the exit syscall, unlike exit(3) std.c._exit(1); } - posix.exit(1); + posix.system.exit(1); } fn writeIntFd(fd: i32, value: ErrInt) !void { diff --git a/lib/std/start.zig b/lib/std/start.zig index a5bec4123108e08654e03b727d2b0e7aeeb67398..64a1c17175dc83fee7d03fb3d47d4cea17592a3b 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -110,7 +110,7 @@ fn main2() callconv(.c) c_int { } fn _start2() callconv(.withStackAlign(.c, 1)) noreturn { - std.posix.exit(callMain()); + std.process.exit(callMain()); } fn spirvMain2() callconv(.kernel) void { @@ -118,7 +118,7 @@ fn spirvMain2() callconv(.kernel) void { } fn wWinMainCRTStartup2() callconv(.c) noreturn { - std.posix.exit(callMain()); + std.process.exit(callMain()); } //////////////////////////////////////////////////////////////////////////////// @@ -627,7 +627,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn { for (slice) |func| func(); } - std.posix.exit(callMainWithArgs(argc, argv, envp)); + std.process.exit(callMainWithArgs(argc, argv, envp)); } fn expandStackSize(phdrs: []elf.Phdr) void { diff --git a/lib/std/tar.zig b/lib/std/tar.zig index 8a0bbb342f5fdd5b7b413042a0326bad36f70ab5..b2a5306458a5e8cea48b5fd78a6172b5c6cfd231 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -606,7 +606,7 @@ pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOp switch (file.kind) { .directory => { if (file_name.len > 0 and !options.exclude_empty_directories) { - try dir.makePath(file_name); + try dir.makePath(io, file_name); } }, .file => { @@ -625,7 +625,7 @@ pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOp }, .sym_link => { const link_name = file.link_name; - createDirAndSymlink(dir, link_name, file_name) catch |err| { + createDirAndSymlink(io, dir, link_name, file_name) catch |err| { const d = options.diagnostics orelse return error.UnableToCreateSymLink; try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{ .code = err, @@ -642,7 +642,7 @@ fn createDirAndFile(io: Io, dir: Io.Dir, file_name: []const u8, mode: Io.File.Mo const fs_file = dir.createFile(io, file_name, .{ .exclusive = true, .mode = mode }) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { - try dir.makePath(dir_name); + try dir.makePath(io, dir_name); return try dir.createFile(io, file_name, .{ .exclusive = true, .mode = mode }); } } @@ -652,11 +652,11 @@ fn createDirAndFile(io: Io, dir: Io.Dir, file_name: []const u8, mode: Io.File.Mo } // Creates a symbolic link at path `file_name` which points to `link_name`. -fn createDirAndSymlink(dir: Io.Dir, link_name: []const u8, file_name: []const u8) !void { +fn createDirAndSymlink(io: Io, dir: Io.Dir, link_name: []const u8, file_name: []const u8) !void { dir.symLink(link_name, file_name, .{}) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { - try dir.makePath(dir_name); + try dir.makePath(io, dir_name); return try dir.symLink(link_name, file_name, .{}); } } @@ -885,15 +885,15 @@ test "create file and symlink" { file = try createDirAndFile(io, root.dir, "a/b/c/file2", default_mode); file.close(io); - createDirAndSymlink(root.dir, "a/b/c/file2", "symlink1") catch |err| { + createDirAndSymlink(io, root.dir, "a/b/c/file2", "symlink1") catch |err| { // On Windows when developer mode is not enabled if (err == error.AccessDenied) return error.SkipZigTest; return err; }; - try createDirAndSymlink(root.dir, "../../../file1", "d/e/f/symlink2"); + try createDirAndSymlink(io, root.dir, "../../../file1", "d/e/f/symlink2"); // Danglink symlnik, file created later - try createDirAndSymlink(root.dir, "../../../g/h/i/file4", "j/k/l/symlink3"); + try createDirAndSymlink(io, root.dir, "../../../g/h/i/file4", "j/k/l/symlink3"); file = try createDirAndFile(io, root.dir, "g/h/i/file4", default_mode); file.close(io); } diff --git a/lib/std/zip.zig b/lib/std/zip.zig index 9d0884709236d22ae85d0a47b0ab95ad53e2aded..acb3fc65abf191afeb505a465b1e66a58743728b 100644 --- a/lib/std/zip.zig +++ b/lib/std/zip.zig @@ -464,6 +464,8 @@ pub const Iterator = struct { filename_buf: []u8, dest: Io.Dir, ) !void { + const io = stream.io; + if (filename_buf.len < self.filename_len) return error.ZipInsufficientBuffer; switch (self.compression_method) { @@ -552,12 +554,10 @@ pub const Iterator = struct { if (filename[filename.len - 1] == '/') { if (self.uncompressed_size != 0) return error.ZipBadDirectorySize; - try dest.makePath(filename[0 .. filename.len - 1]); + try dest.makePath(io, filename[0 .. filename.len - 1]); return; } - const io = stream.io; - const out_file = blk: { if (std.fs.path.dirname(filename)) |dirname| { var parent_dir = try dest.makeOpenPath(dirname, .{}); diff --git a/src/Compilation.zig b/src/Compilation.zig index 3a48705880a7e57bfe1cf727fd5cdaf2665cd2f6..280d34cdbffe632cf203b5c50bb528b559df6ed9 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3180,7 +3180,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE const s = fs.path.sep_str; const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); const o_sub_path = "o" ++ s ++ hex_digest; - renameTmpIntoCache(comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| { + renameTmpIntoCache(io, comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| { return comp.setMiscFailure( .rename_results, "failed to rename compilation results ('{f}{s}') into local cache ('{f}{s}'): {t}", @@ -3399,17 +3399,19 @@ fn flush( /// implementation at the bottom of this function. /// This function is only called when CacheMode is `whole`. fn renameTmpIntoCache( + io: Io, cache_directory: Cache.Directory, tmp_dir_sub_path: []const u8, o_sub_path: []const u8, ) !void { var seen_eaccess = false; while (true) { - fs.rename( + Io.Dir.rename( cache_directory.handle, tmp_dir_sub_path, cache_directory.handle, o_sub_path, + io, ) catch |err| switch (err) { // On Windows, rename fails with `AccessDenied` rather than `PathAlreadyExists`. // See https://github.com/ziglang/zig/issues/8362 @@ -3427,7 +3429,7 @@ fn renameTmpIntoCache( continue; }, error.FileNotFound => { - try cache_directory.handle.makePath("o"); + try cache_directory.handle.makePath(io, "o"); continue; }, else => |e| return e, @@ -5816,7 +5818,7 @@ pub fn translateC( const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest; if (comp.verbose_cimport) log.info("renaming {s} to {s}", .{ tmp_sub_path, o_sub_path }); - try renameTmpIntoCache(comp.dirs.local_cache, tmp_sub_path, o_sub_path); + try renameTmpIntoCache(io, comp.dirs.local_cache, tmp_sub_path, o_sub_path); return .{ .digest = bin_digest, diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index a651107bf5f8b42ccb7868a9ccdd7c709a95024a..e54dcb9914374f0f1af4e1caf9d2fc2348a827b3 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1414,6 +1414,7 @@ fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!U fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void { const gpa = f.arena.child_allocator; + const io = f.job_queue.io; // Recursive directory copy. var it = try dir.walk(gpa); defer it.deinit(); @@ -1428,7 +1429,7 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void .{}, ) catch |err| switch (err) { error.FileNotFound => { - if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname); + if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(io, dirname); try dir.copyFile(entry.path, tmp_dir, entry.path, .{}); }, else => |e| return e, @@ -1441,7 +1442,7 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void // the destination directory, fail with an error instead. tmp_dir.symLink(link_name, entry.path, .{}) catch |err| switch (err) { error.FileNotFound => { - if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname); + if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(io, dirname); try tmp_dir.symLink(link_name, entry.path, .{}); }, else => |e| return e, diff --git a/src/main.zig b/src/main.zig index fe48efa2fd2d9e0d50a47f7d2321afd457ecff83..a063591b64e5b44ce61dfd02b92e5e6205b0f9f5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3382,7 +3382,7 @@ fn buildOutputType( const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{ std.crypto.random.int(u64), ext.canonicalName(target), }); - try dirs.local_cache.handle.makePath("tmp"); + try dirs.local_cache.handle.makePath(io, "tmp"); // Note that in one of the happy paths, execve() is used to switch to // clang in which case any cleanup logic that exists for this temporary @@ -4773,7 +4773,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! var ok_count: usize = 0; for (template_paths) |template_path| { - if (templates.write(arena, Io.Dir.cwd(), sanitized_root_name, template_path, fingerprint)) |_| { + if (templates.write(arena, io, Io.Dir.cwd(), sanitized_root_name, template_path, fingerprint)) |_| { std.log.info("created {s}", .{template_path}); ok_count += 1; } else |err| switch (err) { @@ -7394,20 +7394,21 @@ const Templates = struct { fn write( templates: *Templates, arena: Allocator, + io: Io, out_dir: Io.Dir, root_name: []const u8, template_path: []const u8, fingerprint: Package.Fingerprint, ) !void { if (fs.path.dirname(template_path)) |dirname| { - out_dir.makePath(dirname) catch |err| { - fatal("unable to make path '{s}': {s}", .{ dirname, @errorName(err) }); + out_dir.makePath(io, dirname) catch |err| { + fatal("unable to make path '{s}': {t}", .{ dirname, err }); }; } const max_bytes = 10 * 1024 * 1024; const contents = templates.dir.readFileAlloc(template_path, arena, .limited(max_bytes)) catch |err| { - fatal("unable to read template file '{s}': {s}", .{ template_path, @errorName(err) }); + fatal("unable to read template file '{s}': {t}", .{ template_path, err }); }; templates.buffer.clearRetainingCapacity(); try templates.buffer.ensureUnusedCapacity(contents.len); -- 2.54.0 From 314c906dba32e72317947a15254519b22745b13f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 16:41:24 -0800 Subject: [PATCH 022/195] std.debug: simplify printLineFromFile --- lib/compiler/build_runner.zig | 2 +- lib/std/Build/Fuzz.zig | 2 +- lib/std/Io/test.zig | 12 ++--- lib/std/debug.zig | 74 ++++++++++--------------------- lib/std/debug/ElfFile.zig | 18 ++++---- lib/std/debug/MachOFile.zig | 2 +- lib/std/debug/SelfInfo/Elf.zig | 4 +- lib/std/debug/SelfInfo/MachO.zig | 2 +- lib/std/os/uefi/protocol/file.zig | 9 ---- 9 files changed, 46 insertions(+), 79 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 6e0e2d8ecaaa793c37b4fa23df7edba67d7e8534..36c73e96eb815d9f24b6cff0abba3873c9e231c0 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -824,7 +824,7 @@ fn runStepNames( } if (@bitSizeOf(usize) != 64) { // Current implementation depends on posix.mmap()'s second parameter, `length: usize`, - // being compatible with `std.fs.getEndPos() u64`'s return value. This is not the case + // being compatible with file system's u64 return value. This is not the case // on 32-bit platforms. // Affects or affected by issues #5185, #22523, and #22464. fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index c95c9bd354b591639faa6bb85a75029f8ea4c565..8d5bc27f3a467b659792cba077c4d3ef94c76cca 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -413,7 +413,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO }; defer coverage_file.close(io); - const file_size = coverage_file.getEndPos() catch |err| { + const file_size = coverage_file.length(io) catch |err| { log.err("unable to check len of coverage file '{f}': {t}", .{ coverage_file_path, err }); return error.AlreadyReported; }; diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 8b9d714ee857622b8a0c253cc2de54e26d544b74..e911031c7f697f894ac1736633acd9de4abd935e 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -47,7 +47,7 @@ test "write a file, read it, then delete it" { var file = try tmp.dir.openFile(io, tmp_file_name, .{}); defer file.close(io); - const file_size = try file.getEndPos(); + const file_size = try file.length(io); const expected_file_size: u64 = "begin".len + data.len + "end".len; try expectEqual(expected_file_size, file_size); @@ -77,7 +77,7 @@ test "File seek ops" { // Seek to the end try file.seekFromEnd(0); - try expect((try file.getPos()) == try file.getEndPos()); + try expect((try file.getPos()) == try file.length(io)); // Negative delta try file.seekBy(-4096); try expect((try file.getPos()) == 4096); @@ -100,17 +100,17 @@ test "setEndPos" { defer file.close(io); // Verify that the file size changes and the file offset is not moved - try expect((try file.getEndPos()) == 0); + try expect((try file.length(io)) == 0); try expect((try file.getPos()) == 0); try file.setEndPos(8192); - try expect((try file.getEndPos()) == 8192); + try expect((try file.length(io)) == 8192); try expect((try file.getPos()) == 0); try file.seekTo(100); try file.setEndPos(4096); - try expect((try file.getEndPos()) == 4096); + try expect((try file.length(io)) == 4096); try expect((try file.getPos()) == 100); try file.setEndPos(0); - try expect((try file.getEndPos()) == 0); + try expect((try file.length(io)) == 0); try expect((try file.getPos()) == 100); } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 7ede172d86f428f886a2b1f0aac54881fd2858f0..34f740bfee8635e07caa158ac66239308c4ac309 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -558,9 +558,9 @@ pub fn defaultPanic( stderr.print("{s}\n", .{msg}) catch break :trace; if (@errorReturnTrace()) |t| if (t.index > 0) { - stderr.writeStreamingAll("error return context:\n") catch break :trace; + stderr.writeAll("error return context:\n") catch break :trace; writeStackTrace(t, stderr, tty_config) catch break :trace; - stderr.writeStreamingAll("\nstack trace:\n") catch break :trace; + stderr.writeAll("\nstack trace:\n") catch break :trace; }; writeCurrentStackTrace(.{ .first_address = first_trace_addr orelse @returnAddress(), @@ -617,6 +617,8 @@ pub const StackUnwindOptions = struct { /// /// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it. pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace { + var threaded: Io.Threaded = .init_single_threaded; + const io = threaded.ioBasic(); const empty_trace: StackTrace = .{ .index = 0, .instruction_addresses = &.{} }; if (!std.options.allow_stack_tracing) return empty_trace; var it: StackIterator = .init(options.context); @@ -628,7 +630,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: // Ideally, we would iterate the whole stack so that the `index` in the returned trace was // indicative of how many frames were skipped. However, this has a significant runtime cost // in some cases, so at least for now, we don't do that. - while (index < addr_buf.len) switch (it.next()) { + while (index < addr_buf.len) switch (it.next(io)) { .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break, .end => break, .frame => |ret_addr| { @@ -684,7 +686,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri var total_frames: usize = 0; var wait_for = options.first_address; var printed_any_frame = false; - while (true) switch (it.next()) { + while (true) switch (it.next(io)) { .switch_to_fp => |unwind_error| { switch (StackIterator.fp_usability) { .useless, .unsafe => {}, @@ -1196,54 +1198,26 @@ fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) ! // Need this to always block even in async I/O mode, because this could potentially // be called from e.g. the event loop code crashing. const cwd: Io.Dir = .cwd(); - var f = try cwd.openFile(io, source_location.file_name, .{}); - defer f.close(io); + var file = try cwd.openFile(io, source_location.file_name, .{}); + defer file.close(io); // TODO fstat and make sure that the file has the correct size - var buf: [4096]u8 = undefined; - var amt_read = try f.read(buf[0..]); - const line_start = seek: { - var current_line_start: usize = 0; - var next_line: usize = 1; - while (next_line != source_location.line) { - const slice = buf[current_line_start..amt_read]; - if (mem.findScalar(u8, slice, '\n')) |pos| { - next_line += 1; - if (pos == slice.len - 1) { - amt_read = try f.read(buf[0..]); - current_line_start = 0; - } else current_line_start += pos + 1; - } else if (amt_read < buf.len) { - return error.EndOfFile; - } else { - amt_read = try f.read(buf[0..]); - current_line_start = 0; - } + var buffer: [4096]u8 = undefined; + var file_reader: File.Reader = .init(file, io, &buffer); + const r = &file_reader.interface; + var line_index: usize = 0; + while (r.takeDelimiterExclusive('\n')) |line| { + line_index += 1; + if (line_index == source_location.line) { + // TODO delete hard tabs from the language + mem.replaceScalar(u8, line, '\t', ' '); + try writer.writeAll(line); + // Make sure printing last line of file inserts extra newline. + try writer.writeByte('\n'); + return; } - break :seek current_line_start; - }; - const slice = buf[line_start..amt_read]; - if (mem.findScalar(u8, slice, '\n')) |pos| { - const line = slice[0 .. pos + 1]; - mem.replaceScalar(u8, line, '\t', ' '); - return writer.writeAll(line); - } else { // Line is the last inside the buffer, and requires another read to find delimiter. Alternatively the file ends. - mem.replaceScalar(u8, slice, '\t', ' '); - try writer.writeAll(slice); - while (amt_read == buf.len) { - amt_read = try f.read(buf[0..]); - if (mem.findScalar(u8, buf[0..amt_read], '\n')) |pos| { - const line = buf[0 .. pos + 1]; - mem.replaceScalar(u8, line, '\t', ' '); - return writer.writeAll(line); - } else { - const line = buf[0..amt_read]; - mem.replaceScalar(u8, line, '\t', ' '); - try writer.writeAll(line); - } - } - // Make sure printing last line of file inserts extra newline - try writer.writeByte('\n'); + } else |err| { + return err; } } @@ -1598,7 +1572,7 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex // We're still holding the mutex but that's fine as we're going to // call abort(). const stderr, _ = lockStderrWriter(&.{}); - stderr().writeAll("aborting due to recursive panic\n") catch {}; + stderr.writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. } diff --git a/lib/std/debug/ElfFile.zig b/lib/std/debug/ElfFile.zig index 203ee8effb1b74c19cc4f247ef70878e4d29c5d6..a101309d22c52f4d324fbb43452bef23cd0e958a 100644 --- a/lib/std/debug/ElfFile.zig +++ b/lib/std/debug/ElfFile.zig @@ -123,6 +123,7 @@ pub const LoadError = error{ pub fn load( gpa: Allocator, + io: Io, elf_file: Io.File, opt_build_id: ?[]const u8, di_search_paths: *const DebugInfoSearchPaths, @@ -131,7 +132,7 @@ pub fn load( errdefer arena_instance.deinit(); const arena = arena_instance.allocator(); - var result = loadInner(arena, elf_file, null) catch |err| switch (err) { + var result = loadInner(arena, io, elf_file, null) catch |err| switch (err) { error.CrcMismatch => unreachable, // we passed crc as null else => |e| return e, }; @@ -156,7 +157,7 @@ pub fn load( if (build_id.len < 3) break :build_id; for (di_search_paths.global_debug) |global_debug| { - if (try loadSeparateDebugFile(arena, &result, null, "{s}/.build-id/{x}/{x}.debug", .{ + if (try loadSeparateDebugFile(arena, io, &result, null, "{s}/.build-id/{x}/{x}.debug", .{ global_debug, build_id[0..1], build_id[1..], @@ -164,7 +165,7 @@ pub fn load( } if (di_search_paths.debuginfod_client) |components| { - if (try loadSeparateDebugFile(arena, &result, null, "{s}{s}/{x}/debuginfo", .{ + if (try loadSeparateDebugFile(arena, io, &result, null, "{s}{s}/{x}/debuginfo", .{ components[0], components[1], build_id, @@ -181,18 +182,18 @@ pub fn load( const exe_dir = di_search_paths.exe_dir orelse break :debug_link; - if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/{s}", .{ + if (try loadSeparateDebugFile(arena, io, &result, debug_crc, "{s}/{s}", .{ exe_dir, debug_filename, })) |mapped| break :load_di mapped; - if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/.debug/{s}", .{ + if (try loadSeparateDebugFile(arena, io, &result, debug_crc, "{s}/.debug/{s}", .{ exe_dir, debug_filename, })) |mapped| break :load_di mapped; for (di_search_paths.global_debug) |global_debug| { // This looks like a bug; it isn't. They really do embed the absolute path to the // exe's dirname, *under* the global debug path. - if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/{s}/{s}", .{ + if (try loadSeparateDebugFile(arena, io, &result, debug_crc, "{s}/{s}/{s}", .{ global_debug, exe_dir, debug_filename, @@ -378,7 +379,7 @@ fn loadSeparateDebugFile( const elf_file = Io.Dir.cwd().openFile(io, path, .{}) catch return null; defer elf_file.close(io); - const result = loadInner(arena, elf_file, opt_crc) catch |err| switch (err) { + const result = loadInner(arena, io, elf_file, opt_crc) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.CrcMismatch => return null, else => return null, @@ -423,13 +424,14 @@ const LoadInnerResult = struct { }; fn loadInner( arena: Allocator, + io: Io, elf_file: Io.File, opt_crc: ?u32, ) (LoadError || error{ CrcMismatch, Streaming, Canceled })!LoadInnerResult { const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: { const file_len = std.math.cast( usize, - elf_file.getEndPos() catch |err| switch (err) { + elf_file.length(io) catch |err| switch (err) { error.PermissionDenied => unreachable, // not asking for PROT_EXEC else => |e| return e, }, diff --git a/lib/std/debug/MachOFile.zig b/lib/std/debug/MachOFile.zig index 18126a1c297171fe3f95e97fdf6d1d7dbc675c06..9e81fb8911d759993f99b980125476b0a20229dd 100644 --- a/lib/std/debug/MachOFile.zig +++ b/lib/std/debug/MachOFile.zig @@ -520,7 +520,7 @@ fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) c const file_len = std.math.cast( usize, - file.getEndPos() catch return error.ReadFailed, + file.length(io) catch return error.ReadFailed, ) orelse return error.ReadFailed; return posix.mmap( diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index 6ed18bcb80ad08fd5201d1affe4fb1bab6826f4b..be76f3a8c24fa92e256588359350d65767b031aa 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -326,7 +326,7 @@ const Module = struct { const load_result = if (mod.name.len > 0) res: { var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo; defer file.close(io); - break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name)); + break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name)); } else res: { const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) { error.OutOfMemory => |e| return e, @@ -335,7 +335,7 @@ const Module = struct { defer gpa.free(path); var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo; defer file.close(io); - break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path)); + break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path)); }; var elf_file = load_result catch |err| switch (err) { diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig index db8e5334e65d1ba978f8a9a40f0ecb37ed2d7222..714ff539f1c42073a7f9e5582a0d39311e1c0b49 100644 --- a/lib/std/debug/SelfInfo/MachO.zig +++ b/lib/std/debug/SelfInfo/MachO.zig @@ -622,7 +622,7 @@ fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) c }; defer file.close(io); - const file_end_pos = file.getEndPos() catch |err| switch (err) { + const file_end_pos = file.length(io) catch |err| switch (err) { error.Unexpected => |e| return e, else => return error.ReadFailed, }; diff --git a/lib/std/os/uefi/protocol/file.zig b/lib/std/os/uefi/protocol/file.zig index 9e3e7cc081f9bfc4a340e996126582a93cb3abcb..9b371916a71bb4974e3e9e7627b270a3d5fb308f 100644 --- a/lib/std/os/uefi/protocol/file.zig +++ b/lib/std/os/uefi/protocol/file.zig @@ -163,15 +163,6 @@ pub const File = extern struct { } } - fn getEndPos(self: *File) SeekError!u64 { - const start_pos = try self.getPosition(); - // ignore error - defer self.setPosition(start_pos) catch {}; - - try self.setPosition(end_of_file); - return self.getPosition(); - } - pub fn setPosition(self: *File, position: u64) SeekError!void { switch (self._set_position(self, position)) { .success => {}, -- 2.54.0 From 950d18ef695bb7a28397e080dc3c201559ec4ee2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 17:14:31 -0800 Subject: [PATCH 023/195] update all access() to access(io) --- lib/compiler/aro/aro/Driver.zig | 6 +-- lib/compiler/aro/aro/Driver/Filesystem.zig | 8 +-- lib/compiler/aro/aro/Toolchain.zig | 13 +++-- lib/compiler/aro/backend/Assembly.zig | 4 +- lib/compiler/resinator/cli.zig | 10 ++-- lib/compiler/resinator/main.zig | 6 +-- lib/compiler/std-docs.zig | 8 +-- lib/compiler/translate-c/main.zig | 4 +- lib/std/Build.zig | 2 +- lib/std/Build/Cache/Path.zig | 4 +- lib/std/Build/Step.zig | 12 +++-- lib/std/Build/Step/InstallDir.zig | 2 +- lib/std/Build/Step/Run.zig | 47 +++++++++-------- lib/std/Build/Step/WriteFile.zig | 9 ++-- lib/std/Io.zig | 4 +- lib/std/Io/Dir.zig | 16 +++--- lib/std/Io/Threaded.zig | 13 +++-- lib/std/Io/test.zig | 2 +- lib/std/debug.zig | 8 +-- lib/std/fs/test.zig | 24 ++++----- lib/std/posix.zig | 61 ---------------------- lib/std/posix/test.zig | 12 +++-- lib/std/zig/LibCInstallation.zig | 10 ++-- lib/std/zig/llvm/Builder.zig | 4 +- lib/std/zig/system.zig | 3 +- src/Compilation.zig | 2 +- src/Package/Fetch.zig | 5 +- src/introspect.zig | 4 +- src/libs/mingw.zig | 12 +++-- src/link/C.zig | 3 +- src/link/Lld.zig | 9 ++-- src/link/MachO.zig | 21 ++++---- src/main.zig | 8 +-- 33 files changed, 159 insertions(+), 197 deletions(-) diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig index fec3cea0f877f5c7b3ffa2820971c02e174f0d65..b344cc8a9d140f1128269b9733894483d3915075 100644 --- a/lib/compiler/aro/aro/Driver.zig +++ b/lib/compiler/aro/aro/Driver.zig @@ -1333,7 +1333,7 @@ fn processSource( Io.File.stdout(); defer if (dep_file_name != null) file.close(io); - var file_writer = file.writer(&writer_buf); + var file_writer = file.writer(io, &writer_buf); dep_file.write(&file_writer.interface) catch return d.fatal("unable to write dependency file: {s}", .{errorDescription(file_writer.err.?)}); } @@ -1358,7 +1358,7 @@ fn processSource( Io.File.stdout(); defer if (d.output_name != null) file.close(io); - var file_writer = file.writer(&writer_buf); + var file_writer = file.writer(io, &writer_buf); pp.prettyPrintTokens(&file_writer.interface, dump_mode) catch return d.fatal("unable to write result: {s}", .{errorDescription(file_writer.err.?)}); @@ -1459,7 +1459,7 @@ fn processSource( return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) }); defer out_file.close(io); - var file_writer = out_file.writer(&writer_buf); + var file_writer = out_file.writer(io, &writer_buf); obj.finish(&file_writer.interface) catch return d.fatal("could not output to object file '{s}': {s}", .{ out_file_name, errorDescription(file_writer.err.?) }); } diff --git a/lib/compiler/aro/aro/Driver/Filesystem.zig b/lib/compiler/aro/aro/Driver/Filesystem.zig index 001f10f27c7b5acc5f9e854adfd36ae10b58ead5..c229dfd831e04c64fd55ac42b81ee952dbc0a063 100644 --- a/lib/compiler/aro/aro/Driver/Filesystem.zig +++ b/lib/compiler/aro/aro/Driver/Filesystem.zig @@ -57,8 +57,8 @@ fn existsFake(entries: []const Filesystem.Entry, path: []const u8) bool { return false; } -fn canExecutePosix(path: []const u8) bool { - std.posix.access(path, std.posix.X_OK) catch return false; +fn canExecutePosix(io: Io, path: []const u8) bool { + Io.Dir.accessAbsolute(io, path, .{ .execute = true }) catch return false; // Todo: ensure path is not a directory return true; } @@ -172,10 +172,10 @@ pub const Filesystem = union(enum) { } }; - pub fn exists(fs: Filesystem, path: []const u8) bool { + pub fn exists(fs: Filesystem, io: Io, path: []const u8) bool { switch (fs) { .real => |cwd| { - cwd.access(path, .{}) catch return false; + cwd.access(io, path, .{}) catch return false; return true; }, .fake => |paths| return existsFake(paths, path), diff --git a/lib/compiler/aro/aro/Toolchain.zig b/lib/compiler/aro/aro/Toolchain.zig index 0328c264d08ad4f33c2bf3ea43e235c6d7aa7c4e..0aa9d76fc88a176f9ac5690fbf40dd0563b48aab 100644 --- a/lib/compiler/aro/aro/Toolchain.zig +++ b/lib/compiler/aro/aro/Toolchain.zig @@ -501,7 +501,7 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void { try d.includes.ensureUnusedCapacity(gpa, 1); if (d.resource_dir) |resource_dir| { const path = try std.fs.path.join(arena, &.{ resource_dir, "include" }); - comp.cwd.access(path, .{}) catch { + comp.cwd.access(io, path, .{}) catch { return d.fatal("Aro builtin headers not found in provided -resource-dir", .{}); }; d.includes.appendAssumeCapacity(.{ .kind = .system, .path = path }); @@ -512,7 +512,7 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void { var base_dir = d.comp.cwd.openDir(io, dirname, .{}) catch continue; defer base_dir.close(io); - base_dir.access("include/stddef.h", .{}) catch continue; + base_dir.access(io, "include/stddef.h", .{}) catch continue; const path = try std.fs.path.join(arena, &.{ dirname, "include" }); d.includes.appendAssumeCapacity(.{ .kind = .system, .path = path }); break; @@ -524,12 +524,14 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void { /// Otherwise returns a slice of `buf`. If the file is larger than `buf` partial contents are returned pub fn readFile(tc: *const Toolchain, path: []const u8, buf: []u8) ?[]const u8 { const comp = tc.driver.comp; - return comp.cwd.readFile(comp.io, path, buf) catch null; + const io = comp.io; + return comp.cwd.readFile(io, path, buf) catch null; } pub fn exists(tc: *const Toolchain, path: []const u8) bool { const comp = tc.driver.comp; - comp.cwd.access(comp.io, path, .{}) catch return false; + const io = comp.io; + comp.cwd.access(io, path, .{}) catch return false; return true; } @@ -547,7 +549,8 @@ pub fn canExecute(tc: *const Toolchain, path: []const u8) bool { } const comp = tc.driver.comp; - comp.cwd.access(comp.io, path, .{ .execute = true }) catch return false; + const io = comp.io; + comp.cwd.access(io, path, .{ .execute = true }) catch return false; // Todo: ensure path is not a directory return true; } diff --git a/lib/compiler/aro/backend/Assembly.zig b/lib/compiler/aro/backend/Assembly.zig index 80143bf97f91b6fa79426c1e19cfbe1729586484..4ec4860b514fe1ab0fb35cef8edff7bfaeda3249 100644 --- a/lib/compiler/aro/backend/Assembly.zig +++ b/lib/compiler/aro/backend/Assembly.zig @@ -12,8 +12,8 @@ pub fn deinit(self: *const Assembly, gpa: Allocator) void { gpa.free(self.text); } -pub fn writeToFile(self: Assembly, file: Io.File) !void { - var file_writer = file.writer(&.{}); +pub fn writeToFile(self: Assembly, io: Io, file: Io.File) !void { + var file_writer = file.writer(io, &.{}); var buffers = [_][]const u8{ self.data, self.text }; try file_writer.interface.writeSplatAll(&buffers, 1); diff --git a/lib/compiler/resinator/cli.zig b/lib/compiler/resinator/cli.zig index ae4ece29688d38d2a6de5d22a90a2a8b4ce2fc81..5588390197cb2b9b21181701a1e63d8a832bbb28 100644 --- a/lib/compiler/resinator/cli.zig +++ b/lib/compiler/resinator/cli.zig @@ -250,13 +250,13 @@ pub const Options = struct { /// worlds' situation where we'll be compatible with most use-cases /// of the .rc extension being omitted from the CLI args, but still /// work fine if the file itself does not have an extension. - pub fn maybeAppendRC(options: *Options, cwd: Io.Dir) !void { + pub fn maybeAppendRC(options: *Options, io: Io, cwd: Io.Dir) !void { switch (options.input_source) { .stdio => return, .filename => {}, } if (options.input_format == .rc and std.fs.path.extension(options.input_source.filename).len == 0) { - cwd.access(options.input_source.filename, .{}) catch |err| switch (err) { + cwd.access(io, options.input_source.filename, .{}) catch |err| switch (err) { error.FileNotFound => { var filename_bytes = try options.allocator.alloc(u8, options.input_source.filename.len + 3); @memcpy(filename_bytes[0..options.input_source.filename.len], options.input_source.filename); @@ -2005,19 +2005,19 @@ test "maybeAppendRC" { // appended. var file = try tmp.dir.createFile(io, "foo", .{}); file.close(io); - try options.maybeAppendRC(tmp.dir); + try options.maybeAppendRC(io, tmp.dir); try std.testing.expectEqualStrings("foo", options.input_source.filename); // Now delete the file and try again. But this time change the input format // to non-rc. try tmp.dir.deleteFile("foo"); options.input_format = .res; - try options.maybeAppendRC(tmp.dir); + try options.maybeAppendRC(io, tmp.dir); try std.testing.expectEqualStrings("foo", options.input_source.filename); // Finally, reset the input format to rc. Since the verbatim name is no longer found // and the input filename does not have an extension, .rc should get appended. options.input_format = .rc; - try options.maybeAppendRC(tmp.dir); + try options.maybeAppendRC(io, tmp.dir); try std.testing.expectEqualStrings("foo.rc", options.input_source.filename); } diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 416abc2ab742ad065804e2696589cf756b722a41..1a6ef5eea35786cea6e41f8c027f39268ff7e9e4 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -318,7 +318,7 @@ pub fn main() !void { defer depfile.close(io); var depfile_buffer: [1024]u8 = undefined; - var depfile_writer = depfile.writer(&depfile_buffer); + var depfile_writer = depfile.writer(io, &depfile_buffer); switch (options.depfile_fmt) { .json => { var write_stream: std.json.Stringify = .{ @@ -521,9 +521,9 @@ const IoStream = struct { } }; - pub fn writer(source: *Source, allocator: Allocator, buffer: []u8) Writer { + pub fn writer(source: *Source, allocator: Allocator, io: Io, buffer: []u8) Writer { return switch (source.*) { - .file, .stdio => |file| .{ .file = file.writer(buffer) }, + .file, .stdio => |file| .{ .file = file.writer(io, buffer) }, .memory => |*list| .{ .allocating = .fromArrayList(allocator, list) }, .closed => unreachable, }; diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index d5beab5f179d62cfa676ddfac03c924b41016774..f1382e6eaeb86e71a933659c87b772787d7a4159 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -334,8 +334,8 @@ fn buildWasmBinary( }); defer poller.deinit(); - try sendMessage(child.stdin.?, .update); - try sendMessage(child.stdin.?, .exit); + try sendMessage(io, child.stdin.?, .update); + try sendMessage(io, child.stdin.?, .exit); var result: ?Cache.Path = null; var result_error_bundle = std.zig.ErrorBundle.empty; @@ -421,12 +421,12 @@ fn buildWasmBinary( }; } -fn sendMessage(file: std.Io.File, tag: std.zig.Client.Message.Tag) !void { +fn sendMessage(io: Io, file: std.Io.File, tag: std.zig.Client.Message.Tag) !void { const header: std.zig.Client.Message.Header = .{ .tag = tag, .bytes_len = 0, }; - var w = file.writer(&.{}); + var w = file.writer(io, &.{}); w.interface.writeStruct(header, .little) catch |err| switch (err) { error.WriteFailed => return w.err.?, }; diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index d1401450329d80fe73bd7f50549365e95633aab0..d02f21a2a810383c9c4a2a789c62fc10e386c108 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -232,7 +232,7 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration Io.File.stdout(); defer if (dep_file_name != null) file.close(io); - var file_writer = file.writer(&out_buf); + var file_writer = file.writer(io, &out_buf); dep_file.write(&file_writer.interface) catch return d.fatal("unable to write dependency file: {s}", .{aro.Driver.errorDescription(file_writer.err.?)}); } @@ -263,7 +263,7 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration out_file_path = path; } - var out_writer = out_file.writer(&out_buf); + var out_writer = out_file.writer(io, &out_buf); out_writer.interface.writeAll(rendered_zig) catch {}; out_writer.interface.flush() catch {}; if (out_writer.err) |write_err| diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 1eff8813e5c6623b53a3efac60bf2e3cc620cc30..746b41860b20c93a3dafb4b94faffc6c01fef357 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1699,7 +1699,7 @@ pub fn addCheckFile( return Step.CheckFile.create(b, file_source, options); } -pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.MakeError || Io.Dir.StatFileError)!void { +pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.MakeError || Io.Dir.StatPathError)!void { const io = b.graph.io; if (b.verbose) log.info("truncate {s}", .{dest_path}); const cwd = Io.Dir.cwd(); diff --git a/lib/std/Build/Cache/Path.zig b/lib/std/Build/Cache/Path.zig index 941948a9cd13fa71387fde847eaab89c1f211586..759eb143b8507b73a899595f413f29787250544e 100644 --- a/lib/std/Build/Cache/Path.zig +++ b/lib/std/Build/Cache/Path.zig @@ -118,14 +118,14 @@ pub fn atomicFile( return p.root_dir.handle.atomicFile(joined_path, options); } -pub fn access(p: Path, sub_path: []const u8, flags: Io.Dir.AccessOptions) !void { +pub fn access(p: Path, io: Io, sub_path: []const u8, flags: Io.Dir.AccessOptions) !void { var buf: [fs.max_path_bytes]u8 = undefined; const joined_path = if (p.sub_path.len == 0) sub_path else p: { break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ p.sub_path, sub_path, }) catch return error.NameTooLong; }; - return p.root_dir.handle.access(joined_path, flags); + return p.root_dir.handle.access(io, joined_path, flags); } pub fn makePath(p: Path, io: Io, sub_path: []const u8) !void { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 2ec1c0ef31337da0629f0809004de0aa1bee7ba0..f66f8df4c8b94b86bd2a9d159fb370a61fd94380 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -519,19 +519,21 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u /// Wrapper around `Io.Dir.makePathStatus` that handles verbose and error output. pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.MakePathStatus { const b = s.owner; + const io = b.graph.io; try handleVerbose(b, null, &.{ "install", "-d", dest_path }); - return Io.Dir.cwd().makePathStatus(dest_path) catch |err| + return Io.Dir.cwd().makePathStatus(io, dest_path, .default_dir) catch |err| return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err }); } fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.WebServer, gpa: Allocator) !?Path { const b = s.owner; const arena = b.allocator; + const io = b.graph.io; var timer = try std.time.Timer.start(); - try sendMessage(zp.child.stdin.?, .update); - if (!watch) try sendMessage(zp.child.stdin.?, .exit); + try sendMessage(io, zp.child.stdin.?, .update); + if (!watch) try sendMessage(io, zp.child.stdin.?, .exit); var result: ?Path = null; @@ -668,12 +670,12 @@ fn clearZigProcess(s: *Step, gpa: Allocator) void { } } -fn sendMessage(file: Io.File, tag: std.zig.Client.Message.Tag) !void { +fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { const header: std.zig.Client.Message.Header = .{ .tag = tag, .bytes_len = 0, }; - var w = file.writer(&.{}); + var w = file.writer(io, &.{}); w.interface.writeStruct(header, .little) catch |err| switch (err) { error.WriteFailed => return w.err.?, }; diff --git a/lib/std/Build/Step/InstallDir.zig b/lib/std/Build/Step/InstallDir.zig index 788d5565a7437d32a809a8500762b5ef01e3ab20..d03e72ca75f45a314fc911affc150b1488623f85 100644 --- a/lib/std/Build/Step/InstallDir.zig +++ b/lib/std/Build/Step/InstallDir.zig @@ -71,7 +71,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { defer src_dir.close(io); var it = try src_dir.walk(arena); var all_cached = true; - next_entry: while (try it.next()) |entry| { + next_entry: while (try it.next(io)) |entry| { for (install_dir.options.exclude_extensions) |ext| { if (mem.endsWith(u8, entry.path, ext)) continue :next_entry; } diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 54e77bd6146f41f8cb28afb4ee0455cc93193ddb..f6b29635c179716c57256e4f3a52a8b0f7fd0c14 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1310,7 +1310,7 @@ fn runCommand( const need_cross_libc = exe.is_linking_libc and (root_target.isGnuLibC() or (root_target.isMuslLibC() and exe.linkage == .dynamic)); const other_target = exe.root_module.resolved_target.?.result; - switch (std.zig.system.getExternalExecutor(&b.graph.host.result, &other_target, .{ + switch (std.zig.system.getExternalExecutor(io, &b.graph.host.result, &other_target, .{ .qemu_fixes_dl = need_cross_libc and b.libc_runtimes_dir != null, .link_libc = exe.is_linking_libc, })) { @@ -1702,7 +1702,7 @@ fn evalZigTest( }); var child_killed = false; defer if (!child_killed) { - _ = child.kill() catch {}; + _ = child.kill(io) catch {}; poller.deinit(); run.step.result_peak_rss = @max( run.step.result_peak_rss, @@ -1732,7 +1732,7 @@ fn evalZigTest( child.stdin = null; poller.deinit(); child_killed = true; - const term = try child.wait(); + const term = try child.wait(io); run.step.result_peak_rss = @max( run.step.result_peak_rss, child.resource_usage_statistics.getMaxRss() orelse 0, @@ -1752,7 +1752,7 @@ fn evalZigTest( child.stdin = null; poller.deinit(); child_killed = true; - const term = try child.wait(); + const term = try child.wait(io); run.step.result_peak_rss = @max( run.step.result_peak_rss, child.resource_usage_statistics.getMaxRss() orelse 0, @@ -1840,6 +1840,7 @@ fn pollZigTest( switch (ctx.fuzz.mode) { .forever => { sendRunFuzzTestMessage( + io, child.stdin.?, ctx.unit_test_index, .forever, @@ -1848,6 +1849,7 @@ fn pollZigTest( }, .limit => |limit| { sendRunFuzzTestMessage( + io, child.stdin.?, ctx.unit_test_index, .iterations, @@ -1857,11 +1859,11 @@ fn pollZigTest( } } else if (opt_metadata.*) |*md| { // Previous unit test process died or was killed; we're continuing where it left off - requestNextTest(child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; + requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; } else { // Running unit tests normally run.fuzz_tests.clearRetainingCapacity(); - sendMessage(child.stdin.?, .query_test_metadata) catch |err| return .{ .write_failed = err }; + sendMessage(io, child.stdin.?, .query_test_metadata) catch |err| return .{ .write_failed = err }; } var active_test_index: ?u32 = null; @@ -1977,7 +1979,7 @@ fn pollZigTest( active_test_index = null; if (timer) |*t| t.reset(); - requestNextTest(child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err }; + requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err }; }, .test_started => { active_test_index = opt_metadata.*.?.next_index - 1; @@ -2026,7 +2028,7 @@ fn pollZigTest( active_test_index = null; if (timer) |*t| md.ns_per_test[tr_hdr.index] = t.lap(); - requestNextTest(child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; + requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; }, .coverage_id => { coverage_id = body_r.takeInt(u64, .little) catch unreachable; @@ -2097,7 +2099,7 @@ pub const CachedTestMetadata = struct { } }; -fn requestNextTest(in: Io.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { +fn requestNextTest(io: Io, in: Io.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { while (metadata.next_index < metadata.names.len) { const i = metadata.next_index; metadata.next_index += 1; @@ -2108,31 +2110,31 @@ fn requestNextTest(in: Io.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr if (sub_prog_node.*) |n| n.end(); sub_prog_node.* = metadata.prog_node.start(name, 0); - try sendRunTestMessage(in, .run_test, i); + try sendRunTestMessage(io, in, .run_test, i); return; } else { metadata.next_index = std.math.maxInt(u32); // indicate that all tests are done - try sendMessage(in, .exit); + try sendMessage(io, in, .exit); } } -fn sendMessage(file: Io.File, tag: std.zig.Client.Message.Tag) !void { +fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { const header: std.zig.Client.Message.Header = .{ .tag = tag, .bytes_len = 0, }; - var w = file.writer(&.{}); + var w = file.writer(io, &.{}); w.interface.writeStruct(header, .little) catch |err| switch (err) { error.WriteFailed => return w.err.?, }; } -fn sendRunTestMessage(file: Io.File, tag: std.zig.Client.Message.Tag, index: u32) !void { +fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, index: u32) !void { const header: std.zig.Client.Message.Header = .{ .tag = tag, .bytes_len = 4, }; - var w = file.writer(&.{}); + var w = file.writer(io, &.{}); w.interface.writeStruct(header, .little) catch |err| switch (err) { error.WriteFailed => return w.err.?, }; @@ -2142,6 +2144,7 @@ fn sendRunTestMessage(file: Io.File, tag: std.zig.Client.Message.Tag, index: u32 } fn sendRunFuzzTestMessage( + io: Io, file: Io.File, index: u32, kind: std.Build.abi.fuzz.LimitKind, @@ -2151,7 +2154,7 @@ fn sendRunFuzzTestMessage( .tag = .start_fuzzing, .bytes_len = 4 + 1 + 8, }; - var w = file.writer(&.{}); + var w = file.writer(io, &.{}); w.interface.writeStruct(header, .little) catch |err| switch (err) { error.WriteFailed => return w.err.?, }; @@ -2172,14 +2175,14 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { const arena = b.allocator; try child.spawn(); - errdefer _ = child.kill() catch {}; + errdefer _ = child.kill(io) catch {}; try child.waitForSpawn(); switch (run.stdin) { .bytes => |bytes| { - child.stdin.?.writeAll(bytes) catch |err| { - return run.step.fail("unable to write stdin: {s}", .{@errorName(err)}); + child.stdin.?.writeStreamingAll(io, bytes) catch |err| { + return run.step.fail("unable to write stdin: {t}", .{err}); }; child.stdin.?.close(io); child.stdin = null; @@ -2187,14 +2190,14 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { .lazy_path => |lazy_path| { const path = lazy_path.getPath3(b, &run.step); const file = path.root_dir.handle.openFile(io, path.subPathOrDot(), .{}) catch |err| { - return run.step.fail("unable to open stdin file: {s}", .{@errorName(err)}); + return run.step.fail("unable to open stdin file: {t}", .{err}); }; defer file.close(io); // TODO https://github.com/ziglang/zig/issues/23955 var read_buffer: [1024]u8 = undefined; var file_reader = file.reader(io, &read_buffer); var write_buffer: [1024]u8 = undefined; - var stdin_writer = child.stdin.?.writer(&write_buffer); + var stdin_writer = child.stdin.?.writer(io, &write_buffer); _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { error.ReadFailed => return run.step.fail("failed to read from {f}: {t}", .{ path, file_reader.err.?, @@ -2267,7 +2270,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { run.step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0; return .{ - .term = try child.wait(), + .term = try child.wait(io), .stdout = stdout_bytes, .stderr = stderr_bytes, }; diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index 21346959e7b78c455aadc35928394d1c1d42effe..94b04b42128a3969cf62af0d8484a72bd3368d7c 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -228,7 +228,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { var it = try src_dir.walk(gpa); defer it.deinit(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { if (!dir.options.pathIncluded(entry.path)) continue; switch (entry.kind) { @@ -259,11 +259,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void { write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest }); - var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { - return step.fail("unable to make path '{f}{s}': {s}", .{ - b.cache_root, cache_path, @errorName(err), - }); - }; + var cache_dir = b.cache_root.handle.makeOpenPath(io, cache_path, .{}) catch |err| + return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, cache_path, err }); defer cache_dir.close(io); for (write_file.files.items) |file| { diff --git a/lib/std/Io.zig b/lib/std/Io.zig index ad0764ebe1272c57888463d67be1055cfac465ef..7f5f049d348c77dc85ef09c695e15b2bd01664c8 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -664,13 +664,13 @@ pub const VTable = struct { dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakeError!void, dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakePathError!Dir.MakePathStatus, - dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, + dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, + dirOpenDir: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.OpenError!Dir, dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat, dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat, dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void, dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File, dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File, - dirOpenDir: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.OpenError!Dir, dirClose: *const fn (?*anyopaque, []const Dir) void, dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize, dirRealPath: *const fn (?*anyopaque, Dir, path_name: []const u8, out_buffer: []u8) Dir.RealPathError!usize, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 58f81cbb90452daec55e609830a32a20373ec0c7..755ce924ad639b8b2b93bd15f8e5b1c899eac990 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -191,7 +191,7 @@ pub const SelectiveWalker = struct { while (self.stack.items.len > 0) { const top = &self.stack.items[self.stack.items.len - 1]; var dirname_len = top.dirname_len; - if (top.iter.next() catch |err| { + if (top.iter.next(io) catch |err| { // If we get an error, then we want the user to be able to continue // walking if they want, which means that we need to pop the directory // that errored from the stack. Otherwise, all future `next` calls would @@ -302,7 +302,7 @@ pub const Walker = struct { dir: Dir, basename: [:0]const u8, path: [:0]const u8, - kind: Dir.Entry.Kind, + kind: File.Kind, /// Returns the depth of the entry relative to the initial directory. /// Returns 1 for a direct child of the initial directory, 2 for an entry @@ -320,10 +320,10 @@ pub const Walker = struct { /// After each call to this function, and on deinit(), the memory returned /// from this function becomes invalid. A copy must be made in order to keep /// a reference to the path. - pub fn next(self: *Walker) !?Walker.Entry { - const entry = try self.inner.next(); + pub fn next(self: *Walker, io: Io) !?Walker.Entry { + const entry = try self.inner.next(io); if (entry != null and entry.?.kind == .directory) { - try self.inner.enter(entry.?); + try self.inner.enter(io, entry.?); } return entry; } @@ -495,7 +495,7 @@ pub const WriteFileOptions = struct { flags: File.CreateFlags = .{}, }; -pub const WriteFileError = File.WriteError || File.OpenError; +pub const WriteFileError = File.Writer.Error || File.OpenError; /// Writes content to the file system, using the file creation flags provided. pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void { @@ -556,11 +556,11 @@ pub fn updateFile( } if (path.dirname(dest_path)) |dirname| { - try dest_dir.makePath(io, dirname, .default_dir); + try dest_dir.makePath(io, dirname); } var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available. - var atomic_file = try Dir.atomicFile(dest_dir, dest_path, .{ + var atomic_file = try dest_dir.atomicFile(io, dest_path, .{ .permissions = actual_permissions, .write_buffer = &buffer, }); diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 4ee1d5db53adb1af018c42e6a2fd111019be27f0..16e8930267387c908bbe1f760899f88e1e9bbb8c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -12,7 +12,7 @@ const std = @import("../std.zig"); const Io = std.Io; const net = std.Io.net; const File = std.Io.File; -const Dir = std.Dir; +const Dir = std.Io.Dir; const HostName = std.Io.net.HostName; const IpAddress = std.Io.net.IpAddress; const Allocator = std.mem.Allocator; @@ -1614,13 +1614,14 @@ fn dirMakeOpenPathPosix( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, + permissions: Dir.Permissions, options: Dir.OpenOptions, ) Dir.MakeOpenPathError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); const t_io = ioBasic(t); - return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) { + return dirOpenDirPosix(t, dir, sub_path, permissions, options) catch |err| switch (err) { error.FileNotFound => { - try dir.makePath(t_io, sub_path); + _ = try dir.makePathStatus(t_io, sub_path, permissions); return dirOpenDirPosix(t, dir, sub_path, options); }, else => |e| return e, @@ -1631,12 +1632,15 @@ fn dirMakeOpenPathWindows( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, + permissions: Dir.Permissions, options: Dir.OpenOptions, ) Dir.MakeOpenPathError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const w = windows; + _ = permissions; // TODO apply these permissions + var it = std.fs.path.componentIterator(sub_path); // If there are no components in the path, then create a dummy component with the full path. var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{ @@ -1746,13 +1750,14 @@ fn dirMakeOpenPathWasi( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, + permissions: Dir.Permissions, options: Dir.OpenOptions, ) Dir.MakeOpenPathError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); const t_io = ioBasic(t); return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) { error.FileNotFound => { - try dir.makePath(t_io, sub_path); + _ = try dir.makePathStatus(t_io, sub_path, permissions); return dirOpenDirWasi(t, dir, sub_path, options); }, else => |e| return e, diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index e911031c7f697f894ac1736633acd9de4abd935e..9763fb23979ed35f29cf7ace53414be9fa7eb402 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -30,7 +30,7 @@ test "write a file, read it, then delete it" { var file = try tmp.dir.createFile(io, tmp_file_name, .{}); defer file.close(io); - var file_writer = file.writer(&.{}); + var file_writer = file.writer(io, &.{}); const st = &file_writer.interface; try st.print("begin", .{}); try st.writeAll(&data); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 34f740bfee8635e07caa158ac66239308c4ac309..39207f938d0aeab24ae62deb56ecb7ada61db211 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1279,7 +1279,7 @@ test printLineFromFile { const overlap = 10; var buf: [16]u8 = undefined; - var file_writer = file.writer(&buf); + var file_writer = file.writer(io, &buf); const writer = &file_writer.interface; try writer.splatByteAll('a', std.heap.page_size_min - overlap); try writer.writeByte('\n'); @@ -1296,7 +1296,7 @@ test printLineFromFile { const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); defer gpa.free(path); - var file_writer = file.writer(&.{}); + var file_writer = file.writer(io, &.{}); const writer = &file_writer.interface; try writer.splatByteAll('a', std.heap.page_size_max); @@ -1310,7 +1310,7 @@ test printLineFromFile { const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); defer gpa.free(path); - var file_writer = file.writer(&.{}); + var file_writer = file.writer(io, &.{}); const writer = &file_writer.interface; try writer.splatByteAll('a', 3 * std.heap.page_size_max); @@ -1336,7 +1336,7 @@ test printLineFromFile { const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" }); defer gpa.free(path); - var file_writer = file.writer(&.{}); + var file_writer = file.writer(io, &.{}); const writer = &file_writer.interface; const real_file_start = 3 * std.heap.page_size_min; try writer.splatByteAll('\n', real_file_start); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index bc845004190a85ac3f2175b13a420a19c194eb42..59bacff2d0937917ace8985f284fef00a433a96e 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1206,8 +1206,8 @@ test "deleteTree does not follow symlinks" { try tmp.dir.deleteTree("a"); - try testing.expectError(error.FileNotFound, tmp.dir.access("a", .{})); - try tmp.dir.access("b", .{}); + try testing.expectError(error.FileNotFound, tmp.dir.access(io, "a", .{})); + try tmp.dir.access(io, "b", .{}); } test "deleteTree on a symlink" { @@ -1221,16 +1221,16 @@ test "deleteTree on a symlink" { try setupSymlink(tmp.dir, "file", "filelink", .{}); try tmp.dir.deleteTree("filelink"); - try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{})); - try tmp.dir.access("file", .{}); + try testing.expectError(error.FileNotFound, tmp.dir.access(io, "filelink", .{})); + try tmp.dir.access(io, "file", .{}); // Symlink to a directory try tmp.dir.makePath(io, "dir"); try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true }); try tmp.dir.deleteTree("dirlink"); - try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{})); - try tmp.dir.access("dir", .{}); + try testing.expectError(error.FileNotFound, tmp.dir.access(io, "dirlink", .{})); + try tmp.dir.access(io, "dir", .{}); } test "makePath, put some files in it, deleteTree" { @@ -1358,8 +1358,8 @@ test "makepath relative walks" { // On Windows, .. is resolved before passing the path to NtCreateFile, // meaning everything except `first/C` drops out. try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "C"); - try testing.expectError(error.FileNotFound, tmp.dir.access("second", .{})); - try testing.expectError(error.FileNotFound, tmp.dir.access("third", .{})); + try testing.expectError(error.FileNotFound, tmp.dir.access(io, "second", .{})); + try testing.expectError(error.FileNotFound, tmp.dir.access(io, "third", .{})); }, else => { try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "A"); @@ -1561,10 +1561,10 @@ test "access file" { const file_path = try ctx.transformPath("os_test_tmp" ++ fs.path.sep_str ++ "file.txt"); try ctx.dir.makePath(io, dir_path); - try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{})); + try testing.expectError(error.FileNotFound, ctx.dir.access(io, file_path, .{})); try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "" }); - try ctx.dir.access(file_path, .{}); + try ctx.dir.access(io, file_path, .{}); try ctx.dir.deleteTree(dir_path); } }.impl); @@ -2036,13 +2036,13 @@ test "'.' and '..' in Io.Dir functions" { const update_path = try ctx.transformPath("./subdir/../update"); try ctx.dir.makeDir(subdir_path); - try ctx.dir.access(subdir_path, .{}); + try ctx.dir.access(io, subdir_path, .{}); var created_subdir = try ctx.dir.openDir(io, subdir_path, .{}); created_subdir.close(io); const created_file = try ctx.dir.createFile(io, file_path, .{}); created_file.close(io); - try ctx.dir.access(file_path, .{}); + try ctx.dir.access(io, file_path, .{}); try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{}); try ctx.dir.rename(copy_path, rename_path); diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 42429a49931a95ec9f3b07188649f252efcfaec4..609cf62e82e170617ada1582793e37c643cb4c7e 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -2842,67 +2842,6 @@ pub fn msync(memory: []align(page_size_min) u8, flags: i32) MSyncError!void { } } -pub const AccessError = error{ - AccessDenied, - PermissionDenied, - FileNotFound, - NameTooLong, - InputOutput, - SystemResources, - FileBusy, - SymLinkLoop, - ReadOnlyFileSystem, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, - Canceled, -} || UnexpectedError; - -/// check user's permissions for a file -/// -/// * On Windows, asserts `path` is valid [WTF-8](https://wtf-8.codeberg.page/). -/// * On WASI, invalid UTF-8 passed to `path` causes `error.BadPathName`. -/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding. -/// -/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by -/// Windows. See `fs` for the cross-platform file system API. -pub fn access(path: []const u8, mode: u32) AccessError!void { - if (native_os == .windows) { - @compileError("use std.Io instead"); - } else if (native_os == .wasi and !builtin.link_libc) { - @compileError("wasi doesn't support absolute paths"); - } - const path_c = try toPosixPath(path); - return accessZ(&path_c, mode); -} - -/// Same as `access` except `path` is null-terminated. -pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { - if (native_os == .windows) { - @compileError("use std.Io instead"); - } else if (native_os == .wasi and !builtin.link_libc) { - return access(mem.sliceTo(path, 0), mode); - } - switch (errno(system.access(path, mode))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - .LOOP => return error.SymLinkLoop, - .TXTBSY => return error.FileBusy, - .NOTDIR => return error.FileNotFound, - .NOENT => return error.FileNotFound, - .NAMETOOLONG => return error.NameTooLong, - .INVAL => unreachable, - .FAULT => unreachable, - .IO => return error.InputOutput, - .NOMEM => return error.SystemResources, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - pub const PipeError = error{ SystemFdQuotaExceeded, ProcessFdQuotaExceeded, diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 169c5a70c2bf43c33bffbe99da259f69cdabbaa7..af17f1f9ff3e8360143b9d77623eacacdfc0b420 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -376,7 +376,7 @@ test "mmap" { const file = try tmp.dir.createFile(io, test_out_file, .{}); defer file.close(io); - var stream = file.writer(&.{}); + var stream = file.writer(io, &.{}); var i: usize = 0; while (i < alloc_size / @sizeOf(u32)) : (i += 1) { @@ -741,6 +741,8 @@ test "access smoke test" { if (native_os == .windows) return error.SkipZigTest; if (native_os == .openbsd) return error.SkipZigTest; + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -761,9 +763,9 @@ test "access smoke test" { const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); defer a.free(file_path); if (native_os == .windows) { - try posix.access(file_path, posix.F_OK); + try posix.access(io, file_path, posix.F_OK); } else { - try posix.access(file_path, posix.F_OK | posix.W_OK | posix.R_OK); + try posix.access(io, file_path, posix.F_OK | posix.W_OK | posix.R_OK); } } @@ -771,7 +773,7 @@ test "access smoke test" { // Try to access() a non-existent file - should fail with error.FileNotFound const file_path = try fs.path.join(a, &.{ base_path, "some_other_file" }); defer a.free(file_path); - try expectError(error.FileNotFound, posix.access(file_path, posix.F_OK)); + try expectError(error.FileNotFound, posix.access(io, file_path, posix.F_OK)); } { @@ -786,7 +788,7 @@ test "access smoke test" { const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); defer a.free(file_path); - try posix.access(file_path, posix.F_OK); + try posix.access(io, file_path, posix.F_OK); } } diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index a5c55d3522a763d06ff73bf3536d6277901020d7..f3dc73838f773adc9d25e1e44495b2e81eb9c99e 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -357,7 +357,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F } if (self.sys_include_dir == null) { - if (search_dir.access(sys_include_dir_example_file, .{})) |_| { + if (search_dir.access(io, sys_include_dir_example_file, .{})) |_| { self.sys_include_dir = try allocator.dupeZ(u8, search_path); } else |err| switch (err) { error.FileNotFound => {}, @@ -402,7 +402,7 @@ fn findNativeIncludeDirWindows( }; defer dir.close(io); - dir.access("stdlib.h", .{}) catch |err| switch (err) { + dir.access(io, "stdlib.h", .{}) catch |err| switch (err) { error.FileNotFound => continue, else => return error.FileSystem, }; @@ -450,7 +450,7 @@ fn findNativeCrtDirWindows( }; defer dir.close(io); - dir.access("ucrt.lib", .{}) catch |err| switch (err) { + dir.access(io, "ucrt.lib", .{}) catch |err| switch (err) { error.FileNotFound => continue, else => return error.FileSystem, }; @@ -518,7 +518,7 @@ fn findNativeKernel32LibDir( }; defer dir.close(io); - dir.access("kernel32.lib", .{}) catch |err| switch (err) { + dir.access(io, "kernel32.lib", .{}) catch |err| switch (err) { error.FileNotFound => continue, else => return error.FileSystem, }; @@ -554,7 +554,7 @@ fn findNativeMsvcIncludeDir( }; defer dir.close(io); - dir.access("vcruntime.h", .{}) catch |err| switch (err) { + dir.access(io, "vcruntime.h", .{}) catch |err| switch (err) { error.FileNotFound => return error.LibCStdLibHeaderNotFound, else => return error.FileSystem, }; diff --git a/lib/std/zig/llvm/Builder.zig b/lib/std/zig/llvm/Builder.zig index 443bdcc391210ae1267a2981a03768b4618ed2bf..251c87defc21b0dfd19b0d3a71c0b66b0d32673e 100644 --- a/lib/std/zig/llvm/Builder.zig +++ b/lib/std/zig/llvm/Builder.zig @@ -9589,8 +9589,8 @@ pub fn printToFilePath(b: *Builder, io: Io, dir: Io.Dir, path: []const u8) !void try b.printToFile(io, file, &buffer); } -pub fn printToFile(b: *Builder, file: Io.File, buffer: []u8) !void { - var fw = file.writer(buffer); +pub fn printToFile(b: *Builder, io: Io, file: Io.File, buffer: []u8) !void { + var fw = file.writer(io, buffer); try print(b, &fw.interface); try fw.interface.flush(); } diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 9fa0546c3b7427c03214c12e275b28267094263a..feed21efec24601bc8577a36134f4f91329e2506 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -40,6 +40,7 @@ pub const GetExternalExecutorOptions = struct { /// Return whether or not the given host is capable of running executables of /// the other target. pub fn getExternalExecutor( + io: Io, host: *const std.Target, candidate: *const std.Target, options: GetExternalExecutorOptions, @@ -70,7 +71,7 @@ pub fn getExternalExecutor( if (os_match and cpu_ok) native: { if (options.link_libc) { if (candidate.dynamic_linker.get()) |candidate_dl| { - Io.Dir.cwd().access(candidate_dl, .{}) catch { + Io.Dir.cwd().access(io, candidate_dl, .{}) catch { bad_result = .{ .bad_dl = candidate_dl }; break :native; }; diff --git a/src/Compilation.zig b/src/Compilation.zig index 280d34cdbffe632cf203b5c50bb528b559df6ed9..36429a42f8e31489855cf25bb960b52e237a03d0 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -788,7 +788,7 @@ pub const Directories = struct { const local_cache: Cache.Directory = switch (local_cache_strat) { .override => |path| openUnresolved(arena, io, cwd, path, .@"local cache"), .search => d: { - const maybe_path = introspect.resolveSuitableLocalCacheDir(arena, cwd) catch |err| { + const maybe_path = introspect.resolveSuitableLocalCacheDir(arena, io, cwd) catch |err| { fatal("unable to resolve zig cache directory: {s}", .{@errorName(err)}); }; const path = maybe_path orelse break :d global_cache; diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index e54dcb9914374f0f1af4e1caf9d2fc2348a827b3..860fb8974ec83b49c048e2752268e911cdda0881 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -418,7 +418,7 @@ pub fn run(f: *Fetch) RunError!void { const prefixed_pkg_sub_path = prefixed_pkg_sub_path_buffer[0 .. 2 + hash_slice.len]; const prefix_len: usize = if (f.job_queue.read_only) "p/".len else 0; const pkg_sub_path = prefixed_pkg_sub_path[prefix_len..]; - if (cache_root.handle.access(pkg_sub_path, .{})) |_| { + if (cache_root.handle.access(io, pkg_sub_path, .{})) |_| { assert(f.lazy_status != .unavailable); f.package_root = .{ .root_dir = cache_root, @@ -637,8 +637,9 @@ pub fn computedPackageHash(f: *const Fetch) Package.Hash { /// `computeHash` gets a free check for the existence of `build.zig`, but when /// not computing a hash, we need to do a syscall to check for it. fn checkBuildFileExistence(f: *Fetch) RunError!void { + const io = f.job_queue.io; const eb = &f.error_bundle; - if (f.package_root.access(Package.build_zig_basename, .{})) |_| { + if (f.package_root.access(io, Package.build_zig_basename, .{})) |_| { f.has_build_zig = true; } else |err| switch (err) { error.FileNotFound => {}, diff --git a/src/introspect.zig b/src/introspect.zig index a56a214cbe793580df11f171cd1d5964a95917f5..3f8308961f4f1a136e362e2a1d4b2d0f859f6da5 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -202,11 +202,11 @@ pub const default_local_zig_cache_basename = ".zig-cache"; /// Searches upwards from `cwd` for a directory containing a `build.zig` file. /// If such a directory is found, returns the path to it joined to the `.zig_cache` name. /// Otherwise, returns `null`, indicating no suitable local cache location. -pub fn resolveSuitableLocalCacheDir(arena: Allocator, cwd: []const u8) Allocator.Error!?[]u8 { +pub fn resolveSuitableLocalCacheDir(arena: Allocator, io: Io, cwd: []const u8) Allocator.Error!?[]u8 { var cur_dir = cwd; while (true) { const joined = try fs.path.join(arena, &.{ cur_dir, Package.build_zig_basename }); - if (Io.Dir.cwd().access(joined, .{})) |_| { + if (Io.Dir.cwd().access(io, joined, .{})) |_| { return try fs.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename }); } else |err| switch (err) { error.FileNotFound => { diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index b3ca51e833f296f112722ba56d2a31f9507f62a9..93f88b968913efb7166191a393fabca33b608c75 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -242,7 +242,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const def_file_path = findDef(arena, comp.getTarget(), comp.dirs.zig_lib, lib_name) catch |err| switch (err) { + const def_file_path = findDef(arena, io, comp.getTarget(), comp.dirs.zig_lib, lib_name) catch |err| switch (err) { error.FileNotFound => { log.debug("no {s}.def file available to make a DLL import {s}.lib", .{ lib_name, lib_name }); // In this case we will end up putting foo.lib onto the linker line and letting the linker @@ -402,11 +402,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { pub fn libExists( allocator: Allocator, + io: Io, target: *const std.Target, zig_lib_directory: Cache.Directory, lib_name: []const u8, ) !bool { - const s = findDef(allocator, target, zig_lib_directory, lib_name) catch |err| switch (err) { + const s = findDef(allocator, io, target, zig_lib_directory, lib_name) catch |err| switch (err) { error.FileNotFound => return false, else => |e| return e, }; @@ -418,6 +419,7 @@ pub fn libExists( /// see if a .def file exists. fn findDef( allocator: Allocator, + io: Io, target: *const std.Target, zig_lib_directory: Cache.Directory, lib_name: []const u8, @@ -443,7 +445,7 @@ fn findDef( } else { try override_path.print(fmt_path, .{ lib_path, lib_name }); } - if (Io.Dir.cwd().access(override_path.items, .{})) |_| { + if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| { return override_path.toOwnedSlice(); } else |err| switch (err) { error.FileNotFound => {}, @@ -460,7 +462,7 @@ fn findDef( } else { try override_path.print(fmt_path, .{lib_name}); } - if (Io.Dir.cwd().access(override_path.items, .{})) |_| { + if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| { return override_path.toOwnedSlice(); } else |err| switch (err) { error.FileNotFound => {}, @@ -477,7 +479,7 @@ fn findDef( } else { try override_path.print(fmt_path, .{lib_name}); } - if (Io.Dir.cwd().access(override_path.items, .{})) |_| { + if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| { return override_path.toOwnedSlice(); } else |err| switch (err) { error.FileNotFound => {}, diff --git a/src/link/C.zig b/src/link/C.zig index a001f8fdd9f30f291ec35b3f942458ff1401377d..db5acebf07e9f8b94c4a3b13148dbad07bc3dcc5 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -371,6 +371,7 @@ pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.P const comp = self.base.comp; const diags = &comp.link_diags; const gpa = comp.gpa; + const io = comp.io; const zcu = self.base.comp.zcu.?; const ip = &zcu.intern_pool; const pt: Zcu.PerThread = .activate(zcu, tid); @@ -509,7 +510,7 @@ pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.P const file = self.base.file.?; file.setEndPos(f.file_size) catch |err| return diags.fail("failed to allocate file: {s}", .{@errorName(err)}); - var fw = file.writer(&.{}); + var fw = file.writer(io, &.{}); var w = &fw.interface; w.writeVecAll(f.all_buffers.items) catch |err| switch (err) { error.WriteFailed => return diags.fail("failed to write to '{f}': {s}", .{ diff --git a/src/link/Lld.zig b/src/link/Lld.zig index b25b9da9d99ea4406488a5407de17cef6878915c..ca15a38bb094486d5537716cafe101a890f80980 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -359,6 +359,7 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void { fn coffLink(lld: *Lld, arena: Allocator) !void { const comp = lld.base.comp; const gpa = comp.gpa; + const io = comp.io; const base = &lld.base; const coff = &lld.ofmt.coff; @@ -718,13 +719,13 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena)); continue; } - if (try findLib(arena, lib_basename, coff.lib_directories)) |full_path| { + if (try findLib(arena, io, lib_basename, coff.lib_directories)) |full_path| { argv.appendAssumeCapacity(full_path); continue; } if (target.abi.isGnu()) { const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); - if (try findLib(arena, fallback_name, coff.lib_directories)) |full_path| { + if (try findLib(arena, io, fallback_name, coff.lib_directories)) |full_path| { argv.appendAssumeCapacity(full_path); continue; } @@ -741,9 +742,9 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { try spawnLld(comp, arena, argv.items); } } -fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Cache.Directory) !?[]const u8 { +fn findLib(arena: Allocator, io: Io, name: []const u8, lib_directories: []const Cache.Directory) !?[]const u8 { for (lib_directories) |lib_directory| { - lib_directory.handle.access(name, .{}) catch |err| switch (err) { + lib_directory.handle.access(io, name, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 78e035e2adaacc5c6ac2234cb4afdafff9f70f2b..6cd906340a04629f08dbc59c34292eb956120113 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -829,7 +829,8 @@ pub fn resolveLibSystem( comp: *Compilation, out_libs: anytype, ) !void { - const diags = &self.base.comp.link_diags; + const io = comp.io; + const diags = &comp.link_diags; var test_path = std.array_list.Managed(u8).init(arena); var checked_paths = std.array_list.Managed([]const u8).init(arena); @@ -838,16 +839,16 @@ pub fn resolveLibSystem( if (self.sdk_layout) |sdk_layout| switch (sdk_layout) { .sdk => { const dir = try fs.path.join(arena, &.{ comp.sysroot.?, "usr", "lib" }); - if (try accessLibPath(arena, &test_path, &checked_paths, dir, "System")) break :success; + if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success; }, .vendored => { const dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "darwin" }); - if (try accessLibPath(arena, &test_path, &checked_paths, dir, "System")) break :success; + if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success; }, }; for (self.lib_directories) |directory| { - if (try accessLibPath(arena, &test_path, &checked_paths, directory.path orelse ".", "System")) break :success; + if (try accessLibPath(arena, io, &test_path, &checked_paths, directory.path orelse ".", "System")) break :success; } diags.addMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{}); @@ -1074,6 +1075,7 @@ fn isHoisted(self: *MachO, install_name: []const u8) bool { /// TODO delete this, libraries must be instead resolved when instantiating the compilation pipeline fn accessLibPath( arena: Allocator, + io: Io, test_path: *std.array_list.Managed(u8), checked_paths: *std.array_list.Managed([]const u8), search_dir: []const u8, @@ -1085,7 +1087,7 @@ fn accessLibPath( test_path.clearRetainingCapacity(); try test_path.print("{s}" ++ sep ++ "lib{s}{s}", .{ search_dir, name, ext }); try checked_paths.append(try arena.dupe(u8, test_path.items)); - Io.Dir.cwd().access(test_path.items, .{}) catch |err| switch (err) { + Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; @@ -1097,6 +1099,7 @@ fn accessLibPath( fn accessFrameworkPath( arena: Allocator, + io: Io, test_path: *std.array_list.Managed(u8), checked_paths: *std.array_list.Managed([]const u8), search_dir: []const u8, @@ -1113,7 +1116,7 @@ fn accessFrameworkPath( ext, }); try checked_paths.append(try arena.dupe(u8, test_path.items)); - Io.Dir.cwd().access(test_path.items, .{}) catch |err| switch (err) { + Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; @@ -1172,14 +1175,14 @@ fn parseDependentDylibs(self: *MachO) !void { // Framework for (framework_dirs) |dir| { test_path.clearRetainingCapacity(); - if (try accessFrameworkPath(arena, &test_path, &checked_paths, dir, stem)) break :full_path test_path.items; + if (try accessFrameworkPath(arena, io, &test_path, &checked_paths, dir, stem)) break :full_path test_path.items; } // Library const lib_name = eatPrefix(stem, "lib") orelse stem; for (lib_directories) |lib_directory| { test_path.clearRetainingCapacity(); - if (try accessLibPath(arena, &test_path, &checked_paths, lib_directory.path orelse ".", lib_name)) break :full_path test_path.items; + if (try accessLibPath(arena, io, &test_path, &checked_paths, lib_directory.path orelse ".", lib_name)) break :full_path test_path.items; } } @@ -1194,7 +1197,7 @@ fn parseDependentDylibs(self: *MachO) !void { try test_path.print("{s}{s}", .{ path, ext }); } try checked_paths.append(try arena.dupe(u8, test_path.items)); - Io.Dir.cwd().access(test_path.items, .{}) catch |err| switch (err) { + Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; diff --git a/src/main.zig b/src/main.zig index a063591b64e5b44ce61dfd02b92e5e6205b0f9f5..99850f5ffe95525cb7e0fb5bbfc5d41cfaf832c4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3208,6 +3208,7 @@ fn buildOutputType( for (create_module.framework_dirs.items) |framework_dir_path| { if (try accessFrameworkPath( + io, &test_path, &checked_paths, framework_dir_path, @@ -6626,7 +6627,7 @@ fn warnAboutForeignBinaries( const host_query: std.Target.Query = .{}; const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query); - switch (std.zig.system.getExternalExecutor(&host_target, target, .{ .link_libc = link_libc })) { + switch (std.zig.system.getExternalExecutor(io, &host_target, target, .{ .link_libc = link_libc })) { .native => return, .rosetta => { const host_name = try host_target.zigTriple(arena); @@ -6832,6 +6833,7 @@ const ClangSearchSanitizer = struct { }; fn accessFrameworkPath( + io: Io, test_path: *std.array_list.Managed(u8), checked_paths: *std.array_list.Managed(u8), framework_dir_path: []const u8, @@ -6845,7 +6847,7 @@ fn accessFrameworkPath( framework_dir_path, framework_name, framework_name, ext, }); try checked_paths.print("\n {s}", .{test_path.items}); - Io.Dir.cwd().access(test_path.items, .{}) catch |err| switch (err) { + Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| fatal("unable to search for {s} framework '{s}': {s}", .{ ext, test_path.items, @errorName(e), @@ -7280,7 +7282,7 @@ fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !Build var dirname: []const u8 = cwd_path; while (true) { const joined_path = try fs.path.join(arena, &[_][]const u8{ dirname, build_zig_basename }); - if (Io.Dir.cwd().access(joined_path, .{})) |_| { + if (Io.Dir.cwd().access(io, joined_path, .{})) |_| { const dir = Io.Dir.cwd().openDir(io, dirname, .{}) catch |err| { fatal("unable to open directory while searching for build.zig file, '{s}': {s}", .{ dirname, @errorName(err) }); }; -- 2.54.0 From 4218344dd3178f2fd3d9d00e9ff6895ee344df6d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 17:21:52 -0800 Subject: [PATCH 024/195] std.Build.Cache: remove readSmallFile and writeSmallFile These were to support optimizations involving detecting when to avoid calling into LLD, which are no longer implemented. --- lib/compiler/build_runner.zig | 2 +- lib/compiler/reduce.zig | 12 ++++-- lib/compiler/resinator/main.zig | 2 +- lib/compiler/std-docs.zig | 2 +- lib/std/Build/Cache.zig | 36 +++--------------- lib/std/Build/Step/ConfigHeader.zig | 2 +- lib/std/Build/Step/Run.zig | 2 +- lib/std/Build/Step/UpdateSourceFiles.zig | 2 +- lib/std/Build/Step/WriteFile.zig | 2 +- lib/std/Build/WebServer.zig | 2 +- lib/std/debug.zig | 4 +- lib/std/fs/test.zig | 48 ++++++++++++++---------- lib/std/posix/test.zig | 2 +- src/Compilation.zig | 4 +- src/libs/freebsd.zig | 4 +- src/libs/glibc.zig | 4 +- src/libs/netbsd.zig | 2 +- src/main.zig | 6 +-- 18 files changed, 63 insertions(+), 75 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 36c73e96eb815d9f24b6cff0abba3873c9e231c0..9150d8447067fd2cf98ceefcf84a7bd40782addf 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -459,7 +459,7 @@ pub fn main() !void { } const s = std.fs.path.sep_str; const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{})); - local_cache_directory.handle.writeFile(.{ + local_cache_directory.handle.writeFile(io, .{ .sub_path = tmp_sub_path, .data = buffer.items, .flags = .{ .exclusive = true }, diff --git a/lib/compiler/reduce.zig b/lib/compiler/reduce.zig index d3f33ad81af2ca2dcb9b14f711bb94cf8d22f9e5..0bfa1902abd813074748737dde95b62a5242455c 100644 --- a/lib/compiler/reduce.zig +++ b/lib/compiler/reduce.zig @@ -55,6 +55,10 @@ pub fn main() !void { var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; const gpa = general_purpose_allocator.allocator(); + var threaded: std.Io.Threaded = .init(gpa); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(arena); var opt_checker_path: ?[]const u8 = null; @@ -233,12 +237,12 @@ pub fn main() !void { } } - try Io.Dir.cwd().writeFile(.{ .sub_path = root_source_file_path, .data = rendered.written() }); + try Io.Dir.cwd().writeFile(io, .{ .sub_path = root_source_file_path, .data = rendered.written() }); // std.debug.print("trying this code:\n{s}\n", .{rendered.items}); const interestingness = try runCheck(arena, interestingness_argv.items); - std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{ - subset_size, @tagName(interestingness), start_index, transformations.items.len, + std.debug.print("{d} random transformations: {t}. {d}/{d}\n", .{ + subset_size, interestingness, start_index, transformations.items.len, }); switch (interestingness) { .interesting => { @@ -274,7 +278,7 @@ pub fn main() !void { fixups.clearRetainingCapacity(); rendered.clearRetainingCapacity(); try tree.render(gpa, &rendered.writer, fixups); - try Io.Dir.cwd().writeFile(.{ .sub_path = root_source_file_path, .data = rendered.written() }); + try Io.Dir.cwd().writeFile(io, .{ .sub_path = root_source_file_path, .data = rendered.written() }); return std.process.cleanExit(); } diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 1a6ef5eea35786cea6e41f8c027f39268ff7e9e4..afe1dcbe91ddb8f63a2b0e2a8a2d6d55e3de6396 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -212,7 +212,7 @@ pub fn main() !void { try output_file.writeAll(full_input); }, .filename => |output_filename| { - try Io.Dir.cwd().writeFile(.{ .sub_path = output_filename, .data = full_input }); + try Io.Dir.cwd().writeFile(io, .{ .sub_path = output_filename, .data = full_input }); }, } return; diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index f1382e6eaeb86e71a933659c87b772787d7a4159..8f02f05958bfeb0da68e05534a46ed5b89a89209 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -233,7 +233,7 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { .interface = std.Io.File.Reader.initInterface(&.{}), .size = stat.size, }; - try archiver.writeFile(entry.path, &file_reader, stat.mtime); + try archiver.writeFile(io, entry.path, &file_reader, stat.mtime); } { diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index dab1926f534006dcc600b1da329e3d941edb2960..f7c4d729bca32bf8b0eb7ec178c3cb3d853ba353 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -1276,30 +1276,6 @@ pub const Manifest = struct { } }; -/// On operating systems that support symlinks, does a readlink. On other operating systems, -/// uses the file contents. Windows supports symlinks but only with elevated privileges, so -/// it is treated as not supporting symlinks. -pub fn readSmallFile(dir: Io.Dir, sub_path: []const u8, buffer: []u8) ![]u8 { - if (builtin.os.tag == .windows) { - return dir.readFile(sub_path, buffer); - } else { - return dir.readLink(sub_path, buffer); - } -} - -/// On operating systems that support symlinks, does a symlink. On other operating systems, -/// uses the file contents. Windows supports symlinks but only with elevated privileges, so -/// it is treated as not supporting symlinks. -/// `data` must be a valid UTF-8 encoded file path and 255 bytes or fewer. -pub fn writeSmallFile(dir: Io.Dir, sub_path: []const u8, data: []const u8) !void { - assert(data.len <= 255); - if (builtin.os.tag == .windows) { - return dir.writeFile(.{ .sub_path = sub_path, .data = data }); - } else { - return dir.symLink(data, sub_path, .{}); - } -} - fn hashFile(io: Io, file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.ReadPositionalError!void { var buffer: [2048]u8 = undefined; var hasher = hasher_init; @@ -1338,7 +1314,7 @@ test "cache file and then recall it" { const temp_file = "test.txt"; const temp_manifest_dir = "temp_manifest_dir"; - try tmp.dir.writeFile(.{ .sub_path = temp_file, .data = "Hello, world!\n" }); + try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = "Hello, world!\n" }); // Wait for file timestamps to tick const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); @@ -1404,7 +1380,7 @@ test "check that changing a file makes cache fail" { const original_temp_file_contents = "Hello, world!\n"; const updated_temp_file_contents = "Hello, world; but updated!\n"; - try tmp.dir.writeFile(.{ .sub_path = temp_file, .data = original_temp_file_contents }); + try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = original_temp_file_contents }); // Wait for file timestamps to tick const initial_time = try testGetCurrentFileTimestamp(tmp.dir); @@ -1441,7 +1417,7 @@ test "check that changing a file makes cache fail" { try ch.writeManifest(); } - try tmp.dir.writeFile(.{ .sub_path = temp_file, .data = updated_temp_file_contents }); + try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = updated_temp_file_contents }); { var ch = cache.obtain(); @@ -1521,8 +1497,8 @@ test "Manifest with files added after initial hash work" { const temp_file2 = "cache_hash_post_file_test2.txt"; const temp_manifest_dir = "cache_hash_post_file_manifest_dir"; - try tmp.dir.writeFile(.{ .sub_path = temp_file1, .data = "Hello, world!\n" }); - try tmp.dir.writeFile(.{ .sub_path = temp_file2, .data = "Hello world the second!\n" }); + try tmp.dir.writeFile(io, .{ .sub_path = temp_file1, .data = "Hello, world!\n" }); + try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second!\n" }); // Wait for file timestamps to tick const initial_time = try testGetCurrentFileTimestamp(tmp.dir); @@ -1573,7 +1549,7 @@ test "Manifest with files added after initial hash work" { try testing.expect(mem.eql(u8, &digest1, &digest2)); // Modify the file added after initial hash - try tmp.dir.writeFile(.{ .sub_path = temp_file2, .data = "Hello world the second, updated\n" }); + try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second, updated\n" }); // Wait for file timestamps to tick const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir); diff --git a/lib/std/Build/Step/ConfigHeader.zig b/lib/std/Build/Step/ConfigHeader.zig index f377959610925ac15eea5b5b48df0dfa70064877..589110d4c4bde01c4ae6d5a2c91088129e5bb2f8 100644 --- a/lib/std/Build/Step/ConfigHeader.zig +++ b/lib/std/Build/Step/ConfigHeader.zig @@ -264,7 +264,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { }); }; - b.cache_root.handle.writeFile(.{ .sub_path = sub_path, .data = output }) catch |err| { + b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = output }) catch |err| { return step.fail("unable to write file '{f}{s}': {s}", .{ b.cache_root, sub_path, @errorName(err), }); diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index f6b29635c179716c57256e4f3a52a8b0f7fd0c14..a1618beb02b0b902232d7d262c93fa6c44b33004 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1482,7 +1482,7 @@ fn runCommand( .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace), .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace), }; - b.cache_root.handle.writeFile(.{ .sub_path = sub_path, .data = data }) catch |err| { + b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = data }) catch |err| { return step.fail("unable to write file '{f}{s}': {s}", .{ b.cache_root, sub_path, @errorName(err), }); diff --git a/lib/std/Build/Step/UpdateSourceFiles.zig b/lib/std/Build/Step/UpdateSourceFiles.zig index 44c6ae1ed4c73d3f19b0ee76e208252dd46356c3..eb8a6a85ddde43d336af1721a6de4435af3c7e40 100644 --- a/lib/std/Build/Step/UpdateSourceFiles.zig +++ b/lib/std/Build/Step/UpdateSourceFiles.zig @@ -84,7 +84,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { } switch (output_source_file.contents) { .bytes => |bytes| { - b.build_root.handle.writeFile(.{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| { + b.build_root.handle.writeFile(io, .{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| { return step.fail("unable to write file '{f}{s}': {t}", .{ b.build_root, output_source_file.sub_path, err, }); diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index 94b04b42128a3969cf62af0d8484a72bd3368d7c..3d712fa1d45362bf4d3632a0bc4d7d9235d9e9bb 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -273,7 +273,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { } switch (file.contents) { .bytes => |bytes| { - cache_dir.writeFile(.{ .sub_path = file.sub_path, .data = bytes }) catch |err| { + cache_dir.writeFile(io, .{ .sub_path = file.sub_path, .data = bytes }) catch |err| { return step.fail("unable to write file '{f}{s}{c}{s}': {t}", .{ b.cache_root, cache_path, fs.path.sep, file.sub_path, err, }); diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 162d17f070458cfd4134e55fcb377d90505ee2ba..ed07d04d57f014778e7b7cfcc9e6c14314bab9de 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -523,7 +523,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons if (cached_cwd_path == null) cached_cwd_path = try std.process.getCwdAlloc(gpa); break :cwd cached_cwd_path.?; }; - try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds())); + try archiver.writeFile(io, path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds())); } // intentionally not calling `archiver.finishPedantically` diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 39207f938d0aeab24ae62deb56ecb7ada61db211..cb79bb7855bb18c322f58c7df328f62338b28c1b 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1243,7 +1243,7 @@ test printLineFromFile { { const path = try join(gpa, &.{ test_dir_path, "one_line.zig" }); defer gpa.free(path); - try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); + try test_dir.dir.writeFile(io, .{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); try expectError(error.EndOfFile, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 })); @@ -1254,7 +1254,7 @@ test printLineFromFile { { const path = try fs.path.join(gpa, &.{ test_dir_path, "three_lines.zig" }); defer gpa.free(path); - try test_dir.dir.writeFile(.{ + try test_dir.dir.writeFile(io, .{ .sub_path = "three_lines.zig", .data = \\1 diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 59bacff2d0937917ace8985f284fef00a433a96e..59e0990eb037903d972af702e1c3cb07db1bc94f 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -184,7 +184,7 @@ test "Dir.readLink" { fn impl(ctx: *TestContext) !void { // Create some targets const file_target_path = try ctx.transformPath("file.txt"); - try ctx.dir.writeFile(.{ .sub_path = file_target_path, .data = "nonsense" }); + try ctx.dir.writeFile(io, .{ .sub_path = file_target_path, .data = "nonsense" }); const dir_target_path = try ctx.transformPath("subdir"); try ctx.dir.makeDir(dir_target_path); @@ -487,11 +487,13 @@ test "readLinkAbsolute" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .openbsd) return error.SkipZigTest; + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); // Create some targets - try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" }); + try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = "nonsense" }); try tmp.dir.makeDir("subdir"); // Get base abs path @@ -708,6 +710,7 @@ test "Dir.realpath smoke test" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const allocator = ctx.arena.allocator(); const test_file_path = try ctx.transformPath("test_file"); const test_dir_path = try ctx.transformPath("test_dir"); @@ -720,7 +723,7 @@ test "Dir.realpath smoke test" { try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf)); // Now create the file and dir - try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" }); + try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); try ctx.dir.makeDir(test_dir_path); const base_path = try ctx.transformPath("."); @@ -803,11 +806,12 @@ test "readFileAlloc" { test "Dir.statFile" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const test_file_name = try ctx.transformPath("test_file"); try testing.expectError(error.FileNotFound, ctx.dir.statFile(test_file_name)); - try ctx.dir.writeFile(.{ .sub_path = test_file_name, .data = "" }); + try ctx.dir.writeFile(io, .{ .sub_path = test_file_name, .data = "" }); const stat = try ctx.dir.statFile(test_file_name); try testing.expectEqual(File.Kind.file, stat.kind); @@ -925,6 +929,7 @@ test "makeOpenPath parent dirs do not exist" { test "deleteDir" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const test_dir_path = try ctx.transformPath("test_dir"); const test_file_path = try ctx.transformPath("test_dir" ++ fs.path.sep_str ++ "test_file"); @@ -933,7 +938,7 @@ test "deleteDir" { // deleting a non-empty directory try ctx.dir.makeDir(test_dir_path); - try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" }); + try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path)); // deleting an empty directory @@ -1217,7 +1222,7 @@ test "deleteTree on a symlink" { defer tmp.cleanup(); // Symlink to a file - try tmp.dir.writeFile(.{ .sub_path = "file", .data = "" }); + try tmp.dir.writeFile(io, .{ .sub_path = "file", .data = "" }); try setupSymlink(tmp.dir, "file", "filelink", .{}); try tmp.dir.deleteTree("filelink"); @@ -1241,11 +1246,11 @@ test "makePath, put some files in it, deleteTree" { const dir_path = try ctx.transformPath("os_test_tmp"); try ctx.dir.makePath(io, try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); - try ctx.dir.writeFile(.{ + try ctx.dir.writeFile(io, .{ .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), .data = "nonsense", }); - try ctx.dir.writeFile(.{ + try ctx.dir.writeFile(io, .{ .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), .data = "blah", }); @@ -1264,11 +1269,11 @@ test "makePath, put some files in it, deleteTreeMinStackSize" { const dir_path = try ctx.transformPath("os_test_tmp"); try ctx.dir.makePath(io, try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); - try ctx.dir.writeFile(.{ + try ctx.dir.writeFile(io, .{ .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), .data = "nonsense", }); - try ctx.dir.writeFile(.{ + try ctx.dir.writeFile(io, .{ .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), .data = "blah", }); @@ -1298,7 +1303,7 @@ test "makePath but sub_path contains pre-existing file" { defer tmp.cleanup(); try tmp.dir.makeDir("foo"); - try tmp.dir.writeFile(.{ .sub_path = "foo/bar", .data = "" }); + try tmp.dir.writeFile(io, .{ .sub_path = "foo/bar", .data = "" }); try testing.expectError(error.NotDir, tmp.dir.makePath(io, "foo/bar/baz")); } @@ -1400,7 +1405,7 @@ fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8) !vo var maxed_dir = try iterable_dir.makeOpenPath(maxed_filename, .{}); defer maxed_dir.close(io); - try maxed_dir.writeFile(.{ .sub_path = maxed_filename, .data = "" }); + try maxed_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" }); var walker = try iterable_dir.walk(testing.allocator); defer walker.deinit(); @@ -1513,7 +1518,7 @@ test "setEndPos" { defer tmp.cleanup(); const file_name = "afile.txt"; - try tmp.dir.writeFile(.{ .sub_path = file_name, .data = "ninebytes" }); + try tmp.dir.writeFile(io, .{ .sub_path = file_name, .data = "ninebytes" }); const f = try tmp.dir.openFile(io, file_name, .{ .mode = .read_write }); defer f.close(io); @@ -1563,7 +1568,7 @@ test "access file" { try ctx.dir.makePath(io, dir_path); try testing.expectError(error.FileNotFound, ctx.dir.access(io, file_path, .{})); - try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "" }); + try ctx.dir.writeFile(io, .{ .sub_path = file_path, .data = "" }); try ctx.dir.access(io, file_path, .{}); try ctx.dir.deleteTree(dir_path); } @@ -1659,12 +1664,13 @@ test "sendfile with buffered data" { test "copyFile" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP"; const src_file = try ctx.transformPath("tmp_test_copy_file.txt"); const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt"); const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt"); - try ctx.dir.writeFile(.{ .sub_path = src_file, .data = data }); + try ctx.dir.writeFile(io, .{ .sub_path = src_file, .data = data }); defer ctx.dir.deleteFile(src_file) catch {}; try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{}); @@ -2050,7 +2056,7 @@ test "'.' and '..' in Io.Dir functions" { renamed_file.close(io); try ctx.dir.deleteFile(rename_path); - try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" }); + try ctx.dir.writeFile(io, .{ .sub_path = update_path, .data = "something" }); var dir = ctx.dir; const prev_status = try dir.updateFile(io, file_path, dir, update_path, .{}); try testing.expectEqual(Io.Dir.PrevStatus.stale, prev_status); @@ -2187,7 +2193,7 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path)); try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path)); - try testing.expectError(expected_err, ctx.dir.writeFile(.{ .sub_path = invalid_path, .data = "" })); + try testing.expectError(expected_err, ctx.dir.writeFile(io, .{ .sub_path = invalid_path, .data = "" })); try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{})); @@ -2304,7 +2310,7 @@ test "seekBy" { var tmp_dir = testing.tmpDir(.{}); defer tmp_dir.cleanup(); - try tmp_dir.dir.writeFile(.{ .sub_path = "blah.txt", .data = "let's test seekBy" }); + try tmp_dir.dir.writeFile(io, .{ .sub_path = "blah.txt", .data = "let's test seekBy" }); const f = try tmp_dir.dir.openFile(io, "blah.txt", .{ .mode = .read_only }); defer f.close(io); var reader = f.readerStreaming(io, &.{}); @@ -2350,7 +2356,7 @@ test "File.Writer sendfile with buffered contents" { defer tmp_dir.cleanup(); { - try tmp_dir.dir.writeFile(.{ .sub_path = "a", .data = "bcd" }); + try tmp_dir.dir.writeFile(io, .{ .sub_path = "a", .data = "bcd" }); const in = try tmp_dir.dir.openFile(io, "a", .{}); defer in.close(io); const out = try tmp_dir.dir.createFile(io, "b", .{}); @@ -2391,11 +2397,13 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { } test "readlinkat" { + const io = testing.io; + var tmp = tmpDir(.{}); defer tmp.cleanup(); // create file - try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" }); + try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = "nonsense" }); // create a symbolic link if (native_os == .windows) { diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index af17f1f9ff3e8360143b9d77623eacacdfc0b420..bacfdacf83561ad440f54b8982bfce7b18ff551f 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -145,7 +145,7 @@ test "linkat with different directories" { const subdir = try tmp.dir.makeOpenPath("subdir", .{}); defer tmp.dir.deleteFile(target_name) catch {}; - try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" }); + try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" }); // Test 1: link from file in subdir back up to target in parent directory try posix.linkat(tmp.dir.handle, target_name, subdir.handle, link_name, 0); diff --git a/src/Compilation.zig b/src/Compilation.zig index 36429a42f8e31489855cf25bb960b52e237a03d0..7fe217b38583869d96bb844f636a8407037fe80f 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -5715,7 +5715,7 @@ pub fn translateC( const out_h_sub_path = tmp_sub_path ++ fs.path.sep_str ++ cimport_basename; const out_h_path = try comp.dirs.local_cache.join(arena, &.{out_h_sub_path}); if (comp.verbose_cimport) log.info("writing C import source to {s}", .{out_h_path}); - try cache_dir.writeFile(.{ .sub_path = out_h_sub_path, .data = c_src }); + try cache_dir.writeFile(io, .{ .sub_path = out_h_sub_path, .data = c_src }); break :path out_h_path; }, .path => |p| p, @@ -6572,7 +6572,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 resource_id, resource_type, fmtRcEscape(src_path), }); - try o_dir.writeFile(.{ .sub_path = rc_basename, .data = input }); + try o_dir.writeFile(io, .{ .sub_path = rc_basename, .data = input }); var argv = std.array_list.Managed([]const u8).init(comp.gpa); defer argv.deinit(); diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index cfd8d5554c9ee3b3159a9857d576f827b6fe6a3d..77bd4372d0f6d15e8dbbc2232fe6574aef53ded8 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -520,7 +520,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye for (metadata.all_versions[0 .. target_ver_index + 1]) |ver| { try map_contents.print("FBSD_{d}.{d} {{ }};\n", .{ ver.major, ver.minor }); } - try o_directory.handle.writeFile(.{ .sub_path = all_map_basename, .data = map_contents.items }); + try o_directory.handle.writeFile(io, .{ .sub_path = all_map_basename, .data = map_contents.items }); map_contents.deinit(); } @@ -974,7 +974,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "stdthreads", etc. const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable; - try o_directory.handle.writeFile(.{ .sub_path = asm_file_basename, .data = stubs_asm.items }); + try o_directory.handle.writeFile(io, .{ .sub_path = asm_file_basename, .data = stubs_asm.items }); try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node); } diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index e3d8ce1f7f021fe0da06e979c706608da9053bee..a60dc921bea0b7be434ea266c3c8a83d74f8ef52 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -759,7 +759,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye try map_contents.print("GLIBC_{d}.{d}.{d} {{ }};\n", .{ ver.major, ver.minor, ver.patch }); } } - try o_directory.handle.writeFile(.{ .sub_path = all_map_basename, .data = map_contents.items }); + try o_directory.handle.writeFile(io, .{ .sub_path = all_map_basename, .data = map_contents.items }); map_contents.deinit(); // The most recent allocation of an arena can be freed :) } @@ -1118,7 +1118,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc. const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable; - try o_directory.handle.writeFile(.{ .sub_path = asm_file_basename, .data = stubs_asm.items }); + try o_directory.handle.writeFile(io, .{ .sub_path = asm_file_basename, .data = stubs_asm.items }); try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node); } diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index cb6a80d69d338a3fe9f517c07c1e7d9f04b65a1d..fd80616e9de0ddeac4bc66e9efa8449f88ba3254 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -628,7 +628,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc. const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable; - try o_directory.handle.writeFile(.{ .sub_path = asm_file_basename, .data = stubs_asm.items }); + try o_directory.handle.writeFile(io, .{ .sub_path = asm_file_basename, .data = stubs_asm.items }); try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node); } diff --git a/src/main.zig b/src/main.zig index 99850f5ffe95525cb7e0fb5bbfc5d41cfaf832c4..bef3a3efb5a83209d0ba90d3a3e0f152015541b5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -7164,7 +7164,7 @@ fn cmdFetch( try ast.render(gpa, &aw.writer, fixups); const rendered = aw.written(); - build_root.directory.handle.writeFile(.{ .sub_path = Package.Manifest.basename, .data = rendered }) catch |err| { + build_root.directory.handle.writeFile(io, .{ .sub_path = Package.Manifest.basename, .data = rendered }) catch |err| { fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err }); }; @@ -7207,7 +7207,7 @@ fn createDependenciesModule( { var tmp_dir = try dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}); defer tmp_dir.close(io); - try tmp_dir.writeFile(.{ .sub_path = basename, .data = source }); + try tmp_dir.writeFile(io, .{ .sub_path = basename, .data = source }); } var hh: Cache.HashHelper = .{}; @@ -7438,7 +7438,7 @@ const Templates = struct { i += 1; } - return out_dir.writeFile(.{ + return out_dir.writeFile(io, .{ .sub_path = template_path, .data = templates.buffer.items, .flags = .{ .exclusive = true }, -- 2.54.0 From 4be8be1d2bd6959efae7df95e3f5713adf953a42 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 17:45:17 -0800 Subject: [PATCH 025/195] update all rename() to rename(io) --- lib/compiler/build_runner.zig | 8 +++---- lib/compiler/objcopy.zig | 2 +- lib/std/Build/Cache.zig | 4 ++-- lib/std/Build/Fuzz.zig | 1 + lib/std/Build/Step/Compile.zig | 6 +++--- lib/std/Build/Step/Options.zig | 2 +- lib/std/Build/Step/Run.zig | 22 +++++++------------- lib/std/Build/WebServer.zig | 6 +++--- lib/std/Io.zig | 2 +- lib/std/Io/Dir.zig | 11 ++++++++-- lib/std/fs/test.zig | 38 +++++++++++++++++----------------- lib/std/posix/test.zig | 4 ++-- lib/std/tar.zig | 2 +- lib/std/zip.zig | 2 +- src/Compilation.zig | 6 +++--- src/Package/Fetch.zig | 12 +++++------ src/Package/Fetch/git.zig | 4 ++-- src/Zcu.zig | 14 +++++++++---- src/Zcu/PerThread.zig | 12 +++++------ src/libs/mingw.zig | 2 +- src/link.zig | 2 +- src/link/Lld.zig | 2 +- src/main.zig | 8 ++----- 23 files changed, 88 insertions(+), 84 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 9150d8447067fd2cf98ceefcf84a7bd40782addf..677158645d7023c45b3a7831f6b956d638d66fc6 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -478,14 +478,14 @@ pub fn main() !void { validateSystemLibraryOptions(builder); if (help_menu) { - var w = initStdoutWriter(); + var w = initStdoutWriter(io); printUsage(builder, w) catch return stdout_writer_allocation.err.?; w.flush() catch return stdout_writer_allocation.err.?; return; } if (steps_menu) { - var w = initStdoutWriter(); + var w = initStdoutWriter(io); printSteps(builder, w) catch return stdout_writer_allocation.err.?; w.flush() catch return stdout_writer_allocation.err.?; return; @@ -1847,7 +1847,7 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void { var stdio_buffer_allocation: [256]u8 = undefined; var stdout_writer_allocation: Io.File.Writer = undefined; -fn initStdoutWriter() *Writer { - stdout_writer_allocation = Io.File.stdout().writerStreaming(&stdio_buffer_allocation); +fn initStdoutWriter(io: Io) *Writer { + stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation); return &stdout_writer_allocation.interface; } diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 6473a10dd08a833846f5dc653f9bdb9400d983e8..d9b9b331861d37bebbe70b440627de3b29a6bc4d 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -183,7 +183,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void var output_file = try Io.Dir.cwd().createFile(io, output, .{ .mode = mode }); defer output_file.close(io); - var out = output_file.writer(&output_buffer); + var out = output_file.writer(io, &output_buffer); switch (out_fmt) { .hex, .raw => { diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index f7c4d729bca32bf8b0eb7ec178c3cb3d853ba353..2d6dbc02fa7ec49a5ce0ef22c9a6a6ac0d817ba7 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -1134,13 +1134,13 @@ pub const Manifest = struct { /// lock from exclusive to shared. pub fn writeManifest(self: *Manifest) !void { assert(self.have_exclusive_lock); - + const io = self.cache.io; const manifest_file = self.manifest_file.?; if (self.manifest_dirty) { self.manifest_dirty = false; var buffer: [4000]u8 = undefined; - var fw = manifest_file.writer(&buffer); + var fw = manifest_file.writer(io, &buffer); writeDirtyManifestToStream(self, &fw) catch |err| switch (err) { error.WriteFailed => return fw.err.?, else => |e| return e, diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index 8d5bc27f3a467b659792cba077c4d3ef94c76cca..8fedb7e0679cdf4c6ce48c9a05b5c772d917e69b 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -389,6 +389,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO const target = run_step.producer.?.rootModuleTarget(); var debug_info = std.debug.Info.load( fuzz.gpa, + io, rebuilt_exe_path, &gop.value_ptr.coverage, target.ofmt, diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 4f1e83a5736c783b58a9bc8b934ef5055c627971..dfa54609810b2f938c4fc531ede30d45982ddf13 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1709,7 +1709,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { defer b.cache_root.handle.deleteFile(io, tmp_path) catch { // It's fine if the temporary file can't be cleaned up. }; - b.cache_root.handle.rename(io, tmp_path, args_file) catch |rename_err| switch (rename_err) { + b.cache_root.handle.rename(tmp_path, b.cache_root.handle, args_file, io) catch |rename_err| switch (rename_err) { error.PathAlreadyExists => { // The args file was created by another concurrent build process. }, @@ -1827,14 +1827,14 @@ pub fn doAtomicSymLinks( // sym link for libfoo.so.1 to libfoo.so.1.2.3 const major_only_path = b.pathJoin(&.{ out_dir, filename_major_only }); const cwd: Io.Dir = .cwd(); - cwd.atomicSymLink(io, out_basename, major_only_path, .{}) catch |err| { + cwd.symLinkAtomic(io, out_basename, major_only_path, .{}) catch |err| { return step.fail("unable to symlink {s} -> {s}: {s}", .{ major_only_path, out_basename, @errorName(err), }); }; // sym link for libfoo.so to libfoo.so.1 const name_only_path = b.pathJoin(&.{ out_dir, filename_name_only }); - cwd.atomicSymLink(io, filename_major_only, name_only_path, .{}) catch |err| { + cwd.symLinkAtomic(io, filename_major_only, name_only_path, .{}) catch |err| { return step.fail("Unable to symlink {s} -> {s}: {s}", .{ name_only_path, filename_major_only, @errorName(err), }); diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 1416e0e916236ac7353c632b1af66c8c7a56aa33..676ea4d85189d6b314bd24d9ff8a9a6188f47d84 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -498,7 +498,7 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { }); }; - b.cache_root.handle.rename(io, tmp_sub_path, sub_path) catch |err| switch (err) { + b.cache_root.handle.rename(tmp_sub_path, b.cache_root.handle, sub_path, io) catch |err| switch (err) { error.PathAlreadyExists => { // Other process beat us to it. Clean up the temp file. b.cache_root.handle.deleteFile(io, tmp_sub_path) catch |e| { diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index a1618beb02b0b902232d7d262c93fa6c44b33004..e52dd21a96a242e779f649eafe32921d2fe08cfd 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1042,27 +1042,21 @@ fn make(step: *Step, options: Step.MakeOptions) !void { if (any_output) { const o_sub_path = "o" ++ fs.path.sep_str ++ &digest; - b.cache_root.handle.rename(tmp_dir_path, o_sub_path) catch |err| { + b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |err| { if (err == error.PathAlreadyExists) { b.cache_root.handle.deleteTree(o_sub_path) catch |del_err| { - return step.fail("unable to remove dir '{f}'{s}: {s}", .{ - b.cache_root, - tmp_dir_path, - @errorName(del_err), + return step.fail("unable to remove dir '{f}'{s}: {t}", .{ + b.cache_root, tmp_dir_path, del_err, }); }; - b.cache_root.handle.rename(tmp_dir_path, o_sub_path) catch |retry_err| { - return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{ - b.cache_root, tmp_dir_path, - b.cache_root, o_sub_path, - @errorName(retry_err), + b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |retry_err| { + return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{ + b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, retry_err, }); }; } else { - return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{ - b.cache_root, tmp_dir_path, - b.cache_root, o_sub_path, - @errorName(err), + return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{ + b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, err, }); } }; diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index ed07d04d57f014778e7b7cfcc9e6c14314bab9de..38a7a73588e500b41bad6af98f2abd092c097c40 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -523,7 +523,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons if (cached_cwd_path == null) cached_cwd_path = try std.process.getCwdAlloc(gpa); break :cwd cached_cwd_path.?; }; - try archiver.writeFile(io, path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds())); + try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds())); } // intentionally not calling `archiver.finishPedantically` @@ -587,7 +587,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim }); defer poller.deinit(); - try child.stdin.?.writeAll(@ptrCast(@as([]const std.zig.Client.Message.Header, &.{ + try child.stdin.?.writeStreamingAll(io, @ptrCast(@as([]const std.zig.Client.Message.Header, &.{ .{ .tag = .update, .bytes_len = 0 }, .{ .tag = .exit, .bytes_len = 0 }, }))); @@ -638,7 +638,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim child.stdin.?.close(io); child.stdin = null; - switch (try child.wait()) { + switch (try child.wait(io)) { .Exited => |code| { if (code != 0) { log.err( diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 7f5f049d348c77dc85ef09c695e15b2bd01664c8..193998f0371a6d012eedc7909c4302b0fc1d608a 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -677,7 +677,7 @@ pub const VTable = struct { dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void, dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void, dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void, - dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.RenameError!void, + dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void, dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize, dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void, dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 755ce924ad639b8b2b93bd15f8e5b1c899eac990..7d1f6212dcc2415bd62de8388266724254e479dd 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -501,7 +501,7 @@ pub const WriteFileError = File.Writer.Error || File.OpenError; pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void { var file = try dir.createFile(io, options.sub_path, options.flags); defer file.close(io); - try file.writeAll(io, options.data); + try file.writeStreamingAll(io, options.data); } pub const PrevStatus = enum { @@ -955,6 +955,13 @@ pub fn rename( return io.vtable.dirRename(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path); } +pub fn renameAbsolute(io: Io, old_path: []const u8, new_path: []const u8) RenameError!void { + assert(path.isAbsolute(old_path)); + assert(path.isAbsolute(new_path)); + const my_cwd = cwd(); + return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path); +} + /// Use with `Dir.symLink`, `Dir.symLinkAtomic`, and `symLinkAbsolute` to /// specify whether the symlink will point to a file or a directory. This value /// is ignored on all hosts except Windows where creating symlinks to different @@ -1053,7 +1060,7 @@ pub fn symLinkAtomic( temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer); if (dir.symLink(io, target_path, temp_path, flags)) { - return dir.rename(temp_path, dir, io, sym_link_path); + return dir.rename(temp_path, dir, sym_link_path, io); } else |err| switch (err) { error.PathAlreadyExists => continue, else => |e| return e, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 59e0990eb037903d972af702e1c3cb07db1bc94f..92992f91b48e17df9990f82eb6790d5ba30362db 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -961,14 +961,14 @@ test "Dir.rename files" { const missing_file_path = try ctx.transformPath("missing_file_name"); const something_else_path = try ctx.transformPath("something_else"); - try testing.expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, something_else_path)); + try testing.expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, ctx.dir, something_else_path, io)); // Renaming files const test_file_name = try ctx.transformPath("test_file"); const renamed_test_file_name = try ctx.transformPath("test_file_renamed"); var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true }); file.close(io); - try ctx.dir.rename(test_file_name, renamed_test_file_name); + try ctx.dir.rename(test_file_name, ctx.dir, renamed_test_file_name, io); // Ensure the file was renamed try testing.expectError(error.FileNotFound, ctx.dir.openFile(io, test_file_name, .{})); @@ -976,13 +976,13 @@ test "Dir.rename files" { file.close(io); // Rename to self succeeds - try ctx.dir.rename(renamed_test_file_name, renamed_test_file_name); + try ctx.dir.rename(renamed_test_file_name, ctx.dir, renamed_test_file_name, io); // Rename to existing file succeeds const existing_file_path = try ctx.transformPath("existing_file"); var existing_file = try ctx.dir.createFile(io, existing_file_path, .{ .read = true }); existing_file.close(io); - try ctx.dir.rename(renamed_test_file_name, existing_file_path); + try ctx.dir.rename(renamed_test_file_name, ctx.dir, existing_file_path, io); try testing.expectError(error.FileNotFound, ctx.dir.openFile(io, renamed_test_file_name, .{})); file = try ctx.dir.openFile(io, existing_file_path, .{}); @@ -1007,7 +1007,7 @@ test "Dir.rename directories" { // Renaming directories try ctx.dir.makeDir(test_dir_path); - try ctx.dir.rename(test_dir_path, test_dir_renamed_path); + try ctx.dir.rename(test_dir_path, ctx.dir, test_dir_renamed_path, io); // Ensure the directory was renamed try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{})); @@ -1019,7 +1019,7 @@ test "Dir.rename directories" { dir.close(io); const test_dir_renamed_again_path = try ctx.transformPath("test_dir_renamed_again"); - try ctx.dir.rename(test_dir_renamed_path, test_dir_renamed_again_path); + try ctx.dir.rename(test_dir_renamed_path, ctx.dir, test_dir_renamed_again_path, io); // Ensure the directory was renamed and the file still exists in it try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_renamed_path, .{})); @@ -1044,7 +1044,7 @@ test "Dir.rename directory onto empty dir" { try ctx.dir.makeDir(test_dir_path); try ctx.dir.makeDir(target_dir_path); - try ctx.dir.rename(test_dir_path, target_dir_path); + try ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io); // Ensure the directory was renamed try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{})); @@ -1072,7 +1072,7 @@ test "Dir.rename directory onto non-empty dir" { target_dir.close(io); // Rename should fail with PathAlreadyExists if target_dir is non-empty - try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, target_dir_path)); + try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io)); // Ensure the directory was not renamed var dir = try ctx.dir.openDir(io, test_dir_path, .{}); @@ -1094,8 +1094,8 @@ test "Dir.rename file <-> dir" { var file = try ctx.dir.createFile(io, test_file_path, .{ .read = true }); file.close(io); try ctx.dir.makeDir(test_dir_path); - try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path)); - try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path)); + try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, ctx.dir, test_dir_path, io)); + try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, ctx.dir, test_file_path, io)); } }.impl); } @@ -1114,7 +1114,7 @@ test "rename" { const renamed_test_file_name = "test_file_renamed"; var file = try tmp_dir1.dir.createFile(io, test_file_name, .{ .read = true }); file.close(io); - try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name); + try Dir.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name, io); // ensure the file was renamed try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(io, test_file_name, .{})); @@ -1492,7 +1492,7 @@ test "pwritev, preadv" { var src_file = try tmp.dir.createFile(io, "test.txt", .{ .read = true }); defer src_file.close(io); - var writer = src_file.writer(&.{}); + var writer = src_file.writer(io, &.{}); try writer.seekTo(16); try writer.interface.writeVecAll(&lines); @@ -1593,7 +1593,7 @@ test "sendfile" { var src_file = try dir.createFile(io, "sendfile1.txt", .{ .read = true }); defer src_file.close(io); { - var fw = src_file.writer(&.{}); + var fw = src_file.writer(io, &.{}); try fw.interface.writeVecAll(&vecs); } @@ -1610,7 +1610,7 @@ test "sendfile" { var written_buf: [100]u8 = undefined; var file_reader = src_file.reader(io, &.{}); var fallback_buffer: [50]u8 = undefined; - var file_writer = dest_file.writer(&fallback_buffer); + var file_writer = dest_file.writer(io, &fallback_buffer); try file_writer.interface.writeVecAll(&headers); try file_reader.seekTo(1); try testing.expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10))); @@ -1648,7 +1648,7 @@ test "sendfile with buffered data" { try file_reader.interface.fill(8); var fallback_buffer: [32]u8 = undefined; - var file_writer = dest_file.writer(&fallback_buffer); + var file_writer = dest_file.writer(io, &fallback_buffer); try std.testing.expectEqual(4, try file_writer.interface.sendFileAll(&file_reader, .limited(4))); @@ -2051,7 +2051,7 @@ test "'.' and '..' in Io.Dir functions" { try ctx.dir.access(io, file_path, .{}); try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{}); - try ctx.dir.rename(copy_path, rename_path); + try ctx.dir.rename(copy_path, ctx.dir, rename_path, io); const renamed_file = try ctx.dir.openFile(io, rename_path, .{}); renamed_file.close(io); try ctx.dir.deleteFile(rename_path); @@ -2175,7 +2175,7 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path)); - try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path)); + try testing.expectError(expected_err, ctx.dir.rename(invalid_path, ctx.dir, invalid_path, io)); try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{})); if (native_os == .wasi) { @@ -2208,7 +2208,7 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path)); } - try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path)); + try testing.expectError(expected_err, Dir.rename(ctx.dir, invalid_path, ctx.dir, invalid_path, io)); if (native_os != .wasi and ctx.path_type != .relative) { try testing.expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, .{})); @@ -2334,7 +2334,7 @@ test "seekTo flushes buffered data" { defer file.close(io); { var buf: [16]u8 = undefined; - var file_writer = File.writer(file, &buf); + var file_writer = file.writer(io, file, &buf); try file_writer.interface.writeAll(contents); try file_writer.seekTo(8); diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index bacfdacf83561ad440f54b8982bfce7b18ff551f..756ecfd63f3d557344dae0dc3699a9b2fe627c4b 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -690,7 +690,7 @@ test "rename smoke test" { // Rename the file const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_file" }); defer a.free(new_file_path); - try posix.rename(file_path, new_file_path); + try Io.Dir.renameAbsolute(file_path, new_file_path); } { @@ -717,7 +717,7 @@ test "rename smoke test" { // Rename the directory const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" }); defer a.free(new_file_path); - try posix.rename(file_path, new_file_path); + try Io.Dir.renameAbsolute(file_path, new_file_path); } { diff --git a/lib/std/tar.zig b/lib/std/tar.zig index b2a5306458a5e8cea48b5fd78a6172b5c6cfd231..5e7215f1d72b168a80655ebb85f330c3181e69ff 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -612,7 +612,7 @@ pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOp .file => { if (createDirAndFile(io, dir, file_name, fileMode(file.mode, options))) |fs_file| { defer fs_file.close(io); - var file_writer = fs_file.writer(&file_contents_buffer); + var file_writer = fs_file.writer(io, &file_contents_buffer); try it.streamRemaining(file, &file_writer.interface); try file_writer.interface.flush(); } else |err| { diff --git a/lib/std/zip.zig b/lib/std/zip.zig index acb3fc65abf191afeb505a465b1e66a58743728b..5a91b73d6a768cd2759f66481c2442e9ec6d5fd3 100644 --- a/lib/std/zip.zig +++ b/lib/std/zip.zig @@ -570,7 +570,7 @@ pub const Iterator = struct { }; defer out_file.close(io); var out_file_buffer: [1024]u8 = undefined; - var file_writer = out_file.writer(&out_file_buffer); + var file_writer = out_file.writer(io, &out_file_buffer); const local_data_file_offset: u64 = @as(u64, self.file_offset) + @as(u64, @sizeOf(LocalFileHeader)) + diff --git a/src/Compilation.zig b/src/Compilation.zig index 7fe217b38583869d96bb844f636a8407037fe80f..3a6ae263a5bdc93951b2f5bf9c271d4e9fe97347 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -5303,7 +5303,7 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { defer tar_file.close(io); var buffer: [1024]u8 = undefined; - var tar_file_writer = tar_file.writer(&buffer); + var tar_file_writer = tar_file.writer(io, &buffer); var seen_table: std.AutoArrayHashMapUnmanaged(*Package.Module, []const u8) = .empty; defer seen_table.deinit(comp.gpa); @@ -6448,7 +6448,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(io); const tmp_basename = fs.path.basename(out_obj_path); - try fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename); + try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename, io); break :blk digest; }; @@ -6696,7 +6696,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(io); const tmp_basename = fs.path.basename(out_res_path); - try fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename); + try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename, io); break :blk digest; }; diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 860fb8974ec83b49c048e2752268e911cdda0881..aaba0c28cf506cebf76ab548f5660cd37f2e9d24 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -567,7 +567,7 @@ fn runResource( .root_dir = cache_root, .sub_path = try std.fmt.allocPrint(arena, "p" ++ s ++ "{s}", .{computed_package_hash.toSlice()}), }; - renameTmpIntoCache(cache_root.handle, package_sub_path, f.package_root.sub_path) catch |err| { + renameTmpIntoCache(io, cache_root.handle, package_sub_path, f.package_root.sub_path) catch |err| { const src = try cache_root.join(arena, &.{tmp_dir_sub_path}); const dest = try cache_root.join(arena, &.{f.package_root.sub_path}); try eb.addRootErrorMessage(.{ .msg = try eb.printString( @@ -1319,7 +1319,7 @@ fn unzip( defer zip_file.close(io); var zip_file_buffer: [4096]u8 = undefined; var zip_file_reader = b: { - var zip_file_writer = zip_file.writer(&zip_file_buffer); + var zip_file_writer = zip_file.writer(io, &zip_file_buffer); _ = reader.streamRemaining(&zip_file_writer.interface) catch |err| switch (err) { error.ReadFailed => return error.ReadFailed, @@ -1370,7 +1370,7 @@ fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!U defer pack_file.close(io); var pack_file_buffer: [4096]u8 = undefined; var pack_file_reader = b: { - var pack_file_writer = pack_file.writer(&pack_file_buffer); + var pack_file_writer = pack_file.writer(io, &pack_file_buffer); const fetch_reader = &resource.fetch_stream.reader; _ = try fetch_reader.streamRemaining(&pack_file_writer.interface); try pack_file_writer.interface.flush(); @@ -1380,7 +1380,7 @@ fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!U var index_file = try pack_dir.createFile(io, "pkg.idx", .{ .read = true }); defer index_file.close(io); var index_file_buffer: [2000]u8 = undefined; - var index_file_writer = index_file.writer(&index_file_buffer); + var index_file_writer = index_file.writer(io, &index_file_buffer); { const index_prog_node = f.prog_node.start("Index pack", 0); defer index_prog_node.end(); @@ -1454,11 +1454,11 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void } } -pub fn renameTmpIntoCache(cache_dir: Io.Dir, tmp_dir_sub_path: []const u8, dest_dir_sub_path: []const u8) !void { +pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u8, dest_dir_sub_path: []const u8) !void { assert(dest_dir_sub_path[1] == fs.path.sep); var handled_missing_dir = false; while (true) { - cache_dir.rename(tmp_dir_sub_path, dest_dir_sub_path) catch |err| switch (err) { + cache_dir.rename(tmp_dir_sub_path, cache_dir, dest_dir_sub_path, io) catch |err| switch (err) { error.FileNotFound => { if (handled_missing_dir) return err; cache_dir.makeDir(dest_dir_sub_path[0..1]) catch |mkd_err| switch (mkd_err) { diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index 3f0ffb04cdf928fdaa92a43c05223451693c10c8..4d5bcfc84b16359254baa3868a98fa3c1c7eb4cd 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -1594,7 +1594,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u var index_file = try git_dir.dir.createFile(io, "testrepo.idx", .{ .read = true }); defer index_file.close(io); var index_file_buffer: [2000]u8 = undefined; - var index_file_writer = index_file.writer(&index_file_buffer); + var index_file_writer = index_file.writer(io, &index_file_buffer); try indexPack(testing.allocator, format, &pack_file_reader, &index_file_writer); // Arbitrary size limit on files read while checking the repository contents @@ -1730,7 +1730,7 @@ pub fn main() !void { var index_file = try git_dir.createFile(io, "idx", .{ .read = true }); defer index_file.close(io); var index_file_buffer: [4096]u8 = undefined; - var index_file_writer = index_file.writer(&index_file_buffer); + var index_file_writer = index_file.writer(io, &index_file_buffer); try indexPack(allocator, format, &pack_file_reader, &index_file_writer); std.debug.print("Starting checkout...\n", .{}); diff --git a/src/Zcu.zig b/src/Zcu.zig index 1f2fe89236a02d4f2f83125a63a46533a4091dee..2891da2407fa1744e477310387eb4bab22f271bc 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -2986,7 +2986,13 @@ pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_br: *Io.Reader return zir; } -pub fn saveZirCache(gpa: Allocator, cache_file: Io.File, stat: Io.File.Stat, zir: Zir) (Io.File.WriteError || Allocator.Error)!void { +pub fn saveZirCache( + gpa: Allocator, + io: Io, + cache_file: Io.File, + stat: Io.File.Stat, + zir: Zir, +) (Io.File.WriteError || Allocator.Error)!void { const safety_buffer = if (data_has_safety_tag) try gpa.alloc([8]u8, zir.instructions.len) else @@ -3020,13 +3026,13 @@ pub fn saveZirCache(gpa: Allocator, cache_file: Io.File, stat: Io.File.Stat, zir zir.string_bytes, @ptrCast(zir.extra), }; - var cache_fw = cache_file.writer(&.{}); + var cache_fw = cache_file.writer(io, &.{}); cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) { error.WriteFailed => return cache_fw.err.?, }; } -pub fn saveZoirCache(cache_file: Io.File, stat: Io.File.Stat, zoir: Zoir) Io.File.WriteError!void { +pub fn saveZoirCache(io: Io, cache_file: Io.File, stat: Io.File.Stat, zoir: Zoir) Io.File.WriteError!void { const header: Zoir.Header = .{ .nodes_len = @intCast(zoir.nodes.len), .extra_len = @intCast(zoir.extra.len), @@ -3050,7 +3056,7 @@ pub fn saveZoirCache(cache_file: Io.File, stat: Io.File.Stat, zoir: Zoir) Io.Fil @ptrCast(zoir.compile_errors), @ptrCast(zoir.error_notes), }; - var cache_fw = cache_file.writer(&.{}); + var cache_fw = cache_file.writer(io, &.{}); cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) { error.WriteFailed => return cache_fw.err.?, }; diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 74196705c389fc42a52ee0dc601155ee72e8235f..9d6a45ad2672c24bf4821bcc5becbafe4e03dbc6 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -278,18 +278,18 @@ pub fn updateFile( switch (file.getMode()) { .zig => { file.zir = try AstGen.generate(gpa, file.tree.?); - Zcu.saveZirCache(gpa, cache_file, stat, file.zir.?) catch |err| switch (err) { + Zcu.saveZirCache(gpa, io, cache_file, stat, file.zir.?) catch |err| switch (err) { error.OutOfMemory => |e| return e, - else => log.warn("unable to write cached ZIR code for {f} to {f}{s}: {s}", .{ - file.path.fmt(comp), cache_directory, &hex_digest, @errorName(err), + else => log.warn("unable to write cached ZIR code for {f} to {f}{s}: {t}", .{ + file.path.fmt(comp), cache_directory, &hex_digest, err, }), }; }, .zon => { file.zoir = try ZonGen.generate(gpa, file.tree.?, .{}); - Zcu.saveZoirCache(cache_file, stat, file.zoir.?) catch |err| { - log.warn("unable to write cached ZOIR code for {f} to {f}{s}: {s}", .{ - file.path.fmt(comp), cache_directory, &hex_digest, @errorName(err), + Zcu.saveZoirCache(io, cache_file, stat, file.zoir.?) catch |err| { + log.warn("unable to write cached ZOIR code for {f} to {f}{s}: {t}", .{ + file.path.fmt(comp), cache_directory, &hex_digest, err, }); }; }, diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 93f88b968913efb7166191a393fabca33b608c75..9631ec22f981f64681a511c4f4c288c8e3380c45 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -380,7 +380,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { const lib_final_file = try o_dir.createFile(io, final_lib_basename, .{ .truncate = true }); defer lib_final_file.close(io); var buffer: [1024]u8 = undefined; - var file_writer = lib_final_file.writer(&buffer); + var file_writer = lib_final_file.writer(io, &buffer); try implib.writeCoffArchive(gpa, &file_writer.interface, members); try file_writer.interface.flush(); } diff --git a/src/link.zig b/src/link.zig index 06d18ec2d5073212ca028f68a00d024832de7166..5f3031ca0e9f9a6d431297e84cd0b195db4f702c 100644 --- a/src/link.zig +++ b/src/link.zig @@ -621,7 +621,7 @@ pub const File = struct { }); defer gpa.free(tmp_sub_path); try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, .{}); - try emit.root_dir.handle.rename(tmp_sub_path, emit.sub_path); + try emit.root_dir.handle.rename(tmp_sub_path, emit.root_dir.handle, emit.sub_path, io); switch (builtin.os.tag) { .linux => std.posix.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| { log.warn("ptrace failure: {s}", .{@errorName(err)}); diff --git a/src/link/Lld.zig b/src/link/Lld.zig index ca15a38bb094486d5537716cafe101a890f80980..6920c127623f75f6d77df5206de4fb4322d23ad2 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1631,7 +1631,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi { defer rsp_file.close(io); var rsp_file_buffer: [1024]u8 = undefined; - var rsp_file_writer = rsp_file.writer(&rsp_file_buffer); + var rsp_file_writer = rsp_file.writer(io, &rsp_file_buffer); const rsp_writer = &rsp_file_writer.interface; for (argv[2..]) |arg| { try rsp_writer.writeByte('"'); diff --git a/src/main.zig b/src/main.zig index bef3a3efb5a83209d0ba90d3a3e0f152015541b5..88e236adef872cf49469b2ad3e46b96f9dffdca1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3412,7 +3412,7 @@ fn buildOutputType( const sub_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-stdin{s}", .{ &bin_digest, ext.canonicalName(target), }); - try dirs.local_cache.handle.rename(dump_path, sub_path); + try dirs.local_cache.handle.rename(dump_path, dirs.local_cache.handle, sub_path, io); // Convert `sub_path` to be relative to current working directory. src.src_path = try dirs.local_cache.join(arena, &.{sub_path}); @@ -7216,11 +7216,7 @@ fn createDependenciesModule( const hex_digest = hh.final(); const o_dir_sub_path = try arena.dupe(u8, "o" ++ fs.path.sep_str ++ hex_digest); - try Package.Fetch.renameTmpIntoCache( - dirs.local_cache.handle, - tmp_dir_sub_path, - o_dir_sub_path, - ); + try Package.Fetch.renameTmpIntoCache(io, dirs.local_cache.handle, tmp_dir_sub_path, o_dir_sub_path); const deps_mod = try Package.Module.create(arena, .{ .paths = .{ -- 2.54.0 From 1dcfc8787e86ed94d216976e621a49fc488e8214 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 18:00:55 -0800 Subject: [PATCH 026/195] update all readFileAlloc() to accept Io instance --- lib/compiler/std-docs.zig | 6 +++-- lib/std/Build/Cache.zig | 3 ++- lib/std/Build/Step/CheckFile.zig | 3 ++- lib/std/Build/Step/ConfigHeader.zig | 4 +-- lib/std/Build/WebServer.zig | 5 ++-- lib/std/Io/Dir.zig | 6 ++--- lib/std/crypto/Certificate/Bundle/macos.zig | 3 +-- lib/std/fs/test.zig | 27 +++++++++++---------- lib/std/zig/LibCInstallation.zig | 8 ++---- lib/std/zig/WindowsSdk.zig | 2 +- src/Compilation.zig | 4 +-- src/Package/Fetch/git.zig | 4 +-- src/link.zig | 4 +-- src/link/MachO.zig | 8 +++--- src/link/MachO/CodeSignature.zig | 16 ++++++------ src/main.zig | 21 +++++++--------- 16 files changed, 62 insertions(+), 62 deletions(-) diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index 8f02f05958bfeb0da68e05534a46ed5b89a89209..64524963057476662405f4900a7f87e8b819f5e8 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -179,10 +179,11 @@ fn serveDocsFile( content_type: []const u8, ) !void { const gpa = context.gpa; + const io = context.io; // The desired API is actually sendfile, which will require enhancing std.http.Server. // We load the file with every request so that the user can make changes to the file // and refresh the HTML page without restarting this server. - const file_contents = try context.lib_dir.readFileAlloc(name, gpa, .limited(10 * 1024 * 1024)); + const file_contents = try context.lib_dir.readFileAlloc(io, name, gpa, .limited(10 * 1024 * 1024)); defer gpa.free(file_contents); try request.respond(file_contents, .{ .extra_headers = &.{ @@ -255,6 +256,7 @@ fn serveWasm( optimize_mode: std.builtin.OptimizeMode, ) !void { const gpa = context.gpa; + const io = context.io; var arena_instance = std.heap.ArenaAllocator.init(gpa); defer arena_instance.deinit(); @@ -273,7 +275,7 @@ fn serveWasm( }); // std.http.Server does not have a sendfile API yet. const bin_path = try wasm_base_path.join(arena, bin_name); - const file_contents = try bin_path.root_dir.handle.readFileAlloc(bin_path.sub_path, gpa, .limited(10 * 1024 * 1024)); + const file_contents = try bin_path.root_dir.handle.readFileAlloc(io, bin_path.sub_path, gpa, .limited(10 * 1024 * 1024)); defer gpa.free(file_contents); try request.respond(file_contents, .{ .extra_headers = &.{ diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 2d6dbc02fa7ec49a5ce0ef22c9a6a6ac0d817ba7..d2ba33c74d1196361912140f80f5c5e0accdd69f 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -1075,7 +1075,8 @@ pub const Manifest = struct { fn addDepFileMaybePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void { const gpa = self.cache.gpa; - const dep_file_contents = try dir.readFileAlloc(dep_file_sub_path, gpa, .limited(manifest_file_size_max)); + const io = self.cache.io; + const dep_file_contents = try dir.readFileAlloc(io, dep_file_sub_path, gpa, .limited(manifest_file_size_max)); defer gpa.free(dep_file_contents); var error_buf: std.ArrayList(u8) = .empty; diff --git a/lib/std/Build/Step/CheckFile.zig b/lib/std/Build/Step/CheckFile.zig index 560b6ad0507752ec9af281ae27369c39afb0aa10..1c3813ca824bbea35e6e15b19567dd1b47c212cc 100644 --- a/lib/std/Build/Step/CheckFile.zig +++ b/lib/std/Build/Step/CheckFile.zig @@ -51,11 +51,12 @@ pub fn setName(check_file: *CheckFile, name: []const u8) void { fn make(step: *Step, options: Step.MakeOptions) !void { _ = options; const b = step.owner; + const io = b.graph.io; const check_file: *CheckFile = @fieldParentPtr("step", step); try step.singleUnchangingWatchInput(check_file.source); const src_path = check_file.source.getPath2(b, step); - const contents = Io.Dir.cwd().readFileAlloc(src_path, b.allocator, .limited(check_file.max_bytes)) catch |err| { + const contents = Io.Dir.cwd().readFileAlloc(io, src_path, b.allocator, .limited(check_file.max_bytes)) catch |err| { return step.fail("unable to read '{s}': {s}", .{ src_path, @errorName(err), }); diff --git a/lib/std/Build/Step/ConfigHeader.zig b/lib/std/Build/Step/ConfigHeader.zig index 589110d4c4bde01c4ae6d5a2c91088129e5bb2f8..250bae500945b8e30ffa38eb60e91840f2d8d946 100644 --- a/lib/std/Build/Step/ConfigHeader.zig +++ b/lib/std/Build/Step/ConfigHeader.zig @@ -208,7 +208,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .autoconf_undef, .autoconf_at => |file_source| { try bw.writeAll(c_generated_line); const src_path = file_source.getPath2(b, step); - const contents = Io.Dir.cwd().readFileAlloc(src_path, arena, .limited(config_header.max_bytes)) catch |err| { + const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| { return step.fail("unable to read autoconf input file '{s}': {s}", .{ src_path, @errorName(err), }); @@ -222,7 +222,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .cmake => |file_source| { try bw.writeAll(c_generated_line); const src_path = file_source.getPath2(b, step); - const contents = Io.Dir.cwd().readFileAlloc(src_path, arena, .limited(config_header.max_bytes)) catch |err| { + const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| { return step.fail("unable to read cmake input file '{s}': {s}", .{ src_path, @errorName(err), }); diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 38a7a73588e500b41bad6af98f2abd092c097c40..72306cbab9a302f0bc4dd1a9d00c3bc2e77f8014 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -469,11 +469,12 @@ pub fn serveFile( content_type: []const u8, ) !void { const gpa = ws.gpa; + const io = ws.graph.io; // The desired API is actually sendfile, which will require enhancing http.Server. // We load the file with every request so that the user can make changes to the file // and refresh the HTML page without restarting this server. - const file_contents = path.root_dir.handle.readFileAlloc(path.sub_path, gpa, .limited(10 * 1024 * 1024)) catch |err| { - log.err("failed to read '{f}': {s}", .{ path, @errorName(err) }); + const file_contents = path.root_dir.handle.readFileAlloc(io, path.sub_path, gpa, .limited(10 * 1024 * 1024)) catch |err| { + log.err("failed to read '{f}': {t}", .{ path, err }); return error.AlreadyReported; }; defer gpa.free(file_contents); diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 7d1f6212dcc2415bd62de8388266724254e479dd..cf5e2b4c725d0a7754f657e876687c41deee7904 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -1117,10 +1117,10 @@ pub fn readLink(dir: Dir, io: Io, sub_path: []const u8, buffer: []u8) ReadLinkEr /// On other platforms, `path` is an opaque sequence of bytes with no particular encoding. pub fn readLinkAbsolute(io: Io, absolute_path: []const u8, buffer: []u8) ReadLinkError!usize { assert(path.isAbsolute(absolute_path)); - return io.vtable.dirReadLink(io.userdata, .cwd(), path, buffer); + return io.vtable.dirReadLink(io.userdata, .cwd(), absolute_path, buffer); } -pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{ +pub const ReadFileAllocError = File.OpenError || File.Reader.Error || Allocator.Error || error{ /// File size reached or exceeded the provided limit. StreamTooLong, }; @@ -1603,7 +1603,7 @@ pub const CopyFileOptions = struct { pub const CopyFileError = File.OpenError || File.StatError || File.Atomic.InitError || File.Atomic.FinishError || - File.ReadError || File.WriteError || error{InvalidFileName}; + File.Reader.Error || File.WriteError || error{InvalidFileName}; /// Atomically creates a new file at `dest_path` within `dest_dir` with the /// same contents as `source_path` within `source_dir`, overwriting any already diff --git a/lib/std/crypto/Certificate/Bundle/macos.zig b/lib/std/crypto/Certificate/Bundle/macos.zig index 444d8da67519309684ebe873c101849616f52e96..086c8feb3fee1f25691b2e28c81d0fb105ead700 100644 --- a/lib/std/crypto/Certificate/Bundle/macos.zig +++ b/lib/std/crypto/Certificate/Bundle/macos.zig @@ -17,9 +17,8 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanM "/Library/Keychains/System.keychain", }; - _ = io; // TODO migrate file system to use std.Io for (keychain_paths) |keychain_path| { - const bytes = Io.Dir.cwd().readFileAlloc(keychain_path, gpa, .limited(std.math.maxInt(u32))) catch |err| switch (err) { + const bytes = Io.Dir.cwd().readFileAlloc(io, keychain_path, gpa, .limited(std.math.maxInt(u32))) catch |err| switch (err) { error.StreamTooLong => return error.FileTooBig, else => |e| return e, }; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 92992f91b48e17df9990f82eb6790d5ba30362db..bb3a9da0f5c51f76c8233752dbc3935b2add0088 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -767,7 +767,7 @@ test "readFileAlloc" { var file = try tmp_dir.dir.createFile(io, "test_file", .{ .read = true }); defer file.close(io); - const buf1 = try tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(1024)); + const buf1 = try tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(1024)); defer testing.allocator.free(buf1); try testing.expectEqualStrings("", buf1); @@ -776,7 +776,7 @@ test "readFileAlloc" { { // max_bytes > file_size - const buf2 = try tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(1024)); + const buf2 = try tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(1024)); defer testing.allocator.free(buf2); try testing.expectEqualStrings(write_buf, buf2); } @@ -785,13 +785,13 @@ test "readFileAlloc" { // max_bytes == file_size try testing.expectError( error.StreamTooLong, - tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(write_buf.len)), + tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(write_buf.len)), ); } { // max_bytes == file_size + 1 - const buf2 = try tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(write_buf.len + 1)); + const buf2 = try tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(write_buf.len + 1)); defer testing.allocator.free(buf2); try testing.expectEqualStrings(write_buf, buf2); } @@ -799,7 +799,7 @@ test "readFileAlloc" { // max_bytes < file_size try testing.expectError( error.StreamTooLong, - tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(write_buf.len - 1)), + tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(write_buf.len - 1)), ); } @@ -877,16 +877,16 @@ test "file operations on directories" { switch (native_os) { .dragonfly, .netbsd => { // no error when reading a directory. See https://github.com/ziglang/zig/issues/5732 - const buf = try ctx.dir.readFileAlloc(test_dir_name, testing.allocator, .unlimited); + const buf = try ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited); testing.allocator.free(buf); }, .wasi => { // WASI return EBADF, which gets mapped to NotOpenForReading. // See https://github.com/bytecodealliance/wasmtime/issues/1935 - try testing.expectError(error.NotOpenForReading, ctx.dir.readFileAlloc(test_dir_name, testing.allocator, .unlimited)); + try testing.expectError(error.NotOpenForReading, ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited)); }, else => { - try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(test_dir_name, testing.allocator, .unlimited)); + try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited)); }, } @@ -1679,14 +1679,14 @@ test "copyFile" { try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode }); defer ctx.dir.deleteFile(dest_file2) catch {}; - try expectFileContents(ctx.dir, dest_file, data); - try expectFileContents(ctx.dir, dest_file2, data); + try expectFileContents(io, ctx.dir, dest_file, data); + try expectFileContents(io, ctx.dir, dest_file2, data); } }.impl); } -fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void { - const contents = try dir.readFileAlloc(file_path, testing.allocator, .limited(1000)); +fn expectFileContents(io: Io, dir: Dir, file_path: []const u8, data: []const u8) !void { + const contents = try dir.readFileAlloc(io, file_path, testing.allocator, .limited(1000)); defer testing.allocator.free(contents); try testing.expectEqualSlices(u8, data, contents); @@ -1695,6 +1695,7 @@ fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void { test "AtomicFile" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const allocator = ctx.arena.allocator(); const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt"); const test_content = @@ -1709,7 +1710,7 @@ test "AtomicFile" { try af.file_writer.interface.writeAll(test_content); try af.finish(); } - const content = try ctx.dir.readFileAlloc(test_out_file, allocator, .limited(9999)); + const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999)); try testing.expectEqualStrings(test_content, content); try ctx.dir.deleteFile(test_out_file); diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index f3dc73838f773adc9d25e1e44495b2e81eb9c99e..1463ff4a408af25453ec12020990c056c66dadf9 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -37,11 +37,7 @@ pub const FindError = error{ ZigIsTheCCompiler, }; -pub fn parse( - allocator: Allocator, - libc_file: []const u8, - target: *const std.Target, -) !LibCInstallation { +pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const std.Target) !LibCInstallation { var self: LibCInstallation = .{}; const fields = std.meta.fields(LibCInstallation); @@ -57,7 +53,7 @@ pub fn parse( } } - const contents = try Io.Dir.cwd().readFileAlloc(libc_file, allocator, .limited(std.math.maxInt(usize))); + const contents = try Io.Dir.cwd().readFileAlloc(io, libc_file, allocator, .limited(std.math.maxInt(usize))); defer allocator.free(contents); var it = std.mem.tokenizeScalar(u8, contents, '\n'); diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index dca474020a32f2c9bb00b6cb21d415bec0ba53ac..3f6d58b6bab8e9ecd35be647a48abcbd987b234d 100644 --- a/lib/std/zig/WindowsSdk.zig +++ b/lib/std/zig/WindowsSdk.zig @@ -775,7 +775,7 @@ const MsvcLibDir = struct { writer.writeByte(std.fs.path.sep) catch unreachable; writer.writeAll("state.json") catch unreachable; - const json_contents = instances_dir.readFileAlloc(writer.buffered(), allocator, .limited(std.math.maxInt(usize))) catch continue; + const json_contents = instances_dir.readFileAlloc(io, writer.buffered(), allocator, .limited(std.math.maxInt(usize))) catch continue; defer allocator.free(json_contents); var parsed = std.json.parseFromSlice(std.json.Value, allocator, json_contents, .{}) catch continue; diff --git a/src/Compilation.zig b/src/Compilation.zig index 3a6ae263a5bdc93951b2f5bf9c271d4e9fe97347..79b2d51635a3e185655eba9d5e95b110f81098cc 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -6400,7 +6400,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr if (comp.file_system_inputs != null) { // Use the same file size limit as the cache code does for dependency files. - const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(dep_basename, gpa, .limited(Cache.manifest_file_size_max)); + const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(io, dep_basename, gpa, .limited(Cache.manifest_file_size_max)); defer gpa.free(dep_file_contents); var str_buf: std.ArrayList(u8) = .empty; @@ -6665,7 +6665,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 // Read depfile and update cache manifest { const dep_basename = fs.path.basename(out_dep_path); - const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(dep_basename, arena, .limited(50 * 1024 * 1024)); + const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(io, dep_basename, arena, .limited(50 * 1024 * 1024)); defer arena.free(dep_file_contents); const value = try std.json.parseFromSliceLeaky(std.json.Value, arena, dep_file_contents, .{}); diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index 4d5bcfc84b16359254baa3868a98fa3c1c7eb4cd..1d6aa1a849346d0e74af05ee36c37586ea8306e3 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -1602,7 +1602,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u const max_file_size = 8192; if (!skip_checksums) { - const index_file_data = try git_dir.dir.readFileAlloc("testrepo.idx", testing.allocator, .limited(max_file_size)); + const index_file_data = try git_dir.dir.readFileAlloc(io, "testrepo.idx", testing.allocator, .limited(max_file_size)); defer testing.allocator.free(index_file_data); // testrepo.idx is generated by Git. The index created by this file should // match it exactly. Running `git verify-pack -v testrepo.pack` can verify @@ -1678,7 +1678,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u \\revision 19 \\ ; - const actual_file_contents = try worktree.dir.readFileAlloc("file", testing.allocator, .limited(max_file_size)); + const actual_file_contents = try worktree.dir.readFileAlloc(io, "file", testing.allocator, .limited(max_file_size)); defer testing.allocator.free(actual_file_contents); try testing.expectEqualStrings(expected_file_contents, actual_file_contents); } diff --git a/src/link.zig b/src/link.zig index 5f3031ca0e9f9a6d431297e84cd0b195db4f702c..ff6d938e6feedc5d45b0643bd0d2a5732e8a0d11 100644 --- a/src/link.zig +++ b/src/link.zig @@ -624,12 +624,12 @@ pub const File = struct { try emit.root_dir.handle.rename(tmp_sub_path, emit.root_dir.handle, emit.sub_path, io); switch (builtin.os.tag) { .linux => std.posix.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| { - log.warn("ptrace failure: {s}", .{@errorName(err)}); + log.warn("ptrace failure: {t}", .{err}); }, .maccatalyst, .macos => { const macho_file = base.cast(.macho).?; macho_file.ptraceAttach(pid) catch |err| { - log.warn("attaching failed with error: {s}", .{@errorName(err)}); + log.warn("attaching failed with error: {t}", .{err}); }; }, .windows => unreachable, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6cd906340a04629f08dbc59c34292eb956120113..f996442f2494ef1442391a0bbf9cf585dbf6f36e 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -4347,11 +4347,13 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); + const io = comp.io; + const sdk_dir = switch (sdk_layout) { .sdk => comp.sysroot.?, .vendored => fs.path.join(arena, &.{ comp.dirs.zig_lib.path.?, "libc", "darwin" }) catch return null, }; - if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| { + if (readSdkVersionFromSettings(arena, io, sdk_dir)) |ver| { return parseSdkVersion(ver); } else |_| { // Read from settings should always succeed when vendored. @@ -4374,9 +4376,9 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi // Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout. // Use property `MinimalDisplayName` to determine version. // The file/property is also available with vendored libc. -fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 { +fn readSdkVersionFromSettings(arena: Allocator, io: Io, dir: []const u8) ![]const u8 { const sdk_path = try fs.path.join(arena, &.{ dir, "SDKSettings.json" }); - const contents = try Io.Dir.cwd().readFileAlloc(sdk_path, arena, .limited(std.math.maxInt(u16))); + const contents = try Io.Dir.cwd().readFileAlloc(io, sdk_path, arena, .limited(std.math.maxInt(u16))); const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{}); if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string; return error.SdkVersionFailure; diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index ec516d4af0f1f33bb5038432da06803666436ac3..0955c823b8defdfd2d6876798861ce1dabfe6e7f 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -17,6 +17,12 @@ const MachO = @import("../MachO.zig"); const hash_size = Sha256.digest_length; +page_size: u16, +code_directory: CodeDirectory, +requirements: ?Requirements = null, +entitlements: ?Entitlements = null, +signature: ?Signature = null, + const Blob = union(enum) { code_directory: *CodeDirectory, requirements: *Requirements, @@ -220,12 +226,6 @@ const Signature = struct { } }; -page_size: u16, -code_directory: CodeDirectory, -requirements: ?Requirements = null, -entitlements: ?Entitlements = null, -signature: ?Signature = null, - pub fn init(page_size: u16) CodeSignature { return .{ .page_size = page_size, @@ -246,8 +246,8 @@ pub fn deinit(self: *CodeSignature, allocator: Allocator) void { } } -pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void { - const inner = try Io.Dir.cwd().readFileAlloc(path, allocator, .limited(std.math.maxInt(u32))); +pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, io: Io, path: []const u8) !void { + const inner = try Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(std.math.maxInt(u32))); self.entitlements = .{ .inner = inner }; } diff --git a/src/main.zig b/src/main.zig index 88e236adef872cf49469b2ad3e46b96f9dffdca1..9bb88d373be099554583843eda00ade66674153f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1029,9 +1029,8 @@ fn buildOutputType( if (mem.cutPrefix(u8, arg, "@")) |resp_file_path| { // This is a "compiler response file". We must parse the file and treat its // contents as command line parameters. - args_iter.resp_file = initArgIteratorResponseFile(arena, resp_file_path) catch |err| { - fatal("unable to read response file '{s}': {s}", .{ resp_file_path, @errorName(err) }); - }; + args_iter.resp_file = initArgIteratorResponseFile(arena, io, resp_file_path) catch |err| + fatal("unable to read response file '{s}': {t}", .{ resp_file_path, err }); } else if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { try Io.File.stdout().writeAll(usage_build_generic); @@ -5441,7 +5440,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) // that are missing. const s = fs.path.sep_str; const tmp_sub_path = "tmp" ++ s ++ results_tmp_file_nonce; - const stdout = dirs.local_cache.handle.readFileAlloc(tmp_sub_path, arena, .limited(50 * 1024 * 1024)) catch |err| { + const stdout = dirs.local_cache.handle.readFileAlloc(io, tmp_sub_path, arena, .limited(50 * 1024 * 1024)) catch |err| { fatal("unable to read results of configure phase from '{f}{s}': {s}", .{ dirs.local_cache, tmp_sub_path, @errorName(err), }); @@ -5822,9 +5821,9 @@ pub fn lldMain( const ArgIteratorResponseFile = process.ArgIteratorGeneral(.{ .comments = true, .single_quotes = true }); /// Initialize the arguments from a Response File. "*.rsp" -fn initArgIteratorResponseFile(allocator: Allocator, resp_file_path: []const u8) !ArgIteratorResponseFile { +fn initArgIteratorResponseFile(allocator: Allocator, io: Io, resp_file_path: []const u8) !ArgIteratorResponseFile { const max_bytes = 10 * 1024 * 1024; // 10 MiB of command line arguments is a reasonable limit - const cmd_line = try Io.Dir.cwd().readFileAlloc(resp_file_path, allocator, .limited(max_bytes)); + const cmd_line = try Io.Dir.cwd().readFileAlloc(io, resp_file_path, allocator, .limited(max_bytes)); errdefer allocator.free(cmd_line); return ArgIteratorResponseFile.initTakeOwnership(allocator, cmd_line); @@ -5952,7 +5951,7 @@ pub const ClangArgIterator = struct { }; } - fn next(self: *ClangArgIterator) !void { + fn next(self: *ClangArgIterator, io: Io) !void { assert(self.has_next); assert(self.next_index < self.argv.len); // In this state we know that the parameter we are looking at is a root parameter @@ -5970,10 +5969,8 @@ pub const ClangArgIterator = struct { const arena = self.arena; const resp_file_path = arg[1..]; - self.arg_iterator_response_file = - initArgIteratorResponseFile(arena, resp_file_path) catch |err| { - fatal("unable to read response file '{s}': {s}", .{ resp_file_path, @errorName(err) }); - }; + self.arg_iterator_response_file = initArgIteratorResponseFile(arena, io, resp_file_path) catch |err| + fatal("unable to read response file '{s}': {t}", .{ resp_file_path, err }); // NOTE: The ArgIteratorResponseFile returns tokens from next() that are slices of an // internal buffer. This internal buffer is arena allocated, so it is not cleaned up here. @@ -7405,7 +7402,7 @@ const Templates = struct { } const max_bytes = 10 * 1024 * 1024; - const contents = templates.dir.readFileAlloc(template_path, arena, .limited(max_bytes)) catch |err| { + const contents = templates.dir.readFileAlloc(io, template_path, arena, .limited(max_bytes)) catch |err| { fatal("unable to read template file '{s}': {t}", .{ template_path, err }); }; templates.buffer.clearRetainingCapacity(); -- 2.54.0 From a91c6dc71d42ea59ec53ce4e0ae83e4970731313 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 18:07:55 -0800 Subject: [PATCH 027/195] test: std.fs.File -> std.Io.File --- test/cases/disable_stack_tracing.zig | 2 +- test/incremental/add_decl | 12 ++++++------ test/incremental/add_decl_namespaced | 12 ++++++------ test/incremental/bad_import | 4 ++-- test/incremental/change_embed_file | 6 +++--- test/incremental/change_enum_tag_type | 6 +++--- test/incremental/change_exports | 12 ++++++------ test/incremental/change_fn_type | 6 +++--- test/incremental/change_generic_line_number | 4 ++-- test/incremental/change_line_number | 4 ++-- test/incremental/change_panic_handler | 6 +++--- test/incremental/change_panic_handler_explicit | 6 +++--- test/incremental/change_shift_op | 4 ++-- test/incremental/change_struct_same_fields | 6 +++--- test/incremental/change_zon_file | 6 +++--- test/incremental/change_zon_file_no_result_type | 2 +- test/incremental/compile_log | 6 +++--- test/incremental/fix_astgen_failure | 8 ++++---- test/incremental/function_becomes_inline | 6 +++--- test/incremental/hello | 4 ++-- test/incremental/make_decl_pub | 4 ++-- test/incremental/modify_inline_fn | 4 ++-- test/incremental/move_src | 4 ++-- test/incremental/no_change_preserves_tag_names | 4 ++-- .../recursive_function_becomes_non_recursive | 4 ++-- test/incremental/remove_enum_field | 4 ++-- test/incremental/unreferenced_error | 8 ++++---- test/link/bss/main.zig | 2 +- test/link/elf.zig | 4 ++-- test/link/macho.zig | 4 ++-- test/link/wasm/extern/main.zig | 2 +- test/src/Cases.zig | 6 ++++-- test/src/convert-stack-trace.zig | 2 +- test/standalone/child_process/child.zig | 6 +++--- test/standalone/child_process/main.zig | 2 +- test/standalone/simple/cat/main.zig | 8 ++++---- test/standalone/simple/guess_number/main.zig | 4 ++-- test/standalone/simple/hello_world/hello.zig | 2 +- test/standalone/windows_bat_args/echo-args.zig | 2 +- test/standalone/windows_paths/relative.zig | 2 +- test/standalone/windows_spawn/hello.zig | 2 +- test/standalone/windows_spawn/main.zig | 3 ++- test/tests.zig | 3 ++- 43 files changed, 106 insertions(+), 102 deletions(-) diff --git a/test/cases/disable_stack_tracing.zig b/test/cases/disable_stack_tracing.zig index 044eaf70123445e5b385d1e4d0a635e7801708fd..5b394beeeb384fc7578c3f7e36741a82ff59ccc9 100644 --- a/test/cases/disable_stack_tracing.zig +++ b/test/cases/disable_stack_tracing.zig @@ -5,7 +5,7 @@ pub const std_options: std.Options = .{ pub fn main() !void { var st_buf: [8]usize = undefined; var buf: [1024]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&buf); + var stdout = std.Io.File.stdout().writer(&buf); const captured_st = try foo(&stdout.interface, &st_buf); try std.debug.writeStackTrace(&captured_st, &stdout.interface, .no_color); diff --git a/test/incremental/add_decl b/test/incremental/add_decl index 9efd274b9eb5a6d34667153922ee53f5ec6f7d82..031085ca584c789842b5d74c56b86a065e4b9b89 100644 --- a/test/incremental/add_decl +++ b/test/incremental/add_decl @@ -7,7 +7,7 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(foo); + try std.Io.File.stdout().writeAll(foo); } const foo = "good morning\n"; #expect_stdout="good morning\n" @@ -16,7 +16,7 @@ const foo = "good morning\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(foo); + try std.Io.File.stdout().writeAll(foo); } const foo = "good morning\n"; const bar = "good evening\n"; @@ -26,7 +26,7 @@ const bar = "good evening\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(bar); + try std.Io.File.stdout().writeAll(bar); } const foo = "good morning\n"; const bar = "good evening\n"; @@ -36,7 +36,7 @@ const bar = "good evening\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(qux); + try std.Io.File.stdout().writeAll(qux); } const foo = "good morning\n"; const bar = "good evening\n"; @@ -46,7 +46,7 @@ const bar = "good evening\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(qux); + try std.Io.File.stdout().writeAll(qux); } const foo = "good morning\n"; const bar = "good evening\n"; @@ -57,7 +57,7 @@ const qux = "good night\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(qux); + try std.Io.File.stdout().writeAll(qux); } const qux = "good night\n"; #expect_stdout="good night\n" diff --git a/test/incremental/add_decl_namespaced b/test/incremental/add_decl_namespaced index 1025ae24e1cedef6089272ecb359ee46d7b75c91..cbeaf4865ecb7837d2b7584a5565a1930909e6b5 100644 --- a/test/incremental/add_decl_namespaced +++ b/test/incremental/add_decl_namespaced @@ -7,7 +7,7 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(@This().foo); + try std.Io.File.stdout().writeAll(@This().foo); } const foo = "good morning\n"; #expect_stdout="good morning\n" @@ -16,7 +16,7 @@ const foo = "good morning\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(@This().foo); + try std.Io.File.stdout().writeAll(@This().foo); } const foo = "good morning\n"; const bar = "good evening\n"; @@ -26,7 +26,7 @@ const bar = "good evening\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(@This().bar); + try std.Io.File.stdout().writeAll(@This().bar); } const foo = "good morning\n"; const bar = "good evening\n"; @@ -36,7 +36,7 @@ const bar = "good evening\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(@This().qux); + try std.Io.File.stdout().writeAll(@This().qux); } const foo = "good morning\n"; const bar = "good evening\n"; @@ -47,7 +47,7 @@ const bar = "good evening\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(@This().qux); + try std.Io.File.stdout().writeAll(@This().qux); } const foo = "good morning\n"; const bar = "good evening\n"; @@ -58,7 +58,7 @@ const qux = "good night\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(@This().qux); + try std.Io.File.stdout().writeAll(@This().qux); } const qux = "good night\n"; #expect_stdout="good night\n" diff --git a/test/incremental/bad_import b/test/incremental/bad_import index 9b6be8b1767c1612f046d8b0885d7ce6bd086cef..ad66c17b5b5f6310b4c622e363d09ee4e35600ad 100644 --- a/test/incremental/bad_import +++ b/test/incremental/bad_import @@ -8,7 +8,7 @@ #file=main.zig pub fn main() !void { _ = @import("foo.zig"); - try std.fs.File.stdout().writeAll("success\n"); + try std.Io.File.stdout().writeAll("success\n"); } const std = @import("std"); #file=foo.zig @@ -30,7 +30,7 @@ comptime { #file=main.zig pub fn main() !void { //_ = @import("foo.zig"); - try std.fs.File.stdout().writeAll("success\n"); + try std.Io.File.stdout().writeAll("success\n"); } const std = @import("std"); #expect_stdout="success\n" diff --git a/test/incremental/change_embed_file b/test/incremental/change_embed_file index 85d861ab9319ed3ccaed69ec9bfd80ef82ee1005..1eed90dc3250fa0079176d010565f02002b75f6b 100644 --- a/test/incremental/change_embed_file +++ b/test/incremental/change_embed_file @@ -8,7 +8,7 @@ const std = @import("std"); const string = @embedFile("string.txt"); pub fn main() !void { - try std.fs.File.stdout().writeAll(string); + try std.Io.File.stdout().writeAll(string); } #file=string.txt Hello, World! @@ -28,7 +28,7 @@ Hello again, World! const std = @import("std"); const string = @embedFile("string.txt"); pub fn main() !void { - try std.fs.File.stdout().writeAll("a hardcoded string\n"); + try std.Io.File.stdout().writeAll("a hardcoded string\n"); } #expect_stdout="a hardcoded string\n" @@ -37,7 +37,7 @@ pub fn main() !void { const std = @import("std"); const string = @embedFile("string.txt"); pub fn main() !void { - try std.fs.File.stdout().writeAll(string); + try std.Io.File.stdout().writeAll(string); } #expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound diff --git a/test/incremental/change_enum_tag_type b/test/incremental/change_enum_tag_type index 906f910271554432cf77183d94fcbaec717c45f3..6e72b3e573fc2e6e18285b8b357144bed1b4a855 100644 --- a/test/incremental/change_enum_tag_type +++ b/test/incremental/change_enum_tag_type @@ -15,7 +15,7 @@ const Foo = enum(Tag) { pub fn main() !void { var val: Foo = undefined; val = .a; - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); } const std = @import("std"); @@ -33,7 +33,7 @@ const Foo = enum(Tag) { pub fn main() !void { var val: Foo = undefined; val = .a; - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); } comptime { @@ -56,7 +56,7 @@ const Foo = enum(Tag) { pub fn main() !void { var val: Foo = undefined; val = .a; - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); } const std = @import("std"); diff --git a/test/incremental/change_exports b/test/incremental/change_exports index b0850626d6ed01395bcf7f134f700e3ad34b64f4..0090d45d8ef791e382962925bb15bb74b40e510f 100644 --- a/test/incremental/change_exports +++ b/test/incremental/change_exports @@ -17,7 +17,7 @@ pub fn main() !void { extern const bar: u32; }; S.foo(); - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{}\n", .{S.bar}); } const std = @import("std"); @@ -39,7 +39,7 @@ pub fn main() !void { extern const other: u32; }; S.foo(); - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); } const std = @import("std"); @@ -62,7 +62,7 @@ pub fn main() !void { extern const other: u32; }; S.foo(); - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); } const std = @import("std"); @@ -87,7 +87,7 @@ pub fn main() !void { extern const other: u32; }; S.foo(); - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); } const std = @import("std"); @@ -133,7 +133,7 @@ pub fn main() !void { extern const other: u32; }; S.foo(); - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); } const std = @import("std"); @@ -158,7 +158,7 @@ pub fn main() !void { extern const other: u32; }; S.foo(); - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); } const std = @import("std"); diff --git a/test/incremental/change_fn_type b/test/incremental/change_fn_type index df788684cdc05cb31ad2a8584610dc807a5b0e54..bcf006861ae828724c327edfcc356bfc3e250da0 100644 --- a/test/incremental/change_fn_type +++ b/test/incremental/change_fn_type @@ -8,7 +8,7 @@ pub fn main() !void { try foo(123); } fn foo(x: u8) !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); return stdout_writer.interface.print("{d}\n", .{x}); } const std = @import("std"); @@ -20,7 +20,7 @@ pub fn main() !void { try foo(123); } fn foo(x: i64) !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); return stdout_writer.interface.print("{d}\n", .{x}); } const std = @import("std"); @@ -32,7 +32,7 @@ pub fn main() !void { try foo(-42); } fn foo(x: i64) !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); return stdout_writer.interface.print("{d}\n", .{x}); } const std = @import("std"); diff --git a/test/incremental/change_generic_line_number b/test/incremental/change_generic_line_number index f1920c67e6a4df53f8669f9e971899f5ad95d1cc..252261af6689469ef76161b4eeb7f8e1af910dba 100644 --- a/test/incremental/change_generic_line_number +++ b/test/incremental/change_generic_line_number @@ -7,7 +7,7 @@ const std = @import("std"); fn Printer(message: []const u8) type { return struct { fn print() !void { - try std.fs.File.stdout().writeAll(message); + try std.Io.File.stdout().writeAll(message); } }; } @@ -23,7 +23,7 @@ const std = @import("std"); fn Printer(message: []const u8) type { return struct { fn print() !void { - try std.fs.File.stdout().writeAll(message); + try std.Io.File.stdout().writeAll(message); } }; } diff --git a/test/incremental/change_line_number b/test/incremental/change_line_number index 5c809b8fa99bb32965fed1aef395e0595dd06554..a905745e6ad5a9f2076bb825b3d28c91af340778 100644 --- a/test/incremental/change_line_number +++ b/test/incremental/change_line_number @@ -5,7 +5,7 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll("foo\n"); + try std.Io.File.stdout().writeAll("foo\n"); } #expect_stdout="foo\n" #update=change line number @@ -13,6 +13,6 @@ pub fn main() !void { const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll("foo\n"); + try std.Io.File.stdout().writeAll("foo\n"); } #expect_stdout="foo\n" diff --git a/test/incremental/change_panic_handler b/test/incremental/change_panic_handler index 070384887a21e5d068e0515192a2f05a2a1971d5..2fe9240b9b6d4283f1a034272058c72e59c77ab7 100644 --- a/test/incremental/change_panic_handler +++ b/test/incremental/change_panic_handler @@ -12,7 +12,7 @@ pub fn main() !u8 { } pub const panic = std.debug.FullPanic(myPanic); fn myPanic(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } @@ -29,7 +29,7 @@ pub fn main() !u8 { } pub const panic = std.debug.FullPanic(myPanic); fn myPanic(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } @@ -46,7 +46,7 @@ pub fn main() !u8 { } pub const panic = std.debug.FullPanic(myPanicNew); fn myPanicNew(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } diff --git a/test/incremental/change_panic_handler_explicit b/test/incremental/change_panic_handler_explicit index 774b18bbfd2c4e905a64d9084313886dd18e5171..7daa851c8f9b186f7b7ac3acdaf2e90544d8fbed 100644 --- a/test/incremental/change_panic_handler_explicit +++ b/test/incremental/change_panic_handler_explicit @@ -42,7 +42,7 @@ pub const panic = struct { pub const noreturnReturned = no_panic.noreturnReturned; }; fn myPanic(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } @@ -89,7 +89,7 @@ pub const panic = struct { pub const noreturnReturned = no_panic.noreturnReturned; }; fn myPanic(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } @@ -136,7 +136,7 @@ pub const panic = struct { pub const noreturnReturned = no_panic.noreturnReturned; }; fn myPanicNew(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } diff --git a/test/incremental/change_shift_op b/test/incremental/change_shift_op index 41b5d192664491450b6c61507960a15f2ac73aa0..557c17c0b1adffc9a8d606f7af40278ec6e05fd7 100644 --- a/test/incremental/change_shift_op +++ b/test/incremental/change_shift_op @@ -9,7 +9,7 @@ pub fn main() !void { try foo(0x1300); } fn foo(x: u16) !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("0x{x}\n", .{x << 4}); } const std = @import("std"); @@ -20,7 +20,7 @@ pub fn main() !void { try foo(0x1300); } fn foo(x: u16) !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("0x{x}\n", .{x >> 4}); } const std = @import("std"); diff --git a/test/incremental/change_struct_same_fields b/test/incremental/change_struct_same_fields index 7af1161326279b743ef4e72d64319d94f33988fb..ef8ffab52daa224c7ca48f0a642d3947b21c86cb 100644 --- a/test/incremental/change_struct_same_fields +++ b/test/incremental/change_struct_same_fields @@ -11,7 +11,7 @@ pub fn main() !void { try foo(&val); } fn foo(val: *const S) !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print( "{d} {d}\n", .{ val.x, val.y }, @@ -28,7 +28,7 @@ pub fn main() !void { try foo(&val); } fn foo(val: *const S) !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print( "{d} {d}\n", .{ val.x, val.y }, @@ -45,7 +45,7 @@ pub fn main() !void { try foo(&val); } fn foo(val: *const S) !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print( "{d} {d}\n", .{ val.x, val.y }, diff --git a/test/incremental/change_zon_file b/test/incremental/change_zon_file index 62f73dd3bf7989162060fbb072dd2e9d70e8b05d..8a4c69cf21327ddb3fa84ca44ed773d2b150aa70 100644 --- a/test/incremental/change_zon_file +++ b/test/incremental/change_zon_file @@ -8,7 +8,7 @@ const std = @import("std"); const message: []const u8 = @import("message.zon"); pub fn main() !void { - try std.fs.File.stdout().writeAll(message); + try std.Io.File.stdout().writeAll(message); } #file=message.zon "Hello, World!\n" @@ -29,7 +29,7 @@ pub fn main() !void { const std = @import("std"); const message: []const u8 = @import("message.zon"); pub fn main() !void { - try std.fs.File.stdout().writeAll("a hardcoded string\n"); + try std.Io.File.stdout().writeAll("a hardcoded string\n"); } #expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound #expect_error=main.zig:2:37: note: file imported here @@ -44,6 +44,6 @@ pub fn main() !void { const std = @import("std"); const message: []const u8 = @import("message.zon"); pub fn main() !void { - try std.fs.File.stdout().writeAll(message); + try std.Io.File.stdout().writeAll(message); } #expect_stdout="We're back, World!\n" diff --git a/test/incremental/change_zon_file_no_result_type b/test/incremental/change_zon_file_no_result_type index 498543e4f159c8abee5157958d0dc8f8d1dda009..2dd601765fcb944888942de439864b65e4a0511a 100644 --- a/test/incremental/change_zon_file_no_result_type +++ b/test/incremental/change_zon_file_no_result_type @@ -7,7 +7,7 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(@import("foo.zon").message); + try std.Io.File.stdout().writeAll(@import("foo.zon").message); } #file=foo.zon .{ diff --git a/test/incremental/compile_log b/test/incremental/compile_log index 697bb26569a727fe528329df27f2b8b05c32a510..d52259f122c18c03f69ed4402b07462eefd09e7c 100644 --- a/test/incremental/compile_log +++ b/test/incremental/compile_log @@ -8,7 +8,7 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeAll("Hello, World!\n"); } #expect_stdout="Hello, World!\n" @@ -16,7 +16,7 @@ pub fn main() !void { #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeAll("Hello, World!\n"); @compileLog("this is a log"); } #expect_error=main.zig:4:5: error: found compile log statement @@ -26,6 +26,6 @@ pub fn main() !void { #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeAll("Hello, World!\n"); } #expect_stdout="Hello, World!\n" diff --git a/test/incremental/fix_astgen_failure b/test/incremental/fix_astgen_failure index 8b1b3adbf782a37610e5415b1cf3481b4cf1d421..4dce2492e7f05244baa776b156b29d1564b62c54 100644 --- a/test/incremental/fix_astgen_failure +++ b/test/incremental/fix_astgen_failure @@ -10,21 +10,21 @@ pub fn main() !void { } #file=foo.zig pub fn hello() !void { - try std.fs.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeAll("Hello, World!\n"); } #expect_error=foo.zig:2:9: error: use of undeclared identifier 'std' #update=fix the error #file=foo.zig const std = @import("std"); pub fn hello() !void { - try std.fs.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeAll("Hello, World!\n"); } #expect_stdout="Hello, World!\n" #update=add new error #file=foo.zig const std = @import("std"); pub fn hello() !void { - try std.fs.File.stdout().writeAll(hello_str); + try std.Io.File.stdout().writeAll(hello_str); } #expect_error=foo.zig:3:39: error: use of undeclared identifier 'hello_str' #update=fix the new error @@ -32,6 +32,6 @@ pub fn hello() !void { const std = @import("std"); const hello_str = "Hello, World! Again!\n"; pub fn hello() !void { - try std.fs.File.stdout().writeAll(hello_str); + try std.Io.File.stdout().writeAll(hello_str); } #expect_stdout="Hello, World! Again!\n" diff --git a/test/incremental/function_becomes_inline b/test/incremental/function_becomes_inline index 240d7a54af495684886d05d3210d8e42844a41b0..07cd98ef283ca5950985a6b2d0f93df99ac67bab 100644 --- a/test/incremental/function_becomes_inline +++ b/test/incremental/function_becomes_inline @@ -8,7 +8,7 @@ pub fn main() !void { try foo(); } fn foo() !void { - try std.fs.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeAll("Hello, World!\n"); } const std = @import("std"); #expect_stdout="Hello, World!\n" @@ -19,7 +19,7 @@ pub fn main() !void { try foo(); } inline fn foo() !void { - try std.fs.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeAll("Hello, World!\n"); } const std = @import("std"); #expect_stdout="Hello, World!\n" @@ -30,7 +30,7 @@ pub fn main() !void { try foo(); } inline fn foo() !void { - try std.fs.File.stdout().writeAll("Hello, `inline` World!\n"); + try std.Io.File.stdout().writeAll("Hello, `inline` World!\n"); } const std = @import("std"); #expect_stdout="Hello, `inline` World!\n" diff --git a/test/incremental/hello b/test/incremental/hello index dc6f02177fc4398b685712827f464c5800d135ee..8ff8f61bc0cbb5ee9a040379e8a61be97927a55e 100644 --- a/test/incremental/hello +++ b/test/incremental/hello @@ -7,13 +7,13 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll("good morning\n"); + try std.Io.File.stdout().writeAll("good morning\n"); } #expect_stdout="good morning\n" #update=change the string #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll("おはようございます\n"); + try std.Io.File.stdout().writeAll("おはようございます\n"); } #expect_stdout="おはようございます\n" diff --git a/test/incremental/make_decl_pub b/test/incremental/make_decl_pub index b25b11716092dfae108581d65bf5f413f4b01402..9d90ca51ebc2322671c670fd9896e09087ad694c 100644 --- a/test/incremental/make_decl_pub +++ b/test/incremental/make_decl_pub @@ -12,7 +12,7 @@ pub fn main() !void { #file=foo.zig const std = @import("std"); fn hello() !void { - try std.fs.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeAll("Hello, World!\n"); } #expect_error=main.zig:3:12: error: 'hello' is not marked 'pub' #expect_error=foo.zig:2:1: note: declared here @@ -21,6 +21,6 @@ fn hello() !void { #file=foo.zig const std = @import("std"); pub fn hello() !void { - try std.fs.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeAll("Hello, World!\n"); } #expect_stdout="Hello, World!\n" diff --git a/test/incremental/modify_inline_fn b/test/incremental/modify_inline_fn index d485d8ffd5eac65c3ab4f43862181a40a54fe229..2c1478682ed7255cb85e478847913b0d3311b482 100644 --- a/test/incremental/modify_inline_fn +++ b/test/incremental/modify_inline_fn @@ -8,7 +8,7 @@ const std = @import("std"); pub fn main() !void { const str = getStr(); - try std.fs.File.stdout().writeAll(str); + try std.Io.File.stdout().writeAll(str); } inline fn getStr() []const u8 { return "foo\n"; @@ -19,7 +19,7 @@ inline fn getStr() []const u8 { const std = @import("std"); pub fn main() !void { const str = getStr(); - try std.fs.File.stdout().writeAll(str); + try std.Io.File.stdout().writeAll(str); } inline fn getStr() []const u8 { return "bar\n"; diff --git a/test/incremental/move_src b/test/incremental/move_src index 4f43e8ea6ac559b1e0e785404e2e42e2955ba8b1..d6a21fc562730a05aec5ecbfb4c8541d6a651257 100644 --- a/test/incremental/move_src +++ b/test/incremental/move_src @@ -7,7 +7,7 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() }); } fn foo() u32 { @@ -22,7 +22,7 @@ fn bar() u32 { #file=main.zig const std = @import("std"); pub fn main() !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() }); } diff --git a/test/incremental/no_change_preserves_tag_names b/test/incremental/no_change_preserves_tag_names index 623496119d13af5fa210ec62f3b8607e8f868a06..e399e083e10e198858d1243259e2ed5a0cc9274f 100644 --- a/test/incremental/no_change_preserves_tag_names +++ b/test/incremental/no_change_preserves_tag_names @@ -8,7 +8,7 @@ const std = @import("std"); var some_enum: enum { first, second } = .first; pub fn main() !void { - try std.fs.File.stdout().writeAll(@tagName(some_enum)); + try std.Io.File.stdout().writeAll(@tagName(some_enum)); } #expect_stdout="first" #update=no change @@ -16,6 +16,6 @@ pub fn main() !void { const std = @import("std"); var some_enum: enum { first, second } = .first; pub fn main() !void { - try std.fs.File.stdout().writeAll(@tagName(some_enum)); + try std.Io.File.stdout().writeAll(@tagName(some_enum)); } #expect_stdout="first" diff --git a/test/incremental/recursive_function_becomes_non_recursive b/test/incremental/recursive_function_becomes_non_recursive index a5a03749b8ebc0f4b9cd90cd09b5e5df98ecfea4..20e43b64b717d1507c151c446560ce37616b9719 100644 --- a/test/incremental/recursive_function_becomes_non_recursive +++ b/test/incremental/recursive_function_becomes_non_recursive @@ -9,7 +9,7 @@ pub fn main() !void { try foo(false); } fn foo(recurse: bool) !void { - const stdout = std.fs.File.stdout(); + const stdout = std.Io.File.stdout(); if (recurse) return foo(true); try stdout.writeAll("non-recursive path\n"); } @@ -22,7 +22,7 @@ pub fn main() !void { try foo(true); } fn foo(recurse: bool) !void { - const stdout = std.fs.File.stdout(); + const stdout = std.Io.File.stdout(); if (recurse) return stdout.writeAll("x==1\n"); try stdout.writeAll("non-recursive path\n"); } diff --git a/test/incremental/remove_enum_field b/test/incremental/remove_enum_field index 02daf2a0fb0ed7564b2667569d2f654d2b1a7ae0..bbcf7718e9c8a69cf675d163de898af972468db5 100644 --- a/test/incremental/remove_enum_field +++ b/test/incremental/remove_enum_field @@ -10,7 +10,7 @@ const MyEnum = enum(u8) { bar = 2, }; pub fn main() !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)}); } const std = @import("std"); @@ -22,7 +22,7 @@ const MyEnum = enum(u8) { bar = 2, }; pub fn main() !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)}); } const std = @import("std"); diff --git a/test/incremental/unreferenced_error b/test/incremental/unreferenced_error index 505fb3d5f41443b7af95f7496bed5b8a05c7e5bd..1c1f1f7a8fbd5f8a3d738645cda6b4da1890043c 100644 --- a/test/incremental/unreferenced_error +++ b/test/incremental/unreferenced_error @@ -7,7 +7,7 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(a); + try std.Io.File.stdout().writeAll(a); } const a = "Hello, World!\n"; #expect_stdout="Hello, World!\n" @@ -16,7 +16,7 @@ const a = "Hello, World!\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(a); + try std.Io.File.stdout().writeAll(a); } const a = @compileError("bad a"); #expect_error=main.zig:5:11: error: bad a @@ -25,7 +25,7 @@ const a = @compileError("bad a"); #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(b); + try std.Io.File.stdout().writeAll(b); } const a = @compileError("bad a"); const b = "Hi there!\n"; @@ -35,7 +35,7 @@ const b = "Hi there!\n"; #file=main.zig const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll(a); + try std.Io.File.stdout().writeAll(a); } const a = "Back to a\n"; const b = @compileError("bad b"); diff --git a/test/link/bss/main.zig b/test/link/bss/main.zig index 2785a8360f0b5c9c7cab18d9a18c27d24f4c7ef6..9c104fec04c1ffdd8559a5f98fb3d1f1328ca12f 100644 --- a/test/link/bss/main.zig +++ b/test/link/bss/main.zig @@ -4,7 +4,7 @@ const std = @import("std"); var buffer: [0x1000000]u64 = [1]u64{0} ** 0x1000000; pub fn main() anyerror!void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); buffer[0x10] = 1; diff --git a/test/link/elf.zig b/test/link/elf.zig index 824fc76e9279ae8182fd3818efe33cfa4c780f94..6d9912193baac383129ef4f496d9cea4b4b86c33 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -1323,7 +1323,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step { \\extern var live_var2: i32; \\extern fn live_fn2() void; \\pub fn main() void { - \\ var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + \\ var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail"); \\ live_fn2(); \\} @@ -1365,7 +1365,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step { \\extern var live_var2: i32; \\extern fn live_fn2() void; \\pub fn main() void { - \\ var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + \\ var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail"); \\ live_fn2(); \\} diff --git a/test/link/macho.zig b/test/link/macho.zig index 4fc0cad0eedb0feb667fc2e25a26b73177c5aab8..0f3df8c2d8e28fdc057af21fee1591d7959ceb24 100644 --- a/test/link/macho.zig +++ b/test/link/macho.zig @@ -716,7 +716,7 @@ fn testHelloZig(b: *Build, opts: Options) *Step { const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes = \\const std = @import("std"); \\pub fn main() void { - \\ std.fs.File.stdout().writeAll("Hello world!\n") catch @panic("fail"); + \\ std.Io.File.stdout().writeAll("Hello world!\n") catch @panic("fail"); \\} }); @@ -2371,7 +2371,7 @@ fn testTlsZig(b: *Build, opts: Options) *Step { \\threadlocal var x: i32 = 0; \\threadlocal var y: i32 = -1; \\pub fn main() void { - \\ var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + \\ var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); \\ stdout_writer.interface.print("{d} {d}\n", .{x, y}) catch unreachable; \\ x -= 1; \\ y += 1; diff --git a/test/link/wasm/extern/main.zig b/test/link/wasm/extern/main.zig index 9635f64a4000fd178ad6e0d0edf3901e88510791..a841cc40a6bd01a1a3f88a38f1207d05620cd7b9 100644 --- a/test/link/wasm/extern/main.zig +++ b/test/link/wasm/extern/main.zig @@ -3,6 +3,6 @@ const std = @import("std"); extern const foo: u32; pub fn main() void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); stdout_writer.interface.print("Result: {d}", .{foo}) catch {}; } diff --git a/test/src/Cases.zig b/test/src/Cases.zig index b1fece44d97f4149ab2fbdf4fbab7bcab8c4e481..5889449bbe6297cb180dbc2f09b963b5f796de12 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -1,6 +1,8 @@ const Cases = @This(); const builtin = @import("builtin"); + const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const Allocator = std.mem.Allocator; const getExternalExecutor = std.zig.system.getExternalExecutor; @@ -313,7 +315,7 @@ pub fn addCompile( /// Each file should include a test manifest as a contiguous block of comments at /// the end of the file. The first line should be the test type, followed by a set of /// key-value config values, followed by a blank line, then the expected output. -pub fn addFromDir(ctx: *Cases, dir: std.fs.Dir, b: *std.Build) void { +pub fn addFromDir(ctx: *Cases, dir: Io.Dir, b: *std.Build) void { var current_file: []const u8 = "none"; ctx.addFromDirInner(dir, ¤t_file, b) catch |err| { std.debug.panicExtra( @@ -326,7 +328,7 @@ pub fn addFromDir(ctx: *Cases, dir: std.fs.Dir, b: *std.Build) void { fn addFromDirInner( ctx: *Cases, - iterable_dir: std.fs.Dir, + iterable_dir: Io.Dir, /// This is kept up to date with the currently being processed file so /// that if any errors occur the caller knows it happened during this file. current_file: *[]const u8, diff --git a/test/src/convert-stack-trace.zig b/test/src/convert-stack-trace.zig index 91be53a8e5b2cba938820adbf19a1e8dc7beb5f6..8d04500fbae66fa498b9b857ee4cfea65f7f93b4 100644 --- a/test/src/convert-stack-trace.zig +++ b/test/src/convert-stack-trace.zig @@ -44,7 +44,7 @@ pub fn main() !void { const in_file = try std.fs.cwd().openFile(args[1], .{}); defer in_file.close(); - const out_file: std.fs.File = .stdout(); + const out_file: std.Io.File = .stdout(); var in_fr = in_file.reader(io, &read_buf); var out_fw = out_file.writer(&write_buf); diff --git a/test/standalone/child_process/child.zig b/test/standalone/child_process/child.zig index 2e74f30882fd76684c80303287cc19a29c6cfb8f..5dbf95af4c0bbf74018bb3af50075b7a077d85bb 100644 --- a/test/standalone/child_process/child.zig +++ b/test/standalone/child_process/child.zig @@ -33,12 +33,12 @@ fn run(allocator: std.mem.Allocator, io: Io) !void { } // test stdout pipe; parent verifies - try std.fs.File.stdout().writeAll("hello from stdout"); + try std.Io.File.stdout().writeAll("hello from stdout"); // test stdin pipe from parent const hello_stdin = "hello from stdin"; var buf: [hello_stdin.len]u8 = undefined; - const stdin: std.fs.File = .stdin(); + const stdin: std.Io.File = .stdin(); var reader = stdin.reader(io, &.{}); const n = try reader.interface.readSliceShort(&buf); if (!std.mem.eql(u8, buf[0..n], hello_stdin)) { @@ -47,7 +47,7 @@ fn run(allocator: std.mem.Allocator, io: Io) !void { } fn testError(comptime fmt: []const u8, args: anytype) void { - var stderr_writer = std.fs.File.stderr().writer(&.{}); + var stderr_writer = std.Io.File.stderr().writer(&.{}); const stderr = &stderr_writer.interface; stderr.print("CHILD TEST ERROR: ", .{}) catch {}; stderr.print(fmt, args) catch {}; diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index 5970cdd95283b7ce047b35c8bbf721af0e242e0c..b20a7fed4962ef417b2879fd66ec2cb4030f56b4 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -62,7 +62,7 @@ pub fn main() !void { var parent_test_error = false; fn testError(comptime fmt: []const u8, args: anytype) void { - var stderr_writer = std.fs.File.stderr().writer(&.{}); + var stderr_writer = std.Io.File.stderr().writer(&.{}); const stderr = &stderr_writer.interface; stderr.print("PARENT TEST ERROR: ", .{}) catch {}; stderr.print(fmt, args) catch {}; diff --git a/test/standalone/simple/cat/main.zig b/test/standalone/simple/cat/main.zig index 9ea980aecca4a67ece8caa95359f8356c50acc9f..dea528f4f7b639c098bfd74ab25963d1b888f788 100644 --- a/test/standalone/simple/cat/main.zig +++ b/test/standalone/simple/cat/main.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const fs = std.fs; +const Io = std.Io; const mem = std.mem; const warn = std.log.warn; const fatal = std.process.fatal; @@ -18,11 +18,11 @@ pub fn main() !void { const exe = args[0]; var catted_anything = false; var stdout_buffer: [4096]u8 = undefined; - var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); const stdout = &stdout_writer.interface; - var stdin_reader = fs.File.stdin().readerStreaming(io, &.{}); + var stdin_reader = Io.File.stdin().readerStreaming(io, &.{}); - const cwd = fs.cwd(); + const cwd = Io.Dir.cwd(); for (args[1..]) |arg| { if (mem.eql(u8, arg, "-")) { diff --git a/test/standalone/simple/guess_number/main.zig b/test/standalone/simple/guess_number/main.zig index d477de2b7888dd528265f7286709d797ed8861ad..25226ec1c8ffd1eb419fc4bfeff781cee96114d3 100644 --- a/test/standalone/simple/guess_number/main.zig +++ b/test/standalone/simple/guess_number/main.zig @@ -2,9 +2,9 @@ const builtin = @import("builtin"); const std = @import("std"); pub fn main() !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); const out = &stdout_writer.interface; - const stdin: std.fs.File = .stdin(); + const stdin: std.Io.File = .stdin(); try out.writeAll("Welcome to the Guess Number Game in Zig.\n"); diff --git a/test/standalone/simple/hello_world/hello.zig b/test/standalone/simple/hello_world/hello.zig index 3b2b9106877512d914f9014d57d8fbb1c1a97b82..37c5c4bb1bf2e8204b3b7b87788b1dbc6af80220 100644 --- a/test/standalone/simple/hello_world/hello.zig +++ b/test/standalone/simple/hello_world/hello.zig @@ -1,5 +1,5 @@ const std = @import("std"); pub fn main() !void { - try std.fs.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeAll("Hello, World!\n"); } diff --git a/test/standalone/windows_bat_args/echo-args.zig b/test/standalone/windows_bat_args/echo-args.zig index 054c4a697544ba2a38ef3f93ac20620a70d33ab2..7e73fa54a00584aa6238a587ba9cd3cf6d577b57 100644 --- a/test/standalone/windows_bat_args/echo-args.zig +++ b/test/standalone/windows_bat_args/echo-args.zig @@ -5,7 +5,7 @@ pub fn main() !void { defer arena_state.deinit(); const arena = arena_state.allocator(); - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); const stdout = &stdout_writer.interface; var args = try std.process.argsAlloc(arena); for (args[1..], 1..) |arg, i| { diff --git a/test/standalone/windows_paths/relative.zig b/test/standalone/windows_paths/relative.zig index 83015496674557389b787e6c4eae2c7afa040dc1..7f87151ac8084cc5dfb29833bfe9124b2f01a9b4 100644 --- a/test/standalone/windows_paths/relative.zig +++ b/test/standalone/windows_paths/relative.zig @@ -13,7 +13,7 @@ pub fn main() !void { const relative = try std.fs.path.relative(allocator, args[1], args[2]); defer allocator.free(relative); - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); const stdout = &stdout_writer.interface; try stdout.writeAll(relative); } diff --git a/test/standalone/windows_spawn/hello.zig b/test/standalone/windows_spawn/hello.zig index fb4a827e2310a1ef2d48bb08e7e92674b8645a7b..e54bbe038654d5bac034ea101b90e17b1fb32897 100644 --- a/test/standalone/windows_spawn/hello.zig +++ b/test/standalone/windows_spawn/hello.zig @@ -1,7 +1,7 @@ const std = @import("std"); pub fn main() !void { - var stdout_writer = std.fs.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); const stdout = &stdout_writer.interface; try stdout.writeAll("hello from exe\n"); } diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index 10ee35f4df7b860ffbcd7a2ae32fc410d36e680b..f38807009732a885ec2c4ee41b0a48e2d6393fe6 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const windows = std.os.windows; const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral; @@ -218,7 +219,7 @@ fn testExecWithCwd(allocator: std.mem.Allocator, command: []const u8, cwd: ?[]co try std.testing.expectEqualStrings(expected_stdout, result.stdout); } -fn renameExe(dir: std.fs.Dir, old_sub_path: []const u8, new_sub_path: []const u8) !void { +fn renameExe(dir: Io.Dir, old_sub_path: []const u8, new_sub_path: []const u8) !void { var attempt: u5 = 0; while (true) break dir.rename(old_sub_path, new_sub_path) catch |err| switch (err) { error.AccessDenied => { diff --git a/test/tests.zig b/test/tests.zig index 0911ae2e4691f3e33f2792acaafe55b66ce069d3..2b0e8fb5d042944360d17438b4482a5e125adf93 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2629,10 +2629,11 @@ pub fn addCases( ) !void { const arena = b.allocator; const gpa = b.allocator; + const io = b.graph.io; var cases = @import("src/Cases.zig").init(gpa, arena); - var dir = try b.build_root.handle.openDir("test/cases", .{ .iterate = true }); + var dir = try b.build_root.handle.openDir(io, "test/cases", .{ .iterate = true }); defer dir.close(); cases.addFromDir(dir, b); -- 2.54.0 From 9e3bda5efffb12dd6491b4f7c92ccc89b9043c64 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 18:24:52 -0800 Subject: [PATCH 028/195] tests: close() -> close(io) --- lib/std/Io/File/Writer.zig | 6 +- lib/std/process/Child.zig | 17 ++- test/src/convert-stack-trace.zig | 6 +- test/standalone/child_process/main.zig | 15 ++- test/standalone/cmakedefine/check.zig | 7 +- test/standalone/dirname/build.zig | 10 +- test/standalone/dirname/exists_in.zig | 9 +- test/standalone/dirname/touch.zig | 17 ++- test/standalone/entry_point/check_differ.zig | 7 +- .../glibc_compat/glibc_runtime_check.zig | 4 +- .../install_headers/check_exists.zig | 11 +- test/standalone/libfuzzer/main.zig | 10 +- test/standalone/posix/relpaths.zig | 23 ++-- test/standalone/run_cwd/check_file_exists.zig | 5 +- test/standalone/run_output_caching/main.zig | 8 +- .../run_output_paths/create_file.zig | 4 +- .../self_exe_symlink/create-symlink.zig | 6 +- test/standalone/self_exe_symlink/main.zig | 16 ++- test/standalone/simple/cat/main.zig | 6 +- test/standalone/windows_spawn/main.zig | 120 +++++++++--------- test/tests.zig | 19 +-- 21 files changed, 187 insertions(+), 139 deletions(-) diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index e511e62e195159910f416b6855ebfb18b3086c48..d8c30ddbeff65af193433e878816dd1a14b749d6 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -56,8 +56,9 @@ pub const WriteFileError = error{ pub const SeekError = Io.File.SeekError; -pub fn init(file: File, buffer: []u8) Writer { +pub fn init(file: File, io: Io, buffer: []u8) Writer { return .{ + .io = io, .file = file, .interface = initInterface(buffer), .mode = .positional, @@ -67,8 +68,9 @@ pub fn init(file: File, buffer: []u8) Writer { /// Positional is more threadsafe, since the global seek position is not /// affected, but when such syscalls are not available, preemptively /// initializing in streaming mode will skip a failed syscall. -pub fn initStreaming(file: File, buffer: []u8) Writer { +pub fn initStreaming(file: File, io: Io, buffer: []u8) Writer { return .{ + .io = io, .file = file, .interface = initInterface(buffer), .mode = .streaming, diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 4521cd511fcf83eb336985547c9d0fb5c168b86b..dbbacc496cba2932e266cc2cd8999069a6586946 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -435,8 +435,7 @@ pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. /// If it succeeds, the caller owns result.stdout and result.stderr memory. -pub fn run(args: struct { - allocator: mem.Allocator, +pub fn run(allocator: Allocator, io: Io, args: struct { argv: []const []const u8, cwd: ?[]const u8 = null, cwd_dir: ?Io.Dir = null, @@ -447,7 +446,7 @@ pub fn run(args: struct { expand_arg0: Arg0Expand = .no_expand, progress_node: std.Progress.Node = std.Progress.Node.none, }) RunError!RunResult { - var child = ChildProcess.init(args.argv, args.allocator); + var child = ChildProcess.init(args.argv, allocator); child.stdin_behavior = .Ignore; child.stdout_behavior = .Pipe; child.stderr_behavior = .Pipe; @@ -458,19 +457,19 @@ pub fn run(args: struct { child.progress_node = args.progress_node; var stdout: ArrayList(u8) = .empty; - defer stdout.deinit(args.allocator); + defer stdout.deinit(allocator); var stderr: ArrayList(u8) = .empty; - defer stderr.deinit(args.allocator); + defer stderr.deinit(allocator); try child.spawn(); errdefer { - _ = child.kill() catch {}; + _ = child.kill(io) catch {}; } - try child.collectOutput(args.allocator, &stdout, &stderr, args.max_output_bytes); + try child.collectOutput(allocator, &stdout, &stderr, args.max_output_bytes); return .{ - .stdout = try stdout.toOwnedSlice(args.allocator), - .stderr = try stderr.toOwnedSlice(args.allocator), + .stdout = try stdout.toOwnedSlice(allocator), + .stderr = try stderr.toOwnedSlice(allocator), .term = try child.wait(), }; } diff --git a/test/src/convert-stack-trace.zig b/test/src/convert-stack-trace.zig index 8d04500fbae66fa498b9b857ee4cfea65f7f93b4..272edad18ed8d063144c34b36f0757011d47df6c 100644 --- a/test/src/convert-stack-trace.zig +++ b/test/src/convert-stack-trace.zig @@ -41,13 +41,13 @@ pub fn main() !void { var read_buf: [1024]u8 = undefined; var write_buf: [1024]u8 = undefined; - const in_file = try std.fs.cwd().openFile(args[1], .{}); - defer in_file.close(); + const in_file = try std.Io.Dir.cwd().openFile(io, args[1], .{}); + defer in_file.close(io); const out_file: std.Io.File = .stdout(); var in_fr = in_file.reader(io, &read_buf); - var out_fw = out_file.writer(&write_buf); + var out_fw = out_file.writer(io, &write_buf); const w = &out_fw.interface; diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index b20a7fed4962ef417b2879fd66ec2cb4030f56b4..755beacb6d6bac989d614189c7a69874d8508db1 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; pub fn main() !void { // make sure safety checks are enabled even in release modes @@ -20,7 +21,7 @@ pub fn main() !void { }; defer if (needs_free) gpa.free(child_path); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: Io.Threaded = .init(gpa); defer threaded.deinit(); const io = threaded.io(); @@ -31,7 +32,7 @@ pub fn main() !void { try child.spawn(); const child_stdin = child.stdin.?; try child_stdin.writeAll("hello from stdin"); // verified in child - child_stdin.close(); + child_stdin.close(io); child.stdin = null; const hello_stdout = "hello from stdout"; @@ -39,17 +40,17 @@ pub fn main() !void { var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); const n = try stdout_reader.interface.readSliceShort(&buf); if (!std.mem.eql(u8, buf[0..n], hello_stdout)) { - testError("child stdout: '{s}'; want '{s}'", .{ buf[0..n], hello_stdout }); + testError(io, "child stdout: '{s}'; want '{s}'", .{ buf[0..n], hello_stdout }); } switch (try child.wait()) { .Exited => |code| { const child_ok_code = 42; // set by child if no test errors if (code != child_ok_code) { - testError("child exit code: {d}; want {d}", .{ code, child_ok_code }); + testError(io, "child exit code: {d}; want {d}", .{ code, child_ok_code }); } }, - else => |term| testError("abnormal child exit: {}", .{term}), + else => |term| testError(io, "abnormal child exit: {}", .{term}), } if (parent_test_error) return error.ParentTestError; @@ -61,8 +62,8 @@ pub fn main() !void { var parent_test_error = false; -fn testError(comptime fmt: []const u8, args: anytype) void { - var stderr_writer = std.Io.File.stderr().writer(&.{}); +fn testError(io: Io, comptime fmt: []const u8, args: anytype) void { + var stderr_writer = Io.File.stderr().writer(io, &.{}); const stderr = &stderr_writer.interface; stderr.print("PARENT TEST ERROR: ", .{}) catch {}; stderr.print(fmt, args) catch {}; diff --git a/test/standalone/cmakedefine/check.zig b/test/standalone/cmakedefine/check.zig index 782e7f4dc3c67bd57f898eb0ac00d340d6619a34..d0751913d755d1fe4d175ed9d6f3c8f6d13fdaef 100644 --- a/test/standalone/cmakedefine/check.zig +++ b/test/standalone/cmakedefine/check.zig @@ -9,8 +9,11 @@ pub fn main() !void { const actual_path = args[1]; const expected_path = args[2]; - const actual = try std.fs.cwd().readFileAlloc(actual_path, arena, .limited(1024 * 1024)); - const expected = try std.fs.cwd().readFileAlloc(expected_path, arena, .limited(1024 * 1024)); + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); + + const actual = try std.Io.Dir.cwd().readFileAlloc(io, actual_path, arena, .limited(1024 * 1024)); + const expected = try std.Io.Dir.cwd().readFileAlloc(io, expected_path, arena, .limited(1024 * 1024)); // The actual output starts with a comment which we should strip out before comparing. const comment_str = "/* This file was generated by ConfigHeader using the Zig Build System. */\n"; diff --git a/test/standalone/dirname/build.zig b/test/standalone/dirname/build.zig index 0da85e29236dc7dc80a496b72b699c900e3805fd..b850680ba99966ba5b5fe4603250f66ca85d6095 100644 --- a/test/standalone/dirname/build.zig +++ b/test/standalone/dirname/build.zig @@ -59,13 +59,15 @@ pub fn build(b: *std.Build) void { // Absolute path: const abs_path = setup_abspath: { + // TODO this is a bad pattern, don't do this + const io = b.graph.io; const temp_dir = b.makeTempPath(); - var dir = std.fs.cwd().openDir(temp_dir, .{}) catch @panic("failed to open temp dir"); - defer dir.close(); + var dir = std.Io.Dir.cwd().openDir(io, temp_dir, .{}) catch @panic("failed to open temp dir"); + defer dir.close(io); - var file = dir.createFile("foo.txt", .{}) catch @panic("failed to create file"); - file.close(); + var file = dir.createFile(io, "foo.txt", .{}) catch @panic("failed to create file"); + file.close(io); break :setup_abspath std.Build.LazyPath{ .cwd_relative = temp_dir }; }; diff --git a/test/standalone/dirname/exists_in.zig b/test/standalone/dirname/exists_in.zig index 7aec1f423dce8f1d61fc67d2036b00ccab36223b..7bddc4e61395e1b75accafd37e452f5fc910c167 100644 --- a/test/standalone/dirname/exists_in.zig +++ b/test/standalone/dirname/exists_in.zig @@ -34,8 +34,11 @@ fn run(allocator: std.mem.Allocator) !void { return error.BadUsage; }; - var dir = try std.fs.cwd().openDir(dir_path, .{}); - defer dir.close(); + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); - _ = try dir.statFile(relpath); + var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{}); + defer dir.close(io); + + _ = try dir.statFile(io, relpath); } diff --git a/test/standalone/dirname/touch.zig b/test/standalone/dirname/touch.zig index 43fcabf91efab37c8975c9f432dcdb21ae12a8e1..9255d27c72ebbb182e564e4781c5d888fe47ed52 100644 --- a/test/standalone/dirname/touch.zig +++ b/test/standalone/dirname/touch.zig @@ -26,14 +26,17 @@ fn run(allocator: std.mem.Allocator) !void { return error.BadUsage; }; - const dir_path = std.fs.path.dirname(path) orelse unreachable; - const basename = std.fs.path.basename(path); + const dir_path = std.Io.Dir.path.dirname(path) orelse unreachable; + const basename = std.Io.Dir.path.basename(path); - var dir = try std.fs.cwd().openDir(dir_path, .{}); - defer dir.close(); + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); - _ = dir.statFile(basename) catch { - var file = try dir.createFile(basename, .{}); - file.close(); + var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{}); + defer dir.close(io); + + _ = dir.statFile(io, basename) catch { + var file = try dir.createFile(io, basename, .{}); + file.close(io); }; } diff --git a/test/standalone/entry_point/check_differ.zig b/test/standalone/entry_point/check_differ.zig index 63d1ec0294f21a22c72bfa7a9f79b9a2446581cb..bba45e5f8cf054efd464270e8ad5b405b5f12267 100644 --- a/test/standalone/entry_point/check_differ.zig +++ b/test/standalone/entry_point/check_differ.zig @@ -6,8 +6,11 @@ pub fn main() !void { const args = try std.process.argsAlloc(arena); if (args.len != 3) return error.BadUsage; // usage: 'check_differ ' - const contents_1 = try std.fs.cwd().readFileAlloc(args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty - const contents_2 = try std.fs.cwd().readFileAlloc(args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); + + const contents_1 = try std.Io.Dir.cwd().readFileAlloc(io, args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty + const contents_2 = try std.Io.Dir.cwd().readFileAlloc(io, args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty if (std.mem.eql(u8, contents_1, contents_2)) { return error.FilesMatch; diff --git a/test/standalone/glibc_compat/glibc_runtime_check.zig b/test/standalone/glibc_compat/glibc_runtime_check.zig index 82f4a54ee1e83b01ae548c97c5b3e7a21551ff9e..78b1f7efb37361228130feb642477a0d5bab0dba 100644 --- a/test/standalone/glibc_compat/glibc_runtime_check.zig +++ b/test/standalone/glibc_compat/glibc_runtime_check.zig @@ -28,10 +28,10 @@ extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: [*]const u8) c_int; // PR #17034 - fstat moved between libc_nonshared and libc fn checkStat() !void { - const cwdFd = std.fs.cwd().fd; + const cwd_fd = std.Io.Dir.cwd().handle; var buf: [256]u8 = @splat(0); - var result = fstatat(cwdFd, "a_file_that_definitely_does_not_exist", &buf, 0); + var result = fstatat(cwd_fd, "a_file_that_definitely_does_not_exist", &buf, 0); assert(result == -1); assert(std.posix.errno(result) == .NOENT); diff --git a/test/standalone/install_headers/check_exists.zig b/test/standalone/install_headers/check_exists.zig index 62706749aac0398f7256fd6f1b5b3d5c652e6346..8a7104e4f2072dcf002528a7340c5191630ba37e 100644 --- a/test/standalone/install_headers/check_exists.zig +++ b/test/standalone/install_headers/check_exists.zig @@ -11,8 +11,11 @@ pub fn main() !void { var arg_it = try std.process.argsWithAllocator(arena); _ = arg_it.next(); - const cwd = std.fs.cwd(); - const cwd_realpath = try cwd.realpathAlloc(arena, "."); + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); + + const cwd = std.Io.Dir.cwd(); + const cwd_realpath = try cwd.realPathAlloc(io, arena, "."); while (arg_it.next()) |file_path| { if (file_path.len > 0 and file_path[0] == '!') { @@ -20,7 +23,7 @@ pub fn main() !void { "exclusive file check '{s}{c}{s}' failed", .{ cwd_realpath, std.fs.path.sep, file_path[1..] }, ); - if (std.fs.cwd().statFile(file_path[1..])) |_| { + if (cwd.statFile(io, file_path[1..])) |_| { return error.FileFound; } else |err| switch (err) { error.FileNotFound => {}, @@ -31,7 +34,7 @@ pub fn main() !void { "inclusive file check '{s}{c}{s}' failed", .{ cwd_realpath, std.fs.path.sep, file_path }, ); - _ = try std.fs.cwd().statFile(file_path); + _ = try cwd.statFile(io, file_path); } } } diff --git a/test/standalone/libfuzzer/main.zig b/test/standalone/libfuzzer/main.zig index b275b6d593dccd1853cf71359984c1b4b128a168..077af4ad3eb171f3ea0f2666d968e141071ddd71 100644 --- a/test/standalone/libfuzzer/main.zig +++ b/test/standalone/libfuzzer/main.zig @@ -20,8 +20,8 @@ pub fn main() !void { const io = threaded.io(); const cache_dir_path = args.next() orelse @panic("expected cache directory path argument"); - var cache_dir = try std.fs.cwd().openDir(cache_dir_path, .{}); - defer cache_dir.close(); + var cache_dir = try std.Io.Dir.cwd().openDir(io, cache_dir_path, .{}); + defer cache_dir.close(io); abi.fuzzer_init(.fromSlice(cache_dir_path)); abi.fuzzer_init_test(testOne, .fromSlice("test")); @@ -30,8 +30,8 @@ pub fn main() !void { const pc_digest = abi.fuzzer_coverage().id; const coverage_file_path = "v/" ++ std.fmt.hex(pc_digest); - const coverage_file = try cache_dir.openFile(coverage_file_path, .{}); - defer coverage_file.close(); + const coverage_file = try cache_dir.openFile(io, coverage_file_path, .{}); + defer coverage_file.close(io); var read_buf: [@sizeOf(abi.SeenPcsHeader)]u8 = undefined; var r = coverage_file.reader(io, &read_buf); @@ -42,6 +42,6 @@ pub fn main() !void { const expected_len = @sizeOf(abi.SeenPcsHeader) + try std.math.divCeil(usize, pcs_header.pcs_len, @bitSizeOf(usize)) * @sizeOf(usize) + pcs_header.pcs_len * @sizeOf(usize); - if (try coverage_file.getEndPos() != expected_len) + if (try coverage_file.length(io) != expected_len) return error.WrongEnd; } diff --git a/test/standalone/posix/relpaths.zig b/test/standalone/posix/relpaths.zig index 40e2e09464bf66a968a4c540ae05776602f605a7..70e9fad6873e4d74a4e704ff8e48299bff8a6cc1 100644 --- a/test/standalone/posix/relpaths.zig +++ b/test/standalone/posix/relpaths.zig @@ -1,9 +1,11 @@ // Test relative paths through POSIX APIS. These tests have to change the cwd, so // they shouldn't be Zig unit tests. -const std = @import("std"); const builtin = @import("builtin"); +const std = @import("std"); +const Io = std.Io; + pub fn main() !void { if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir @@ -11,6 +13,11 @@ pub fn main() !void { const a = Allocator.allocator(); defer std.debug.assert(Allocator.deinit() == .ok); + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); + + // TODO this API isn't supposed to be used outside of unit testing. make it compilation error if used + // outside of unit testing. var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); @@ -18,7 +25,7 @@ pub fn main() !void { try tmp.dir.setAsCwd(); try test_symlink(a, tmp); - try test_link(tmp); + try test_link(io, tmp); } fn test_symlink(a: std.mem.Allocator, tmp: std.testing.TmpDir) !void { @@ -65,7 +72,7 @@ fn getLinkInfo(fd: std.posix.fd_t) !struct { std.posix.ino_t, std.posix.nlink_t return .{ st.ino, st.nlink }; } -fn test_link(tmp: std.testing.TmpDir) !void { +fn test_link(io: Io, tmp: std.testing.TmpDir) !void { switch (builtin.target.os.tag) { .linux, .illumos => {}, else => return, @@ -74,17 +81,17 @@ fn test_link(tmp: std.testing.TmpDir) !void { const target_name = "link-target"; const link_name = "newlink"; - try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" }); + try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" }); // Test 1: create the relative link from inside tmp try std.posix.link(target_name, link_name); // Verify - const efd = try tmp.dir.openFile(target_name, .{}); - defer efd.close(); + const efd = try tmp.dir.openFile(io, target_name, .{}); + defer efd.close(io); - const nfd = try tmp.dir.openFile(link_name, .{}); - defer nfd.close(); + const nfd = try tmp.dir.openFile(io, link_name, .{}); + defer nfd.close(io); { const eino, _ = try getLinkInfo(efd.handle); diff --git a/test/standalone/run_cwd/check_file_exists.zig b/test/standalone/run_cwd/check_file_exists.zig index 640fc99a7a4af931cde4177c0b00d13e151b0e5e..8830cc115a569475332122f604fa1dd626aa01a7 100644 --- a/test/standalone/run_cwd/check_file_exists.zig +++ b/test/standalone/run_cwd/check_file_exists.zig @@ -8,7 +8,10 @@ pub fn main() !void { if (args.len != 2) return error.BadUsage; const path = args[1]; - std.fs.cwd().access(path, .{}) catch return error.AccessFailed; + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); + + std.Io.Dir.cwd().access(io, path, .{}) catch return error.AccessFailed; } const std = @import("std"); diff --git a/test/standalone/run_output_caching/main.zig b/test/standalone/run_output_caching/main.zig index e4e6332f1128095edfe3b77927dc81dbd170f7a1..c21d8d0f6bbd8ea21dd41348e7d6cd4dff020383 100644 --- a/test/standalone/run_output_caching/main.zig +++ b/test/standalone/run_output_caching/main.zig @@ -1,10 +1,12 @@ const std = @import("std"); pub fn main() !void { + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); var args = try std.process.argsWithAllocator(std.heap.page_allocator); _ = args.skip(); const filename = args.next().?; - const file = try std.fs.cwd().createFile(filename, .{}); - defer file.close(); - try file.writeAll(filename); + const file = try std.Io.Dir.cwd().createFile(io, filename, .{}); + defer file.close(io); + try file.writeAll(io, filename); } diff --git a/test/standalone/run_output_paths/create_file.zig b/test/standalone/run_output_paths/create_file.zig index 260c36f10cea84c132a06f72266e4ecf99c772b7..4830790c79e9530a85a7496cf0c40d098cb6ca13 100644 --- a/test/standalone/run_output_paths/create_file.zig +++ b/test/standalone/run_output_paths/create_file.zig @@ -1,10 +1,12 @@ const std = @import("std"); pub fn main() !void { + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); var args = try std.process.argsWithAllocator(std.heap.page_allocator); _ = args.skip(); const dir_name = args.next().?; - const dir = try std.fs.cwd().openDir(if (std.mem.startsWith(u8, dir_name, "--dir=")) + const dir = try std.Io.Dir.cwd().openDir(io, if (std.mem.startsWith(u8, dir_name, "--dir=")) dir_name["--dir=".len..] else dir_name, .{}); diff --git a/test/standalone/self_exe_symlink/create-symlink.zig b/test/standalone/self_exe_symlink/create-symlink.zig index dac5891ba89ff396a1e0517485ff427b44bd90c2..6ccf6596aa89398f1f50db56a02efacfc3317aac 100644 --- a/test/standalone/self_exe_symlink/create-symlink.zig +++ b/test/standalone/self_exe_symlink/create-symlink.zig @@ -14,5 +14,9 @@ pub fn main() anyerror!void { // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`. const exe_rel_path = try std.fs.path.relative(allocator, std.fs.path.dirname(symlink_path) orelse ".", exe_path); defer allocator.free(exe_rel_path); - try std.fs.cwd().symLink(exe_rel_path, symlink_path, .{}); + + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); + + try std.Io.Dir.cwd().symLink(io, exe_rel_path, symlink_path, .{}); } diff --git a/test/standalone/self_exe_symlink/main.zig b/test/standalone/self_exe_symlink/main.zig index b74c4c7f95da12fd450ae46f3d87bd91029645d3..4df5b027203a12c0ecf9af8a28455134d46077f4 100644 --- a/test/standalone/self_exe_symlink/main.zig +++ b/test/standalone/self_exe_symlink/main.zig @@ -1,15 +1,19 @@ const std = @import("std"); pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer std.debug.assert(gpa.deinit() == .ok); - const allocator = gpa.allocator(); + var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks"); + const gpa = debug_allocator.allocator(); - const self_path = try std.fs.selfExePathAlloc(allocator); - defer allocator.free(self_path); + var threaded: std.Io.Threaded = .init(gpa); + defer threaded.deinit(); + const io = threaded.io(); + + const self_path = try std.fs.selfExePathAlloc(gpa); + defer gpa.free(self_path); var self_exe = try std.fs.openSelfExe(.{}); - defer self_exe.close(); + defer self_exe.close(io); var buf: [std.fs.max_path_bytes]u8 = undefined; const self_exe_path = try std.os.getFdPath(self_exe.handle, &buf); diff --git a/test/standalone/simple/cat/main.zig b/test/standalone/simple/cat/main.zig index dea528f4f7b639c098bfd74ab25963d1b888f788..699fc8075da282815d3204416a6194ebd23587e7 100644 --- a/test/standalone/simple/cat/main.zig +++ b/test/standalone/simple/cat/main.zig @@ -18,7 +18,7 @@ pub fn main() !void { const exe = args[0]; var catted_anything = false; var stdout_buffer: [4096]u8 = undefined; - var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout = &stdout_writer.interface; var stdin_reader = Io.File.stdin().readerStreaming(io, &.{}); @@ -32,8 +32,8 @@ pub fn main() !void { } else if (mem.startsWith(u8, arg, "-")) { return usage(exe); } else { - const file = cwd.openFile(arg, .{}) catch |err| fatal("unable to open file: {t}\n", .{err}); - defer file.close(); + const file = cwd.openFile(io, arg, .{}) catch |err| fatal("unable to open file: {t}\n", .{err}); + defer file.close(io); catted_anything = true; var file_reader = file.reader(io, &.{}); diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index f38807009732a885ec2c4ee41b0a48e2d6393fe6..1acae0aca2bc93281d3857c1d2358f5fb877285c 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -1,15 +1,20 @@ const std = @import("std"); const Io = std.Io; +const Allocator = std.mem.Allocator; const windows = std.os.windows; const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral; pub fn main() anyerror!void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer if (gpa.deinit() == .leak) @panic("found memory leaks"); - const allocator = gpa.allocator(); + var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks"); + const gpa = debug_allocator.allocator(); - var it = try std.process.argsWithAllocator(allocator); + var threaded: std.Io.Threaded = .init(gpa); + defer threaded.deinit(); + const io = threaded.io(); + + var it = try std.process.argsWithAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const hello_exe_cache_path = it.next() orelse unreachable; @@ -17,14 +22,14 @@ pub fn main() anyerror!void { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const tmp_absolute_path = try tmp.dir.realpathAlloc(allocator, "."); - defer allocator.free(tmp_absolute_path); - const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeAllocZ(allocator, tmp_absolute_path); - defer allocator.free(tmp_absolute_path_w); - const cwd_absolute_path = try std.fs.cwd().realpathAlloc(allocator, "."); - defer allocator.free(cwd_absolute_path); - const tmp_relative_path = try std.fs.path.relative(allocator, cwd_absolute_path, tmp_absolute_path); - defer allocator.free(tmp_relative_path); + const tmp_absolute_path = try tmp.dir.realpathAlloc(gpa, "."); + defer gpa.free(tmp_absolute_path); + const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeAllocZ(gpa, tmp_absolute_path); + defer gpa.free(tmp_absolute_path_w); + const cwd_absolute_path = try Io.Dir.cwd().realpathAlloc(gpa, "."); + defer gpa.free(cwd_absolute_path); + const tmp_relative_path = try std.fs.path.relative(gpa, cwd_absolute_path, tmp_absolute_path); + defer gpa.free(tmp_relative_path); // Clear PATH std.debug.assert(windows.kernel32.SetEnvironmentVariableW( @@ -39,10 +44,10 @@ pub fn main() anyerror!void { ) == windows.TRUE); // No PATH, so it should fail to find anything not in the cwd - try testExecError(error.FileNotFound, allocator, "something_missing"); + try testExecError(error.FileNotFound, gpa, "something_missing"); // make sure we don't get error.BadPath traversing out of cwd with a relative path - try testExecError(error.FileNotFound, allocator, "..\\.\\.\\.\\\\..\\more_missing"); + try testExecError(error.FileNotFound, gpa, "..\\.\\.\\.\\\\..\\more_missing"); std.debug.assert(windows.kernel32.SetEnvironmentVariableW( utf16Literal("PATH"), @@ -50,14 +55,14 @@ pub fn main() anyerror!void { ) == windows.TRUE); // Move hello.exe into the tmp dir which is now added to the path - try std.fs.cwd().copyFile(hello_exe_cache_path, tmp.dir, "hello.exe", .{}); + try Io.Dir.cwd().copyFile(hello_exe_cache_path, tmp.dir, "hello.exe", .{}); // with extension should find the .exe (case insensitive) - try testExec(allocator, "HeLLo.exe", "hello from exe\n"); + try testExec(gpa, "HeLLo.exe", "hello from exe\n"); // without extension should find the .exe (case insensitive) - try testExec(allocator, "heLLo", "hello from exe\n"); + try testExec(gpa, "heLLo", "hello from exe\n"); // with invalid cwd - try std.testing.expectError(error.FileNotFound, testExecWithCwd(allocator, "hello.exe", "missing_dir", "")); + try std.testing.expectError(error.FileNotFound, testExecWithCwd(gpa, io, "hello.exe", "missing_dir", "")); // now add a .bat try tmp.dir.writeFile(.{ .sub_path = "hello.bat", .data = "@echo hello from bat" }); @@ -65,33 +70,33 @@ pub fn main() anyerror!void { try tmp.dir.writeFile(.{ .sub_path = "hello.cmd", .data = "@echo hello from cmd" }); // with extension should find the .bat (case insensitive) - try testExec(allocator, "heLLo.bat", "hello from bat\r\n"); + try testExec(gpa, "heLLo.bat", "hello from bat\r\n"); // with extension should find the .cmd (case insensitive) - try testExec(allocator, "heLLo.cmd", "hello from cmd\r\n"); + try testExec(gpa, "heLLo.cmd", "hello from cmd\r\n"); // without extension should find the .exe (since its first in PATHEXT) - try testExec(allocator, "heLLo", "hello from exe\n"); + try testExec(gpa, "heLLo", "hello from exe\n"); // now rename the exe to not have an extension try renameExe(tmp.dir, "hello.exe", "hello"); // with extension should now fail - try testExecError(error.FileNotFound, allocator, "hello.exe"); + try testExecError(error.FileNotFound, gpa, "hello.exe"); // without extension should succeed (case insensitive) - try testExec(allocator, "heLLo", "hello from exe\n"); + try testExec(gpa, "heLLo", "hello from exe\n"); try tmp.dir.makeDir("something"); try renameExe(tmp.dir, "hello", "something/hello.exe"); - const relative_path_no_ext = try std.fs.path.join(allocator, &.{ tmp_relative_path, "something/hello" }); - defer allocator.free(relative_path_no_ext); + const relative_path_no_ext = try std.fs.path.join(gpa, &.{ tmp_relative_path, "something/hello" }); + defer gpa.free(relative_path_no_ext); // Giving a full relative path to something/hello should work - try testExec(allocator, relative_path_no_ext, "hello from exe\n"); + try testExec(gpa, relative_path_no_ext, "hello from exe\n"); // But commands with path separators get excluded from PATH searching, so this will fail - try testExecError(error.FileNotFound, allocator, "something/hello"); + try testExecError(error.FileNotFound, gpa, "something/hello"); // Now that .BAT is the first PATHEXT that should be found, this should succeed - try testExec(allocator, "heLLo", "hello from bat\r\n"); + try testExec(gpa, "heLLo", "hello from bat\r\n"); // Add a hello.exe that is not a valid executable try tmp.dir.writeFile(.{ .sub_path = "hello.exe", .data = "invalid" }); @@ -100,18 +105,18 @@ pub fn main() anyerror!void { // case for .EXE extensions, where if they ever try to get executed but they are // invalid, that gets treated as a fatal error wherever they are found and InvalidExe // is returned immediately. - try testExecError(error.InvalidExe, allocator, "hello.exe"); + try testExecError(error.InvalidExe, gpa, "hello.exe"); // Same thing applies to the command with no extension--even though there is a // hello.bat that could be executed, it should stop after it tries executing // hello.exe and getting InvalidExe. - try testExecError(error.InvalidExe, allocator, "hello"); + try testExecError(error.InvalidExe, gpa, "hello"); // If we now rename hello.exe to have no extension, it will behave differently try renameExe(tmp.dir, "hello.exe", "hello"); // Now, trying to execute it without an extension should treat InvalidExe as recoverable // and skip over it and find hello.bat and execute that - try testExec(allocator, "hello", "hello from bat\r\n"); + try testExec(gpa, "hello", "hello from bat\r\n"); // If we rename the invalid exe to something else try renameExe(tmp.dir, "hello", "goodbye"); @@ -119,13 +124,13 @@ pub fn main() anyerror!void { // since that is what the original error will be after searching for 'goodbye' // in the cwd. It will try to execute 'goodbye' from the PATH but the InvalidExe error // should be ignored in this case. - try testExecError(error.FileNotFound, allocator, "goodbye"); + try testExecError(error.FileNotFound, gpa, "goodbye"); // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir try tmp.dir.setAsCwd(); defer tmp.parent_dir.setAsCwd() catch {}; - const something_subdir_abs_path = try std.mem.concatWithSentinel(allocator, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0); - defer allocator.free(something_subdir_abs_path); + const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0); + defer gpa.free(something_subdir_abs_path); std.debug.assert(windows.kernel32.SetEnvironmentVariableW( utf16Literal("PATH"), @@ -134,37 +139,37 @@ pub fn main() anyerror!void { // Now trying to execute goodbye should give error.InvalidExe since it's the original // error that we got when trying within the cwd - try testExecError(error.InvalidExe, allocator, "goodbye"); + try testExecError(error.InvalidExe, gpa, "goodbye"); // hello should still find the .bat - try testExec(allocator, "hello", "hello from bat\r\n"); + try testExec(gpa, "hello", "hello from bat\r\n"); // If we rename something/hello.exe to something/goodbye.exe try renameExe(tmp.dir, "something/hello.exe", "something/goodbye.exe"); // And try to execute goodbye, then the one in something should be found // since the one in cwd is an invalid executable - try testExec(allocator, "goodbye", "hello from exe\n"); + try testExec(gpa, "goodbye", "hello from exe\n"); // If we use an absolute path to execute the invalid goodbye - const goodbye_abs_path = try std.mem.join(allocator, "\\", &.{ tmp_absolute_path, "goodbye" }); - defer allocator.free(goodbye_abs_path); + const goodbye_abs_path = try std.mem.join(gpa, "\\", &.{ tmp_absolute_path, "goodbye" }); + defer gpa.free(goodbye_abs_path); // then the PATH should not be searched and we should get InvalidExe - try testExecError(error.InvalidExe, allocator, goodbye_abs_path); + try testExecError(error.InvalidExe, gpa, goodbye_abs_path); // If we try to exec but provide a cwd that is an absolute path, the PATH // should still be searched and the goodbye.exe in something should be found. - try testExecWithCwd(allocator, "goodbye", tmp_absolute_path, "hello from exe\n"); + try testExecWithCwd(gpa, "goodbye", tmp_absolute_path, "hello from exe\n"); // introduce some extra path separators into the path which is dealt with inside the spawn call. const denormed_something_subdir_size = std.mem.replacementSize(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\")); - const denormed_something_subdir_abs_path = try allocator.allocSentinel(u16, denormed_something_subdir_size, 0); - defer allocator.free(denormed_something_subdir_abs_path); + const denormed_something_subdir_abs_path = try gpa.allocSentinel(u16, denormed_something_subdir_size, 0); + defer gpa.free(denormed_something_subdir_abs_path); _ = std.mem.replace(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\"), denormed_something_subdir_abs_path); - const denormed_something_subdir_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, denormed_something_subdir_abs_path); - defer allocator.free(denormed_something_subdir_wtf8); + const denormed_something_subdir_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, denormed_something_subdir_abs_path); + defer gpa.free(denormed_something_subdir_wtf8); // clear the path to ensure that the match comes from the cwd std.debug.assert(windows.kernel32.SetEnvironmentVariableW( @@ -172,18 +177,18 @@ pub fn main() anyerror!void { null, ) == windows.TRUE); - try testExecWithCwd(allocator, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n"); + try testExecWithCwd(gpa, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n"); // normalization should also work if the non-normalized path is found in the PATH var. std.debug.assert(windows.kernel32.SetEnvironmentVariableW( utf16Literal("PATH"), denormed_something_subdir_abs_path, ) == windows.TRUE); - try testExec(allocator, "goodbye", "hello from exe\n"); + try testExec(gpa, "goodbye", "hello from exe\n"); // now make sure we can launch executables "outside" of the cwd var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{}); - defer subdir_cwd.close(); + defer subdir_cwd.close(io); try renameExe(tmp.dir, "something/goodbye.exe", "hello.exe"); try subdir_cwd.setAsCwd(); @@ -195,25 +200,24 @@ pub fn main() anyerror!void { ) == windows.TRUE); // while we're at it make sure non-windows separators work fine - try testExec(allocator, "../hello", "hello from exe\n"); + try testExec(gpa, "../hello", "hello from exe\n"); } -fn testExecError(err: anyerror, allocator: std.mem.Allocator, command: []const u8) !void { - return std.testing.expectError(err, testExec(allocator, command, "")); +fn testExecError(err: anyerror, gpa: Allocator, command: []const u8) !void { + return std.testing.expectError(err, testExec(gpa, command, "")); } -fn testExec(allocator: std.mem.Allocator, command: []const u8, expected_stdout: []const u8) !void { - return testExecWithCwd(allocator, command, null, expected_stdout); +fn testExec(gpa: Allocator, command: []const u8, expected_stdout: []const u8) !void { + return testExecWithCwd(gpa, command, null, expected_stdout); } -fn testExecWithCwd(allocator: std.mem.Allocator, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void { - const result = try std.process.Child.run(.{ - .allocator = allocator, +fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void { + const result = try std.process.Child.run(gpa, io, .{ .argv = &[_][]const u8{command}, .cwd = cwd, }); - defer allocator.free(result.stdout); - defer allocator.free(result.stderr); + defer gpa.free(result.stdout); + defer gpa.free(result.stderr); try std.testing.expectEqualStrings("", result.stderr); try std.testing.expectEqualStrings(expected_stdout, result.stdout); diff --git a/test/tests.zig b/test/tests.zig index 2b0e8fb5d042944360d17438b4482a5e125adf93..5ca170c83c288296d6f81cc290f15b4aeea4f8cb 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2024,6 +2024,7 @@ pub fn addLinkTests( pub fn addCliTests(b: *std.Build) *Step { const step = b.step("test-cli", "Test the command line interface"); const s = std.fs.path.sep_str; + const io = b.graph.io; { // Test `zig init`. @@ -2132,13 +2133,13 @@ pub fn addCliTests(b: *std.Build) *Step { const tmp_path = b.makeTempPath(); const unformatted_code = " // no reason for indent"; - var dir = std.fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled"); - defer dir.close(); + var dir = std.Io.Dir.cwd().openDir(io, tmp_path, .{}) catch @panic("unhandled"); + defer dir.close(io); dir.writeFile(.{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled"); dir.writeFile(.{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled"); dir.makeDir("subdir") catch @panic("unhandled"); - var subdir = dir.openDir("subdir", .{}) catch @panic("unhandled"); - defer subdir.close(); + var subdir = dir.openDir(io, "subdir", .{}) catch @panic("unhandled"); + defer subdir.close(io); subdir.writeFile(.{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled"); // Test zig fmt affecting only the appropriate files. @@ -2634,7 +2635,7 @@ pub fn addCases( var cases = @import("src/Cases.zig").init(gpa, arena); var dir = try b.build_root.handle.openDir(io, "test/cases", .{ .iterate = true }); - defer dir.close(); + defer dir.close(io); cases.addFromDir(dir, b); try @import("cases.zig").addCases(&cases, build_options, b); @@ -2680,6 +2681,8 @@ pub fn addDebuggerTests(b: *std.Build, options: DebuggerContext.Options) ?*Step } pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void { + const io = b.graph.io; + const incr_check = b.addExecutable(.{ .name = "incr-check", .root_module = b.createModule(.{ @@ -2689,11 +2692,11 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void { }), }); - var dir = try b.build_root.handle.openDir("test/incremental", .{ .iterate = true }); - defer dir.close(); + var dir = try b.build_root.handle.openDir(io, "test/incremental", .{ .iterate = true }); + defer dir.close(io); var it = try dir.walk(b.graph.arena); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { if (entry.kind != .file) continue; const run = b.addRunArtifact(incr_check); -- 2.54.0 From 3725f72293c87a73e0c11e74739574c7b78bb53d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 18:46:55 -0800 Subject: [PATCH 029/195] update std.process.Child.run occurences to use io --- lib/compiler/libc.zig | 11 +- lib/compiler/reduce.zig | 7 +- lib/std/Build/Step.zig | 4 +- lib/std/zig/LibCInstallation.zig | 118 +++++++++------------- lib/std/zig/system/darwin.zig | 29 +++--- src/main.zig | 3 +- test/standalone/child_process/main.zig | 2 +- test/standalone/windows_bat_args/fuzz.zig | 17 ++-- test/standalone/windows_bat_args/test.zig | 11 +- test/standalone/windows_paths/test.zig | 48 +++++---- 10 files changed, 115 insertions(+), 135 deletions(-) diff --git a/lib/compiler/libc.zig b/lib/compiler/libc.zig index 142b87062eece7b5015ec2c8ff7666a9546fce7c..7a6edf0c44dec4f5a6d2a3e324dfc6c9c11d8b2b 100644 --- a/lib/compiler/libc.zig +++ b/lib/compiler/libc.zig @@ -78,7 +78,7 @@ pub fn main() !void { if (input_file) |libc_file| { const libc = try arena.create(LibCInstallation); libc.* = LibCInstallation.parse(arena, libc_file, &target) catch |err| { - fatal("unable to parse libc file at path {s}: {s}", .{ libc_file, @errorName(err) }); + fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err }); }; break :libc libc; } else { @@ -97,7 +97,7 @@ pub fn main() !void { libc_installation, ) catch |err| { const zig_target = try target.zigTriple(arena); - fatal("unable to detect libc for target {s}: {s}", .{ zig_target, @errorName(err) }); + fatal("unable to detect libc for target {s}: {t}", .{ zig_target, err }); }; if (libc_dirs.libc_include_dir_list.len == 0) { @@ -115,19 +115,18 @@ pub fn main() !void { if (input_file) |libc_file| { var libc = LibCInstallation.parse(gpa, libc_file, &target) catch |err| { - fatal("unable to parse libc file at path {s}: {s}", .{ libc_file, @errorName(err) }); + fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err }); }; defer libc.deinit(gpa); } else { if (!target_query.canDetectLibC()) { fatal("unable to detect libc for non-native target", .{}); } - var libc = LibCInstallation.findNative(.{ - .allocator = gpa, + var libc = LibCInstallation.findNative(gpa, io, .{ .verbose = true, .target = &target, }) catch |err| { - fatal("unable to detect native libc: {s}", .{@errorName(err)}); + fatal("unable to detect native libc: {t}", .{err}); }; defer libc.deinit(gpa); diff --git a/lib/compiler/reduce.zig b/lib/compiler/reduce.zig index 0bfa1902abd813074748737dde95b62a5242455c..f10bd12e9d9ec77a03fe1f471493c0d88399b4b6 100644 --- a/lib/compiler/reduce.zig +++ b/lib/compiler/reduce.zig @@ -307,11 +307,8 @@ fn termToInteresting(term: std.process.Child.Term) Interestingness { }; } -fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness { - const result = try std.process.Child.run(.{ - .allocator = arena, - .argv = argv, - }); +fn runCheck(arena: Allocator, io: Io, argv: []const []const u8) !Interestingness { + const result = try std.process.Child.run(arena, io, .{ .argv = argv }); if (result.stderr.len != 0) std.debug.print("{s}", .{result.stderr}); return termToInteresting(result.term); diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index f66f8df4c8b94b86bd2a9d159fb370a61fd94380..8115aaa1a1995211cf8af7d25863697ce6d9336c 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -350,6 +350,7 @@ pub fn captureChildProcess( argv: []const []const u8, ) !std.process.Child.RunResult { const arena = s.owner.allocator; + const io = s.owner.graph.io; // If an error occurs, it's happened in this command: assert(s.result_failed_command == null); @@ -358,8 +359,7 @@ pub fn captureChildProcess( try handleChildProcUnsupported(s); try handleVerbose(s.owner, null, argv); - const result = std.process.Child.run(.{ - .allocator = arena, + const result = std.process.Child.run(arena, io, .{ .argv = argv, .progress_node = progress_node, }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index 1463ff4a408af25453ec12020990c056c66dadf9..6051eecb948ec5dccbb9f1ef93bcaf445502714b 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -166,8 +166,6 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void { } pub const FindNativeOptions = struct { - allocator: Allocator, - io: Io, target: *const std.Target, /// If enabled, will print human-friendly errors to stderr. @@ -175,10 +173,7 @@ pub const FindNativeOptions = struct { }; /// Finds the default, native libc. -pub fn findNative(args: FindNativeOptions) FindError!LibCInstallation { - const gpa = args.allocator; - const io = args.io; - +pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!LibCInstallation { var self: LibCInstallation = .{}; if (is_darwin and args.target.os.tag.isDarwin()) { @@ -203,14 +198,14 @@ pub fn findNative(args: FindNativeOptions) FindError!LibCInstallation { }; defer sdk.free(gpa); - try self.findNativeMsvcIncludeDir(args, sdk); - try self.findNativeMsvcLibDir(args, sdk); - try self.findNativeKernel32LibDir(args, sdk); - try self.findNativeIncludeDirWindows(args, sdk); - try self.findNativeCrtDirWindows(args, sdk); + try self.findNativeMsvcIncludeDir(gpa, io, sdk); + try self.findNativeMsvcLibDir(gpa, sdk); + try self.findNativeKernel32LibDir(gpa, io, args, sdk); + try self.findNativeIncludeDirWindows(gpa, io, args, sdk); + try self.findNativeCrtDirWindows(gpa, io, args.target, sdk); } else if (is_haiku) { try self.findNativeIncludeDirPosix(args); - try self.findNativeGccDirHaiku(args); + try self.findNativeGccDirHaiku(gpa, io, args); self.crt_dir = try gpa.dupeZ(u8, "/system/develop/lib"); } else if (builtin.target.os.tag == .illumos) { // There is only one libc, and its headers/libraries are always in the same spot. @@ -221,7 +216,7 @@ pub fn findNative(args: FindNativeOptions) FindError!LibCInstallation { try self.findNativeIncludeDirPosix(args); switch (builtin.target.os.tag) { .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try gpa.dupeZ(u8, "/usr/lib"), - .linux => try self.findNativeCrtDirPosix(args), + .linux => try self.findNativeCrtDirPosix(gpa, io, args), else => {}, } } else { @@ -241,12 +236,9 @@ pub fn deinit(self: *LibCInstallation, allocator: Allocator) void { self.* = undefined; } -fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void { - const allocator = args.allocator; - const io = args.io; - +fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void { // Detect infinite loops. - var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { + var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) { error.Unexpected => unreachable, // WASI-only else => |e| return e, }; @@ -265,7 +257,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F const dev_null = if (is_windows) "nul" else "/dev/null"; - var argv = std.array_list.Managed([]const u8).init(allocator); + var argv = std.array_list.Managed([]const u8).init(gpa); defer argv.deinit(); try appendCcExe(&argv, skip_cc_env_var); @@ -276,8 +268,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F dev_null, }); - const run_res = std.process.Child.run(.{ - .allocator = allocator, + const run_res = std.process.Child.run(gpa, io, .{ .argv = argv.items, .max_output_bytes = 1024 * 1024, .env_map = &env_map, @@ -294,8 +285,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F }, }; defer { - allocator.free(run_res.stdout); - allocator.free(run_res.stderr); + gpa.free(run_res.stdout); + gpa.free(run_res.stderr); } switch (run_res.term) { .Exited => |code| if (code != 0) { @@ -309,7 +300,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F } var it = std.mem.tokenizeAny(u8, run_res.stderr, "\n\r"); - var search_paths = std.array_list.Managed([]const u8).init(allocator); + var search_paths = std.array_list.Managed([]const u8).init(gpa); defer search_paths.deinit(); while (it.next()) |line| { if (line.len != 0 and line[0] == ' ') { @@ -345,7 +336,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F if (self.include_dir == null) { if (search_dir.access(include_dir_example_file, .{})) |_| { - self.include_dir = try allocator.dupeZ(u8, search_path); + self.include_dir = try gpa.dupeZ(u8, search_path); } else |err| switch (err) { error.FileNotFound => {}, else => return error.FileSystem, @@ -354,7 +345,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F if (self.sys_include_dir == null) { if (search_dir.access(io, sys_include_dir_example_file, .{})) |_| { - self.sys_include_dir = try allocator.dupeZ(u8, search_path); + self.sys_include_dir = try gpa.dupeZ(u8, search_path); } else |err| switch (err) { error.FileNotFound => {}, else => return error.FileSystem, @@ -372,16 +363,14 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F fn findNativeIncludeDirWindows( self: *LibCInstallation, - args: FindNativeOptions, + gpa: Allocator, + io: Io, sdk: std.zig.WindowsSdk, ) FindError!void { - const allocator = args.allocator; - const io = args.io; - var install_buf: [2]std.zig.WindowsSdk.Installation = undefined; const installs = fillInstallations(&install_buf, sdk); - var result_buf = std.array_list.Managed(u8).init(allocator); + var result_buf = std.array_list.Managed(u8).init(gpa); defer result_buf.deinit(); for (installs) |install| { @@ -412,19 +401,18 @@ fn findNativeIncludeDirWindows( fn findNativeCrtDirWindows( self: *LibCInstallation, - args: FindNativeOptions, + gpa: Allocator, + io: Io, + target: *const std.Target, sdk: std.zig.WindowsSdk, ) FindError!void { - const allocator = args.allocator; - const io = args.io; - var install_buf: [2]std.zig.WindowsSdk.Installation = undefined; const installs = fillInstallations(&install_buf, sdk); - var result_buf = std.array_list.Managed(u8).init(allocator); + var result_buf = std.array_list.Managed(u8).init(gpa); defer result_buf.deinit(); - const arch_sub_dir = switch (args.target.cpu.arch) { + const arch_sub_dir = switch (target.cpu.arch) { .x86 => "x86", .x86_64 => "x64", .arm, .armeb => "arm", @@ -457,9 +445,8 @@ fn findNativeCrtDirWindows( return error.LibCRuntimeNotFound; } -fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void { - self.crt_dir = try ccPrintFileName(.{ - .allocator = args.allocator, +fn findNativeCrtDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void { + self.crt_dir = try ccPrintFileName(gpa, io, .{ .search_basename = switch (args.target.os.tag) { .linux => if (args.target.abi.isAndroid()) "crtbegin_dynamic.o" else "crt1.o", else => "crt1.o", @@ -469,9 +456,8 @@ fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindE }); } -fn findNativeGccDirHaiku(self: *LibCInstallation, args: FindNativeOptions) FindError!void { - self.gcc_dir = try ccPrintFileName(.{ - .allocator = args.allocator, +fn findNativeGccDirHaiku(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void { + self.gcc_dir = try ccPrintFileName(gpa, io, .{ .search_basename = "crtbeginS.o", .want_dirname = .only_dir, .verbose = args.verbose, @@ -480,16 +466,15 @@ fn findNativeGccDirHaiku(self: *LibCInstallation, args: FindNativeOptions) FindE fn findNativeKernel32LibDir( self: *LibCInstallation, + gpa: Allocator, + io: Io, args: FindNativeOptions, sdk: std.zig.WindowsSdk, ) FindError!void { - const allocator = args.allocator; - const io = args.io; - var install_buf: [2]std.zig.WindowsSdk.Installation = undefined; const installs = fillInstallations(&install_buf, sdk); - var result_buf = std.array_list.Managed(u8).init(allocator); + var result_buf = std.array_list.Managed(u8).init(gpa); defer result_buf.deinit(); const arch_sub_dir = switch (args.target.cpu.arch) { @@ -527,18 +512,16 @@ fn findNativeKernel32LibDir( fn findNativeMsvcIncludeDir( self: *LibCInstallation, - args: FindNativeOptions, + gpa: Allocator, + io: Io, sdk: std.zig.WindowsSdk, ) FindError!void { - const allocator = args.allocator; - const io = args.io; - const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCStdLibHeaderNotFound; const up1 = fs.path.dirname(msvc_lib_dir) orelse return error.LibCStdLibHeaderNotFound; const up2 = fs.path.dirname(up1) orelse return error.LibCStdLibHeaderNotFound; - const dir_path = try fs.path.join(allocator, &[_][]const u8{ up2, "include" }); - errdefer allocator.free(dir_path); + const dir_path = try fs.path.join(gpa, &[_][]const u8{ up2, "include" }); + errdefer gpa.free(dir_path); var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch |err| switch (err) { error.FileNotFound, @@ -560,27 +543,23 @@ fn findNativeMsvcIncludeDir( fn findNativeMsvcLibDir( self: *LibCInstallation, - args: FindNativeOptions, + gpa: Allocator, sdk: std.zig.WindowsSdk, ) FindError!void { - const allocator = args.allocator; const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCRuntimeNotFound; - self.msvc_lib_dir = try allocator.dupe(u8, msvc_lib_dir); + self.msvc_lib_dir = try gpa.dupe(u8, msvc_lib_dir); } pub const CCPrintFileNameOptions = struct { - allocator: Allocator, search_basename: []const u8, want_dirname: enum { full_path, only_dir }, verbose: bool = false, }; /// caller owns returned memory -fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { - const allocator = args.allocator; - +fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 { // Detect infinite loops. - var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { + var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) { error.Unexpected => unreachable, // WASI-only else => |e| return e, }; @@ -597,17 +576,16 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { break :blk false; }; - var argv = std.array_list.Managed([]const u8).init(allocator); + var argv = std.array_list.Managed([]const u8).init(gpa); defer argv.deinit(); - const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={s}", .{args.search_basename}); - defer allocator.free(arg1); + const arg1 = try std.fmt.allocPrint(gpa, "-print-file-name={s}", .{args.search_basename}); + defer gpa.free(arg1); try appendCcExe(&argv, skip_cc_env_var); try argv.append(arg1); - const run_res = std.process.Child.run(.{ - .allocator = allocator, + const run_res = std.process.Child.run(gpa, io, .{ .argv = argv.items, .max_output_bytes = 1024 * 1024, .env_map = &env_map, @@ -621,8 +599,8 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { else => return error.UnableToSpawnCCompiler, }; defer { - allocator.free(run_res.stdout); - allocator.free(run_res.stderr); + gpa.free(run_res.stdout); + gpa.free(run_res.stderr); } switch (run_res.term) { .Exited => |code| if (code != 0) { @@ -641,10 +619,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { // So we detect failure by checking if the output matches exactly the input. if (std.mem.eql(u8, line, args.search_basename)) return error.LibCRuntimeNotFound; switch (args.want_dirname) { - .full_path => return allocator.dupeZ(u8, line), + .full_path => return gpa.dupeZ(u8, line), .only_dir => { const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound; - return allocator.dupeZ(u8, dirname); + return gpa.dupeZ(u8, dirname); }, } } diff --git a/lib/std/zig/system/darwin.zig b/lib/std/zig/system/darwin.zig index fbd6da2a9ef6004d936edb85c40641622d261330..b493ccf0ec378bd24fd33fbea534b00f07c53fb0 100644 --- a/lib/std/zig/system/darwin.zig +++ b/lib/std/zig/system/darwin.zig @@ -1,28 +1,29 @@ const std = @import("std"); +const Io = std.Io; const mem = std.mem; -const Allocator = mem.Allocator; +const Allocator = std.mem.Allocator; const Target = std.Target; const Version = std.SemanticVersion; pub const macos = @import("darwin/macos.zig"); /// Check if SDK is installed on Darwin without triggering CLT installation popup window. -/// Note: simply invoking `xcrun` will inevitably trigger the CLT installation popup. +/// +/// Simply invoking `xcrun` will inevitably trigger the CLT installation popup. /// Therefore, we resort to invoking `xcode-select --print-path` and checking /// if the status is nonzero. +/// /// stderr from xcode-select is ignored. +/// /// If error.OutOfMemory occurs in Allocator, this function returns null. -pub fn isSdkInstalled(allocator: Allocator) bool { - const result = std.process.Child.run(.{ - .allocator = allocator, +pub fn isSdkInstalled(gpa: Allocator, io: Io) bool { + const result = std.process.Child.run(gpa, io, .{ .argv = &.{ "xcode-select", "--print-path" }, }) catch return false; - defer { - allocator.free(result.stderr); - allocator.free(result.stdout); + gpa.free(result.stderr); + gpa.free(result.stdout); } - return switch (result.term) { .Exited => |code| if (code == 0) result.stdout.len > 0 else false, else => false, @@ -34,7 +35,7 @@ pub fn isSdkInstalled(allocator: Allocator) bool { /// Caller owns the memory. /// stderr from xcrun is ignored. /// If error.OutOfMemory occurs in Allocator, this function returns null. -pub fn getSdk(allocator: Allocator, target: *const Target) ?[]const u8 { +pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 { const is_simulator_abi = target.abi == .simulator; const sdk = switch (target.os.tag) { .driverkit => "driverkit", @@ -46,16 +47,16 @@ pub fn getSdk(allocator: Allocator, target: *const Target) ?[]const u8 { else => return null, }; const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" }; - const result = std.process.Child.run(.{ .allocator = allocator, .argv = argv }) catch return null; + const result = std.process.Child.run(gpa, io, .{ .argv = argv }) catch return null; defer { - allocator.free(result.stderr); - allocator.free(result.stdout); + gpa.free(result.stderr); + gpa.free(result.stdout); } switch (result.term) { .Exited => |code| if (code != 0) return null, else => return null, } - return allocator.dupe(u8, mem.trimEnd(u8, result.stdout, "\r\n")) catch null; + return gpa.dupe(u8, mem.trimEnd(u8, result.stdout, "\r\n")) catch null; } test { diff --git a/src/main.zig b/src/main.zig index 9bb88d373be099554583843eda00ade66674153f..58f0e68301b7c0ea7dff91762b1c60e2f01e0381 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4017,8 +4017,7 @@ fn createModule( any_name_queries_remaining) { if (create_module.libc_installation == null) { - create_module.libc_installation = LibCInstallation.findNative(.{ - .allocator = arena, + create_module.libc_installation = LibCInstallation.findNative(arena, io, .{ .verbose = true, .target = target, }) catch |err| { diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index 755beacb6d6bac989d614189c7a69874d8508db1..2a28845a56f2598b7d4a02ad21a6f7888710e6da 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -57,7 +57,7 @@ pub fn main() !void { // Check that FileNotFound is consistent across platforms when trying to spawn an executable that doesn't exist const missing_child_path = try std.mem.concat(gpa, u8, &.{ child_path, "_intentionally_missing" }); defer gpa.free(missing_child_path); - try std.testing.expectError(error.FileNotFound, std.process.Child.run(.{ .allocator = gpa, .argv = &.{missing_child_path} })); + try std.testing.expectError(error.FileNotFound, std.process.Child.run(gpa, io, .{ .argv = &.{missing_child_path} })); } var parent_test_error = false; diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig index 8b9895b52dd6994a37bebe30159f19c4321c889d..bbc0d94077c532d7ea2e5c13fa2f46c3fc9951e4 100644 --- a/test/standalone/windows_bat_args/fuzz.zig +++ b/test/standalone/windows_bat_args/fuzz.zig @@ -1,5 +1,7 @@ -const std = @import("std"); const builtin = @import("builtin"); + +const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; pub fn main() anyerror!void { @@ -78,13 +80,13 @@ pub fn main() anyerror!void { } } -fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void { - try testExecBat(gpa, "args1.bat", args, env); - try testExecBat(gpa, "args2.bat", args, env); - try testExecBat(gpa, "args3.bat", args, env); +fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void { + try testExecBat(gpa, io, "args1.bat", args, env); + try testExecBat(gpa, io, "args2.bat", args, env); + try testExecBat(gpa, io, "args3.bat", args, env); } -fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { +fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { const argv = try gpa.alloc([]const u8, 1 + args.len); defer gpa.free(argv); argv[0] = bat; @@ -92,8 +94,7 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat"); - const result = try std.process.Child.run(.{ - .allocator = gpa, + const result = try std.process.Child.run(gpa, io, .{ .env_map = env, .argv = argv, }); diff --git a/test/standalone/windows_bat_args/test.zig b/test/standalone/windows_bat_args/test.zig index 4690d983f3a1e2fb97a8dc79fdbe043d53c6e3d0..558adc248f13d6de7675e8ec754c16fb800d484a 100644 --- a/test/standalone/windows_bat_args/test.zig +++ b/test/standalone/windows_bat_args/test.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const Io = std.Io; +const Allocator = std.mem.Allocator; pub fn main() anyerror!void { var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init; @@ -121,17 +123,17 @@ pub fn main() anyerror!void { try std.testing.expectError(error.FileNotFound, tmp.dir.access("file.txt", .{})); } -fn testExecError(err: anyerror, gpa: std.mem.Allocator, args: []const []const u8) !void { +fn testExecError(err: anyerror, gpa: Allocator, args: []const []const u8) !void { return std.testing.expectError(err, testExec(gpa, args, null)); } -fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void { +fn testExec(gpa: Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void { try testExecBat(gpa, "args1.bat", args, env); try testExecBat(gpa, "args2.bat", args, env); try testExecBat(gpa, "args3.bat", args, env); } -fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { +fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { const argv = try gpa.alloc([]const u8, 1 + args.len); defer gpa.free(argv); argv[0] = bat; @@ -139,8 +141,7 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat"); - const result = try std.process.Child.run(.{ - .allocator = gpa, + const result = try std.process.Child.run(gpa, io, .{ .env_map = env, .argv = argv, }); diff --git a/test/standalone/windows_paths/test.zig b/test/standalone/windows_paths/test.zig index 2ec23417e697027824cd0804f49dbc770d78ca36..f5fa5947666908f888dbac8dcac55522c93cefab 100644 --- a/test/standalone/windows_paths/test.zig +++ b/test/standalone/windows_paths/test.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; pub fn main() anyerror!void { var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); @@ -9,6 +10,9 @@ pub fn main() anyerror!void { if (args.len < 2) return error.MissingArgs; + var threaded: Io.Threaded = .init_single_threaded; + const io = threaded.io(); + const exe_path = args[1]; const cwd_path = try std.process.getCwdAlloc(arena); @@ -33,39 +37,39 @@ pub fn main() anyerror!void { // With the special =X: environment variable set, drive-relative paths that // don't match the CWD's drive letter are resolved against that env var. - try checkRelative(arena, "..\\..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &alt_drive_env_map); - try checkRelative(arena, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &alt_drive_env_map); + try checkRelative(arena, io, "..\\..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &alt_drive_env_map); + try checkRelative(arena, io, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &alt_drive_env_map); // Without that environment variable set, drive-relative paths that don't match the // CWD's drive letter are resolved against the root of the drive. - try checkRelative(arena, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env); - try checkRelative(arena, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env); + try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env); + try checkRelative(arena, io, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env); // Bare drive-relative path with no components - try checkRelative(arena, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &empty_env); - try checkRelative(arena, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &empty_env); + try checkRelative(arena, io, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &empty_env); + try checkRelative(arena, io, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &empty_env); // Bare drive-relative path with no components, drive-CWD set - try checkRelative(arena, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &alt_drive_env_map); - try checkRelative(arena, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &alt_drive_env_map); + try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &alt_drive_env_map); + try checkRelative(arena, io, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &alt_drive_env_map); // Bare drive-relative path relative to the CWD should be equivalent if drive-CWD is set - try checkRelative(arena, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, null, &alt_drive_env_map); - try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, null, &alt_drive_env_map); + try checkRelative(arena, io, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, null, &alt_drive_env_map); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, null, &alt_drive_env_map); // Bare drive-relative should always be equivalent to itself - try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map); - try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map); - try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env); - try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env); } if (parsed_cwd_path.kind == .unc_absolute) { const drive_abs_path = try std.fmt.allocPrint(arena, "{c}:\\foo\\bar", .{alt_drive_letter}); { - try checkRelative(arena, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, null, &empty_env); - try checkRelative(arena, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, null, &empty_env); + try checkRelative(arena, io, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, null, &empty_env); + try checkRelative(arena, io, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, null, &empty_env); } } else if (parsed_cwd_path.kind == .drive_absolute) { const cur_drive_letter = parsed_cwd_path.root[0]; @@ -73,14 +77,14 @@ pub fn main() anyerror!void { const unc_cwd = try std.fmt.allocPrint(arena, "\\\\127.0.0.1\\{c}$\\{s}", .{ cur_drive_letter, path_beyond_root }); { - try checkRelative(arena, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, null, &empty_env); - try checkRelative(arena, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, null, &empty_env); + try checkRelative(arena, io, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, null, &empty_env); + try checkRelative(arena, io, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, null, &empty_env); } { const drive_abs = cwd_path; const drive_rel = parsed_cwd_path.root[0..2]; - try checkRelative(arena, "", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env); - try checkRelative(arena, "", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env); + try checkRelative(arena, io, "", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env); } } else { return error.UnexpectedPathType; @@ -89,13 +93,13 @@ pub fn main() anyerror!void { fn checkRelative( allocator: std.mem.Allocator, + io: Io, expected_stdout: []const u8, argv: []const []const u8, cwd: ?[]const u8, env_map: ?*const std.process.EnvMap, ) !void { - const result = try std.process.Child.run(.{ - .allocator = allocator, + const result = try std.process.Child.run(allocator, io, .{ .argv = argv, .cwd = cwd, .env_map = env_map, -- 2.54.0 From ebdbbd20ace6e93b581b90075f52946b3832da93 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 19:41:21 -0800 Subject: [PATCH 030/195] update makeDir() sites to specify permissions --- lib/compiler/reduce.zig | 16 +++--- lib/std/Build/Step/CheckObject.zig | 2 + lib/std/Build/Step/RemoveDir.zig | 11 ++-- lib/std/Build/Step/Run.zig | 2 +- lib/std/fs/test.zig | 70 +++++++++++------------ lib/std/testing.zig | 2 +- src/Compilation.zig | 22 +++---- src/Package/Fetch.zig | 8 ++- src/Package/Fetch/git.zig | 4 +- src/main.zig | 1 + test/src/Cases.zig | 4 +- test/standalone/posix/relpaths.zig | 21 +++---- test/standalone/windows_bat_args/fuzz.zig | 11 ++-- test/standalone/windows_bat_args/test.zig | 9 ++- test/standalone/windows_spawn/main.zig | 8 +-- test/tests.zig | 8 +-- 16 files changed, 102 insertions(+), 97 deletions(-) diff --git a/lib/compiler/reduce.zig b/lib/compiler/reduce.zig index f10bd12e9d9ec77a03fe1f471493c0d88399b4b6..0c61111d10297b12555f78662a431ead685a9a8d 100644 --- a/lib/compiler/reduce.zig +++ b/lib/compiler/reduce.zig @@ -92,9 +92,7 @@ pub fn main() !void { if (i >= args.len) fatal("expected 32-bit integer after {s}", .{arg}); const next_arg = args[i]; seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| { - fatal("unable to parse seed '{s}' as 32-bit integer: {s}", .{ - next_arg, @errorName(err), - }); + fatal("unable to parse seed '{s}' as 32-bit integer: {t}", .{ next_arg, err }); }; } else { fatal("unrecognized parameter: '{s}'", .{arg}); @@ -125,7 +123,7 @@ pub fn main() !void { var astgen_input: std.Io.Writer.Allocating = .init(gpa); defer astgen_input.deinit(); - var tree = try parse(gpa, root_source_file_path); + var tree = try parse(gpa, io, root_source_file_path); defer { gpa.free(tree.source); tree.deinit(gpa); @@ -190,7 +188,7 @@ pub fn main() !void { std.debug.print("{s} ", .{@tagName(t)}); } std.debug.print("\n", .{}); - try transformationsToFixups(gpa, arena, root_source_file_path, this_set, &fixups); + try transformationsToFixups(gpa, arena, io, root_source_file_path, this_set, &fixups); rendered.clearRetainingCapacity(); try tree.render(gpa, &rendered.writer, fixups); @@ -246,7 +244,7 @@ pub fn main() !void { }); switch (interestingness) { .interesting => { - const new_tree = try parse(gpa, root_source_file_path); + const new_tree = try parse(gpa, io, root_source_file_path); gpa.free(tree.source); tree.deinit(gpa); tree = new_tree; @@ -317,6 +315,7 @@ fn runCheck(arena: Allocator, io: Io, argv: []const []const u8) !Interestingness fn transformationsToFixups( gpa: Allocator, arena: Allocator, + io: Io, root_source_file_path: []const u8, transforms: []const Walk.Transformation, fixups: *Ast.Render.Fixups, @@ -354,7 +353,7 @@ fn transformationsToFixups( inline_imported_file.imported_string, }); defer gpa.free(full_imported_path); - var other_file_ast = try parse(gpa, full_imported_path); + var other_file_ast = try parse(gpa, io, full_imported_path); defer { gpa.free(other_file_ast.source); other_file_ast.deinit(gpa); @@ -398,8 +397,9 @@ fn transformationsToFixups( }; } -fn parse(gpa: Allocator, file_path: []const u8) !Ast { +fn parse(gpa: Allocator, io: Io, file_path: []const u8) !Ast { const source_code = Io.Dir.cwd().readFileAllocOptions( + io, file_path, gpa, .limited(std.math.maxInt(u32)), diff --git a/lib/std/Build/Step/CheckObject.zig b/lib/std/Build/Step/CheckObject.zig index c6c11ce2b9a05a96869dcadf2ea8770ff729f29b..ac8fafaa3fc965e808466c4573a7a6746c683097 100644 --- a/lib/std/Build/Step/CheckObject.zig +++ b/lib/std/Build/Step/CheckObject.zig @@ -547,12 +547,14 @@ pub fn checkComputeCompare( fn make(step: *Step, make_options: Step.MakeOptions) !void { _ = make_options; const b = step.owner; + const io = b.graph.io; const gpa = b.allocator; const check_object: *CheckObject = @fieldParentPtr("step", step); try step.singleUnchangingWatchInput(check_object.source); const src_path = check_object.source.getPath3(b, step); const contents = src_path.root_dir.handle.readFileAllocOptions( + io, src_path.sub_path, gpa, .limited(check_object.max_bytes), diff --git a/lib/std/Build/Step/RemoveDir.zig b/lib/std/Build/Step/RemoveDir.zig index e2d4c02abca8270603279dca01061cbe98a1f798..6f933da9eea5ed19def986230047c9d3400e1501 100644 --- a/lib/std/Build/Step/RemoveDir.zig +++ b/lib/std/Build/Step/RemoveDir.zig @@ -27,6 +27,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { _ = options; const b = step.owner; + const io = b.graph.io; const remove_dir: *RemoveDir = @fieldParentPtr("step", step); step.clearWatchInputs(); @@ -34,15 +35,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const full_doomed_path = remove_dir.doomed_path.getPath2(b, step); - b.build_root.handle.deleteTree(full_doomed_path) catch |err| { + b.build_root.handle.deleteTree(io, full_doomed_path) catch |err| { if (b.build_root.path) |base| { - return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{ - base, full_doomed_path, @errorName(err), - }); + return step.fail("unable to recursively delete path '{s}/{s}': {t}", .{ base, full_doomed_path, err }); } else { - return step.fail("unable to recursively delete path '{s}': {s}", .{ - full_doomed_path, @errorName(err), - }); + return step.fail("unable to recursively delete path '{s}': {t}", .{ full_doomed_path, err }); } }; } diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index e52dd21a96a242e779f649eafe32921d2fe08cfd..19d2e7e61cdb2b282c32e732cd8e20b913b0592e 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1044,7 +1044,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |err| { if (err == error.PathAlreadyExists) { - b.cache_root.handle.deleteTree(o_sub_path) catch |del_err| { + b.cache_root.handle.deleteTree(io, o_sub_path) catch |del_err| { return step.fail("unable to remove dir '{f}'{s}: {t}", .{ b.cache_root, tmp_dir_path, del_err, }); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index bb3a9da0f5c51f76c8233752dbc3935b2add0088..edc15526b26af305df5a556da581b6fc1ced149e 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -186,7 +186,7 @@ test "Dir.readLink" { const file_target_path = try ctx.transformPath("file.txt"); try ctx.dir.writeFile(io, .{ .sub_path = file_target_path, .data = "nonsense" }); const dir_target_path = try ctx.transformPath("subdir"); - try ctx.dir.makeDir(dir_target_path); + try ctx.dir.makeDir(io, dir_target_path, .default_dir); // On Windows, symlink targets always use the canonical path separator const canonical_file_target_path = try ctx.toCanonicalPathSep(file_target_path); @@ -282,7 +282,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { const dir_target_path = try ctx.transformPath("subdir"); - try ctx.dir.makeDir(dir_target_path); + try ctx.dir.makeDir(io, dir_target_path, .default_dir); try setupSymlink(ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); @@ -363,7 +363,7 @@ test "openDir" { fn impl(ctx: *TestContext) !void { const allocator = ctx.arena.allocator(); const subdir_path = try ctx.transformPath("subdir"); - try ctx.dir.makeDir(subdir_path); + try ctx.dir.makeDir(io, subdir_path, .default_dir); for ([_][]const u8{ "", ".", ".." }) |sub_path| { const dir_path = try fs.path.join(allocator, &.{ subdir_path, sub_path }); @@ -398,7 +398,7 @@ test "openDirAbsolute" { const tmp_ino = (try tmp.dir.stat(io)).inode; - try tmp.dir.makeDir("subdir"); + try tmp.dir.makeDir(io, "subdir", .default_dir); const sub_path = try tmp.dir.realpathAlloc(testing.allocator, "subdir"); defer testing.allocator.free(sub_path); @@ -494,7 +494,7 @@ test "readLinkAbsolute" { // Create some targets try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = "nonsense" }); - try tmp.dir.makeDir("subdir"); + try tmp.dir.makeDir(io, "subdir", .default_dir); // Get base abs path var arena = ArenaAllocator.init(testing.allocator); @@ -531,7 +531,7 @@ test "Dir.Iterator" { const file = try tmp_dir.dir.createFile(io, "some_file", .{}); file.close(io); - try tmp_dir.dir.makeDir("some_dir"); + try tmp_dir.dir.makeDir(io, "some_dir", .default_dir); var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); @@ -600,7 +600,7 @@ test "Dir.Iterator twice" { const file = try tmp_dir.dir.createFile(io, "some_file", .{}); file.close(io); - try tmp_dir.dir.makeDir("some_dir"); + try tmp_dir.dir.makeDir(io, "some_dir", .default_dir); var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); @@ -635,7 +635,7 @@ test "Dir.Iterator reset" { const file = try tmp_dir.dir.createFile(io, "some_file", .{}); file.close(io); - try tmp_dir.dir.makeDir("some_dir"); + try tmp_dir.dir.makeDir(io, "some_dir", .default_dir); var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); @@ -682,7 +682,7 @@ test "Dir.Iterator but dir is deleted during iteration" { // This is a contrived reproduction, but this could happen outside of the program, in another thread, etc. // If we get an error while trying to delete, we can skip this test (this will happen on platforms // like Windows which will give FileBusy if the directory is currently open for iteration). - tmp.dir.deleteTree("subdir") catch return error.SkipZigTest; + tmp.dir.deleteTree(io, "subdir") catch return error.SkipZigTest; // Now, when we try to iterate, the next call should return null immediately. const entry = try iterator.next(); @@ -724,7 +724,7 @@ test "Dir.realpath smoke test" { // Now create the file and dir try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); - try ctx.dir.makeDir(test_dir_path); + try ctx.dir.makeDir(io, test_dir_path, .default_dir); const base_path = try ctx.transformPath("."); const base_realpath = try ctx.dir.realpathAlloc(allocator, base_path); @@ -842,7 +842,7 @@ test "directory operations on files" { var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true }); file.close(io); - try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name)); + try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(io, test_file_name, .default_dir)); try testing.expectError(error.NotDir, ctx.dir.openDir(io, test_file_name, .{})); try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name)); @@ -870,7 +870,7 @@ test "file operations on directories" { fn impl(ctx: *TestContext) !void { const test_dir_name = try ctx.transformPath("test_dir"); - try ctx.dir.makeDir(test_dir_name); + try ctx.dir.makeDir(io, test_dir_name, .default_dir); try testing.expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{})); try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name)); @@ -937,7 +937,7 @@ test "deleteDir" { try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(test_dir_path)); // deleting a non-empty directory - try ctx.dir.makeDir(test_dir_path); + try ctx.dir.makeDir(io, test_dir_path, .default_dir); try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path)); @@ -1006,7 +1006,7 @@ test "Dir.rename directories" { const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed"); // Renaming directories - try ctx.dir.makeDir(test_dir_path); + try ctx.dir.makeDir(io, test_dir_path, .default_dir); try ctx.dir.rename(test_dir_path, ctx.dir, test_dir_renamed_path, io); // Ensure the directory was renamed @@ -1042,8 +1042,8 @@ test "Dir.rename directory onto empty dir" { const test_dir_path = try ctx.transformPath("test_dir"); const target_dir_path = try ctx.transformPath("target_dir_path"); - try ctx.dir.makeDir(test_dir_path); - try ctx.dir.makeDir(target_dir_path); + try ctx.dir.makeDir(io, test_dir_path, .default_dir); + try ctx.dir.makeDir(io, target_dir_path, .default_dir); try ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io); // Ensure the directory was renamed @@ -1064,7 +1064,7 @@ test "Dir.rename directory onto non-empty dir" { const test_dir_path = try ctx.transformPath("test_dir"); const target_dir_path = try ctx.transformPath("target_dir_path"); - try ctx.dir.makeDir(test_dir_path); + try ctx.dir.makeDir(io, test_dir_path, .default_dir); var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{}); var file = try target_dir.createFile(io, "test_file", .{ .read = true }); @@ -1093,7 +1093,7 @@ test "Dir.rename file <-> dir" { var file = try ctx.dir.createFile(io, test_file_path, .{ .read = true }); file.close(io); - try ctx.dir.makeDir(test_dir_path); + try ctx.dir.makeDir(io, test_dir_path, .default_dir); try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, ctx.dir, test_dir_path, io)); try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, ctx.dir, test_file_path, io)); } @@ -1163,7 +1163,7 @@ test "renameAbsolute" { // Renaming directories const test_dir_name = "test_dir"; const renamed_test_dir_name = "test_dir_renamed"; - try tmp_dir.dir.makeDir(test_dir_name); + try tmp_dir.dir.makeDir(io, test_dir_name, .default_dir); try fs.renameAbsolute( try fs.path.join(allocator, &.{ base_path, test_dir_name }), try fs.path.join(allocator, &.{ base_path, renamed_test_dir_name }), @@ -1209,7 +1209,7 @@ test "deleteTree does not follow symlinks" { try setupSymlink(a, "../b", "b", .{ .is_directory = true }); } - try tmp.dir.deleteTree("a"); + try tmp.dir.deleteTree(io, "a"); try testing.expectError(error.FileNotFound, tmp.dir.access(io, "a", .{})); try tmp.dir.access(io, "b", .{}); @@ -1225,7 +1225,7 @@ test "deleteTree on a symlink" { try tmp.dir.writeFile(io, .{ .sub_path = "file", .data = "" }); try setupSymlink(tmp.dir, "file", "filelink", .{}); - try tmp.dir.deleteTree("filelink"); + try tmp.dir.deleteTree(io, "filelink"); try testing.expectError(error.FileNotFound, tmp.dir.access(io, "filelink", .{})); try tmp.dir.access(io, "file", .{}); @@ -1233,7 +1233,7 @@ test "deleteTree on a symlink" { try tmp.dir.makePath(io, "dir"); try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true }); - try tmp.dir.deleteTree("dirlink"); + try tmp.dir.deleteTree(io, "dirlink"); try testing.expectError(error.FileNotFound, tmp.dir.access(io, "dirlink", .{})); try tmp.dir.access(io, "dir", .{}); } @@ -1255,7 +1255,7 @@ test "makePath, put some files in it, deleteTree" { .data = "blah", }); - try ctx.dir.deleteTree(dir_path); + try ctx.dir.deleteTree(io, dir_path); try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{})); } }.impl); @@ -1291,7 +1291,7 @@ test "makePath in a directory that no longer exists" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.parent_dir.deleteTree(&tmp.sub_path); + try tmp.parent_dir.deleteTree(io, &tmp.sub_path); try testing.expectError(error.FileNotFound, tmp.dir.makePath(io, "sub-path")); } @@ -1302,7 +1302,7 @@ test "makePath but sub_path contains pre-existing file" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makeDir("foo"); + try tmp.dir.makeDir(io, "foo", .default_dir); try tmp.dir.writeFile(io, .{ .sub_path = "foo/bar", .data = "" }); try testing.expectError(error.NotDir, tmp.dir.makePath(io, "foo/bar/baz")); @@ -1319,10 +1319,10 @@ test "makepath existing directories" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makeDir("A"); + try tmp.dir.makeDir(io, "A", .default_dir); var tmpA = try tmp.dir.openDir(io, "A", .{}); defer tmpA.close(io); - try tmpA.makeDir("B"); + try tmpA.makeDir(io, "B", .default_dir); const testPath = "A" ++ fs.path.sep_str ++ "B" ++ fs.path.sep_str ++ "C"; try tmp.dir.makePath(io, testPath); @@ -1336,7 +1336,7 @@ test "makepath through existing valid symlink" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makeDir("realfolder"); + try tmp.dir.makeDir(io, "realfolder", .default_dir); try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{}); try tmp.dir.makePath(io, "working-symlink" ++ fs.path.sep_str ++ "in-realfolder"); @@ -1419,7 +1419,7 @@ fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8) !vo } // ensure that we can delete the tree - try iterable_dir.deleteTree(maxed_filename); + try iterable_dir.deleteTree(io, maxed_filename); } test "max file name component lengths" { @@ -1570,7 +1570,7 @@ test "access file" { try ctx.dir.writeFile(io, .{ .sub_path = file_path, .data = "" }); try ctx.dir.access(io, file_path, .{}); - try ctx.dir.deleteTree(dir_path); + try ctx.dir.deleteTree(io, dir_path); } }.impl); } @@ -2042,7 +2042,7 @@ test "'.' and '..' in Io.Dir functions" { const rename_path = try ctx.transformPath("./subdir/../rename"); const update_path = try ctx.transformPath("./subdir/../update"); - try ctx.dir.makeDir(subdir_path); + try ctx.dir.makeDir(io, subdir_path, .default_dir); try ctx.dir.access(io, subdir_path, .{}); var created_subdir = try ctx.dir.openDir(io, subdir_path, .{}); created_subdir.close(io); @@ -2120,7 +2120,7 @@ test "chmod" { try file.chmod(0o644); try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat(io)).mode & 0o7777); - try tmp.dir.makeDir("test_dir"); + try tmp.dir.makeDir(io, "test_dir", .default_dir); var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); defer dir.close(io); @@ -2141,7 +2141,7 @@ test "chown" { defer file.close(io); try file.chown(null, null); - try tmp.dir.makeDir("test_dir"); + try tmp.dir.makeDir(io, "test_dir", .default_dir); var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); defer dir.close(io); @@ -2165,7 +2165,7 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{})); - try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path)); + try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path, .default_dir)); try testing.expectError(expected_err, ctx.dir.makePath(invalid_path)); try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{})); @@ -2191,7 +2191,7 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{})); try testing.expectError(expected_err, ctx.dir.readFileAlloc(invalid_path, testing.allocator, .limited(0))); - try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path)); + try testing.expectError(expected_err, ctx.dir.deleteTree(io, invalid_path)); try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path)); try testing.expectError(expected_err, ctx.dir.writeFile(io, .{ .sub_path = invalid_path, .data = "" })); diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 99d67ec1322fa8633a71bf25124f1cb2df5f0845..9eb2ad5e96ae5fc4e5f3169a5080406c28ea897d 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -616,7 +616,7 @@ pub const TmpDir = struct { pub fn cleanup(self: *TmpDir) void { self.dir.close(io); - self.parent_dir.deleteTree(&self.sub_path) catch {}; + self.parent_dir.deleteTree(io, &self.sub_path) catch {}; self.parent_dir.close(io); self.* = undefined; } diff --git a/src/Compilation.zig b/src/Compilation.zig index 79b2d51635a3e185655eba9d5e95b110f81098cc..617421e2790f85c3c7a5d2771e51b7abdc61d709 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2823,12 +2823,9 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { return; } const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); - comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| { - log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{ - comp.dirs.local_cache.path orelse ".", - fs.path.sep, - tmp_dir_sub_path, - @errorName(err), + comp.dirs.local_cache.handle.deleteTree(io, tmp_dir_sub_path) catch |err| { + log.warn("failed to delete temporary directory '{s}{c}{s}': {t}", .{ + comp.dirs.local_cache.path orelse ".", fs.path.sep, tmp_dir_sub_path, err, }); }; } @@ -2847,12 +2844,9 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { tmp_dir.handle.close(io); whole.tmp_artifact_directory = null; const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); - comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| { - log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{ - comp.dirs.local_cache.path orelse ".", - fs.path.sep, - tmp_dir_sub_path, - @errorName(err), + comp.dirs.local_cache.handle.deleteTree(io, tmp_dir_sub_path) catch |err| { + log.warn("failed to delete temporary directory '{s}{c}{s}': {t}", .{ + comp.dirs.local_cache.path orelse ".", fs.path.sep, tmp_dir_sub_path, err, }); }; } @@ -3419,13 +3413,13 @@ fn renameTmpIntoCache( .windows => { if (seen_eaccess) return error.AccessDenied; seen_eaccess = true; - try cache_directory.handle.deleteTree(o_sub_path); + try cache_directory.handle.deleteTree(io, o_sub_path); continue; }, else => return error.AccessDenied, }, error.PathAlreadyExists => { - try cache_directory.handle.deleteTree(o_sub_path); + try cache_directory.handle.deleteTree(io, o_sub_path); continue; }, error.FileNotFound => { diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index aaba0c28cf506cebf76ab548f5660cd37f2e9d24..3552a7fc06ceb10e89db1e69ec671b5c519875bd 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -656,9 +656,11 @@ fn checkBuildFileExistence(f: *Fetch) RunError!void { /// This function populates `f.manifest` or leaves it `null`. fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { + const io = f.job_queue.io; const eb = &f.error_bundle; const arena = f.arena.allocator(); const manifest_bytes = pkg_root.root_dir.handle.readFileAllocOptions( + io, try fs.path.join(arena, &.{ pkg_root.sub_path, Manifest.basename }), arena, .limited(Manifest.max_bytes), @@ -1409,7 +1411,7 @@ fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!U } } - try out_dir.deleteTree(".git"); + try out_dir.deleteTree(io, ".git"); return res; } @@ -1461,7 +1463,7 @@ pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u cache_dir.rename(tmp_dir_sub_path, cache_dir, dest_dir_sub_path, io) catch |err| switch (err) { error.FileNotFound => { if (handled_missing_dir) return err; - cache_dir.makeDir(dest_dir_sub_path[0..1]) catch |mkd_err| switch (mkd_err) { + cache_dir.makeDir(io, dest_dir_sub_path[0..1], .default_dir) catch |mkd_err| switch (mkd_err) { error.PathAlreadyExists => handled_missing_dir = true, else => |e| return e, }; @@ -1469,7 +1471,7 @@ pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u }, error.PathAlreadyExists, error.AccessDenied => { // Package has been already downloaded and may already be in use on the system. - cache_dir.deleteTree(tmp_dir_sub_path) catch { + cache_dir.deleteTree(io, tmp_dir_sub_path) catch { // Garbage files leftover in zig-cache/tmp/ is, as they say // on Star Trek, "operating within normal parameters". }; diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index 1d6aa1a849346d0e74af05ee36c37586ea8306e3..33ff982b66a5d65f7ad8f62b2f3b40565f8fbd49 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -253,7 +253,7 @@ pub const Repository = struct { while (try tree_iter.next()) |entry| { switch (entry.type) { .directory => { - try dir.makeDir(entry.name); + try dir.makeDir(io, entry.name, .default_dir); var subdir = try dir.openDir(io, entry.name, .{}); defer subdir.close(io); const sub_path = try std.fs.path.join(repository.odb.allocator, &.{ current_path, entry.name }); @@ -296,7 +296,7 @@ pub const Repository = struct { .gitlink => { // Consistent with git archive behavior, create the directory but // do nothing else - try dir.makeDir(entry.name); + try dir.makeDir(io, entry.name, .default_dir); }, } } diff --git a/src/main.zig b/src/main.zig index 58f0e68301b7c0ea7dff91762b1c60e2f01e0381..a832a79dbe475fd880ffafd217adcc8eff4a15a0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -7316,6 +7316,7 @@ fn loadManifest( ) !struct { Package.Manifest, Ast } { const manifest_bytes = while (true) { break options.dir.readFileAllocOptions( + io, Package.Manifest.basename, arena, .limited(Package.Manifest.max_bytes), diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 5889449bbe6297cb180dbc2f09b963b5f796de12..bf3eade8f4de329e34be4044617c246f5f4c6efe 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -10,6 +10,7 @@ const ArrayList = std.ArrayList; gpa: Allocator, arena: Allocator, +io: Io, cases: std.array_list.Managed(Case), pub const IncrementalCase = struct { @@ -334,6 +335,7 @@ fn addFromDirInner( current_file: *[]const u8, b: *std.Build, ) !void { + const io = ctx.io; var it = try iterable_dir.walk(ctx.arena); var filenames: ArrayList([]const u8) = .empty; @@ -349,7 +351,7 @@ fn addFromDirInner( current_file.* = filename; const max_file_size = 10 * 1024 * 1024; - const src = try iterable_dir.readFileAllocOptions(filename, ctx.arena, .limited(max_file_size), .@"1", 0); + const src = try iterable_dir.readFileAllocOptions(io, filename, ctx.arena, .limited(max_file_size), .@"1", 0); // Parse the manifest var manifest = try TestManifest.parse(ctx.arena, src); diff --git a/test/standalone/posix/relpaths.zig b/test/standalone/posix/relpaths.zig index 70e9fad6873e4d74a4e704ff8e48299bff8a6cc1..ed143ccdc232ac2263ee0fc0fd3f51528654a6bc 100644 --- a/test/standalone/posix/relpaths.zig +++ b/test/standalone/posix/relpaths.zig @@ -5,13 +5,14 @@ const builtin = @import("builtin"); const std = @import("std"); const Io = std.Io; +const Allocator = std.mem.Allocator; pub fn main() !void { if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir - var Allocator = std.heap.DebugAllocator(.{}){}; - const a = Allocator.allocator(); - defer std.debug.assert(Allocator.deinit() == .ok); + var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + const gpa = debug_allocator.allocator(); + defer std.debug.assert(debug_allocator.deinit() == .ok); var threaded: std.Io.Threaded = .init_single_threaded; const io = threaded.io(); @@ -24,22 +25,22 @@ pub fn main() !void { // Want to test relative paths, so cd into the tmpdir for these tests try tmp.dir.setAsCwd(); - try test_symlink(a, tmp); + try test_symlink(gpa, io, tmp); try test_link(io, tmp); } -fn test_symlink(a: std.mem.Allocator, tmp: std.testing.TmpDir) !void { +fn test_symlink(gpa: Allocator, io: Io, tmp: std.testing.TmpDir) !void { const target_name = "symlink-target"; const symlink_name = "symlinker"; // Create the target file - try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "nonsense" }); + try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "nonsense" }); if (builtin.target.os.tag == .windows) { - const wtarget_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, target_name); - const wsymlink_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, symlink_name); - defer a.free(wtarget_name); - defer a.free(wsymlink_name); + const wtarget_name = try std.unicode.wtf8ToWtf16LeAllocZ(gpa, target_name); + const wsymlink_name = try std.unicode.wtf8ToWtf16LeAllocZ(gpa, symlink_name); + defer gpa.free(wtarget_name); + defer gpa.free(wsymlink_name); std.os.windows.CreateSymbolicLink(tmp.dir.fd, wsymlink_name, wtarget_name, false) catch |err| switch (err) { // Symlink requires admin privileges on windows, so this test can legitimately fail. diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig index bbc0d94077c532d7ea2e5c13fa2f46c3fc9951e4..6c89c50e0bb87b4821e386fd6d0855be1ecf22ae 100644 --- a/test/standalone/windows_bat_args/fuzz.zig +++ b/test/standalone/windows_bat_args/fuzz.zig @@ -9,6 +9,9 @@ pub fn main() anyerror!void { defer std.debug.assert(debug_alloc_inst.deinit() == .ok); const gpa = debug_alloc_inst.allocator(); + var threaded: Io.Threaded = .init(gpa); + const io = threaded.io(); + var it = try std.process.argsWithAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name @@ -58,15 +61,15 @@ pub fn main() anyerror!void { const preamble_len = buf.items.len; try buf.appendSlice(gpa, " %*"); - try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items }); + try tmp.dir.writeFile(io, .{ .sub_path = "args1.bat", .data = buf.items }); buf.shrinkRetainingCapacity(preamble_len); try buf.appendSlice(gpa, " %1 %2 %3 %4 %5 %6 %7 %8 %9"); - try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items }); + try tmp.dir.writeFile(io, .{ .sub_path = "args2.bat", .data = buf.items }); buf.shrinkRetainingCapacity(preamble_len); try buf.appendSlice(gpa, " \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\""); - try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items }); + try tmp.dir.writeFile(io, .{ .sub_path = "args3.bat", .data = buf.items }); buf.shrinkRetainingCapacity(preamble_len); var i: u64 = 0; @@ -74,7 +77,7 @@ pub fn main() anyerror!void { const rand_arg = try randomArg(gpa, rand); defer gpa.free(rand_arg); - try testExec(gpa, &.{rand_arg}, null); + try testExec(gpa, io, &.{rand_arg}, null); i += 1; } diff --git a/test/standalone/windows_bat_args/test.zig b/test/standalone/windows_bat_args/test.zig index 558adc248f13d6de7675e8ec754c16fb800d484a..04d2fa37f0954c9aa5f54ea89b801d37e5c51467 100644 --- a/test/standalone/windows_bat_args/test.zig +++ b/test/standalone/windows_bat_args/test.zig @@ -7,6 +7,9 @@ pub fn main() anyerror!void { defer std.debug.assert(debug_alloc_inst.deinit() == .ok); const gpa = debug_alloc_inst.allocator(); + var threaded: Io.Threaded = .init(gpa); + const io = threaded.io(); + var it = try std.process.argsWithAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name @@ -32,15 +35,15 @@ pub fn main() anyerror!void { const preamble_len = buf.items.len; try buf.appendSlice(gpa, " %*"); - try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items }); + try tmp.dir.writeFile(io, .{ .sub_path = "args1.bat", .data = buf.items }); buf.shrinkRetainingCapacity(preamble_len); try buf.appendSlice(gpa, " %1 %2 %3 %4 %5 %6 %7 %8 %9"); - try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items }); + try tmp.dir.writeFile(io, .{ .sub_path = "args2.bat", .data = buf.items }); buf.shrinkRetainingCapacity(preamble_len); try buf.appendSlice(gpa, " \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\""); - try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items }); + try tmp.dir.writeFile(io, .{ .sub_path = "args3.bat", .data = buf.items }); buf.shrinkRetainingCapacity(preamble_len); // Test cases are from https://github.com/rust-lang/rust/blob/master/tests/ui/std/windows-bat-args.rs diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index 1acae0aca2bc93281d3857c1d2358f5fb877285c..0d82ecff975d6d6d0630c47167e7f57ff32349ef 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -65,9 +65,9 @@ pub fn main() anyerror!void { try std.testing.expectError(error.FileNotFound, testExecWithCwd(gpa, io, "hello.exe", "missing_dir", "")); // now add a .bat - try tmp.dir.writeFile(.{ .sub_path = "hello.bat", .data = "@echo hello from bat" }); + try tmp.dir.writeFile(io, .{ .sub_path = "hello.bat", .data = "@echo hello from bat" }); // and a .cmd - try tmp.dir.writeFile(.{ .sub_path = "hello.cmd", .data = "@echo hello from cmd" }); + try tmp.dir.writeFile(io, .{ .sub_path = "hello.cmd", .data = "@echo hello from cmd" }); // with extension should find the .bat (case insensitive) try testExec(gpa, "heLLo.bat", "hello from bat\r\n"); @@ -84,7 +84,7 @@ pub fn main() anyerror!void { // without extension should succeed (case insensitive) try testExec(gpa, "heLLo", "hello from exe\n"); - try tmp.dir.makeDir("something"); + try tmp.dir.makeDir(io, "something", .default_dir); try renameExe(tmp.dir, "hello", "something/hello.exe"); const relative_path_no_ext = try std.fs.path.join(gpa, &.{ tmp_relative_path, "something/hello" }); @@ -99,7 +99,7 @@ pub fn main() anyerror!void { try testExec(gpa, "heLLo", "hello from bat\r\n"); // Add a hello.exe that is not a valid executable - try tmp.dir.writeFile(.{ .sub_path = "hello.exe", .data = "invalid" }); + try tmp.dir.writeFile(io, .{ .sub_path = "hello.exe", .data = "invalid" }); // Trying to execute it with extension will give InvalidExe. This is a special // case for .EXE extensions, where if they ever try to get executed but they are diff --git a/test/tests.zig b/test/tests.zig index 5ca170c83c288296d6f81cc290f15b4aeea4f8cb..015c3535bbd707a3b9130a5a4000337426d9bff2 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2135,12 +2135,12 @@ pub fn addCliTests(b: *std.Build) *Step { var dir = std.Io.Dir.cwd().openDir(io, tmp_path, .{}) catch @panic("unhandled"); defer dir.close(io); - dir.writeFile(.{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled"); - dir.writeFile(.{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled"); - dir.makeDir("subdir") catch @panic("unhandled"); + dir.writeFile(io, .{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled"); + dir.writeFile(io, .{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled"); + dir.makeDir(io, "subdir", .default_dir) catch @panic("unhandled"); var subdir = dir.openDir(io, "subdir", .{}) catch @panic("unhandled"); defer subdir.close(io); - subdir.writeFile(.{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled"); + subdir.writeFile(io, .{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled"); // Test zig fmt affecting only the appropriate files. const run1 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "fmt1.zig" }); -- 2.54.0 From 4a53e5b0b4131c6b8e18bb551e8215e425f8ac71 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 20:03:50 -0800 Subject: [PATCH 031/195] fix a handful of compilation errors related to std.fs migration --- lib/compiler/resinator/cli.zig | 2 +- lib/compiler/resinator/main.zig | 2 +- lib/compiler/std-docs.zig | 18 ++--- lib/std/Build.zig | 2 +- lib/std/Build/Cache.zig | 2 +- lib/std/Build/Step.zig | 2 +- lib/std/Build/Step/Run.zig | 4 +- lib/std/Build/WebServer.zig | 2 +- lib/std/Io/Dir.zig | 7 +- lib/std/Io/File.zig | 4 ++ lib/std/Io/File/Atomic.zig | 7 +- lib/std/Io/net/test.zig | 2 +- lib/std/Io/test.zig | 2 +- lib/std/fs/test.zig | 26 +++---- lib/std/posix/test.zig | 2 +- lib/std/process/Child.zig | 94 +++++++++++++------------- lib/std/tar.zig | 36 +++++----- src/Compilation.zig | 15 ++-- src/Package/Fetch.zig | 12 ++-- src/link.zig | 14 ++-- src/link/Lld.zig | 8 +-- src/main.zig | 13 ++-- test/src/Cases.zig | 3 +- test/standalone/child_process/main.zig | 2 +- 24 files changed, 148 insertions(+), 133 deletions(-) diff --git a/lib/compiler/resinator/cli.zig b/lib/compiler/resinator/cli.zig index 5588390197cb2b9b21181701a1e63d8a832bbb28..d2dd71b1f6878eaab2cec7a15a225de1fcf7f5c5 100644 --- a/lib/compiler/resinator/cli.zig +++ b/lib/compiler/resinator/cli.zig @@ -2010,7 +2010,7 @@ test "maybeAppendRC" { // Now delete the file and try again. But this time change the input format // to non-rc. - try tmp.dir.deleteFile("foo"); + try tmp.dir.deleteFile(io, "foo"); options.input_format = .res; try options.maybeAppendRC(io, tmp.dir); try std.testing.expectEqualStrings("foo", options.input_source.filename); diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index afe1dcbe91ddb8f63a2b0e2a8a2d6d55e3de6396..6c12903f06f3bde8918c20b51beca360c3b1a41a 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -440,7 +440,7 @@ const IoStream = struct { // Delete the output file on error file.close(io); // Failing to delete is not really a big deal, so swallow any errors - Io.Dir.cwd().deleteFile(self.name) catch {}; + Io.Dir.cwd().deleteFile(io, self.name) catch {}; }, .stdio, .memory, .closed => return, } diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index 64524963057476662405f4900a7f87e8b819f5e8..538558fe48917ea7bd71d9a435dc1685e02e52ac 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -72,8 +72,8 @@ pub fn main() !void { const url_with_newline = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port}); std.Io.File.stdout().writeAll(url_with_newline) catch {}; if (should_open_browser) { - openBrowserTab(gpa, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| { - std.log.err("unable to open browser: {s}", .{@errorName(err)}); + openBrowserTab(gpa, io, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| { + std.log.err("unable to open browser: {t}", .{err}); }; } @@ -89,7 +89,7 @@ pub fn main() !void { while (true) { const connection = try http_server.accept(); _ = std.Thread.spawn(.{}, accept, .{ &context, connection }) catch |err| { - std.log.err("unable to accept connection: {s}", .{@errorName(err)}); + std.log.err("unable to accept connection: {t}", .{err}); connection.stream.close(io); continue; }; @@ -328,7 +328,7 @@ fn buildWasmBinary( child.stdin_behavior = .Pipe; child.stdout_behavior = .Pipe; child.stderr_behavior = .Pipe; - try child.spawn(); + try child.spawn(io); var poller = std.Io.poll(gpa, enum { stdout, stderr }, .{ .stdout = child.stdout.?, @@ -434,13 +434,13 @@ fn sendMessage(io: Io, file: std.Io.File, tag: std.zig.Client.Message.Tag) !void }; } -fn openBrowserTab(gpa: Allocator, url: []const u8) !void { +fn openBrowserTab(gpa: Allocator, io: Io, url: []const u8) !void { // Until https://github.com/ziglang/zig/issues/19205 is implemented, we // spawn a thread for this child process. - _ = try std.Thread.spawn(.{}, openBrowserTabThread, .{ gpa, url }); + _ = try std.Thread.spawn(.{}, openBrowserTabThread, .{ gpa, io, url }); } -fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void { +fn openBrowserTabThread(gpa: Allocator, io: Io, url: []const u8) !void { const main_exe = switch (builtin.os.tag) { .windows => "explorer", .macos => "open", @@ -450,6 +450,6 @@ fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void { child.stdin_behavior = .Ignore; child.stdout_behavior = .Ignore; child.stderr_behavior = .Ignore; - try child.spawn(); - _ = try child.wait(); + try child.spawn(io); + _ = try child.wait(io); } diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 746b41860b20c93a3dafb4b94faffc6c01fef357..ae2ab1c4d015b0018ef0325c1d22c94dcae3df96 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1838,7 +1838,7 @@ pub fn runAllowFail( child.env_map = &b.graph.env_map; try Step.handleVerbose2(b, null, child.env_map, argv); - try child.spawn(); + try child.spawn(io); var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch { diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index d2ba33c74d1196361912140f80f5c5e0accdd69f..b97efc767770eb742a9fac4efdcf48ae5304cfec 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -1300,7 +1300,7 @@ fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp { }); defer { file.close(io); - dir.deleteFile(test_out_file) catch {}; + dir.deleteFile(io, test_out_file) catch {}; } return (try file.stat(io)).mtime; diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 8115aaa1a1995211cf8af7d25863697ce6d9336c..9c7fcc757f0ebc2c95e4c5486c728af611beeb00 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -455,7 +455,7 @@ pub fn evalZigProcess( child.request_resource_usage_statistics = true; child.progress_node = prog_node; - child.spawn() catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); + child.spawn(io) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); const zp = try gpa.create(ZigProcess); zp.* = .{ diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 19d2e7e61cdb2b282c32e732cd8e20b913b0592e..95024061d81317ab180ee7ceb4546a98b05d1258 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1689,7 +1689,7 @@ fn evalZigTest( }; while (true) { - try child.spawn(); + try child.spawn(io); var poller = std.Io.poll(gpa, StdioPollEnum, .{ .stdout = child.stdout.?, .stderr = child.stderr.?, @@ -2168,7 +2168,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { const io = b.graph.io; const arena = b.allocator; - try child.spawn(); + try child.spawn(io); errdefer _ = child.kill(io) catch {}; try child.waitForSpawn(); diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 72306cbab9a302f0bc4dd1a9d00c3bc2e77f8014..5f633c594872c2a63854a5b1d2b3e281beee2621 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -580,7 +580,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim child.stdin_behavior = .Pipe; child.stdout_behavior = .Pipe; child.stderr_behavior = .Pipe; - try child.spawn(); + try child.spawn(io); var poller = Io.poll(gpa, enum { stdout, stderr }, .{ .stdout = child.stdout.?, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index cf5e2b4c725d0a7754f657e876687c41deee7904..d6d6aa1be2de77a1bccf67dd4c491b8000e6aa7f 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -1366,7 +1366,7 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { => |e| return e, }; } else { - if (parent_dir.deleteFile(name)) { + if (parent_dir.deleteFile(io, name)) { continue :process_stack; } else |err| switch (err) { error.FileNotFound => continue :process_stack, @@ -1477,7 +1477,7 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, dir_name = result; continue :scan_dir; } else { - if (dir.deleteFile(entry.name)) { + if (dir.deleteFile(io, entry.name)) { continue :dir_it; } else |err| switch (err) { error.FileNotFound => continue :dir_it, @@ -1567,7 +1567,7 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin => |e| return e, }; } else { - if (dir.deleteFile(sub_path)) { + if (dir.deleteFile(io, sub_path)) { return null; } else |err| switch (err) { error.FileNotFound => return null, @@ -1588,6 +1588,7 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin error.FileBusy, error.BadPathName, error.NetworkNotFound, + error.Canceled, error.Unexpected, => |e| return e, } diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 302d2a8ca518bbe2010c5900b9ce16f9ec152301..6d4dd4f3233b337b4eebd4b284056075543e0ca7 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -407,6 +407,10 @@ pub const Permissions = std.options.FilePermissions orelse if (is_windows) enum( return @intFromEnum(self); } + pub fn fromMode(mode: std.posix.mode_t) @This() { + return @enumFromInt(mode); + } + /// Returns `true` if and only if no class has write permissions. pub fn readOnly(self: @This()) bool { const mode = toMode(self); diff --git a/lib/std/Io/File/Atomic.zig b/lib/std/Io/File/Atomic.zig index ccb81815ed9404220bded08102dad504586323b7..7d412703edadfb7878049a6de671a33ef46c7db2 100644 --- a/lib/std/Io/File/Atomic.zig +++ b/lib/std/Io/File/Atomic.zig @@ -20,7 +20,7 @@ pub const InitError = File.OpenError; pub fn init( io: Io, dest_basename: []const u8, - mode: File.Mode, + permissions: File.Permissions, dir: Dir, close_dir_on_deinit: bool, write_buffer: []u8, @@ -28,7 +28,10 @@ pub fn init( while (true) { const random_integer = std.crypto.random.int(u64); const tmp_sub_path = std.fmt.hex(random_integer); - const file = dir.createFile(io, &tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) { + const file = dir.createFile(io, &tmp_sub_path, .{ + .permissions = permissions, + .exclusive = true, + }) catch |err| switch (err) { error.PathAlreadyExists => continue, else => |e| return e, }; diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig index c9ed0d32847852e259c021aa9af0a718ee40c8c9..23edab5a5d8454298b014cfb3b9ca965c56bc021 100644 --- a/lib/std/Io/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -278,7 +278,7 @@ test "listen on a unix socket, send bytes, receive bytes" { defer testing.allocator.free(socket_path); const socket_addr = try net.UnixAddress.init(socket_path); - defer Io.Dir.cwd().deleteFile(socket_path) catch {}; + defer Io.Dir.cwd().deleteFile(io, socket_path) catch {}; var server = try socket_addr.listen(io, .{}); defer server.socket.close(io); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 9763fb23979ed35f29cf7ace53414be9fa7eb402..02bdb591d00d38c58a9a1310a71e7ebe080ef543 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -60,7 +60,7 @@ test "write a file, read it, then delete it" { try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data)); try expect(mem.eql(u8, contents[contents.len - "end".len ..], "end")); } - try tmp.dir.deleteFile(tmp_file_name); + try tmp.dir.deleteFile(io, tmp_file_name); } test "File seek ops" { diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index edc15526b26af305df5a556da581b6fc1ced149e..da0a0cff79fd9e6c38bdd6d4d392465e39864662 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -873,7 +873,7 @@ test "file operations on directories" { try ctx.dir.makeDir(io, test_dir_name, .default_dir); try testing.expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{})); - try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name)); + try testing.expectError(error.IsDir, ctx.dir.deleteFile(io, test_dir_name)); switch (native_os) { .dragonfly, .netbsd => { // no error when reading a directory. See https://github.com/ziglang/zig/issues/5732 @@ -942,7 +942,7 @@ test "deleteDir" { try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path)); // deleting an empty directory - try ctx.dir.deleteFile(test_file_path); + try ctx.dir.deleteFile(io, test_file_path); try ctx.dir.deleteDir(test_dir_path); } }.impl); @@ -1671,13 +1671,13 @@ test "copyFile" { const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt"); try ctx.dir.writeFile(io, .{ .sub_path = src_file, .data = data }); - defer ctx.dir.deleteFile(src_file) catch {}; + defer ctx.dir.deleteFile(io, src_file) catch {}; try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{}); - defer ctx.dir.deleteFile(dest_file) catch {}; + defer ctx.dir.deleteFile(io, dest_file) catch {}; try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode }); - defer ctx.dir.deleteFile(dest_file2) catch {}; + defer ctx.dir.deleteFile(io, dest_file2) catch {}; try expectFileContents(io, ctx.dir, dest_file, data); try expectFileContents(io, ctx.dir, dest_file2, data); @@ -1713,7 +1713,7 @@ test "AtomicFile" { const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999)); try testing.expectEqualStrings(test_content, content); - try ctx.dir.deleteFile(test_out_file); + try ctx.dir.deleteFile(io, test_out_file); } }.impl); } @@ -2055,7 +2055,7 @@ test "'.' and '..' in Io.Dir functions" { try ctx.dir.rename(copy_path, ctx.dir, rename_path, io); const renamed_file = try ctx.dir.openFile(io, rename_path, .{}); renamed_file.close(io); - try ctx.dir.deleteFile(rename_path); + try ctx.dir.deleteFile(io, rename_path); try ctx.dir.writeFile(io, .{ .sub_path = update_path, .data = "something" }); var dir = ctx.dir; @@ -2113,19 +2113,19 @@ test "chmod" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - const file = try tmp.dir.createFile(io, "test_file", .{ .mode = 0o600 }); + const file = try tmp.dir.createFile(io, "test_file", .{ .permissions = .fromMode(0o600) }); defer file.close(io); - try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat(io)).mode & 0o7777); + try testing.expectEqual(@as(posix.mode_t, 0o600), (try file.stat(io)).permissions.toMode() & 0o7777); - try file.chmod(0o644); - try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat(io)).mode & 0o7777); + try file.setPermissions(io, .fromMode(0o644)); + try testing.expectEqual(@as(posix.mode_t, 0o644), (try file.stat(io)).permissions.toMode() & 0o7777); try tmp.dir.makeDir(io, "test_dir", .default_dir); var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); defer dir.close(io); - try dir.chmod(0o700); - try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat(io)).mode & 0o7777); + try dir.setPermissions(io, .fromMode(0o700)); + try testing.expectEqual(@as(posix.mode_t, 0o700), (try dir.stat(io)).permissions.toMode() & 0o7777); } test "chown" { diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 756ecfd63f3d557344dae0dc3699a9b2fe627c4b..b5255ad2a1c719a4dfef0d7aa79526b4898a573a 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -144,7 +144,7 @@ test "linkat with different directories" { const subdir = try tmp.dir.makeOpenPath("subdir", .{}); - defer tmp.dir.deleteFile(target_name) catch {}; + defer tmp.dir.deleteFile(io, target_name) catch {}; try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" }); // Test 1: link from file in subdir back up to target in parent directory diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index dbbacc496cba2932e266cc2cd8999069a6586946..17139e66b8ee9564cf9058922aec9ddc3a92d9ff 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -1,4 +1,4 @@ -const ChildProcess = @This(); +const Child = @This(); const builtin = @import("builtin"); const native_os = builtin.os.tag; @@ -31,7 +31,7 @@ pub const Id = switch (native_os) { id: Id, thread_handle: if (native_os == .windows) windows.HANDLE else void, -allocator: mem.Allocator, +allocator: Allocator, /// The writing end of the child process's standard input pipe. /// Usage requires `stdin_behavior == StdIo.Pipe`. @@ -229,7 +229,7 @@ pub const StdIo = enum { }; /// First argument in argv is the executable. -pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess { +pub fn init(argv: []const []const u8, allocator: Allocator) Child { return .{ .allocator = allocator, .argv = argv, @@ -252,7 +252,7 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess { }; } -pub fn setUserName(self: *ChildProcess, name: []const u8) !void { +pub fn setUserName(self: *Child, name: []const u8) !void { const user_info = try process.getUserInfo(name); self.uid = user_info.uid; self.gid = user_info.gid; @@ -260,7 +260,7 @@ pub fn setUserName(self: *ChildProcess, name: []const u8) !void { /// On success must call `kill` or `wait`. /// After spawning the `id` is available. -pub fn spawn(self: *ChildProcess) SpawnError!void { +pub fn spawn(self: *Child, io: Io) SpawnError!void { if (!process.can_spawn) { @compileError("the target operating system cannot spawn processes"); } @@ -268,17 +268,17 @@ pub fn spawn(self: *ChildProcess) SpawnError!void { if (native_os == .windows) { return self.spawnWindows(); } else { - return self.spawnPosix(); + return self.spawnPosix(io); } } -pub fn spawnAndWait(self: *ChildProcess) SpawnError!Term { - try self.spawn(); - return self.wait(); +pub fn spawnAndWait(child: *Child, io: Io) SpawnError!Term { + try child.spawn(io); + return child.wait(io); } /// Forcibly terminates child process and then cleans up all resources. -pub fn kill(self: *ChildProcess, io: Io) !Term { +pub fn kill(self: *Child, io: Io) !Term { if (native_os == .windows) { return self.killWindows(io, 1); } else { @@ -286,7 +286,7 @@ pub fn kill(self: *ChildProcess, io: Io) !Term { } } -pub fn killWindows(self: *ChildProcess, io: Io, exit_code: windows.UINT) !Term { +pub fn killWindows(self: *Child, io: Io, exit_code: windows.UINT) !Term { if (self.term) |term| { self.cleanupStreams(io); return term; @@ -308,7 +308,7 @@ pub fn killWindows(self: *ChildProcess, io: Io, exit_code: windows.UINT) !Term { return self.term.?; } -pub fn killPosix(self: *ChildProcess, io: Io) !Term { +pub fn killPosix(self: *Child, io: Io) !Term { if (self.term) |term| { self.cleanupStreams(io); return term; @@ -325,7 +325,7 @@ pub const WaitError = SpawnError || std.os.windows.GetProcessMemoryInfoError; /// On some targets, `spawn` may not report all spawn errors, such as `error.InvalidExe`. /// This function will block until any spawn errors can be reported, and return them. -pub fn waitForSpawn(self: *ChildProcess) SpawnError!void { +pub fn waitForSpawn(self: *Child) SpawnError!void { if (native_os == .windows) return; // `spawn` reports everything if (self.term) |term| { _ = term catch |spawn_err| return spawn_err; @@ -355,7 +355,7 @@ pub fn waitForSpawn(self: *ChildProcess) SpawnError!void { } /// Blocks until child process terminates and then cleans up all resources. -pub fn wait(self: *ChildProcess, io: Io) WaitError!Term { +pub fn wait(self: *Child, io: Io) WaitError!Term { try self.waitForSpawn(); // report spawn errors if (self.term) |term| { self.cleanupStreams(io); @@ -381,7 +381,7 @@ pub const RunResult = struct { /// /// The process must be started with stdout_behavior and stderr_behavior == .Pipe pub fn collectOutput( - child: ChildProcess, + child: Child, /// Used for `stdout` and `stderr`. allocator: Allocator, stdout: *ArrayList(u8), @@ -446,7 +446,7 @@ pub fn run(allocator: Allocator, io: Io, args: struct { expand_arg0: Arg0Expand = .no_expand, progress_node: std.Progress.Node = std.Progress.Node.none, }) RunError!RunResult { - var child = ChildProcess.init(args.argv, allocator); + var child = Child.init(args.argv, allocator); child.stdin_behavior = .Ignore; child.stdout_behavior = .Pipe; child.stderr_behavior = .Pipe; @@ -461,7 +461,7 @@ pub fn run(allocator: Allocator, io: Io, args: struct { var stderr: ArrayList(u8) = .empty; defer stderr.deinit(allocator); - try child.spawn(); + try child.spawn(io); errdefer { _ = child.kill(io) catch {}; } @@ -474,7 +474,7 @@ pub fn run(allocator: Allocator, io: Io, args: struct { }; } -fn waitUnwrappedWindows(self: *ChildProcess, io: Io) WaitError!void { +fn waitUnwrappedWindows(self: *Child, io: Io) WaitError!void { const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false); self.term = @as(SpawnError!Term, x: { @@ -496,7 +496,7 @@ fn waitUnwrappedWindows(self: *ChildProcess, io: Io) WaitError!void { return result; } -fn waitUnwrappedPosix(self: *ChildProcess, io: Io) void { +fn waitUnwrappedPosix(self: *Child, io: Io) void { const res: posix.WaitPidResult = res: { if (self.request_resource_usage_statistics) { switch (native_os) { @@ -531,11 +531,11 @@ fn waitUnwrappedPosix(self: *ChildProcess, io: Io) void { self.handleWaitResult(status); } -fn handleWaitResult(self: *ChildProcess, status: u32) void { +fn handleWaitResult(self: *Child, status: u32) void { self.term = statusToTerm(status); } -fn cleanupStreams(self: *ChildProcess, io: Io) void { +fn cleanupStreams(self: *Child, io: Io) void { if (self.stdin) |*stdin| { stdin.close(io); self.stdin = null; @@ -561,7 +561,7 @@ fn statusToTerm(status: u32) Term { Term{ .Unknown = status }; } -fn spawnPosix(self: *ChildProcess) SpawnError!void { +fn spawnPosix(self: *Child, io: Io) SpawnError!void { // The child process does need to access (one end of) these pipes. However, // we must initially set CLOEXEC to avoid a race condition. If another thread // is racing to spawn a different child process, we don't want it to inherit @@ -659,7 +659,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { })).ptr; } else { // TODO come up with a solution for this. - @panic("missing std lib enhancement: ChildProcess implementation has no way to collect the environment variables to forward to the child process"); + @panic("missing std lib enhancement: std.process.Child implementation has no way to collect the environment variables to forward to the child process"); } }; @@ -671,41 +671,41 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { const pid_result = try posix.fork(); if (pid_result == 0) { // we are the child - setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); - setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); - setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); + setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err); + setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err); + setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err); if (self.cwd_dir) |cwd| { - posix.fchdir(cwd.handle) catch |err| forkChildErrReport(err_pipe[1], err); + posix.fchdir(cwd.handle) catch |err| forkChildErrReport(io, err_pipe[1], err); } else if (self.cwd) |cwd| { - posix.chdir(cwd) catch |err| forkChildErrReport(err_pipe[1], err); + posix.chdir(cwd) catch |err| forkChildErrReport(io, err_pipe[1], err); } // Must happen after fchdir above, the cwd file descriptor might be // equal to prog_fileno and be clobbered by this dup2 call. - if (prog_pipe[1] != -1) posix.dup2(prog_pipe[1], prog_fileno) catch |err| forkChildErrReport(err_pipe[1], err); + if (prog_pipe[1] != -1) posix.dup2(prog_pipe[1], prog_fileno) catch |err| forkChildErrReport(io, err_pipe[1], err); if (self.gid) |gid| { - posix.setregid(gid, gid) catch |err| forkChildErrReport(err_pipe[1], err); + posix.setregid(gid, gid) catch |err| forkChildErrReport(io, err_pipe[1], err); } if (self.uid) |uid| { - posix.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err); + posix.setreuid(uid, uid) catch |err| forkChildErrReport(io, err_pipe[1], err); } if (self.pgid) |pid| { - posix.setpgid(0, pid) catch |err| forkChildErrReport(err_pipe[1], err); + posix.setpgid(0, pid) catch |err| forkChildErrReport(io, err_pipe[1], err); } if (self.start_suspended) { - posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(err_pipe[1], err); + posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(io, err_pipe[1], err); } const err = switch (self.expand_arg0) { .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), }; - forkChildErrReport(err_pipe[1], err); + forkChildErrReport(io, err_pipe[1], err); } // we are the parent @@ -750,7 +750,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { self.progress_node.setIpcFd(prog_pipe[0]); } -fn spawnWindows(self: *ChildProcess) SpawnError!void { +fn spawnWindows(self: *Child) SpawnError!void { var saAttr = windows.SECURITY_ATTRIBUTES{ .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), .bInheritHandle = windows.TRUE, @@ -880,7 +880,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void { const app_name_wtf8 = self.argv[0]; const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8); - // the cwd set in ChildProcess is in effect when choosing the executable path + // the cwd set in Child is in effect when choosing the executable path // to match posix semantics var cwd_path_w_needs_free = false; const cwd_path_w = x: { @@ -965,7 +965,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void { // If the app name had path separators, that disallows PATH searching, // and there's no need to search the PATH if the app name is absolute. // We still search the path if the cwd is absolute because of the - // "cwd set in ChildProcess is in effect when choosing the executable path + // "cwd set in Child is in effect when choosing the executable path // to match posix semantics" behavior--we don't want to skip searching // the PATH just because we were trying to set the cwd of the child process. if (app_dirname_w != null or app_name_is_absolute) { @@ -1039,8 +1039,8 @@ fn destroyPipe(pipe: [2]posix.fd_t) void { // Child of fork calls this to report an error to the fork parent. // Then the child exits. -fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { - writeIntFd(fd, @as(ErrInt, @intFromError(err))) catch {}; +fn forkChildErrReport(io: Io, fd: i32, err: Child.SpawnError) noreturn { + writeIntFd(io, fd, @as(ErrInt, @intFromError(err))) catch {}; // If we're linking libc, some naughty applications may have registered atexit handlers // which we really do not want to run in the fork child. I caught LLVM doing this and // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne, @@ -1052,9 +1052,9 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { posix.system.exit(1); } -fn writeIntFd(fd: i32, value: ErrInt) !void { +fn writeIntFd(io: Io, fd: i32, value: ErrInt) !void { var buffer: [8]u8 = undefined; - var fw: File.Writer = .initStreaming(.{ .handle = fd }, &buffer); + var fw: File.Writer = .initStreaming(.{ .handle = fd }, io, &buffer); fw.interface.writeInt(u64, value, .little) catch unreachable; fw.interface.flush() catch return error.SystemResources; } @@ -1078,7 +1078,7 @@ const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8); /// Note: `app_buf` should not contain any leading path separators. /// Note: If the dir is the cwd, dir_buf should be empty (len = 0). fn windowsCreateProcessPathExt( - allocator: mem.Allocator, + allocator: Allocator, dir_buf: *ArrayList(u16), app_buf: *ArrayList(u16), pathext: [:0]const u16, @@ -1525,9 +1525,9 @@ const WindowsCommandLineCache = struct { script_cmd_line: ?[:0]u16 = null, cmd_exe_path: ?[:0]u16 = null, argv: []const []const u8, - allocator: mem.Allocator, + allocator: Allocator, - fn init(allocator: mem.Allocator, argv: []const []const u8) WindowsCommandLineCache { + fn init(allocator: Allocator, argv: []const []const u8) WindowsCommandLineCache { return .{ .allocator = allocator, .argv = argv, @@ -1571,7 +1571,7 @@ const WindowsCommandLineCache = struct { /// Returns the absolute path of `cmd.exe` within the Windows system directory. /// The caller owns the returned slice. -fn windowsCmdExePath(allocator: mem.Allocator) error{ OutOfMemory, Unexpected }![:0]u16 { +fn windowsCmdExePath(allocator: Allocator) error{ OutOfMemory, Unexpected }![:0]u16 { var buf = try ArrayList(u16).initCapacity(allocator, 128); errdefer buf.deinit(allocator); while (true) { @@ -1608,7 +1608,7 @@ const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 }; /// /// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead. fn argvToCommandLineWindows( - allocator: mem.Allocator, + allocator: Allocator, argv: []const []const u8, ) ArgvToCommandLineError![:0]u16 { var buf = std.array_list.Managed(u8).init(allocator); @@ -1784,7 +1784,7 @@ const ArgvToScriptCommandLineError = error{ /// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise. /// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem. fn argvToScriptCommandLineWindows( - allocator: mem.Allocator, + allocator: Allocator, /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD. /// The script must have been verified to exist at this path before calling this function. script_path: []const u16, diff --git a/lib/std/tar.zig b/lib/std/tar.zig index 5e7215f1d72b168a80655ebb85f330c3181e69ff..7bb20a9959cd54bea9a106cb90abcc915904f1b7 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -610,7 +610,7 @@ pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOp } }, .file => { - if (createDirAndFile(io, dir, file_name, fileMode(file.mode, options))) |fs_file| { + if (createDirAndFile(io, dir, file_name, filePermissions(file.mode, options))) |fs_file| { defer fs_file.close(io); var file_writer = fs_file.writer(io, &file_contents_buffer); try it.streamRemaining(file, &file_writer.interface); @@ -638,12 +638,12 @@ pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOp } } -fn createDirAndFile(io: Io, dir: Io.Dir, file_name: []const u8, mode: Io.File.Mode) !Io.File { - const fs_file = dir.createFile(io, file_name, .{ .exclusive = true, .mode = mode }) catch |err| { +fn createDirAndFile(io: Io, dir: Io.Dir, file_name: []const u8, permissions: Io.File.Permissions) !Io.File { + const fs_file = dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions }) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { try dir.makePath(io, dir_name); - return try dir.createFile(io, file_name, .{ .exclusive = true, .mode = mode }); + return try dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions }); } } return err; @@ -880,9 +880,9 @@ test "create file and symlink" { var root = testing.tmpDir(.{}); defer root.cleanup(); - var file = try createDirAndFile(io, root.dir, "file1", default_mode); + var file = try createDirAndFile(io, root.dir, "file1", .default_file); file.close(io); - file = try createDirAndFile(io, root.dir, "a/b/c/file2", default_mode); + file = try createDirAndFile(io, root.dir, "a/b/c/file2", .default_file); file.close(io); createDirAndSymlink(io, root.dir, "a/b/c/file2", "symlink1") catch |err| { @@ -894,7 +894,7 @@ test "create file and symlink" { // Danglink symlnik, file created later try createDirAndSymlink(io, root.dir, "../../../g/h/i/file4", "j/k/l/symlink3"); - file = try createDirAndFile(io, root.dir, "g/h/i/file4", default_mode); + file = try createDirAndFile(io, root.dir, "g/h/i/file4", .default_file); file.close(io); } @@ -1118,30 +1118,30 @@ fn normalizePath(bytes: []u8) []u8 { return bytes; } -const default_mode = Io.File.default_mode; - // File system mode based on tar header mode and mode_mode options. -fn fileMode(mode: u32, options: PipeOptions) Io.File.Mode { +fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { + const default_mode = 0o666; + if (!std.fs.has_executable_bit or options.mode_mode == .ignore) - return default_mode; + return .fromMode(default_mode); const S = std.posix.S; // The mode from the tar file is inspected for the owner executable bit. if (mode & S.IXUSR == 0) - return default_mode; + return .fromMode(default_mode); // This bit is copied to the group and other executable bits. // Other bits of the mode are left as the default when creating files. - return default_mode | S.IXUSR | S.IXGRP | S.IXOTH; + return .fromMode(default_mode | S.IXUSR | S.IXGRP | S.IXOTH); } -test fileMode { +test filePermissions { if (!std.fs.has_executable_bit) return error.SkipZigTest; - try testing.expectEqual(default_mode, fileMode(0o744, PipeOptions{ .mode_mode = .ignore })); - try testing.expectEqual(0o777, fileMode(0o744, PipeOptions{})); - try testing.expectEqual(0o666, fileMode(0o644, PipeOptions{})); - try testing.expectEqual(0o666, fileMode(0o655, PipeOptions{})); + try testing.expectEqual(0o666, filePermissions(0o744, PipeOptions{ .mode_mode = .ignore })); + try testing.expectEqual(0o777, filePermissions(0o744, PipeOptions{})); + try testing.expectEqual(0o666, filePermissions(0o644, PipeOptions{})); + try testing.expectEqual(0o666, filePermissions(0o655, PipeOptions{})); } test "executable bit" { diff --git a/src/Compilation.zig b/src/Compilation.zig index 617421e2790f85c3c7a5d2771e51b7abdc61d709..c63fa9a3c1a344b74e55ac3816413dbd5088be7b 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -5782,7 +5782,7 @@ pub fn translateC( } // Just to save disk space, we delete the file because it is never needed again. - cache_tmp_dir.deleteFile(dep_basename) catch |err| { + cache_tmp_dir.deleteFile(io, dep_basename) catch |err| { log.warn("failed to delete '{s}': {t}", .{ dep_file_path, err }); }; } @@ -6314,11 +6314,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr } // Just to save disk space, we delete the files that are never needed again. - defer if (out_diag_path) |diag_file_path| zig_cache_tmp_dir.deleteFile(fs.path.basename(diag_file_path)) catch |err| switch (err) { + defer if (out_diag_path) |diag_file_path| zig_cache_tmp_dir.deleteFile(io, fs.path.basename(diag_file_path)) catch |err| switch (err) { error.FileNotFound => {}, // the file wasn't created due to an error we reported else => log.warn("failed to delete '{s}': {s}", .{ diag_file_path, @errorName(err) }), }; - defer if (out_dep_path) |dep_file_path| zig_cache_tmp_dir.deleteFile(fs.path.basename(dep_file_path)) catch |err| switch (err) { + defer if (out_dep_path) |dep_file_path| zig_cache_tmp_dir.deleteFile(io, fs.path.basename(dep_file_path)) catch |err| switch (err) { error.FileNotFound => {}, // the file wasn't created due to an error we reported else => log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }), }; @@ -6329,7 +6329,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr child.stdout_behavior = .Inherit; child.stderr_behavior = .Inherit; - const term = child.spawnAndWait() catch |err| { + const term = child.spawnAndWait(io) catch |err| { return comp.failCObj(c_object, "failed to spawn zig clang (passthrough mode) {s}: {s}", .{ argv.items[0], @errorName(err) }); }; switch (term) { @@ -6347,7 +6347,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr child.stdout_behavior = .Ignore; child.stderr_behavior = .Pipe; - try child.spawn(); + try child.spawn(io); var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); @@ -6723,6 +6723,7 @@ fn spawnZigRc( argv: []const []const u8, child_progress_node: std.Progress.Node, ) !void { + const io = comp.io; var node_name: std.ArrayList(u8) = .empty; defer node_name.deinit(arena); @@ -6732,8 +6733,8 @@ fn spawnZigRc( child.stderr_behavior = .Pipe; child.progress_node = child_progress_node; - child.spawn() catch |err| { - return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {s}", .{ argv[0], @errorName(err) }); + child.spawn(io) catch |err| { + return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {t}", .{ argv[0], err }); }; var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{ diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 3552a7fc06ceb10e89db1e69ec671b5c519875bd..e9753734e91c70b66507decd8006b95609efae30 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1347,7 +1347,7 @@ fn unzip( .diagnostics = &diagnostics, }) catch |err| return f.fail(f.location_tok, try eb.printString("zip extract failed: {t}", .{err})); - cache_root.handle.deleteFile(&zip_path) catch |err| + cache_root.handle.deleteFile(io, &zip_path) catch |err| return f.fail(f.location_tok, try eb.printString("delete temporary zip failed: {t}", .{err})); return .{ .root_dir = diagnostics.root_dir }; @@ -1547,7 +1547,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute .fs_path = fs_path, .failure = undefined, // to be populated by the worker }; - group.async(io, workerDeleteFile, .{ root_dir, deleted_file }); + group.async(io, workerDeleteFile, .{ io, root_dir, deleted_file }); try deleted_files.append(deleted_file); continue; } @@ -1669,8 +1669,8 @@ fn workerHashFile(dir: Io.Dir, hashed_file: *HashedFile) void { hashed_file.failure = hashFileFallible(dir, hashed_file); } -fn workerDeleteFile(dir: Io.Dir, deleted_file: *DeletedFile) void { - deleted_file.failure = deleteFileFallible(dir, deleted_file); +fn workerDeleteFile(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) void { + deleted_file.failure = deleteFileFallible(io, dir, deleted_file); } fn hashFileFallible(io: Io, dir: Io.Dir, hashed_file: *HashedFile) HashedFile.Error!void { @@ -1712,8 +1712,8 @@ fn hashFileFallible(io: Io, dir: Io.Dir, hashed_file: *HashedFile) HashedFile.Er hashed_file.size = file_size; } -fn deleteFileFallible(dir: Io.Dir, deleted_file: *DeletedFile) DeletedFile.Error!void { - try dir.deleteFile(deleted_file.fs_path); +fn deleteFileFallible(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) DeletedFile.Error!void { + try dir.deleteFile(io, deleted_file.fs_path); } fn setExecutable(file: Io.File) !void { diff --git a/src/link.zig b/src/link.zig index ff6d938e6feedc5d45b0643bd0d2a5732e8a0d11..7ebe1c6ba29c3c2eb0262c3ec1f58b3a04c38df8 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1235,22 +1235,26 @@ pub const File = struct { ty: InternPool.Index, }; - pub fn determineMode( + pub fn determinePermissions( output_mode: std.builtin.OutputMode, link_mode: std.builtin.LinkMode, - ) Io.File.Mode { + ) Io.File.Permissions { // On common systems with a 0o022 umask, 0o777 will still result in a file created // with 0o755 permissions, but it works appropriately if the system is configured // more leniently. As another data point, C's fopen seems to open files with the // 666 mode. - const executable_mode = if (builtin.target.os.tag == .windows) 0 else 0o777; + const executable_mode: Io.FilePermissions = if (builtin.target.os.tag == .windows) + .default_file + else + .fromMode(0o777); + switch (output_mode) { .Lib => return switch (link_mode) { .dynamic => executable_mode, - .static => Io.File.default_mode, + .static => .default_file, }, .Exe => return executable_mode, - .Obj => return Io.File.default_mode, + .Obj => return .default_file, } } diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 6920c127623f75f6d77df5206de4fb4322d23ad2..e3127b24bf9719d45a414ff53598aa6547910a88 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1608,13 +1608,13 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi child.stdout_behavior = .Inherit; child.stderr_behavior = .Inherit; - break :term child.spawnAndWait(); + break :term child.spawnAndWait(io); } else term: { child.stdin_behavior = .Ignore; child.stdout_behavior = .Ignore; child.stderr_behavior = .Pipe; - child.spawn() catch |err| break :term err; + child.spawn(io) catch |err| break :term err; var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited); break :term child.wait(); @@ -1658,13 +1658,13 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi rsp_child.stdout_behavior = .Inherit; rsp_child.stderr_behavior = .Inherit; - break :term rsp_child.spawnAndWait() catch |err| break :err err; + break :term rsp_child.spawnAndWait(io) catch |err| break :err err; } else { rsp_child.stdin_behavior = .Ignore; rsp_child.stdout_behavior = .Ignore; rsp_child.stderr_behavior = .Pipe; - rsp_child.spawn() catch |err| break :err err; + rsp_child.spawn(io) catch |err| break :err err; var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{}); stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited); break :term rsp_child.wait() catch |err| break :err err; diff --git a/src/main.zig b/src/main.zig index a832a79dbe475fd880ffafd217adcc8eff4a15a0..3835e759491bdbc6ff279b9a7014a85561439673 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4457,7 +4457,7 @@ fn runOrTest( const term_result = t: { std.debug.lockStdErr(); defer std.debug.unlockStdErr(); - break :t child.spawnAndWait(); + break :t child.spawnAndWait(io); }; const term = term_result catch |err| { try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); @@ -4512,6 +4512,7 @@ fn runOrTestHotSwap( all_args: []const []const u8, runtime_args_start: ?usize, ) !std.process.Child.Id { + const io = comp.io; const lf = comp.bin_file.?; const exe_path = switch (builtin.target.os.tag) { @@ -4593,7 +4594,7 @@ fn runOrTestHotSwap( child.stdout_behavior = .Inherit; child.stderr_behavior = .Inherit; - try child.spawn(); + try child.spawn(io); return child.id; }, @@ -5419,8 +5420,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) const term = t: { std.debug.lockStdErr(); defer std.debug.unlockStdErr(); - break :t child.spawnAndWait() catch |err| { - fatal("failed to spawn build runner {s}: {s}", .{ child_argv.items[0], @errorName(err) }); + break :t child.spawnAndWait(io) catch |err| { + fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err }); }; }; @@ -5444,7 +5445,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) dirs.local_cache, tmp_sub_path, @errorName(err), }); }; - dirs.local_cache.handle.deleteFile(tmp_sub_path) catch {}; + dirs.local_cache.handle.deleteFile(io, tmp_sub_path) catch {}; var it = mem.splitScalar(u8, stdout, '\n'); var any_errors = false; @@ -5685,7 +5686,7 @@ fn jitCmd( child.stdout_behavior = if (options.capture == null) .Inherit else .Pipe; child.stderr_behavior = .Inherit; - try child.spawn(); + try child.spawn(io); if (options.capture) |ptr| { var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); diff --git a/test/src/Cases.zig b/test/src/Cases.zig index bf3eade8f4de329e34be4044617c246f5f4c6efe..82b59f722d50620efc0b19a3edaf219e752bbecd 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -461,6 +461,7 @@ pub fn lowerToBuildSteps( parent_step: *std.Build.Step, options: CaseTestOptions, ) void { + const io = self.io; const host = b.resolveTargetQuery(.{}); const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM"); @@ -595,7 +596,7 @@ pub fn lowerToBuildSteps( }, .Execution => |expected_stdout| no_exec: { const run = if (case.target.result.ofmt == .c) run_step: { - if (getExternalExecutor(&host.result, &case.target.result, .{ .link_libc = true }) != .native) { + if (getExternalExecutor(io, &host.result, &case.target.result, .{ .link_libc = true }) != .native) { // We wouldn't be able to run the compiled C code. break :no_exec; } diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index 2a28845a56f2598b7d4a02ad21a6f7888710e6da..3aaec0f2b232cd7c7044e368b526dc42a83b4bb0 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -29,7 +29,7 @@ pub fn main() !void { child.stdin_behavior = .Pipe; child.stdout_behavior = .Pipe; child.stderr_behavior = .Inherit; - try child.spawn(); + try child.spawn(io); const child_stdin = child.stdin.?; try child_stdin.writeAll("hello from stdin"); // verified in child child_stdin.close(io); -- 2.54.0 From bee8005fe6817ade9191de0493888b14cdbcac31 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 21:00:04 -0800 Subject: [PATCH 032/195] std.heap.DebugAllocator: never detect TTY config instead, allow the user to set it as a field. this fixes a bug where leak printing and error printing would run tty config detection for stderr, and then emit a log, which is not necessary going to print to stderr. however, the nice defaults are gone; the user must explicitly assign the tty_config field during initialization or else the logging will not have color. related: https://github.com/ziglang/zig/issues/24510 --- lib/compiler/build_runner.zig | 2 +- lib/std/Build/Fuzz.zig | 19 ++++++----- lib/std/Build/Step/InstallArtifact.zig | 2 +- lib/std/Build/Step/WriteFile.zig | 2 +- lib/std/Io/Dir.zig | 8 ++--- lib/std/Io/File.zig | 2 +- lib/std/Io/File/Atomic.zig | 2 +- lib/std/Io/File/Writer.zig | 9 ++--- lib/std/Io/Threaded.zig | 32 +++++++++--------- lib/std/Io/net/HostName.zig | 2 +- lib/std/Io/test.zig | 5 +-- lib/std/Io/tty.zig | 9 ++--- lib/std/debug.zig | 4 ++- lib/std/debug/Info.zig | 7 ++-- lib/std/heap/debug_allocator.zig | 39 +++++++++++++++------- lib/std/process/Child.zig | 2 +- lib/std/zig.zig | 4 +-- src/link/Dwarf.zig | 6 ++-- src/link/MappedFile.zig | 2 +- src/main.zig | 23 ++++++++----- test/link/macho.zig | 3 +- test/src/Cases.zig | 5 +-- test/standalone/ios/build.zig | 4 ++- test/standalone/self_exe_symlink/build.zig | 4 --- test/tests.zig | 2 +- 25 files changed, 113 insertions(+), 86 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 677158645d7023c45b3a7831f6b956d638d66fc6..05cd21ecdb07d244741964ef86c1506e3b824b17 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -435,7 +435,7 @@ pub fn main() !void { if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{}); } - const ttyconf = color.detectTtyConf(); + const ttyconf = color.detectTtyConf(io); const main_progress_node = std.Progress.start(.{ .disable_printing = (color == .off), diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index 8fedb7e0679cdf4c6ce48c9a05b5c772d917e69b..8837d7d5270ff2ddad7808e2077c4d212c526557 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -360,12 +360,13 @@ fn coverageRunCancelable(fuzz: *Fuzz) Io.Cancelable!void { fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutOfMemory, AlreadyReported, Canceled }!void { assert(fuzz.mode == .forever); const ws = fuzz.mode.forever.ws; + const gpa = fuzz.gpa; const io = fuzz.io; try fuzz.coverage_mutex.lock(io); defer fuzz.coverage_mutex.unlock(io); - const gop = try fuzz.coverage_files.getOrPut(fuzz.gpa, coverage_id); + const gop = try fuzz.coverage_files.getOrPut(gpa, coverage_id); if (gop.found_existing) { // We are fuzzing the same executable with multiple threads. // Perhaps the same unit test; perhaps a different one. In any @@ -383,12 +384,12 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO .entry_points = .{}, .start_timestamp = ws.now(), }; - errdefer gop.value_ptr.coverage.deinit(fuzz.gpa); + errdefer gop.value_ptr.coverage.deinit(gpa); const rebuilt_exe_path = run_step.rebuilt_executable.?; const target = run_step.producer.?.rootModuleTarget(); var debug_info = std.debug.Info.load( - fuzz.gpa, + gpa, io, rebuilt_exe_path, &gop.value_ptr.coverage, @@ -400,7 +401,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO }); return error.AlreadyReported; }; - defer debug_info.deinit(fuzz.gpa); + defer debug_info.deinit(gpa); const coverage_file_path: Build.Cache.Path = .{ .root_dir = run_step.step.owner.cache_root, @@ -434,14 +435,14 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); const pcs = header.pcAddrs(); - const source_locations = try fuzz.gpa.alloc(Coverage.SourceLocation, pcs.len); - errdefer fuzz.gpa.free(source_locations); + const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len); + errdefer gpa.free(source_locations); // Unfortunately the PCs array that LLVM gives us from the 8-bit PC // counters feature is not sorted. var sorted_pcs: std.MultiArrayList(struct { pc: u64, index: u32, sl: Coverage.SourceLocation }) = .{}; - defer sorted_pcs.deinit(fuzz.gpa); - try sorted_pcs.resize(fuzz.gpa, pcs.len); + defer sorted_pcs.deinit(gpa); + try sorted_pcs.resize(gpa, pcs.len); @memcpy(sorted_pcs.items(.pc), pcs); for (sorted_pcs.items(.index), 0..) |*v, i| v.* = @intCast(i); sorted_pcs.sortUnstable(struct { @@ -452,7 +453,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO } }{ .addrs = sorted_pcs.items(.pc) }); - debug_info.resolveAddresses(fuzz.gpa, sorted_pcs.items(.pc), sorted_pcs.items(.sl)) catch |err| { + debug_info.resolveAddresses(gpa, io, sorted_pcs.items(.pc), sorted_pcs.items(.sl)) catch |err| { log.err("failed to resolve addresses to source locations: {t}", .{err}); return error.AlreadyReported; }; diff --git a/lib/std/Build/Step/InstallArtifact.zig b/lib/std/Build/Step/InstallArtifact.zig index 4c1506aa335d43503571d902438b4bc2ba8b4a98..019d465f01d6b2784e337ed410bd2b073bffb210 100644 --- a/lib/std/Build/Step/InstallArtifact.zig +++ b/lib/std/Build/Step/InstallArtifact.zig @@ -172,7 +172,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { defer src_dir.close(io); var it = try src_dir.walk(b.allocator); - next_entry: while (try it.next()) |entry| { + next_entry: while (try it.next(io)) |entry| { for (dir.options.exclude_extensions) |ext| { if (std.mem.endsWith(u8, entry.path, ext)) continue :next_entry; } diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index 3d712fa1d45362bf4d3632a0bc4d7d9235d9e9bb..53222289e6fd0723c594f2e1c31f6e8015eb4f45 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -309,7 +309,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { var it = try already_open_dir.walk(gpa); defer it.deinit(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { if (!dir.options.pathIncluded(entry.path)) continue; const src_entry_path = try src_dir_path.join(arena, entry.path); diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index d6d6aa1be2de77a1bccf67dd4c491b8000e6aa7f..6c40e0217e6b6aed8646953653fb3e7c5fb11fa5 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -574,7 +574,7 @@ pub fn updateFile( error.WriteFailed => return atomic_file.file_writer.err.?, }; try atomic_file.flush(); - try atomic_file.file_writer.file.updateTimes(src_stat.atime, src_stat.mtime); + try atomic_file.file_writer.file.setTimestamps(io, src_stat.atime, src_stat.mtime); try atomic_file.renameIntoPlace(); return .stale; } @@ -1238,7 +1238,7 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { process_stack: while (stack.items.len != 0) { var top = &stack.items[stack.items.len - 1]; - while (try top.iter.next()) |entry| { + while (try top.iter.next(io)) |entry| { var treat_as_dir = entry.kind == .directory; handle_entry: while (true) { if (treat_as_dir) { @@ -1695,9 +1695,9 @@ pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFil else try parent.openDir(io, dirname, .{}); - return .init(path.basename(dest_path), options.permissions, dir, true, options.write_buffer); + return .init(io, path.basename(dest_path), options.permissions, dir, true, options.write_buffer); } else { - return .init(dest_path, options.permissions, parent, false, options.write_buffer); + return .init(io, dest_path, options.permissions, parent, false, options.write_buffer); } } diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 6d4dd4f3233b337b4eebd4b284056075543e0ca7..ead129e3c50a24bf73fa322cd776ac3f605088ff 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -460,7 +460,7 @@ pub fn setTimestamps( last_accessed: Io.Timestamp, last_modified: Io.Timestamp, ) SetTimestampsError!void { - return io.vtable.fileUpdateTimes(io.userdata, file, last_accessed, last_modified); + return io.vtable.fileSetTimestamps(io.userdata, file, last_accessed, last_modified); } /// Sets the accessed and modification timestamps of `file` to the current wall diff --git a/lib/std/Io/File/Atomic.zig b/lib/std/Io/File/Atomic.zig index 7d412703edadfb7878049a6de671a33ef46c7db2..340303ca39ebdb71eadf07623d66ad725efc1e67 100644 --- a/lib/std/Io/File/Atomic.zig +++ b/lib/std/Io/File/Atomic.zig @@ -66,7 +66,7 @@ pub fn deinit(af: *Atomic) void { af.* = undefined; } -pub const FlushError = File.WriteError; +pub const FlushError = File.Writer.Error; pub fn flush(af: *Atomic) FlushError!void { af.file_writer.interface.flush() catch |err| switch (err) { diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index d8c30ddbeff65af193433e878816dd1a14b749d6..cc971edbf44753edf9b081cbdbbcc002619e0e69 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -158,7 +158,7 @@ pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) fn sendFilePositional(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { const io = w.io; const header = w.interface.buffered(); - const n = io.vtable.fileSendFilePositional(io.userdata, w.file, header, file_reader, limit, w.pos) catch |err| switch (err) { + const n = io.vtable.fileWriteFilePositional(io.userdata, w.file, header, file_reader, limit, w.pos) catch |err| switch (err) { error.Unseekable => { w.mode = w.mode.toStreaming(); const pos = w.pos; @@ -187,7 +187,7 @@ fn sendFilePositional(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) fn sendFileStreaming(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { const io = w.io; const header = w.interface.buffered(); - const n = io.vtable.fileSendFileStreaming(io.userdata, w.file, header, file_reader, limit) catch |err| switch (err) { + const n = io.vtable.fileWriteFileStreaming(io.userdata, w.file, header, file_reader, limit) catch |err| switch (err) { error.Canceled => { w.err = error.Canceled; return error.WriteFailed; @@ -226,7 +226,7 @@ pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void { } } -pub const EndError = File.SetEndPosError || Io.Writer.Error; +pub const EndError = File.SetLengthError || Io.Writer.Error; /// Flushes any buffered data and sets the end position of the file. /// @@ -236,11 +236,12 @@ pub const EndError = File.SetEndPosError || Io.Writer.Error; /// Flush failure is handled by setting `err` so that it can be handled /// along with other write failures. pub fn end(w: *Writer) EndError!void { + const io = w.io; try w.interface.flush(); switch (w.mode) { .positional, .positional_reading, - => w.file.setLength(w.pos) catch |err| switch (err) { + => w.file.setLength(io, w.pos) catch |err| switch (err) { error.NonResizable => return, else => |e| return e, }, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 16e8930267387c908bbe1f760899f88e1e9bbb8c..945e47d5fd85bdbb4db71c47743e9f4d5a5e6c92 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -4,8 +4,6 @@ const builtin = @import("builtin"); const native_os = builtin.os.tag; const is_windows = native_os == .windows; const is_darwin = native_os.isDarwin(); -const windows = std.os.windows; -const ws2_32 = std.os.windows.ws2_32; const is_debug = builtin.mode == .Debug; const std = @import("../std.zig"); @@ -19,6 +17,8 @@ const Allocator = std.mem.Allocator; const Alignment = std.mem.Alignment; const assert = std.debug.assert; const posix = std.posix; +const windows = std.os.windows; +const ws2_32 = std.os.windows.ws2_32; /// Thread-safe. allocator: Allocator, @@ -1452,7 +1452,7 @@ const dirMake = switch (native_os) { else => dirMakePosix, }; -fn dirMakePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void { +fn dirMakePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.MakeError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -1461,7 +1461,7 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir try current_thread.beginSyscall(); while (true) { - switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, mode))) { + switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, permissions.toMode()))) { .SUCCESS => { current_thread.endSyscall(); return; @@ -1498,8 +1498,8 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir } } -fn dirMakeWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void { - if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, mode); +fn dirMakeWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.MakeError!void { + if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, permissions); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); try current_thread.beginSyscall(); @@ -1540,13 +1540,13 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir. } } -fn dirMakeWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void { +fn dirMakeWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.MakeError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); try current_thread.checkCancel(); const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); - _ = mode; + _ = permissions; // TODO use this value const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{ .dir = dir.handle, .access_mask = .{ @@ -1570,7 +1570,7 @@ fn dirMakePath( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, - mode: Dir.Mode, + permissions: Dir.Permissions, ) Dir.MakePathError!Dir.MakePathStatus { const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -1578,7 +1578,7 @@ fn dirMakePath( var status: Dir.MakePathStatus = .existed; var component = it.last() orelse return error.BadPathName; while (true) { - if (dirMake(t, dir, component.path, mode)) |_| { + if (dirMake(t, dir, component.path, permissions)) |_| { status = .created; } else |err| switch (err) { error.PathAlreadyExists => { @@ -4945,7 +4945,7 @@ fn dirSetTimestamps( sub_path: []const u8, last_accessed: Io.Timestamp, last_modified: Io.Timestamp, - options: File.SetTimestampsOptions, + options: Dir.SetTimestampsOptions, ) File.SetTimestampsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -4997,7 +4997,7 @@ fn dirSetTimestampsNow( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, - options: File.SetTimestampsOptions, + options: Dir.SetTimestampsOptions, ) File.SetTimestampsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -6271,7 +6271,7 @@ fn fileWriteStreaming( header: []const u8, data: []const []const u8, splat: usize, -) File.WriteStreamingError!usize { +) File.Writer.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -9690,7 +9690,7 @@ fn statFromLinux(stx: *const std.os.linux.Statx) File.Stat { return .{ .inode = stx.ino, .size = stx.size, - .mode = stx.mode, + .permissions = .fromMode(stx.mode), .kind = switch (stx.mode & std.os.linux.S.IFMT) { std.os.linux.S.IFDIR => .directory, std.os.linux.S.IFCHR => .character_device, @@ -9714,7 +9714,7 @@ fn statFromPosix(st: *const posix.Stat) File.Stat { return .{ .inode = st.ino, .size = @bitCast(st.size), - .mode = st.mode, + .permissions = .fromMode(st.mode), .kind = k: { const m = st.mode & posix.S.IFMT; switch (m) { @@ -10019,7 +10019,7 @@ fn lookupHosts( options: HostName.LookupOptions, ) !void { const t_io = io(t); - const file = File.openAbsolute(t_io, "/etc/hosts", .{}) catch |err| switch (err) { + const file = Dir.openFileAbsolute(t_io, "/etc/hosts", .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.AccessDenied, diff --git a/lib/std/Io/net/HostName.zig b/lib/std/Io/net/HostName.zig index 628a97d1f84d0f9269986847e08cd3d6e5dd831d..84484b9dc1fc73f48a511cad522fa94d4a89b578 100644 --- a/lib/std/Io/net/HostName.zig +++ b/lib/std/Io/net/HostName.zig @@ -343,7 +343,7 @@ pub const ResolvConf = struct { .attempts = 2, }; - const file = Io.File.openAbsolute(io, "/etc/resolv.conf", .{}) catch |err| switch (err) { + const file = Io.Dir.openFileAbsolute(io, "/etc/resolv.conf", .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.AccessDenied, diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 02bdb591d00d38c58a9a1310a71e7ebe080ef543..891c751ef21ad0abe34f7ca1257c27444070cba6 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -114,7 +114,7 @@ test "setEndPos" { try expect((try file.getPos()) == 100); } -test "updateTimes" { +test "setTimestamps" { const io = testing.io; var tmp = tmpDir(.{}); @@ -126,7 +126,8 @@ test "updateTimes" { const stat_old = try file.stat(io); // Set atime and mtime to 5s before - try file.updateTimes( + try file.setTimestamps( + io, stat_old.atime.subDuration(.fromSeconds(5)), stat_old.mtime.subDuration(.fromSeconds(5)), ); diff --git a/lib/std/Io/tty.zig b/lib/std/Io/tty.zig index d1f8b576c22015f8e69e5a2599c21c24fc1ac0e4..65f1b7dad5451df1d19463d47007b4a055f0f82a 100644 --- a/lib/std/Io/tty.zig +++ b/lib/std/Io/tty.zig @@ -2,6 +2,7 @@ const builtin = @import("builtin"); const native_os = builtin.os.tag; const std = @import("std"); +const Io = std.Io; const File = std.Io.File; const process = std.process; const windows = std.os.windows; @@ -39,7 +40,7 @@ pub const Config = union(enum) { /// This includes feature checks for ANSI escape codes and the Windows console API, as well as /// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default. /// Will attempt to enable ANSI escape code support if necessary/possible. - pub fn detect(file: File) Config { + pub fn detect(io: Io, file: File) Config { const force_color: ?bool = if (builtin.os.tag == .wasi) null // wasi does not support environment variables else if (process.hasNonEmptyEnvVarConstant("NO_COLOR")) @@ -51,7 +52,7 @@ pub const Config = union(enum) { if (force_color == false) return .no_color; - if (file.enableAnsiEscapeCodes()) |_| { + if (file.enableAnsiEscapeCodes(io)) |_| { return .escape_codes; } else |_| {} @@ -74,9 +75,9 @@ pub const Config = union(enum) { reset_attributes: u16, }; - pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || std.Io.Writer.Error; + pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; - pub fn setColor(conf: Config, w: *std.Io.Writer, color: Color) SetColorError!void { + pub fn setColor(conf: Config, w: *Io.Writer, color: Color) SetColorError!void { nosuspend switch (conf) { .no_color => return, .escape_codes => { diff --git a/lib/std/debug.zig b/lib/std/debug.zig index cb79bb7855bb18c322f58c7df328f62338b28c1b..8f1cd50e8e758c59089e9648f5f4fed88b5d623d 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -286,11 +286,13 @@ pub fn unlockStdErr() void { pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } { const global = struct { var conf: ?tty.Config = null; + var single_threaded_io: Io.Threaded = .init_single_threaded; }; + const io = global.single_threaded_io.io(); const w = std.Progress.lockStderrWriter(buffer); // The stderr lock also locks access to `global.conf`. if (global.conf == null) { - global.conf = .detect(.stderr()); + global.conf = .detect(io, .stderr()); } return .{ w, global.conf.? }; } diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index 6b31f03f72ad366bb73e60ea71953b65cce557af..34e79227d1375c621648d3c0eb74e9fb92dc736d 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -42,7 +42,7 @@ pub fn load( var file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); defer file.close(io); - var elf_file: ElfFile = try .load(gpa, file, null, &.none); + var elf_file: ElfFile = try .load(gpa, io, file, null, &.none); errdefer elf_file.deinit(gpa); if (elf_file.dwarf == null) return error.MissingDebugInfo; @@ -58,7 +58,7 @@ pub fn load( const path_str = try path.toString(gpa); defer gpa.free(path_str); - var macho_file: MachOFile = try .load(gpa, path_str, arch); + var macho_file: MachOFile = try .load(gpa, io, path_str, arch); errdefer macho_file.deinit(gpa); return .{ @@ -85,6 +85,7 @@ pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError || error{U pub fn resolveAddresses( info: *Info, gpa: Allocator, + io: Io, /// Asserts the addresses are in ascending order. sorted_pc_addrs: []const u64, /// Asserts its length equals length of `sorted_pc_addrs`. @@ -97,7 +98,7 @@ pub fn resolveAddresses( // Resolving all of the addresses at once unfortunately isn't so easy in Mach-O binaries // due to split debug information. For now, we'll just resolve the addreses one by one. for (sorted_pc_addrs, output) |pc_addr, *src_loc| { - const dwarf, const dwarf_pc_addr = mf.getDwarfForAddress(gpa, pc_addr) catch |err| switch (err) { + const dwarf, const dwarf_pc_addr = mf.getDwarfForAddress(gpa, io, pc_addr) catch |err| switch (err) { error.InvalidMachO, error.InvalidDwarf => return error.InvalidDebugInfo, else => |e| return e, }; diff --git a/lib/std/heap/debug_allocator.zig b/lib/std/heap/debug_allocator.zig index 3183becd828288968754c9b3e222105db449066a..27b1b9179f24299cd86d111a24ff9799016a3677 100644 --- a/lib/std/heap/debug_allocator.zig +++ b/lib/std/heap/debug_allocator.zig @@ -179,6 +179,8 @@ pub fn DebugAllocator(comptime config: Config) type { total_requested_bytes: @TypeOf(total_requested_bytes_init) = total_requested_bytes_init, requested_memory_limit: @TypeOf(requested_memory_limit_init) = requested_memory_limit_init, mutex: @TypeOf(mutex_init) = mutex_init, + /// Set this value differently to affect how errors and leaks are logged. + tty_config: std.Io.tty.Config = .no_color, const Self = @This(); @@ -458,9 +460,9 @@ pub fn DebugAllocator(comptime config: Config) type { /// Emits log messages for leaks and then returns the number of detected leaks (0 if no leaks were detected). pub fn detectLeaks(self: *Self) usize { - var leaks: usize = 0; + const tty_config = self.tty_config; - const tty_config: std.Io.tty.Config = .detect(.stderr()); + var leaks: usize = 0; for (self.buckets, 0..) |init_optional_bucket, size_class_index| { var optional_bucket = init_optional_bucket; @@ -533,10 +535,15 @@ pub fn DebugAllocator(comptime config: Config) type { @memset(addr_buf[@min(st.index, addr_buf.len)..], 0); } - fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void { + fn reportDoubleFree( + tty_config: std.Io.tty.Config, + ret_addr: usize, + alloc_stack_trace: StackTrace, + free_stack_trace: StackTrace, + ) void { + @branchHint(.cold); var addr_buf: [stack_n]usize = undefined; const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); - const tty_config: std.Io.tty.Config = .detect(.stderr()); log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{ std.debug.FormatStackTrace{ .stack_trace = alloc_stack_trace, @@ -580,7 +587,7 @@ pub fn DebugAllocator(comptime config: Config) type { if (config.retain_metadata and entry.value_ptr.freed) { if (config.safety) { - reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); + reportDoubleFree(self.tty_config, ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); @panic("Unrecoverable double free"); } else { unreachable; @@ -588,9 +595,10 @@ pub fn DebugAllocator(comptime config: Config) type { } if (config.safety and old_mem.len != entry.value_ptr.bytes.len) { + @branchHint(.cold); var addr_buf: [stack_n]usize = undefined; const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); - const tty_config: std.Io.tty.Config = .detect(.stderr()); + const tty_config = self.tty_config; log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ entry.value_ptr.bytes.len, old_mem.len, @@ -693,7 +701,7 @@ pub fn DebugAllocator(comptime config: Config) type { if (config.retain_metadata and entry.value_ptr.freed) { if (config.safety) { - reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); + reportDoubleFree(self.tty_config, ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); return; } else { unreachable; @@ -701,9 +709,10 @@ pub fn DebugAllocator(comptime config: Config) type { } if (config.safety and old_mem.len != entry.value_ptr.bytes.len) { + @branchHint(.cold); var addr_buf: [stack_n]usize = undefined; const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); - const tty_config: std.Io.tty.Config = .detect(.stderr()); + const tty_config = self.tty_config; log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ entry.value_ptr.bytes.len, old_mem.len, @@ -915,6 +924,7 @@ pub fn DebugAllocator(comptime config: Config) type { if (!is_used) { if (config.safety) { reportDoubleFree( + self.tty_config, return_address, bucketStackTrace(bucket, slot_count, slot_index, .alloc), bucketStackTrace(bucket, slot_count, slot_index, .free), @@ -935,7 +945,8 @@ pub fn DebugAllocator(comptime config: Config) type { var addr_buf: [stack_n]usize = undefined; const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); if (old_memory.len != requested_size) { - const tty_config: std.Io.tty.Config = .detect(.stderr()); + @branchHint(.cold); + const tty_config = self.tty_config; log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ requested_size, old_memory.len, @@ -950,7 +961,8 @@ pub fn DebugAllocator(comptime config: Config) type { }); } if (alignment != slot_alignment) { - const tty_config: std.Io.tty.Config = .detect(.stderr()); + @branchHint(.cold); + const tty_config = self.tty_config; log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{ slot_alignment.toByteUnits(), alignment.toByteUnits(), @@ -1028,6 +1040,7 @@ pub fn DebugAllocator(comptime config: Config) type { const is_used = @as(u1, @truncate(used_byte.* >> used_bit_index)) != 0; if (!is_used) { reportDoubleFree( + self.tty_config, return_address, bucketStackTrace(bucket, slot_count, slot_index, .alloc), bucketStackTrace(bucket, slot_count, slot_index, .free), @@ -1044,7 +1057,8 @@ pub fn DebugAllocator(comptime config: Config) type { var addr_buf: [stack_n]usize = undefined; const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); if (memory.len != requested_size) { - const tty_config: std.Io.tty.Config = .detect(.stderr()); + @branchHint(.cold); + const tty_config = self.tty_config; log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ requested_size, memory.len, @@ -1059,7 +1073,8 @@ pub fn DebugAllocator(comptime config: Config) type { }); } if (alignment != slot_alignment) { - const tty_config: std.Io.tty.Config = .detect(.stderr()); + @branchHint(.cold); + const tty_config = self.tty_config; log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{ slot_alignment.toByteUnits(), alignment.toByteUnits(), diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 17139e66b8ee9564cf9058922aec9ddc3a92d9ff..05cc4b39449ec6cd80ebdf5b186e77209b2d8211 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -470,7 +470,7 @@ pub fn run(allocator: Allocator, io: Io, args: struct { return .{ .stdout = try stdout.toOwnedSlice(allocator), .stderr = try stderr.toOwnedSlice(allocator), - .term = try child.wait(), + .term = try child.wait(io), }; } diff --git a/lib/std/zig.zig b/lib/std/zig.zig index c8a0dcde3b7b1e9a25e55b4e78f75b77ebfdca98..ac6a0787de67bb5a32a5ab861432d0b0f1bf8133 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -60,9 +60,9 @@ pub const Color = enum { .off => .no_color, }; } - pub fn detectTtyConf(color: Color) Io.tty.Config { + pub fn detectTtyConf(color: Color, io: Io) Io.tty.Config { return switch (color) { - .auto => .detect(.stderr()), + .auto => .detect(io, .stderr()), .on => .escape_codes, .off => .no_color, }; diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index cfb02fba38b4e4c5da9b5fcf6675d335b5b36246..c675af3e22d463e9422107b87c4e23b419a973e1 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -51,10 +51,10 @@ pub const UpdateError = error{ } || codegen.GenerateSymbolError || Io.File.OpenError || - Io.File.SetEndPosError || + Io.File.LengthError || Io.File.CopyRangeError || - Io.File.PReadError || - Io.File.PWriteError; + Io.File.ReadPositionalError || + Io.File.WritePositionalError; pub const FlushError = UpdateError; diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index a61c6e764cb6495c63d66d9433aa6b16a4c05b64..4e58aae01a05f2fd557c860f4133c11f84e8e1e2 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -28,7 +28,7 @@ writers: std.SinglyLinkedList, pub const growth_factor = 4; -pub const Error = std.posix.MMapError || std.posix.MRemapError || Io.File.SetEndPosError || error{ +pub const Error = std.posix.MMapError || std.posix.MRemapError || Io.File.LengthError || error{ NotFile, SystemResources, IsDir, diff --git a/src/main.zig b/src/main.zig index 3835e759491bdbc6ff279b9a7014a85561439673..bb940a1fe70dd250cb7d8cbc15ee7d40ca51b686 100644 --- a/src/main.zig +++ b/src/main.zig @@ -162,17 +162,20 @@ var debug_allocator: std.heap.DebugAllocator(.{ .stack_trace_frames = build_options.mem_leak_frames, }) = .init; +const use_debug_allocator = build_options.debug_gpa or + (native_os != .wasi and !builtin.link_libc and switch (builtin.mode) { + .Debug, .ReleaseSafe => true, + .ReleaseFast, .ReleaseSmall => false, + }); + pub fn main() anyerror!void { - const gpa, const is_debug = gpa: { - if (build_options.debug_gpa) break :gpa .{ debug_allocator.allocator(), true }; - if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false }; - if (builtin.link_libc) break :gpa .{ std.heap.c_allocator, false }; - break :gpa switch (builtin.mode) { - .Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true }, - .ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false }, - }; + const gpa = gpa: { + if (use_debug_allocator) break :gpa debug_allocator.allocator(); + if (native_os == .wasi) break :gpa std.heap.wasm_allocator; + if (builtin.link_libc) break :gpa std.heap.c_allocator; + break :gpa std.heap.smp_allocator; }; - defer if (is_debug) { + defer if (use_debug_allocator) { _ = debug_allocator.deinit(); }; var arena_instance = std.heap.ArenaAllocator.init(gpa); @@ -244,6 +247,8 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { threaded.stack_size = thread_stack_size; const io = threaded.io(); + debug_allocator.tty_config = .detect(io, .stderr()); + const cmd = args[1]; const cmd_args = args[2..]; if (mem.eql(u8, cmd, "build-exe")) { diff --git a/test/link/macho.zig b/test/link/macho.zig index 0f3df8c2d8e28fdc057af21fee1591d7959ceb24..12a74e0825bb19eff463189229e0dd3baa52a0db 100644 --- a/test/link/macho.zig +++ b/test/link/macho.zig @@ -868,9 +868,10 @@ fn testLayout(b: *Build, opts: Options) *Step { } fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step { + const io = b.graph.io; const test_step = addTestStep(b, "link-directly-cpp-tbd", opts); - const sdk = std.zig.system.darwin.getSdk(b.allocator, &opts.target.result) orelse + const sdk = std.zig.system.darwin.getSdk(b.allocator, io, &opts.target.result) orelse @panic("macOS SDK is required to run the test"); const exe = addExecutable(b, opts, .{ diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 82b59f722d50620efc0b19a3edaf219e752bbecd..f72748e5ef20d470f2204e6ea9b977c1e09688e4 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -339,7 +339,7 @@ fn addFromDirInner( var it = try iterable_dir.walk(ctx.arena); var filenames: ArrayList([]const u8) = .empty; - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { if (entry.kind != .file) continue; // Ignore stuff such as .swp files @@ -431,9 +431,10 @@ fn addFromDirInner( } } -pub fn init(gpa: Allocator, arena: Allocator) Cases { +pub fn init(gpa: Allocator, arena: Allocator, io: Io) Cases { return .{ .gpa = gpa, + .io = io, .cases = .init(gpa), .arena = arena, }; diff --git a/test/standalone/ios/build.zig b/test/standalone/ios/build.zig index b07b5b17ea20c4f6ee82cba0a2f9fa7e8e865978..b87d55993b6615c60700cb05d39cd8148be717fb 100644 --- a/test/standalone/ios/build.zig +++ b/test/standalone/ios/build.zig @@ -23,7 +23,9 @@ pub fn build(b: *std.Build) void { }), }); - if (std.zig.system.darwin.getSdk(b.allocator, &target.result)) |sdk| { + const io = b.graph.io; + + if (std.zig.system.darwin.getSdk(b.allocator, io, &target.result)) |sdk| { b.sysroot = sdk; exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) }); exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) }); diff --git a/test/standalone/self_exe_symlink/build.zig b/test/standalone/self_exe_symlink/build.zig index 651740c04be04442d1f85007f6a23bc2938a6a30..137848b9535fe4f361733f6dba443b58a5a7bf05 100644 --- a/test/standalone/self_exe_symlink/build.zig +++ b/test/standalone/self_exe_symlink/build.zig @@ -9,10 +9,6 @@ pub fn build(b: *std.Build) void { const optimize: std.builtin.OptimizeMode = .Debug; const target = b.graph.host; - // The test requires getFdPath in order to to get the path of the - // File returned by openSelfExe - if (!std.os.isGetFdPathSupportedOnTarget(target.result.os)) return; - const main = b.addExecutable(.{ .name = "main", .root_module = b.createModule(.{ diff --git a/test/tests.zig b/test/tests.zig index 015c3535bbd707a3b9130a5a4000337426d9bff2..ee324329e284ebcc06496f3f972b32b602205dd5 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2632,7 +2632,7 @@ pub fn addCases( const gpa = b.allocator; const io = b.graph.io; - var cases = @import("src/Cases.zig").init(gpa, arena); + var cases = @import("src/Cases.zig").init(gpa, arena, io); var dir = try b.build_root.handle.openDir(io, "test/cases", .{ .iterate = true }); defer dir.close(io); -- 2.54.0 From 90f7259ef17e8e07c2c3f71bea65d103cfe52c07 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 21:57:46 -0800 Subject: [PATCH 033/195] std.Progress: use a global static Io instance This decision should be audited and discussed. Some factors: * Passing an Io instance into start. * Avoiding reference to global static instance if it won't be used, so that it doesn't bloat the executable. * Being able to use std.debug.print, and related functionality when debugging std.Io instances and std.Progress. --- lib/std/Io/Threaded.zig | 20 +++++++++++- lib/std/Progress.zig | 71 +++++++++++++++++++++-------------------- lib/std/debug.zig | 11 ++++--- 3 files changed, 62 insertions(+), 40 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 945e47d5fd85bdbb4db71c47743e9f4d5a5e6c92..459440f5a6da9b3a81b4ea3ffb90fb5de4d11c85 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -771,6 +771,7 @@ pub fn io(t: *Threaded) Io { .windows => netWriteWindows, else => netWritePosix, }, + .netWriteFile = netWriteFile, .netSend = switch (native_os) { .windows => netSendWindows, else => netSendPosix, @@ -872,6 +873,7 @@ pub fn ioBasic(t: *Threaded) Io { .netClose = netCloseUnavailable, .netRead = netReadUnavailable, .netWrite = netWriteUnavailable, + .netWriteFile = netWriteFileUnavailable, .netSend = netSendUnavailable, .netReceive = netReceiveUnavailable, .netInterfaceNameResolve = netInterfaceNameResolveUnavailable, @@ -6782,7 +6784,7 @@ fn netWriteFile( header: []const u8, file_reader: *File.Reader, limit: Io.Limit, -) net.Stream.WriteFileError!usize { +) net.Stream.Writer.WriteFileError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; _ = socket_handle; @@ -6792,6 +6794,22 @@ fn netWriteFile( return error.Unimplemented; // TODO } +fn netWriteFileUnavailable( + userdata: ?*anyopaque, + socket_handle: net.Socket.Handle, + header: []const u8, + file_reader: *File.Reader, + limit: Io.Limit, +) net.Stream.Writer.WriteFileError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + _ = socket_handle; + _ = header; + _ = file_reader; + _ = limit; + return error.NetworkDown; +} + fn fileWriteFilePositional( userdata: ?*anyopaque, file: File, diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index d8b22c2db0f3a3819fce9f6186f1b42ef722f783..a839de8c4f27c6a2a6c911041810a04c63b1eeb1 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -458,13 +458,16 @@ pub fn start(options: Options) Node { if (noop_impl) return Node.none; + const io = static_threaded_io.io(); + if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| { global_progress.update_thread = std.Thread.spawn(.{}, ipcThreadRun, .{ - @as(posix.fd_t, switch (@typeInfo(posix.fd_t)) { + io, + @as(Io.File, .{ .handle = switch (@typeInfo(posix.fd_t)) { .int => ipc_fd, .pointer => @ptrFromInt(ipc_fd), else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)), - }), + } }), }) catch |err| { std.log.warn("failed to spawn IPC thread for communicating progress to parent: {s}", .{@errorName(err)}); return Node.none; @@ -476,9 +479,9 @@ pub fn start(options: Options) Node { } const stderr: Io.File = .stderr(); global_progress.terminal = stderr; - if (stderr.enableAnsiEscapeCodes()) |_| { + if (stderr.enableAnsiEscapeCodes(io)) |_| { global_progress.terminal_mode = .ansi_escape_codes; - } else |_| if (is_windows and stderr.isTty()) { + } else |_| if (is_windows and stderr.isTty(io)) { global_progress.terminal_mode = TerminalMode{ .windows_api = .{ .code_page = windows.kernel32.GetConsoleOutputCP(), } }; @@ -499,8 +502,8 @@ pub fn start(options: Options) Node { if (switch (global_progress.terminal_mode) { .off => unreachable, // handled a few lines above - .ansi_escape_codes => std.Thread.spawn(.{}, updateThreadRun, .{}), - .windows_api => if (is_windows) std.Thread.spawn(.{}, windowsApiUpdateThreadRun, .{}) else unreachable, + .ansi_escape_codes => std.Thread.spawn(.{}, updateThreadRun, .{io}), + .windows_api => if (is_windows) std.Thread.spawn(.{}, windowsApiUpdateThreadRun, .{io}) else unreachable, }) |thread| { global_progress.update_thread = thread; } else |err| { @@ -531,7 +534,7 @@ fn wait(timeout_ns: u64) bool { return resize_flag or (global_progress.cols == 0); } -fn updateThreadRun() void { +fn updateThreadRun(io: Io) void { // Store this data in the thread so that it does not need to be part of the // linker data of the main executable. var serialized_buffer: Serialized.Buffer = undefined; @@ -544,7 +547,7 @@ fn updateThreadRun() void { const buffer, _ = computeRedraw(&serialized_buffer); if (stderr_mutex.tryLock()) { defer stderr_mutex.unlock(); - write(buffer) catch return; + write(io, buffer) catch return; global_progress.need_clear = true; } } @@ -555,7 +558,7 @@ fn updateThreadRun() void { if (@atomicLoad(bool, &global_progress.done, .monotonic)) { stderr_mutex.lock(); defer stderr_mutex.unlock(); - return clearWrittenWithEscapeCodes() catch {}; + return clearWrittenWithEscapeCodes(io) catch {}; } maybeUpdateSize(resize_flag); @@ -563,7 +566,7 @@ fn updateThreadRun() void { const buffer, _ = computeRedraw(&serialized_buffer); if (stderr_mutex.tryLock()) { defer stderr_mutex.unlock(); - write(buffer) catch return; + write(io, buffer) catch return; global_progress.need_clear = true; } } @@ -577,7 +580,7 @@ fn windowsApiWriteMarker() void { _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null); } -fn windowsApiUpdateThreadRun() void { +fn windowsApiUpdateThreadRun(io: Io) void { var serialized_buffer: Serialized.Buffer = undefined; { @@ -589,7 +592,7 @@ fn windowsApiUpdateThreadRun() void { if (stderr_mutex.tryLock()) { defer stderr_mutex.unlock(); windowsApiWriteMarker(); - write(buffer) catch return; + write(io, buffer) catch return; global_progress.need_clear = true; windowsApiMoveToMarker(nl_n) catch return; } @@ -611,7 +614,7 @@ fn windowsApiUpdateThreadRun() void { defer stderr_mutex.unlock(); clearWrittenWindowsApi() catch return; windowsApiWriteMarker(); - write(buffer) catch return; + write(io, buffer) catch return; global_progress.need_clear = true; windowsApiMoveToMarker(nl_n) catch return; } @@ -624,8 +627,9 @@ fn windowsApiUpdateThreadRun() void { /// /// The lock is recursive; the same thread may hold the lock multiple times. pub fn lockStdErr() void { + const io = stderr_file_writer.io; stderr_mutex.lock(); - clearWrittenWithEscapeCodes() catch {}; + clearWrittenWithEscapeCodes(io) catch {}; } pub fn unlockStdErr() void { @@ -636,10 +640,12 @@ pub fn unlockStdErr() void { const stderr_writer: *Writer = &stderr_file_writer.interface; /// Protected by `stderr_mutex`. var stderr_file_writer: Io.File.Writer = .{ + .io = static_threaded_io.io(), .interface = Io.File.Writer.initInterface(&.{}), .file = if (is_windows) undefined else .stderr(), .mode = .streaming, }; +var static_threaded_io: Io.Threaded = .init_single_threaded; /// Allows the caller to freely write to the returned `Writer`, /// initialized with `buffer`, until `unlockStderrWriter` is called. @@ -647,9 +653,10 @@ var stderr_file_writer: Io.File.Writer = .{ /// During the lock, any `std.Progress` information is cleared from the terminal. /// /// The lock is recursive; the same thread may hold the lock multiple times. -pub fn lockStderrWriter(buffer: []u8) *Writer { +pub fn lockStderrWriter(buffer: []u8) *Io.Writer { + const io = stderr_file_writer.io; stderr_mutex.lock(); - clearWrittenWithEscapeCodes() catch {}; + clearWrittenWithEscapeCodes(io) catch {}; if (is_windows) stderr_file_writer.file = .stderr(); stderr_writer.flush() catch {}; stderr_writer.buffer = buffer; @@ -663,7 +670,7 @@ pub fn unlockStderrWriter() void { stderr_mutex.unlock(); } -fn ipcThreadRun(fd: posix.fd_t) anyerror!void { +fn ipcThreadRun(io: Io, file: Io.File) anyerror!void { // Store this data in the thread so that it does not need to be part of the // linker data of the main executable. var serialized_buffer: Serialized.Buffer = undefined; @@ -675,7 +682,7 @@ fn ipcThreadRun(fd: posix.fd_t) anyerror!void { return; const serialized = serialize(&serialized_buffer); - writeIpc(fd, serialized) catch |err| switch (err) { + writeIpc(io, file, serialized) catch |err| switch (err) { error.BrokenPipe => return, }; } @@ -687,7 +694,7 @@ fn ipcThreadRun(fd: posix.fd_t) anyerror!void { return; const serialized = serialize(&serialized_buffer); - writeIpc(fd, serialized) catch |err| switch (err) { + writeIpc(io, file, serialized) catch |err| switch (err) { error.BrokenPipe => return, }; } @@ -786,11 +793,11 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { } } -fn clearWrittenWithEscapeCodes() anyerror!void { +fn clearWrittenWithEscapeCodes(io: Io) anyerror!void { if (noop_impl or !global_progress.need_clear) return; global_progress.need_clear = false; - try write(clear ++ progress_remove); + try write(io, clear ++ progress_remove); } /// U+25BA or ► @@ -1417,13 +1424,13 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool { return nl_n + 2 < p.rows; } -fn write(buf: []const u8) anyerror!void { - try global_progress.terminal.writeAll(buf); +fn write(io: Io, buf: []const u8) anyerror!void { + try global_progress.terminal.writeStreamingAll(io, buf); } var remaining_write_trash_bytes: usize = 0; -fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void { +fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void { // Byteswap if necessary to ensure little endian over the pipe. This is // needed because the parent or child process might be running in qemu. if (is_big_endian) for (serialized.storage) |*s| s.byteSwap(); @@ -1434,11 +1441,7 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void { const storage = std.mem.sliceAsBytes(serialized.storage); const parents = std.mem.sliceAsBytes(serialized.parents); - var vecs: [3]posix.iovec_const = .{ - .{ .base = header.ptr, .len = header.len }, - .{ .base = storage.ptr, .len = storage.len }, - .{ .base = parents.ptr, .len = parents.len }, - }; + var vecs: [3][]const u8 = .{ header, storage, parents }; // Ensures the packet can fit in the pipe buffer. const upper_bound_msg_len = 1 + node_storage_buffer_len * @sizeOf(Node.Storage) + @@ -1449,7 +1452,7 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void { // We do this in a separate write call to give a better chance for the // writev below to be in a single packet. const n = @min(parents.len, remaining_write_trash_bytes); - if (posix.write(fd, parents[0..n])) |written| { + if (io.vtable.fileWriteStreaming(io.userdata, file, &.{}, &.{parents[0..n]}, 1)) |written| { remaining_write_trash_bytes -= written; continue; } else |err| switch (err) { @@ -1464,7 +1467,7 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void { // If this write would block we do not want to keep trying, but we need to // know if a partial message was written. - if (writevNonblock(fd, &vecs)) |written| { + if (writevNonblock(io, file, &vecs)) |written| { const total = header.len + storage.len + parents.len; if (written < total) { remaining_write_trash_bytes = total - written; @@ -1479,7 +1482,7 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void { } } -fn writevNonblock(fd: posix.fd_t, iov: []posix.iovec_const) posix.WriteError!usize { +fn writevNonblock(io: Io, file: Io.File, iov: [][]const u8) Io.File.Writer.Error!usize { var iov_index: usize = 0; var written: usize = 0; var total_written: usize = 0; @@ -1488,9 +1491,9 @@ fn writevNonblock(fd: posix.fd_t, iov: []posix.iovec_const) posix.WriteError!usi written >= iov[iov_index].len else return total_written) : (iov_index += 1) written -= iov[iov_index].len; - iov[iov_index].base += written; + iov[iov_index].ptr += written; iov[iov_index].len -= written; - written = try posix.writev(fd, iov[iov_index..]); + written = try io.vtable.fileWriteStreaming(io.userdata, file, &.{}, iov, 1); if (written == 0) return total_written; total_written += written; } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 8f1cd50e8e758c59089e9648f5f4fed88b5d623d..6ffa2542718e3bca0670d7005bcd72e20aa73952 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -286,13 +286,12 @@ pub fn unlockStdErr() void { pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } { const global = struct { var conf: ?tty.Config = null; - var single_threaded_io: Io.Threaded = .init_single_threaded; }; - const io = global.single_threaded_io.io(); const w = std.Progress.lockStderrWriter(buffer); + const file_writer: *File.Writer = @fieldParentPtr("interface", w); // The stderr lock also locks access to `global.conf`. if (global.conf == null) { - global.conf = .detect(io, .stderr()); + global.conf = .detect(file_writer.io, .stderr()); } return .{ w, global.conf.? }; } @@ -619,13 +618,15 @@ pub const StackUnwindOptions = struct { /// /// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it. pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); const empty_trace: StackTrace = .{ .index = 0, .instruction_addresses = &.{} }; if (!std.options.allow_stack_tracing) return empty_trace; var it: StackIterator = .init(options.context); defer it.deinit(); if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace; + + var threaded: Io.Threaded = .init_single_threaded; + const io = threaded.ioBasic(); + var total_frames: usize = 0; var index: usize = 0; var wait_for = options.first_address; -- 2.54.0 From 5b436d2c5125b9cf9a08b3bff0dcb0248c4d1ec0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 23:03:34 -0800 Subject: [PATCH 034/195] build_runner compiling again --- lib/std/Io/Dir.zig | 81 ++++++++------------------ lib/std/Io/File.zig | 17 +----- lib/std/Io/File/Writer.zig | 25 ++++---- lib/std/Io/Threaded.zig | 116 +++++++++++++++++++------------------ lib/std/Io/net.zig | 4 +- lib/std/fs/test.zig | 12 ++-- src/Package/Fetch.zig | 4 +- 7 files changed, 111 insertions(+), 148 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 6c40e0217e6b6aed8646953653fb3e7c5fb11fa5..9c4a1f1df8d6ab47866e8e0a7e498d7bf3579739 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -198,7 +198,7 @@ pub const SelectiveWalker = struct { // likely just fail with the same error. var item = self.stack.pop().?; if (self.stack.items.len != 0) { - item.iter.dir.close(io); + item.iter.reader.dir.close(io); } return err; }) |entry| { @@ -211,7 +211,7 @@ pub const SelectiveWalker = struct { self.name_buffer.appendSliceAssumeCapacity(entry.name); self.name_buffer.appendAssumeCapacity(0); const walker_entry: Walker.Entry = .{ - .dir = top.iter.dir, + .dir = top.iter.reader.dir, .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0], .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0], .kind = entry.kind, @@ -220,7 +220,7 @@ pub const SelectiveWalker = struct { } else { var item = self.stack.pop().?; if (self.stack.items.len != 0) { - item.iter.dir.close(io); + item.iter.reader.dir.close(io); } } } @@ -260,7 +260,7 @@ pub const SelectiveWalker = struct { var item = self.stack.pop().?; if (self.stack.items.len != 0) { @branchHint(.likely); - item.iter.dir.close(io); + item.iter.reader.dir.close(io); } } }; @@ -741,7 +741,6 @@ pub const RealPathError = error{ FileNotFound, AccessDenied, PermissionDenied, - NameTooLong, NotSupported, NotDir, SymLinkLoop, @@ -757,9 +756,6 @@ pub const RealPathError = error{ DeviceBusy, SharingViolation, PipeBusy, - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, /// On Windows, `\\server` or `\\server\share` was not found. NetworkNotFound, PathAlreadyExists, @@ -772,7 +768,7 @@ pub const RealPathError = error{ /// On Windows, the volume does not contain a recognized file system. File /// system drivers might not be loaded, or the volume may be corrupt. UnrecognizedVolume, -} || Io.Cancelable || Io.UnexpectedError; +} || PathNameError || Io.Cancelable || Io.UnexpectedError; /// This function returns the canonicalized absolute pathname of `pathname` /// relative to this `Dir`. If `pathname` is absolute, ignores this `Dir` @@ -824,19 +820,12 @@ pub const DeleteFileError = error{ FileSystem, IsDir, SymLinkLoop, - NameTooLong, NotDir, SystemResources, ReadOnlyFileSystem, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - /// Windows: file paths cannot contain these characters: - /// '/', '*', '?', '"', '<', '>', '|' - BadPathName, /// On Windows, `\\server` or `\\server\share` was not found. NetworkNotFound, -} || Io.Cancelable || Io.UnexpectedError; +} || PathNameError || Io.Cancelable || Io.UnexpectedError; /// Delete a file name and possibly the file it refers to, based on an open directory handle. /// @@ -864,17 +853,12 @@ pub const DeleteDirError = error{ FileBusy, FileSystem, SymLinkLoop, - NameTooLong, NotDir, SystemResources, ReadOnlyFileSystem, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, /// On Windows, `\\server` or `\\server\share` was not found. NetworkNotFound, -} || Io.Cancelable || Io.UnexpectedError; +} || PathNameError || Io.Cancelable || Io.UnexpectedError; /// Returns `error.DirNotEmpty` if the directory is not empty. /// @@ -910,7 +894,6 @@ pub const RenameError = error{ IsDir, SymLinkLoop, LinkQuotaExceeded, - NameTooLong, FileNotFound, NotDir, SystemResources, @@ -918,10 +901,6 @@ pub const RenameError = error{ PathAlreadyExists, ReadOnlyFileSystem, RenameAcrossMountPoints, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, NoDevice, SharingViolation, PipeBusy, @@ -933,7 +912,7 @@ pub const RenameError = error{ /// intercepts file system operations and makes them significantly slower /// in addition to possibly failing with this error code. AntivirusInterference, -} || Io.Cancelable || Io.UnexpectedError; +} || PathNameError || Io.Cancelable || Io.UnexpectedError; /// Change the name or location of a file or directory. /// @@ -985,12 +964,7 @@ pub const SymLinkError = error{ NoSpaceLeft, ReadOnlyFileSystem, NotDir, - NameTooLong, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, -} || Io.Cancelable || Io.UnexpectedError; +} || PathNameError || Io.Cancelable || Io.UnexpectedError; /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. /// @@ -1075,15 +1049,10 @@ pub const ReadLinkError = error{ PermissionDenied, FileSystem, SymLinkLoop, - NameTooLong, FileNotFound, SystemResources, NotLink, NotDir, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, /// Windows-only. This error may occur if the opened reparse point is /// of unsupported type. UnsupportedReparsePointType, @@ -1095,7 +1064,7 @@ pub const ReadLinkError = error{ /// intercepts file system operations and makes them significantly slower /// in addition to possibly failing with this error code. AntivirusInterference, -} || Io.Cancelable || Io.UnexpectedError; +} || PathNameError || Io.Cancelable || Io.UnexpectedError; /// Obtain target of a symbolic link. /// @@ -1183,7 +1152,6 @@ pub const DeleteTreeError = error{ FileTooBig, SymLinkLoop, ProcessFdQuotaExceeded, - NameTooLong, SystemFdQuotaExceeded, NoDevice, SystemResources, @@ -1194,15 +1162,9 @@ pub const DeleteTreeError = error{ /// One of the path components was not a directory. /// This error is unreachable if `sub_path` does not contain a path separator. NotDir, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - /// On Windows, file paths cannot contain these characters: - /// '/', '*', '?', '"', '<', '>', '|' - BadPathName, /// On Windows, `\\server` or `\\server\share` was not found. NetworkNotFound, -} || Io.Cancelable || Io.UnexpectedError; +} || PathNameError || Io.Cancelable || Io.UnexpectedError; /// Whether `sub_path` describes a symlink, file, or directory, this function /// removes it. If it cannot be removed because it is a non-empty directory, @@ -1222,7 +1184,7 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { iter: Dir.Iterator, fn closeAll(inner_io: Io, items: []@This()) void { - for (items) |*item| item.iter.dir.close(inner_io); + for (items) |*item| item.iter.reader.dir.close(inner_io); } }; @@ -1243,7 +1205,7 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { handle_entry: while (true) { if (treat_as_dir) { if (stack.unusedCapacitySlice().len >= 1) { - var iterable_dir = top.iter.dir.openDir(io, entry.name, .{ + var iterable_dir = top.iter.reader.dir.openDir(io, entry.name, .{ .follow_symlinks = false, .iterate = true, }) catch |err| switch (err) { @@ -1273,16 +1235,16 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { }; stack.appendAssumeCapacity(.{ .name = entry.name, - .parent_dir = top.iter.dir, + .parent_dir = top.iter.reader.dir, .iter = iterable_dir.iterateAssumeFirstIteration(), }); continue :process_stack; } else { - try top.iter.dir.deleteTreeMinStackSizeWithKindHint(io, entry.name, entry.kind); + try top.iter.reader.dir.deleteTreeMinStackSizeWithKindHint(io, entry.name, entry.kind); break :handle_entry; } } else { - if (top.iter.dir.deleteFile(io, entry.name)) { + if (top.iter.reader.dir.deleteFile(io, entry.name)) { break :handle_entry; } else |err| switch (err) { error.FileNotFound => break :handle_entry, @@ -1305,6 +1267,7 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { error.FileBusy, error.BadPathName, error.NetworkNotFound, + error.Canceled, error.Unexpected, => |e| return e, } @@ -1314,7 +1277,7 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { // On Windows, we can't delete until the dir's handle has been closed, so // close it before we try to delete. - top.iter.dir.close(io); + top.iter.reader.dir.close(io); // In order to avoid double-closing the directory when cleaning up // the stack in the case of an error, we save the relevant portions and @@ -1324,7 +1287,7 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { stack.items.len -= 1; var need_to_retry: bool = false; - parent_dir.deleteDir(name) catch |err| switch (err) { + parent_dir.deleteDir(io, name) catch |err| switch (err) { error.FileNotFound => {}, error.DirNotEmpty => need_to_retry = true, else => |e| return e, @@ -1389,6 +1352,7 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { error.FileBusy, error.BadPathName, error.NetworkNotFound, + error.Canceled, error.Unexpected, => |e| return e, } @@ -1437,7 +1401,7 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, scan_dir: while (true) { var dir_it = dir.iterateAssumeFirstIteration(); - dir_it: while (try dir_it.next()) |entry| { + dir_it: while (try dir_it.next(io)) |entry| { var treat_as_dir = entry.kind == .directory; handle_entry: while (true) { if (treat_as_dir) { @@ -1500,6 +1464,7 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, error.FileBusy, error.BadPathName, error.NetworkNotFound, + error.Canceled, error.Unexpected, => |e| return e, } @@ -1727,7 +1692,7 @@ pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerEr return io.vtable.dirSetOwner(io.userdata, dir, owner, group); } -pub const SetTimestampsError = File.SetTimestampsError; +pub const SetTimestampsError = File.SetTimestampsError || PathNameError; pub const SetTimestampsOptions = struct { follow_symlinks: bool = true, diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index ead129e3c50a24bf73fa322cd776ac3f605088ff..7718b56f8afab25c67b3f32fe55827b5fb05d1a1 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -284,7 +284,9 @@ pub fn isTty(file: File, io: Io) bool { return io.vtable.fileIsTty(io.userdata, file); } -pub const EnableAnsiEscapeCodesError = error{} || Io.Cancelable || Io.UnexpectedError; +pub const EnableAnsiEscapeCodesError = error{ + NotTerminalDevice, +} || Io.Cancelable || Io.UnexpectedError; pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError!void { return io.vtable.fileEnableAnsiEscapeCodes(io.userdata, file); @@ -492,19 +494,6 @@ pub fn writePositional(file: File, io: Io, buffer: []const []const u8, offset: u return io.vtable.fileWritePositional(io.userdata, file, buffer, offset); } -pub const WriteFileStreamingError = error{ - /// `out_fd` is an unconnected socket, or out_fd closed its read end. - BrokenPipe, - /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd. - UnsupportedOperation, - /// Nonblocking I/O has been selected but the write would block. - WouldBlock, - /// Unspecified error while reading from in_fd. - InputOutput, - /// Insufficient kernel memory to read from in_fd. - SystemResources, -} || Io.Cancelable || Io.UnexpectedError; - pub const SeekError = error{ Unseekable, /// The file descriptor does not hold the required rights to seek on it. diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index cc971edbf44753edf9b081cbdbbcc002619e0e69..ec58824c06b062c64f4b588259f7d315bb728ea7 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -28,7 +28,9 @@ pub const Error = error{ /// File descriptor does not hold the required rights to write to it. AccessDenied, PermissionDenied, + /// File is an unconnected socket, or closed its read end. BrokenPipe, + /// Insufficient kernel memory to read from in_fd. SystemResources, NotOpenForWriting, /// The process cannot access the file because another process has locked @@ -39,20 +41,15 @@ pub const Error = error{ /// This error occurs when a device gets disconnected before or mid-flush /// while it's being written to - errno(6): No such device or address. NoDevice, + FileBusy, } || Io.Cancelable || Io.UnexpectedError; -pub const WriteFileError = error{ - /// `out_fd` is an unconnected socket, or out_fd closed its read end. - BrokenPipe, +pub const WriteFileError = Error || error{ /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd. - UnsupportedOperation, - /// Nonblocking I/O has been selected but the write would block. - WouldBlock, - /// Unspecified error while reading from in_fd. - InputOutput, - /// Insufficient kernel memory to read from in_fd. - SystemResources, -} || Io.Cancelable || Io.UnexpectedError; + Unimplemented, + EndOfStream, + ReadFailed, +}; pub const SeekError = Io.File.SeekError; @@ -175,6 +172,9 @@ fn sendFilePositional(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) w.err = error.Canceled; return error.WriteFailed; }, + error.EndOfStream => return error.EndOfStream, + error.Unimplemented => return error.Unimplemented, + error.ReadFailed => return error.ReadFailed, else => |e| { w.write_file_err = e; return error.WriteFailed; @@ -192,6 +192,9 @@ fn sendFileStreaming(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) w.err = error.Canceled; return error.WriteFailed; }, + error.EndOfStream => return error.EndOfStream, + error.Unimplemented => return error.Unimplemented, + error.ReadFailed => return error.ReadFailed, else => |e| { w.write_file_err = e; return error.WriteFailed; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 459440f5a6da9b3a81b4ea3ffb90fb5de4d11c85..639e5cf6cd3cb6a49cead87895fc71501071c8c8 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -696,6 +696,7 @@ pub fn io(t: *Threaded) Io { .dirOpenFile = dirOpenFile, .dirOpenDir = dirOpenDir, .dirClose = dirClose, + .dirRead = dirRead, .dirRealPath = dirRealPath, .dirDeleteFile = dirDeleteFile, .dirDeleteDir = dirDeleteDir, @@ -822,6 +823,7 @@ pub fn ioBasic(t: *Threaded) Io { .dirOpenFile = dirOpenFile, .dirOpenDir = dirOpenDir, .dirClose = dirClose, + .dirRead = dirRead, .dirRealPath = dirRealPath, .dirDeleteFile = dirDeleteFile, .dirDeleteDir = dirDeleteDir, @@ -1621,7 +1623,7 @@ fn dirMakeOpenPathPosix( ) Dir.MakeOpenPathError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); const t_io = ioBasic(t); - return dirOpenDirPosix(t, dir, sub_path, permissions, options) catch |err| switch (err) { + return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) { error.FileNotFound => { _ = try dir.makePathStatus(t_io, sub_path, permissions); return dirOpenDirPosix(t, dir, sub_path, options); @@ -2388,7 +2390,7 @@ fn dirCreateFilePosix( try current_thread.beginSyscall(); const fd: posix.fd_t = while (true) { - const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.mode); + const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.permissions.toMode()); switch (posix.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); @@ -3256,6 +3258,14 @@ fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { for (dirs) |dir| posix.close(dir.handle); } +fn dirRead(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + _ = dir_reader; + _ = buffer; + @panic("TODO"); +} + const dirRealPath = switch (native_os) { .windows => dirRealPathWindows, else => dirRealPathPosix, @@ -3412,7 +3422,6 @@ fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_b switch (e) { .ACCES => return error.AccessDenied, .FAULT => |err| return errnoBug(err), - .INVAL => return error.NotLink, .IO => return error.FileSystem, .LOOP => return error.SymLinkLoop, .NAMETOOLONG => return error.NameTooLong, @@ -4562,7 +4571,7 @@ fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void { const current_thread = Thread.getCurrent(t); try current_thread.beginSyscall(); while (true) { - switch (posix.system.fsync(file.handle)) { + switch (posix.errno(posix.system.fsync(file.handle))) { .SUCCESS => return current_thread.endSyscall(), .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { @@ -4640,7 +4649,7 @@ fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool { const linux = std.os.linux; try current_thread.beginSyscall(); while (true) { - var wsz: linux.winsize = undefined; + var wsz: posix.winsize = undefined; const fd: usize = @bitCast(@as(isize, file.handle)); const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz)); switch (linux.errno(rc)) { @@ -4948,7 +4957,7 @@ fn dirSetTimestamps( last_accessed: Io.Timestamp, last_modified: Io.Timestamp, options: Dir.SetTimestampsOptions, -) File.SetTimestampsError!void { +) Dir.SetTimestampsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -4961,8 +4970,8 @@ fn dirSetTimestamps( } const times: [2]posix.timespec = .{ - timestampToPosix(last_accessed), - timestampToPosix(last_modified), + timestampToPosix(last_accessed.nanoseconds), + timestampToPosix(last_modified.nanoseconds), }; const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; @@ -5000,7 +5009,7 @@ fn dirSetTimestampsNow( dir: Dir, sub_path: []const u8, options: Dir.SetTimestampsOptions, -) File.SetTimestampsError!void { +) Dir.SetTimestampsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5068,8 +5077,8 @@ fn fileSetTimestamps( } const times: [2]posix.timespec = .{ - timestampToPosix(last_accessed), - timestampToPosix(last_modified), + timestampToPosix(last_accessed.nanoseconds), + timestampToPosix(last_modified.nanoseconds), }; if (native_os == .wasi and !builtin.link_libc) { @@ -5226,7 +5235,7 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v } } - const operation = switch (lock) { + const operation: i32 = switch (lock) { .none => posix.LOCK.UN, .shared => posix.LOCK.SH, .exclusive => posix.LOCK.EX, @@ -5288,7 +5297,7 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro } } - const operation = switch (lock) { + const operation: i32 = switch (lock) { .none => posix.LOCK.UN, .shared => posix.LOCK.SH | posix.LOCK.NB, .exclusive => posix.LOCK.EX | posix.LOCK.NB, @@ -5346,27 +5355,16 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void { return; } - try current_thread.beginSyscall(); while (true) { switch (posix.errno(posix.system.flock(file.handle, posix.LOCK.UN))) { - .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), - .INTR => { - try current_thread.checkCancel(); - continue; - }, - else => |e| { - current_thread.endSyscall(); - if (is_debug) switch (e) { - .AGAIN => unreachable, // unlocking can't block - .BADF => unreachable, // File descriptor used after closed. - .INVAL => unreachable, // invalid parameters - .NOLCK => unreachable, // Resource deallocation. - .OPNOTSUPP => unreachable, // We already got the lock. - else => unreachable, // Resource deallocation must succeed. - }; - return; - }, + .SUCCESS => return, + .CANCELED, .INTR => continue, + .AGAIN => return assert(!is_debug), // unlocking can't block + .BADF => return assert(!is_debug), // File descriptor used after closed. + .INVAL => return assert(!is_debug), // invalid parameters + .NOLCK => return assert(!is_debug), // Resource deallocation. + .OPNOTSUPP => return assert(!is_debug), // We already got the lock. + else => return assert(!is_debug), // Resource deallocation must succeed. } } } @@ -5424,7 +5422,7 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError! switch (posix.errno(posix.system.flock(file.handle, operation))) { .SUCCESS => { current_thread.endSyscall(); - return true; + return; }, .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { @@ -6255,8 +6253,9 @@ fn fileWritePositional( .NOSPC => return error.NoSpaceLeft, .PERM => return error.PermissionDenied, .PIPE => return error.BrokenPipe, - .CONNRESET => return error.ConnectionResetByPeer, + .CONNRESET => |err| return errnoBug(err), // Not a socket handle. .BUSY => return error.DeviceBusy, + .TXTBSY => return error.FileBusy, .NXIO => return error.Unseekable, .SPIPE => return error.Unseekable, .OVERFLOW => return error.Unseekable, @@ -6373,7 +6372,7 @@ fn fileWriteStreaming( .NOSPC => return error.NoSpaceLeft, .PERM => return error.PermissionDenied, .PIPE => return error.BrokenPipe, - .CONNRESET => return error.ConnectionResetByPeer, + .CONNRESET => |err| return errnoBug(err), // Not a socket handle. .BUSY => return error.DeviceBusy, else => |err| return posix.unexpectedErrno(err), } @@ -6388,12 +6387,12 @@ fn fileWriteFileStreaming( header: []const u8, file_reader: *File.Reader, limit: Io.Limit, -) File.WriteFileStreamingError!usize { +) File.Writer.WriteFileError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); const reader_buffered = file_reader.interface.buffered(); if (reader_buffered.len >= @intFromEnum(limit)) { const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); - file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + file_reader.interface.toss(n -| header.len); return n; } const file_limit = @intFromEnum(limit) - reader_buffered.len; @@ -6404,7 +6403,7 @@ fn fileWriteFileStreaming( if (size - file_reader.pos == 0) { if (reader_buffered.len != 0) { const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); - file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + file_reader.interface.toss(n -| header.len); return n; } else { return error.EndOfStream; @@ -6495,7 +6494,7 @@ fn fileWriteFileStreaming( return error.EndOfStream; } const ubytes: usize = @intCast(sbytes); - file_reader.seekBy(ubytes -| header.len) catch return error.ReadFailed; + file_reader.interface.toss(ubytes -| header.len); return ubytes; } @@ -6581,7 +6580,7 @@ fn fileWriteFileStreaming( return error.EndOfStream; } const u_len: usize = @bitCast(len); - file_reader.seekBy(u_len -| header.len) catch return error.ReadFailed; + file_reader.interface.toss(u_len -| header.len); return u_len; } @@ -6591,7 +6590,7 @@ fn fileWriteFileStreaming( // Linux sendfile does not support headers. if (header.len != 0 or reader_buffered.len != 0) { const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); - file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + file_reader.interface.toss(n -| header.len); return n; } const max_count = 0x7ffff000; // Avoid EINVAL. @@ -6671,7 +6670,7 @@ fn fileWriteFileStreaming( if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; if (header.len != 0 or reader_buffered.len != 0) { const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); - file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + file_reader.interface.toss(n -| header.len); return n; } var off_in: i64 = undefined; @@ -6682,7 +6681,7 @@ fn fileWriteFileStreaming( break :p &off_in; }, .streaming => null, - .failure => return error.WriteFailed, + .failure => return error.ReadFailed, }; const current_thread = Thread.getCurrent(t); const n: usize = switch (native_os) { @@ -6712,13 +6711,16 @@ fn fileWriteFileStreaming( assert(error.Unexpected == switch (e) { .FBIG => return error.FileTooBig, .IO => return error.InputOutput, - .ISDIR => return error.IsDir, .NOMEM => return error.SystemResources, .NOSPC => return error.NoSpaceLeft, - .OVERFLOW => return error.Overflow, + .OVERFLOW => |err| errnoBug(err), // We avoid passing too large a count. .PERM => return error.PermissionDenied, - .TXTBSY => return error.SwapFile, - .XDEV => return error.NotSameFileSystem, + .BUSY => return error.DeviceBusy, + .TXTBSY => return error.FileBusy, + // copy_file_range can still work but not on + // this pair of file descriptors. + .XDEV => return error.Unimplemented, + .ISDIR => |err| errnoBug(err), .BADF => |err| errnoBug(err), else => |err| posix.unexpectedErrno(err), }); @@ -6791,7 +6793,7 @@ fn netWriteFile( _ = header; _ = file_reader; _ = limit; - return error.Unimplemented; // TODO + @panic("TODO"); } fn netWriteFileUnavailable( @@ -6822,7 +6824,7 @@ fn fileWriteFilePositional( const reader_buffered = file_reader.interface.buffered(); if (reader_buffered.len >= @intFromEnum(limit)) { const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); - file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + file_reader.interface.toss(n -| header.len); return n; } const out_fd = file.handle; @@ -6832,7 +6834,7 @@ fn fileWriteFilePositional( if (size - file_reader.pos == 0) { if (reader_buffered.len != 0) { const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); - file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + file_reader.interface.toss(n -| header.len); return n; } else { return error.EndOfStream; @@ -6844,7 +6846,7 @@ fn fileWriteFilePositional( if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; if (header.len != 0 or reader_buffered.len != 0) { const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); - file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + file_reader.interface.toss(n -| header.len); return n; } var off_in: i64 = undefined; @@ -6855,7 +6857,7 @@ fn fileWriteFilePositional( break :p &off_in; }, .streaming => null, - .failure => return error.WriteFailed, + .failure => return error.ReadFailed, }; var off_out: i64 = @intCast(offset); const current_thread = Thread.getCurrent(t); @@ -6886,15 +6888,17 @@ fn fileWriteFilePositional( assert(error.Unexpected == switch (e) { .FBIG => return error.FileTooBig, .IO => return error.InputOutput, - .ISDIR => return error.IsDir, .NOMEM => return error.SystemResources, .NOSPC => return error.NoSpaceLeft, .OVERFLOW => return error.Unseekable, .NXIO => return error.Unseekable, .SPIPE => return error.Unseekable, .PERM => return error.PermissionDenied, - .TXTBSY => return error.SwapFile, - .XDEV => return error.NotSameFileSystem, + .TXTBSY => return error.FileBusy, + // copy_file_range can still work but not on + // this pair of file descriptors. + .XDEV => return error.Unimplemented, + .ISDIR => |err| errnoBug(err), .BADF => |err| errnoBug(err), else => |err| posix.unexpectedErrno(err), }); @@ -6962,7 +6966,7 @@ fn fileWriteFilePositional( const size = file_reader.getSize() catch break :fcf; if (header.len != 0 or reader_buffered.len != 0) { const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); - file_reader.seekBy(n -| header.len) catch return error.ReadFailed; + file_reader.interface.toss(n -| header.len); return n; } const current_thread = Thread.getCurrent(t); diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 8b8728b6f107cab5dc98d56e336abb9504431716..8b1523fbd3068410cff2e0be4dff83cae0de6f82 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -1290,7 +1290,9 @@ pub const Stream = struct { SocketNotBound, } || Io.UnexpectedError || Io.Cancelable; - pub const WriteFileError = error{} || Io.Cancelable || Io.UnexpectedError; + pub const WriteFileError = error{ + NetworkDown, + } || Io.Cancelable || Io.UnexpectedError; pub fn init(stream: Stream, io: Io, buffer: []u8) Writer { return .{ diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index da0a0cff79fd9e6c38bdd6d4d392465e39864662..3a4b551ae8dfbe9b518a28bd85ae6f061810fcbe 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -844,7 +844,7 @@ test "directory operations on files" { try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(io, test_file_name, .default_dir)); try testing.expectError(error.NotDir, ctx.dir.openDir(io, test_file_name, .{})); - try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name)); + try testing.expectError(error.NotDir, ctx.dir.deleteDir(io, test_file_name)); if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name)); @@ -934,16 +934,16 @@ test "deleteDir" { const test_file_path = try ctx.transformPath("test_dir" ++ fs.path.sep_str ++ "test_file"); // deleting a non-existent directory - try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(test_dir_path)); + try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(io, test_dir_path)); // deleting a non-empty directory try ctx.dir.makeDir(io, test_dir_path, .default_dir); try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); - try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path)); + try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(io, test_dir_path)); // deleting an empty directory try ctx.dir.deleteFile(io, test_file_path); - try ctx.dir.deleteDir(test_dir_path); + try ctx.dir.deleteDir(io, test_dir_path); } }.impl); } @@ -2062,7 +2062,7 @@ test "'.' and '..' in Io.Dir functions" { const prev_status = try dir.updateFile(io, file_path, dir, update_path, .{}); try testing.expectEqual(Io.Dir.PrevStatus.stale, prev_status); - try ctx.dir.deleteDir(subdir_path); + try ctx.dir.deleteDir(io, subdir_path); } }.impl); } @@ -2174,7 +2174,7 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path)); - try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path)); + try testing.expectError(expected_err, ctx.dir.deleteDir(io, invalid_path)); try testing.expectError(expected_err, ctx.dir.rename(invalid_path, ctx.dir, invalid_path, io)); diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index e9753734e91c70b66507decd8006b95609efae30..137d7635e7001d45692ac2d7112b2abb2a4a3da4 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -578,7 +578,7 @@ fn runResource( }; // Remove temporary directory root if not already renamed to global cache. if (!std.mem.eql(u8, package_sub_path, tmp_dir_sub_path)) { - cache_root.handle.deleteDir(tmp_dir_sub_path) catch {}; + cache_root.handle.deleteDir(io, tmp_dir_sub_path) catch {}; } // Validate the computed hash against the expected hash. If invalid, this @@ -1593,7 +1593,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute var i: usize = 0; while (i < sus_dirs.count()) : (i += 1) { const sus_dir = sus_dirs.keys()[i]; - root_dir.deleteDir(sus_dir) catch |err| switch (err) { + root_dir.deleteDir(io, sus_dir) catch |err| switch (err) { error.DirNotEmpty => continue, error.FileNotFound => continue, else => |e| { -- 2.54.0 From 03526c59d4e2a00f83347cf06c741a3ed4fec520 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 Dec 2025 23:14:34 -0800 Subject: [PATCH 035/195] std.debug: fix printLineFromFile --- lib/compiler/test_runner.zig | 4 ++-- lib/std/Io/File.zig | 2 +- lib/std/debug.zig | 10 ++++------ lib/std/testing.zig | 6 +++--- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 07a6724ec090bb65307eb584d09b7945c2bad7b4..b0ef17a7e9e541385fbd1e4fd407c46cda17139d 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -75,7 +75,7 @@ pub fn main() void { fn mainServer() !void { @disableInstrumentation(); var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io.io(), &stdin_buffer); - var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io.io(), &stdout_buffer); var server = try std.zig.Server.init(.{ .in = &stdin_reader.interface, .out = &stdout_writer.interface, @@ -228,7 +228,7 @@ fn mainTerminal() void { .root_name = "Test", .estimated_total_items = test_fn_list.len, }); - const have_tty = Io.File.stderr().isTty(); + const have_tty = Io.File.stderr().isTty(runner_threaded_io.io()) catch unreachable; var leaks: usize = 0; for (test_fn_list, 0..) |test_fn, i| { diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 7718b56f8afab25c67b3f32fe55827b5fb05d1a1..ccf18a8f566f365bd6cc1222dedfc26f4c8a147a 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -280,7 +280,7 @@ pub fn sync(file: File, io: Io) SyncError!void { /// See also: /// * `enableAnsiEscapeCodes` /// * `supportsAnsiEscapeCodes`. -pub fn isTty(file: File, io: Io) bool { +pub fn isTty(file: File, io: Io) Io.Cancelable!bool { return io.vtable.fileIsTty(io.userdata, file); } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 6ffa2542718e3bca0670d7005bcd72e20aa73952..7b111215f3d25bc8f997b6b3c55c451e23e9eb9e 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1207,9 +1207,8 @@ fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) ! var buffer: [4096]u8 = undefined; var file_reader: File.Reader = .init(file, io, &buffer); - const r = &file_reader.interface; var line_index: usize = 0; - while (r.takeDelimiterExclusive('\n')) |line| { + while (try file_reader.interface.takeDelimiter('\n')) |line| { line_index += 1; if (line_index == source_location.line) { // TODO delete hard tabs from the language @@ -1219,9 +1218,8 @@ fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) ! try writer.writeByte('\n'); return; } - } else |err| { - return err; } + return error.EndOfStream; } test printLineFromFile { @@ -1248,7 +1246,7 @@ test printLineFromFile { defer gpa.free(path); try test_dir.dir.writeFile(io, .{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); - try expectError(error.EndOfFile, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 })); + try expectError(error.EndOfStream, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 })); try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written()); @@ -1317,7 +1315,7 @@ test printLineFromFile { const writer = &file_writer.interface; try writer.splatByteAll('a', 3 * std.heap.page_size_max); - try expectError(error.EndOfFile, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 })); + try expectError(error.EndOfStream, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 })); try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written()); diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 9eb2ad5e96ae5fc4e5f3169a5080406c28ea897d..ab9f1f73c31baf9c8b47d26cffe82ef55c95e791 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -629,12 +629,12 @@ pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir { _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); const cwd = Io.Dir.cwd(); - var cache_dir = cwd.makeOpenPath(".zig-cache", .{}) catch + var cache_dir = cwd.makeOpenPath(io, ".zig-cache", .{}) catch @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); defer cache_dir.close(io); - const parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch + const parent_dir = cache_dir.makeOpenPath(io, "tmp", .{}) catch @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); - const dir = parent_dir.makeOpenPath(&sub_path, opts) catch + const dir = parent_dir.makeOpenPath(io, &sub_path, .{ .open_options = opts }) catch @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); return .{ -- 2.54.0 From 78d262d96ee6200c7a6bc0a41fe536d263c24d92 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 9 Dec 2025 18:44:39 -0800 Subject: [PATCH 036/195] std: WIP: debug-level stderr writing --- lib/std/Io.zig | 31 +++++++++ lib/std/Io/Dir.zig | 5 +- lib/std/Io/Threaded.zig | 32 +++++++++ lib/std/Progress.zig | 149 ++++++++++++++-------------------------- lib/std/debug.zig | 73 +++++++++++++------- lib/std/log.zig | 16 ++++- 6 files changed, 181 insertions(+), 125 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 193998f0371a6d012eedc7909c4302b0fc1d608a..833cc4ec0f364b58573244a195a99aa5f4b18f70 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -560,6 +560,13 @@ pub const net = @import("Io/net.zig"); userdata: ?*anyopaque, vtable: *const VTable, +/// This is the global, process-wide protection to coordinate stderr writes. +/// +/// The primary motivation for recursive mutex here is so that a panic while +/// stderr mutex is held still dumps the stack trace and other debug +/// information. +pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; + pub const VTable = struct { /// If it returns `null` it means `result` has been already populated and /// `await` will be a no-op. @@ -733,6 +740,10 @@ pub const VTable = struct { netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void, + + lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*Writer, + tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*Writer, + unlockStderrWriter: *const fn (?*anyopaque) void, }; pub const Cancelable = error{ @@ -2167,3 +2178,23 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) { else => unreachable, } } + +/// For doing application-level writes to the standard error stream. +/// Coordinates also with debug-level writes that are ignorant of Io interface +/// and implementations. When this returns, `stderr_thread_mutex` will be +/// locked. +/// +/// See also: +/// * `tryLockStderrWriter` +pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*Writer { + return io.vtable.lockStderrWriter(io.userdata, buffer); +} + +/// Same as `lockStderrWriter` but uncancelable and non-blocking. +pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*Writer { + return io.vtable.tryLockStderrWriter(io.userdata, buffer); +} + +pub fn unlockStderrWriter(io: Io) void { + return io.vtable.unlockStderrWriter(io.userdata); +} diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 9c4a1f1df8d6ab47866e8e0a7e498d7bf3579739..a9d28440eb1e1b33581e7d3910e6db542718b059 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -342,7 +342,7 @@ pub const Walker = struct { /// Recursively iterates over a directory. /// -/// `dir` must have been opened with `OpenOptions{.iterate = true}`. +/// `dir` must have been opened with `OpenOptions.iterate` set to `true`. /// /// `Walker.deinit` releases allocated memory and directory handles. /// @@ -350,7 +350,8 @@ pub const Walker = struct { /// /// `dir` will not be closed after walking it. /// -/// See also `walkSelectively`. +/// See also: +/// * `walkSelectively` pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker { return .{ .inner = try walkSelectively(dir, allocator) }; } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 639e5cf6cd3cb6a49cead87895fc71501071c8c8..9d6d9f979eeda1e47d9c9e47915b32f157254702 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -77,6 +77,8 @@ use_sendfile: UseSendfile = .default, use_copy_file_range: UseCopyFileRange = .default, use_fcopyfile: UseFcopyfile = .default, +stderr_writer: Io.Writer, + pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { enabled, disabled, @@ -9514,6 +9516,36 @@ fn netLookupFallible( return error.OptionUnsupported; } +fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*Io.Writer { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + // Only global mutex since this is Threaded. + Io.stderr_thread_mutex.lock(); + if (is_windows) t.stderr_writer.file = .stderr(); + std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; + t.stderr_writer.flush() catch {}; + t.stderr_writer.buffer = buffer; + return &t.stderr_writer; +} + +fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*Io.Writer { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + // Only global mutex since this is Threaded. + if (!Io.stderr_thread_mutex.tryLock()) return null; + std.Progress.clearWrittenWithEscapeCodes(t.io()) catch {}; + if (is_windows) t.stderr_writer.file = .stderr(); + t.stderr_writer.flush() catch {}; + t.stderr_writer.buffer = buffer; + return &t.stderr_writer; +} + +fn unlockStderrWriter(userdata: ?*anyopaque) void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + t.stderr_writer.flush() catch {}; + t.stderr_writer.end = 0; + t.stderr_writer.buffer = &.{}; + Io.stderr_thread_mutex.unlock(); +} + pub const PosixAddress = extern union { any: posix.sockaddr, in: posix.sockaddr.in, diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index a839de8c4f27c6a2a6c911041810a04c63b1eeb1..5528591620961a0985b9264c7e4c9d03c084dfdb 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -13,16 +13,18 @@ const assert = std.debug.assert; const posix = std.posix; const Writer = std.Io.Writer; -/// `null` if the current node (and its children) should -/// not print on update() +/// Currently this API only supports this value being set to stderr, which +/// happens automatically inside `start`. terminal: Io.File, +io: Io, + terminal_mode: TerminalMode, -update_thread: ?std.Thread, +update_worker: ?Io.Future(void), /// Atomically set by SIGWINCH as well as the root done() function. -redraw_event: std.Thread.ResetEvent, +redraw_event: Io.ResetEvent, /// Indicates a request to shut down and reset global state. /// Accessed atomically. done: bool, @@ -95,9 +97,9 @@ pub const Options = struct { /// Must be at least 200 bytes. draw_buffer: []u8 = &default_draw_buffer, /// How many nanoseconds between writing updates to the terminal. - refresh_rate_ns: u64 = 80 * std.time.ns_per_ms, + refresh_rate_ns: Io.Duration = .fromMilliseconds(80), /// How many nanoseconds to keep the output hidden - initial_delay_ns: u64 = 200 * std.time.ns_per_ms, + initial_delay_ns: Io.Duration = .fromMilliseconds(200), /// If provided, causes the progress item to have a denominator. /// 0 means unknown. estimated_total_items: usize = 0, @@ -330,7 +332,7 @@ pub const Node = struct { } else { @atomicStore(bool, &global_progress.done, true, .monotonic); global_progress.redraw_event.set(); - if (global_progress.update_thread) |thread| thread.join(); + if (global_progress.update_worker) |worker| worker.await(global_progress.io); } } @@ -391,9 +393,10 @@ pub const Node = struct { }; var global_progress: Progress = .{ + .io = undefined, .terminal = undefined, .terminal_mode = .off, - .update_thread = null, + .update_worker = null, .redraw_event = .unset, .refresh_rate_ns = undefined, .initial_delay_ns = undefined, @@ -403,6 +406,7 @@ var global_progress: Progress = .{ .done = false, .need_clear = false, .status = .working, + .start_failure = .unstarted, .node_parents = &node_parents_buffer, .node_storage = &node_storage_buffer, @@ -411,6 +415,13 @@ var global_progress: Progress = .{ .node_end_index = 0, }; +pub const StartFailure = union(enum) { + unstarted, + spawn_ipc_worker: error{ConcurrencyUnavailable}, + spawn_update_worker: error{ConcurrencyUnavailable}, + parse_env_var: error{}, +}; + const node_storage_buffer_len = 83; var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined; var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined; @@ -437,7 +448,7 @@ const noop_impl = builtin.single_threaded or switch (builtin.os.tag) { /// Asserts there is only one global Progress instance. /// /// Call `Node.end` when done. -pub fn start(options: Options) Node { +pub fn start(options: Options, io: Io) Node { // Ensure there is only 1 global Progress object. if (global_progress.node_end_index != 0) { debug_start_trace.dump(); @@ -458,10 +469,10 @@ pub fn start(options: Options) Node { if (noop_impl) return Node.none; - const io = static_threaded_io.io(); + global_progress.io = io; if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| { - global_progress.update_thread = std.Thread.spawn(.{}, ipcThreadRun, .{ + global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, @as(Io.File, .{ .handle = switch (@typeInfo(posix.fd_t)) { .int => ipc_fd, @@ -469,7 +480,7 @@ pub fn start(options: Options) Node { else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)), } }), }) catch |err| { - std.log.warn("failed to spawn IPC thread for communicating progress to parent: {s}", .{@errorName(err)}); + global_progress.start_failure = .{ .spawn_ipc_worker = err }; return Node.none; }; } else |env_err| switch (env_err) { @@ -502,17 +513,17 @@ pub fn start(options: Options) Node { if (switch (global_progress.terminal_mode) { .off => unreachable, // handled a few lines above - .ansi_escape_codes => std.Thread.spawn(.{}, updateThreadRun, .{io}), - .windows_api => if (is_windows) std.Thread.spawn(.{}, windowsApiUpdateThreadRun, .{io}) else unreachable, - }) |thread| { - global_progress.update_thread = thread; + .ansi_escape_codes => io.concurrent(updateThreadRun, .{io}), + .windows_api => if (is_windows) io.concurrent(windowsApiUpdateThreadRun, .{io}) else unreachable, + }) |future| { + global_progress.update_worker = future; } else |err| { - std.log.warn("unable to spawn thread for printing progress to terminal: {s}", .{@errorName(err)}); + global_progress.start_failure = .{ .spawn_update_worker = err }; return Node.none; } }, else => |e| { - std.log.warn("invalid ZIG_PROGRESS file descriptor integer: {s}", .{@errorName(e)}); + global_progress.start_failure = .{ .parse_env_var = e }; return Node.none; }, } @@ -545,10 +556,10 @@ fn updateThreadRun(io: Io) void { maybeUpdateSize(resize_flag); const buffer, _ = computeRedraw(&serialized_buffer); - if (stderr_mutex.tryLock()) { - defer stderr_mutex.unlock(); - write(io, buffer) catch return; + if (io.tryLockStderrWriter(&.{})) |w| { + defer io.unlockStderrWriter(); global_progress.need_clear = true; + w.writeAll(buffer) catch return; } } @@ -556,18 +567,18 @@ fn updateThreadRun(io: Io) void { const resize_flag = wait(global_progress.refresh_rate_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) { - stderr_mutex.lock(); - defer stderr_mutex.unlock(); - return clearWrittenWithEscapeCodes(io) catch {}; + const w = io.lockStderrWriter(&.{}) catch return; + defer io.unlockStderrWriter(); + return clearWrittenWithEscapeCodes(w) catch {}; } maybeUpdateSize(resize_flag); const buffer, _ = computeRedraw(&serialized_buffer); - if (stderr_mutex.tryLock()) { - defer stderr_mutex.unlock(); - write(io, buffer) catch return; + if (io.tryLockStderrWriter(&.{})) |w| { + defer io.unlockStderrWriter(); global_progress.need_clear = true; + w.writeAll(buffer) catch return; } } } @@ -589,11 +600,11 @@ fn windowsApiUpdateThreadRun(io: Io) void { maybeUpdateSize(resize_flag); const buffer, const nl_n = computeRedraw(&serialized_buffer); - if (stderr_mutex.tryLock()) { - defer stderr_mutex.unlock(); + if (io.tryLockStderrWriter()) |w| { + defer io.unlockStderrWriter(); windowsApiWriteMarker(); - write(io, buffer) catch return; global_progress.need_clear = true; + w.writeAll(buffer) catch return; windowsApiMoveToMarker(nl_n) catch return; } } @@ -602,74 +613,25 @@ fn windowsApiUpdateThreadRun(io: Io) void { const resize_flag = wait(global_progress.refresh_rate_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) { - stderr_mutex.lock(); - defer stderr_mutex.unlock(); + _ = io.lockStderrWriter() catch return; + defer io.unlockStderrWriter(); return clearWrittenWindowsApi() catch {}; } maybeUpdateSize(resize_flag); const buffer, const nl_n = computeRedraw(&serialized_buffer); - if (stderr_mutex.tryLock()) { - defer stderr_mutex.unlock(); + if (io.tryLockStderrWriter()) |w| { + defer io.unlockStderrWriter(); clearWrittenWindowsApi() catch return; windowsApiWriteMarker(); - write(io, buffer) catch return; global_progress.need_clear = true; + w.writeAll(buffer) catch return; windowsApiMoveToMarker(nl_n) catch return; } } } -/// Allows the caller to freely write to stderr until `unlockStdErr` is called. -/// -/// During the lock, any `std.Progress` information is cleared from the terminal. -/// -/// The lock is recursive; the same thread may hold the lock multiple times. -pub fn lockStdErr() void { - const io = stderr_file_writer.io; - stderr_mutex.lock(); - clearWrittenWithEscapeCodes(io) catch {}; -} - -pub fn unlockStdErr() void { - stderr_mutex.unlock(); -} - -/// Protected by `stderr_mutex`. -const stderr_writer: *Writer = &stderr_file_writer.interface; -/// Protected by `stderr_mutex`. -var stderr_file_writer: Io.File.Writer = .{ - .io = static_threaded_io.io(), - .interface = Io.File.Writer.initInterface(&.{}), - .file = if (is_windows) undefined else .stderr(), - .mode = .streaming, -}; -var static_threaded_io: Io.Threaded = .init_single_threaded; - -/// Allows the caller to freely write to the returned `Writer`, -/// initialized with `buffer`, until `unlockStderrWriter` is called. -/// -/// During the lock, any `std.Progress` information is cleared from the terminal. -/// -/// The lock is recursive; the same thread may hold the lock multiple times. -pub fn lockStderrWriter(buffer: []u8) *Io.Writer { - const io = stderr_file_writer.io; - stderr_mutex.lock(); - clearWrittenWithEscapeCodes(io) catch {}; - if (is_windows) stderr_file_writer.file = .stderr(); - stderr_writer.flush() catch {}; - stderr_writer.buffer = buffer; - return stderr_writer; -} - -pub fn unlockStderrWriter() void { - stderr_writer.flush() catch {}; - stderr_writer.end = 0; - stderr_writer.buffer = &.{}; - stderr_mutex.unlock(); -} - fn ipcThreadRun(io: Io, file: Io.File) anyerror!void { // Store this data in the thread so that it does not need to be part of the // linker data of the main executable. @@ -793,11 +755,11 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { } } -fn clearWrittenWithEscapeCodes(io: Io) anyerror!void { +fn clearWrittenWithEscapeCodes(w: *Io.Writer) anyerror!void { if (noop_impl or !global_progress.need_clear) return; + try w.writeAll(clear ++ progress_remove); global_progress.need_clear = false; - try write(io, clear ++ progress_remove); } /// U+25BA or ► @@ -997,7 +959,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff const n = posix.read(fd, pipe_buf[bytes_read..]) catch |err| switch (err) { error.WouldBlock => break, else => |e| { - std.log.debug("failed to read child progress data: {s}", .{@errorName(e)}); + std.log.debug("failed to read child progress data: {t}", .{e}); main_storage.completed_count = 0; main_storage.estimated_total_count = 0; continue :main_loop; @@ -1424,10 +1386,6 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool { return nl_n + 2 < p.rows; } -fn write(io: Io, buf: []const u8) anyerror!void { - try global_progress.terminal.writeStreamingAll(io, buf); -} - var remaining_write_trash_bytes: usize = 0; fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void { @@ -1459,7 +1417,7 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi error.WouldBlock => return, error.BrokenPipe => return error.BrokenPipe, else => |e| { - std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)}); + std.log.debug("failed to send progress to parent process: {t}", .{e}); return error.BrokenPipe; }, } @@ -1476,7 +1434,7 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi error.WouldBlock => {}, error.BrokenPipe => return error.BrokenPipe, else => |e| { - std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)}); + std.log.debug("failed to send progress to parent process: {t}", .{e}); return error.BrokenPipe; }, } @@ -1568,11 +1526,6 @@ const have_sigwinch = switch (builtin.os.tag) { else => false, }; -/// The primary motivation for recursive mutex here is so that a panic while -/// stderr mutex is held still dumps the stack trace and other debug -/// information. -var stderr_mutex = std.Thread.Mutex.Recursive.init; - fn copyAtomicStore(dest: []align(@alignOf(usize)) u8, src: []const u8) void { assert(dest.len == src.len); const chunked_len = dest.len / @sizeOf(usize); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 7b111215f3d25bc8f997b6b3c55c451e23e9eb9e..7e10c5af32e4490e6bf51ee63453811230dc0fdb 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -262,17 +262,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { else => true, }; -/// Allows the caller to freely write to stderr until `unlockStdErr` is called. -/// -/// During the lock, any `std.Progress` information is cleared from the terminal. -pub fn lockStdErr() void { - std.Progress.lockStdErr(); -} - -pub fn unlockStdErr() void { - std.Progress.unlockStdErr(); -} - /// Allows the caller to freely write to stderr until `unlockStderrWriter` is called. /// /// During the lock, any `std.Progress` information is cleared from the terminal. @@ -281,17 +270,21 @@ pub fn unlockStdErr() void { /// times. The primary motivation is that this allows the panic handler to safely dump the stack /// trace and panic message even if the mutex was held at the panic site. /// -/// The returned `Writer` does not need to be manually flushed: flushing is performed automatically -/// when the matching `unlockStderrWriter` call occurs. +/// The returned `Writer` does not need to be manually flushed: flushing is +/// performed automatically when the matching `unlockStderrWriter` call occurs. +/// +/// This is a low-level debugging primitive that bypasses the `Io` interface, +/// writing directly to stderr using the most basic syscalls available. This +/// function does not switch threads, switch stacks, or suspend. +/// +/// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with +/// the application's chosen `Io` implementation. pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } { - const global = struct { - var conf: ?tty.Config = null; - }; + Io.stderr_thread_mutex.lock(); const w = std.Progress.lockStderrWriter(buffer); - const file_writer: *File.Writer = @fieldParentPtr("interface", w); // The stderr lock also locks access to `global.conf`. - if (global.conf == null) { - global.conf = .detect(file_writer.io, .stderr()); + if (StderrWriter.singleton.tty_config == null) { + StderrWriter.singleton.tty_config = .detect(io, .stderr()); } return .{ w, global.conf.? }; } @@ -300,11 +293,17 @@ pub fn unlockStderrWriter() void { std.Progress.unlockStderrWriter(); } -/// Print to stderr, silently returning on failure. Intended for use in "printf -/// debugging". Use `std.log` functions for proper logging. +/// Writes to stderr, ignoring errors. +/// +/// This is a low-level debugging primitive that bypasses the `Io` interface, +/// writing directly to stderr using the most basic syscalls available. This +/// function does not switch threads, switch stacks, or suspend. /// /// Uses a 64-byte buffer for formatted printing which is flushed before this /// function returns. +/// +/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to +/// integrate with the application's chosen `Io` implementation. pub fn print(comptime fmt: []const u8, args: anytype) void { var buffer: [64]u8 = undefined; const bw, _ = lockStderrWriter(&buffer); @@ -312,6 +311,34 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { nosuspend bw.print(fmt, args) catch return; } +const StderrWriter = struct { + interface: Writer, + tty_config: ?tty.Config, + + var singleton: StderrWriter = .{ + .interface = .{ + .buffer = &.{}, + .vtable = &.{ .drain = drain }, + }, + .tty_config = null, + }; + + fn drain(io_w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { + const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); + var n: usize = 0; + const header = w.interface.buffered(); + if (header.len != 0) n += try std.Io.Threaded.debugWrite(header); + for (data[0 .. data.len - 1]) |d| { + if (d.len != 0) n += try std.Io.Threaded.debugWrite(d); + } + const pattern = data[data.len - 1]; + if (pattern.len != 0) { + for (0..splat) |_| n += try std.Io.Threaded.debugWrite(pattern); + } + return io_w.consume(n); + } +}; + /// Marked `inline` to propagate a comptime-known error to callers. pub inline fn getSelfDebugInfo() !*SelfInfo { if (SelfInfo == void) return error.UnsupportedTarget; @@ -767,7 +794,7 @@ pub const FormatStackTrace = struct { stack_trace: StackTrace, tty_config: tty.Config, - pub fn format(context: @This(), writer: *Io.Writer) Io.Writer.Error!void { + pub fn format(context: @This(), writer: *Writer) Writer.Error!void { try writer.writeAll("\n"); try writeStackTrace(&context.stack_trace, writer, context.tty_config); } @@ -1608,7 +1635,7 @@ test "manage resources correctly" { const gpa = std.testing.allocator; var threaded: Io.Threaded = .init_single_threaded; const io = threaded.ioBasic(); - var discarding: Io.Writer.Discarding = .init(&.{}); + var discarding: Writer.Discarding = .init(&.{}); var di: SelfInfo = .init; defer di.deinit(gpa); try printSourceAtAddress( diff --git a/lib/std/log.zig b/lib/std/log.zig index 9568f9ba5211192d0258b3a3a6654a165dbd0b0b..461dfca36e7452e09d4b4c98162af75a558ea749 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -80,6 +80,8 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool { return @intFromEnum(level) <= @intFromEnum(std.options.log_level); } +var static_threaded_io: std.Io.Threaded = .init_single_threaded; + /// The default implementation for the log function. Custom log functions may /// forward log messages to this function. /// @@ -90,10 +92,20 @@ pub fn defaultLog( comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype, +) void { + return defaultLogIo(level, scope, format, args, static_threaded_io.io()); +} + +pub fn defaultLogIo( + comptime level: Level, + comptime scope: @EnumLiteral(), + comptime format: []const u8, + args: anytype, + io: std.Io, ) void { var buffer: [64]u8 = undefined; - const stderr, const ttyconf = std.debug.lockStderrWriter(&buffer); - defer std.debug.unlockStderrWriter(); + const stderr, const ttyconf = io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); ttyconf.setColor(stderr, switch (level) { .err => .red, .warn => .yellow, -- 2.54.0 From ffcbd48a1220ce6d652ee762001d88baa385de49 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 9 Dec 2025 22:10:12 -0800 Subject: [PATCH 037/195] std: rework TTY detection and printing This commit sketches an idea for how to deal with detection of file streams as being terminals. When a File stream is a terminal, writes through the stream should have their escapes stripped unless the programmer explicitly enables terminal escapes. Furthermore, the programmer needs a convenient API for intentionally outputting escapes into the stream. In particular it should be possible to set colors that are silently discarded when the stream is not a terminal. This commit makes `Io.File.Writer` track the terminal mode in the already-existing `mode` field, making it the appropriate place to implement escape stripping. `Io.lockStderrWriter` returns a `*Io.File.Writer` with terminal detection already done by default. This is a higher-level application layer stream for writing to stderr. Meanwhile, `std.debug.lockStderrWriter` also returns a `*Io.File.Writer` but a lower-level one that is hard-coded to use a static single-threaded `std.Io.Threaded` instance. This is the same instance that is used for collecting debug information and iterating the unwind info. --- lib/std/Io.zig | 22 +-- lib/std/Io/File/Writer.zig | 243 +++++++++++++++++++++++++++++-- lib/std/Io/Threaded.zig | 47 ++++-- lib/std/Io/tty.zig | 135 ----------------- lib/std/Progress.zig | 5 +- lib/std/debug.zig | 226 +++++++++++++--------------- lib/std/heap/debug_allocator.zig | 96 +++--------- lib/std/log.zig | 76 ++++++---- lib/std/process.zig | 8 +- src/main.zig | 2 - 10 files changed, 454 insertions(+), 406 deletions(-) delete mode 100644 lib/std/Io/tty.zig diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 833cc4ec0f364b58573244a195a99aa5f4b18f70..fdd813536cd8001a3543f121b978a8237103fc49 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -82,8 +82,6 @@ pub const Limit = enum(usize) { pub const Reader = @import("Io/Reader.zig"); pub const Writer = @import("Io/Writer.zig"); -pub const tty = @import("Io/tty.zig"); - pub fn poll( gpa: Allocator, comptime StreamEnum: type, @@ -535,7 +533,6 @@ test { _ = net; _ = Reader; _ = Writer; - _ = tty; _ = Evented; _ = Threaded; _ = @import("Io/test.zig"); @@ -720,6 +717,9 @@ pub const VTable = struct { processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File, processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize, + lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*File.Writer, + tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*File.Writer, + unlockStderrWriter: *const fn (?*anyopaque) void, now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, sleep: *const fn (?*anyopaque, Timeout) SleepError!void, @@ -740,10 +740,6 @@ pub const VTable = struct { netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void, - - lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*Writer, - tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*Writer, - unlockStderrWriter: *const fn (?*anyopaque) void, }; pub const Cancelable = error{ @@ -2186,13 +2182,17 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) { /// /// See also: /// * `tryLockStderrWriter` -pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*Writer { - return io.vtable.lockStderrWriter(io.userdata, buffer); +pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer { + const result = try io.vtable.lockStderrWriter(io.userdata, buffer); + result.io = io; + return result; } /// Same as `lockStderrWriter` but uncancelable and non-blocking. -pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*Writer { - return io.vtable.tryLockStderrWriter(io.userdata, buffer); +pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer { + const result = io.vtable.tryLockStderrWriter(io.userdata, buffer) orelse return null; + result.io = io; + return result; } pub fn unlockStderrWriter(io: Io) void { diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index ec58824c06b062c64f4b588259f7d315bb728ea7..0e995908c4ac1d521c5ff1fdbc8f13cef551bddb 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -1,4 +1,6 @@ const Writer = @This(); +const builtin = @import("builtin"); +const is_windows = builtin.os.tag == .windows; const std = @import("../../std.zig"); const Io = std.Io; @@ -16,7 +18,144 @@ write_file_err: ?WriteFileError = null, seek_err: ?SeekError = null, interface: Io.Writer, -pub const Mode = File.Reader.Mode; +pub const Mode = union(enum) { + /// Uses `Io.VTable.fileWriteFileStreaming` if possible. Not a terminal. + /// `setColor` does nothing. + streaming, + /// Uses `Io.VTable.fileWriteFilePositional` if possible. Not a terminal. + /// `setColor` does nothing. + positional, + /// Avoids `Io.VTable.fileWriteFileStreaming`. Not a terminal. `setColor` + /// does nothing. + streaming_simple, + /// Avoids `Io.VTable.fileWriteFilePositional`. Not a terminal. `setColor` + /// does nothing. + positional_simple, + /// It's a terminal. Writes are escaped so as to strip escape sequences. + /// Color is enabled. + terminal_escaped, + /// It's a terminal. Colors are enabled via calling + /// SetConsoleTextAttribute. Writes are not escaped. + terminal_winapi: TerminalWinapi, + /// Indicates writing cannot continue because of a seek failure. + failure, + + pub fn toStreaming(m: @This()) @This() { + return switch (m) { + .positional, .streaming => .streaming, + .positional_simple, .streaming_simple => .streaming_simple, + inline else => |_, x| x, + }; + } + + pub fn toSimple(m: @This()) @This() { + return switch (m) { + .positional, .positional_simple => .positional_simple, + .streaming, .streaming_simple => .streaming_simple, + inline else => |x| x, + }; + } + + pub fn toUnescaped(m: @This()) @This() { + return switch (m) { + .terminal_escaped => .streaming_simple, + inline else => |x| x, + }; + } + + pub const TerminalWinapi = if (!is_windows) noreturn else struct { + handle: File.Handle, + reset_attributes: u16, + }; + + /// Detect suitable TTY configuration options for the given file (commonly + /// stdout/stderr). + /// + /// Will attempt to enable ANSI escape code support if necessary/possible. + pub fn detect(io: Io, file: File, want_color: bool, fallback: Mode) Io.Cancelable!Mode { + if (!want_color) return if (try file.isTty(io)) .terminal_escaped else fallback; + + if (file.enableAnsiEscapeCodes(io)) |_| { + return .terminal_escaped; + } else |err| switch (err) { + error.Canceled => return error.Canceled, + error.NotTerminalDevice, error.Unexpected => {}, + } + + if (is_windows and file.isTty(io)) { + const windows = std.os.windows; + var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; + if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) { + return .{ .terminal_winapi = .{ + .handle = file.handle, + .reset_attributes = info.wAttributes, + } }; + } + return .terminal_escaped; + } + + return fallback; + } + + pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; + + pub fn setColor(mode: Mode, io_w: *Io.Writer, color: Color) Mode.SetColorError!void { + switch (mode) { + .streaming, .positional, .streaming_simple, .positional_simple, .failure => return, + .terminal_escaped => { + const color_string = switch (color) { + .black => "\x1b[30m", + .red => "\x1b[31m", + .green => "\x1b[32m", + .yellow => "\x1b[33m", + .blue => "\x1b[34m", + .magenta => "\x1b[35m", + .cyan => "\x1b[36m", + .white => "\x1b[37m", + .bright_black => "\x1b[90m", + .bright_red => "\x1b[91m", + .bright_green => "\x1b[92m", + .bright_yellow => "\x1b[93m", + .bright_blue => "\x1b[94m", + .bright_magenta => "\x1b[95m", + .bright_cyan => "\x1b[96m", + .bright_white => "\x1b[97m", + .bold => "\x1b[1m", + .dim => "\x1b[2m", + .reset => "\x1b[0m", + }; + try io_w.writeAll(color_string); + }, + .terminal_winapi => |ctx| { + const windows = std.os.windows; + const attributes: windows.WORD = switch (color) { + .black => 0, + .red => windows.FOREGROUND_RED, + .green => windows.FOREGROUND_GREEN, + .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN, + .blue => windows.FOREGROUND_BLUE, + .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE, + .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, + .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, + .bright_black => windows.FOREGROUND_INTENSITY, + .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY, + .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, + .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, + .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + // "dim" is not supported using basic character attributes, but let's still make it do *something*. + // This matches the old behavior of TTY.Color before the bright variants were added. + .dim => windows.FOREGROUND_INTENSITY, + .reset => ctx.reset_attributes, + }; + try io_w.flush(); + try windows.SetConsoleTextAttribute(ctx.handle, attributes); + }, + } + } +}; pub const Error = error{ DiskQuota, @@ -74,6 +213,16 @@ pub fn initStreaming(file: File, io: Io, buffer: []u8) Writer { }; } +/// Detects if `file` is terminal and sets the mode accordingly. +pub fn initDetect(file: File, io: Io, buffer: []u8) Io.Cancelable!Writer { + return .{ + .io = io, + .file = file, + .interface = initInterface(buffer), + .mode = try .detect(io, file, true, .positional), + }; +} + pub fn initInterface(buffer: []u8) Io.Writer { return .{ .vtable = &.{ @@ -99,8 +248,9 @@ pub fn moveToReader(w: *Writer) File.Reader { pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); switch (w.mode) { - .positional, .positional_reading => return drainPositional(w, data, splat), - .streaming, .streaming_reading => return drainStreaming(w, data, splat), + .positional, .positional_simple => return drainPositional(w, data, splat), + .streaming, .streaming_simple, .terminal_winapi => return drainStreaming(w, data, splat), + .terminal_escaped => return drainEscaping(w, data, splat), .failure => return error.WriteFailed, } } @@ -141,13 +291,38 @@ fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer. return w.interface.consume(n); } +fn findTerminalEscape(buffer: []const u8) ?usize { + return std.mem.findScalar(u8, buffer, 0x1b); +} + +fn drainEscaping(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { + const io = w.io; + const header = w.interface.buffered(); + if (findTerminalEscape(header)) |i| { + _ = i; + @panic("TODO strip terminal escape sequence"); + } + for (data) |d| { + if (findTerminalEscape(d)) |i| { + _ = i; + @panic("TODO strip terminal escape sequence"); + } + } + const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| { + w.err = err; + return error.WriteFailed; + }; + w.pos += n; + return w.interface.consume(n); +} + pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); switch (w.mode) { .positional => return sendFilePositional(w, file_reader, limit), - .positional_reading => return error.Unimplemented, + .positional_simple => return error.Unimplemented, .streaming => return sendFileStreaming(w, file_reader, limit), - .streaming_reading => return error.Unimplemented, + .streaming_simple, .terminal_escaped, .terminal_winapi => return error.Unimplemented, .failure => return error.WriteFailed, } } @@ -214,10 +389,10 @@ pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void { assert(w.interface.buffered().len == 0); const io = w.io; switch (w.mode) { - .positional, .positional_reading => { + .positional, .positional_simple => { w.pos = offset; }, - .streaming, .streaming_reading => { + .streaming, .streaming_simple, .terminal_escaped, .terminal_winapi => { if (w.seek_err) |err| return err; io.vtable.fileSeekTo(io.userdata, w.file, offset) catch |err| { w.seek_err = err; @@ -243,15 +418,65 @@ pub fn end(w: *Writer) EndError!void { try w.interface.flush(); switch (w.mode) { .positional, - .positional_reading, + .positional_simple, => w.file.setLength(io, w.pos) catch |err| switch (err) { error.NonResizable => return, else => |e| return e, }, .streaming, - .streaming_reading, + .streaming_simple, .failure, => {}, } } + +pub const Color = enum { + black, + red, + green, + yellow, + blue, + magenta, + cyan, + white, + bright_black, + bright_red, + bright_green, + bright_yellow, + bright_blue, + bright_magenta, + bright_cyan, + bright_white, + dim, + bold, + reset, +}; + +pub const SetColorError = Mode.SetColorError; + +pub fn setColor(w: *Writer, color: Color) SetColorError!void { + return w.mode.setColor(&w.interface, color); +} + +pub fn disableEscape(w: *Writer) Mode { + const prev = w.mode; + w.mode = w.mode.toUnescaped(); + return prev; +} + +pub fn restoreEscape(w: *Writer, mode: Mode) void { + w.mode = mode; +} + +pub fn writeAllUnescaped(w: *Writer, bytes: []const u8) Io.Error!void { + const prev_mode = w.disableEscape(); + defer w.restoreEscape(prev_mode); + return w.interface.writeAll(bytes); +} + +pub fn printUnescaped(w: *Writer, comptime fmt: []const u8, args: anytype) Io.Error!void { + const prev_mode = w.disableEscape(); + defer w.restoreEscape(prev_mode); + return w.interface.print(fmt, args); +} diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 9d6d9f979eeda1e47d9c9e47915b32f157254702..c2a3e15f15b7e78bee124ff522b453175aed3be3 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -77,7 +77,13 @@ use_sendfile: UseSendfile = .default, use_copy_file_range: UseCopyFileRange = .default, use_fcopyfile: UseFcopyfile = .default, -stderr_writer: Io.Writer, +stderr_writer: File.Writer = .{ + .io = undefined, + .interface = Io.File.Writer.initInterface(&.{}), + .file = if (is_windows) undefined else .stderr(), + .mode = undefined, +}, +stderr_writer_initialized: bool = false, pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { enabled, @@ -737,6 +743,9 @@ pub fn io(t: *Threaded) Io { .processExecutableOpen = processExecutableOpen, .processExecutablePath = processExecutablePath, + .lockStderrWriter = lockStderrWriter, + .tryLockStderrWriter = tryLockStderrWriter, + .unlockStderrWriter = unlockStderrWriter, .now = now, .sleep = sleep, @@ -864,6 +873,9 @@ pub fn ioBasic(t: *Threaded) Io { .processExecutableOpen = processExecutableOpen, .processExecutablePath = processExecutablePath, + .lockStderrWriter = lockStderrWriter, + .tryLockStderrWriter = tryLockStderrWriter, + .unlockStderrWriter = unlockStderrWriter, .now = now, .sleep = sleep, @@ -9516,33 +9528,42 @@ fn netLookupFallible( return error.OptionUnsupported; } -fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*Io.Writer { +fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Writer { const t: *Threaded = @ptrCast(@alignCast(userdata)); // Only global mutex since this is Threaded. Io.stderr_thread_mutex.lock(); - if (is_windows) t.stderr_writer.file = .stderr(); + if (!t.stderr_writer_initialized) { + if (is_windows) t.stderr_writer.file = .stderr(); + t.stderr_writer.mode = try .detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple); + t.stderr_writer_initialized = true; + } std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; - t.stderr_writer.flush() catch {}; - t.stderr_writer.buffer = buffer; + t.stderr_writer.interface.flush() catch {}; + t.stderr_writer.interface.buffer = buffer; return &t.stderr_writer; } -fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*Io.Writer { +fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer { const t: *Threaded = @ptrCast(@alignCast(userdata)); // Only global mutex since this is Threaded. if (!Io.stderr_thread_mutex.tryLock()) return null; - std.Progress.clearWrittenWithEscapeCodes(t.io()) catch {}; - if (is_windows) t.stderr_writer.file = .stderr(); - t.stderr_writer.flush() catch {}; - t.stderr_writer.buffer = buffer; + if (!t.stderr_writer_initialized) { + if (is_windows) t.stderr_writer.file = .stderr(); + t.stderr_writer.mode = File.Writer.Mode.detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple) catch + return null; + t.stderr_writer_initialized = true; + } + std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; + t.stderr_writer.interface.flush() catch {}; + t.stderr_writer.interface.buffer = buffer; return &t.stderr_writer; } fn unlockStderrWriter(userdata: ?*anyopaque) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - t.stderr_writer.flush() catch {}; - t.stderr_writer.end = 0; - t.stderr_writer.buffer = &.{}; + t.stderr_writer.interface.flush() catch {}; + t.stderr_writer.interface.end = 0; + t.stderr_writer.interface.buffer = &.{}; Io.stderr_thread_mutex.unlock(); } diff --git a/lib/std/Io/tty.zig b/lib/std/Io/tty.zig deleted file mode 100644 index 65f1b7dad5451df1d19463d47007b4a055f0f82a..0000000000000000000000000000000000000000 --- a/lib/std/Io/tty.zig +++ /dev/null @@ -1,135 +0,0 @@ -const builtin = @import("builtin"); -const native_os = builtin.os.tag; - -const std = @import("std"); -const Io = std.Io; -const File = std.Io.File; -const process = std.process; -const windows = std.os.windows; - -pub const Color = enum { - black, - red, - green, - yellow, - blue, - magenta, - cyan, - white, - bright_black, - bright_red, - bright_green, - bright_yellow, - bright_blue, - bright_magenta, - bright_cyan, - bright_white, - dim, - bold, - reset, -}; - -/// Provides simple functionality for manipulating the terminal in some way, -/// such as coloring text, etc. -pub const Config = union(enum) { - no_color, - escape_codes, - windows_api: if (native_os == .windows) WindowsContext else noreturn, - - /// Detect suitable TTY configuration options for the given file (commonly stdout/stderr). - /// This includes feature checks for ANSI escape codes and the Windows console API, as well as - /// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default. - /// Will attempt to enable ANSI escape code support if necessary/possible. - pub fn detect(io: Io, file: File) Config { - const force_color: ?bool = if (builtin.os.tag == .wasi) - null // wasi does not support environment variables - else if (process.hasNonEmptyEnvVarConstant("NO_COLOR")) - false - else if (process.hasNonEmptyEnvVarConstant("CLICOLOR_FORCE")) - true - else - null; - - if (force_color == false) return .no_color; - - if (file.enableAnsiEscapeCodes(io)) |_| { - return .escape_codes; - } else |_| {} - - if (native_os == .windows and file.isTty()) { - var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) == windows.FALSE) { - return if (force_color == true) .escape_codes else .no_color; - } - return .{ .windows_api = .{ - .handle = file.handle, - .reset_attributes = info.wAttributes, - } }; - } - - return if (force_color == true) .escape_codes else .no_color; - } - - pub const WindowsContext = struct { - handle: File.Handle, - reset_attributes: u16, - }; - - pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; - - pub fn setColor(conf: Config, w: *Io.Writer, color: Color) SetColorError!void { - nosuspend switch (conf) { - .no_color => return, - .escape_codes => { - const color_string = switch (color) { - .black => "\x1b[30m", - .red => "\x1b[31m", - .green => "\x1b[32m", - .yellow => "\x1b[33m", - .blue => "\x1b[34m", - .magenta => "\x1b[35m", - .cyan => "\x1b[36m", - .white => "\x1b[37m", - .bright_black => "\x1b[90m", - .bright_red => "\x1b[91m", - .bright_green => "\x1b[92m", - .bright_yellow => "\x1b[93m", - .bright_blue => "\x1b[94m", - .bright_magenta => "\x1b[95m", - .bright_cyan => "\x1b[96m", - .bright_white => "\x1b[97m", - .bold => "\x1b[1m", - .dim => "\x1b[2m", - .reset => "\x1b[0m", - }; - try w.writeAll(color_string); - }, - .windows_api => |ctx| { - const attributes = switch (color) { - .black => 0, - .red => windows.FOREGROUND_RED, - .green => windows.FOREGROUND_GREEN, - .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN, - .blue => windows.FOREGROUND_BLUE, - .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE, - .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, - .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, - .bright_black => windows.FOREGROUND_INTENSITY, - .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY, - .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, - .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, - .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, - .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, - .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, - .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, - // "dim" is not supported using basic character attributes, but let's still make it do *something*. - // This matches the old behavior of TTY.Color before the bright variants were added. - .dim => windows.FOREGROUND_INTENSITY, - .reset => ctx.reset_attributes, - }; - try w.flush(); - try windows.SetConsoleTextAttribute(ctx.handle, attributes); - }, - }; - } -}; diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 5528591620961a0985b9264c7e4c9d03c084dfdb..799e3f9c9b7b9c4337870b9490a79d4384ac2be1 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -755,10 +755,9 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { } } -fn clearWrittenWithEscapeCodes(w: *Io.Writer) anyerror!void { +pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) anyerror!void { if (noop_impl or !global_progress.need_clear) return; - - try w.writeAll(clear ++ progress_remove); + try file_writer.interface.writeAllUnescaped(clear ++ progress_remove); global_progress.need_clear = false; } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 7e10c5af32e4490e6bf51ee63453811230dc0fdb..347dbf85ecc52787fd9a492651c8f05ca415cec7 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1,7 +1,6 @@ const std = @import("std.zig"); const Io = std.Io; const Writer = std.Io.Writer; -const tty = std.Io.tty; const math = std.math; const mem = std.mem; const posix = std.posix; @@ -262,6 +261,10 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { else => true, }; +/// This is used for debug information and debug printing. It is intentionally +/// separate from the application's `Io` instance. +var static_single_threaded_io: Io.Threaded = .init_single_threaded; + /// Allows the caller to freely write to stderr until `unlockStderrWriter` is called. /// /// During the lock, any `std.Progress` information is cleared from the terminal. @@ -279,18 +282,12 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { /// /// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with /// the application's chosen `Io` implementation. -pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } { - Io.stderr_thread_mutex.lock(); - const w = std.Progress.lockStderrWriter(buffer); - // The stderr lock also locks access to `global.conf`. - if (StderrWriter.singleton.tty_config == null) { - StderrWriter.singleton.tty_config = .detect(io, .stderr()); - } - return .{ w, global.conf.? }; +pub fn lockStderrWriter(buffer: []u8) *File.Writer { + return static_single_threaded_io.ioBasic().lockStderrWriter(buffer) catch unreachable; } pub fn unlockStderrWriter() void { - std.Progress.unlockStderrWriter(); + static_single_threaded_io.ioBasic().unlockStderrWriter(); } /// Writes to stderr, ignoring errors. @@ -305,39 +302,13 @@ pub fn unlockStderrWriter() void { /// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to /// integrate with the application's chosen `Io` implementation. pub fn print(comptime fmt: []const u8, args: anytype) void { - var buffer: [64]u8 = undefined; - const bw, _ = lockStderrWriter(&buffer); - defer unlockStderrWriter(); - nosuspend bw.print(fmt, args) catch return; -} - -const StderrWriter = struct { - interface: Writer, - tty_config: ?tty.Config, - - var singleton: StderrWriter = .{ - .interface = .{ - .buffer = &.{}, - .vtable = &.{ .drain = drain }, - }, - .tty_config = null, - }; - - fn drain(io_w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { - const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - var n: usize = 0; - const header = w.interface.buffered(); - if (header.len != 0) n += try std.Io.Threaded.debugWrite(header); - for (data[0 .. data.len - 1]) |d| { - if (d.len != 0) n += try std.Io.Threaded.debugWrite(d); - } - const pattern = data[data.len - 1]; - if (pattern.len != 0) { - for (0..splat) |_| n += try std.Io.Threaded.debugWrite(pattern); - } - return io_w.consume(n); + nosuspend { + var buffer: [64]u8 = undefined; + const stderr = lockStderrWriter(&buffer); + defer unlockStderrWriter(); + stderr.interface.print(fmt, args) catch return; } -}; +} /// Marked `inline` to propagate a comptime-known error to callers. pub inline fn getSelfDebugInfo() !*SelfInfo { @@ -357,16 +328,16 @@ pub fn dumpHex(bytes: []const u8) void { } /// Prints a hexadecimal view of the bytes, returning any error that occurs. -pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !void { +pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !void { var chunks = mem.window(u8, bytes, 16, 16); while (chunks.next()) |window| { // 1. Print the address. const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10; - try tty_config.setColor(bw, .dim); + try fwm.setColor(bw, .dim); // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more. // Also, make sure all lines are aligned by padding the address. try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); - try tty_config.setColor(bw, .reset); + try fwm.setColor(bw, .reset); // 2. Print the bytes. for (window, 0..) |byte, index| { @@ -386,7 +357,7 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) ! try bw.writeByte(byte); } else { // Related: https://github.com/ziglang/zig/issues/7600 - if (tty_config == .windows_api) { + if (fwm == .terminal_winapi) { try bw.writeByte('.'); continue; } @@ -408,11 +379,11 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) ! test dumpHexFallible { const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 }; - var aw: Writer.Allocating = .init(std.testing.allocator); + var aw: Writer.Allocating = .init(testing.allocator); defer aw.deinit(); try dumpHexFallible(&aw.writer, .no_color, bytes); - const expected = try std.fmt.allocPrint(std.testing.allocator, + const expected = try std.fmt.allocPrint(testing.allocator, \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ \\{x:0>[2]} 01 12 13 ... \\ @@ -421,8 +392,8 @@ test dumpHexFallible { @intFromPtr(bytes.ptr) + 16, @sizeOf(usize) * 2, }); - defer std.testing.allocator.free(expected); - try std.testing.expectEqualStrings(expected, aw.written()); + defer testing.allocator.free(expected); + try testing.expectEqualStrings(expected, aw.written()); } /// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic. @@ -437,7 +408,7 @@ pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *con /// away, and in fact the optimizer is able to use the assertion in its /// heuristics. /// -/// Inside a test block, it is best to use the `std.testing` module rather than +/// Inside a test block, it is best to use the `testing` module rather than /// this function, because this function may not detect a test failure in /// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert /// function is the correct function to use. @@ -574,26 +545,26 @@ pub fn defaultPanic( _ = panicking.fetchAdd(1, .seq_cst); trace: { - const stderr, const tty_config = lockStderrWriter(&.{}); + const stderr = lockStderrWriter(&.{}); defer unlockStderrWriter(); if (builtin.single_threaded) { - stderr.print("panic: ", .{}) catch break :trace; + stderr.interface.print("panic: ", .{}) catch break :trace; } else { const current_thread_id = std.Thread.getCurrentId(); - stderr.print("thread {d} panic: ", .{current_thread_id}) catch break :trace; + stderr.interface.print("thread {d} panic: ", .{current_thread_id}) catch break :trace; } - stderr.print("{s}\n", .{msg}) catch break :trace; + stderr.interface.print("{s}\n", .{msg}) catch break :trace; if (@errorReturnTrace()) |t| if (t.index > 0) { - stderr.writeAll("error return context:\n") catch break :trace; - writeStackTrace(t, stderr, tty_config) catch break :trace; - stderr.writeAll("\nstack trace:\n") catch break :trace; + stderr.interface.writeAll("error return context:\n") catch break :trace; + writeStackTrace(t, &stderr.interface, stderr.mode) catch break :trace; + stderr.interface.writeAll("\nstack trace:\n") catch break :trace; }; writeCurrentStackTrace(.{ .first_address = first_trace_addr orelse @returnAddress(), .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! - }, stderr, tty_config) catch break :trace; + }, &stderr.interface, stderr.mode) catch break :trace; } waitForOtherThreadToFinishPanicking(); @@ -603,8 +574,8 @@ pub fn defaultPanic( // A panic happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). - const stderr, _ = lockStderrWriter(&.{}); - stderr.writeAll("aborting due to recursive panic\n") catch {}; + const stderr = lockStderrWriter(&.{}); + stderr.interface.writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. } @@ -651,8 +622,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: defer it.deinit(); if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace; - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); + const io = static_single_threaded_io.ioBasic(); var total_frames: usize = 0; var index: usize = 0; @@ -686,36 +656,34 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: /// Write the current stack trace to `writer`, annotated with source locations. /// /// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing. -pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - +pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void { if (!std.options.allow_stack_tracing) { - tty_config.setColor(writer, .dim) catch {}; + fwm.setColor(writer, .dim) catch {}; try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; return; } const di_gpa = getDebugInfoAllocator(); const di = getSelfDebugInfo() catch |err| switch (err) { error.UnsupportedTarget => { - tty_config.setColor(writer, .dim) catch {}; + fwm.setColor(writer, .dim) catch {}; try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{}); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; return; }, }; var it: StackIterator = .init(options.context); defer it.deinit(); if (!it.stratOk(options.allow_unsafe_unwind)) { - tty_config.setColor(writer, .dim) catch {}; + fwm.setColor(writer, .dim) catch {}; try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{}); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; return; } var total_frames: usize = 0; var wait_for = options.first_address; var printed_any_frame = false; + const io = static_single_threaded_io.ioBasic(); while (true) switch (it.next(io)) { .switch_to_fp => |unwind_error| { switch (StackIterator.fp_usability) { @@ -733,31 +701,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri error.Unexpected => "unexpected error", }; if (it.stratOk(options.allow_unsafe_unwind)) { - tty_config.setColor(writer, .dim) catch {}; + fwm.setColor(writer, .dim) catch {}; try writer.print( "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n", .{ module_name, unwind_error.address, caption }, ); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; } else { - tty_config.setColor(writer, .dim) catch {}; + fwm.setColor(writer, .dim) catch {}; try writer.print( "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n", .{ module_name, unwind_error.address, caption }, ); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; return; } }, .end => break, .frame => |ret_addr| { if (total_frames > 10_000) { - tty_config.setColor(writer, .dim) catch {}; + fwm.setColor(writer, .dim) catch {}; try writer.print( "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n", .{total_frames}, ); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; return; } total_frames += 1; @@ -767,7 +735,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri } // `ret_addr` is the return address, which is *after* the function call. // Subtract 1 to get an address *in* the function call for a better source location. - try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config); + try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm); printed_any_frame = true; }, }; @@ -775,7 +743,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri } /// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors. pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { - const stderr, const tty_config = lockStderrWriter(&.{}); + const stderr = lockStderrWriter(&.{}); defer unlockStderrWriter(); writeCurrentStackTrace(.{ .first_address = a: { @@ -785,33 +753,40 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { }, .context = options.context, .allow_unsafe_unwind = options.allow_unsafe_unwind, - }, stderr, tty_config) catch |err| switch (err) { + }, &stderr.interface, stderr.mode) catch |err| switch (err) { error.WriteFailed => {}, }; } pub const FormatStackTrace = struct { stack_trace: StackTrace, - tty_config: tty.Config, - pub fn format(context: @This(), writer: *Writer) Writer.Error!void { - try writer.writeAll("\n"); - try writeStackTrace(&context.stack_trace, writer, context.tty_config); + pub const Decorated = struct { + stack_trace: StackTrace, + file_writer_mode: File.Writer.Mode, + + pub fn format(decorated: Decorated, writer: *Writer) Writer.Error!void { + try writer.writeByte('\n'); + try writeStackTrace(&decorated.stack_trace, writer, decorated.file_writer_mode); + } + }; + + pub fn format(context: FormatStackTrace, writer: *Writer) Writer.Error!void { + return Decorated.format(.{ + .stack_trace = context.stack_trace, + .file_writer_mode = .streaming, + }, writer); } }; /// Write a previously captured stack trace to `writer`, annotated with source locations. -pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void { +pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void { if (!std.options.allow_stack_tracing) { - tty_config.setColor(writer, .dim) catch {}; + fwm.setColor(writer, .dim) catch {}; try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; return; } - // We use an independent Io implementation here in case there was a problem - // with the application's Io implementation itself. - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace. @@ -820,22 +795,23 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.C const di_gpa = getDebugInfoAllocator(); const di = getSelfDebugInfo() catch |err| switch (err) { error.UnsupportedTarget => { - tty_config.setColor(writer, .dim) catch {}; + fwm.setColor(writer, .dim) catch {}; try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{}); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; return; }, }; + const io = static_single_threaded_io.ioBasic(); const captured_frames = @min(n_frames, st.instruction_addresses.len); for (st.instruction_addresses[0..captured_frames]) |ret_addr| { // `ret_addr` is the return address, which is *after* the function call. // Subtract 1 to get an address *in* the function call for a better source location. - try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config); + try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm); } if (n_frames > captured_frames) { - tty_config.setColor(writer, .bold) catch {}; + fwm.setColor(writer, .bold) catch {}; try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames}); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; } } /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. @@ -1143,7 +1119,7 @@ fn printSourceAtAddress( debug_info: *SelfInfo, writer: *Writer, address: usize, - tty_config: tty.Config, + fwm: File.Writer.Mode, ) Writer.Error!void { const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) { error.MissingDebugInfo, @@ -1151,15 +1127,15 @@ fn printSourceAtAddress( error.InvalidDebugInfo, => .unknown, error.ReadFailed, error.Unexpected, error.Canceled => s: { - tty_config.setColor(writer, .dim) catch {}; + fwm.setColor(writer, .dim) catch {}; try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; break :s .unknown; }, error.OutOfMemory => s: { - tty_config.setColor(writer, .dim) catch {}; + fwm.setColor(writer, .dim) catch {}; try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; break :s .unknown; }, }; @@ -1171,7 +1147,7 @@ fn printSourceAtAddress( address, symbol.name orelse "???", symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???", - tty_config, + fwm, ); } fn printLineInfo( @@ -1181,10 +1157,10 @@ fn printLineInfo( address: usize, symbol_name: []const u8, compile_unit_name: []const u8, - tty_config: tty.Config, + fwm: File.Writer.Mode, ) Writer.Error!void { nosuspend { - tty_config.setColor(writer, .bold) catch {}; + fwm.setColor(writer, .bold) catch {}; if (source_location) |*sl| { try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); @@ -1192,11 +1168,11 @@ fn printLineInfo( try writer.writeAll("???:?:?"); } - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; try writer.writeAll(": "); - tty_config.setColor(writer, .dim) catch {}; + fwm.setColor(writer, .dim) catch {}; try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; try writer.writeAll("\n"); // Show the matching source code line if possible @@ -1207,9 +1183,9 @@ fn printLineInfo( const space_needed = @as(usize, @intCast(sl.column - 1)); try writer.splatByteAll(' ', space_needed); - tty_config.setColor(writer, .green) catch {}; + fwm.setColor(writer, .green) catch {}; try writer.writeAll("^"); - tty_config.setColor(writer, .reset) catch {}; + fwm.setColor(writer, .reset) catch {}; } try writer.writeAll("\n"); } else |_| { @@ -1250,18 +1226,18 @@ fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) ! } test printLineFromFile { - const io = std.testing.io; - const gpa = std.testing.allocator; + const io = testing.io; + const gpa = testing.allocator; var aw: Writer.Allocating = .init(gpa); defer aw.deinit(); const output_stream = &aw.writer; const join = std.fs.path.join; - const expectError = std.testing.expectError; - const expectEqualStrings = std.testing.expectEqualStrings; + const expectError = testing.expectError; + const expectEqualStrings = testing.expectEqualStrings; - var test_dir = std.testing.tmpDir(.{}); + var test_dir = testing.tmpDir(.{}); defer test_dir.cleanup(); // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths. const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] }); @@ -1578,19 +1554,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex _ = panicking.fetchAdd(1, .seq_cst); trace: { - const stderr, const tty_config = lockStderrWriter(&.{}); + const stderr = lockStderrWriter(&.{}); defer unlockStderrWriter(); if (addr) |a| { - stderr.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; + stderr.interface.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; } else { - stderr.print("{s} (no address available)\n", .{name}) catch break :trace; + stderr.interface.print("{s} (no address available)\n", .{name}) catch break :trace; } if (opt_ctx) |context| { writeCurrentStackTrace(.{ .context = context, .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! - }, stderr, tty_config) catch break :trace; + }, &stderr.interface, stderr.mode) catch break :trace; } } }, @@ -1599,8 +1575,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex // A segfault happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). - const stderr, _ = lockStderrWriter(&.{}); - stderr.writeAll("aborting due to recursive panic\n") catch {}; + const stderr = lockStderrWriter(&.{}); + stderr.interface.writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. } @@ -1632,9 +1608,9 @@ test "manage resources correctly" { return @returnAddress(); } }; - const gpa = std.testing.allocator; - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); + const gpa = testing.allocator; + const io = testing.io; + var discarding: Writer.Discarding = .init(&.{}); var di: SelfInfo = .init; defer di.deinit(gpa); diff --git a/lib/std/heap/debug_allocator.zig b/lib/std/heap/debug_allocator.zig index 27b1b9179f24299cd86d111a24ff9799016a3677..66ff59a7116dd6915f5f46c0bb883be2134afdbb 100644 --- a/lib/std/heap/debug_allocator.zig +++ b/lib/std/heap/debug_allocator.zig @@ -179,8 +179,6 @@ pub fn DebugAllocator(comptime config: Config) type { total_requested_bytes: @TypeOf(total_requested_bytes_init) = total_requested_bytes_init, requested_memory_limit: @TypeOf(requested_memory_limit_init) = requested_memory_limit_init, mutex: @TypeOf(mutex_init) = mutex_init, - /// Set this value differently to affect how errors and leaks are logged. - tty_config: std.Io.tty.Config = .no_color, const Self = @This(); @@ -427,7 +425,6 @@ pub fn DebugAllocator(comptime config: Config) type { bucket: *BucketHeader, size_class_index: usize, used_bits_count: usize, - tty_config: std.Io.tty.Config, ) usize { const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index)); const slot_count = slot_counts[size_class_index]; @@ -444,11 +441,7 @@ pub fn DebugAllocator(comptime config: Config) type { const page_addr = @intFromPtr(bucket) & ~(page_size - 1); const addr = page_addr + slot_index * size_class; log.err("memory address 0x{x} leaked: {f}", .{ - addr, - std.debug.FormatStackTrace{ - .stack_trace = stack_trace, - .tty_config = tty_config, - }, + addr, std.debug.FormatStackTrace{ .stack_trace = stack_trace }, }); leaks += 1; } @@ -460,8 +453,6 @@ pub fn DebugAllocator(comptime config: Config) type { /// Emits log messages for leaks and then returns the number of detected leaks (0 if no leaks were detected). pub fn detectLeaks(self: *Self) usize { - const tty_config = self.tty_config; - var leaks: usize = 0; for (self.buckets, 0..) |init_optional_bucket, size_class_index| { @@ -469,7 +460,7 @@ pub fn DebugAllocator(comptime config: Config) type { const slot_count = slot_counts[size_class_index]; const used_bits_count = usedBitsCount(slot_count); while (optional_bucket) |bucket| { - leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count, tty_config); + leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count); optional_bucket = bucket.prev; } } @@ -480,10 +471,7 @@ pub fn DebugAllocator(comptime config: Config) type { const stack_trace = large_alloc.getStackTrace(.alloc); log.err("memory address 0x{x} leaked: {f}", .{ @intFromPtr(large_alloc.bytes.ptr), - std.debug.FormatStackTrace{ - .stack_trace = stack_trace, - .tty_config = tty_config, - }, + std.debug.FormatStackTrace{ .stack_trace = stack_trace }, }); leaks += 1; } @@ -535,28 +523,14 @@ pub fn DebugAllocator(comptime config: Config) type { @memset(addr_buf[@min(st.index, addr_buf.len)..], 0); } - fn reportDoubleFree( - tty_config: std.Io.tty.Config, - ret_addr: usize, - alloc_stack_trace: StackTrace, - free_stack_trace: StackTrace, - ) void { + fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void { @branchHint(.cold); var addr_buf: [stack_n]usize = undefined; const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{ - std.debug.FormatStackTrace{ - .stack_trace = alloc_stack_trace, - .tty_config = tty_config, - }, - std.debug.FormatStackTrace{ - .stack_trace = free_stack_trace, - .tty_config = tty_config, - }, - std.debug.FormatStackTrace{ - .stack_trace = second_free_stack_trace, - .tty_config = tty_config, - }, + std.debug.FormatStackTrace{ .stack_trace = alloc_stack_trace }, + std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, + std.debug.FormatStackTrace{ .stack_trace = second_free_stack_trace }, }); } @@ -587,7 +561,7 @@ pub fn DebugAllocator(comptime config: Config) type { if (config.retain_metadata and entry.value_ptr.freed) { if (config.safety) { - reportDoubleFree(self.tty_config, ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); + reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); @panic("Unrecoverable double free"); } else { unreachable; @@ -598,18 +572,11 @@ pub fn DebugAllocator(comptime config: Config) type { @branchHint(.cold); var addr_buf: [stack_n]usize = undefined; const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); - const tty_config = self.tty_config; log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ entry.value_ptr.bytes.len, old_mem.len, - std.debug.FormatStackTrace{ - .stack_trace = entry.value_ptr.getStackTrace(.alloc), - .tty_config = tty_config, - }, - std.debug.FormatStackTrace{ - .stack_trace = free_stack_trace, - .tty_config = tty_config, - }, + std.debug.FormatStackTrace{ .stack_trace = entry.value_ptr.getStackTrace(.alloc) }, + std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, }); } @@ -701,7 +668,7 @@ pub fn DebugAllocator(comptime config: Config) type { if (config.retain_metadata and entry.value_ptr.freed) { if (config.safety) { - reportDoubleFree(self.tty_config, ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); + reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free)); return; } else { unreachable; @@ -712,18 +679,11 @@ pub fn DebugAllocator(comptime config: Config) type { @branchHint(.cold); var addr_buf: [stack_n]usize = undefined; const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); - const tty_config = self.tty_config; log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ entry.value_ptr.bytes.len, old_mem.len, - std.debug.FormatStackTrace{ - .stack_trace = entry.value_ptr.getStackTrace(.alloc), - .tty_config = tty_config, - }, - std.debug.FormatStackTrace{ - .stack_trace = free_stack_trace, - .tty_config = tty_config, - }, + std.debug.FormatStackTrace{ .stack_trace = entry.value_ptr.getStackTrace(.alloc) }, + std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, }); } @@ -924,7 +884,6 @@ pub fn DebugAllocator(comptime config: Config) type { if (!is_used) { if (config.safety) { reportDoubleFree( - self.tty_config, return_address, bucketStackTrace(bucket, slot_count, slot_index, .alloc), bucketStackTrace(bucket, slot_count, slot_index, .free), @@ -946,34 +905,24 @@ pub fn DebugAllocator(comptime config: Config) type { const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); if (old_memory.len != requested_size) { @branchHint(.cold); - const tty_config = self.tty_config; log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ requested_size, old_memory.len, std.debug.FormatStackTrace{ .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), - .tty_config = tty_config, - }, - std.debug.FormatStackTrace{ - .stack_trace = free_stack_trace, - .tty_config = tty_config, }, + std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, }); } if (alignment != slot_alignment) { @branchHint(.cold); - const tty_config = self.tty_config; log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{ slot_alignment.toByteUnits(), alignment.toByteUnits(), std.debug.FormatStackTrace{ .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), - .tty_config = tty_config, - }, - std.debug.FormatStackTrace{ - .stack_trace = free_stack_trace, - .tty_config = tty_config, }, + std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, }); } } @@ -1040,7 +989,6 @@ pub fn DebugAllocator(comptime config: Config) type { const is_used = @as(u1, @truncate(used_byte.* >> used_bit_index)) != 0; if (!is_used) { reportDoubleFree( - self.tty_config, return_address, bucketStackTrace(bucket, slot_count, slot_index, .alloc), bucketStackTrace(bucket, slot_count, slot_index, .free), @@ -1058,34 +1006,24 @@ pub fn DebugAllocator(comptime config: Config) type { const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); if (memory.len != requested_size) { @branchHint(.cold); - const tty_config = self.tty_config; log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ requested_size, memory.len, std.debug.FormatStackTrace{ .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), - .tty_config = tty_config, - }, - std.debug.FormatStackTrace{ - .stack_trace = free_stack_trace, - .tty_config = tty_config, }, + std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, }); } if (alignment != slot_alignment) { @branchHint(.cold); - const tty_config = self.tty_config; log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{ slot_alignment.toByteUnits(), alignment.toByteUnits(), std.debug.FormatStackTrace{ .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), - .tty_config = tty_config, - }, - std.debug.FormatStackTrace{ - .stack_trace = free_stack_trace, - .tty_config = tty_config, }, + std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, }); } } diff --git a/lib/std/log.zig b/lib/std/log.zig index 461dfca36e7452e09d4b4c98162af75a558ea749..029f724f445dffeb4f13a3d34a25a3efc84e16f5 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -15,7 +15,7 @@ //! //! For an example implementation of the `logFn` function, see `defaultLog`, //! which is the default implementation. It outputs to stderr, using color if -//! the detected `std.Io.tty.Config` supports it. Its output looks like this: +//! supported. Its output looks like this: //! ``` //! error: this is an error //! error(scope): this is an error with a non-default scope @@ -80,8 +80,6 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool { return @intFromEnum(level) <= @intFromEnum(std.options.log_level); } -var static_threaded_io: std.Io.Threaded = .init_single_threaded; - /// The default implementation for the log function. Custom log functions may /// forward log messages to this function. /// @@ -92,37 +90,65 @@ pub fn defaultLog( comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype, -) void { - return defaultLogIo(level, scope, format, args, static_threaded_io.io()); -} - -pub fn defaultLogIo( - comptime level: Level, - comptime scope: @EnumLiteral(), - comptime format: []const u8, - args: anytype, - io: std.Io, ) void { var buffer: [64]u8 = undefined; - const stderr, const ttyconf = io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); - ttyconf.setColor(stderr, switch (level) { + const stderr = std.debug.lockStderrWriter(&buffer); + defer std.debug.unlockStderrWriter(); + return defaultLogFileWriter(level, scope, format, args, stderr); +} + +pub fn defaultLogFileWriter( + comptime level: Level, + comptime scope: @EnumLiteral(), + comptime format: []const u8, + args: anytype, + fw: *std.Io.File.Writer, +) void { + fw.setColor(switch (level) { .err => .red, .warn => .yellow, .info => .green, .debug => .magenta, }) catch {}; - ttyconf.setColor(stderr, .bold) catch {}; - stderr.writeAll(level.asText()) catch return; - ttyconf.setColor(stderr, .reset) catch {}; - ttyconf.setColor(stderr, .dim) catch {}; - ttyconf.setColor(stderr, .bold) catch {}; + fw.setColor(.bold) catch {}; + fw.interface.writeAll(level.asText()) catch return; + fw.setColor(.reset) catch {}; + fw.setColor(.dim) catch {}; + fw.setColor(.bold) catch {}; if (scope != .default) { - stderr.print("({s})", .{@tagName(scope)}) catch return; + fw.interface.print("({s})", .{@tagName(scope)}) catch return; } - stderr.writeAll(": ") catch return; - ttyconf.setColor(stderr, .reset) catch {}; - stderr.print(format ++ "\n", args) catch return; + fw.interface.writeAll(": ") catch return; + fw.setColor(.reset) catch {}; + fw.interface.print(format ++ "\n", decorateArgs(args, fw.mode)) catch return; +} + +fn DecorateArgs(comptime Args: type) type { + const fields = @typeInfo(Args).@"struct".fields; + var new_fields: [fields.len]type = undefined; + for (fields, &new_fields) |old, *new| { + if (old.type == std.debug.FormatStackTrace) { + new.* = std.debug.FormatStackTrace.Decorated; + } else { + new.* = old.type; + } + } + return @Tuple(&new_fields); +} + +fn decorateArgs(args: anytype, file_writer_mode: std.Io.File.Writer.Mode) DecorateArgs(@TypeOf(args)) { + var new_args: DecorateArgs(@TypeOf(args)) = undefined; + inline for (args, &new_args) |old, *new| { + if (@TypeOf(old) == std.debug.FormatStackTrace) { + new.* = .{ + .stack_trace = old.stack_trace, + .file_writer_mode = file_writer_mode, + }; + } else { + new.* = old; + } + } + return new_args; } /// Returns a scoped logging namespace that logs all messages using the scope diff --git a/lib/std/process.zig b/lib/std/process.zig index f7ecf5fdc240eeb3847b94a0b2ede6193acdaeb4..5c6e6f89eb556045b4e03c89892e1a0ff0ef77e9 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -439,25 +439,25 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError } /// On Windows, `key` must be valid WTF-8. -pub fn hasEnvVarConstant(comptime key: []const u8) bool { +pub inline fn hasEnvVarConstant(comptime key: []const u8) bool { if (native_os == .windows) { const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); return getenvW(key_w) != null; } else if (native_os == .wasi and !builtin.link_libc) { - @compileError("hasEnvVarConstant is not supported for WASI without libc"); + return false; } else { return posix.getenv(key) != null; } } /// On Windows, `key` must be valid WTF-8. -pub fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { +pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { if (native_os == .windows) { const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); const value = getenvW(key_w) orelse return false; return value.len != 0; } else if (native_os == .wasi and !builtin.link_libc) { - @compileError("hasNonEmptyEnvVarConstant is not supported for WASI without libc"); + return false; } else { const value = posix.getenv(key) orelse return false; return value.len != 0; diff --git a/src/main.zig b/src/main.zig index bb940a1fe70dd250cb7d8cbc15ee7d40ca51b686..2eebb6d060728935851dd599dd992154c3f7d927 100644 --- a/src/main.zig +++ b/src/main.zig @@ -247,8 +247,6 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { threaded.stack_size = thread_stack_size; const io = threaded.io(); - debug_allocator.tty_config = .detect(io, .stderr()); - const cmd = args[1]; const cmd_args = args[2..]; if (mem.eql(u8, cmd, "build-exe")) { -- 2.54.0 From 3a6e15449b887481a0ed24fc948157c222f0072a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 9 Dec 2025 23:12:09 -0800 Subject: [PATCH 038/195] std.Io: add ResetEvent based on futexes and add futex to the vtable. A future commit may make the other sync primitives based on futexes. --- lib/std/Io/File/Writer.zig | 8 ++++---- lib/std/Progress.zig | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 0e995908c4ac1d521c5ff1fdbc8f13cef551bddb..1ba4e375b668a0b66d98628f6d3bd55079db37a3 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -52,14 +52,14 @@ pub const Mode = union(enum) { return switch (m) { .positional, .positional_simple => .positional_simple, .streaming, .streaming_simple => .streaming_simple, - inline else => |x| x, + inline else => |_, x| x, }; } pub fn toUnescaped(m: @This()) @This() { return switch (m) { .terminal_escaped => .streaming_simple, - inline else => |x| x, + inline else => |_, x| x, }; } @@ -469,13 +469,13 @@ pub fn restoreEscape(w: *Writer, mode: Mode) void { w.mode = mode; } -pub fn writeAllUnescaped(w: *Writer, bytes: []const u8) Io.Error!void { +pub fn writeAllUnescaped(w: *Writer, bytes: []const u8) Io.Writer.Error!void { const prev_mode = w.disableEscape(); defer w.restoreEscape(prev_mode); return w.interface.writeAll(bytes); } -pub fn printUnescaped(w: *Writer, comptime fmt: []const u8, args: anytype) Io.Error!void { +pub fn printUnescaped(w: *Writer, comptime fmt: []const u8, args: anytype) Io.Writer.Error!void { const prev_mode = w.disableEscape(); defer w.restoreEscape(prev_mode); return w.interface.print(fmt, args); diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 799e3f9c9b7b9c4337870b9490a79d4384ac2be1..63fdddc231c33591dcb563257127732107182f02 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -52,6 +52,8 @@ node_freelist: Freelist, /// value may at times temporarily exceed the node count. node_end_index: u32, +start_failure: StartFailure, + pub const Status = enum { /// Indicates the application is progressing towards completion of a task. /// Unless the application is interactive, this is the only status the @@ -448,6 +450,8 @@ const noop_impl = builtin.single_threaded or switch (builtin.os.tag) { /// Asserts there is only one global Progress instance. /// /// Call `Node.end` when done. +/// +/// If an error occurs, `start_failure` will be populated. pub fn start(options: Options, io: Io) Node { // Ensure there is only 1 global Progress object. if (global_progress.node_end_index != 0) { @@ -757,7 +761,7 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) anyerror!void { if (noop_impl or !global_progress.need_clear) return; - try file_writer.interface.writeAllUnescaped(clear ++ progress_remove); + try file_writer.writeAllUnescaped(clear ++ progress_remove); global_progress.need_clear = false; } -- 2.54.0 From 95b0399d1bf7e12aefeba79652141d202f805fe4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 10 Dec 2025 18:14:43 -0800 Subject: [PATCH 039/195] std: finish implementing futexWait with timer --- lib/std/Io/Threaded.zig | 1 - lib/std/Progress.zig | 61 ++++++++++++++++++++++------------------- lib/std/debug.zig | 12 ++++---- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c2a3e15f15b7e78bee124ff522b453175aed3be3..41e93cee5b2642f9670e25eaf07142fe1fdd87dd 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1147,7 +1147,6 @@ const GroupClosure = struct { const group = gc.group; const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); const event: *Io.Event = @ptrCast(&group.context); - current_thread.current_closure = closure; current_thread.cancel_protection = .unblocked; diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 63fdddc231c33591dcb563257127732107182f02..603f53091e1bc51747fdcd4c387bc9ef1863e3db 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -24,7 +24,7 @@ terminal_mode: TerminalMode, update_worker: ?Io.Future(void), /// Atomically set by SIGWINCH as well as the root done() function. -redraw_event: Io.ResetEvent, +redraw_event: Io.Event, /// Indicates a request to shut down and reset global state. /// Accessed atomically. done: bool, @@ -333,8 +333,9 @@ pub const Node = struct { } } else { @atomicStore(bool, &global_progress.done, true, .monotonic); - global_progress.redraw_event.set(); - if (global_progress.update_worker) |worker| worker.await(global_progress.io); + const io = global_progress.io; + global_progress.redraw_event.set(io); + if (global_progress.update_worker) |*worker| worker.await(io); } } @@ -421,7 +422,7 @@ pub const StartFailure = union(enum) { unstarted, spawn_ipc_worker: error{ConcurrencyUnavailable}, spawn_update_worker: error{ConcurrencyUnavailable}, - parse_env_var: error{}, + parse_env_var: error{ InvalidCharacter, Overflow }, }; const node_storage_buffer_len = 83; @@ -452,7 +453,7 @@ const noop_impl = builtin.single_threaded or switch (builtin.os.tag) { /// Call `Node.end` when done. /// /// If an error occurs, `start_failure` will be populated. -pub fn start(options: Options, io: Io) Node { +pub fn start(io: Io, options: Options) Node { // Ensure there is only 1 global Progress object. if (global_progress.node_end_index != 0) { debug_start_trace.dump(); @@ -467,8 +468,8 @@ pub fn start(options: Options, io: Io) Node { assert(options.draw_buffer.len >= 200); global_progress.draw_buffer = options.draw_buffer; - global_progress.refresh_rate_ns = options.refresh_rate_ns; - global_progress.initial_delay_ns = options.initial_delay_ns; + global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds()); + global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds()); if (noop_impl) return Node.none; @@ -541,9 +542,13 @@ pub fn setStatus(new_status: Status) void { } /// Returns whether a resize is needed to learn the terminal size. -fn wait(timeout_ns: u64) bool { - const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_| true else |err| switch (err) { - error.Timeout => false, +fn wait(io: Io, timeout_ns: u64) bool { + const timeout: Io.Timeout = .{ .duration = .{ + .clock = .awake, + .raw = .fromNanoseconds(timeout_ns), + } }; + const resize_flag = if (global_progress.redraw_event.waitTimeout(io, timeout)) |_| true else |err| switch (err) { + error.Timeout, error.Canceled => false, }; global_progress.redraw_event.reset(); return resize_flag or (global_progress.cols == 0); @@ -555,34 +560,34 @@ fn updateThreadRun(io: Io) void { var serialized_buffer: Serialized.Buffer = undefined; { - const resize_flag = wait(global_progress.initial_delay_ns); + const resize_flag = wait(io, global_progress.initial_delay_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; maybeUpdateSize(resize_flag); const buffer, _ = computeRedraw(&serialized_buffer); - if (io.tryLockStderrWriter(&.{})) |w| { + if (io.tryLockStderrWriter(&.{})) |fw| { defer io.unlockStderrWriter(); global_progress.need_clear = true; - w.writeAll(buffer) catch return; + fw.writeAllUnescaped(buffer) catch return; } } while (true) { - const resize_flag = wait(global_progress.refresh_rate_ns); + const resize_flag = wait(io, global_progress.refresh_rate_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) { - const w = io.lockStderrWriter(&.{}) catch return; + const fw = io.lockStderrWriter(&.{}) catch return; defer io.unlockStderrWriter(); - return clearWrittenWithEscapeCodes(w) catch {}; + return clearWrittenWithEscapeCodes(fw) catch {}; } maybeUpdateSize(resize_flag); const buffer, _ = computeRedraw(&serialized_buffer); - if (io.tryLockStderrWriter(&.{})) |w| { + if (io.tryLockStderrWriter(&.{})) |fw| { defer io.unlockStderrWriter(); global_progress.need_clear = true; - w.writeAll(buffer) catch return; + fw.writeAllUnescaped(buffer) catch return; } } } @@ -599,22 +604,22 @@ fn windowsApiUpdateThreadRun(io: Io) void { var serialized_buffer: Serialized.Buffer = undefined; { - const resize_flag = wait(global_progress.initial_delay_ns); + const resize_flag = wait(io, global_progress.initial_delay_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; maybeUpdateSize(resize_flag); const buffer, const nl_n = computeRedraw(&serialized_buffer); - if (io.tryLockStderrWriter()) |w| { + if (io.tryLockStderrWriter()) |fw| { defer io.unlockStderrWriter(); windowsApiWriteMarker(); global_progress.need_clear = true; - w.writeAll(buffer) catch return; + fw.writeAllUnescaped(buffer) catch return; windowsApiMoveToMarker(nl_n) catch return; } } while (true) { - const resize_flag = wait(global_progress.refresh_rate_ns); + const resize_flag = wait(io, global_progress.refresh_rate_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) { _ = io.lockStderrWriter() catch return; @@ -625,24 +630,24 @@ fn windowsApiUpdateThreadRun(io: Io) void { maybeUpdateSize(resize_flag); const buffer, const nl_n = computeRedraw(&serialized_buffer); - if (io.tryLockStderrWriter()) |w| { + if (io.tryLockStderrWriter()) |fw| { defer io.unlockStderrWriter(); clearWrittenWindowsApi() catch return; windowsApiWriteMarker(); global_progress.need_clear = true; - w.writeAll(buffer) catch return; + fw.writeAllUnescaped(buffer) catch return; windowsApiMoveToMarker(nl_n) catch return; } } } -fn ipcThreadRun(io: Io, file: Io.File) anyerror!void { +fn ipcThreadRun(io: Io, file: Io.File) void { // Store this data in the thread so that it does not need to be part of the // linker data of the main executable. var serialized_buffer: Serialized.Buffer = undefined; { - _ = wait(global_progress.initial_delay_ns); + _ = wait(io, global_progress.initial_delay_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; @@ -654,7 +659,7 @@ fn ipcThreadRun(io: Io, file: Io.File) anyerror!void { } while (true) { - _ = wait(global_progress.refresh_rate_ns); + _ = wait(io, global_progress.refresh_rate_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; @@ -1504,7 +1509,7 @@ fn handleSigWinch(sig: posix.SIG, info: *const posix.siginfo_t, ctx_ptr: ?*anyop _ = info; _ = ctx_ptr; assert(sig == .WINCH); - global_progress.redraw_event.set(); + global_progress.redraw_event.set(global_progress.io); } const have_sigwinch = switch (builtin.os.tag) { diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 347dbf85ecc52787fd9a492651c8f05ca415cec7..69d8c942c41cca89ad3dcadc304dbed1e1c3753a 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -816,9 +816,9 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer. } /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. pub fn dumpStackTrace(st: *const StackTrace) void { - const stderr, const tty_config = lockStderrWriter(&.{}); + const stderr = lockStderrWriter(&.{}); defer unlockStderrWriter(); - writeStackTrace(st, stderr, tty_config) catch |err| switch (err) { + writeStackTrace(st, &stderr.interface, stderr.mode) catch |err| switch (err) { error.WriteFailed => {}, }; } @@ -1682,21 +1682,21 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize pub fn dump(t: @This()) void { if (!enabled) return; - const stderr, const tty_config = lockStderrWriter(&.{}); + const stderr = lockStderrWriter(&.{}); defer unlockStderrWriter(); const end = @min(t.index, size); for (t.addrs[0..end], 0..) |frames_array, i| { - stderr.print("{s}:\n", .{t.notes[i]}) catch return; + stderr.interface.print("{s}:\n", .{t.notes[i]}) catch return; var frames_array_mutable = frames_array; const frames = mem.sliceTo(frames_array_mutable[0..], 0); const stack_trace: StackTrace = .{ .index = frames.len, .instruction_addresses = frames, }; - writeStackTrace(&stack_trace, stderr, tty_config) catch return; + writeStackTrace(&stack_trace, &stderr.interface, stderr.mode) catch return; } if (t.index > end) { - stderr.print("{d} more traces not shown; consider increasing trace size\n", .{ + stderr.interface.print("{d} more traces not shown; consider increasing trace size\n", .{ t.index - end, }) catch return; } -- 2.54.0 From 7837d975dcaa17ff22f536607c4ff6db7b697b04 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 10 Dec 2025 18:25:23 -0800 Subject: [PATCH 040/195] std: save terminal escape stripping for later --- lib/std/Io/File/Writer.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 1ba4e375b668a0b66d98628f6d3bd55079db37a3..8f9f573a4bf624dcab9b7646ed175ca916ca7866 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -300,12 +300,12 @@ fn drainEscaping(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.E const header = w.interface.buffered(); if (findTerminalEscape(header)) |i| { _ = i; - @panic("TODO strip terminal escape sequence"); + // TODO strip terminal escape sequences here } for (data) |d| { if (findTerminalEscape(d)) |i| { _ = i; - @panic("TODO strip terminal escape sequence"); + // TODO strip terminal escape sequences here } } const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| { -- 2.54.0 From b042e935228db5d46271d4d3d17afeb9ba5d7ce3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 10 Dec 2025 20:55:11 -0800 Subject: [PATCH 041/195] std: update tty config references in the build system --- lib/compiler/build_runner.zig | 208 ++++++++++++++++----------------- lib/std/Build.zig | 60 +++++----- lib/std/Build/Fuzz.zig | 20 ++-- lib/std/Build/Step.zig | 9 +- lib/std/Build/Step/Compile.zig | 10 +- lib/std/Build/Step/Run.zig | 68 ++++++----- lib/std/Build/WebServer.zig | 4 - lib/std/Io/File/Writer.zig | 41 ++++++- lib/std/debug.zig | 2 +- lib/std/log.zig | 30 +---- lib/std/testing.zig | 42 +++---- lib/std/zig.zig | 12 +- lib/std/zig/ErrorBundle.zig | 59 +++++----- 13 files changed, 271 insertions(+), 294 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 05cd21ecdb07d244741964ef86c1506e3b824b17..acba1a4621a43047250b4f6b73e5961c56cfa777 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -14,7 +14,6 @@ const WebServer = std.Build.WebServer; const Allocator = std.mem.Allocator; const fatal = std.process.fatal; const Writer = std.Io.Writer; -const tty = std.Io.tty; pub const root = @import("@build"); pub const dependencies = @import("@dependencies"); @@ -435,9 +434,7 @@ pub fn main() !void { if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{}); } - const ttyconf = color.detectTtyConf(io); - - const main_progress_node = std.Progress.start(.{ + const main_progress_node = std.Progress.start(io, .{ .disable_printing = (color == .off), }); defer main_progress_node.end(); @@ -509,8 +506,6 @@ pub fn main() !void { .error_style = error_style, .multiline_errors = multiline_errors, .summary = summary orelse if (watch or webui_listen != null) .line else .failures, - - .ttyconf = ttyconf, }; defer { run.memory_blocked_steps.deinit(gpa); @@ -524,10 +519,10 @@ pub fn main() !void { prepare(arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) { error.DependencyLoopDetected => { - // Perhaps in the future there could be an Advanced Options flag such as - // --debug-build-runner-leaks which would make this code return instead of - // calling exit. - std.debug.lockStdErr(); + // Perhaps in the future there could be an Advanced Options flag + // such as --debug-build-runner-leaks which would make this code + // return instead of calling exit. + _ = std.debug.lockStderrWriter(&.{}); process.exit(1); }, else => |e| return e, @@ -545,7 +540,6 @@ pub fn main() !void { if (builtin.single_threaded) unreachable; // `fatal` above break :ws .init(.{ .gpa = gpa, - .ttyconf = ttyconf, .graph = &graph, .all_steps = run.step_stack.keys(), .root_prog_node = main_progress_node, @@ -560,9 +554,9 @@ pub fn main() !void { } rebuild: while (true) : (if (run.error_style.clearOnUpdate()) { - const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation); + const stderr = std.debug.lockStderrWriter(&stdio_buffer_allocation); defer std.debug.unlockStderrWriter(); - try bw.writeAll("\x1B[2J\x1B[3J\x1B[H"); + try stderr.writeAllUnescaped("\x1B[2J\x1B[3J\x1B[H"); }) { if (run.web_server) |*ws| ws.startBuild(); @@ -663,9 +657,6 @@ const Run = struct { memory_blocked_steps: std.ArrayList(*Step), /// Allocated into `gpa`. step_stack: std.AutoArrayHashMapUnmanaged(*Step, void), - /// Similar to the `tty.Config` returned by `std.debug.lockStderrWriter`, - /// but also respects the '--color' flag. - ttyconf: tty.Config, claimed_rss: usize, error_style: ErrorStyle, @@ -839,7 +830,6 @@ fn runStepNames( var f = std.Build.Fuzz.init( gpa, io, - run.ttyconf, step_stack.keys(), parent_prog_node, mode, @@ -866,18 +856,20 @@ fn runStepNames( .none => break :summary, } - const w, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation); + const stderr = std.debug.lockStderrWriter(&stdio_buffer_allocation); defer std.debug.unlockStderrWriter(); - const ttyconf = run.ttyconf; + + const w = &stderr.interface; + const fwm = stderr.mode; const total_count = success_count + failure_count + pending_count + skipped_count; - ttyconf.setColor(w, .cyan) catch {}; - ttyconf.setColor(w, .bold) catch {}; + fwm.setColor(w, .cyan) catch {}; + fwm.setColor(w, .bold) catch {}; w.writeAll("Build Summary: ") catch {}; - ttyconf.setColor(w, .reset) catch {}; + fwm.setColor(w, .reset) catch {}; w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {}; { - ttyconf.setColor(w, .dim) catch {}; + fwm.setColor(w, .dim) catch {}; var first = true; if (skipped_count > 0) { w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {}; @@ -888,12 +880,12 @@ fn runStepNames( first = false; } if (!first) w.writeByte(')') catch {}; - ttyconf.setColor(w, .reset) catch {}; + fwm.setColor(w, .reset) catch {}; } if (test_count > 0) { w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {}; - ttyconf.setColor(w, .dim) catch {}; + fwm.setColor(w, .dim) catch {}; var first = true; if (test_skip_count > 0) { w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {}; @@ -912,7 +904,7 @@ fn runStepNames( first = false; } if (!first) w.writeByte(')') catch {}; - ttyconf.setColor(w, .reset) catch {}; + fwm.setColor(w, .reset) catch {}; } w.writeAll("\n") catch {}; @@ -926,7 +918,7 @@ fn runStepNames( var print_node: PrintNode = .{ .parent = null }; if (step_names.len == 0) { print_node.last = true; - printTreeStep(b, b.default_step, run, w, ttyconf, &print_node, &step_stack_copy) catch {}; + printTreeStep(b, b.default_step, run, w, fwm, &print_node, &step_stack_copy) catch {}; } else { const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: { var i: usize = step_names.len; @@ -945,7 +937,7 @@ fn runStepNames( for (step_names, 0..) |step_name, i| { const tls = b.top_level_steps.get(step_name).?; print_node.last = i + 1 == last_index; - printTreeStep(b, &tls.step, run, w, ttyconf, &print_node, &step_stack_copy) catch {}; + printTreeStep(b, &tls.step, run, w, fwm, &print_node, &step_stack_copy) catch {}; } } w.writeByte('\n') catch {}; @@ -962,7 +954,7 @@ fn runStepNames( if (run.error_style.verboseContext()) break :code 1; // failure; print build command break :code 2; // failure; do not print build command }; - std.debug.lockStdErr(); + _ = std.debug.lockStderrWriter(&.{}); process.exit(code); } @@ -971,31 +963,31 @@ const PrintNode = struct { last: bool = false, }; -fn printPrefix(node: *PrintNode, stderr: *Writer, ttyconf: tty.Config) !void { +fn printPrefix(node: *PrintNode, w: *Writer, fwm: File.Writer.Mode) !void { const parent = node.parent orelse return; if (parent.parent == null) return; - try printPrefix(parent, stderr, ttyconf); + try printPrefix(parent, w, fwm); if (parent.last) { - try stderr.writeAll(" "); + try w.writeAll(" "); } else { - try stderr.writeAll(switch (ttyconf) { - .no_color, .windows_api => "| ", - .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ + try w.writeAll(switch (fwm) { + .terminal_escaped => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ + else => "| ", }); } } -fn printChildNodePrefix(stderr: *Writer, ttyconf: tty.Config) !void { - try stderr.writeAll(switch (ttyconf) { - .no_color, .windows_api => "+- ", - .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─ +fn printChildNodePrefix(w: *Writer, fwm: File.Writer.Mode) !void { + try w.writeAll(switch (fwm) { + .terminal_escaped => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─ + else => "+- ", }); } fn printStepStatus( s: *Step, stderr: *Writer, - ttyconf: tty.Config, + fwm: File.Writer.Mode, run: *const Run, ) !void { switch (s.state) { @@ -1005,13 +997,13 @@ fn printStepStatus( .running => unreachable, .dependency_failure => { - try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .dim); try stderr.writeAll(" transitive failure\n"); - try ttyconf.setColor(stderr, .reset); + try fwm.setColor(stderr, .reset); }, .success => { - try ttyconf.setColor(stderr, .green); + try fwm.setColor(stderr, .green); if (s.result_cached) { try stderr.writeAll(" cached"); } else if (s.test_results.test_count > 0) { @@ -1019,19 +1011,19 @@ fn printStepStatus( assert(s.test_results.test_count == pass_count + s.test_results.skip_count); try stderr.print(" {d} pass", .{pass_count}); if (s.test_results.skip_count > 0) { - try ttyconf.setColor(stderr, .reset); + try fwm.setColor(stderr, .reset); try stderr.writeAll(", "); - try ttyconf.setColor(stderr, .yellow); + try fwm.setColor(stderr, .yellow); try stderr.print("{d} skip", .{s.test_results.skip_count}); } - try ttyconf.setColor(stderr, .reset); + try fwm.setColor(stderr, .reset); try stderr.print(" ({d} total)", .{s.test_results.test_count}); } else { try stderr.writeAll(" success"); } - try ttyconf.setColor(stderr, .reset); + try fwm.setColor(stderr, .reset); if (s.result_duration_ns) |ns| { - try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .dim); if (ns >= std.time.ns_per_min) { try stderr.print(" {d}m", .{ns / std.time.ns_per_min}); } else if (ns >= std.time.ns_per_s) { @@ -1043,11 +1035,11 @@ fn printStepStatus( } else { try stderr.print(" {d}ns", .{ns}); } - try ttyconf.setColor(stderr, .reset); + try fwm.setColor(stderr, .reset); } if (s.result_peak_rss != 0) { const rss = s.result_peak_rss; - try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .dim); if (rss >= 1000_000_000) { try stderr.print(" MaxRSS:{d}G", .{rss / 1000_000_000}); } else if (rss >= 1000_000) { @@ -1057,25 +1049,25 @@ fn printStepStatus( } else { try stderr.print(" MaxRSS:{d}B", .{rss}); } - try ttyconf.setColor(stderr, .reset); + try fwm.setColor(stderr, .reset); } try stderr.writeAll("\n"); }, .skipped, .skipped_oom => |skip| { - try ttyconf.setColor(stderr, .yellow); + try fwm.setColor(stderr, .yellow); try stderr.writeAll(" skipped"); if (skip == .skipped_oom) { try stderr.writeAll(" (not enough memory)"); - try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .dim); try stderr.print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss }); - try ttyconf.setColor(stderr, .yellow); + try fwm.setColor(stderr, .yellow); } try stderr.writeAll("\n"); - try ttyconf.setColor(stderr, .reset); + try fwm.setColor(stderr, .reset); }, .failure => { - try printStepFailure(s, stderr, ttyconf, false); - try ttyconf.setColor(stderr, .reset); + try printStepFailure(s, stderr, fwm, false); + try fwm.setColor(stderr, .reset); }, } } @@ -1083,48 +1075,48 @@ fn printStepStatus( fn printStepFailure( s: *Step, stderr: *Writer, - ttyconf: tty.Config, + fwm: File.Writer.Mode, dim: bool, ) !void { if (s.result_error_bundle.errorMessageCount() > 0) { - try ttyconf.setColor(stderr, .red); + try fwm.setColor(stderr, .red); try stderr.print(" {d} errors\n", .{ s.result_error_bundle.errorMessageCount(), }); } else if (!s.test_results.isSuccess()) { // These first values include all of the test "statuses". Every test is either passsed, // skipped, failed, crashed, or timed out. - try ttyconf.setColor(stderr, .green); + try fwm.setColor(stderr, .green); try stderr.print(" {d} pass", .{s.test_results.passCount()}); - try ttyconf.setColor(stderr, .reset); - if (dim) try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .reset); + if (dim) try fwm.setColor(stderr, .dim); if (s.test_results.skip_count > 0) { try stderr.writeAll(", "); - try ttyconf.setColor(stderr, .yellow); + try fwm.setColor(stderr, .yellow); try stderr.print("{d} skip", .{s.test_results.skip_count}); - try ttyconf.setColor(stderr, .reset); - if (dim) try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .reset); + if (dim) try fwm.setColor(stderr, .dim); } if (s.test_results.fail_count > 0) { try stderr.writeAll(", "); - try ttyconf.setColor(stderr, .red); + try fwm.setColor(stderr, .red); try stderr.print("{d} fail", .{s.test_results.fail_count}); - try ttyconf.setColor(stderr, .reset); - if (dim) try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .reset); + if (dim) try fwm.setColor(stderr, .dim); } if (s.test_results.crash_count > 0) { try stderr.writeAll(", "); - try ttyconf.setColor(stderr, .red); + try fwm.setColor(stderr, .red); try stderr.print("{d} crash", .{s.test_results.crash_count}); - try ttyconf.setColor(stderr, .reset); - if (dim) try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .reset); + if (dim) try fwm.setColor(stderr, .dim); } if (s.test_results.timeout_count > 0) { try stderr.writeAll(", "); - try ttyconf.setColor(stderr, .red); + try fwm.setColor(stderr, .red); try stderr.print("{d} timeout", .{s.test_results.timeout_count}); - try ttyconf.setColor(stderr, .reset); - if (dim) try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .reset); + if (dim) try fwm.setColor(stderr, .dim); } try stderr.print(" ({d} total)", .{s.test_results.test_count}); @@ -1134,10 +1126,10 @@ fn printStepFailure( // 2 pass, 1 skip, 2 fail (5 total); 2 leaks if (s.test_results.leak_count > 0) { try stderr.writeAll("; "); - try ttyconf.setColor(stderr, .red); + try fwm.setColor(stderr, .red); try stderr.print("{d} leaks", .{s.test_results.leak_count}); - try ttyconf.setColor(stderr, .reset); - if (dim) try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .reset); + if (dim) try fwm.setColor(stderr, .dim); } // It's usually not helpful to know how many error logs there were because they tend to @@ -1151,19 +1143,19 @@ fn printStepFailure( }; if (show_err_logs) { try stderr.writeAll("; "); - try ttyconf.setColor(stderr, .red); + try fwm.setColor(stderr, .red); try stderr.print("{d} error logs", .{s.test_results.log_err_count}); - try ttyconf.setColor(stderr, .reset); - if (dim) try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .reset); + if (dim) try fwm.setColor(stderr, .dim); } try stderr.writeAll("\n"); } else if (s.result_error_msgs.items.len > 0) { - try ttyconf.setColor(stderr, .red); + try fwm.setColor(stderr, .red); try stderr.writeAll(" failure\n"); } else { assert(s.result_stderr.len > 0); - try ttyconf.setColor(stderr, .red); + try fwm.setColor(stderr, .red); try stderr.writeAll(" stderr\n"); } } @@ -1173,7 +1165,7 @@ fn printTreeStep( s: *Step, run: *const Run, stderr: *Writer, - ttyconf: tty.Config, + fwm: File.Writer.Mode, parent_node: *PrintNode, step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), ) !void { @@ -1186,26 +1178,26 @@ fn printTreeStep( .failures => s.state == .success, }; if (skip) return; - try printPrefix(parent_node, stderr, ttyconf); + try printPrefix(parent_node, stderr, fwm); if (parent_node.parent != null) { if (parent_node.last) { - try printChildNodePrefix(stderr, ttyconf); + try printChildNodePrefix(stderr, fwm); } else { - try stderr.writeAll(switch (ttyconf) { - .no_color, .windows_api => "+- ", - .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ + try stderr.writeAll(switch (fwm) { + .terminal_escaped => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ + else => "+- ", }); } } - if (!first) try ttyconf.setColor(stderr, .dim); + if (!first) try fwm.setColor(stderr, .dim); // dep_prefix omitted here because it is redundant with the tree. try stderr.writeAll(s.name); if (first) { - try printStepStatus(s, stderr, ttyconf, run); + try printStepStatus(s, stderr, fwm, run); const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: { var i: usize = s.dependencies.items.len; @@ -1227,7 +1219,7 @@ fn printTreeStep( .parent = parent_node, .last = i == last_index, }; - try printTreeStep(b, dep, run, stderr, ttyconf, &print_node, step_stack); + try printTreeStep(b, dep, run, stderr, fwm, &print_node, step_stack); } } else { if (s.dependencies.items.len == 0) { @@ -1237,7 +1229,7 @@ fn printTreeStep( s.dependencies.items.len, }); } - try ttyconf.setColor(stderr, .reset); + try fwm.setColor(stderr, .reset); } } @@ -1368,7 +1360,6 @@ fn workerMakeOneStep( .progress_node = sub_prog_node, .watch = run.watch, .web_server = if (run.web_server) |*ws| ws else null, - .ttyconf = run.ttyconf, .unit_test_timeout_ns = run.unit_test_timeout_ns, .gpa = gpa, }); @@ -1378,10 +1369,9 @@ fn workerMakeOneStep( const show_error_msgs = s.result_error_msgs.items.len > 0; const show_stderr = s.result_stderr.len > 0; if (show_error_msgs or show_compile_errors or show_stderr) { - const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation); + const stderr = std.debug.lockStderrWriter(&stdio_buffer_allocation); defer std.debug.unlockStderrWriter(); - const ttyconf = run.ttyconf; - printErrorMessages(gpa, s, .{}, bw, ttyconf, run.error_style, run.multiline_errors) catch {}; + printErrorMessages(gpa, s, .{}, &stderr.interface, stderr.mode, run.error_style, run.multiline_errors) catch {}; } handle_result: { @@ -1449,7 +1439,7 @@ pub fn printErrorMessages( failing_step: *Step, options: std.zig.ErrorBundle.RenderOptions, stderr: *Writer, - ttyconf: tty.Config, + fwm: File.Writer.Mode, error_style: ErrorStyle, multiline_errors: MultilineErrors, ) !void { @@ -1464,29 +1454,29 @@ pub fn printErrorMessages( } // Now, `step_stack` has the subtree that we want to print, in reverse order. - try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .dim); var indent: usize = 0; while (step_stack.pop()) |s| : (indent += 1) { if (indent > 0) { try stderr.splatByteAll(' ', (indent - 1) * 3); - try printChildNodePrefix(stderr, ttyconf); + try printChildNodePrefix(stderr, fwm); } try stderr.writeAll(s.name); if (s == failing_step) { - try printStepFailure(s, stderr, ttyconf, true); + try printStepFailure(s, stderr, fwm, true); } else { try stderr.writeAll("\n"); } } - try ttyconf.setColor(stderr, .reset); + try fwm.setColor(stderr, .reset); } else { // Just print the failing step itself. - try ttyconf.setColor(stderr, .dim); + try fwm.setColor(stderr, .dim); try stderr.writeAll(failing_step.name); - try printStepFailure(failing_step, stderr, ttyconf, true); - try ttyconf.setColor(stderr, .reset); + try printStepFailure(failing_step, stderr, fwm, true); + try fwm.setColor(stderr, .reset); } if (failing_step.result_stderr.len > 0) { @@ -1496,12 +1486,12 @@ pub fn printErrorMessages( } } - try failing_step.result_error_bundle.renderToWriter(options, stderr, ttyconf); + try failing_step.result_error_bundle.renderToWriter(options, stderr, fwm); for (failing_step.result_error_msgs.items) |msg| { - try ttyconf.setColor(stderr, .red); + try fwm.setColor(stderr, .red); try stderr.writeAll("error:"); - try ttyconf.setColor(stderr, .reset); + try fwm.setColor(stderr, .reset); if (std.mem.indexOfScalar(u8, msg, '\n') == null) { try stderr.print(" {s}\n", .{msg}); } else switch (multiline_errors) { @@ -1519,9 +1509,9 @@ pub fn printErrorMessages( if (error_style.verboseContext()) { if (failing_step.result_failed_command) |cmd_str| { - try ttyconf.setColor(stderr, .red); + try fwm.setColor(stderr, .red); try stderr.writeAll("failed command: "); - try ttyconf.setColor(stderr, .reset); + try fwm.setColor(stderr, .reset); try stderr.writeAll(cmd_str); try stderr.writeByte('\n'); } diff --git a/lib/std/Build.zig b/lib/std/Build.zig index ae2ab1c4d015b0018ef0325c1d22c94dcae3df96..085254fdde048aa7ba5e49d298aac5d3ba4efd3e 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -5,9 +5,8 @@ const std = @import("std.zig"); const Io = std.Io; const fs = std.fs; const mem = std.mem; -const debug = std.debug; const panic = std.debug.panic; -const assert = debug.assert; +const assert = std.debug.assert; const log = std.log; const StringHashMap = std.StringHashMap; const Allocator = std.mem.Allocator; @@ -2090,7 +2089,7 @@ pub fn dependencyFromBuildZig( } const full_path = b.pathFromRoot("build.zig.zon"); - debug.panic("'{}' is not a build.zig struct of a dependency in '{s}'", .{ build_zig, full_path }); + std.debug.panic("'{}' is not a build.zig struct of a dependency in '{s}'", .{ build_zig, full_path }); } fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool { @@ -2249,9 +2248,9 @@ pub const GeneratedFile = struct { pub fn getPath2(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) []const u8 { return gen.path orelse { - const w, const ttyconf = debug.lockStderrWriter(&.{}); - dumpBadGetPathHelp(gen.step, w, ttyconf, src_builder, asking_step) catch {}; - debug.unlockStderrWriter(); + const stderr = std.debug.lockStderrWriter(&.{}); + dumpBadGetPathHelp(gen.step, &stderr.interface, stderr.mode, src_builder, asking_step) catch {}; + std.debug.unlockStderrWriter(); @panic("misconfigured build script"); }; } @@ -2458,9 +2457,9 @@ pub const LazyPath = union(enum) { var file_path: Cache.Path = .{ .root_dir = Cache.Directory.cwd(), .sub_path = gen.file.path orelse { - const w, const ttyconf = debug.lockStderrWriter(&.{}); - dumpBadGetPathHelp(gen.file.step, w, ttyconf, src_builder, asking_step) catch {}; - debug.unlockStderrWriter(); + const stderr = std.debug.lockStderrWriter(&.{}); + dumpBadGetPathHelp(gen.file.step, &stderr.interface, stderr.mode, src_builder, asking_step) catch {}; + std.debug.unlockStderrWriter(); @panic("misconfigured build script"); }, }; @@ -2550,37 +2549,40 @@ fn dumpBadDirnameHelp( comptime msg: []const u8, args: anytype, ) anyerror!void { - const w, const tty_config = debug.lockStderrWriter(&.{}); - defer debug.unlockStderrWriter(); + const stderr = std.debug.lockStderrWriter(&.{}); + defer std.debug.unlockStderrWriter(); + + const w = &stderr.interface; + const fwm = stderr.mode; try w.print(msg, args); if (fail_step) |s| { - tty_config.setColor(w, .red) catch {}; + fwm.setColor(w, .red) catch {}; try w.writeAll(" The step was created by this stack trace:\n"); - tty_config.setColor(w, .reset) catch {}; + fwm.setColor(w, .reset) catch {}; - s.dump(w, tty_config); + s.dump(w, fwm); } if (asking_step) |as| { - tty_config.setColor(w, .red) catch {}; + fwm.setColor(w, .red) catch {}; try w.print(" The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name}); - tty_config.setColor(w, .reset) catch {}; + fwm.setColor(w, .reset) catch {}; - as.dump(w, tty_config); + as.dump(w, fwm); } - tty_config.setColor(w, .red) catch {}; + fwm.setColor(w, .red) catch {}; try w.writeAll(" Hope that helps. Proceeding to panic.\n"); - tty_config.setColor(w, .reset) catch {}; + fwm.setColor(w, .reset) catch {}; } /// In this function the stderr mutex has already been locked. pub fn dumpBadGetPathHelp( s: *Step, - w: *std.Io.Writer, - tty_config: std.Io.tty.Config, + w: *Io.Writer, + fwm: File.Writer.Mode, src_builder: *Build, asking_step: ?*Step, ) anyerror!void { @@ -2594,21 +2596,21 @@ pub fn dumpBadGetPathHelp( s.name, }); - tty_config.setColor(w, .red) catch {}; + fwm.setColor(w, .red) catch {}; try w.writeAll(" The step was created by this stack trace:\n"); - tty_config.setColor(w, .reset) catch {}; + fwm.setColor(w, .reset) catch {}; - s.dump(w, tty_config); + s.dump(w, fwm); if (asking_step) |as| { - tty_config.setColor(w, .red) catch {}; + fwm.setColor(w, .red) catch {}; try w.print(" The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name}); - tty_config.setColor(w, .reset) catch {}; + fwm.setColor(w, .reset) catch {}; - as.dump(w, tty_config); + as.dump(w, fwm); } - tty_config.setColor(w, .red) catch {}; + fwm.setColor(w, .red) catch {}; try w.writeAll(" Hope that helps. Proceeding to panic.\n"); - tty_config.setColor(w, .reset) catch {}; + fwm.setColor(w, .reset) catch {}; } pub const InstallDir = union(enum) { diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index 8837d7d5270ff2ddad7808e2077c4d212c526557..c2b7d1e9e9c0e474a44b710ca3a4f3ac8f9d7fc8 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -9,14 +9,12 @@ const Allocator = std.mem.Allocator; const log = std.log; const Coverage = std.debug.Coverage; const abi = Build.abi.fuzz; -const tty = std.Io.tty; const Fuzz = @This(); const build_runner = @import("root"); gpa: Allocator, io: Io, -ttyconf: tty.Config, mode: Mode, /// Allocated into `gpa`. @@ -77,7 +75,6 @@ const CoverageMap = struct { pub fn init( gpa: Allocator, io: Io, - ttyconf: tty.Config, all_steps: []const *Build.Step, root_prog_node: std.Progress.Node, mode: Mode, @@ -95,7 +92,7 @@ pub fn init( if (run.producer == null) continue; if (run.fuzz_tests.items.len == 0) continue; try steps.append(gpa, run); - rebuild_group.async(io, rebuildTestsWorkerRun, .{ run, gpa, ttyconf, rebuild_node }); + rebuild_group.async(io, rebuildTestsWorkerRun, .{ run, gpa, rebuild_node }); } if (steps.items.len == 0) fatal("no fuzz tests found", .{}); @@ -115,7 +112,6 @@ pub fn init( return .{ .gpa = gpa, .io = io, - .ttyconf = ttyconf, .mode = mode, .run_steps = run_steps, .group = .init, @@ -154,14 +150,14 @@ pub fn deinit(fuzz: *Fuzz) void { fuzz.gpa.free(fuzz.run_steps); } -fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, ttyconf: tty.Config, parent_prog_node: std.Progress.Node) void { - rebuildTestsWorkerRunFallible(run, gpa, ttyconf, parent_prog_node) catch |err| { +fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) void { + rebuildTestsWorkerRunFallible(run, gpa, parent_prog_node) catch |err| { const compile = run.producer.?; log.err("step '{s}': failed to rebuild in fuzz mode: {t}", .{ compile.step.name, err }); }; } -fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: tty.Config, parent_prog_node: std.Progress.Node) !void { +fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) !void { const compile = run.producer.?; const prog_node = parent_prog_node.start(compile.step.name, 0); defer prog_node.end(); @@ -174,9 +170,9 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: tty.Co if (show_error_msgs or show_compile_errors or show_stderr) { var buf: [256]u8 = undefined; - const w, _ = std.debug.lockStderrWriter(&buf); + const stderr = std.debug.lockStderrWriter(&buf); defer std.debug.unlockStderrWriter(); - build_runner.printErrorMessages(gpa, &compile.step, .{}, w, ttyconf, .verbose, .indent) catch {}; + build_runner.printErrorMessages(gpa, &compile.step, .{}, &stderr.interface, stderr.mode, .verbose, .indent) catch {}; } const rebuilt_bin_path = result catch |err| switch (err) { @@ -200,9 +196,9 @@ fn fuzzWorkerRun( run.rerunInFuzzMode(fuzz, unit_test_index, prog_node) catch |err| switch (err) { error.MakeFailed => { var buf: [256]u8 = undefined; - const w, _ = std.debug.lockStderrWriter(&buf); + const stderr = std.debug.lockStderrWriter(&buf); defer std.debug.unlockStderrWriter(); - build_runner.printErrorMessages(gpa, &run.step, .{}, w, fuzz.ttyconf, .verbose, .indent) catch {}; + build_runner.printErrorMessages(gpa, &run.step, .{}, &stderr.interface, stderr.mode, .verbose, .indent) catch {}; return; }, else => { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 9c7fcc757f0ebc2c95e4c5486c728af611beeb00..0df58b24b78a7a0ab1be7efdf514406fafd57786 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -117,7 +117,6 @@ pub const MakeOptions = struct { // it currently breaks because `std.net.Address` doesn't work there. Work around for now. .wasm32 => void, }, - ttyconf: std.Io.tty.Config, /// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds. unit_test_timeout_ns: ?u64, /// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`. @@ -329,16 +328,16 @@ pub fn cast(step: *Step, comptime T: type) ?*T { } /// For debugging purposes, prints identifying information about this Step. -pub fn dump(step: *Step, w: *Io.Writer, tty_config: Io.tty.Config) void { +pub fn dump(step: *Step, w: *Io.Writer, fwm: Io.File.Writer.Mode) void { if (step.debug_stack_trace.instruction_addresses.len > 0) { w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {}; - std.debug.writeStackTrace(&step.debug_stack_trace, w, tty_config) catch {}; + std.debug.writeStackTrace(&step.debug_stack_trace, w, fwm) catch {}; } else { const field = "debug_stack_frames_count"; comptime assert(@hasField(Build, field)); - tty_config.setColor(w, .yellow) catch {}; + fwm.setColor(w, .yellow) catch {}; w.print("name: '{s}'. no stack trace collected for this step, see std.Build." ++ field ++ "\n", .{step.name}) catch {}; - tty_config.setColor(w, .reset) catch {}; + fwm.setColor(w, .reset) catch {}; } } diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index dfa54609810b2f938c4fc531ede30d45982ddf13..d703e55b8727ad7fe2e575f059e676d100099d59 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -926,15 +926,15 @@ fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking const maybe_path: ?*GeneratedFile = @field(compile, tag_name); const generated_file = maybe_path orelse { - const w, const ttyconf = std.debug.lockStderrWriter(&.{}); - std.Build.dumpBadGetPathHelp(&compile.step, w, ttyconf, compile.step.owner, asking_step) catch {}; + const stderr = std.debug.lockStderrWriter(&.{}); + std.Build.dumpBadGetPathHelp(&compile.step, &stderr.interface, stderr.mode, compile.step.owner, asking_step) catch {}; std.debug.unlockStderrWriter(); @panic("missing emit option for " ++ tag_name); }; const path = generated_file.path orelse { - const w, const ttyconf = std.debug.lockStderrWriter(&.{}); - std.Build.dumpBadGetPathHelp(&compile.step, w, ttyconf, compile.step.owner, asking_step) catch {}; + const stderr = std.debug.lockStderrWriter(&.{}); + std.Build.dumpBadGetPathHelp(&compile.step, &stderr.interface, stderr.mode, compile.step.owner, asking_step) catch {}; std.debug.unlockStderrWriter(); @panic(tag_name ++ " is null. Is there a missing step dependency?"); }; @@ -1904,7 +1904,7 @@ fn checkCompileErrors(compile: *Compile) !void { try actual_eb.renderToWriter(.{ .include_reference_trace = false, .include_source_line = false, - }, &aw.writer, .no_color); + }, &aw.writer, .streaming); break :ae try aw.toOwnedSlice(); }; diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 95024061d81317ab180ee7ceb4546a98b05d1258..8aafc778205f120d49986b7f7a90fb8385ebcb99 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -5,7 +5,7 @@ const std = @import("std"); const Io = std.Io; const Build = std.Build; const Step = std.Build.Step; -const fs = std.fs; +const Dir = std.Io.Dir; const mem = std.mem; const process = std.process; const EnvMap = std.process.EnvMap; @@ -26,19 +26,7 @@ cwd: ?Build.LazyPath, env_map: ?*EnvMap, /// Controls the `NO_COLOR` and `CLICOLOR_FORCE` environment variables. -color: enum { - /// `CLICOLOR_FORCE` is set, and `NO_COLOR` is unset. - enable, - /// `NO_COLOR` is set, and `CLICOLOR_FORCE` is unset. - disable, - /// If the build runner is using color, equivalent to `.enable`. Otherwise, equivalent to `.disable`. - inherit, - /// If stderr is captured or checked, equivalent to `.disable`. Otherwise, equivalent to `.inherit`. - auto, - /// The build runner does not modify the `CLICOLOR_FORCE` or `NO_COLOR` environment variables. - /// They are treated like normal variables, so can be controlled through `setEnvironmentVariable`. - manual, -} = .auto, +color: Color = .auto, /// When `true` prevents `ZIG_PROGRESS` environment variable from being passed /// to the child process, which otherwise would be used for the child to send @@ -112,6 +100,20 @@ rebuilt_executable: ?Path, /// If this Run step was produced by a Compile step, it is tracked here. producer: ?*Step.Compile, +pub const Color = enum { + /// `CLICOLOR_FORCE` is set, and `NO_COLOR` is unset. + enable, + /// `NO_COLOR` is set, and `CLICOLOR_FORCE` is unset. + disable, + /// If the build runner is using color, equivalent to `.enable`. Otherwise, equivalent to `.disable`. + inherit, + /// If stderr is captured or checked, equivalent to `.disable`. Otherwise, equivalent to `.inherit`. + auto, + /// The build runner does not modify the `CLICOLOR_FORCE` or `NO_COLOR` environment variables. + /// They are treated like normal variables, so can be controlled through `setEnvironmentVariable`. + manual, +}; + pub const StdIn = union(enum) { none, bytes: []const u8, @@ -565,7 +567,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void { if (prev_path) |pp| { const new_path = b.fmt("{s}{c}{s}", .{ pp, - if (use_wine) fs.path.delimiter_windows else fs.path.delimiter, + if (use_wine) Dir.path.delimiter_windows else Dir.path.delimiter, search_path, }); env_map.put(key, new_path) catch @panic("OOM"); @@ -748,7 +750,7 @@ fn checksContainStderr(checks: []const StdIo.Check) bool { fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 { const b = run.step.owner; const path_str = path.toString(b.graph.arena) catch @panic("OOM"); - if (std.fs.path.isAbsolute(path_str)) { + if (Dir.path.isAbsolute(path_str)) { // Absolute paths don't need changing. return path_str; } @@ -756,19 +758,19 @@ fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 { const child_lazy_cwd = run.cwd orelse break :rel path_str; const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM"); // Convert it from relative to *our* cwd, to relative to the *child's* cwd. - break :rel std.fs.path.relative(b.graph.arena, child_cwd, path_str) catch @panic("OOM"); + break :rel Dir.path.relative(b.graph.arena, child_cwd, path_str) catch @panic("OOM"); }; // Not every path can be made relative, e.g. if the path and the child cwd are on different // disk designators on Windows. In that case, `relative` will return an absolute path which we can // just return. - if (std.fs.path.isAbsolute(child_cwd_rel)) { + if (Dir.path.isAbsolute(child_cwd_rel)) { return child_cwd_rel; } // We're not done yet. In some cases this path must be prefixed with './': // * On POSIX, the executable name cannot be a single component like 'foo' // * Some executables might treat a leading '-' like a flag, which we must avoid // There's no harm in it, so just *always* apply this prefix. - return std.fs.path.join(b.graph.arena, &.{ ".", child_cwd_rel }) catch @panic("OOM"); + return Dir.path.join(b.graph.arena, &.{ ".", child_cwd_rel }) catch @panic("OOM"); } const IndexedOutput = struct { @@ -965,11 +967,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void { &digest, ); - const output_dir_path = "o" ++ fs.path.sep_str ++ &digest; + const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest; for (output_placeholders.items) |placeholder| { const output_sub_path = b.pathJoin(&.{ output_dir_path, placeholder.output.basename }); const output_sub_dir_path = switch (placeholder.tag) { - .output_file => fs.path.dirname(output_sub_path).?, + .output_file => Dir.path.dirname(output_sub_path).?, .output_directory => output_sub_path, else => unreachable, }; @@ -995,13 +997,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void { // We do not know the final output paths yet, use temp paths to run the command. const rand_int = std.crypto.random.int(u64); - const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); + const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); for (output_placeholders.items) |placeholder| { const output_components = .{ tmp_dir_path, placeholder.output.basename }; const output_sub_path = b.pathJoin(&output_components); const output_sub_dir_path = switch (placeholder.tag) { - .output_file => fs.path.dirname(output_sub_path).?, + .output_file => Dir.path.dirname(output_sub_path).?, .output_directory => output_sub_path, else => unreachable, }; @@ -1023,7 +1025,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, options, null); - const dep_file_dir = Io.Dir.cwd(); + const dep_file_dir = Dir.cwd(); const dep_file_basename = dep_output_file.generated_file.getPath2(b, step); if (has_side_effects) try man.addDepFile(dep_file_dir, dep_file_basename) @@ -1040,7 +1042,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { // Rename into place if (any_output) { - const o_sub_path = "o" ++ fs.path.sep_str ++ &digest; + const o_sub_path = "o" ++ Dir.path.sep_str ++ &digest; b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |err| { if (err == error.PathAlreadyExists) { @@ -1139,12 +1141,11 @@ pub fn rerunInFuzzMode( const has_side_effects = false; const rand_int = std.crypto.random.int(u64); - const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); + const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{ .progress_node = prog_node, .watch = undefined, // not used by `runCommand` .web_server = null, // only needed for time reports - .ttyconf = fuzz.ttyconf, .unit_test_timeout_ns = null, // don't time out fuzz tests for now .gpa = fuzz.gpa, }, .{ @@ -1266,10 +1267,7 @@ fn runCommand( try env_map.put("NO_COLOR", "1"); env_map.remove("CLICOLOR_FORCE"); }, - .inherit => switch (options.ttyconf) { - .no_color, .windows_api => continue :color .disable, - .escape_codes => continue :color .enable, - }, + .inherit => {}, .auto => { const capture_stderr = run.captured_stderr != null or switch (run.stdio) { .check => |checks| checksContainStderr(checks.items), @@ -1464,7 +1462,7 @@ fn runCommand( captured.output.generated_file.path = output_path; const sub_path = b.pathJoin(&output_components); - const sub_path_dirname = fs.path.dirname(sub_path).?; + const sub_path_dirname = Dir.path.dirname(sub_path).?; b.cache_root.handle.makePath(io, sub_path_dirname) catch |err| { return step.fail("unable to make path '{f}{s}': {s}", .{ b.cache_root, sub_path_dirname, @errorName(err), @@ -1650,8 +1648,8 @@ fn spawnChildAndCollect( if (!run.disable_zig_progress and !inherit) { child.progress_node = options.progress_node; } - if (inherit) std.debug.lockStdErr(); - defer if (inherit) std.debug.unlockStdErr(); + if (inherit) _ = std.debug.lockStderrWriter(&.{}); + defer if (inherit) std.debug.unlockStderrWriter(); var timer = try std.time.Timer.start(); const res = try evalGeneric(run, &child); run.step.result_duration_ns = timer.read(); @@ -2277,7 +2275,7 @@ fn addPathForDynLibs(run: *Run, artifact: *Step.Compile) void { if (compile.root_module.resolved_target.?.result.os.tag == .windows and compile.isDynamicLibrary()) { - addPathDir(run, fs.path.dirname(compile.getEmittedBin().getPath2(b, &run.step)).?); + addPathDir(run, Dir.path.dirname(compile.getEmittedBin().getPath2(b, &run.step)).?); } } } diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 5f633c594872c2a63854a5b1d2b3e281beee2621..ccb715919207b4e9dfa5a1893b2e7411ed24659c 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -2,7 +2,6 @@ gpa: Allocator, graph: *const Build.Graph, all_steps: []const *Build.Step, listen_address: net.IpAddress, -ttyconf: Io.tty.Config, root_prog_node: std.Progress.Node, watch: bool, @@ -52,7 +51,6 @@ pub fn notifyUpdate(ws: *WebServer) void { pub const Options = struct { gpa: Allocator, - ttyconf: Io.tty.Config, graph: *const std.Build.Graph, all_steps: []const *Build.Step, root_prog_node: std.Progress.Node, @@ -98,7 +96,6 @@ pub fn init(opts: Options) WebServer { return .{ .gpa = opts.gpa, - .ttyconf = opts.ttyconf, .graph = opts.graph, .all_steps = all_steps, .listen_address = opts.listen_address, @@ -233,7 +230,6 @@ pub fn finishBuild(ws: *WebServer, opts: struct { ws.fuzz = Fuzz.init( ws.gpa, ws.graph.io, - ws.ttyconf, ws.all_steps, ws.root_prog_node, .{ .forever = .{ .ws = ws } }, diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 8f9f573a4bf624dcab9b7646ed175ca916ca7866..03e4ad8a1509ae06c6849249a236cebf2e7f8010 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -99,7 +99,7 @@ pub const Mode = union(enum) { pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; - pub fn setColor(mode: Mode, io_w: *Io.Writer, color: Color) Mode.SetColorError!void { + pub fn setColor(mode: Mode, io_w: *Io.Writer, color: Color) SetColorError!void { switch (mode) { .streaming, .positional, .streaming_simple, .positional_simple, .failure => return, .terminal_escaped => { @@ -155,6 +155,34 @@ pub const Mode = union(enum) { }, } } + + fn DecorateArgs(comptime Args: type) type { + const fields = @typeInfo(Args).@"struct".fields; + var new_fields: [fields.len]type = undefined; + for (fields, &new_fields) |old, *new| { + if (old.type == std.debug.FormatStackTrace) { + new.* = std.debug.FormatStackTrace.Decorated; + } else { + new.* = old.type; + } + } + return @Tuple(&new_fields); + } + + pub fn decorateArgs(file_writer_mode: std.Io.File.Writer.Mode, args: anytype) DecorateArgs(@TypeOf(args)) { + var new_args: DecorateArgs(@TypeOf(args)) = undefined; + inline for (args, &new_args) |old, *new| { + if (@TypeOf(old) == std.debug.FormatStackTrace) { + new.* = .{ + .stack_trace = old.stack_trace, + .file_writer_mode = file_writer_mode, + }; + } else { + new.* = old; + } + } + return new_args; + } }; pub const Error = error{ @@ -426,6 +454,8 @@ pub fn end(w: *Writer) EndError!void { .streaming, .streaming_simple, + .terminal_escaped, + .terminal_winapi, .failure, => {}, } @@ -453,10 +483,11 @@ pub const Color = enum { reset, }; -pub const SetColorError = Mode.SetColorError; - -pub fn setColor(w: *Writer, color: Color) SetColorError!void { - return w.mode.setColor(&w.interface, color); +pub fn setColor(w: *Writer, color: Color) Io.Writer.Error!void { + return w.mode.setColor(&w.interface, color) catch |err| switch (err) { + error.WriteFailed => |e| return e, + else => |e| w.err = e, + }; } pub fn disableEscape(w: *Writer) Mode { diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 69d8c942c41cca89ad3dcadc304dbed1e1c3753a..67f7d3a9fefc6ec156ef35dcf49e4b67ca11fb2e 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -306,7 +306,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { var buffer: [64]u8 = undefined; const stderr = lockStderrWriter(&buffer); defer unlockStderrWriter(); - stderr.interface.print(fmt, args) catch return; + stderr.interface.print(fmt, stderr.mode.decorateArgs(args)) catch return; } } diff --git a/lib/std/log.zig b/lib/std/log.zig index 029f724f445dffeb4f13a3d34a25a3efc84e16f5..b75bada5803d93199e1d4c2ce47fa77c4b33e886 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -120,35 +120,7 @@ pub fn defaultLogFileWriter( } fw.interface.writeAll(": ") catch return; fw.setColor(.reset) catch {}; - fw.interface.print(format ++ "\n", decorateArgs(args, fw.mode)) catch return; -} - -fn DecorateArgs(comptime Args: type) type { - const fields = @typeInfo(Args).@"struct".fields; - var new_fields: [fields.len]type = undefined; - for (fields, &new_fields) |old, *new| { - if (old.type == std.debug.FormatStackTrace) { - new.* = std.debug.FormatStackTrace.Decorated; - } else { - new.* = old.type; - } - } - return @Tuple(&new_fields); -} - -fn decorateArgs(args: anytype, file_writer_mode: std.Io.File.Writer.Mode) DecorateArgs(@TypeOf(args)) { - var new_args: DecorateArgs(@TypeOf(args)) = undefined; - inline for (args, &new_args) |old, *new| { - if (@TypeOf(old) == std.debug.FormatStackTrace) { - new.* = .{ - .stack_trace = old.stack_trace, - .file_writer_mode = file_writer_mode, - }; - } else { - new.* = old; - } - } - return new_args; + fw.interface.print(format ++ "\n", fw.mode.decorateArgs(args)) catch return; } /// Returns a scoped logging namespace that logs all messages using the scope diff --git a/lib/std/testing.zig b/lib/std/testing.zig index ab9f1f73c31baf9c8b47d26cffe82ef55c95e791..092b65d71ac9bb4f06842882086fdbf8453a3509 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -354,11 +354,10 @@ test expectApproxEqRel { } } -/// This function is intended to be used only in tests. When the two slices are not -/// equal, prints diagnostics to stderr to show exactly how they are not equal (with -/// the differences highlighted in red), then returns a test failure error. -/// The colorized output is optional and controlled by the return of `Io.tty.Config.detect`. -/// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead. +/// This function is intended to be used only in tests. When the two slices are +/// not equal, prints diagnostics to stderr to show exactly how they are not +/// equal (with the differences highlighted in red), then returns a test +/// failure error. pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void { const diff_index: usize = diff_index: { const shortest = @min(expected.len, actual.len); @@ -369,20 +368,13 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const break :diff_index if (expected.len == actual.len) return else shortest; }; if (!backend_can_print) return error.TestExpectedEqual; - const stderr_w, const ttyconf = std.debug.lockStderrWriter(&.{}); + const stderr = std.debug.lockStderrWriter(&.{}); defer std.debug.unlockStderrWriter(); - failEqualSlices(T, expected, actual, diff_index, stderr_w, ttyconf) catch {}; + failEqualSlices(T, expected, actual, diff_index, &stderr.interface, stderr.mode) catch {}; return error.TestExpectedEqual; } -fn failEqualSlices( - comptime T: type, - expected: []const T, - actual: []const T, - diff_index: usize, - w: *Io.Writer, - ttyconf: Io.tty.Config, -) !void { +fn failEqualSlices(comptime T: type, expected: []const T, actual: []const T, diff_index: usize, w: *Io.Writer, fwm: Io.File.Writer.Mode) !void { try w.print("slices differ. first difference occurs at index {d} (0x{X})\n", .{ diff_index, diff_index }); // TODO: Should this be configurable by the caller? @@ -404,12 +396,12 @@ fn failEqualSlices( var differ = if (T == u8) BytesDiffer{ .expected = expected_window, .actual = actual_window, - .ttyconf = ttyconf, + .file_writer_mode = fwm, } else SliceDiffer(T){ .start_index = window_start, .expected = expected_window, .actual = actual_window, - .ttyconf = ttyconf, + .file_writer_mode = fwm, }; // Print indexes as hex for slices of u8 since it's more likely to be binary data where @@ -466,7 +458,7 @@ fn SliceDiffer(comptime T: type) type { start_index: usize, expected: []const T, actual: []const T, - ttyconf: Io.tty.Config, + file_writer_mode: Io.File.Writer.Mode, const Self = @This(); @@ -474,13 +466,13 @@ fn SliceDiffer(comptime T: type) type { for (self.expected, 0..) |value, i| { const full_index = self.start_index + i; const diff = if (i < self.actual.len) !std.meta.eql(self.actual[i], value) else true; - if (diff) try self.ttyconf.setColor(writer, .red); + if (diff) try self.file_writer_mode.setColor(writer, .red); if (@typeInfo(T) == .pointer) { try writer.print("[{}]{*}: {any}\n", .{ full_index, value, value }); } else { try writer.print("[{}]: {any}\n", .{ full_index, value }); } - if (diff) try self.ttyconf.setColor(writer, .reset); + if (diff) try self.file_writer_mode.setColor(writer, .reset); } } }; @@ -489,7 +481,7 @@ fn SliceDiffer(comptime T: type) type { const BytesDiffer = struct { expected: []const u8, actual: []const u8, - ttyconf: Io.tty.Config, + file_writer_mode: Io.File.Writer.Mode, pub fn write(self: BytesDiffer, writer: *Io.Writer) !void { var expected_iterator = std.mem.window(u8, self.expected, 16, 16); @@ -516,7 +508,7 @@ const BytesDiffer = struct { try self.writeDiff(writer, "{c}", .{byte}, diff); } else { // TODO: remove this `if` when https://github.com/ziglang/zig/issues/7600 is fixed - if (self.ttyconf == .windows_api) { + if (self.file_writer_mode == .terminal_winapi) { try self.writeDiff(writer, ".", .{}, diff); continue; } @@ -538,9 +530,9 @@ const BytesDiffer = struct { } fn writeDiff(self: BytesDiffer, writer: *Io.Writer, comptime fmt: []const u8, args: anytype, diff: bool) !void { - if (diff) try self.ttyconf.setColor(writer, .red); + if (diff) try self.file_writer_mode.setColor(writer, .red); try writer.print(fmt, args); - if (diff) try self.ttyconf.setColor(writer, .reset); + if (diff) try self.file_writer_mode.setColor(writer, .reset); } }; @@ -1162,7 +1154,6 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime } else |err| switch (err) { error.OutOfMemory => { if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) { - const tty_config: Io.tty.Config = .detect(.stderr()); print( "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}", .{ @@ -1174,7 +1165,6 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime failing_allocator_inst.deallocations, std.debug.FormatStackTrace{ .stack_trace = failing_allocator_inst.getStackTrace(), - .tty_config = tty_config, }, }, ); diff --git a/lib/std/zig.zig b/lib/std/zig.zig index ac6a0787de67bb5a32a5ab861432d0b0f1bf8133..b6203326f26d9cc538b4909ebd84c43c370ec814 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -53,18 +53,18 @@ pub const Color = enum { /// Assume stderr is a terminal. on, - pub fn getTtyConf(color: Color, detected: Io.tty.Config) Io.tty.Config { + pub fn getTtyConf(color: Color, detected: Io.File.Writer.Mode) Io.File.Writer.Mode { return switch (color) { .auto => detected, - .on => .escape_codes, - .off => .no_color, + .on => .terminal_escaped, + .off => .streaming, }; } - pub fn detectTtyConf(color: Color, io: Io) Io.tty.Config { + pub fn detectTtyConf(color: Color, io: Io) Io.File.Writer.Mode { return switch (color) { .auto => .detect(io, .stderr()), - .on => .escape_codes, - .off => .no_color, + .on => .terminal_escaped, + .off => .streaming, }; } }; diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index ef6203cbe85042c40306a27e67a4b45123403b1d..cc6182c8aa67064e77b44ebdcac1391d2577841e 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -164,15 +164,20 @@ pub const RenderOptions = struct { pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions, color: std.zig.Color) void { var buffer: [256]u8 = undefined; - const w, const ttyconf = std.debug.lockStderrWriter(&buffer); + const stderr = std.debug.lockStderrWriter(&buffer); defer std.debug.unlockStderrWriter(); - renderToWriter(eb, options, w, color.getTtyConf(ttyconf)) catch return; + renderToWriter(eb, options, &stderr.interface, color.getTtyConf(stderr.mode)) catch return; } -pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, w: *Writer, ttyconf: Io.tty.Config) (Writer.Error || std.posix.UnexpectedError)!void { +pub fn renderToWriter( + eb: ErrorBundle, + options: RenderOptions, + w: *Writer, + fwm: Io.File.Writer.Mode, +) Io.File.Writer.Mode.SetColorError!void { if (eb.extra.len == 0) return; for (eb.getMessages()) |err_msg| { - try renderErrorMessageToWriter(eb, options, err_msg, w, ttyconf, "error", .red, 0); + try renderErrorMessageToWriter(eb, options, err_msg, w, fwm, "error", .red, 0); } if (options.include_log_text) { @@ -189,18 +194,18 @@ fn renderErrorMessageToWriter( options: RenderOptions, err_msg_index: MessageIndex, w: *Writer, - ttyconf: Io.tty.Config, + fwm: Io.File.Writer.Mode, kind: []const u8, - color: Io.tty.Color, + color: Io.File.Writer.Color, indent: usize, -) (Writer.Error || std.posix.UnexpectedError)!void { +) Io.File.Writer.Mode.SetColorError!void { const err_msg = eb.getErrorMessage(err_msg_index); if (err_msg.src_loc != .none) { const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc)); var prefix: Writer.Discarding = .init(&.{}); try w.splatByteAll(' ', indent); prefix.count += indent; - try ttyconf.setColor(w, .bold); + try fwm.setColor(w, .bold); try w.print("{s}:{d}:{d}: ", .{ eb.nullTerminatedString(src.data.src_path), src.data.line + 1, @@ -211,7 +216,7 @@ fn renderErrorMessageToWriter( src.data.line + 1, src.data.column + 1, }); - try ttyconf.setColor(w, color); + try fwm.setColor(w, color); try w.writeAll(kind); prefix.count += kind.len; try w.writeAll(": "); @@ -219,17 +224,17 @@ fn renderErrorMessageToWriter( // This is the length of the part before the error message: // e.g. "file.zig:4:5: error: " const prefix_len: usize = @intCast(prefix.count); - try ttyconf.setColor(w, .reset); - try ttyconf.setColor(w, .bold); + try fwm.setColor(w, .reset); + try fwm.setColor(w, .bold); if (err_msg.count == 1) { try writeMsg(eb, err_msg, w, prefix_len); try w.writeByte('\n'); } else { try writeMsg(eb, err_msg, w, prefix_len); - try ttyconf.setColor(w, .dim); + try fwm.setColor(w, .dim); try w.print(" ({d} times)\n", .{err_msg.count}); } - try ttyconf.setColor(w, .reset); + try fwm.setColor(w, .reset); if (src.data.source_line != 0 and options.include_source_line) { const line = eb.nullTerminatedString(src.data.source_line); for (line) |b| switch (b) { @@ -242,19 +247,19 @@ fn renderErrorMessageToWriter( // -1 since span.main includes the caret const after_caret = src.data.span_end -| src.data.span_main -| 1; try w.splatByteAll(' ', src.data.column - before_caret); - try ttyconf.setColor(w, .green); + try fwm.setColor(w, .green); try w.splatByteAll('~', before_caret); try w.writeByte('^'); try w.splatByteAll('~', after_caret); try w.writeByte('\n'); - try ttyconf.setColor(w, .reset); + try fwm.setColor(w, .reset); } for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, options, note, w, ttyconf, "note", .cyan, indent); + try renderErrorMessageToWriter(eb, options, note, w, fwm, "note", .cyan, indent); } if (src.data.reference_trace_len > 0 and options.include_reference_trace) { - try ttyconf.setColor(w, .reset); - try ttyconf.setColor(w, .dim); + try fwm.setColor(w, .reset); + try fwm.setColor(w, .dim); try w.print("referenced by:\n", .{}); var ref_index = src.end; for (0..src.data.reference_trace_len) |_| { @@ -281,25 +286,25 @@ fn renderErrorMessageToWriter( ); } } - try ttyconf.setColor(w, .reset); + try fwm.setColor(w, .reset); } } else { - try ttyconf.setColor(w, color); + try fwm.setColor(w, color); try w.splatByteAll(' ', indent); try w.writeAll(kind); try w.writeAll(": "); - try ttyconf.setColor(w, .reset); + try fwm.setColor(w, .reset); const msg = eb.nullTerminatedString(err_msg.msg); if (err_msg.count == 1) { try w.print("{s}\n", .{msg}); } else { try w.print("{s}", .{msg}); - try ttyconf.setColor(w, .dim); + try fwm.setColor(w, .dim); try w.print(" ({d} times)\n", .{err_msg.count}); } - try ttyconf.setColor(w, .reset); + try fwm.setColor(w, .reset); for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, options, note, w, ttyconf, "note", .cyan, indent + 4); + try renderErrorMessageToWriter(eb, options, note, w, fwm, "note", .cyan, indent + 4); } } } @@ -806,12 +811,10 @@ pub const Wip = struct { }; defer bundle.deinit(std.testing.allocator); - const ttyconf: Io.tty.Config = .no_color; - var bundle_buf: Writer.Allocating = .init(std.testing.allocator); const bundle_bw = &bundle_buf.interface; defer bundle_buf.deinit(); - try bundle.renderToWriter(.{ .ttyconf = ttyconf }, bundle_bw); + try bundle.renderToWriter(bundle_bw); var copy = copy: { var wip: ErrorBundle.Wip = undefined; @@ -827,7 +830,7 @@ pub const Wip = struct { var copy_buf: Writer.Allocating = .init(std.testing.allocator); const copy_bw = ©_buf.interface; defer copy_buf.deinit(); - try copy.renderToWriter(.{ .ttyconf = ttyconf }, copy_bw); + try copy.renderToWriter(copy_bw); try std.testing.expectEqualStrings(bundle_bw.written(), copy_bw.written()); } -- 2.54.0 From 7bc0166b7c34ac0120f50aceb7132ffaa4aeec83 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 10 Dec 2025 22:21:51 -0800 Subject: [PATCH 042/195] std.Io: implement dirRead for Linux unfortunately, Io.Dir.SelectiveWalker is copying the iterator which has multiple problems --- lib/std/Io/Dir.zig | 14 +-- lib/std/Io/Threaded.zig | 183 +++++++++++++++++++++++++++++++--------- lib/std/posix.zig | 171 ------------------------------------- 3 files changed, 152 insertions(+), 216 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index a9d28440eb1e1b33581e7d3910e6db542718b059..5da2b16f82929766edc0d2247775f20c198c5c1e 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -184,6 +184,11 @@ pub const SelectiveWalker = struct { pub const Error = Io.Dir.Iterator.Error || Allocator.Error; + const StackItem = struct { + iter: Dir.Iterator, + dirname_len: usize, + }; + /// After each call to this function, and on deinit(), the memory returned /// from this function becomes invalid. A copy must be made in order to keep /// a reference to the path. @@ -268,7 +273,7 @@ pub const SelectiveWalker = struct { /// Recursively iterates over a directory, but requires the user to /// opt-in to recursing into each directory entry. /// -/// `dir` must have been opened with `OpenOptions{.iterate = true}`. +/// `dir` must have been opened with `OpenOptions.iterate` set to `true`. /// /// `Walker.deinit` releases allocated memory and directory handles. /// @@ -278,7 +283,7 @@ pub const SelectiveWalker = struct { /// /// See also `walk`. pub fn walkSelectively(dir: Dir, allocator: Allocator) !SelectiveWalker { - var stack: std.ArrayList(Walker.StackItem) = .empty; + var stack: std.ArrayList(SelectiveWalker.StackItem) = .empty; try stack.append(allocator, .{ .iter = dir.iterate(), @@ -312,11 +317,6 @@ pub const Walker = struct { } }; - const StackItem = struct { - iter: Dir.Iterator, - dirname_len: usize, - }; - /// After each call to this function, and on deinit(), the memory returned /// from this function becomes invalid. A copy must be made in order to keep /// a reference to the path. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 41e93cee5b2642f9670e25eaf07142fe1fdd87dd..55cc21db0e8b2ba9c44781ebe85175ff14a9c7d1 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3271,12 +3271,113 @@ fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { for (dirs) |dir| posix.close(dir.handle); } -fn dirRead(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { +const dirRead = switch (native_os) { + .linux => dirReadLinux, + else => dirReadUnimplemented, +}; + +fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { + const linux = std.os.linux; const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; + const current_thread = Thread.getCurrent(t); + const Header = extern struct { + fill_end: usize, + }; + const header: *Header = @ptrCast(&dr.buffer); + const header_end: usize = @sizeOf(Header); + if (dr.index < header_end) { + // Initialize header. + dr.index = header_end; + header.* = .{ .fill_end = header_end }; + } + var buffer_index: usize = 0; + while (buffer.len - buffer_index != 0) { + if (header.fill_end - dr.index == 0) { + // Refill the buffer, unless we've already created references to + // buffered data. + if (buffer_index != 0) break; + if (dr.state == .reset) { + posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) { + error.Unseekable => return error.Unexpected, + else => |e| return e, + }; + dr.state = .reading; + } + const dents_buffer = dr.buffer[header_end..]; + try current_thread.beginSyscall(); + const n = while (true) { + const rc = linux.getdents64(dr.dir.handle, dents_buffer.ptr, dents_buffer.len); + switch (linux.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break rc; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. + .FAULT => |err| return errnoBug(err), + .NOTDIR => |err| return errnoBug(err), + // To be consistent across platforms, iteration + // ends if the directory being iterated is deleted + // during iteration. This matches the behavior of + // non-Linux UNIX platforms. + .NOENT => { + dr.state = .finished; + return 0; + }, + .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net. + .ACCES => return error.AccessDenied, // Lacking permission to iterate this directory. + else => |err| return posix.unexpectedErrno(err), + } + }, + } + }; + if (n == 0) { + dr.state = .finished; + return 0; + } + dr.index = header_end; + header.fill_end = header_end + n; + } + const linux_entry: *align(1) linux.dirent64 = @ptrCast(&dr.buffer[dr.index]); + const next_index = dr.index + linux_entry.reclen; + dr.index = next_index; + + const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0); + // Skip "." and ".." entries. + if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; + + const entry_kind: File.Kind = switch (linux_entry.type) { + linux.DT.BLK => .block_device, + linux.DT.CHR => .character_device, + linux.DT.DIR => .directory, + linux.DT.FIFO => .named_pipe, + linux.DT.LNK => .sym_link, + linux.DT.REG => .file, + linux.DT.SOCK => .unix_domain_socket, + else => .unknown, + }; + buffer[buffer_index] = .{ + .name = name, + .kind = entry_kind, + .inode = linux_entry.ino, + }; + buffer_index += 1; + } + return buffer_index; +} + +fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { + _ = userdata; _ = dir_reader; _ = buffer; - @panic("TODO"); + return error.Unimplemented; } const dirRealPath = switch (native_os) { @@ -5908,6 +6009,47 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi const current_thread = Thread.getCurrent(t); const fd = file.handle; + if (native_os == .windows) { + try current_thread.checkCancel(); + return windows.SetFilePointerEx_BEGIN(fd, offset); + } + + if (native_os == .wasi and !builtin.link_libc) { + try current_thread.beginSyscall(); + while (true) { + var new_offset: std.os.wasi.filesize_t = undefined; + switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + + if (posix.SEEK == void) return error.Unseekable; + + return posixSeekTo(current_thread, fd, offset); +} + +fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekError!void { if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { try current_thread.beginSyscall(); while (true) { @@ -5937,41 +6079,6 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi } } - if (native_os == .windows) { - try current_thread.checkCancel(); - return windows.SetFilePointerEx_BEGIN(fd, offset); - } - - if (native_os == .wasi and !builtin.link_libc) while (true) { - var new_offset: std.os.wasi.filesize_t = undefined; - try current_thread.beginSyscall(); - switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { - .SUCCESS => { - current_thread.endSyscall(); - return; - }, - .INTR => { - try current_thread.checkCancel(); - continue; - }, - .CANCELED => return current_thread.endSyscallCanceled(), - else => |e| { - current_thread.endSyscall(); - switch (e) { - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), - } - }, - } - }; - - if (posix.SEEK == void) return error.Unseekable; - try current_thread.beginSyscall(); while (true) { switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) { diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 609cf62e82e170617ada1582793e37c643cb4c7e..aa270e611df7acc13dcf6af2a8d0c93b38bbb9e0 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -2986,177 +2986,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { } } -pub const SeekError = std.Io.File.SeekError; - -pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, offset, &result, SEEK.SET))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (native_os == .windows) { - return windows.SetFilePointerEx_BEGIN(fd, offset); - } - if (native_os == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.SET))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - -/// Repositions read/write file offset relative to the current offset. -pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.CUR))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (native_os == .windows) { - return windows.SetFilePointerEx_CURRENT(fd, offset); - } - if (native_os == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, offset, .CUR, &new_offset)) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.CUR))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - -/// Repositions read/write file offset relative to the end. -pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.END))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (native_os == .windows) { - return windows.SetFilePointerEx_END(fd, offset); - } - if (native_os == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, offset, .END, &new_offset)) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.END))) { - .SUCCESS => return, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - -/// Returns the read/write file offset relative to the beginning. -pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - switch (errno(system.llseek(fd, 0, &result, SEEK.CUR))) { - .SUCCESS => return result, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } - } - if (native_os == .windows) { - return windows.SetFilePointerEx_CURRENT_get(fd); - } - if (native_os == .wasi and !builtin.link_libc) { - var new_offset: wasi.filesize_t = undefined; - switch (wasi.fd_seek(fd, 0, .CUR, &new_offset)) { - .SUCCESS => return new_offset, - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } - const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek; - const rc = lseek_sym(fd, 0, SEEK.CUR); - switch (errno(rc)) { - .SUCCESS => return @bitCast(rc), - .BADF => unreachable, // always a race condition - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return unexpectedErrno(err), - } -} - pub const FcntlError = error{ PermissionDenied, FileBusy, -- 2.54.0 From 94ef56ee26558daea3c7a1468f898c94735d1658 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 10 Dec 2025 23:09:08 -0800 Subject: [PATCH 043/195] std.Io.Dir: fix walking * Make Io.Dir.Reader lower level, accepting a buffer. * Make Io.Dir.Iterator higher level, requesting only one Entry with every call to `next`. --- lib/std/Io/Dir.zig | 68 ++++++++++++++++++++++++----------------- lib/std/Io/Threaded.zig | 19 +++--------- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 5da2b16f82929766edc0d2247775f20c198c5c1e..2c6bee99a5b6aea629bddd2d8d395144a3de34ee 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -95,8 +95,13 @@ pub const Reader = struct { dir: Dir, state: State, /// Stores I/O implementation specific data. - buffer: [2048]u8 align(@alignOf(usize)), + buffer: []u8 align(@alignOf(usize)), + /// Index of next entry in `buffer`. index: usize, + /// Fill position of `buffer`. + end: usize, + + pub const min_buffer_len = 32; pub const State = enum { /// Indicates the next call to `read` should rewind and start over the @@ -112,27 +117,45 @@ pub const Reader = struct { SystemResources, } || Io.UnexpectedError || Io.Cancelable; - pub fn init(dir: Dir) Reader { + pub fn init(dir: Dir, buffer: []align(@alignOf(usize)) u8) Reader { + assert(buffer.len >= min_buffer_len); return .{ .dir = dir, .state = .reset, .index = 0, - .buffer = undefined, + .end = 0, + .buffer = buffer, }; } + /// All `Entry.name` are invalidated with the next call to `read` or + /// `next`. pub fn read(r: *Reader, io: Io, buffer: []Entry) Error!usize { return io.vtable.dirRead(io.userdata, r, buffer); } + + /// `Entry.name` is invalidated with the next call to `read` or `next`. + pub fn next(r: *Reader, io: Io) Error!?Entry { + var buffer: [1]Entry = undefined; + while (true) { + const n = try read(r, io, &buffer); + if (n == 1) return buffer[0]; + if (r.state == .finished) return null; + } + } }; +/// This API is designed for convenience rather than performance: +/// * It chooses a buffer size rather than allowing the user to provide one. +/// * It is movable by only requesting one `Entry` at a time from the `Io` +/// implementation rather than doing batch operations. +/// +/// Still, it will do a decent job of minimizing syscall overhead. For a +/// lower level abstraction, see `Reader`. For a higher level abstraction, +/// see `Walker`. pub const Iterator = struct { reader: Reader, - buffer: [32]Entry, - /// Index of next entry in `buffer`. - index: usize, - /// Fill position of `buffer`. - end: usize, + reader_buffer: [2048]u8 align(@alignOf(usize)), pub const Error = Reader.Error; @@ -142,27 +165,16 @@ pub const Iterator = struct { .dir = dir, .state = reader_state, .index = 0, + .end = 0, .buffer = undefined, }, - .buffer = undefined, - .index = 0, - .end = 0, + .reader_buffer = undefined, }; } pub fn next(it: *Iterator, io: Io) Error!?Entry { - if (it.end - it.index == 0) { - if (it.reader.state == .finished) return null; - it.end = try it.reader.read(io, &it.buffer); - it.index = 0; - if (it.end - it.index == 0) { - assert(it.reader.state == .finished); - return null; - } - } - const index = it.index; - it.index = index + 1; - return it.buffer[index]; + it.reader.buffer = &it.reader_buffer; + return it.reader.next(io); } }; @@ -178,14 +190,14 @@ pub fn iterateAssumeFirstIteration(dir: Dir) Iterator { } pub const SelectiveWalker = struct { - stack: std.ArrayList(Walker.StackItem), + stack: std.ArrayList(StackItem), name_buffer: std.ArrayList(u8), allocator: Allocator, - pub const Error = Io.Dir.Iterator.Error || Allocator.Error; + pub const Error = Iterator.Error || Allocator.Error; const StackItem = struct { - iter: Dir.Iterator, + iter: Iterator, dirname_len: usize, }; @@ -942,7 +954,7 @@ pub fn renameAbsolute(io: Io, old_path: []const u8, new_path: []const u8) Rename return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path); } -/// Use with `Dir.symLink`, `Dir.symLinkAtomic`, and `symLinkAbsolute` to +/// Use with `symLink`, `symLinkAtomic`, and `symLinkAbsolute` to /// specify whether the symlink will point to a file or a directory. This value /// is ignored on all hosts except Windows where creating symlinks to different /// resource types, requires different flags. By default, `symLinkAbsolute` is @@ -1182,7 +1194,7 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { const StackItem = struct { name: []const u8, parent_dir: Dir, - iter: Dir.Iterator, + iter: Iterator, fn closeAll(inner_io: Io, items: []@This()) void { for (items) |*item| item.iter.reader.dir.close(inner_io); diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 55cc21db0e8b2ba9c44781ebe85175ff14a9c7d1..05b00b555fd68fe6048f5bfcbef707ef64f5920e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3280,19 +3280,9 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir const linux = std.os.linux; const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); - const Header = extern struct { - fill_end: usize, - }; - const header: *Header = @ptrCast(&dr.buffer); - const header_end: usize = @sizeOf(Header); - if (dr.index < header_end) { - // Initialize header. - dr.index = header_end; - header.* = .{ .fill_end = header_end }; - } var buffer_index: usize = 0; while (buffer.len - buffer_index != 0) { - if (header.fill_end - dr.index == 0) { + if (dr.end - dr.index == 0) { // Refill the buffer, unless we've already created references to // buffered data. if (buffer_index != 0) break; @@ -3303,10 +3293,9 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir }; dr.state = .reading; } - const dents_buffer = dr.buffer[header_end..]; try current_thread.beginSyscall(); const n = while (true) { - const rc = linux.getdents64(dr.dir.handle, dents_buffer.ptr, dents_buffer.len); + const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); switch (linux.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); @@ -3342,8 +3331,8 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir dr.state = .finished; return 0; } - dr.index = header_end; - header.fill_end = header_end + n; + dr.index = 0; + dr.end = n; } const linux_entry: *align(1) linux.dirent64 = @ptrCast(&dr.buffer[dr.index]); const next_index = dr.index + linux_entry.reclen; -- 2.54.0 From 91fa252cf20554495e719cacf83f488bef740a67 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 10 Dec 2025 23:55:09 -0800 Subject: [PATCH 044/195] std.Io: implement dir reading for BSDs --- lib/std/Io.zig | 8 +- lib/std/Io/Dir.zig | 6 + lib/std/Io/Threaded.zig | 293 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 297 insertions(+), 10 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index fdd813536cd8001a3543f121b978a8237103fc49..9db215c74719e7faf0dfe016a5fa621692dc5df6 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -2183,16 +2183,12 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) { /// See also: /// * `tryLockStderrWriter` pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer { - const result = try io.vtable.lockStderrWriter(io.userdata, buffer); - result.io = io; - return result; + return io.vtable.lockStderrWriter(io.userdata, buffer); } /// Same as `lockStderrWriter` but uncancelable and non-blocking. pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer { - const result = io.vtable.tryLockStderrWriter(io.userdata, buffer) orelse return null; - result.io = io; - return result; + return io.vtable.tryLockStderrWriter(io.userdata, buffer); } pub fn unlockStderrWriter(io: Io) void { diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 2c6bee99a5b6aea629bddd2d8d395144a3de34ee..4e70a7be8929340e7cb843fc33ec027eb77c507a 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -143,6 +143,12 @@ pub const Reader = struct { if (r.state == .finished) return null; } } + + pub fn reset(r: *Reader) void { + r.state = .reset; + r.index = 0; + r.end = 0; + } }; /// This API is designed for convenience rather than performance: diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 05b00b555fd68fe6048f5bfcbef707ef64f5920e..61974f4717febcb2480314f2ff5c7b303147cab9 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1870,10 +1870,14 @@ fn dirStatPathPosix( const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; + return posixStatPath(current_thread, dir.handle, sub_path_posix, flags); +} + +fn posixStatPath(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatPathError!File.Stat { try current_thread.beginSyscall(); while (true) { var stat = std.mem.zeroes(posix.Stat); - switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, flags))) { + switch (posix.errno(fstatat_sym(dir_fd, sub_path, &stat, flags))) { .SUCCESS => { current_thread.endSyscall(); return statFromPosix(&stat); @@ -3273,6 +3277,12 @@ fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { const dirRead = switch (native_os) { .linux => dirReadLinux, + .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => dirReadDarwin, + .freebsd, .netbsd, .dragonfly, .openbsd => dirReadBsd, + .illumos => dirReadIllumos, + .haiku => dirReadHaiku, + .windows => dirReadWindows, + .wasi => dirReadWasi, else => dirReadUnimplemented, }; @@ -3339,7 +3349,6 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir dr.index = next_index; const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0); - // Skip "." and ".." entries. if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; const entry_kind: File.Kind = switch (linux_entry.type) { @@ -3362,6 +3371,278 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir return buffer_index; } +fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + const Header = extern struct { + seek: i64, + }; + const header: *Header = @ptrCast(&dr.buffer); + const header_end: usize = @sizeOf(Header); + if (dr.index < header_end) { + // Initialize header. + dr.index = header_end; + header.* = .{ .seek = 0 }; + } + var buffer_index: usize = 0; + while (buffer.len - buffer_index != 0) { + if (dr.end - dr.index == 0) { + // Refill the buffer, unless we've already created references to + // buffered data. + if (buffer_index != 0) break; + if (dr.state == .reset) { + posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) { + error.Unseekable => return error.Unexpected, + else => |e| return e, + }; + dr.state = .reading; + } + const dents_buffer = dr.buffer[header_end..]; + try current_thread.beginSyscall(); + const n: usize = while (true) { + const rc = posix.system.getdirentries(dr.dir.fd, dents_buffer.ptr, dents_buffer.len, &header.seek); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability + .FAULT => |err| return errnoBug(err), + .NOTDIR => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, + } + }; + if (n == 0) { + dr.state = .finished; + return 0; + } + dr.index = header_end; + dr.end = header_end + n; + } + const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); + const next_index = dr.index + darwin_entry.reclen; + dr.index = next_index; + + const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen]; + if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) + continue; + + const entry_kind: File.Kind = switch (darwin_entry.type) { + posix.DT.BLK => .block_device, + posix.DT.CHR => .character_device, + posix.DT.DIR => .directory, + posix.DT.FIFO => .named_pipe, + posix.DT.LNK => .sym_link, + posix.DT.REG => .file, + posix.DT.SOCK => .unix_domain_socket, + posix.DT.WHT => .whiteout, + else => .unknown, + }; + buffer[buffer_index] = .{ + .name = name, + .kind = entry_kind, + .inode = darwin_entry.ino, + }; + buffer_index += 1; + } + return buffer_index; +} + +fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + var buffer_index: usize = 0; + while (buffer.len - buffer_index != 0) { + if (dr.end - dr.index == 0) { + // Refill the buffer, unless we've already created references to + // buffered data. + if (buffer_index != 0) break; + if (dr.state == .reset) { + posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) { + error.Unseekable => return error.Unexpected, + else => |e| return e, + }; + dr.state = .reading; + } + try current_thread.beginSyscall(); + const n: usize = while (true) { + const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability + .FAULT => |err| return errnoBug(err), + .NOTDIR => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + // Introduced in freebsd 13.2: directory unlinked + // but still open. To be consistent, iteration ends + // if the directory being iterated is deleted + // during iteration. + .NOENT => { + dr.state = .finished; + return 0; + }, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + }; + if (n == 0) { + dr.state = .finished; + return 0; + } + dr.index = 0; + dr.end = n; + } + const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); + const next_index = dr.index + + if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen(); + dr.index = next_index; + + const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen]; + + const skip_zero_fileno = switch (native_os) { + // fileno=0 is used to mark invalid entries or deleted files. + .openbsd, .netbsd => true, + else => false, + }; + if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or + (skip_zero_fileno and bsd_entry.fileno == 0)) + { + continue; + } + + const entry_kind: File.Kind = switch (bsd_entry.type) { + posix.DT.BLK => .block_device, + posix.DT.CHR => .character_device, + posix.DT.DIR => .directory, + posix.DT.FIFO => .named_pipe, + posix.DT.LNK => .sym_link, + posix.DT.REG => .file, + posix.DT.SOCK => .unix_domain_socket, + posix.DT.WHT => .whiteout, + else => .unknown, + }; + buffer[buffer_index] = .{ + .name = name, + .kind = entry_kind, + .inode = bsd_entry.fileno, + }; + buffer_index += 1; + } + return buffer_index; +} + +fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + var buffer_index: usize = 0; + while (buffer.len - buffer_index != 0) { + if (dr.end - dr.index == 0) { + // Refill the buffer, unless we've already created references to + // buffered data. + if (buffer_index != 0) break; + if (dr.state == .reset) { + posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) { + error.Unseekable => return error.Unexpected, + else => |e| return e, + }; + dr.state = .reading; + } + try current_thread.beginSyscall(); + const n: usize = while (true) { + const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break rc; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability + .FAULT => |err| return errnoBug(err), + .NOTDIR => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, + } + }; + if (n == 0) { + dr.state = .finished; + return 0; + } + dr.index = 0; + dr.end = n; + } + const entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); + const next_index = dr.index + entry.reclen; + dr.index = next_index; + + const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0); + if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; + + // illumos dirent doesn't expose type, so we have to call stat to get it. + const stat = try posixStatPath(current_thread, dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW); + + buffer[buffer_index] = .{ + .name = name, + .kind = stat.kind, + .inode = entry.ino, + }; + buffer_index += 1; + } + return buffer_index; +} + +fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { + _ = userdata; + _ = dr; + _ = buffer; + @panic("TODO"); +} + +fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { + _ = userdata; + _ = dr; + _ = buffer; + @panic("TODO"); +} + +fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { + _ = userdata; + _ = dr; + _ = buffer; + @panic("TODO"); +} + fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { _ = userdata; _ = dir_reader; @@ -9628,8 +9909,10 @@ fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Wri // Only global mutex since this is Threaded. Io.stderr_thread_mutex.lock(); if (!t.stderr_writer_initialized) { + const io_t = ioBasic(t); if (is_windows) t.stderr_writer.file = .stderr(); - t.stderr_writer.mode = try .detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple); + t.stderr_writer.io = io_t; + t.stderr_writer.mode = try .detect(io_t, t.stderr_writer.file, true, .streaming_simple); t.stderr_writer_initialized = true; } std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; @@ -9643,8 +9926,10 @@ fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer { // Only global mutex since this is Threaded. if (!Io.stderr_thread_mutex.tryLock()) return null; if (!t.stderr_writer_initialized) { + const io_t = ioBasic(t); if (is_windows) t.stderr_writer.file = .stderr(); - t.stderr_writer.mode = File.Writer.Mode.detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple) catch + t.stderr_writer.io = io_t; + t.stderr_writer.mode = File.Writer.Mode.detect(io_t, t.stderr_writer.file, true, .streaming_simple) catch return null; t.stderr_writer_initialized = true; } -- 2.54.0 From c4cefd68358470d464bf66bc41985cadd874db73 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 11 Dec 2025 13:42:47 -0800 Subject: [PATCH 045/195] std.Build.Step.Run: restore Color.inherit logic --- lib/std/Build/Step/Run.zig | 58 ++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 8aafc778205f120d49986b7f7a90fb8385ebcb99..f1e5313905f3ccc8786fb04c6ea3302a1c02896b 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1257,30 +1257,6 @@ fn runCommand( }; defer env_map.deinit(); - color: switch (run.color) { - .manual => {}, - .enable => { - try env_map.put("CLICOLOR_FORCE", "1"); - env_map.remove("NO_COLOR"); - }, - .disable => { - try env_map.put("NO_COLOR", "1"); - env_map.remove("CLICOLOR_FORCE"); - }, - .inherit => {}, - .auto => { - const capture_stderr = run.captured_stderr != null or switch (run.stdio) { - .check => |checks| checksContainStderr(checks.items), - .infer_from_args, .inherit, .zig_test => false, - }; - if (capture_stderr) { - continue :color .disable; - } else { - continue :color .inherit; - } - }, - } - const opt_generic_result = spawnChildAndCollect(run, argv, &env_map, has_side_effects, options, fuzz_context) catch |err| term: { // InvalidExe: cpu arch mismatch // FileNotFound: can happen with a wrong dynamic linker path @@ -1648,7 +1624,10 @@ fn spawnChildAndCollect( if (!run.disable_zig_progress and !inherit) { child.progress_node = options.progress_node; } - if (inherit) _ = std.debug.lockStderrWriter(&.{}); + if (inherit) { + const stderr = std.debug.lockStderrWriter(&.{}); + try setColorEnvironmentVariables(run, env_map, stderr.mode); + } defer if (inherit) std.debug.unlockStderrWriter(); var timer = try std.time.Timer.start(); const res = try evalGeneric(run, &child); @@ -1657,6 +1636,35 @@ fn spawnChildAndCollect( } } +fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, fwm: Io.File.Writer.Mode) !void { + color: switch (run.color) { + .manual => {}, + .enable => { + try env_map.put("CLICOLOR_FORCE", "1"); + env_map.remove("NO_COLOR"); + }, + .disable => { + try env_map.put("NO_COLOR", "1"); + env_map.remove("CLICOLOR_FORCE"); + }, + .inherit => switch (fwm) { + .terminal_escaped => continue :color .enable, + else => continue :color .disable, + }, + .auto => { + const capture_stderr = run.captured_stderr != null or switch (run.stdio) { + .check => |checks| checksContainStderr(checks.items), + .infer_from_args, .inherit, .zig_test => false, + }; + if (capture_stderr) { + continue :color .disable; + } else { + continue :color .inherit; + } + }, + } +} + const StdioPollEnum = enum { stdout, stderr }; fn evalZigTest( -- 2.54.0 From 0e230993d51d0ecded40d5235ada4f2f64036b26 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 11 Dec 2025 21:00:14 -0800 Subject: [PATCH 046/195] std.Io.Dir: add setFilePermissions and setFileOwner --- lib/std/Io.zig | 2 + lib/std/Io/Dir.zig | 41 ++++++++ lib/std/Io/Threaded.zig | 179 ++++++++++++++++++++++++++++++++++- lib/std/os/linux.zig | 4 +- lib/std/posix.zig | 184 ------------------------------------ lib/std/posix/test.zig | 2 +- lib/std/tar.zig | 6 +- lib/std/zig/parser_test.zig | 14 +-- src/Package/Fetch.zig | 4 +- src/link/Lld.zig | 12 +-- src/link/Wasm.zig | 2 +- 11 files changed, 239 insertions(+), 211 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 9db215c74719e7faf0dfe016a5fa621692dc5df6..b76cbebe3ac2edc4660c23346ea696aa2a891c19 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -684,7 +684,9 @@ pub const VTable = struct { dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void, dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize, dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void, + dirSetFileOwner: *const fn (?*anyopaque, Dir, []const u8, ?File.Uid, ?File.Gid, Dir.SetFileOwnerOptions) Dir.SetFileOwnerError!void, dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void, + dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void, dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 4e70a7be8929340e7cb843fc33ec027eb77c507a..085667edca81cae7195ccd78230f2710cad926cd 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -1698,6 +1698,29 @@ pub fn setPermissions(dir: Dir, io: Io, new_permissions: File.Permissions) SetPe return io.vtable.dirSetPermissions(io.userdata, dir, new_permissions); } +pub const SetFilePermissionsError = PathNameError || SetPermissionsError || error{ + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + /// `SetFilePermissionsOptions.follow_symlinks` was set to false, which is + /// not allowed by the file system or operating system. + OperationNotSupported, +}; + +pub const SetFilePermissionsOptions = struct { + follow_symlinks: bool = true, +}; + +/// Also known as "chmodat". +pub fn setFilePermissions( + dir: Dir, + io: Io, + sub_path: []const u8, + new_permissions: File.Permissions, + options: SetFilePermissionsOptions, +) SetFilePermissionsError!void { + return io.vtable.dirSetFilePermissions(io.userdata, sub_path, dir, new_permissions, options); +} + pub const SetOwnerError = File.SetOwnerError; /// Also known as "chown". @@ -1711,6 +1734,24 @@ pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerEr return io.vtable.dirSetOwner(io.userdata, dir, owner, group); } +pub const SetFileOwnerError = PathNameError || SetOwnerError; + +pub const SetFileOwnerOptions = struct { + follow_symlinks: bool = true, +}; + +/// Also known as "fchownat". +pub fn setFileOwner( + dir: Dir, + io: Io, + sub_path: []const u8, + owner: ?File.Uid, + group: ?File.Gid, + options: SetFileOwnerOptions, +) SetOwnerError!void { + return io.vtable.dirSetFileOwner(io.userdata, dir, sub_path, owner, group, options); +} + pub const SetTimestampsError = File.SetTimestampsError || PathNameError; pub const SetTimestampsOptions = struct { diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 61974f4717febcb2480314f2ff5c7b303147cab9..2fb0f3ad1f1b77fb8dbe6d05bfa93e8e7a79937c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -76,6 +76,7 @@ old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, use_sendfile: UseSendfile = .default, use_copy_file_range: UseCopyFileRange = .default, use_fcopyfile: UseFcopyfile = .default, +use_fchmodat2: UseFchmodat2 = .default, stderr_writer: File.Writer = .{ .io = undefined, @@ -124,6 +125,15 @@ pub const UseFcopyfile = if (have_fcopyfile) enum { pub const default: UseFcopyfile = .disabled; }; +pub const UseFchmodat2 = if (have_fchmodat2 and !have_fchmodat_flags) enum { + enabled, + disabled, + pub const default: UseFchmodat2 = .enabled; +} else enum { + disabled, + pub const default: UseFchmodat2 = .disabled; +}; + const Thread = struct { /// The value that needs to be passed to pthread_kill or tgkill in order to /// send a signal. @@ -712,7 +722,9 @@ pub fn io(t: *Threaded) Io { .dirSymLink = dirSymLink, .dirReadLink = dirReadLink, .dirSetOwner = dirSetOwner, + .dirSetFileOwner = dirSetFileOwner, .dirSetPermissions = dirSetPermissions, + .dirSetFilePermissions = dirSetFilePermissions, .dirSetTimestamps = dirSetTimestamps, .dirSetTimestampsNow = dirSetTimestampsNow, @@ -842,7 +854,9 @@ pub fn ioBasic(t: *Threaded) Io { .dirSymLink = dirSymLink, .dirReadLink = dirReadLink, .dirSetOwner = dirSetOwner, + .dirSetFileOwner = dirSetFileOwner, .dirSetPermissions = dirSetPermissions, + .dirSetFilePermissions = dirSetFilePermissions, .dirSetTimestamps = dirSetTimestamps, .dirSetTimestampsNow = dirSetTimestampsNow, @@ -921,6 +935,11 @@ const have_copy_file_range = switch (native_os) { else => false, }; const have_fcopyfile = is_darwin; +const have_fchmodat2 = native_os == .linux and + (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and + (builtin.abi.isAndroid() or !std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); +const have_fchmodat_flags = native_os != .linux or + (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; @@ -4862,11 +4881,11 @@ const dirSetPermissions = switch (native_os) { }; fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { - // TODO I think we can actually set permissions on a dir on windows? - _ = userdata; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; _ = dir; _ = permissions; - return error.Unexpected; + @panic("TODO"); } fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { @@ -4875,6 +4894,133 @@ fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Perm return setPermissionsPosix(current_thread, dir.handle, permissions.toMode()); } +fn dirSetFilePermissions( + userdata: ?*anyopaque, + dir: Dir, + sub_path: []const u8, + permissions: Dir.Permissions, + options: Dir.SetFilePermissionsOptions, +) Dir.SetFilePermissionsError!void { + if (!Dir.Permissions.has_executable_bit) return error.Unexpected; + if (is_windows) @panic("TODO"); + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + var path_buffer: [posix.PATH_MAX]u8 = undefined; + const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + + const mode = permissions.toMode(); + const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; + + return posixFchmodat(t, current_thread, dir.handle, sub_path_posix, mode, flags); +} + +fn posixFchmodat( + t: *Threaded, + current_thread: *Thread, + dir_fd: posix.fd_t, + path: [*:0]const u8, + mode: posix.mode_t, + flags: u32, +) Dir.SetFilePermissionsError!void { + // No special handling for linux is needed if we can use the libc fallback + // or `flags` is empty. Glibc only added the fallback in 2.32. + if (have_fchmodat_flags or flags == 0) { + try current_thread.beginSyscall(); + while (true) { + const rc = if (have_fchmodat_flags) + posix.system.fchmodat(dir_fd, path, mode, flags) + else + posix.system.fchmodat(dir_fd, path, mode); + switch (posix.errno(rc)) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ACCES => return error.AccessDenied, + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .OPNOTSUPP => return error.OperationNotSupported, + .PERM => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + + if (@atomicLoad(UseFchmodat2, &t.use_fchmodat2, .monotonic) == .disabled) + return fchmodatFallback(current_thread, dir_fd, path, mode, flags); + + comptime assert(native_os == .linux); + + try current_thread.beginSyscall(); + while (true) { + switch (std.os.linux.errno(std.os.linux.fchmodat2(dir_fd, path, mode, flags))) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ACCES => return error.AccessDenied, + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.FileNotFound, + .OPNOTSUPP => return error.OperationNotSupported, + .PERM => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + .NOSYS => { + @atomicStore(UseFchmodat2, &t.use_fchmodat2, .disabled, .monotonic); + return fchmodatFallback(current_thread, dir_fd, path, mode, flags); + }, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn fchmodatFallback( + current_thread: *Thread, + dir_fd: posix.fd_t, + path: [*:0]const u8, + mode: posix.mode_t, + flags: u32, +) Dir.SetFilePermissionsError!void { + _ = current_thread; + _ = dir_fd; + _ = path; + _ = mode; + _ = flags; + // I deleted the previous fallback implementation because it looked wrong to me. Please cross-reference + // fhmodat.c in musl libc before blindly restoring the implementation. + @panic("TODO"); +} + const dirSetOwner = switch (native_os) { .windows => dirSetOwnerUnsupported, else => dirSetOwnerPosix, @@ -4927,6 +5073,33 @@ fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: } } +fn dirSetFileOwner( + userdata: ?*anyopaque, + dir: Dir, + sub_path: []const u8, + owner: ?File.Uid, + group: ?File.Gid, + options: Dir.SetFileOwnerOptions, +) Dir.SetFileOwnerError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (is_windows) { + @panic("TODO"); + } + + var path_buffer: [posix.PATH_MAX]u8 = undefined; + const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + + _ = current_thread; + _ = dir; + _ = sub_path_posix; + _ = owner; + _ = group; + _ = options; + @panic("TODO"); +} + const fileSync = switch (native_os) { .windows => fileSyncWindows, else => fileSyncPosix, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index a12654ee443daeb6821b122cba5ed9092f1c02e8..c8e9180dbfe9ef0268042d86681c9dda7469c378 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1420,7 +1420,7 @@ pub fn chmod(path: [*:0]const u8, mode: mode_t) usize { if (@hasField(SYS, "chmod")) { return syscall2(.chmod, @intFromPtr(path), mode); } else { - return fchmodat(AT.FDCWD, path, mode, 0); + return fchmodat(AT.FDCWD, path, mode); } } @@ -1432,7 +1432,7 @@ pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize { } } -pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t, _: u32) usize { +pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t) usize { return syscall3(.fchmodat, @bitCast(@as(isize, fd)), @intFromPtr(path), mode); } diff --git a/lib/std/posix.zig b/lib/std/posix.zig index aa270e611df7acc13dcf6af2a8d0c93b38bbb9e0..da2b0e8786ce434a1995acead4e3f0339789cac8 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -310,190 +310,6 @@ pub fn close(fd: fd_t) void { } } -pub const FChmodAtError = std.Io.File.SetPermissionsError || error{ - /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded - /// `PATH_MAX`. - NameTooLong, - /// `path` resolves to a symbolic link, and `AT.SYMLINK_NOFOLLOW` was set - /// in `flags`. This error only occurs on Linux, where changing the mode of - /// a symbolic link has no meaning and can cause undefined behaviour on - /// certain filesystems. - /// - /// The procfs fallback was used but procfs was not mounted. - OperationNotSupported, - /// The procfs fallback was used but the process exceeded its open file - /// limit. - ProcessFdQuotaExceeded, - /// The procfs fallback was used but the system exceeded it open file limit. - SystemFdQuotaExceeded, -}; - -/// Changes the `mode` of `path` relative to the directory referred to by -/// `dirfd`. The process must have the correct privileges in order to do this -/// successfully, or must have the effective user ID matching the owner of the -/// file. -/// -/// On Linux the `fchmodat2` syscall will be used if available, otherwise a -/// workaround using procfs will be employed. Changing the mode of a symbolic -/// link with `AT.SYMLINK_NOFOLLOW` set will also return -/// `OperationNotSupported`, as: -/// -/// 1. Permissions on the link are ignored when resolving its target. -/// 2. This operation has been known to invoke undefined behaviour across -/// different filesystems[1]. -/// -/// [1]: https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html. -pub inline fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { - if (!fs.has_executable_bit) @compileError("fchmodat unsupported by target OS"); - - // No special handling for linux is needed if we can use the libc fallback - // or `flags` is empty. Glibc only added the fallback in 2.32. - const skip_fchmodat_fallback = native_os != .linux or - (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })) or - flags == 0; - - // This function is marked inline so that when flags is comptime-known, - // skip_fchmodat_fallback will be comptime-known true. - if (skip_fchmodat_fallback) - return fchmodat1(dirfd, path, mode, flags); - - return fchmodat2(dirfd, path, mode, flags); -} - -fn fchmodat1(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { - const path_c = try toPosixPath(path); - while (true) { - const res = system.fchmodat(dirfd, &path_c, mode, flags); - switch (errno(res)) { - .SUCCESS => return, - .INTR => continue, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .OPNOTSUPP => return error.OperationNotSupported, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } - } -} - -fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { - const global = struct { - var has_fchmodat2: bool = true; - }; - const path_c = try toPosixPath(path); - const use_fchmodat2 = (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse false) and - @atomicLoad(bool, &global.has_fchmodat2, .monotonic); - while (use_fchmodat2) { - // Later on this should be changed to `system.fchmodat2` - // when the musl/glibc add a wrapper. - const res = linux.fchmodat2(dirfd, &path_c, mode, flags); - switch (linux.errno(res)) { - .SUCCESS => return, - .INTR => continue, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.FileNotFound, - .OPNOTSUPP => return error.OperationNotSupported, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - - .NOSYS => { - @atomicStore(bool, &global.has_fchmodat2, false, .monotonic); - break; - }, - else => |err| return unexpectedErrno(err), - } - } - - // Fallback to changing permissions using procfs: - // - // 1. Open `path` as a `PATH` descriptor. - // 2. Stat the fd and check if it isn't a symbolic link. - // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`. - // 4. Pass the procfs path to `chmod` with the `mode`. - var pathfd: fd_t = undefined; - while (true) { - const rc = system.openat(dirfd, &path_c, .{ .PATH = true, .NOFOLLOW = true, .CLOEXEC = true }, @as(mode_t, 0)); - switch (errno(rc)) { - .SUCCESS => { - pathfd = @intCast(rc); - break; - }, - .INTR => continue, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } - } - defer close(pathfd); - - const path_mode = if (linux.wrapped.statx( - pathfd, - "", - AT.EMPTY_PATH, - .{ .TYPE = true }, - )) |stx| blk: { - assert(stx.mask.TYPE); - break :blk stx.mode; - } else |err| switch (err) { - error.NameTooLong => unreachable, - error.FileNotFound => unreachable, - else => |e| return e, - }; - // Even though we only wanted TYPE, the kernel can still fill in the additional bits. - if ((path_mode & S.IFMT) == S.IFLNK) - return error.OperationNotSupported; - - var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; - const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/fd/{d}", .{pathfd}, 0) catch unreachable; - while (true) { - const res = system.chmod(proc_path, mode); - switch (errno(res)) { - // Getting NOENT here means that procfs isn't mounted. - .NOENT => return error.OperationNotSupported, - - .SUCCESS => return, - .INTR => continue, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.FileNotFound, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return unexpectedErrno(err), - } - } -} - pub const RebootError = error{ PermissionDenied, } || UnexpectedError; diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index b5255ad2a1c719a4dfef0d7aa79526b4898a573a..1764d7ad358e6a1b120039c7dba41620f92735b5 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -895,7 +895,7 @@ fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void { } test "fchmodat smoke test" { - if (!std.fs.has_executable_bit) return error.SkipZigTest; + if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); diff --git a/lib/std/tar.zig b/lib/std/tar.zig index 7bb20a9959cd54bea9a106cb90abcc915904f1b7..92dc8b88f9f3a4b9b7a7cb5ad575814bac56303f 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -1122,7 +1122,7 @@ fn normalizePath(bytes: []u8) []u8 { fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { const default_mode = 0o666; - if (!std.fs.has_executable_bit or options.mode_mode == .ignore) + if (!Io.File.Permissions.has_executable_bit or options.mode_mode == .ignore) return .fromMode(default_mode); const S = std.posix.S; @@ -1137,7 +1137,7 @@ fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { } test filePermissions { - if (!std.fs.has_executable_bit) return error.SkipZigTest; + if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; try testing.expectEqual(0o666, filePermissions(0o744, PipeOptions{ .mode_mode = .ignore })); try testing.expectEqual(0o777, filePermissions(0o744, PipeOptions{})); try testing.expectEqual(0o666, filePermissions(0o644, PipeOptions{})); @@ -1145,7 +1145,7 @@ test filePermissions { } test "executable bit" { - if (!std.fs.has_executable_bit) return error.SkipZigTest; + if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; const io = testing.io; const S = std.posix.S; diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 3ce2603947e9f2693f480787b302d3b58c32cd6f..6064d3b54000882e5423641f1cc644b5c31ef2bb 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6334,7 +6334,7 @@ var fixed_buffer_mem: [100 * 1024]u8 = undefined; fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 { var buffer: [64]u8 = undefined; - const stderr, _ = std.debug.lockStderrWriter(&buffer); + const stderr = std.debug.lockStderrWriter(&buffer); defer std.debug.unlockStderrWriter(); var tree = try std.zig.Ast.parse(allocator, source, .zig); @@ -6342,17 +6342,17 @@ fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: * for (tree.errors) |parse_error| { const loc = tree.tokenLocation(0, parse_error.token); - try stderr.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); - try tree.renderError(parse_error, stderr); - try stderr.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); + try stderr.printUnescaped("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); + try tree.renderError(parse_error, &stderr.interface); + try stderr.interface.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); { var i: usize = 0; while (i < loc.column) : (i += 1) { - try stderr.writeAll(" "); + try stderr.writeAllUnescaped(" "); } - try stderr.writeAll("^"); + try stderr.writeAllUnescaped("^"); } - try stderr.writeAll("\n"); + try stderr.writeAllUnescaped("\n"); } if (tree.errors.len != 0) { return error.ParseError; diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 137d7635e7001d45692ac2d7112b2abb2a4a3da4..844acf933952612b4053ca55baf92ee82f232676 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1717,7 +1717,7 @@ fn deleteFileFallible(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) DeletedFi } fn setExecutable(file: Io.File) !void { - if (!std.fs.has_executable_bit) return; + if (!Io.File.Permissions.has_executable_bit) return; const S = std.posix.S; const mode = Io.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH; @@ -2183,7 +2183,7 @@ test "tarball without root folder" { } test "set executable bit based on file content" { - if (!std.fs.has_executable_bit) return error.SkipZigTest; + if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; const gpa = std.testing.allocator; const io = std.testing.io; diff --git a/src/link/Lld.zig b/src/link/Lld.zig index e3127b24bf9719d45a414ff53598aa6547910a88..91455f3fde70166ca1120e7e2933d2de4d439488 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1327,6 +1327,7 @@ fn getLDMOption(target: *const std.Target) ?[]const u8 { } fn wasmLink(lld: *Lld, arena: Allocator) !void { const comp = lld.base.comp; + const diags = &comp.link_diags; const shared_memory = comp.config.shared_memory; const export_memory = comp.config.export_memory; const import_memory = comp.config.import_memory; @@ -1566,17 +1567,12 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { // is not the case, it means we will get "exec format error" when trying to run // it, and then can react to that in the same way as trying to run an ELF file // from a foreign CPU architecture. - if (fs.has_executable_bit and target.os.tag == .wasi and + if (Io.File.Permissions.has_executable_bit and target.os.tag == .wasi and comp.config.output_mode == .Exe) { - // TODO: what's our strategy for reporting linker errors from this function? - // report a nice error here with the file path if it fails instead of - // just returning the error code. // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. - std.posix.fchmodat(Io.Dir.cwd().handle, full_out_path, 0o744, 0) catch |err| switch (err) { - error.OperationNotSupported => unreachable, // Not a symlink. - else => |e| return e, - }; + Io.Dir.cwd().setFilePermissions(full_out_path, .fromMode(0o744), .{}) catch |err| + return diags.fail("{s}: failed to enable executable permissions: {t}", .{ full_out_path, err }); } } } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 5f89625d56122b6d4872280d5c7c02fa1589ce39..80a248eb6f8af26a3a2f43868d7ac3d134fe5274 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3002,7 +3002,7 @@ pub fn createEmpty( wasm.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ .truncate = true, .read = true, - .mode = if (fs.has_executable_bit) + .mode = if (Io.File.Permissions.has_executable_bit) if (target.os.tag == .wasi and output_mode == .Exe) Io.File.default_mode | 0b001_000_000 else -- 2.54.0 From 68621afd2e203d82b6f53bf4ede951827fa98db8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 11 Dec 2025 22:11:16 -0800 Subject: [PATCH 047/195] std.tar: update fs API calls to take io argument --- lib/compiler/aro/aro/Compilation.zig | 3 +- lib/std/Build/Cache/Path.zig | 4 +- lib/std/Io.zig | 2 +- lib/std/Io/Dir.zig | 12 +++--- lib/std/Io/File.zig | 3 ++ lib/std/Io/Threaded.zig | 50 ++++++++++++------------ lib/std/fs/test.zig | 57 +++++++++++++++------------- lib/std/tar.zig | 54 +++++++++++--------------- lib/std/tar/test.zig | 4 +- lib/std/zig/WindowsSdk.zig | 2 +- lib/std/zig/parser_test.zig | 2 +- src/Builtin.zig | 3 +- src/Package/Fetch.zig | 20 +++++----- src/Package/Fetch/git.zig | 2 +- src/fmt.zig | 2 +- 15 files changed, 110 insertions(+), 110 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index b3e4d5544da9f245174a9e6e6f72d664c0a95ce7..a78f18dd743575f44d1693d0adf566c7b6eba264 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -2162,8 +2162,9 @@ pub fn locSlice(comp: *const Compilation, loc: Source.Location) []const u8 { } pub fn getSourceMTimeUncached(comp: *const Compilation, source_id: Source.Id) ?u64 { + const io = comp.io; const source = comp.getSource(source_id); - if (comp.cwd.statFile(source.path)) |stat| { + if (comp.cwd.statFile(io, source.path, .{})) |stat| { return std.math.cast(u64, stat.mtime.toSeconds()); } else |_| { return null; diff --git a/lib/std/Build/Cache/Path.zig b/lib/std/Build/Cache/Path.zig index 759eb143b8507b73a899595f413f29787250544e..54104afa413b8874d1ed74adf2cb927ebfc22110 100644 --- a/lib/std/Build/Cache/Path.zig +++ b/lib/std/Build/Cache/Path.zig @@ -94,14 +94,14 @@ pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io return p.root_dir.handle.makeOpenPath(joined_path, opts); } -pub fn statFile(p: Path, sub_path: []const u8) !Io.Dir.Stat { +pub fn statFile(p: Path, io: Io, sub_path: []const u8) !Io.Dir.Stat { var buf: [fs.max_path_bytes]u8 = undefined; const joined_path = if (p.sub_path.len == 0) sub_path else p: { break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ p.sub_path, sub_path, }) catch return error.NameTooLong; }; - return p.root_dir.handle.statFile(joined_path); + return p.root_dir.handle.statFile(io, joined_path, .{}); } pub fn atomicFile( diff --git a/lib/std/Io.zig b/lib/std/Io.zig index b76cbebe3ac2edc4660c23346ea696aa2a891c19..83a2e940bf389922d756f9bb0bb2c8d3570a46a2 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -671,7 +671,7 @@ pub const VTable = struct { dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, dirOpenDir: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.OpenError!Dir, dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat, - dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat, + dirStatFile: *const fn (?*anyopaque, Dir, []const u8, Dir.StatFileOptions) Dir.StatFileError!File.Stat, dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void, dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File, dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 085667edca81cae7195ccd78230f2710cad926cd..0bc3982a61f5014e116cdd201d89347aadc6f19c 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -669,7 +669,7 @@ pub fn makeDirAbsolute(io: Io, absolute_path: []const u8, permissions: Permissio test makeDirAbsolute {} -pub const MakePathError = MakeError || StatPathError; +pub const MakePathError = MakeError || StatFileError; /// Creates parent directories with default permissions as necessary to ensure /// `sub_path` exists as a directory. @@ -708,7 +708,7 @@ pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permi return io.vtable.dirMakePath(io.userdata, dir, sub_path, permissions); } -pub const MakeOpenPathError = MakeError || OpenError || StatPathError; +pub const MakeOpenPathError = MakeError || OpenError || StatFileError; pub const MakeOpenPathOptions = struct { open_options: OpenOptions = .{}, @@ -734,9 +734,9 @@ pub fn stat(dir: Dir, io: Io) StatError!Stat { return io.vtable.dirStat(io.userdata, dir); } -pub const StatPathError = File.OpenError || File.StatError; +pub const StatFileError = File.OpenError || File.StatError; -pub const StatPathOptions = struct { +pub const StatFileOptions = struct { follow_symlinks: bool = true, }; @@ -752,8 +752,8 @@ pub const StatPathOptions = struct { /// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). /// * On WASI, `sub_path` should be encoded as valid UTF-8. /// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat { - return io.vtable.dirStatPath(io.userdata, dir, sub_path, options); +pub fn statFile(dir: Dir, io: Io, sub_path: []const u8, options: StatFileOptions) StatFileError!Stat { + return io.vtable.dirStatFile(io.userdata, dir, sub_path, options); } pub const RealPathError = error{ diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index ccf18a8f566f365bd6cc1222dedfc26f4c8a147a..3c04909d46df6e40b3a2890b1570f00fad245ef6 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -372,6 +372,7 @@ pub const Permissions = std.options.FilePermissions orelse if (is_windows) enum( _, pub const default_dir: @This() = .default_file; + pub const executable_file: @This() = .default_file; pub const has_executable_bit = false; const windows = std.os.windows; @@ -401,6 +402,7 @@ pub const Permissions = std.options.FilePermissions orelse if (is_windows) enum( /// process-scoped "umask" setting to adjust this number for file creation. default_file = 0o666, default_dir = 0o755, + executable_file = 0o777, _, pub const has_executable_bit = true; @@ -428,6 +430,7 @@ pub const Permissions = std.options.FilePermissions orelse if (is_windows) enum( } else enum(u0) { default_file = 0, pub const default_dir: @This() = .default_file; + pub const executable_file: @This() = .default_file; pub const has_executable_bit = false; }; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 2fb0f3ad1f1b77fb8dbe6d05bfa93e8e7a79937c..d4c4da3990260f7c5a23a12f423608a1eb53bc3e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -708,7 +708,7 @@ pub fn io(t: *Threaded) Io { .dirMakePath = dirMakePath, .dirMakeOpenPath = dirMakeOpenPath, .dirStat = dirStat, - .dirStatPath = dirStatPath, + .dirStatFile = dirStatFile, .dirAccess = dirAccess, .dirCreateFile = dirCreateFile, .dirOpenFile = dirOpenFile, @@ -840,7 +840,7 @@ pub fn ioBasic(t: *Threaded) Io { .dirMakePath = dirMakePath, .dirMakeOpenPath = dirMakeOpenPath, .dirStat = dirStat, - .dirStatPath = dirStatPath, + .dirStatFile = dirStatFile, .dirAccess = dirAccess, .dirCreateFile = dirCreateFile, .dirOpenFile = dirOpenFile, @@ -1623,7 +1623,7 @@ fn dirMakePath( // could cause an infinite loop check_dir: { // workaround for windows, see https://github.com/ziglang/zig/issues/16738 - const fstat = dirStatPath(t, dir, component.path, .{}) catch |stat_err| switch (stat_err) { + const fstat = dirStatFile(t, dir, component.path, .{}) catch |stat_err| switch (stat_err) { error.IsDir => break :check_dir, else => |e| return e, }; @@ -1752,7 +1752,7 @@ fn dirMakeOpenPathWindows( // could cause an infinite loop check_dir: { // workaround for windows, see https://github.com/ziglang/zig/issues/16738 - const fstat = dirStatPathWindows(t, dir, component.path, .{ + const fstat = dirStatFileWindows(t, dir, component.path, .{ .follow_symlinks = options.follow_symlinks, }) catch |stat_err| switch (stat_err) { error.IsDir => break :check_dir, @@ -1806,19 +1806,19 @@ fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { return fileStat(t, file); } -const dirStatPath = switch (native_os) { - .linux => dirStatPathLinux, - .windows => dirStatPathWindows, - .wasi => dirStatPathWasi, - else => dirStatPathPosix, +const dirStatFile = switch (native_os) { + .linux => dirStatFileLinux, + .windows => dirStatFileWindows, + .wasi => dirStatFileWasi, + else => dirStatFilePosix, }; -fn dirStatPathLinux( +fn dirStatFileLinux( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, - options: Dir.StatPathOptions, -) Dir.StatPathError!File.Stat { + options: Dir.StatFileOptions, +) Dir.StatFileError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const linux = std.os.linux; @@ -1875,12 +1875,12 @@ fn dirStatPathLinux( } } -fn dirStatPathPosix( +fn dirStatFilePosix( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, - options: Dir.StatPathOptions, -) Dir.StatPathError!File.Stat { + options: Dir.StatFileOptions, +) Dir.StatFileError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -1889,10 +1889,10 @@ fn dirStatPathPosix( const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; - return posixStatPath(current_thread, dir.handle, sub_path_posix, flags); + return posixStatFile(current_thread, dir.handle, sub_path_posix, flags); } -fn posixStatPath(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatPathError!File.Stat { +fn posixStatFile(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatFileError!File.Stat { try current_thread.beginSyscall(); while (true) { var stat = std.mem.zeroes(posix.Stat); @@ -1927,12 +1927,12 @@ fn posixStatPath(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]cons } } -fn dirStatPathWindows( +fn dirStatFileWindows( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, - options: Dir.StatPathOptions, -) Dir.StatPathError!File.Stat { + options: Dir.StatFileOptions, +) Dir.StatFileError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); const file = try dirOpenFileWindows(t, dir, sub_path, .{ .follow_symlinks = options.follow_symlinks, @@ -1941,13 +1941,13 @@ fn dirStatPathWindows( return fileStatWindows(t, file); } -fn dirStatPathWasi( +fn dirStatFileWasi( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, - options: Dir.StatPathOptions, -) Dir.StatPathError!File.Stat { - if (builtin.link_libc) return dirStatPathPosix(userdata, dir, sub_path, options); + options: Dir.StatFileOptions, +) Dir.StatFileError!File.Stat { + if (builtin.link_libc) return dirStatFilePosix(userdata, dir, sub_path, options); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; @@ -3629,7 +3629,7 @@ fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; // illumos dirent doesn't expose type, so we have to call stat to get it. - const stat = try posixStatPath(current_thread, dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW); + const stat = try posixStatFile(current_thread, dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW); buffer[buffer_index] = .{ .name = name, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 3a4b551ae8dfbe9b518a28bd85ae6f061810fcbe..dd9b21d77b3941d522c0111200808ed786ff5869 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -160,8 +160,8 @@ fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep: // For use in test setup. If the symlink creation fails on Windows with // AccessDenied, then make the test failure silent (it is not a Zig failure). -fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void { - return dir.symLink(target, link, flags) catch |err| switch (err) { +fn setupSymlink(io: Io, dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void { + return dir.symLink(io, target, link, flags) catch |err| switch (err) { // Symlink requires admin privileges on windows, so this test can legitimately fail. error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err, else => return err, @@ -193,15 +193,15 @@ test "Dir.readLink" { const canonical_dir_target_path = try ctx.toCanonicalPathSep(dir_target_path); // test 1: symlink to a file - try setupSymlink(ctx.dir, file_target_path, "symlink1", .{}); - try testReadLink(ctx.dir, canonical_file_target_path, "symlink1"); + try setupSymlink(io, ctx.dir, file_target_path, "symlink1", .{}); + try testReadLink(io, ctx.dir, canonical_file_target_path, "symlink1"); if (builtin.os.tag == .windows) { try testReadLinkW(testing.allocator, ctx.dir, canonical_file_target_path, "symlink1"); } // test 2: symlink to a directory (can be different on Windows) - try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true }); - try testReadLink(ctx.dir, canonical_dir_target_path, "symlink2"); + try setupSymlink(io, ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true }); + try testReadLink(io, ctx.dir, canonical_dir_target_path, "symlink2"); if (builtin.os.tag == .windows) { try testReadLinkW(testing.allocator, ctx.dir, canonical_dir_target_path, "symlink2"); } @@ -211,8 +211,8 @@ test "Dir.readLink" { const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file); var subdir = try ctx.dir.makeOpenPath("subdir", .{}); defer subdir.close(io); - try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{}); - try testReadLink(subdir, canonical_parent_file, "relative-link.txt"); + try setupSymlink(io, subdir, canonical_parent_file, "relative-link.txt", .{}); + try testReadLink(io, subdir, canonical_parent_file, "relative-link.txt"); if (builtin.os.tag == .windows) { try testReadLinkW(testing.allocator, subdir, canonical_parent_file, "relative-link.txt"); } @@ -246,9 +246,9 @@ test "Dir.readLink on non-symlinks" { }.impl); } -fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { +fn testReadLink(io: Io, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { var buffer: [fs.max_path_bytes]u8 = undefined; - const actual = try dir.readLink(symlink_path, buffer[0..]); + const actual = try dir.readLink(io, symlink_path, &buffer); try testing.expectEqualStrings(target_path, actual); } @@ -284,7 +284,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { const dir_target_path = try ctx.transformPath("subdir"); try ctx.dir.makeDir(io, dir_target_path, .default_dir); - try setupSymlink(ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); + try setupSymlink(io, ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); var symlink: Dir = switch (builtin.target.os.tag) { .windows => windows_symlink: { @@ -809,11 +809,11 @@ test "Dir.statFile" { const io = ctx.io; const test_file_name = try ctx.transformPath("test_file"); - try testing.expectError(error.FileNotFound, ctx.dir.statFile(test_file_name)); + try testing.expectError(error.FileNotFound, ctx.dir.statFile(io, test_file_name, .{})); try ctx.dir.writeFile(io, .{ .sub_path = test_file_name, .data = "" }); - const stat = try ctx.dir.statFile(test_file_name); + const stat = try ctx.dir.statFile(io, test_file_name, .{}); try testing.expectEqual(File.Kind.file, stat.kind); } }.impl); @@ -822,12 +822,13 @@ test "Dir.statFile" { test "statFile on dangling symlink" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const symlink_name = try ctx.transformPath("dangling-symlink"); const symlink_target = "." ++ fs.path.sep_str ++ "doesnotexist"; - try setupSymlink(ctx.dir, symlink_target, symlink_name, .{}); + try setupSymlink(io, ctx.dir, symlink_target, symlink_name, .{}); - try std.testing.expectError(error.FileNotFound, ctx.dir.statFile(symlink_name)); + try std.testing.expectError(error.FileNotFound, ctx.dir.statFile(io, symlink_name, .{})); } }.impl); } @@ -1206,7 +1207,7 @@ test "deleteTree does not follow symlinks" { var a = try tmp.dir.makeOpenPath("a", .{}); defer a.close(io); - try setupSymlink(a, "../b", "b", .{ .is_directory = true }); + try setupSymlink(io, a, "../b", "b", .{ .is_directory = true }); } try tmp.dir.deleteTree(io, "a"); @@ -1223,7 +1224,7 @@ test "deleteTree on a symlink" { // Symlink to a file try tmp.dir.writeFile(io, .{ .sub_path = "file", .data = "" }); - try setupSymlink(tmp.dir, "file", "filelink", .{}); + try setupSymlink(io, tmp.dir, "file", "filelink", .{}); try tmp.dir.deleteTree(io, "filelink"); try testing.expectError(error.FileNotFound, tmp.dir.access(io, "filelink", .{})); @@ -1231,7 +1232,7 @@ test "deleteTree on a symlink" { // Symlink to a directory try tmp.dir.makePath(io, "dir"); - try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true }); + try setupSymlink(io, tmp.dir, "dir", "dirlink", .{ .is_directory = true }); try tmp.dir.deleteTree(io, "dirlink"); try testing.expectError(error.FileNotFound, tmp.dir.access(io, "dirlink", .{})); @@ -1337,7 +1338,7 @@ test "makepath through existing valid symlink" { defer tmp.cleanup(); try tmp.dir.makeDir(io, "realfolder", .default_dir); - try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{}); + try setupSymlink(io, tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{}); try tmp.dir.makePath(io, "working-symlink" ++ fs.path.sep_str ++ "in-realfolder"); @@ -2178,12 +2179,12 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, ctx.dir.rename(invalid_path, ctx.dir, invalid_path, io)); - try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{})); + try testing.expectError(expected_err, ctx.dir.symLink(io, invalid_path, invalid_path, .{})); if (native_os == .wasi) { try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{})); } - try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{})); + try testing.expectError(expected_err, ctx.dir.readLink(io, invalid_path, &[_]u8{})); if (native_os == .wasi) { try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{})); } @@ -2386,14 +2387,16 @@ test "File.Writer sendfile with buffered contents" { test "readlink on Windows" { if (native_os != .windows) return error.SkipZigTest; - try testReadlink("C:\\ProgramData", "C:\\Users\\All Users"); - try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User"); - try testReadlink("C:\\Users", "C:\\Documents and Settings"); + const io = testing.io; + + try testReadLinkWindows(io, "C:\\ProgramData", "C:\\Users\\All Users"); + try testReadLinkWindows(io, "C:\\Users\\Default", "C:\\Users\\Default User"); + try testReadLinkWindows(io, "C:\\Users", "C:\\Documents and Settings"); } -fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { +fn testReadLinkWindows(io: Io, target_path: []const u8, symlink_path: []const u8) !void { var buffer: [fs.max_path_bytes]u8 = undefined; - const given = try Dir.readLinkAbsolute(symlink_path, buffer[0..]); + const given = try Dir.readLinkAbsolute(io, symlink_path, &buffer); try expect(mem.eql(u8, target_path, given)); } @@ -2424,6 +2427,6 @@ test "readlinkat" { // read the link var buffer: [fs.max_path_bytes]u8 = undefined; - const read_link = try tmp.dir.readLink("link", &buffer); + const read_link = try tmp.dir.readLink(io, "link", &buffer); try expect(mem.eql(u8, "file.txt", read_link)); } diff --git a/lib/std/tar.zig b/lib/std/tar.zig index 92dc8b88f9f3a4b9b7a7cb5ad575814bac56303f..d45aa9944324b8f6441d7e1bed7b4b2bae19a6b3 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -653,11 +653,11 @@ fn createDirAndFile(io: Io, dir: Io.Dir, file_name: []const u8, permissions: Io. // Creates a symbolic link at path `file_name` which points to `link_name`. fn createDirAndSymlink(io: Io, dir: Io.Dir, link_name: []const u8, file_name: []const u8) !void { - dir.symLink(link_name, file_name, .{}) catch |err| { + dir.symLink(io, link_name, file_name, .{}) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { try dir.makePath(io, dir_name); - return try dir.symLink(link_name, file_name, .{}); + return try dir.symLink(io, link_name, file_name, .{}); } } return err; @@ -996,15 +996,15 @@ test pipeToFileSystem { return err; }; - try testing.expectError(error.FileNotFound, dir.statFile("empty")); - try testing.expect((try dir.statFile("a/file")).kind == .file); - try testing.expect((try dir.statFile("b/symlink")).kind == .file); // statFile follows symlink + try testing.expectError(error.FileNotFound, dir.statFile(io, "empty", .{})); + try testing.expect((try dir.statFile(io, "a/file", .{})).kind == .file); + try testing.expect((try dir.statFile(io, "b/symlink", .{})).kind == .file); // statFile follows symlink var buf: [32]u8 = undefined; try testing.expectEqualSlices( u8, "../a/file", - normalizePath(try dir.readLink("b/symlink", &buf)), + normalizePath(buf[0..try dir.readLink(io, "b/symlink", &buf)]), ); } @@ -1120,28 +1120,18 @@ fn normalizePath(bytes: []u8) []u8 { // File system mode based on tar header mode and mode_mode options. fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { - const default_mode = 0o666; - - if (!Io.File.Permissions.has_executable_bit or options.mode_mode == .ignore) - return .fromMode(default_mode); - - const S = std.posix.S; - - // The mode from the tar file is inspected for the owner executable bit. - if (mode & S.IXUSR == 0) - return .fromMode(default_mode); - - // This bit is copied to the group and other executable bits. - // Other bits of the mode are left as the default when creating files. - return .fromMode(default_mode | S.IXUSR | S.IXGRP | S.IXOTH); + return if (!Io.File.Permissions.has_executable_bit or options.mode_mode == .ignore or (mode & 0o100) == 0) + .default_file + else + .executable_file; } test filePermissions { if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; - try testing.expectEqual(0o666, filePermissions(0o744, PipeOptions{ .mode_mode = .ignore })); - try testing.expectEqual(0o777, filePermissions(0o744, PipeOptions{})); - try testing.expectEqual(0o666, filePermissions(0o644, PipeOptions{})); - try testing.expectEqual(0o666, filePermissions(0o655, PipeOptions{})); + try testing.expectEqual(.default_file, filePermissions(0o744, .{ .mode_mode = .ignore })); + try testing.expectEqual(.executable_file, filePermissions(0o744, .{})); + try testing.expectEqual(.default_file, filePermissions(0o644, .{})); + try testing.expectEqual(.default_file, filePermissions(0o655, .{})); } test "executable bit" { @@ -1167,19 +1157,21 @@ test "executable bit" { return err; }; - const fs = try tmp.dir.statFile("a/file"); + const fs = try tmp.dir.statFile(io, "a/file", .{}); try testing.expect(fs.kind == .file); + const mode = fs.permissions.toMode(); + if (opt == .executable_bit_only) { // Executable bit is set for user, group and others - try testing.expect(fs.mode & S.IXUSR > 0); - try testing.expect(fs.mode & S.IXGRP > 0); - try testing.expect(fs.mode & S.IXOTH > 0); + try testing.expect(mode & S.IXUSR > 0); + try testing.expect(mode & S.IXGRP > 0); + try testing.expect(mode & S.IXOTH > 0); } if (opt == .ignore) { - try testing.expect(fs.mode & S.IXUSR == 0); - try testing.expect(fs.mode & S.IXGRP == 0); - try testing.expect(fs.mode & S.IXOTH == 0); + try testing.expect(mode & S.IXUSR == 0); + try testing.expect(mode & S.IXGRP == 0); + try testing.expect(mode & S.IXOTH == 0); } } } diff --git a/lib/std/tar/test.zig b/lib/std/tar/test.zig index b3fbd8f4b3cce2f1c887346f716f8e1840e9cee0..61aeb03519c82f8bdd9977b22f979086014dc2af 100644 --- a/lib/std/tar/test.zig +++ b/lib/std/tar/test.zig @@ -504,6 +504,6 @@ test "case sensitivity" { }; // on case sensitive os both files are created - try testing.expect((try root.dir.statFile("alacritty/darkermatrix.yml")).kind == .file); - try testing.expect((try root.dir.statFile("alacritty/Darkermatrix.yml")).kind == .file); + try testing.expect((try root.dir.statFile(io, "alacritty/darkermatrix.yml", .{})).kind == .file); + try testing.expect((try root.dir.statFile(io, "alacritty/Darkermatrix.yml", .{})).kind == .file); } diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index 3f6d58b6bab8e9ecd35be647a48abcbd987b234d..6da25822ca516cf78f76a1051ecd75df806028ab 100644 --- a/lib/std/zig/WindowsSdk.zig +++ b/lib/std/zig/WindowsSdk.zig @@ -1012,7 +1012,7 @@ const MsvcLibDir = struct { var dir = std.fs.openDirAbsolute(lib_dir_path, .{}) catch return false; defer dir.close(io); - const stat = dir.statFile("vcruntime.lib") catch return false; + const stat = dir.statFile(io, "vcruntime.lib", .{}) catch return false; if (stat.kind != .file) return false; diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 6064d3b54000882e5423641f1cc644b5c31ef2bb..9d9a6a48cedb0a02219f48e98c4f5cac3703cb75 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -4539,7 +4539,7 @@ test "zig fmt: Only indent multiline string literals in function calls" { test "zig fmt: Don't add extra newline after if" { try testCanonical( \\pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void { - \\ if (cwd().symLink(existing_path, new_path, .{})) { + \\ if (foo().bar(existing_path, new_path, .{})) { \\ return; \\ } \\} diff --git a/src/Builtin.zig b/src/Builtin.zig index b0077f2276c56fa37c9337ec7b4c4e04cbfef7b4..9e4fae8e6a1832e6cfba0eea1ec68963021c1c4a 100644 --- a/src/Builtin.zig +++ b/src/Builtin.zig @@ -313,8 +313,9 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void { assert(file.source != null); const root_dir, const sub_path = file.path.openInfo(comp.dirs); + const io = comp.io; - if (root_dir.statFile(sub_path)) |stat| { + if (root_dir.statFile(io, sub_path, .{})) |stat| { if (stat.size != file.source.?.len) { std.log.warn( "the cached file '{f}' had the wrong size. Expected {d}, found {d}. " ++ diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 844acf933952612b4053ca55baf92ee82f232676..d25ee55c32bb1b94a02485fcfeeda551a1d6a488 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1440,13 +1440,13 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void }, .sym_link => { var buf: [fs.max_path_bytes]u8 = undefined; - const link_name = try dir.readLink(entry.path, &buf); + const link_name = try dir.readLink(io, entry.path, &buf); // TODO: if this would create a symlink to outside // the destination directory, fail with an error instead. - tmp_dir.symLink(link_name, entry.path, .{}) catch |err| switch (err) { + tmp_dir.symLink(io, link_name, entry.path, .{}) catch |err| switch (err) { error.FileNotFound => { if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(io, dirname); - try tmp_dir.symLink(link_name, entry.path, .{}); + try tmp_dir.symLink(io, link_name, entry.path, .{}); }, else => |e| return e, }; @@ -1698,7 +1698,7 @@ fn hashFileFallible(io: Io, dir: Io.Dir, hashed_file: *HashedFile) HashedFile.Er } }, .link => { - const link_name = try dir.readLink(hashed_file.fs_path, &buf); + const link_name = try dir.readLink(io, hashed_file.fs_path, &buf); if (fs.path.sep != canonical_sep) { // Package hashes are intended to be consistent across // platforms which means we must normalize path separators @@ -2218,13 +2218,13 @@ test "set executable bit based on file content" { defer out.close(io); const S = std.posix.S; // expect executable bit not set - try std.testing.expect((try out.statFile("file1")).mode & S.IXUSR == 0); - try std.testing.expect((try out.statFile("script_without_shebang")).mode & S.IXUSR == 0); + try std.testing.expect((try out.statFile(io, "file1", .{})).mode & S.IXUSR == 0); + try std.testing.expect((try out.statFile(io, "script_without_shebang", .{})).mode & S.IXUSR == 0); // expect executable bit set - try std.testing.expect((try out.statFile("hello")).mode & S.IXUSR != 0); - try std.testing.expect((try out.statFile("script")).mode & S.IXUSR != 0); - try std.testing.expect((try out.statFile("script_with_shebang_without_exec_bit")).mode & S.IXUSR != 0); - try std.testing.expect((try out.statFile("hello_ln")).mode & S.IXUSR != 0); + try std.testing.expect((try out.statFile(io, "hello", .{})).mode & S.IXUSR != 0); + try std.testing.expect((try out.statFile(io, "script", .{})).mode & S.IXUSR != 0); + try std.testing.expect((try out.statFile(io, "script_with_shebang_without_exec_bit", .{})).mode & S.IXUSR != 0); + try std.testing.expect((try out.statFile(io, "hello_ln", .{})).mode & S.IXUSR != 0); // // $ ls -al zig-cache/tmp/OCz9ovUcstDjTC_U/zig-global-cache/p/1220fecb4c06a9da8673c87fe8810e15785f1699212f01728eadce094d21effeeef3 diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index 33ff982b66a5d65f7ad8f62b2f3b40565f8fbd49..255f0e6f6961a9e35230e43646d262ec3787aa6d 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -281,7 +281,7 @@ pub const Repository = struct { const symlink_object = try repository.odb.readObject(); if (symlink_object.type != .blob) return error.InvalidFile; const link_name = symlink_object.data; - dir.symLink(link_name, entry.name, .{}) catch |e| { + dir.symLink(io, link_name, entry.name, .{}) catch |e| { const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name }); errdefer diagnostics.allocator.free(file_name); const link_name_dup = try diagnostics.allocator.dupe(u8, link_name); diff --git a/src/fmt.zig b/src/fmt.zig index 1a1e5298e22ef43a39344f7e44ea511ce6e3b93b..59b4470f81820fe1482114e68105997d0876d96c 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -182,7 +182,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! // Mark any excluded files/directories as already seen, // so that they are skipped later during actual processing for (excluded_files.items) |file_path| { - const stat = Io.Dir.cwd().statFile(file_path) catch |err| switch (err) { + const stat = Io.Dir.cwd().statFile(io, file_path, .{}) catch |err| switch (err) { error.FileNotFound => continue, // On Windows, statFile does not work for directories error.IsDir => dir: { -- 2.54.0 From e1cf753db72425fd944f6fe9f2a991fb1de3f942 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 11 Dec 2025 22:35:55 -0800 Subject: [PATCH 048/195] std: update fchmodat tests --- lib/std/Build.zig | 2 +- lib/std/Io/Dir.zig | 2 +- lib/std/Io/Kqueue.zig | 4 +- lib/std/fs/test.zig | 487 ++++++++++++++++++++--------------------- lib/std/posix/test.zig | 61 ------ 5 files changed, 243 insertions(+), 313 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 085254fdde048aa7ba5e49d298aac5d3ba4efd3e..b7300cb5df036d3eb04d519fd38faaed60c756cb 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1698,7 +1698,7 @@ pub fn addCheckFile( return Step.CheckFile.create(b, file_source, options); } -pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.MakeError || Io.Dir.StatPathError)!void { +pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.MakeError || Io.Dir.StatFileError)!void { const io = b.graph.io; if (b.verbose) log.info("truncate {s}", .{dest_path}); const cwd = Io.Dir.cwd(); diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 0bc3982a61f5014e116cdd201d89347aadc6f19c..be82b8b8b26065098f94f96999d1ec177ce9935c 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -1710,7 +1710,7 @@ pub const SetFilePermissionsOptions = struct { follow_symlinks: bool = true, }; -/// Also known as "chmodat". +/// Also known as "fchmodat". pub fn setFilePermissions( dir: Dir, io: Io, diff --git a/lib/std/Io/Kqueue.zig b/lib/std/Io/Kqueue.zig index e34c862ae99ec017aae3247e3d7037ed264c26fe..9f9403d5adaed561ce0cb4db850331eb208450d9 100644 --- a/lib/std/Io/Kqueue.zig +++ b/lib/std/Io/Kqueue.zig @@ -873,7 +873,7 @@ pub fn io(k: *Kqueue) Io { .dirMakePath = dirMakePath, .dirMakeOpenPath = dirMakeOpenPath, .dirStat = dirStat, - .dirStatPath = dirStatPath, + .dirStatFile = dirStatFile, .fileStat = fileStat, .dirAccess = dirAccess, @@ -1144,7 +1144,7 @@ fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { _ = dir; @panic("TODO"); } -fn dirStatPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.StatPathOptions) Dir.StatPathError!File.Stat { +fn dirStatFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.StatPathOptions) Dir.StatFileError!File.Stat { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = dir; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index dd9b21d77b3941d522c0111200808ed786ff5869..5205e8eacf1606fca5297aa8b4d7df4faf74dda1 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -3,19 +3,23 @@ const native_os = builtin.os.tag; const std = @import("../std.zig"); const Io = std.Io; -const testing = std.testing; -const expect = std.testing.expect; const fs = std.fs; const mem = std.mem; const wasi = std.os.wasi; const windows = std.os.windows; -const posix = std.posix; const ArenaAllocator = std.heap.ArenaAllocator; const Dir = std.Io.Dir; const File = std.Io.File; -const tmpDir = std.testing.tmpDir; const SymLinkFlags = std.Io.Dir.SymLinkFlags; +const testing = std.testing; +const expect = std.testing.expect; +const expectError = std.testing.expectError; +const expectEqual = std.testing.expectEqual; +const tmpDir = std.testing.tmpDir; +const expectEqualStrings = std.testing.expectEqualStrings; +const expectEqualSlices = std.testing.expectEqualSlices; + const PathType = enum { relative, absolute, @@ -29,7 +33,7 @@ const PathType = enum { }; } - pub const TransformError = posix.RealPathError || error{OutOfMemory}; + pub const TransformError = Io.Dir.RealPathError || error{OutOfMemory}; pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8; pub fn getTransformFn(comptime path_type: PathType) TransformFn { @@ -249,7 +253,7 @@ test "Dir.readLink on non-symlinks" { fn testReadLink(io: Io, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { var buffer: [fs.max_path_bytes]u8 = undefined; const actual = try dir.readLink(io, symlink_path, &buffer); - try testing.expectEqualStrings(target_path, actual); + try expectEqualStrings(target_path, actual); } fn testReadLinkW(allocator: mem.Allocator, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { @@ -260,13 +264,13 @@ fn testReadLinkW(allocator: mem.Allocator, dir: Dir, target_path: []const u8, sy const wtf16_buffer = try allocator.alloc(u16, target_path_w.len); defer allocator.free(wtf16_buffer); const actual = try dir.readLinkW(symlink_path_w.span(), wtf16_buffer); - try testing.expectEqualSlices(u16, target_path_w, actual); + try expectEqualSlices(u16, target_path_w, actual); } fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { var buffer: [fs.max_path_bytes]u8 = undefined; const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]); - try testing.expectEqualStrings(target_path, given); + try expectEqualStrings(target_path, given); } test "File.stat on a File that is a symlink returns Kind.sym_link" { @@ -286,72 +290,11 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { try setupSymlink(io, ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); - var symlink: Dir = switch (builtin.target.os.tag) { - .windows => windows_symlink: { - const sub_path_w = try windows.cStrToPrefixedFileW(ctx.dir.handle, "symlink"); - - var result: Dir = .{ - .handle = undefined, - }; - - const path_len_bytes = @as(u16, @intCast(sub_path_w.span().len * 2)); - var nt_name = windows.UNICODE_STRING{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(&sub_path_w.data), - }; - var attr: windows.OBJECT_ATTRIBUTES = .{ - .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), - .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else ctx.dir.handle, - .Attributes = 0, - .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }; - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - const rc = windows.ntdll.NtCreateFile( - &result.handle, - windows.STANDARD_RIGHTS_READ | windows.FILE_READ_ATTRIBUTES | windows.FILE_READ_EA | windows.SYNCHRONIZE | windows.FILE_TRAVERSE, - &attr, - &io_status_block, - null, - .{ .NORMAL = true }, - .VALID_FLAGS, - .OPEN, - .{ - .DIRECTORY_FILE = true, - .IO = .SYNCHRONOUS_NONALERT, - .OPEN_FOR_BACKUP_INTENT = true, - .OPEN_REPARSE_POINT = true, // the important thing here - }, - null, - 0, - ); - - switch (rc) { - .SUCCESS => break :windows_symlink .{ .fd = result.handle }, - else => return windows.unexpectedStatus(rc), - } - }, - .linux => linux_symlink: { - const sub_path_c = try posix.toPosixPath("symlink"); - // the O_NOFOLLOW | O_PATH combination can obtain a fd to a symlink - // note that if O_DIRECTORY is set, then this will error with ENOTDIR - const flags: posix.O = .{ - .NOFOLLOW = true, - .PATH = true, - .ACCMODE = .RDONLY, - .CLOEXEC = true, - }; - const fd = try posix.openatZ(ctx.dir.handle, &sub_path_c, flags, 0); - break :linux_symlink .{ .handle = fd }; - }, - else => unreachable, - }; + var symlink: Dir = try ctx.dir.openDir("symlink", .{ .follow_symlinks = false }); defer symlink.close(io); const stat = try symlink.stat(io); - try testing.expectEqual(File.Kind.sym_link, stat.kind); + try expectEqual(File.Kind.sym_link, stat.kind); } }.impl); } @@ -417,7 +360,7 @@ test "openDirAbsolute" { defer dir.close(io); const ino = (try dir.stat(io)).inode; - try testing.expectEqual(tmp_ino, ino); + try expectEqual(tmp_ino, ino); } { @@ -429,7 +372,7 @@ test "openDirAbsolute" { defer dir.close(io); const ino = (try dir.stat(io)).inode; - try testing.expectEqual(sub_ino, ino); + try expectEqual(sub_ino, ino); } { @@ -441,7 +384,7 @@ test "openDirAbsolute" { defer dir.close(io); const ino = (try dir.stat(io)).inode; - try testing.expectEqual(tmp_ino, ino); + try expectEqual(tmp_ino, ino); } } @@ -480,7 +423,7 @@ test "openDir non-cwd parent '..'" { const actual_path = try dir.realpathAlloc(testing.allocator, "."); defer testing.allocator.free(actual_path); - try testing.expectEqualStrings(expected_path, actual_path); + try expectEqualStrings(expected_path, actual_path); } test "readLinkAbsolute" { @@ -548,7 +491,7 @@ test "Dir.Iterator" { try entries.append(Dir.Entry{ .name = name, .kind = entry.kind }); } - try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' + try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); } @@ -619,7 +562,7 @@ test "Dir.Iterator twice" { try entries.append(Dir.Entry{ .name = name, .kind = entry.kind }); } - try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' + try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); } @@ -655,7 +598,7 @@ test "Dir.Iterator reset" { try entries.append(.{ .name = name, .kind = entry.kind }); } - try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' + try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); @@ -690,7 +633,7 @@ test "Dir.Iterator but dir is deleted during iteration" { // On Linux, we can opt-in to receiving a more specific error by calling `nextLinux` if (native_os == .linux) { - try std.testing.expectError(error.DirNotFound, iterator.nextLinux()); + try expectError(error.DirNotFound, iterator.nextLinux()); } } @@ -717,10 +660,10 @@ test "Dir.realpath smoke test" { var buf: [fs.max_path_bytes]u8 = undefined; // FileNotFound if the path doesn't exist - try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_file_path)); - try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf)); - try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_dir_path)); - try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf)); + try expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_file_path)); + try expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf)); + try expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_dir_path)); + try expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf)); // Now create the file and dir try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); @@ -740,19 +683,19 @@ test "Dir.realpath smoke test" { // First, test non-alloc version { const file_path = try ctx.dir.realpath(test_file_path, &buf); - try testing.expectEqualStrings(expected_file_path, file_path); + try expectEqualStrings(expected_file_path, file_path); const dir_path = try ctx.dir.realpath(test_dir_path, &buf); - try testing.expectEqualStrings(expected_dir_path, dir_path); + try expectEqualStrings(expected_dir_path, dir_path); } // Next, test alloc version { const file_path = try ctx.dir.realpathAlloc(allocator, test_file_path); - try testing.expectEqualStrings(expected_file_path, file_path); + try expectEqualStrings(expected_file_path, file_path); const dir_path = try ctx.dir.realpathAlloc(allocator, test_dir_path); - try testing.expectEqualStrings(expected_dir_path, dir_path); + try expectEqualStrings(expected_dir_path, dir_path); } } }.impl); @@ -769,7 +712,7 @@ test "readFileAlloc" { const buf1 = try tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(1024)); defer testing.allocator.free(buf1); - try testing.expectEqualStrings("", buf1); + try expectEqualStrings("", buf1); const write_buf: []const u8 = "this is a test.\nthis is a test.\nthis is a test.\nthis is a test.\n"; try file.writeAll(write_buf); @@ -778,12 +721,12 @@ test "readFileAlloc" { // max_bytes > file_size const buf2 = try tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(1024)); defer testing.allocator.free(buf2); - try testing.expectEqualStrings(write_buf, buf2); + try expectEqualStrings(write_buf, buf2); } { // max_bytes == file_size - try testing.expectError( + try expectError( error.StreamTooLong, tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(write_buf.len)), ); @@ -793,11 +736,11 @@ test "readFileAlloc" { // max_bytes == file_size + 1 const buf2 = try tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(write_buf.len + 1)); defer testing.allocator.free(buf2); - try testing.expectEqualStrings(write_buf, buf2); + try expectEqualStrings(write_buf, buf2); } // max_bytes < file_size - try testing.expectError( + try expectError( error.StreamTooLong, tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(write_buf.len - 1)), ); @@ -809,12 +752,12 @@ test "Dir.statFile" { const io = ctx.io; const test_file_name = try ctx.transformPath("test_file"); - try testing.expectError(error.FileNotFound, ctx.dir.statFile(io, test_file_name, .{})); + try expectError(error.FileNotFound, ctx.dir.statFile(io, test_file_name, .{})); try ctx.dir.writeFile(io, .{ .sub_path = test_file_name, .data = "" }); const stat = try ctx.dir.statFile(io, test_file_name, .{}); - try testing.expectEqual(File.Kind.file, stat.kind); + try expectEqual(File.Kind.file, stat.kind); } }.impl); } @@ -828,7 +771,7 @@ test "statFile on dangling symlink" { try setupSymlink(io, ctx.dir, symlink_target, symlink_name, .{}); - try std.testing.expectError(error.FileNotFound, ctx.dir.statFile(io, symlink_name, .{})); + try expectError(error.FileNotFound, ctx.dir.statFile(io, symlink_name, .{})); } }.impl); } @@ -843,19 +786,19 @@ test "directory operations on files" { var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true }); file.close(io); - try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(io, test_file_name, .default_dir)); - try testing.expectError(error.NotDir, ctx.dir.openDir(io, test_file_name, .{})); - try testing.expectError(error.NotDir, ctx.dir.deleteDir(io, test_file_name)); + try expectError(error.PathAlreadyExists, ctx.dir.makeDir(io, test_file_name, .default_dir)); + try expectError(error.NotDir, ctx.dir.openDir(io, test_file_name, .{})); + try expectError(error.NotDir, ctx.dir.deleteDir(io, test_file_name)); if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { - try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name)); - try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name)); + try expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name)); + try expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name)); } // ensure the file still exists and is a file as a sanity check file = try ctx.dir.openFile(io, test_file_name, .{}); const stat = try file.stat(io); - try testing.expectEqual(File.Kind.file, stat.kind); + try expectEqual(File.Kind.file, stat.kind); file.close(io); } }.impl); @@ -873,8 +816,8 @@ test "file operations on directories" { try ctx.dir.makeDir(io, test_dir_name, .default_dir); - try testing.expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{})); - try testing.expectError(error.IsDir, ctx.dir.deleteFile(io, test_dir_name)); + try expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{})); + try expectError(error.IsDir, ctx.dir.deleteFile(io, test_dir_name)); switch (native_os) { .dragonfly, .netbsd => { // no error when reading a directory. See https://github.com/ziglang/zig/issues/5732 @@ -884,10 +827,10 @@ test "file operations on directories" { .wasi => { // WASI return EBADF, which gets mapped to NotOpenForReading. // See https://github.com/bytecodealliance/wasmtime/issues/1935 - try testing.expectError(error.NotOpenForReading, ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited)); + try expectError(error.NotOpenForReading, ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited)); }, else => { - try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited)); + try expectError(error.IsDir, ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited)); }, } @@ -898,12 +841,12 @@ test "file operations on directories" { } else { // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms. // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732 - try testing.expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .mode = .read_write })); + try expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .mode = .read_write })); } if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { - try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{})); - try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name)); + try expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{})); + try expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name)); } // ensure the directory still exists as a sanity check @@ -935,12 +878,12 @@ test "deleteDir" { const test_file_path = try ctx.transformPath("test_dir" ++ fs.path.sep_str ++ "test_file"); // deleting a non-existent directory - try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(io, test_dir_path)); + try expectError(error.FileNotFound, ctx.dir.deleteDir(io, test_dir_path)); // deleting a non-empty directory try ctx.dir.makeDir(io, test_dir_path, .default_dir); try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); - try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(io, test_dir_path)); + try expectError(error.DirNotEmpty, ctx.dir.deleteDir(io, test_dir_path)); // deleting an empty directory try ctx.dir.deleteFile(io, test_file_path); @@ -962,7 +905,7 @@ test "Dir.rename files" { const missing_file_path = try ctx.transformPath("missing_file_name"); const something_else_path = try ctx.transformPath("something_else"); - try testing.expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, ctx.dir, something_else_path, io)); + try expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, ctx.dir, something_else_path, io)); // Renaming files const test_file_name = try ctx.transformPath("test_file"); @@ -972,7 +915,7 @@ test "Dir.rename files" { try ctx.dir.rename(test_file_name, ctx.dir, renamed_test_file_name, io); // Ensure the file was renamed - try testing.expectError(error.FileNotFound, ctx.dir.openFile(io, test_file_name, .{})); + try expectError(error.FileNotFound, ctx.dir.openFile(io, test_file_name, .{})); file = try ctx.dir.openFile(io, renamed_test_file_name, .{}); file.close(io); @@ -985,7 +928,7 @@ test "Dir.rename files" { existing_file.close(io); try ctx.dir.rename(renamed_test_file_name, ctx.dir, existing_file_path, io); - try testing.expectError(error.FileNotFound, ctx.dir.openFile(io, renamed_test_file_name, .{})); + try expectError(error.FileNotFound, ctx.dir.openFile(io, renamed_test_file_name, .{})); file = try ctx.dir.openFile(io, existing_file_path, .{}); file.close(io); } @@ -1011,7 +954,7 @@ test "Dir.rename directories" { try ctx.dir.rename(test_dir_path, ctx.dir, test_dir_renamed_path, io); // Ensure the directory was renamed - try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{})); + try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{})); var dir = try ctx.dir.openDir(io, test_dir_renamed_path, .{}); // Put a file in the directory @@ -1023,7 +966,7 @@ test "Dir.rename directories" { try ctx.dir.rename(test_dir_renamed_path, ctx.dir, test_dir_renamed_again_path, io); // Ensure the directory was renamed and the file still exists in it - try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_renamed_path, .{})); + try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_renamed_path, .{})); dir = try ctx.dir.openDir(io, test_dir_renamed_again_path, .{}); file = try dir.openFile(io, "test_file", .{}); file.close(io); @@ -1048,7 +991,7 @@ test "Dir.rename directory onto empty dir" { try ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io); // Ensure the directory was renamed - try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{})); + try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{})); var dir = try ctx.dir.openDir(io, target_dir_path, .{}); dir.close(io); } @@ -1073,7 +1016,7 @@ test "Dir.rename directory onto non-empty dir" { target_dir.close(io); // Rename should fail with PathAlreadyExists if target_dir is non-empty - try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io)); + try expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io)); // Ensure the directory was not renamed var dir = try ctx.dir.openDir(io, test_dir_path, .{}); @@ -1095,8 +1038,8 @@ test "Dir.rename file <-> dir" { var file = try ctx.dir.createFile(io, test_file_path, .{ .read = true }); file.close(io); try ctx.dir.makeDir(io, test_dir_path, .default_dir); - try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, ctx.dir, test_dir_path, io)); - try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, ctx.dir, test_file_path, io)); + try expectError(error.IsDir, ctx.dir.rename(test_file_path, ctx.dir, test_dir_path, io)); + try expectError(error.NotDir, ctx.dir.rename(test_dir_path, ctx.dir, test_file_path, io)); } }.impl); } @@ -1118,7 +1061,7 @@ test "rename" { try Dir.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name, io); // ensure the file was renamed - try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(io, test_file_name, .{})); + try expectError(error.FileNotFound, tmp_dir1.dir.openFile(io, test_file_name, .{})); file = try tmp_dir2.dir.openFile(io, renamed_test_file_name, .{}); file.close(io); } @@ -1139,7 +1082,7 @@ test "renameAbsolute" { const base_path = try tmp_dir.dir.realpathAlloc(allocator, "."); - try testing.expectError(error.FileNotFound, fs.renameAbsolute( + try expectError(error.FileNotFound, fs.renameAbsolute( try fs.path.join(allocator, &.{ base_path, "missing_file_name" }), try fs.path.join(allocator, &.{ base_path, "something_else" }), )); @@ -1155,10 +1098,10 @@ test "renameAbsolute" { ); // ensure the file was renamed - try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(io, test_file_name, .{})); + try expectError(error.FileNotFound, tmp_dir.dir.openFile(io, test_file_name, .{})); file = try tmp_dir.dir.openFile(io, renamed_test_file_name, .{}); const stat = try file.stat(io); - try testing.expectEqual(File.Kind.file, stat.kind); + try expectEqual(File.Kind.file, stat.kind); file.close(io); // Renaming directories @@ -1171,7 +1114,7 @@ test "renameAbsolute" { ); // ensure the directory was renamed - try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(io, test_dir_name, .{})); + try expectError(error.FileNotFound, tmp_dir.dir.openDir(io, test_dir_name, .{})); var dir = try tmp_dir.dir.openDir(io, renamed_test_dir_name, .{}); dir.close(io); } @@ -1193,7 +1136,7 @@ test "executablePath" { const buf_self_exe_path = try std.process.executablePath(io, &buf); const alloc_self_exe_path = try std.process.executablePathAlloc(io, testing.allocator); defer testing.allocator.free(alloc_self_exe_path); - try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path); + try expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path); } test "deleteTree does not follow symlinks" { @@ -1212,7 +1155,7 @@ test "deleteTree does not follow symlinks" { try tmp.dir.deleteTree(io, "a"); - try testing.expectError(error.FileNotFound, tmp.dir.access(io, "a", .{})); + try expectError(error.FileNotFound, tmp.dir.access(io, "a", .{})); try tmp.dir.access(io, "b", .{}); } @@ -1227,7 +1170,7 @@ test "deleteTree on a symlink" { try setupSymlink(io, tmp.dir, "file", "filelink", .{}); try tmp.dir.deleteTree(io, "filelink"); - try testing.expectError(error.FileNotFound, tmp.dir.access(io, "filelink", .{})); + try expectError(error.FileNotFound, tmp.dir.access(io, "filelink", .{})); try tmp.dir.access(io, "file", .{}); // Symlink to a directory @@ -1235,7 +1178,7 @@ test "deleteTree on a symlink" { try setupSymlink(io, tmp.dir, "dir", "dirlink", .{ .is_directory = true }); try tmp.dir.deleteTree(io, "dirlink"); - try testing.expectError(error.FileNotFound, tmp.dir.access(io, "dirlink", .{})); + try expectError(error.FileNotFound, tmp.dir.access(io, "dirlink", .{})); try tmp.dir.access(io, "dir", .{}); } @@ -1257,7 +1200,7 @@ test "makePath, put some files in it, deleteTree" { }); try ctx.dir.deleteTree(io, dir_path); - try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{})); + try expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{})); } }.impl); } @@ -1280,7 +1223,7 @@ test "makePath, put some files in it, deleteTreeMinStackSize" { }); try ctx.dir.deleteTreeMinStackSize(dir_path); - try testing.expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{})); + try expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{})); } }.impl); } @@ -1294,7 +1237,7 @@ test "makePath in a directory that no longer exists" { defer tmp.cleanup(); try tmp.parent_dir.deleteTree(io, &tmp.sub_path); - try testing.expectError(error.FileNotFound, tmp.dir.makePath(io, "sub-path")); + try expectError(error.FileNotFound, tmp.dir.makePath(io, "sub-path")); } test "makePath but sub_path contains pre-existing file" { @@ -1306,7 +1249,7 @@ test "makePath but sub_path contains pre-existing file" { try tmp.dir.makeDir(io, "foo", .default_dir); try tmp.dir.writeFile(io, .{ .sub_path = "foo/bar", .data = "" }); - try testing.expectError(error.NotDir, tmp.dir.makePath(io, "foo/bar/baz")); + try expectError(error.NotDir, tmp.dir.makePath(io, "foo/bar/baz")); } fn expectDir(io: Io, dir: Dir, path: []const u8) !void { @@ -1364,8 +1307,8 @@ test "makepath relative walks" { // On Windows, .. is resolved before passing the path to NtCreateFile, // meaning everything except `first/C` drops out. try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "C"); - try testing.expectError(error.FileNotFound, tmp.dir.access(io, "second", .{})); - try testing.expectError(error.FileNotFound, tmp.dir.access(io, "third", .{})); + try expectError(error.FileNotFound, tmp.dir.access(io, "second", .{})); + try expectError(error.FileNotFound, tmp.dir.access(io, "third", .{})); }, else => { try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "A"); @@ -1413,10 +1356,10 @@ fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8) !vo var count: usize = 0; while (try walker.next()) |entry| { - try testing.expectEqualStrings(maxed_filename, entry.basename); + try expectEqualStrings(maxed_filename, entry.basename); count += 1; } - try testing.expectEqual(@as(usize, 2), count); + try expectEqual(@as(usize, 2), count); } // ensure that we can delete the tree @@ -1467,14 +1410,14 @@ test "writev, readv" { try writer.interface.writeVecAll(&write_vecs); try writer.interface.flush(); - try testing.expectEqual(@as(u64, line1.len + line2.len), try src_file.length(io)); + try expectEqual(@as(u64, line1.len + line2.len), try src_file.length(io)); var reader = writer.moveToReader(io); try reader.seekTo(0); try reader.interface.readVecAll(&read_vecs); - try testing.expectEqualStrings(&buf1, "line2\n"); - try testing.expectEqualStrings(&buf2, "line1\n"); - try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1)); + try expectEqualStrings(&buf1, "line2\n"); + try expectEqualStrings(&buf2, "line1\n"); + try expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1)); } test "pwritev, preadv" { @@ -1498,14 +1441,14 @@ test "pwritev, preadv" { try writer.seekTo(16); try writer.interface.writeVecAll(&lines); try writer.interface.flush(); - try testing.expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.length(io)); + try expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.length(io)); var reader = writer.moveToReader(io); try reader.seekTo(16); try reader.interface.readVecAll(&read_vecs); - try testing.expectEqualStrings(&buf1, "line2\n"); - try testing.expectEqualStrings(&buf2, "line1\n"); - try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1)); + try expectEqualStrings(&buf1, "line2\n"); + try expectEqualStrings(&buf2, "line1\n"); + try expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1)); } test "setEndPos" { @@ -1529,34 +1472,34 @@ test "setEndPos" { { try f.setEndPos(initial_size); - try testing.expectEqual(initial_size, try f.length(io)); + try expectEqual(initial_size, try f.length(io)); try reader.seekTo(0); - try testing.expectEqual(initial_size, try reader.interface.readSliceShort(&buffer)); - try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]); + try expectEqual(initial_size, try reader.interface.readSliceShort(&buffer)); + try expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]); } { const larger = initial_size + 4; try f.setEndPos(larger); - try testing.expectEqual(larger, try f.length(io)); + try expectEqual(larger, try f.length(io)); try reader.seekTo(0); - try testing.expectEqual(larger, try reader.interface.readSliceShort(&buffer)); - try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]); + try expectEqual(larger, try reader.interface.readSliceShort(&buffer)); + try expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]); } { const smaller = initial_size - 5; try f.setEndPos(smaller); - try testing.expectEqual(smaller, try f.length(io)); + try expectEqual(smaller, try f.length(io)); try reader.seekTo(0); - try testing.expectEqual(smaller, try reader.interface.readSliceShort(&buffer)); - try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]); + try expectEqual(smaller, try reader.interface.readSliceShort(&buffer)); + try expectEqualStrings("nine", buffer[0..@intCast(smaller)]); } try f.setEndPos(0); - try testing.expectEqual(0, try f.length(io)); + try expectEqual(0, try f.length(io)); try reader.seekTo(0); - try testing.expectEqual(0, try reader.interface.readSliceShort(&buffer)); + try expectEqual(0, try reader.interface.readSliceShort(&buffer)); } test "access file" { @@ -1567,7 +1510,7 @@ test "access file" { const file_path = try ctx.transformPath("os_test_tmp" ++ fs.path.sep_str ++ "file.txt"); try ctx.dir.makePath(io, dir_path); - try testing.expectError(error.FileNotFound, ctx.dir.access(io, file_path, .{})); + try expectError(error.FileNotFound, ctx.dir.access(io, file_path, .{})); try ctx.dir.writeFile(io, .{ .sub_path = file_path, .data = "" }); try ctx.dir.access(io, file_path, .{}); @@ -1614,13 +1557,13 @@ test "sendfile" { var file_writer = dest_file.writer(io, &fallback_buffer); try file_writer.interface.writeVecAll(&headers); try file_reader.seekTo(1); - try testing.expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10))); + try expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10))); try file_writer.interface.writeVecAll(&trailers); try file_writer.interface.flush(); var fr = file_writer.moveToReader(io); try fr.seekTo(0); const amt = try fr.interface.readSliceShort(&written_buf); - try testing.expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]); + try expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]); } test "sendfile with buffered data" { @@ -1651,15 +1594,15 @@ test "sendfile with buffered data" { var fallback_buffer: [32]u8 = undefined; var file_writer = dest_file.writer(io, &fallback_buffer); - try std.testing.expectEqual(4, try file_writer.interface.sendFileAll(&file_reader, .limited(4))); + try expectEqual(4, try file_writer.interface.sendFileAll(&file_reader, .limited(4))); var written_buf: [8]u8 = undefined; var fr = file_writer.moveToReader(io); try fr.seekTo(0); const amt = try fr.interface.readSliceShort(&written_buf); - try std.testing.expectEqual(4, amt); - try std.testing.expectEqualSlices(u8, "AAAA", written_buf[0..amt]); + try expectEqual(4, amt); + try expectEqualSlices(u8, "AAAA", written_buf[0..amt]); } test "copyFile" { @@ -1690,7 +1633,7 @@ fn expectFileContents(io: Io, dir: Dir, file_path: []const u8, data: []const u8) const contents = try dir.readFileAlloc(io, file_path, testing.allocator, .limited(1000)); defer testing.allocator.free(contents); - try testing.expectEqualSlices(u8, data, contents); + try expectEqualSlices(u8, data, contents); } test "AtomicFile" { @@ -1712,7 +1655,7 @@ test "AtomicFile" { try af.finish(); } const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999)); - try testing.expectEqualStrings(test_content, content); + try expectEqualStrings(test_content, content); try ctx.dir.deleteFile(io, test_out_file); } @@ -1731,7 +1674,7 @@ test "open file with exclusive nonblocking lock twice" { defer file1.close(io); const file2 = ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true }); - try testing.expectError(error.WouldBlock, file2); + try expectError(error.WouldBlock, file2); } }.impl); } @@ -1748,7 +1691,7 @@ test "open file with shared and exclusive nonblocking lock" { defer file1.close(io); const file2 = ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true }); - try testing.expectError(error.WouldBlock, file2); + try expectError(error.WouldBlock, file2); } }.impl); } @@ -1765,7 +1708,7 @@ test "open file with exclusive and shared nonblocking lock" { defer file1.close(io); const file2 = ctx.dir.createFile(io, filename, .{ .lock = .shared, .lock_nonblocking = true }); - try testing.expectError(error.WouldBlock, file2); + try expectError(error.WouldBlock, file2); } }.impl); } @@ -1805,7 +1748,7 @@ test "open file with exclusive lock twice, make sure second lock waits" { // Wait for the spawned thread to start trying to acquire the exclusive file lock. // Then wait a bit to make sure that can't acquire it since we currently hold the file lock. started.wait(); - try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms)); + try expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms)); // Release the file lock which should unlock the thread to lock it and set the locked event. file.close(io); @@ -1846,7 +1789,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { .lock_nonblocking = true, }); file1.close(io); - try testing.expectError(error.WouldBlock, file2); + try expectError(error.WouldBlock, file2); } test "read from locked file" { @@ -1871,9 +1814,9 @@ test "read from locked file" { defer f2.close(io); var buffer: [1]u8 = undefined; if (builtin.os.tag == .windows) { - try std.testing.expectError(error.LockViolation, f2.read(&buffer)); + try expectError(error.LockViolation, f2.read(&buffer)); } else { - try std.testing.expectEqual(0, f2.read(&buffer)); + try expectEqual(0, f2.read(&buffer)); } } } @@ -1925,7 +1868,7 @@ test "walker" { std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); return err; }; - testing.expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| { + expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| { std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); return err; }; @@ -1934,7 +1877,7 @@ test "walker" { defer entry_dir.close(io); num_walked += 1; } - try testing.expectEqual(expected_paths.kvs.len, num_walked); + try expectEqual(expected_paths.kvs.len, num_walked); } test "selective walker, skip entries that start with ." { @@ -1992,7 +1935,7 @@ test "selective walker, skip entries that start with ." { std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); return err; }; - testing.expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| { + expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| { std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); return err; }; @@ -2002,7 +1945,7 @@ test "selective walker, skip entries that start with ." { defer entry_dir.close(io); num_walked += 1; } - try testing.expectEqual(expected_paths.kvs.len, num_walked); + try expectEqual(expected_paths.kvs.len, num_walked); } test "walker without fully iterating" { @@ -2025,7 +1968,7 @@ test "walker without fully iterating" { num_walked += 1; break; } - try testing.expectEqual(@as(usize, 1), num_walked); + try expectEqual(@as(usize, 1), num_walked); } test "'.' and '..' in Io.Dir functions" { @@ -2061,7 +2004,7 @@ test "'.' and '..' in Io.Dir functions" { try ctx.dir.writeFile(io, .{ .sub_path = update_path, .data = "something" }); var dir = ctx.dir; const prev_status = try dir.updateFile(io, file_path, dir, update_path, .{}); - try testing.expectEqual(Io.Dir.PrevStatus.stale, prev_status); + try expectEqual(Io.Dir.PrevStatus.stale, prev_status); try ctx.dir.deleteDir(io, subdir_path); } @@ -2106,8 +2049,7 @@ test "'.' and '..' in absolute functions" { } test "chmod" { - if (native_os == .windows or native_os == .wasi) - return error.SkipZigTest; + if (native_os == .windows or native_os == .wasi) return; const io = testing.io; @@ -2116,17 +2058,17 @@ test "chmod" { const file = try tmp.dir.createFile(io, "test_file", .{ .permissions = .fromMode(0o600) }); defer file.close(io); - try testing.expectEqual(@as(posix.mode_t, 0o600), (try file.stat(io)).permissions.toMode() & 0o7777); + try expectEqual(0o600, (try file.stat(io)).permissions.toMode() & 0o7777); try file.setPermissions(io, .fromMode(0o644)); - try testing.expectEqual(@as(posix.mode_t, 0o644), (try file.stat(io)).permissions.toMode() & 0o7777); + try expectEqual(0o644, (try file.stat(io)).permissions.toMode() & 0o7777); try tmp.dir.makeDir(io, "test_dir", .default_dir); var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); defer dir.close(io); try dir.setPermissions(io, .fromMode(0o700)); - try testing.expectEqual(@as(posix.mode_t, 0o700), (try dir.stat(io)).permissions.toMode() & 0o7777); + try expectEqual(0o700, (try dir.stat(io)).permissions.toMode() & 0o7777); } test "chown" { @@ -2162,70 +2104,70 @@ test "invalid UTF-8/WTF-8 paths" { // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte const invalid_path = try ctx.transformPath("\xFF"); - try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{})); + try expectError(expected_err, ctx.dir.openFile(invalid_path, .{})); - try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{})); + try expectError(expected_err, ctx.dir.createFile(invalid_path, .{})); - try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path, .default_dir)); + try expectError(expected_err, ctx.dir.makeDir(invalid_path, .default_dir)); - try testing.expectError(expected_err, ctx.dir.makePath(invalid_path)); - try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{})); + try expectError(expected_err, ctx.dir.makePath(invalid_path)); + try expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{})); - try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{})); + try expectError(expected_err, ctx.dir.openDir(invalid_path, .{})); - try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path)); + try expectError(expected_err, ctx.dir.deleteFile(invalid_path)); - try testing.expectError(expected_err, ctx.dir.deleteDir(io, invalid_path)); + try expectError(expected_err, ctx.dir.deleteDir(io, invalid_path)); - try testing.expectError(expected_err, ctx.dir.rename(invalid_path, ctx.dir, invalid_path, io)); + try expectError(expected_err, ctx.dir.rename(invalid_path, ctx.dir, invalid_path, io)); - try testing.expectError(expected_err, ctx.dir.symLink(io, invalid_path, invalid_path, .{})); + try expectError(expected_err, ctx.dir.symLink(io, invalid_path, invalid_path, .{})); if (native_os == .wasi) { - try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{})); + try expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{})); } - try testing.expectError(expected_err, ctx.dir.readLink(io, invalid_path, &[_]u8{})); + try expectError(expected_err, ctx.dir.readLink(io, invalid_path, &[_]u8{})); if (native_os == .wasi) { - try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{})); + try expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{})); } - try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{})); - try testing.expectError(expected_err, ctx.dir.readFileAlloc(invalid_path, testing.allocator, .limited(0))); + try expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{})); + try expectError(expected_err, ctx.dir.readFileAlloc(invalid_path, testing.allocator, .limited(0))); - try testing.expectError(expected_err, ctx.dir.deleteTree(io, invalid_path)); - try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path)); + try expectError(expected_err, ctx.dir.deleteTree(io, invalid_path)); + try expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path)); - try testing.expectError(expected_err, ctx.dir.writeFile(io, .{ .sub_path = invalid_path, .data = "" })); + try expectError(expected_err, ctx.dir.writeFile(io, .{ .sub_path = invalid_path, .data = "" })); - try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{})); + try expectError(expected_err, ctx.dir.access(invalid_path, .{})); var dir = ctx.dir; - try testing.expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{})); - try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{})); + try expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{})); + try expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{})); - try testing.expectError(expected_err, ctx.dir.statFile(invalid_path)); + try expectError(expected_err, ctx.dir.statFile(invalid_path)); if (native_os != .wasi) { - try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{})); - try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path)); + try expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{})); + try expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path)); } - try testing.expectError(expected_err, Dir.rename(ctx.dir, invalid_path, ctx.dir, invalid_path, io)); + try expectError(expected_err, Dir.rename(ctx.dir, invalid_path, ctx.dir, invalid_path, io)); if (native_os != .wasi and ctx.path_type != .relative) { - try testing.expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, .{})); - try testing.expectError(expected_err, Dir.makeDirAbsolute(invalid_path)); - try testing.expectError(expected_err, Dir.deleteDirAbsolute(invalid_path)); - try testing.expectError(expected_err, Dir.renameAbsolute(invalid_path, invalid_path)); - try testing.expectError(expected_err, Dir.openDirAbsolute(invalid_path, .{})); - try testing.expectError(expected_err, Dir.openFileAbsolute(invalid_path, .{})); - try testing.expectError(expected_err, Dir.accessAbsolute(invalid_path, .{})); - try testing.expectError(expected_err, Dir.createFileAbsolute(invalid_path, .{})); - try testing.expectError(expected_err, Dir.deleteFileAbsolute(invalid_path)); + try expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, .{})); + try expectError(expected_err, Dir.makeDirAbsolute(invalid_path)); + try expectError(expected_err, Dir.deleteDirAbsolute(invalid_path)); + try expectError(expected_err, Dir.renameAbsolute(invalid_path, invalid_path)); + try expectError(expected_err, Dir.openDirAbsolute(invalid_path, .{})); + try expectError(expected_err, Dir.openFileAbsolute(invalid_path, .{})); + try expectError(expected_err, Dir.accessAbsolute(invalid_path, .{})); + try expectError(expected_err, Dir.createFileAbsolute(invalid_path, .{})); + try expectError(expected_err, Dir.deleteFileAbsolute(invalid_path)); var readlink_buf: [Dir.max_path_bytes]u8 = undefined; - try testing.expectError(expected_err, Dir.readLinkAbsolute(invalid_path, &readlink_buf)); - try testing.expectError(expected_err, Dir.symLinkAbsolute(invalid_path, invalid_path, .{})); - try testing.expectError(expected_err, Dir.realpathAlloc(testing.allocator, invalid_path)); + try expectError(expected_err, Dir.readLinkAbsolute(invalid_path, &readlink_buf)); + try expectError(expected_err, Dir.symLinkAbsolute(invalid_path, invalid_path, .{})); + try expectError(expected_err, Dir.realpathAlloc(testing.allocator, invalid_path)); } } }.impl); @@ -2259,8 +2201,8 @@ test "read file non vectored" { else => |e| return e, }; } - try testing.expectEqualStrings(contents, w.buffered()); - try testing.expectEqual(contents.len, i); + try expectEqualStrings(contents, w.buffered()); + try expectEqual(contents.len, i); } test "seek keeping partial buffer" { @@ -2282,7 +2224,7 @@ test "seek keeping partial buffer" { var read_buffer: [3]u8 = undefined; var file_reader: Io.File.Reader = .init(file, io, &read_buffer); - try testing.expectEqual(0, file_reader.logicalPos()); + try expectEqual(0, file_reader.logicalPos()); var buf: [4]u8 = undefined; try file_reader.interface.readSliceAll(&buf); @@ -2292,18 +2234,18 @@ test "seek keeping partial buffer" { return; } - try testing.expectEqual(4, file_reader.logicalPos()); - try testing.expectEqual(7, file_reader.pos); + try expectEqual(4, file_reader.logicalPos()); + try expectEqual(7, file_reader.pos); try file_reader.seekTo(6); - try testing.expectEqual(6, file_reader.logicalPos()); - try testing.expectEqual(7, file_reader.pos); + try expectEqual(6, file_reader.logicalPos()); + try expectEqual(7, file_reader.pos); - try testing.expectEqualStrings("0123", &buf); + try expectEqualStrings("0123", &buf); const n = try file_reader.interface.readSliceShort(&buf); - try testing.expectEqual(4, n); + try expectEqual(4, n); - try testing.expectEqualStrings("6789", &buf); + try expectEqualStrings("6789", &buf); } test "seekBy" { @@ -2320,8 +2262,8 @@ test "seekBy" { var buffer: [20]u8 = undefined; const n = try reader.interface.readSliceShort(&buffer); - try testing.expectEqual(15, n); - try testing.expectEqualStrings("t's test seekBy", buffer[0..15]); + try expectEqual(15, n); + try expectEqualStrings("t's test seekBy", buffer[0..15]); } test "seekTo flushes buffered data" { @@ -2348,7 +2290,7 @@ test "seekTo flushes buffered data" { var buf: [4]u8 = undefined; try file_reader.interface.readSliceAll(&buf); - try std.testing.expectEqualStrings(contents, &buf); + try expectEqualStrings(contents, &buf); } test "File.Writer sendfile with buffered contents" { @@ -2372,7 +2314,7 @@ test "File.Writer sendfile with buffered contents" { var out_buf: [1]u8 = undefined; var out_w = out.writerStreaming(&out_buf); try out_w.interface.writeByte('a'); - try testing.expectEqual(3, try out_w.interface.sendFileAll(&in_r, .unlimited)); + try expectEqual(3, try out_w.interface.sendFileAll(&in_r, .unlimited)); try out_w.interface.flush(); } @@ -2380,8 +2322,8 @@ test "File.Writer sendfile with buffered contents" { defer check.close(io); var check_buf: [4]u8 = undefined; var check_r = check.reader(io, &check_buf); - try testing.expectEqualStrings("abcd", try check_r.interface.take(4)); - try testing.expectError(error.EndOfStream, check_r.interface.takeByte()); + try expectEqualStrings("abcd", try check_r.interface.take(4)); + try expectError(error.EndOfStream, check_r.interface.takeByte()); } test "readlink on Windows" { @@ -2410,23 +2352,72 @@ test "readlinkat" { try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = "nonsense" }); // create a symbolic link - if (native_os == .windows) { - std.os.windows.CreateSymbolicLink( - tmp.dir.handle, - &[_]u16{ 'l', 'i', 'n', 'k' }, - &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, - false, - ) catch |err| switch (err) { + tmp.dir.symLink("file.txt", "link", .{}) catch |err| switch (err) { + error.AccessDenied => { // Symlink requires admin privileges on windows, so this test can legitimately fail. - error.AccessDenied => return error.SkipZigTest, - else => return err, - }; - } else { - try posix.symlinkat("file.txt", tmp.dir.handle, "link"); - } + if (native_os == .windows) return error.SkipZigTest; + }, + }; // read the link var buffer: [fs.max_path_bytes]u8 = undefined; const read_link = try tmp.dir.readLink(io, "link", &buffer); - try expect(mem.eql(u8, "file.txt", read_link)); + try expectEqualStrings("file.txt", read_link); +} + +test "fchmodat smoke test" { + if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; + + if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) { + // https://github.com/ziglang/zig/issues/23808 + return error.SkipZigTest; + } + + const io = testing.io; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + try expectError(error.FileNotFound, tmp.dir.setPermissions(io, "regfile", 0o666, .{})); + const file = try tmp.dir.createFile(io, "regfile", .{ + .exclusive = true, + .permissions = .fromMode(0o644), + }); + file.close(io); + + if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and + builtin.os.tag == .linux and !builtin.link_libc) + { + return error.SkipZigTest; // No `fstatat()`. + } + + try tmp.dir.symLink(io, "regfile", "symlink", .{}); + const sym_mode = blk: { + const st = try tmp.dir.statFile(io, "symlink", .{ .follow_symlinks = false }); + break :blk st.permissions.toMode() & 0b111_111_111; + }; + + try tmp.dir.setFilePermissions(io, "regfile", .fromMode(0o640), .{}); + try expectMode(io, tmp.dir, "regfile", .fromMode(0o640)); + try tmp.dir.setFilePermissions(io, "regfile", .fromMode(0o600), .{ .follow_symlinks = false }); + try expectMode(io, tmp.dir, "regfile", .fromMode(0o600)); + + try tmp.dir.setFilePermissions(io, "symlink", .fromMode(0o640), .{}); + try expectMode(io, tmp.dir, "regfile", .fromMode(0o640)); + try expectMode(io, tmp.dir, "symlink", .fromMode(sym_mode)); + + var test_link = true; + tmp.dir.setFilePermissions(io, "symlink", .fromMode(0o600), .{ .follow_symlinks = false }) catch |err| switch (err) { + error.OperationNotSupported => test_link = false, + else => |e| return e, + }; + if (test_link) + try expectMode(tmp.dir.handle, "symlink", 0o600); + try expectMode(tmp.dir.handle, "regfile", 0o640); +} + +fn expectMode(io: Io, dir: Dir, file: []const u8, permissions: File.Permissions) !void { + const mode = permissions.toMode(); + const st = try dir.statFile(io, file, .{ .follow_symlinks = false }); + try expectEqual(mode, st.mode & 0b111_111_111); } diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 1764d7ad358e6a1b120039c7dba41620f92735b5..87361a503891f626a53a97ec07263365f768f60b 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -870,67 +870,6 @@ test "pwrite with empty buffer" { try expectEqual(rc, 0); } -fn getFileMode(dir: posix.fd_t, path: []const u8) !posix.mode_t { - const path_z = try posix.toPosixPath(path); - const mode: posix.mode_t = if (native_os == .linux) blk: { - const stx = try linux.wrapped.statx( - dir, - &path_z, - posix.AT.SYMLINK_NOFOLLOW, - .{ .MODE = true }, - ); - std.debug.assert(stx.mask.MODE); - break :blk stx.mode; - } else blk: { - const st = try posix.fstatatZ(dir, &path_z, posix.AT.SYMLINK_NOFOLLOW); - break :blk st.mode; - }; - - return mode & 0b111_111_111; -} - -fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void { - const actual = try getFileMode(dir, file); - try expectEqual(mode, actual & 0b111_111_111); -} - -test "fchmodat smoke test" { - if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - try expectError(error.FileNotFound, posix.fchmodat(tmp.dir.handle, "regfile", 0o666, 0)); - const fd = try posix.openat( - tmp.dir.handle, - "regfile", - .{ .ACCMODE = .WRONLY, .CREAT = true, .EXCL = true, .TRUNC = true }, - 0o644, - ); - posix.close(fd); - - try posix.symlinkat("regfile", tmp.dir.handle, "symlink"); - const sym_mode = try getFileMode(tmp.dir.handle, "symlink"); - - try posix.fchmodat(tmp.dir.handle, "regfile", 0o640, 0); - try expectMode(tmp.dir.handle, "regfile", 0o640); - try posix.fchmodat(tmp.dir.handle, "regfile", 0o600, posix.AT.SYMLINK_NOFOLLOW); - try expectMode(tmp.dir.handle, "regfile", 0o600); - - try posix.fchmodat(tmp.dir.handle, "symlink", 0o640, 0); - try expectMode(tmp.dir.handle, "regfile", 0o640); - try expectMode(tmp.dir.handle, "symlink", sym_mode); - - var test_link = true; - posix.fchmodat(tmp.dir.handle, "symlink", 0o600, posix.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) { - error.OperationNotSupported => test_link = false, - else => |e| return e, - }; - if (test_link) - try expectMode(tmp.dir.handle, "symlink", 0o600); - try expectMode(tmp.dir.handle, "regfile", 0o640); -} - const CommonOpenFlags = packed struct { ACCMODE: posix.ACCMODE = .RDONLY, CREAT: bool = false, -- 2.54.0 From 16f8af1b9a7a287ac6fdefec5949725c55cbe179 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 11 Dec 2025 23:18:42 -0800 Subject: [PATCH 049/195] compiler: update various code to new fs API --- lib/compiler/aro/main.zig | 2 +- lib/std/Build/Cache.zig | 8 +- lib/std/Build/Cache/Path.zig | 4 +- lib/std/Io/Dir.zig | 2 +- lib/std/crypto/Certificate/Bundle.zig | 3 +- lib/std/crypto/Certificate/Bundle/macos.zig | 2 +- lib/std/fs/test.zig | 14 +- lib/std/posix/test.zig | 2 +- lib/std/zip.zig | 4 +- src/Compilation.zig | 30 ++-- src/Package/Fetch.zig | 14 +- src/Package/Fetch/git.zig | 4 +- src/Zcu.zig | 2 +- src/crash_report.zig | 10 +- src/fmt.zig | 12 +- src/introspect.zig | 54 ++++--- src/libs/freebsd.zig | 4 +- src/libs/glibc.zig | 4 +- src/libs/mingw.zig | 4 +- src/libs/netbsd.zig | 4 +- src/link.zig | 15 +- src/link/Coff.zig | 4 +- src/link/Dwarf.zig | 1 - src/link/Elf.zig | 2 +- src/link/Elf2.zig | 4 +- src/link/MachO.zig | 35 +++-- src/link/MappedFile.zig | 7 +- src/link/Wasm.zig | 6 +- src/main.zig | 155 +++++++++----------- src/print_env.zig | 17 ++- src/print_targets.zig | 16 +- test/standalone/self_exe_symlink/main.zig | 2 +- 32 files changed, 228 insertions(+), 219 deletions(-) diff --git a/lib/compiler/aro/main.zig b/lib/compiler/aro/main.zig index d1be1dbb21ab01bc5d95ab5894f54b08485fcdf3..9e0440febfb6bf3c196dec11380c7f726e2a5812 100644 --- a/lib/compiler/aro/main.zig +++ b/lib/compiler/aro/main.zig @@ -43,7 +43,7 @@ pub fn main() u8 { return 1; }; - const aro_name = std.fs.selfExePathAlloc(gpa) catch { + const aro_name = process.executablePathAlloc(io, gpa) catch { std.debug.print("unable to find Aro executable path\n", .{}); if (fast_exit) process.exit(1); return 1; diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index b97efc767770eb742a9fac4efdcf48ae5304cfec..e2c848b6fdcb7b2862c9c9a7db1b603861b4a331 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -1330,7 +1330,7 @@ test "cache file and then recall it" { var cache: Cache = .{ .io = io, .gpa = testing.allocator, - .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.makeOpenPath(io, temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); @@ -1396,7 +1396,7 @@ test "check that changing a file makes cache fail" { var cache: Cache = .{ .io = io, .gpa = testing.allocator, - .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.makeOpenPath(io, temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); @@ -1456,7 +1456,7 @@ test "no file inputs" { var cache: Cache = .{ .io = io, .gpa = testing.allocator, - .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.makeOpenPath(io, temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); @@ -1515,7 +1515,7 @@ test "Manifest with files added after initial hash work" { var cache: Cache = .{ .io = io, .gpa = testing.allocator, - .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.makeOpenPath(io, temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); diff --git a/lib/std/Build/Cache/Path.zig b/lib/std/Build/Cache/Path.zig index 54104afa413b8874d1ed74adf2cb927ebfc22110..51f9e7aecd15b5527f005460d17acc86d62f6f20 100644 --- a/lib/std/Build/Cache/Path.zig +++ b/lib/std/Build/Cache/Path.zig @@ -84,14 +84,14 @@ pub fn openDir( return p.root_dir.handle.openDir(io, joined_path, args); } -pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io.Dir { +pub fn makeOpenPath(p: Path, io: Io, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io.Dir { var buf: [fs.max_path_bytes]u8 = undefined; const joined_path = if (p.sub_path.len == 0) sub_path else p: { break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ p.sub_path, sub_path, }) catch return error.NameTooLong; }; - return p.root_dir.handle.makeOpenPath(joined_path, opts); + return p.root_dir.handle.makeOpenPath(io, joined_path, opts); } pub fn statFile(p: Path, io: Io, sub_path: []const u8) !Io.Dir.Stat { diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index be82b8b8b26065098f94f96999d1ec177ce9935c..cd3018190098e18bbf5b49e3e5edb3143c53c084 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -1588,7 +1588,7 @@ pub const CopyFileOptions = struct { pub const CopyFileError = File.OpenError || File.StatError || File.Atomic.InitError || File.Atomic.FinishError || - File.Reader.Error || File.WriteError || error{InvalidFileName}; + File.Reader.Error || File.Writer.Error || error{InvalidFileName}; /// Atomically creates a new file at `dest_path` within `dest_dir` with the /// same contents as `source_path` within `source_dir`, overwriting any already diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index 1b87c97949be14adfaf93a5cdbbac4838f9ee488..386e1b6b5a2492db2b772bca3ef015c6c01eeab5 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -242,8 +242,7 @@ pub fn addCertsFromFilePath( } pub const AddCertsFromFileError = Allocator.Error || - Io.File.GetSeekPosError || - Io.File.ReadError || + Io.File.Reader.Error || ParseCertError || std.base64.Error || error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker, Streaming }; diff --git a/lib/std/crypto/Certificate/Bundle/macos.zig b/lib/std/crypto/Certificate/Bundle/macos.zig index 086c8feb3fee1f25691b2e28c81d0fb105ead700..ea8a91702d6f82767973e1f830159518ac08e7e0 100644 --- a/lib/std/crypto/Certificate/Bundle/macos.zig +++ b/lib/std/crypto/Certificate/Bundle/macos.zig @@ -6,7 +6,7 @@ const mem = std.mem; const Allocator = std.mem.Allocator; const Bundle = @import("../Bundle.zig"); -pub const RescanMacError = Allocator.Error || Io.File.OpenError || Io.File.ReadError || Io.File.SeekError || Bundle.ParseCertError || error{EndOfStream}; +pub const RescanMacError = Allocator.Error || Io.File.OpenError || Io.File.Reader.Error || Io.File.SeekError || Bundle.ParseCertError || error{EndOfStream}; pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanMacError!void { cb.bytes.clearRetainingCapacity(); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 5205e8eacf1606fca5297aa8b4d7df4faf74dda1..1a8e5adfbd56fa9aa6e63f12cc7621705fb186ad 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -213,7 +213,7 @@ test "Dir.readLink" { // test 3: relative path symlink const parent_file = ".." ++ fs.path.sep_str ++ "target.txt"; const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file); - var subdir = try ctx.dir.makeOpenPath("subdir", .{}); + var subdir = try ctx.dir.makeOpenPath(io, "subdir", .{}); defer subdir.close(io); try setupSymlink(io, subdir, canonical_parent_file, "relative-link.txt", .{}); try testReadLink(io, subdir, canonical_parent_file, "relative-link.txt"); @@ -411,7 +411,7 @@ test "openDir non-cwd parent '..'" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - var subdir = try tmp.dir.makeOpenPath("subdir", .{}); + var subdir = try tmp.dir.makeOpenPath(io, "subdir", .{}); defer subdir.close(io); var dir = try subdir.openDir(io, "..", .{}); @@ -613,7 +613,7 @@ test "Dir.Iterator but dir is deleted during iteration" { defer tmp.cleanup(); // Create directory and setup an iterator for it - var subdir = try tmp.dir.makeOpenPath("subdir", .{ .iterate = true }); + var subdir = try tmp.dir.makeOpenPath(io, "subdir", .{ .iterate = true }); defer subdir.close(io); var iterator = subdir.iterate(); @@ -862,7 +862,7 @@ test "makeOpenPath parent dirs do not exist" { var tmp_dir = tmpDir(.{}); defer tmp_dir.cleanup(); - var dir = try tmp_dir.dir.makeOpenPath("root_dir/parent_dir/some_dir", .{}); + var dir = try tmp_dir.dir.makeOpenPath(io, "root_dir/parent_dir/some_dir", .{}); dir.close(io); // double check that the full directory structure was created @@ -1010,7 +1010,7 @@ test "Dir.rename directory onto non-empty dir" { try ctx.dir.makeDir(io, test_dir_path, .default_dir); - var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{}); + var target_dir = try ctx.dir.makeOpenPath(io, target_dir_path, .{}); var file = try target_dir.createFile(io, "test_file", .{ .read = true }); file.close(io); target_dir.close(io); @@ -1147,7 +1147,7 @@ test "deleteTree does not follow symlinks" { try tmp.dir.makePath(io, "b"); { - var a = try tmp.dir.makeOpenPath("a", .{}); + var a = try tmp.dir.makeOpenPath(io, "a", .{}); defer a.close(io); try setupSymlink(io, a, "../b", "b", .{ .is_directory = true }); @@ -1346,7 +1346,7 @@ test "makepath ignores '.'" { fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8) !void { // setup, create a dir and a nested file both with maxed filenames, and walk the dir { - var maxed_dir = try iterable_dir.makeOpenPath(maxed_filename, .{}); + var maxed_dir = try iterable_dir.makeOpenPath(io, maxed_filename, .{}); defer maxed_dir.close(io); try maxed_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" }); diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 87361a503891f626a53a97ec07263365f768f60b..64511ff4a22b404ba8ce50b22c639772d76fc5b3 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -142,7 +142,7 @@ test "linkat with different directories" { const target_name = "link-target"; const link_name = "newlink"; - const subdir = try tmp.dir.makeOpenPath("subdir", .{}); + const subdir = try tmp.dir.makeOpenPath(io, "subdir", .{}); defer tmp.dir.deleteFile(io, target_name) catch {}; try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" }); diff --git a/lib/std/zip.zig b/lib/std/zip.zig index 5a91b73d6a768cd2759f66481c2442e9ec6d5fd3..770bfd8ae7ab69e008fc13b37f1298b4429d2598 100644 --- a/lib/std/zip.zig +++ b/lib/std/zip.zig @@ -117,7 +117,7 @@ pub const EndRecord = extern struct { return record; } - pub const FindFileError = File.Reader.SizeError || File.SeekError || File.ReadError || error{ + pub const FindFileError = File.Reader.SizeError || File.SeekError || File.Reader.Error || error{ ZipNoEndRecord, EndOfStream, ReadFailed, @@ -560,7 +560,7 @@ pub const Iterator = struct { const out_file = blk: { if (std.fs.path.dirname(filename)) |dirname| { - var parent_dir = try dest.makeOpenPath(dirname, .{}); + var parent_dir = try dest.makeOpenPath(io, dirname, .{}); defer parent_dir.close(io); const basename = std.fs.path.basename(filename); diff --git a/src/Compilation.zig b/src/Compilation.zig index c63fa9a3c1a344b74e55ac3816413dbd5088be7b..9e76b8fecaef97b6285fa1c4defe36b4571e6aa3 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -832,7 +832,7 @@ pub const Directories = struct { const nonempty_path = if (path.len == 0) "." else path; const handle_or_err = switch (thing) { .@"zig lib" => Io.Dir.cwd().openDir(io, nonempty_path, .{}), - .@"global cache", .@"local cache" => Io.Dir.cwd().makeOpenPath(nonempty_path, .{}), + .@"global cache", .@"local cache" => Io.Dir.cwd().makeOpenPath(io, nonempty_path, .{}), }; return .{ .path = if (path.len == 0) null else path, @@ -2111,7 +2111,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, cache.* = .{ .gpa = gpa, .io = io, - .manifest_dir = options.dirs.local_cache.handle.makeOpenPath("h", .{}) catch |err| { + .manifest_dir = options.dirs.local_cache.handle.makeOpenPath(io, "h", .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } }); }, }; @@ -2161,7 +2161,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, // to redundantly happen for each AstGen operation. const zir_sub_dir = "z"; - var local_zir_dir = options.dirs.local_cache.handle.makeOpenPath(zir_sub_dir, .{}) catch |err| { + var local_zir_dir = options.dirs.local_cache.handle.makeOpenPath(io, zir_sub_dir, .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = zir_sub_dir, .err = err } }); }; errdefer local_zir_dir.close(io); @@ -2169,7 +2169,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .handle = local_zir_dir, .path = try options.dirs.local_cache.join(arena, &.{zir_sub_dir}), }; - var global_zir_dir = options.dirs.global_cache.handle.makeOpenPath(zir_sub_dir, .{}) catch |err| { + var global_zir_dir = options.dirs.global_cache.handle.makeOpenPath(io, zir_sub_dir, .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .global, .sub = zir_sub_dir, .err = err } }); }; errdefer global_zir_dir.close(io); @@ -2440,7 +2440,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, const digest = hash.final(); const artifact_sub_dir = "o" ++ fs.path.sep_str ++ digest; - var artifact_dir = options.dirs.local_cache.handle.makeOpenPath(artifact_sub_dir, .{}) catch |err| { + var artifact_dir = options.dirs.local_cache.handle.makeOpenPath(io, artifact_sub_dir, .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = artifact_sub_dir, .err = err } }); }; errdefer artifact_dir.close(io); @@ -2895,7 +2895,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE tmp_dir_rand_int = std.crypto.random.int(u64); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); - const handle = comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}) catch |err| { + const handle = comp.dirs.local_cache.handle.makeOpenPath(io, tmp_dir_sub_path, .{}) catch |err| { return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err }); }; break :d .{ .path = path, .handle = handle }; @@ -2976,7 +2976,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE tmp_dir_rand_int = std.crypto.random.int(u64); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); - const handle = comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}) catch |err| { + const handle = comp.dirs.local_cache.handle.makeOpenPath(io, tmp_dir_sub_path, .{}) catch |err| { return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err }); }; break :d .{ .path = path, .handle = handle }; @@ -5267,7 +5267,7 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { const io = comp.io; const docs_path = comp.resolveEmitPath(comp.emit_docs.?); - var out_dir = docs_path.root_dir.handle.makeOpenPath(docs_path.sub_path, .{}) catch |err| { + var out_dir = docs_path.root_dir.handle.makeOpenPath(io, docs_path.sub_path, .{}) catch |err| { return comp.lockAndSetMiscFailure( .docs_copy, "unable to create output directory '{f}': {s}", @@ -5509,7 +5509,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU assert(docs_bin_file.sub_path.len > 0); // emitted binary is not a directory const docs_path = comp.resolveEmitPath(comp.emit_docs.?); - var out_dir = docs_path.root_dir.handle.makeOpenPath(docs_path.sub_path, .{}) catch |err| { + var out_dir = docs_path.root_dir.handle.makeOpenPath(io, docs_path.sub_path, .{}) catch |err| { comp.lockAndSetMiscFailure( .docs_copy, "unable to create output directory '{f}': {t}", @@ -5699,7 +5699,7 @@ pub fn translateC( const tmp_basename = std.fmt.hex(std.crypto.random.int(u64)); const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename; const cache_dir = comp.dirs.local_cache.handle; - var cache_tmp_dir = try cache_dir.makeOpenPath(tmp_sub_path, .{}); + var cache_tmp_dir = try cache_dir.makeOpenPath(io, tmp_sub_path, .{}); defer cache_tmp_dir.close(io); const translated_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, translated_basename }); @@ -6274,7 +6274,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // We can't know the digest until we do the C compiler invocation, // so we need a temporary filename. const out_obj_path = try comp.tmpFilePath(arena, o_basename); - var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{}); + var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath(io, "tmp", .{}); defer zig_cache_tmp_dir.close(io); const out_diag_path = if (comp.clang_passthrough_mode or !ext.clangSupportsDiagnostics()) @@ -6439,7 +6439,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // Rename into place. const digest = man.final(); const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest }); - var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); + var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(io, o_sub_path, .{}); defer o_dir.close(io); const tmp_basename = fs.path.basename(out_obj_path); try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename, io); @@ -6528,7 +6528,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 const digest = man.final(); const o_sub_path = try fs.path.join(arena, &.{ "o", &digest }); - var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); + var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(io, o_sub_path, .{}); defer o_dir.close(io); const in_rc_path = try comp.dirs.local_cache.join(comp.gpa, &.{ @@ -6616,7 +6616,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 const rc_basename_noext = src_basename[0 .. src_basename.len - fs.path.extension(src_basename).len]; const digest = if (try man.hit()) man.final() else blk: { - var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{}); + var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath(io, "tmp", .{}); defer zig_cache_tmp_dir.close(io); const res_filename = try std.fmt.allocPrint(arena, "{s}.res", .{rc_basename_noext}); @@ -6687,7 +6687,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 // Rename into place. const digest = man.final(); const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest }); - var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); + var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(io, o_sub_path, .{}); defer o_dir.close(io); const tmp_basename = fs.path.basename(out_res_path); try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename, io); diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index d25ee55c32bb1b94a02485fcfeeda551a1d6a488..33214a47dbf0d74f1af158b227fa71b6828b5942 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -500,12 +500,12 @@ fn runResource( var tmp_directory: Cache.Directory = .{ .path = tmp_directory_path, .handle = handle: { - const dir = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{ + const dir = cache_root.handle.makeOpenPath(io, tmp_dir_sub_path, .{ .iterate = true, }) catch |err| { try eb.addRootErrorMessage(.{ - .msg = try eb.printString("unable to create temporary directory '{s}': {s}", .{ - tmp_directory_path, @errorName(err), + .msg = try eb.printString("unable to create temporary directory '{s}': {t}", .{ + tmp_directory_path, err, }), }); return error.FetchFailed; @@ -524,7 +524,7 @@ fn runResource( if (native_os == .linux and f.job_queue.work_around_btrfs_bug) { // https://github.com/ziglang/zig/issues/17095 pkg_path.root_dir.handle.close(io); - pkg_path.root_dir.handle = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{ + pkg_path.root_dir.handle = cache_root.handle.makeOpenPath(io, tmp_dir_sub_path, .{ .iterate = true, }) catch @panic("btrfs workaround failed"); } @@ -1366,7 +1366,7 @@ fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!U // we do not attempt to replicate the exact structure of a real .git // directory, since that isn't relevant for fetching a package. { - var pack_dir = try out_dir.makeOpenPath(".git", .{}); + var pack_dir = try out_dir.makeOpenPath(io, ".git", .{}); defer pack_dir.close(io); var pack_file = try pack_dir.createFile(io, "pkg.pack", .{ .read = true }); defer pack_file.close(io); @@ -1743,7 +1743,7 @@ const HashedFile = struct { const Error = Io.File.OpenError || - Io.File.ReadError || + Io.File.Reader.Error || Io.File.StatError || Io.File.ChmodError || Io.Dir.ReadLinkError; @@ -2258,7 +2258,7 @@ const TestFetchBuilder = struct { cache_parent_dir: std.Io.Dir, path_or_url: []const u8, ) !*Fetch { - const cache_dir = try cache_parent_dir.makeOpenPath("zig-global-cache", .{}); + const cache_dir = try cache_parent_dir.makeOpenPath(io, "zig-global-cache", .{}); self.http_client = .{ .allocator = allocator, .io = io }; self.global_cache_directory = .{ .handle = cache_dir, .path = null }; diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index 255f0e6f6961a9e35230e43646d262ec3787aa6d..f53c96cc3d4ae9cfdeb46c897af5b9713687b221 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -1720,10 +1720,10 @@ pub fn main() !void { var pack_file_reader = pack_file.reader(io, &pack_file_buffer); const commit = try Oid.parse(format, args[3]); - var worktree = try Io.Dir.cwd().makeOpenPath(args[4], .{}); + var worktree = try Io.Dir.cwd().makeOpenPath(io, args[4], .{}); defer worktree.close(io); - var git_dir = try worktree.makeOpenPath(".git", .{}); + var git_dir = try worktree.makeOpenPath(io, ".git", .{}); defer git_dir.close(io); std.debug.print("Starting index...\n", .{}); diff --git a/src/Zcu.zig b/src/Zcu.zig index 2891da2407fa1744e477310387eb4bab22f271bc..e0e254d807a93db553c1bbb6bb4be19fd9b317b4 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -1200,7 +1200,7 @@ pub const EmbedFile = struct { /// `.none` means the file was not loaded, so `stat` is undefined. val: InternPool.Index, /// If this is `null` and `val` is `.none`, the file has never been loaded. - err: ?(Io.File.OpenError || Io.File.StatError || Io.File.ReadError || error{UnexpectedEof}), + err: ?(Io.File.OpenError || Io.File.StatError || Io.File.Reader.Error || error{UnexpectedEof}), stat: Cache.File.Stat, pub const Index = enum(u32) { diff --git a/src/crash_report.zig b/src/crash_report.zig index d525d4b3b5d91e32b715723fd871439ac8f28cd2..76503fdc536ee3ed98dafb633bb58ed0d7aac0ab 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -95,19 +95,19 @@ fn dumpCrashContext() Io.Writer.Error!void { // TODO: this does mean that a different thread could grab the stderr mutex between the context // and the actual panic printing, which would be quite confusing. - const stderr, _ = std.debug.lockStderrWriter(&.{}); + const stderr = std.debug.lockStderrWriter(&.{}); defer std.debug.unlockStderrWriter(); - try stderr.writeAll("Compiler crash context:\n"); + try stderr.interface.writeAll("Compiler crash context:\n"); if (CodegenFunc.current) |*cg| { const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav; const func_fqn = cg.zcu.intern_pool.getNav(func_nav).fqn; - try stderr.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)}); + try stderr.interface.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)}); } else if (AnalyzeBody.current) |anal| { - try dumpCrashContextSema(anal, stderr, &S.crash_heap); + try dumpCrashContextSema(anal, &stderr.interface, &S.crash_heap); } else { - try stderr.writeAll("(no context)\n\n"); + try stderr.interface.writeAll("(no context)\n\n"); } } fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void { diff --git a/src/fmt.zig b/src/fmt.zig index 59b4470f81820fe1482114e68105997d0876d96c..bec3496e683507387e4a84f97827b43f8be66dec 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -59,7 +59,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try Io.File.stdout().writeAll(usage_fmt); + try Io.File.stdout().writeStreamingAll(io, usage_fmt); return process.cleanExit(); } else if (mem.eql(u8, arg, "--color")) { if (i + 1 >= args.len) { @@ -154,7 +154,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! process.exit(code); } - return Io.File.stdout().writeAll(formatted); + return Io.File.stdout().writeStreamingAll(io, formatted); } if (input_files.items.len == 0) { @@ -162,7 +162,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! } var stdout_buffer: [4096]u8 = undefined; - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); var fmt: Fmt = .{ .gpa = gpa, @@ -231,7 +231,7 @@ fn fmtPathDir( if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; var dir_it = dir.iterate(); - while (try dir_it.next()) |entry| { + while (try dir_it.next(io)) |entry| { const is_dir = entry.kind == .directory; if (mem.startsWith(u8, entry.name, ".")) continue; @@ -244,7 +244,7 @@ fn fmtPathDir( try fmtPathDir(fmt, full_path, check_mode, dir, entry.name); } else { fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| { - std.log.err("unable to format '{s}': {s}", .{ full_path, @errorName(err) }); + std.log.err("unable to format '{s}': {t}", .{ full_path, err }); fmt.any_error = true; return; }; @@ -355,7 +355,7 @@ fn fmtPathFile( try fmt.stdout_writer.interface.print("{s}\n", .{file_path}); fmt.any_error = true; } else { - var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode, .write_buffer = &.{} }); + var af = try dir.atomicFile(io, sub_path, .{ .permissions = stat.permissions, .write_buffer = &.{} }); defer af.deinit(); try af.file_writer.interface.writeAll(fmt.out_buffer.written()); diff --git a/src/introspect.zig b/src/introspect.zig index 3f8308961f4f1a136e362e2a1d4b2d0f859f6da5..0a57505aebd2fe890bec2583b214718592445cb4 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -3,10 +3,9 @@ const build_options = @import("build_options"); const std = @import("std"); const Io = std.Io; +const Dir = std.Io.Dir; const mem = std.mem; const Allocator = std.mem.Allocator; -const os = std.os; -const fs = std.fs; const Cache = std.Build.Cache; const Compilation = @import("Compilation.zig"); @@ -16,11 +15,11 @@ const Package = @import("Package.zig"); /// The path of the returned Directory is relative to `base`. /// The handle of the returned Directory is open. fn testZigInstallPrefix(io: Io, base_dir: Io.Dir) ?Cache.Directory { - const test_index_file = "std" ++ fs.path.sep_str ++ "std.zig"; + const test_index_file = "std" ++ Dir.path.sep_str ++ "std.zig"; zig_dir: { // Try lib/zig/std/std.zig - const lib_zig = "lib" ++ fs.path.sep_str ++ "zig"; + const lib_zig = "lib" ++ Dir.path.sep_str ++ "zig"; var test_zig_dir = base_dir.openDir(io, lib_zig, .{}) catch break :zig_dir; const file = test_zig_dir.openFile(io, test_index_file, .{}) catch { test_zig_dir.close(io); @@ -44,13 +43,13 @@ fn testZigInstallPrefix(io: Io, base_dir: Io.Dir) ?Cache.Directory { pub fn findZigLibDir(gpa: Allocator, io: Io) !Cache.Directory { const cwd_path = try getResolvedCwd(gpa); defer gpa.free(cwd_path); - const self_exe_path = try fs.selfExePathAlloc(gpa); + const self_exe_path = try std.process.executablePathAlloc(io, gpa); defer gpa.free(self_exe_path); return findZigLibDirFromSelfExe(gpa, io, cwd_path, self_exe_path); } -/// Like `std.process.getCwdAlloc`, but also resolves the path with `std.fs.path.resolve`. This +/// Like `std.process.getCwdAlloc`, but also resolves the path with `Dir.path.resolve`. This /// means the path has no repeated separators, no "." or ".." components, and no trailing separator. /// On WASI, "" is returned instead of ".". pub fn getResolvedCwd(gpa: Allocator) error{ @@ -68,8 +67,8 @@ pub fn getResolvedCwd(gpa: Allocator) error{ } const cwd = try std.process.getCwdAlloc(gpa); defer gpa.free(cwd); - const resolved = try fs.path.resolve(gpa, &.{cwd}); - std.debug.assert(fs.path.isAbsolute(resolved)); + const resolved = try Dir.path.resolve(gpa, &.{cwd}); + std.debug.assert(Dir.path.isAbsolute(resolved)); return resolved; } @@ -84,12 +83,12 @@ pub fn findZigLibDirFromSelfExe( ) error{ OutOfMemory, FileNotFound }!Cache.Directory { const cwd = Io.Dir.cwd(); var cur_path: []const u8 = self_exe_path; - while (fs.path.dirname(cur_path)) |dirname| : (cur_path = dirname) { + while (Dir.path.dirname(cur_path)) |dirname| : (cur_path = dirname) { var base_dir = cwd.openDir(io, dirname, .{}) catch continue; defer base_dir.close(io); const sub_directory = testZigInstallPrefix(io, base_dir) orelse continue; - const p = try fs.path.join(allocator, &.{ dirname, sub_directory.path.? }); + const p = try Dir.path.join(allocator, &.{ dirname, sub_directory.path.? }); defer allocator.free(p); const resolved = try resolvePath(allocator, cwd_path, &.{p}); @@ -113,18 +112,18 @@ pub fn resolveGlobalCacheDir(allocator: Allocator) ![]u8 { if (builtin.os.tag != .windows) { if (std.zig.EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| { if (cache_root.len > 0) { - return fs.path.join(allocator, &.{ cache_root, appname }); + return Dir.path.join(allocator, &.{ cache_root, appname }); } } if (std.zig.EnvVar.HOME.getPosix()) |home| { - return fs.path.join(allocator, &.{ home, ".cache", appname }); + return Dir.path.join(allocator, &.{ home, ".cache", appname }); } } - return fs.getAppDataDir(allocator, appname); + return std.fs.getAppDataDir(allocator, appname); } -/// Similar to `fs.path.resolve`, but converts to a cwd-relative path, or, if that would +/// Similar to `Dir.path.resolve`, but converts to a cwd-relative path, or, if that would /// start with a relative up-dir (".."), an absolute path based on the cwd. Also, the cwd /// returns the empty string ("") instead of ".". pub fn resolvePath( @@ -136,7 +135,7 @@ pub fn resolvePath( ) Allocator.Error![]u8 { if (builtin.target.os.tag == .wasi) { std.debug.assert(mem.eql(u8, cwd_resolved, "")); - const res = try fs.path.resolve(gpa, paths); + const res = try Dir.path.resolve(gpa, paths); if (mem.eql(u8, res, ".")) { gpa.free(res); return ""; @@ -146,16 +145,16 @@ pub fn resolvePath( // Heuristic for a fast path: if no component is absolute and ".." never appears, we just need to resolve `paths`. for (paths) |p| { - if (fs.path.isAbsolute(p)) break; // absolute path + if (Dir.path.isAbsolute(p)) break; // absolute path if (mem.indexOf(u8, p, "..") != null) break; // may contain up-dir } else { // no absolute path, no "..". - const res = try fs.path.resolve(gpa, paths); + const res = try Dir.path.resolve(gpa, paths); if (mem.eql(u8, res, ".")) { gpa.free(res); return ""; } - std.debug.assert(!fs.path.isAbsolute(res)); + std.debug.assert(!Dir.path.isAbsolute(res)); std.debug.assert(!isUpDir(res)); return res; } @@ -164,19 +163,19 @@ pub fn resolvePath( // Optimization: `paths` often has just one element. const path_resolved = switch (paths.len) { 0 => unreachable, - 1 => try fs.path.resolve(gpa, &.{ cwd_resolved, paths[0] }), + 1 => try Dir.path.resolve(gpa, &.{ cwd_resolved, paths[0] }), else => r: { const all_paths = try gpa.alloc([]const u8, paths.len + 1); defer gpa.free(all_paths); all_paths[0] = cwd_resolved; @memcpy(all_paths[1..], paths); - break :r try fs.path.resolve(gpa, all_paths); + break :r try Dir.path.resolve(gpa, all_paths); }, }; errdefer gpa.free(path_resolved); - std.debug.assert(fs.path.isAbsolute(path_resolved)); - std.debug.assert(fs.path.isAbsolute(cwd_resolved)); + std.debug.assert(Dir.path.isAbsolute(path_resolved)); + std.debug.assert(Dir.path.isAbsolute(cwd_resolved)); if (!std.mem.startsWith(u8, path_resolved, cwd_resolved)) return path_resolved; // not in cwd if (path_resolved.len == cwd_resolved.len) { @@ -184,7 +183,7 @@ pub fn resolvePath( gpa.free(path_resolved); return ""; } - if (path_resolved[cwd_resolved.len] != std.fs.path.sep) return path_resolved; // not in cwd (last component differs) + if (path_resolved[cwd_resolved.len] != Dir.path.sep) return path_resolved; // not in cwd (last component differs) // in cwd; extract sub path const sub_path = try gpa.dupe(u8, path_resolved[cwd_resolved.len + 1 ..]); @@ -192,9 +191,8 @@ pub fn resolvePath( return sub_path; } -/// TODO move this to std.fs.path pub fn isUpDir(p: []const u8) bool { - return mem.startsWith(u8, p, "..") and (p.len == 2 or p[2] == fs.path.sep); + return mem.startsWith(u8, p, "..") and (p.len == 2 or p[2] == Dir.path.sep); } pub const default_local_zig_cache_basename = ".zig-cache"; @@ -205,12 +203,12 @@ pub const default_local_zig_cache_basename = ".zig-cache"; pub fn resolveSuitableLocalCacheDir(arena: Allocator, io: Io, cwd: []const u8) Allocator.Error!?[]u8 { var cur_dir = cwd; while (true) { - const joined = try fs.path.join(arena, &.{ cur_dir, Package.build_zig_basename }); + const joined = try Dir.path.join(arena, &.{ cur_dir, Package.build_zig_basename }); if (Io.Dir.cwd().access(io, joined, .{})) |_| { - return try fs.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename }); + return try Dir.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename }); } else |err| switch (err) { error.FileNotFound => { - cur_dir = fs.path.dirname(cur_dir) orelse return null; + cur_dir = Dir.path.dirname(cur_dir) orelse return null; continue; }, else => return null, diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index 77bd4372d0f6d15e8dbbc2232fe6574aef53ded8..ee638718b7c3934ea1ea1136162b2a65125c0041 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -444,7 +444,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye var cache: Cache = .{ .gpa = gpa, .io = io, - .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}), + .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath(io, "h", .{}), }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); @@ -477,7 +477,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); var o_directory: Cache.Directory = .{ - .handle = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}), + .handle = try comp.dirs.global_cache.handle.makeOpenPath(io, o_sub_path, .{}), .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}), }; defer o_directory.handle.close(io); diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index a60dc921bea0b7be434ea266c3c8a83d74f8ef52..de86413cfd630e72bb8b092db7374fd8243fc6c2 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -679,7 +679,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye var cache: Cache = .{ .gpa = gpa, .io = io, - .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}), + .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath(io, "h", .{}), }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); @@ -712,7 +712,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); var o_directory: Cache.Directory = .{ - .handle = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}), + .handle = try comp.dirs.global_cache.handle.makeOpenPath(io, o_sub_path, .{}), .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}), }; defer o_directory.handle.close(io); diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 9631ec22f981f64681a511c4f4c288c8e3380c45..05a9de71e790e1926f5a1082a339866ac4f84cfc 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -258,7 +258,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { var cache: Cache = .{ .gpa = gpa, .io = io, - .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}), + .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath(io, "h", .{}), }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); @@ -297,7 +297,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { const digest = man.final(); const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); - var o_dir = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}); + var o_dir = try comp.dirs.global_cache.handle.makeOpenPath(io, o_sub_path, .{}); defer o_dir.close(io); const aro = @import("aro"); diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index fd80616e9de0ddeac4bc66e9efa8449f88ba3254..33dd62d851ba0f789ddddaaa7aa28d1863c9eddf 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -385,7 +385,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye var cache: Cache = .{ .gpa = gpa, .io = io, - .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}), + .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath(io, "h", .{}), }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); @@ -418,7 +418,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); var o_directory: Cache.Directory = .{ - .handle = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}), + .handle = try comp.dirs.global_cache.handle.makeOpenPath(io, o_sub_path, .{}), .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}), }; defer o_directory.handle.close(io); diff --git a/src/link.zig b/src/link.zig index 7ebe1c6ba29c3c2eb0262c3ec1f58b3a04c38df8..9914b1af3af46e454fd7e2476defe3517436d34c 100644 --- a/src/link.zig +++ b/src/link.zig @@ -2170,28 +2170,27 @@ fn resolvePathInputLib( }) { var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { error.FileNotFound => return .no_match, - else => |e| fatal("unable to search for {s} library '{f}': {s}", .{ - @tagName(link_mode), std.fmt.alt(test_path, .formatEscapeChar), @errorName(e), + else => |e| fatal("unable to search for {t} library '{f}': {t}", .{ + link_mode, std.fmt.alt(test_path, .formatEscapeChar), e, }), }; errdefer file.close(io); try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len)); - const n = file.preadAll(ld_script_bytes.items, 0) catch |err| fatal("failed to read '{f}': {s}", .{ - std.fmt.alt(test_path, .formatEscapeChar), @errorName(err), - }); + const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err| + fatal("failed to read '{f}': {t}", .{ std.fmt.alt(test_path, .formatEscapeChar), err }); const buf = ld_script_bytes.items[0..n]; if (mem.startsWith(u8, buf, std.elf.MAGIC) or mem.startsWith(u8, buf, std.elf.ARMAG)) { // Appears to be an ELF or archive file. return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query); } const stat = file.stat(io) catch |err| - fatal("failed to stat {f}: {s}", .{ test_path, @errorName(err) }); + fatal("failed to stat {f}: {t}", .{ test_path, err }); const size = std.math.cast(u32, stat.size) orelse fatal("{f}: linker script too big", .{test_path}); try ld_script_bytes.resize(gpa, size); const buf2 = ld_script_bytes.items[n..]; - const n2 = file.preadAll(buf2, n) catch |err| - fatal("failed to read {f}: {s}", .{ test_path, @errorName(err) }); + const n2 = file.readPositionalAll(io, buf2, n) catch |err| + fatal("failed to read {f}: {t}", .{ test_path, err }); if (n2 != buf2.len) fatal("failed to read {f}: unexpected end of file", .{test_path}); // This `Io` is only used for a mutex, and we know we aren't doing anything async/concurrent. diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 009e59ed0d8a38dbff3ad1bf3a9609766c31e7d6..2585d43eba9e3f7a9b4b887f0b9bfd094f89279a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -636,7 +636,7 @@ fn create( const coff = try arena.create(Coff); const file = try path.root_dir.handle.createFile(io, path.sub_path, .{ .read = true, - .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode), + .permissions = link.File.determinePermissions(comp.config.output_mode, comp.config.link_mode), }); errdefer file.close(io); coff.* = .{ @@ -653,7 +653,7 @@ fn create( .allow_shlib_undefined = false, .stack_size = 0, }, - .mf = try .init(file, comp.gpa), + .mf = try .init(file, comp.gpa, io), .nodes = .empty, .import_table = .{ .ni = .none, diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index c675af3e22d463e9422107b87c4e23b419a973e1..8b05272033bddd8e5315a68c8475a3537a87b472 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -52,7 +52,6 @@ pub const UpdateError = error{ codegen.GenerateSymbolError || Io.File.OpenError || Io.File.LengthError || - Io.File.CopyRangeError || Io.File.ReadPositionalError || Io.File.WritePositionalError; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 13a624a295b753746c2ab94c8ae1b4d112ee79eb..2916f5ee2598f72749185d21b5282aca08a57e7b 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -320,7 +320,7 @@ pub fn createEmpty( self.base.file = try emit.root_dir.handle.createFile(io, sub_path, .{ .truncate = true, .read = true, - .mode = link.File.determineMode(output_mode, link_mode), + .permissions = link.File.determinePermissions(output_mode, link_mode), }); const gpa = comp.gpa; diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 72fdb244a47084d4dec6d2c4d8c7426dfeb52b9b..fc4794a7b02499db756e2515e3f5dc443ccbbf24 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -976,7 +976,7 @@ fn create( const elf = try arena.create(Elf); const file = try path.root_dir.handle.createFile(io, path.sub_path, .{ .read = true, - .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode), + .permissions = link.File.determinePermissions(comp.config.output_mode, comp.config.link_mode), }); errdefer file.close(io); elf.* = .{ @@ -994,7 +994,7 @@ fn create( .stack_size = 0, }, .options = options, - .mf = try .init(file, comp.gpa), + .mf = try .init(file, comp.gpa, io), .ni = .{ .tls = .none, }, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index f996442f2494ef1442391a0bbf9cf585dbf6f36e..6c380637eedf1d60aee278494d1378a914b8656f 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -224,7 +224,7 @@ pub fn createEmpty( self.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ .truncate = true, .read = true, - .mode = link.File.determineMode(output_mode, link_mode), + .permissions = link.File.determinePermissions(output_mode, link_mode), }); // Append null file @@ -3157,7 +3157,9 @@ fn detectAllocCollision(self: *MachO, start: u64, size: u64) !?u64 { } } - if (at_end) try self.base.file.?.setEndPos(end); + const comp = self.base.comp; + const io = comp.io; + if (at_end) try self.base.file.?.setLength(io, end); return null; } @@ -3292,7 +3294,7 @@ pub fn reopenDebugInfo(self: *MachO) !void { ); defer gpa.free(d_sym_path); - var d_sym_bundle = try self.base.emit.root_dir.handle.makeOpenPath(d_sym_path, .{}); + var d_sym_bundle = try self.base.emit.root_dir.handle.makeOpenPath(io, d_sym_path, .{}); defer d_sym_bundle.close(io); self.d_sym.?.file = try d_sym_bundle.createFile(io, fs.path.basename(self.base.emit.sub_path), .{ @@ -3303,6 +3305,10 @@ pub fn reopenDebugInfo(self: *MachO) !void { // TODO: move to ZigObject fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { + const comp = self.base.comp; + const gpa = comp.gpa; + const io = comp.io; + if (!self.base.isRelocatable()) { const base_vmaddr = blk: { const pagezero_size = self.pagezero_size orelse default_pagezero_size; @@ -3357,7 +3363,11 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { if (options.zo.dwarf) |*dwarf| { // Create dSYM bundle. log.debug("creating {s}.dSYM bundle", .{options.emit.sub_path}); - self.d_sym = .{ .allocator = self.base.comp.gpa, .file = null }; + self.d_sym = .{ + .io = io, + .allocator = gpa, + .file = null, + }; try self.reopenDebugInfo(); try self.d_sym.?.initMetadata(self); try dwarf.initMetadata(); @@ -3477,6 +3487,9 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo const seg_id = self.sections.items(.segment_id)[sect_index]; const seg = &self.segments.items[seg_id]; + const comp = self.base.comp; + const io = comp.io; + if (!sect.isZerofill()) { const allocated_size = self.allocatedSize(sect.offset); if (needed_size > allocated_size) { @@ -3498,7 +3511,7 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo sect.offset = @intCast(new_offset); } else if (sect.offset + allocated_size == std.math.maxInt(u64)) { - try self.base.file.?.setEndPos(sect.offset + needed_size); + try self.base.file.?.setLength(io, sect.offset + needed_size); } seg.filesize = needed_size; } @@ -3520,6 +3533,8 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo } fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void { + const comp = self.base.comp; + const io = comp.io; const sect = &self.sections.items(.header)[sect_index]; if (!sect.isZerofill()) { @@ -3547,7 +3562,7 @@ fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void sect.offset = @intCast(new_offset); sect.addr = new_addr; } else if (sect.offset + allocated_size == std.math.maxInt(u64)) { - try self.base.file.?.setEndPos(sect.offset + needed_size); + try self.base.file.?.setLength(io, sect.offset + needed_size); } } sect.size = needed_size; @@ -5346,12 +5361,12 @@ pub fn pwriteAll(macho_file: *MachO, bytes: []const u8, offset: u64) error{LinkF }; } -pub fn setEndPos(macho_file: *MachO, length: u64) error{LinkFailure}!void { +pub fn setLength(macho_file: *MachO, length: u64) error{LinkFailure}!void { const comp = macho_file.base.comp; + const io = comp.io; const diags = &comp.link_diags; - macho_file.base.file.?.setEndPos(length) catch |err| { - return diags.fail("failed to set file end pos: {s}", .{@errorName(err)}); - }; + macho_file.base.file.?.setLength(io, length) catch |err| + return diags.fail("failed to set file end pos: {t}", .{err}); } pub fn cast(macho_file: *MachO, comptime T: type, x: anytype) error{LinkFailure}!T { diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 4e58aae01a05f2fd557c860f4133c11f84e8e1e2..974b03ef7f753b3e77fb275a974d55342d08a95b 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -10,6 +10,7 @@ const assert = std.debug.assert; const linux = std.os.linux; const windows = std.os.windows; +io: Io, file: std.Io.File, flags: packed struct { block_size: std.mem.Alignment, @@ -36,8 +37,9 @@ pub const Error = std.posix.MMapError || std.posix.MRemapError || Io.File.Length NoSpaceLeft, }; -pub fn init(file: std.Io.File, gpa: std.mem.Allocator) !MappedFile { +pub fn init(file: std.Io.File, gpa: std.mem.Allocator, io: Io) !MappedFile { var mf: MappedFile = .{ + .io = io, .file = file, .flags = undefined, .section = if (is_windows) windows.INVALID_HANDLE_VALUE else {}, @@ -624,13 +626,14 @@ pub fn addNodeAfter( } fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested_size: u64) !void { + const io = mf.io; const node = ni.get(mf); const old_offset, const old_size = node.location().resolve(mf); const new_size = node.flags.alignment.forward(@intCast(requested_size)); // Resize the entire file if (ni == Node.Index.root) { try mf.ensureCapacityForSetLocation(gpa); - try mf.file.setEndPos(new_size); + try mf.file.setLength(io, new_size); try mf.ensureTotalCapacity(@intCast(new_size)); ni.setLocationAssumeCapacity(mf, old_offset, new_size); return; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 80a248eb6f8af26a3a2f43868d7ac3d134fe5274..489fb8e6fea1409a6417a9de22656ae6db331554 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3002,11 +3002,11 @@ pub fn createEmpty( wasm.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ .truncate = true, .read = true, - .mode = if (Io.File.Permissions.has_executable_bit) + .permissions = if (Io.File.Permissions.has_executable_bit) if (target.os.tag == .wasi and output_mode == .Exe) - Io.File.default_mode | 0b001_000_000 + .executable_file else - Io.File.default_mode + .default_file else 0, }); diff --git a/src/main.zig b/src/main.zig index 2eebb6d060728935851dd599dd992154c3f7d927..47e5af23da80e900b8c9f156a1d36b4e4be1a763 100644 --- a/src/main.zig +++ b/src/main.zig @@ -335,19 +335,20 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { } else if (mem.eql(u8, cmd, "targets")) { dev.check(.targets_command); const host = std.zig.resolveTargetQueryOrFatal(io, .{}); - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); try @import("print_targets.zig").cmdTargets(arena, io, cmd_args, &stdout_writer.interface, &host); return stdout_writer.interface.flush(); } else if (mem.eql(u8, cmd, "version")) { dev.check(.version_command); - try Io.File.stdout().writeAll(build_options.version ++ "\n"); + try Io.File.stdout().writeStreamingAll(io, build_options.version ++ "\n"); return; } else if (mem.eql(u8, cmd, "env")) { dev.check(.env_command); const host = std.zig.resolveTargetQueryOrFatal(io, .{}); - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); try @import("print_env.zig").cmdEnv( arena, + io, &stdout_writer.interface, args, if (native_os == .wasi) wasi_preopens, @@ -361,10 +362,10 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { }); } else if (mem.eql(u8, cmd, "zen")) { dev.check(.zen_command); - return Io.File.stdout().writeAll(info_zen); + return Io.File.stdout().writeStreamingAll(io, info_zen); } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) { dev.check(.help_command); - return Io.File.stdout().writeAll(usage); + return Io.File.stdout().writeStreamingAll(io, usage); } else if (mem.eql(u8, cmd, "ast-check")) { return cmdAstCheck(arena, io, cmd_args); } else if (mem.eql(u8, cmd, "detect-cpu")) { @@ -374,7 +375,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "dump-zir")) { return cmdDumpZir(arena, io, cmd_args); } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "llvm-ints")) { - return cmdDumpLlvmInts(gpa, arena, cmd_args); + return cmdDumpLlvmInts(gpa, arena, io, cmd_args); } else { std.log.info("{s}", .{usage}); fatal("unknown command: {s}", .{args[1]}); @@ -701,7 +702,7 @@ const Emit = union(enum) { yes: []const u8, const OutputToCacheReason = enum { listen, @"zig run", @"zig test" }; - fn resolve(io: Io, emit: Emit, default_basename: []const u8, output_to_cache: ?OutputToCacheReason) Compilation.CreateOptions.Emit { + fn resolve(emit: Emit, io: Io, default_basename: []const u8, output_to_cache: ?OutputToCacheReason) Compilation.CreateOptions.Emit { return switch (emit) { .no => .no, .yes_default_path => if (output_to_cache != null) .yes_cache else .{ .yes_path = default_basename }, @@ -1036,7 +1037,7 @@ fn buildOutputType( fatal("unable to read response file '{s}': {t}", .{ resp_file_path, err }); } else if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try Io.File.stdout().writeAll(usage_build_generic); + try Io.File.stdout().writeStreamingAll(io, usage_build_generic); return cleanExit(); } else if (mem.eql(u8, arg, "--")) { if (arg_mode == .run) { @@ -1858,9 +1859,7 @@ fn buildOutputType( var must_link = false; var file_ext: ?Compilation.FileExt = null; while (it.has_next) { - it.next() catch |err| { - fatal("unable to parse command line parameters: {s}", .{@errorName(err)}); - }; + it.next(io) catch |err| fatal("unable to parse command line parameters: {t}", .{err}); switch (it.zig_equivalent) { .target => target_arch_os_abi = it.only_arg, // example: -target riscv64-linux-unknown .o => { @@ -2836,9 +2835,9 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-V")) { warn("ignoring request for supported emulations: unimplemented", .{}); } else if (mem.eql(u8, arg, "-v")) { - try Io.File.stdout().writeAll("zig ld " ++ build_options.version ++ "\n"); + try Io.File.stdout().writeStreamingAll(io, "zig ld " ++ build_options.version ++ "\n"); } else if (mem.eql(u8, arg, "--version")) { - try Io.File.stdout().writeAll("zig ld " ++ build_options.version ++ "\n"); + try Io.File.stdout().writeStreamingAll(io, "zig ld " ++ build_options.version ++ "\n"); process.exit(0); } else { fatal("unsupported linker arg: {s}", .{arg}); @@ -3077,14 +3076,13 @@ fn buildOutputType( const self_exe_path = switch (native_os) { .wasi => {}, - else => fs.selfExePathAlloc(arena) catch |err| { - fatal("unable to find zig self exe path: {s}", .{@errorName(err)}); - }, + else => process.executablePathAlloc(io, arena) catch |err| fatal("unable to find zig self exe path: {t}", .{err}), }; // This `init` calls `fatal` on error. var dirs: Compilation.Directories = .init( arena, + io, override_lib_dir, override_global_cache_dir, s: { @@ -3097,11 +3095,9 @@ fn buildOutputType( if (native_os == .wasi) wasi_preopens, self_exe_path, ); - defer dirs.deinit(); + defer dirs.deinit(io); - if (linker_optimization) |o| { - warn("ignoring deprecated linker optimization setting '{s}'", .{o}); - } + if (linker_optimization) |o| warn("ignoring deprecated linker optimization setting '{s}'", .{o}); create_module.dirs = dirs; create_module.opts.emit_llvm_ir = emit_llvm_ir != .no; @@ -3324,18 +3320,18 @@ fn buildOutputType( }; const default_h_basename = try std.fmt.allocPrint(arena, "{s}.h", .{root_name}); - const emit_h_resolved = emit_h.resolve(default_h_basename, output_to_cache); + const emit_h_resolved = emit_h.resolve(io, default_h_basename, output_to_cache); const default_asm_basename = try std.fmt.allocPrint(arena, "{s}.s", .{root_name}); - const emit_asm_resolved = emit_asm.resolve(default_asm_basename, output_to_cache); + const emit_asm_resolved = emit_asm.resolve(io, default_asm_basename, output_to_cache); const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{s}.ll", .{root_name}); - const emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename, output_to_cache); + const emit_llvm_ir_resolved = emit_llvm_ir.resolve(io, default_llvm_ir_basename, output_to_cache); const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name}); - const emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename, output_to_cache); + const emit_llvm_bc_resolved = emit_llvm_bc.resolve(io, default_llvm_bc_basename, output_to_cache); - const emit_docs_resolved = emit_docs.resolve("docs", output_to_cache); + const emit_docs_resolved = emit_docs.resolve(io, "docs", output_to_cache); const is_exe_or_dyn_lib = switch (create_module.resolved_options.output_mode) { .Obj => false, @@ -3356,7 +3352,7 @@ fn buildOutputType( const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name}); const emit_implib_resolved: Compilation.CreateOptions.Emit = switch (emit_implib) { .no => .no, - .yes => emit_implib.resolve(default_implib_basename, output_to_cache), + .yes => emit_implib.resolve(io, default_implib_basename, output_to_cache), .yes_default_path => emit: { if (output_to_cache != null) break :emit .yes_cache; const p = try fs.path.join(arena, &.{ @@ -3399,7 +3395,7 @@ fn buildOutputType( // for the hashing algorithm here and in the cache are the same. // We are providing our own cache key, because this file has nothing // to do with the cache manifest. - var file_writer = f.writer(&.{}); + var file_writer = f.writer(io, &.{}); var buffer: [1000]u8 = undefined; var hasher = file_writer.interface.hashed(Cache.Hasher.init("0123456789abcdef"), &buffer); var stdin_reader = Io.File.stdin().readerStreaming(io, &.{}); @@ -3633,13 +3629,13 @@ fn buildOutputType( if (show_builtin) { const builtin_opts = comp.root_mod.getBuiltinOptions(comp.config); const source = try builtin_opts.generate(arena); - return Io.File.stdout().writeAll(source); + return Io.File.stdout().writeStreamingAll(io, source); } switch (listen) { .none => {}, .stdio => { var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer); - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); try serve( comp, &stdin_reader.interface, @@ -3930,11 +3926,8 @@ fn createModule( } if (target.isMinGW()) { - const exists = mingw.libExists(arena, target, create_module.dirs.zig_lib, lib_name) catch |err| { - fatal("failed to check zig installation for DLL import libs: {s}", .{ - @errorName(err), - }); - }; + const exists = mingw.libExists(arena, io, target, create_module.dirs.zig_lib, lib_name) catch |err| + fatal("failed to check zig installation for DLL import libs: {t}", .{err}); if (exists) { try create_module.windows_libs.put(arena, lib_name, {}); continue; @@ -4009,11 +4002,8 @@ fn createModule( } if (create_module.libc_paths_file) |paths_file| { - create_module.libc_installation = LibCInstallation.parse(arena, paths_file, target) catch |err| { - fatal("unable to parse libc paths file at path {s}: {s}", .{ - paths_file, @errorName(err), - }); - }; + create_module.libc_installation = LibCInstallation.parse(arena, io, paths_file, target) catch |err| + fatal("unable to parse libc paths file at path {s}: {t}", .{ paths_file, err }); } if (target.os.tag == .windows and (target.abi == .msvc or target.abi == .itanium) and @@ -4024,7 +4014,7 @@ fn createModule( .verbose = true, .target = target, }) catch |err| { - fatal("unable to find native libc installation: {s}", .{@errorName(err)}); + fatal("unable to find native libc installation: {t}", .{err}); }; } try create_module.lib_directories.ensureUnusedCapacity(arena, 2); @@ -4163,7 +4153,7 @@ fn serve( var child_pid: ?std.process.Child.Id = null; - const main_progress_node = std.Progress.start(.{}); + const main_progress_node = std.Progress.start(io, .{}); const file_system_inputs = comp.file_system_inputs.?; const IncrementalDebugServer = if (build_options.enable_debug_extensions and !builtin.single_threaded) @@ -4694,7 +4684,7 @@ fn cmdTranslateC( }); }; defer zig_file.close(io); - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); var file_reader = zig_file.reader(io, &.{}); _ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited); try stdout_writer.interface.flush(); @@ -4744,7 +4734,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! if (mem.eql(u8, arg, "-m") or mem.eql(u8, arg, "--minimal")) { template = .minimal; } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try Io.File.stdout().writeAll(usage_init); + try Io.File.stdout().writeStreamingAll(io, usage_init); return cleanExit(); } else { fatal("unrecognized parameter: '{s}'", .{arg}); @@ -4764,7 +4754,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! switch (template) { .example => { var templates = findTemplates(gpa, arena, io); - defer templates.deinit(); + defer templates.deinit(io); const s = fs.path.sep_str; const template_paths = [_][]const u8{ @@ -4898,7 +4888,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) const argv_index_exe = child_argv.items.len; _ = try child_argv.addOne(); - const self_exe_path = try fs.selfExePathAlloc(arena); + const self_exe_path = try process.executablePathAlloc(io, arena); try child_argv.append(self_exe_path); const argv_index_zig_lib_dir = child_argv.items.len; @@ -5079,7 +5069,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) const work_around_btrfs_bug = native_os == .linux and EnvVar.ZIG_BTRFS_WORKAROUND.isSet(); - const root_prog_node = std.Progress.start(.{ + const root_prog_node = std.Progress.start(io, .{ .disable_printing = (color == .off), .root_name = "Compile Build Script", }); @@ -5114,7 +5104,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) const paths_file = debug_libc_paths_file orelse break :lci null; if (!build_options.enable_debug_extensions) unreachable; const lci = try arena.create(LibCInstallation); - lci.* = try .parse(arena, paths_file, &resolved_target.result); + lci.* = try .parse(arena, io, paths_file, &resolved_target.result); break :lci lci; }; @@ -5129,6 +5119,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) // This `init` calls `fatal` on error. var dirs: Compilation.Directories = .init( arena, + io, override_lib_dir, override_global_cache_dir, .{ .override = path: { @@ -5138,7 +5129,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) {}, self_exe_path, ); - defer dirs.deinit(); + defer dirs.deinit(io); child_argv.items[argv_index_zig_lib_dir] = dirs.zig_lib.path orelse cwd_path; child_argv.items[argv_index_build_file] = build_root.directory.path orelse cwd_path; @@ -5421,11 +5412,10 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) child.stderr_behavior = .Inherit; const term = t: { - std.debug.lockStdErr(); - defer std.debug.unlockStdErr(); - break :t child.spawnAndWait(io) catch |err| { + _ = std.debug.lockStderrWriter(&.{}); + defer std.debug.unlockStderrWriter(); + break :t child.spawnAndWait(io) catch |err| fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err }); - }; }; switch (term) { @@ -5517,7 +5507,7 @@ fn jitCmd( dev.check(.jit_command); const color: Color = .auto; - const root_prog_node = if (options.progress_node) |node| node else std.Progress.start(.{ + const root_prog_node = if (options.progress_node) |node| node else std.Progress.start(io, .{ .disable_printing = (color == .off), }); @@ -5529,9 +5519,8 @@ fn jitCmd( .is_explicit_dynamic_linker = false, }; - const self_exe_path = fs.selfExePathAlloc(arena) catch |err| { - fatal("unable to find self exe path: {s}", .{@errorName(err)}); - }; + const self_exe_path = process.executablePathAlloc(io, arena) catch |err| + fatal("unable to find self exe path: {t}", .{err}); const optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet()) .Debug @@ -5544,13 +5533,14 @@ fn jitCmd( // This `init` calls `fatal` on error. var dirs: Compilation.Directories = .init( arena, + io, override_lib_dir, override_global_cache_dir, .global, if (native_os == .wasi) wasi_preopens, self_exe_path, ); - defer dirs.deinit(); + defer dirs.deinit(io); const thread_limit = @min( @max(std.Thread.getCpuCount() catch 1, 1), @@ -5629,7 +5619,7 @@ fn jitCmd( defer comp.destroy(); if (options.server) { - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); var server: std.zig.Server = .{ .out = &stdout_writer.interface, .in = undefined, // won't be receiving messages @@ -5696,7 +5686,7 @@ fn jitCmd( ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); } - const term = try child.wait(); + const term = try child.wait(io); switch (term) { .Exited => |code| { if (code == 0) { @@ -6160,7 +6150,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try Io.File.stdout().writeAll(usage_ast_check); + try Io.File.stdout().writeStreamingAll(io, usage_ast_check); return cleanExit(); } else if (mem.eql(u8, arg, "-t")) { want_output_text = true; @@ -6211,7 +6201,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { const tree = try Ast.parse(arena, source, mode); - var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout_bw = &stdout_writer.interface; switch (mode) { .zig => { @@ -6334,7 +6324,7 @@ fn cmdDetectCpu(io: Io, args: []const []const u8) !void { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try Io.File.stdout().writeAll(detect_cpu_usage); + try Io.File.stdout().writeStreamingAll(io, detect_cpu_usage); return cleanExit(); } else if (mem.eql(u8, arg, "--llvm")) { use_llvm = true; @@ -6355,10 +6345,10 @@ fn cmdDetectCpu(io: Io, args: []const []const u8) !void { const name = llvm.GetHostCPUName() orelse fatal("LLVM could not figure out the host cpu name", .{}); const features = llvm.GetHostCPUFeatures() orelse fatal("LLVM could not figure out the host cpu feature set", .{}); const cpu = try detectNativeCpuWithLLVM(builtin.cpu.arch, name, features); - try printCpu(cpu); + try printCpu(io, cpu); } else { const host_target = std.zig.resolveTargetQueryOrFatal(io, .{}); - try printCpu(host_target.cpu); + try printCpu(io, host_target.cpu); } } @@ -6425,8 +6415,8 @@ fn detectNativeCpuWithLLVM( return result; } -fn printCpu(cpu: std.Target.Cpu) !void { - var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); +fn printCpu(io: Io, cpu: std.Target.Cpu) !void { + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout_bw = &stdout_writer.interface; if (cpu.model.llvm_name) |llvm_name| { @@ -6448,6 +6438,7 @@ fn printCpu(cpu: std.Target.Cpu) !void { fn cmdDumpLlvmInts( gpa: Allocator, arena: Allocator, + io: Io, args: []const []const u8, ) !void { dev.check(.llvm_ints_command); @@ -6475,7 +6466,7 @@ fn cmdDumpLlvmInts( const dl = tm.createTargetDataLayout(); const context = llvm.Context.create(); - var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout_bw = &stdout_writer.interface; for ([_]u16{ 1, 8, 16, 32, 64, 128, 256 }) |bits| { const int_type = context.intType(bits); @@ -6501,7 +6492,7 @@ fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void { defer f.close(io); const zir = try Zcu.loadZirCache(arena, io, f); - var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout_bw = &stdout_writer.interface; { const instruction_bytes = zir.instructions.len * @@ -6585,7 +6576,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .empty; try Zcu.mapOldZirToNew(arena, old_zir, new_zir, &inst_map); - var stdout_writer = Io.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout_bw = &stdout_writer.interface; { try stdout_bw.print("Instruction mappings:\n", .{}); @@ -6917,7 +6908,7 @@ fn cmdFetch( const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try Io.File.stdout().writeAll(usage_fetch); + try Io.File.stdout().writeStreamingAll(io, usage_fetch); return cleanExit(); } else if (mem.eql(u8, arg, "--global-cache-dir")) { if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); @@ -6951,7 +6942,7 @@ fn cmdFetch( try http_client.initDefaultProxies(arena); - var root_prog_node = std.Progress.start(.{ + var root_prog_node = std.Progress.start(io, .{ .root_name = "Fetch", }); defer root_prog_node.end(); @@ -6959,7 +6950,7 @@ fn cmdFetch( var global_cache_directory: Directory = l: { const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena); break :l .{ - .handle = try Io.Dir.cwd().makeOpenPath(p, .{}), + .handle = try Io.Dir.cwd().makeOpenPath(io, p, .{}), .path = p, }; }; @@ -7026,7 +7017,7 @@ fn cmdFetch( const name = switch (save) { .no => { - var stdout = Io.File.stdout().writerStreaming(&stdout_buffer); + var stdout = Io.File.stdout().writerStreaming(io, &stdout_buffer); try stdout.interface.print("{s}\n", .{package_hash_slice}); try stdout.interface.flush(); return cleanExit(); @@ -7044,7 +7035,7 @@ fn cmdFetch( var build_root = try findBuildRoot(arena, io, .{ .cwd_path = cwd_path, }); - defer build_root.deinit(); + defer build_root.deinit(io); // The name to use in case the manifest file needs to be created now. const init_root_name = fs.path.basename(build_root.directory.path orelse cwd_path); @@ -7205,7 +7196,7 @@ fn createDependenciesModule( const rand_int = std.crypto.random.int(u64); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); { - var tmp_dir = try dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}); + var tmp_dir = try dirs.local_cache.handle.makeOpenPath(io, tmp_dir_sub_path, .{}); defer tmp_dir.close(io); try tmp_dir.writeFile(io, .{ .sub_path = basename, .data = source }); } @@ -7446,28 +7437,28 @@ fn writeSimpleTemplateFile(io: Io, file_name: []const u8, comptime fmt: []const const f = try Io.Dir.cwd().createFile(io, file_name, .{ .exclusive = true }); defer f.close(io); var buf: [4096]u8 = undefined; - var fw = f.writer(&buf); + var fw = f.writer(io, &buf); try fw.interface.print(fmt, args); try fw.interface.flush(); } fn findTemplates(gpa: Allocator, arena: Allocator, io: Io) Templates { const cwd_path = introspect.getResolvedCwd(arena) catch |err| { - fatal("unable to get cwd: {s}", .{@errorName(err)}); + fatal("unable to get cwd: {t}", .{err}); }; - const self_exe_path = fs.selfExePathAlloc(arena) catch |err| { - fatal("unable to find self exe path: {s}", .{@errorName(err)}); + const self_exe_path = process.executablePathAlloc(io, arena) catch |err| { + fatal("unable to find self exe path: {t}", .{err}); }; var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, io, cwd_path, self_exe_path) catch |err| { - fatal("unable to find zig installation directory '{s}': {s}", .{ self_exe_path, @errorName(err) }); + fatal("unable to find zig installation directory '{s}': {t}", .{ self_exe_path, err }); }; const s = fs.path.sep_str; const template_sub_path = "init"; const template_dir = zig_lib_directory.handle.openDir(io, template_sub_path, .{}) catch |err| { const path = zig_lib_directory.path orelse "."; - fatal("unable to open zig project template directory '{s}{s}{s}': {s}", .{ - path, s, template_sub_path, @errorName(err), + fatal("unable to open zig project template directory '{s}{s}{s}': {t}", .{ + path, s, template_sub_path, err, }); }; diff --git a/src/print_env.zig b/src/print_env.zig index e1847688adf9dfa0d3b84ff2642eb4a337983bc7..3540a58d188605726c41338ccffeb4a6bc382ca1 100644 --- a/src/print_env.zig +++ b/src/print_env.zig @@ -1,13 +1,17 @@ -const std = @import("std"); const builtin = @import("builtin"); -const build_options = @import("build_options"); -const Compilation = @import("Compilation.zig"); + +const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; const EnvVar = std.zig.EnvVar; const fatal = std.process.fatal; +const build_options = @import("build_options"); +const Compilation = @import("Compilation.zig"); + pub fn cmdEnv( arena: Allocator, + io: Io, out: *std.Io.Writer, args: []const []const u8, wasi_preopens: switch (builtin.target.os.tag) { @@ -21,20 +25,21 @@ pub fn cmdEnv( const self_exe_path = switch (builtin.target.os.tag) { .wasi => args[0], - else => std.fs.selfExePathAlloc(arena) catch |err| { - fatal("unable to find zig self exe path: {s}", .{@errorName(err)}); + else => std.process.executablePathAlloc(io, arena) catch |err| { + fatal("unable to find zig self exe path: {t}", .{err}); }, }; var dirs: Compilation.Directories = .init( arena, + io, override_lib_dir, override_global_cache_dir, .global, if (builtin.target.os.tag == .wasi) wasi_preopens, if (builtin.target.os.tag != .wasi) self_exe_path, ); - defer dirs.deinit(); + defer dirs.deinit(io); const zig_lib_dir = dirs.zig_lib.path orelse ""; const zig_std_dir = try dirs.zig_lib.join(arena, &.{"std"}); diff --git a/src/print_targets.zig b/src/print_targets.zig index 2f80187de1931a0678c29ad8cd2ce553bb1f1863..a5e89506ad0224cf95a462435e4d91295d7665aa 100644 --- a/src/print_targets.zig +++ b/src/print_targets.zig @@ -1,14 +1,16 @@ const std = @import("std"); +const Io = std.Io; const fs = std.fs; const mem = std.mem; const meta = std.meta; const fatal = std.process.fatal; const Allocator = std.mem.Allocator; const Target = std.Target; -const target = @import("target.zig"); const assert = std.debug.assert; + const glibc = @import("libs/glibc.zig"); const introspect = @import("introspect.zig"); +const target = @import("target.zig"); pub fn cmdTargets( allocator: Allocator, @@ -18,19 +20,19 @@ pub fn cmdTargets( native_target: *const Target, ) !void { _ = args; - var zig_lib_directory = introspect.findZigLibDir(allocator) catch |err| { - fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)}); - }; + var zig_lib_directory = introspect.findZigLibDir(allocator, io) catch |err| + fatal("unable to find zig installation directory: {t}", .{err}); defer zig_lib_directory.handle.close(io); defer allocator.free(zig_lib_directory.path.?); const abilists_contents = zig_lib_directory.handle.readFileAlloc( + io, glibc.abilists_path, allocator, .limited(glibc.abilists_max_size), ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - else => fatal("unable to read " ++ glibc.abilists_path ++ ": {s}", .{@errorName(err)}), + else => fatal("unable to read " ++ glibc.abilists_path ++ ": {t}", .{err}), }; defer allocator.free(abilists_contents); @@ -49,9 +51,7 @@ pub fn cmdTargets( { var libc_obj = try root_obj.beginTupleField("libc", .{}); for (std.zig.target.available_libcs) |libc| { - const tmp = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ - @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi), - }); + const tmp = try std.fmt.allocPrint(allocator, "{t}-{t}-{t}", .{ libc.arch, libc.os, libc.abi }); defer allocator.free(tmp); try libc_obj.field(tmp, .{}); } diff --git a/test/standalone/self_exe_symlink/main.zig b/test/standalone/self_exe_symlink/main.zig index 4df5b027203a12c0ecf9af8a28455134d46077f4..dfe3acb7a0b8f99ee0ce0f2f79aa66ad6fee196f 100644 --- a/test/standalone/self_exe_symlink/main.zig +++ b/test/standalone/self_exe_symlink/main.zig @@ -9,7 +9,7 @@ pub fn main() !void { defer threaded.deinit(); const io = threaded.io(); - const self_path = try std.fs.selfExePathAlloc(gpa); + const self_path = try std.process.executablePathAlloc(io, gpa); defer gpa.free(self_path); var self_exe = try std.fs.openSelfExe(.{}); -- 2.54.0 From 1ed845e1f6fabdde8795513de2d06bc9b573779c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 11 Dec 2025 23:41:57 -0800 Subject: [PATCH 050/195] update occurences of setEndPos to setLength --- lib/compiler/objcopy.zig | 3 +- lib/std/Io/test.zig | 68 +++++++++++++++++++++++++++++---- lib/std/fs/test.zig | 51 ------------------------- src/Zcu/PerThread.zig | 2 +- src/link/C.zig | 4 +- src/link/Elf.zig | 13 +++++-- src/link/Elf/relocatable.zig | 3 +- src/link/MachO/DebugSymbols.zig | 6 ++- src/link/MachO/relocatable.zig | 3 +- src/link/MappedFile.zig | 5 +++ src/link/Wasm.zig | 4 +- 11 files changed, 89 insertions(+), 73 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index d9b9b331861d37bebbe70b440627de3b29a6bc4d..90fd122294ea7d5c935b8b48e651271376dd1662 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -676,8 +676,9 @@ fn containsValidAddressRange(segments: []*BinaryElfSegment) bool { } fn padFile(out: *File.Writer, opt_size: ?u64) !void { + const io = out.io; const size = opt_size orelse return; - try out.file.setEndPos(size); + try out.file.setLength(io, size); } test "HexWriter.Record.Address has correct payload and checksum" { diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 891c751ef21ad0abe34f7ca1257c27444070cba6..8f32555b52e96b22c2a8ee630feb4b3db18fb64b 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -3,16 +3,17 @@ const native_endian = builtin.cpu.arch.endian(); const std = @import("std"); const Io = std.Io; -const testing = std.testing; -const expect = std.testing.expect; -const expectEqual = std.testing.expectEqual; -const expectError = std.testing.expectError; const DefaultPrng = std.Random.DefaultPrng; const mem = std.mem; const fs = std.fs; const File = std.Io.File; const assert = std.debug.assert; +const testing = std.testing; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectError = std.testing.expectError; +const expectEqualStrings = std.testing.expectEqualStrings; const tmpDir = std.testing.tmpDir; test "write a file, read it, then delete it" { @@ -89,7 +90,7 @@ test "File seek ops" { try expect((try file.getPos()) == 1234); } -test "setEndPos" { +test "setLength" { const io = testing.io; var tmp = tmpDir(.{}); @@ -102,18 +103,69 @@ test "setEndPos" { // Verify that the file size changes and the file offset is not moved try expect((try file.length(io)) == 0); try expect((try file.getPos()) == 0); - try file.setEndPos(8192); + try file.setLength(io, 8192); try expect((try file.length(io)) == 8192); try expect((try file.getPos()) == 0); try file.seekTo(100); - try file.setEndPos(4096); + try file.setLength(io, 4096); try expect((try file.length(io)) == 4096); try expect((try file.getPos()) == 100); - try file.setEndPos(0); + try file.setLength(io, 0); try expect((try file.length(io)) == 0); try expect((try file.getPos()) == 100); } +test "legacy setLength" { + // https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission) + if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; + if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23806 + + const io = testing.io; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + const file_name = "afile.txt"; + try tmp.dir.writeFile(io, .{ .sub_path = file_name, .data = "ninebytes" }); + const f = try tmp.dir.openFile(io, file_name, .{ .mode = .read_write }); + defer f.close(io); + + const initial_size = try f.length(io); + var buffer: [32]u8 = undefined; + var reader = f.reader(io, &.{}); + + { + try f.setLength(io, initial_size); + try expectEqual(initial_size, try f.length(io)); + try reader.seekTo(0); + try expectEqual(initial_size, try reader.interface.readSliceShort(&buffer)); + try expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]); + } + + { + const larger = initial_size + 4; + try f.setLength(io, larger); + try expectEqual(larger, try f.length(io)); + try reader.seekTo(0); + try expectEqual(larger, try reader.interface.readSliceShort(&buffer)); + try expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]); + } + + { + const smaller = initial_size - 5; + try f.setLength(io, smaller); + try expectEqual(smaller, try f.length(io)); + try reader.seekTo(0); + try expectEqual(smaller, try reader.interface.readSliceShort(&buffer)); + try expectEqualStrings("nine", buffer[0..@intCast(smaller)]); + } + + try f.setLength(io, 0); + try expectEqual(0, try f.length(io)); + try reader.seekTo(0); + try expectEqual(0, try reader.interface.readSliceShort(&buffer)); +} + test "setTimestamps" { const io = testing.io; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 1a8e5adfbd56fa9aa6e63f12cc7621705fb186ad..0f4707c6cd87c0e674688c883f0d008702bd7485 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1451,57 +1451,6 @@ test "pwritev, preadv" { try expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1)); } -test "setEndPos" { - // https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission) - if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23806 - - const io = testing.io; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - const file_name = "afile.txt"; - try tmp.dir.writeFile(io, .{ .sub_path = file_name, .data = "ninebytes" }); - const f = try tmp.dir.openFile(io, file_name, .{ .mode = .read_write }); - defer f.close(io); - - const initial_size = try f.length(io); - var buffer: [32]u8 = undefined; - var reader = f.reader(io, &.{}); - - { - try f.setEndPos(initial_size); - try expectEqual(initial_size, try f.length(io)); - try reader.seekTo(0); - try expectEqual(initial_size, try reader.interface.readSliceShort(&buffer)); - try expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]); - } - - { - const larger = initial_size + 4; - try f.setEndPos(larger); - try expectEqual(larger, try f.length(io)); - try reader.seekTo(0); - try expectEqual(larger, try reader.interface.readSliceShort(&buffer)); - try expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]); - } - - { - const smaller = initial_size - 5; - try f.setEndPos(smaller); - try expectEqual(smaller, try f.length(io)); - try reader.seekTo(0); - try expectEqual(smaller, try reader.interface.readSliceShort(&buffer)); - try expectEqualStrings("nine", buffer[0..@intCast(smaller)]); - } - - try f.setEndPos(0); - try expectEqual(0, try f.length(io)); - try reader.seekTo(0); - try expectEqual(0, try reader.interface.readSliceShort(&buffer)); -} - test "access file" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 9d6a45ad2672c24bf4821bcc5becbafe4e03dbc6..2333d7f286c6db98ae09c945a3b4cdfc7a5477fc 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -245,7 +245,7 @@ pub fn updateFile( if (need_update) { // The cache is definitely stale so delete the contents to avoid an underwrite later. - cache_file.setEndPos(0) catch |err| switch (err) { + cache_file.setLength(io, 0) catch |err| switch (err) { error.FileTooBig => unreachable, // 0 is not too big else => |e| return e, }; diff --git a/src/link/C.zig b/src/link/C.zig index db5acebf07e9f8b94c4a3b13148dbad07bc3dcc5..93e771ebfc3d01a61f7fb5268bd84fe8b639f489 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -509,7 +509,7 @@ pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.P }, self.getString(av_block.code)); const file = self.base.file.?; - file.setEndPos(f.file_size) catch |err| return diags.fail("failed to allocate file: {s}", .{@errorName(err)}); + file.setLength(io, f.file_size) catch |err| return diags.fail("failed to allocate file: {t}", .{err}); var fw = file.writer(io, &.{}); var w = &fw.interface; w.writeVecAll(f.all_buffers.items) catch |err| switch (err) { @@ -800,7 +800,7 @@ pub fn flushEmitH(zcu: *Zcu) !void { }); defer file.close(io); - try file.setEndPos(file_size); + try file.setLength(io, file_size); try file.pwritevAll(all_buffers.items, 0); } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 2916f5ee2598f72749185d21b5282aca08a57e7b..11bf4489579f31e74ab48b9021778e7af4aebf7e 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -487,6 +487,8 @@ pub fn getUavVAddr(self: *Elf, uav: InternPool.Index, reloc_info: link.File.Relo /// Returns end pos of collision, if any. fn detectAllocCollision(self: *Elf, start: u64, size: u64) !?u64 { + const comp = self.base.comp; + const io = comp.io; const small_ptr = self.ptr_width == .p32; const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); if (start < ehdr_size) @@ -526,7 +528,7 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) !?u64 { } } - if (at_end) try self.base.file.?.setEndPos(end); + if (at_end) try self.base.file.?.setLength(io, end); return null; } @@ -556,6 +558,8 @@ pub fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u64) !u64 { } pub fn growSection(self: *Elf, shdr_index: u32, needed_size: u64, min_alignment: u64) !void { + const comp = self.base.comp; + const io = comp.io; const shdr = &self.sections.items(.shdr)[shdr_index]; if (shdr.sh_type != elf.SHT_NOBITS) { @@ -589,7 +593,7 @@ pub fn growSection(self: *Elf, shdr_index: u32, needed_size: u64, min_alignment: shdr.sh_offset = new_offset; } else if (shdr.sh_offset + allocated_size == std.math.maxInt(u64)) { - try self.base.file.?.setEndPos(shdr.sh_offset + needed_size); + try self.base.file.?.setLength(io, shdr.sh_offset + needed_size); } } @@ -4446,10 +4450,11 @@ pub fn pwriteAll(elf_file: *Elf, bytes: []const u8, offset: u64) error{LinkFailu }; } -pub fn setEndPos(elf_file: *Elf, length: u64) error{LinkFailure}!void { +pub fn setLength(elf_file: *Elf, length: u64) error{LinkFailure}!void { const comp = elf_file.base.comp; + const io = comp.i; const diags = &comp.link_diags; - elf_file.base.file.?.setEndPos(length) catch |err| { + elf_file.base.file.?.setLength(io, length) catch |err| { return diags.fail("failed to set file end pos: {s}", .{@errorName(err)}); }; } diff --git a/src/link/Elf/relocatable.zig b/src/link/Elf/relocatable.zig index 7adeecdcdedbad72e3a6f0567bd3c596a37e9abb..0627d8586fdbb52fc3bb6cfd8260a1edb79d29e2 100644 --- a/src/link/Elf/relocatable.zig +++ b/src/link/Elf/relocatable.zig @@ -1,5 +1,6 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void { const gpa = comp.gpa; + const io = comp.io; const diags = &comp.link_diags; if (diags.hasErrors()) return error.LinkFailure; @@ -125,7 +126,7 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void { assert(writer.buffered().len == total_size); - try elf_file.base.file.?.setEndPos(total_size); + try elf_file.base.file.?.setLength(io, total_size); try elf_file.base.file.?.pwriteAll(writer.buffered(), 0); if (diags.hasErrors()) return error.LinkFailure; diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 8a97f2844f2c41efc819bd8d6ce0760870414224..1ec19ecea79c01cf6e2ffdb92b32fde600dea046 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -125,6 +125,7 @@ pub fn growSection( requires_file_copy: bool, macho_file: *MachO, ) !void { + const io = self.io; const sect = self.getSectionPtr(sect_index); const allocated_size = self.allocatedSize(sect.offset); @@ -152,7 +153,7 @@ pub fn growSection( sect.offset = @intCast(new_offset); } else if (sect.offset + allocated_size == std.math.maxInt(u64)) { - try self.file.?.setEndPos(sect.offset + needed_size); + try self.file.?.setLength(io, sect.offset + needed_size); } sect.size = needed_size; @@ -176,6 +177,7 @@ pub fn markDirty(self: *DebugSymbols, sect_index: u8, macho_file: *MachO) void { } fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 { + const io = self.io; var at_end = true; const end = start + padToIdeal(size); @@ -188,7 +190,7 @@ fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 { } } - if (at_end) try self.file.?.setEndPos(end); + if (at_end) try self.file.?.setLength(io, end); return null; } diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index e9f78a8ef2fd6d04d652332cab95049c82c6a293..6bddfc63897583d1c838e244f4a4fbd375fcda21 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -80,6 +80,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void { const gpa = comp.gpa; + const io = comp.io; const diags = &macho_file.base.comp.link_diags; var positionals = std.array_list.Managed(link.Input).init(gpa); @@ -230,7 +231,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? assert(writer.end == total_size); - try macho_file.setEndPos(total_size); + try macho_file.setLength(io, total_size); try macho_file.pwriteAll(writer.buffered(), 0); if (diags.hasErrors()) return error.LinkFailure; diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 974b03ef7f753b3e77fb275a974d55342d08a95b..06767b3709bf53ee6e662734a0fc15803c6c2c21 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -35,6 +35,11 @@ pub const Error = std.posix.MMapError || std.posix.MRemapError || Io.File.Length IsDir, Unseekable, NoSpaceLeft, + + InputOutput, + FileTooBig, + FileBusy, + NonResizable, }; pub fn init(file: std.Io.File, gpa: std.mem.Allocator, io: Io) !MappedFile { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 489fb8e6fea1409a6417a9de22656ae6db331554..39e3bb3d5d4def35c3288e3dc7edfd5eaac69cab 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3018,12 +3018,12 @@ pub fn createEmpty( fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void { const diags = &wasm.base.comp.link_diags; const obj = link.openObject(path, false, false) catch |err| { - switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) { + switch (diags.failParse(path, "failed to open object: {t}", .{err})) { error.LinkFailure => return, } }; wasm.parseObject(obj) catch |err| { - switch (diags.failParse(path, "failed to parse object: {s}", .{@errorName(err)})) { + switch (diags.failParse(path, "failed to parse object: {t}", .{err})) { error.LinkFailure => return, } }; -- 2.54.0 From 6484101f7860988d2147b519c0c53b9fdc23b7c2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 12 Dec 2025 13:12:56 -0800 Subject: [PATCH 051/195] update occurences of std.fs.openDirAbsolute --- lib/compiler/aro/aro/Diagnostics.zig | 2 +- lib/std/crypto/Certificate/Bundle.zig | 6 +- lib/std/fs/test.zig | 12 +- lib/std/zig/WindowsSdk.zig | 272 +++++++++++++------------- 4 files changed, 146 insertions(+), 146 deletions(-) diff --git a/lib/compiler/aro/aro/Diagnostics.zig b/lib/compiler/aro/aro/Diagnostics.zig index 2d58a217ea8af9a02e8a113f9e38f251b8c17a4e..9cbc134f021941d35234d54f729b28b10cee425e 100644 --- a/lib/compiler/aro/aro/Diagnostics.zig +++ b/lib/compiler/aro/aro/Diagnostics.zig @@ -292,7 +292,7 @@ const Diagnostics = @This(); output: union(enum) { to_writer: struct { writer: *std.Io.Writer, - color: std.Io.tty.Config, + color: std.Io.File.Writer.Mode, }, to_list: struct { messages: std.ArrayList(Message) = .empty, diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index 386e1b6b5a2492db2b772bca3ef015c6c01eeab5..a4af82b2f269a7b48bbd4ae3fc89b687d14d58c7 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -9,8 +9,8 @@ const builtin = @import("builtin"); const std = @import("../../std.zig"); const Io = std.Io; +const Dir = std.Io.Dir; const assert = std.debug.assert; -const fs = std.fs; const mem = std.mem; const crypto = std.crypto; const Allocator = std.mem.Allocator; @@ -192,8 +192,8 @@ pub fn addCertsFromDirPathAbsolute( now: Io.Timestamp, abs_dir_path: []const u8, ) AddCertsFromDirPathError!void { - assert(fs.path.isAbsolute(abs_dir_path)); - var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true }); + assert(Dir.path.isAbsolute(abs_dir_path)); + var iterable_dir = try Dir.openDirAbsolute(io, abs_dir_path, .{ .iterate = true }); defer iterable_dir.close(io); return addCertsFromDir(cb, gpa, io, now, iterable_dir); } diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 0f4707c6cd87c0e674688c883f0d008702bd7485..fa3eaed3fa9515badc978106420307df1890048e 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -346,7 +346,7 @@ test "openDirAbsolute" { defer testing.allocator.free(sub_path); // Can open sub_path - var tmp_sub = try fs.openDirAbsolute(sub_path, .{}); + var tmp_sub = try Dir.openDirAbsolute(io, sub_path, .{}); defer tmp_sub.close(io); const sub_ino = (try tmp_sub.stat(io)).inode; @@ -356,7 +356,7 @@ test "openDirAbsolute" { const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".." }); defer testing.allocator.free(dir_path); - var dir = try fs.openDirAbsolute(dir_path, .{}); + var dir = try Dir.openDirAbsolute(io, dir_path, .{}); defer dir.close(io); const ino = (try dir.stat(io)).inode; @@ -368,7 +368,7 @@ test "openDirAbsolute" { const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, "." }); defer testing.allocator.free(dir_path); - var dir = try fs.openDirAbsolute(dir_path, .{}); + var dir = try Dir.openDirAbsolute(io, dir_path, .{}); defer dir.close(io); const ino = (try dir.stat(io)).inode; @@ -380,7 +380,7 @@ test "openDirAbsolute" { const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".", "..", "." }); defer testing.allocator.free(dir_path); - var dir = try fs.openDirAbsolute(dir_path, .{}); + var dir = try Dir.openDirAbsolute(io, dir_path, .{}); defer dir.close(io); const ino = (try dir.stat(io)).inode; @@ -1978,7 +1978,7 @@ test "'.' and '..' in absolute functions" { const subdir_path = try fs.path.join(allocator, &.{ base_path, "./subdir" }); try fs.makeDirAbsolute(subdir_path); try fs.accessAbsolute(subdir_path, .{}); - var created_subdir = try fs.openDirAbsolute(subdir_path, .{}); + var created_subdir = try Dir.openDirAbsolute(io, subdir_path, .{}); created_subdir.close(io); const created_file_path = try fs.path.join(allocator, &.{ subdir_path, "../file" }); @@ -2108,7 +2108,7 @@ test "invalid UTF-8/WTF-8 paths" { try expectError(expected_err, Dir.makeDirAbsolute(invalid_path)); try expectError(expected_err, Dir.deleteDirAbsolute(invalid_path)); try expectError(expected_err, Dir.renameAbsolute(invalid_path, invalid_path)); - try expectError(expected_err, Dir.openDirAbsolute(invalid_path, .{})); + try expectError(expected_err, Dir.openDirAbsolute(io, invalid_path, .{})); try expectError(expected_err, Dir.openFileAbsolute(invalid_path, .{})); try expectError(expected_err, Dir.accessAbsolute(invalid_path, .{})); try expectError(expected_err, Dir.createFileAbsolute(invalid_path, .{})); diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index 6da25822ca516cf78f76a1051ecd75df806028ab..55680707936d2f33414b4d1d3423dec4117af693 100644 --- a/lib/std/zig/WindowsSdk.zig +++ b/lib/std/zig/WindowsSdk.zig @@ -3,6 +3,7 @@ const builtin = @import("builtin"); const std = @import("std"); const Io = std.Io; +const Dir = std.Io.Dir; const Writer = std.Io.Writer; const Allocator = std.mem.Allocator; @@ -22,8 +23,8 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len /// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory. /// Caller owns the result's fields. -/// After finishing work, call `free(allocator)`. -pub fn find(allocator: Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { +/// Returns memory allocated by `gpa` +pub fn find(gpa: Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { if (builtin.os.tag != .windows) return error.NotFound; //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed @@ -32,27 +33,27 @@ pub fn find(allocator: Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, }; defer roots_key.closeKey(); - const windows10sdk = Installation.find(allocator, roots_key, "KitsRoot10", "", "v10.0") catch |err| switch (err) { + const windows10sdk = Installation.find(gpa, roots_key, "KitsRoot10", "", "v10.0") catch |err| switch (err) { error.InstallationNotFound => null, error.PathTooLong => null, error.VersionTooLong => null, error.OutOfMemory => return error.OutOfMemory, }; - errdefer if (windows10sdk) |*w| w.free(allocator); + errdefer if (windows10sdk) |*w| w.free(gpa); - const windows81sdk = Installation.find(allocator, roots_key, "KitsRoot81", "winver", "v8.1") catch |err| switch (err) { + const windows81sdk = Installation.find(gpa, roots_key, "KitsRoot81", "winver", "v8.1") catch |err| switch (err) { error.InstallationNotFound => null, error.PathTooLong => null, error.VersionTooLong => null, error.OutOfMemory => return error.OutOfMemory, }; - errdefer if (windows81sdk) |*w| w.free(allocator); + errdefer if (windows81sdk) |*w| w.free(gpa); - const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(allocator, arch) catch |err| switch (err) { + const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, arch) catch |err| switch (err) { error.MsvcLibDirNotFound => null, error.OutOfMemory => return error.OutOfMemory, }; - errdefer allocator.free(msvc_lib_dir); + errdefer gpa.free(msvc_lib_dir); return .{ .windows10sdk = windows10sdk, @@ -61,15 +62,15 @@ pub fn find(allocator: Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, }; } -pub fn free(sdk: WindowsSdk, allocator: Allocator) void { +pub fn free(sdk: WindowsSdk, gpa: Allocator) void { if (sdk.windows10sdk) |*w10sdk| { - w10sdk.free(allocator); + w10sdk.free(gpa); } if (sdk.windows81sdk) |*w81sdk| { - w81sdk.free(allocator); + w81sdk.free(gpa); } if (sdk.msvc_lib_dir) |msvc_lib_dir| { - allocator.free(msvc_lib_dir); + gpa.free(msvc_lib_dir); } } @@ -77,8 +78,8 @@ pub fn free(sdk: WindowsSdk, allocator: Allocator) void { /// and a version. Returns slice of version strings sorted in descending order. /// Caller owns result. fn iterateAndFilterByVersion( - iterator: *Io.Dir.Iterator, - allocator: Allocator, + iterator: *Dir.Iterator, + gpa: Allocator, prefix: []const u8, ) error{OutOfMemory}![][]const u8 { const Version = struct { @@ -95,11 +96,11 @@ fn iterateAndFilterByVersion( std.mem.order(u8, lhs.build, rhs.build); } }; - var versions = std.array_list.Managed(Version).init(allocator); - var dirs = std.array_list.Managed([]const u8).init(allocator); + var versions = std.array_list.Managed(Version).init(gpa); + var dirs = std.array_list.Managed([]const u8).init(gpa); defer { versions.deinit(); - for (dirs.items) |filtered_dir| allocator.free(filtered_dir); + for (dirs.items) |filtered_dir| gpa.free(filtered_dir); dirs.deinit(); } @@ -119,8 +120,8 @@ fn iterateAndFilterByVersion( num.* = Version.parseNum(num_it.next() orelse break) orelse continue :iterate else if (num_it.next()) |_| continue; - const name = try allocator.dupe(u8, suffix); - errdefer allocator.free(name); + const name = try gpa.dupe(u8, suffix); + errdefer gpa.free(name); if (underscore) |pos| version.build = name[pos + 1 ..]; try versions.append(version); @@ -177,7 +178,7 @@ const RegistryWtf8 = struct { /// Get string from registry. /// Caller owns result. - pub fn getString(reg: RegistryWtf8, allocator: Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 { + pub fn getString(reg: RegistryWtf8, gpa: Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 { const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: { var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable; @@ -193,11 +194,11 @@ const RegistryWtf8 = struct { }; const registry_wtf16le: RegistryWtf16Le = .{ .key = reg.key }; - const value_wtf16le = try registry_wtf16le.getString(allocator, subkey_wtf16le, value_name_wtf16le); - defer allocator.free(value_wtf16le); + const value_wtf16le = try registry_wtf16le.getString(gpa, subkey_wtf16le, value_name_wtf16le); + defer gpa.free(value_wtf16le); - const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_wtf16le); - errdefer allocator.free(value_wtf8); + const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, value_wtf16le); + errdefer gpa.free(value_wtf8); return value_wtf8; } @@ -285,7 +286,7 @@ const RegistryWtf16Le = struct { } /// Get string ([:0]const u16) from registry. - fn getString(reg: RegistryWtf16Le, allocator: Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 { + fn getString(reg: RegistryWtf16Le, gpa: Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 { var actual_type: windows.ULONG = undefined; // Calculating length to allocate @@ -314,8 +315,8 @@ const RegistryWtf16Le = struct { else => return error.NotAString, } - const value_wtf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable); - errdefer allocator.free(value_wtf16le_buf); + const value_wtf16le_buf: []u16 = try gpa.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable); + errdefer gpa.free(value_wtf16le_buf); return_code_int = windows.advapi32.RegGetValueW( reg.key, @@ -349,7 +350,7 @@ const RegistryWtf16Le = struct { break :value_wtf16le std.mem.span(value_wtf16le_overestimated); }; - _ = allocator.resize(value_wtf16le_buf, value_wtf16le.len); + _ = gpa.resize(value_wtf16le_buf, value_wtf16le.len); return value_wtf16le; } @@ -417,66 +418,65 @@ pub const Installation = struct { /// Find path and version of Windows SDK. /// Caller owns the result's fields. - /// After finishing work, call `free(allocator)`. fn find( - allocator: Allocator, + gpa: Allocator, roots_key: RegistryWtf8, roots_subkey: []const u8, prefix: []const u8, version_key_name: []const u8, ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { roots: { - const installation = findFromRoot(allocator, roots_key, roots_subkey, prefix) catch + const installation = findFromRoot(gpa, roots_key, roots_subkey, prefix) catch break :roots; if (installation.isValidVersion()) return installation; - installation.free(allocator); + installation.free(gpa); } { - const installation = try findFromInstallationFolder(allocator, version_key_name); + const installation = try findFromInstallationFolder(gpa, version_key_name); if (installation.isValidVersion()) return installation; - installation.free(allocator); + installation.free(gpa); } return error.InstallationNotFound; } fn findFromRoot( - allocator: Allocator, + gpa: Allocator, io: Io, roots_key: RegistryWtf8, roots_subkey: []const u8, prefix: []const u8, ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { const path = path: { - const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", roots_subkey) catch |err| switch (err) { + const path_maybe_with_trailing_slash = roots_key.getString(gpa, "", roots_subkey) catch |err| switch (err) { error.NotAString => return error.InstallationNotFound, error.ValueNameNotFound => return error.InstallationNotFound, error.StringNotFound => return error.InstallationNotFound, error.OutOfMemory => return error.OutOfMemory, }; - if (path_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) { - allocator.free(path_maybe_with_trailing_slash); + if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) { + gpa.free(path_maybe_with_trailing_slash); return error.PathTooLong; } - var path = std.array_list.Managed(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash); + var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash); errdefer path.deinit(); // String might contain trailing slash, so trim it here if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop(); break :path try path.toOwnedSlice(); }; - errdefer allocator.free(path); + errdefer gpa.free(path); const version = version: { - var buf: [std.fs.max_path_bytes]u8 = undefined; + var buf: [Dir.max_path_bytes]u8 = undefined; const sdk_lib_dir_path = std.fmt.bufPrint(buf[0..], "{s}\\Lib\\", .{path}) catch |err| switch (err) { error.NoSpaceLeft => return error.PathTooLong, }; - if (!std.fs.path.isAbsolute(sdk_lib_dir_path)) return error.InstallationNotFound; + if (!Dir.path.isAbsolute(sdk_lib_dir_path)) return error.InstallationNotFound; // enumerate files in sdk path looking for latest version - var sdk_lib_dir = std.fs.openDirAbsolute(sdk_lib_dir_path, .{ + var sdk_lib_dir = Dir.openDirAbsolute(io, sdk_lib_dir_path, .{ .iterate = true, }) catch |err| switch (err) { error.NameTooLong => return error.PathTooLong, @@ -485,21 +485,21 @@ pub const Installation = struct { defer sdk_lib_dir.close(io); var iterator = sdk_lib_dir.iterate(); - const versions = try iterateAndFilterByVersion(&iterator, allocator, prefix); + const versions = try iterateAndFilterByVersion(&iterator, gpa, prefix); if (versions.len == 0) return error.InstallationNotFound; defer { - for (versions[1..]) |version| allocator.free(version); - allocator.free(versions); + for (versions[1..]) |version| gpa.free(version); + gpa.free(versions); } break :version versions[0]; }; - errdefer allocator.free(version); + errdefer gpa.free(version); return .{ .path = path, .version = version }; } fn findFromInstallationFolder( - allocator: Allocator, + gpa: Allocator, version_key_name: []const u8, ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined; @@ -518,7 +518,7 @@ pub const Installation = struct { defer key.closeKey(); const path: []const u8 = path: { - const path_maybe_with_trailing_slash = key.getString(allocator, "", "InstallationFolder") catch |err| switch (err) { + const path_maybe_with_trailing_slash = key.getString(gpa, "", "InstallationFolder") catch |err| switch (err) { error.NotAString => return error.InstallationNotFound, error.ValueNameNotFound => return error.InstallationNotFound, error.StringNotFound => return error.InstallationNotFound, @@ -526,12 +526,12 @@ pub const Installation = struct { error.OutOfMemory => return error.OutOfMemory, }; - if (path_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) { - allocator.free(path_maybe_with_trailing_slash); + if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) { + gpa.free(path_maybe_with_trailing_slash); return error.PathTooLong; } - var path = std.array_list.Managed(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash); + var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash); errdefer path.deinit(); // String might contain trailing slash, so trim it here @@ -540,12 +540,12 @@ pub const Installation = struct { const path_without_trailing_slash = try path.toOwnedSlice(); break :path path_without_trailing_slash; }; - errdefer allocator.free(path); + errdefer gpa.free(path); const version: []const u8 = version: { // note(dimenus): Microsoft doesn't include the .0 in the ProductVersion key.... - const version_without_0 = key.getString(allocator, "", "ProductVersion") catch |err| switch (err) { + const version_without_0 = key.getString(gpa, "", "ProductVersion") catch |err| switch (err) { error.NotAString => return error.InstallationNotFound, error.ValueNameNotFound => return error.InstallationNotFound, error.StringNotFound => return error.InstallationNotFound, @@ -553,11 +553,11 @@ pub const Installation = struct { error.OutOfMemory => return error.OutOfMemory, }; if (version_without_0.len + ".0".len > product_version_max_length) { - allocator.free(version_without_0); + gpa.free(version_without_0); return error.VersionTooLong; } - var version = std.array_list.Managed(u8).fromOwnedSlice(allocator, version_without_0); + var version = std.array_list.Managed(u8).fromOwnedSlice(gpa, version_without_0); errdefer version.deinit(); try version.appendSlice(".0"); @@ -565,14 +565,14 @@ pub const Installation = struct { const version_with_0 = try version.toOwnedSlice(); break :version version_with_0; }; - errdefer allocator.free(version); + errdefer gpa.free(version); return .{ .path = path, .version = version }; } /// Check whether this version is enumerated in registry. fn isValidVersion(installation: Installation) bool { - var buf: [std.fs.max_path_bytes]u8 = undefined; + var buf: [Dir.max_path_bytes]u8 = undefined; const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ windows_kits_reg_key, installation.version, @@ -601,21 +601,21 @@ pub const Installation = struct { return (reg_value == 1); } - fn free(install: Installation, allocator: Allocator) void { - allocator.free(install.path); - allocator.free(install.version); + fn free(install: Installation, gpa: Allocator) void { + gpa.free(install.path); + gpa.free(install.version); } }; const MsvcLibDir = struct { - fn findInstancesDirViaSetup(allocator: Allocator) error{ OutOfMemory, PathNotFound }!Io.Dir { + fn findInstancesDirViaSetup(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir { const vs_setup_key_path = "SOFTWARE\\Microsoft\\VisualStudio\\Setup"; const vs_setup_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, vs_setup_key_path, .{}) catch |err| switch (err) { error.KeyNotFound => return error.PathNotFound, }; defer vs_setup_key.closeKey(); - const packages_path = vs_setup_key.getString(allocator, "", "CachePath") catch |err| switch (err) { + const packages_path = vs_setup_key.getString(gpa, "", "CachePath") catch |err| switch (err) { error.NotAString, error.ValueNameNotFound, error.StringNotFound, @@ -623,24 +623,24 @@ const MsvcLibDir = struct { error.OutOfMemory => return error.OutOfMemory, }; - defer allocator.free(packages_path); + defer gpa.free(packages_path); - if (!std.fs.path.isAbsolute(packages_path)) return error.PathNotFound; + if (!Dir.path.isAbsolute(packages_path)) return error.PathNotFound; - const instances_path = try std.fs.path.join(allocator, &.{ packages_path, "_Instances" }); - defer allocator.free(instances_path); + const instances_path = try Dir.path.join(gpa, &.{ packages_path, "_Instances" }); + defer gpa.free(instances_path); - return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return error.PathNotFound; + return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound; } - fn findInstancesDirViaCLSID(allocator: Allocator) error{ OutOfMemory, PathNotFound }!Io.Dir { + fn findInstancesDirViaCLSID(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir { const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}"; const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid, .{}) catch |err| switch (err) { error.KeyNotFound => return error.PathNotFound, }; defer setup_config_key.closeKey(); - const dll_path = setup_config_key.getString(allocator, "InprocServer32", "") catch |err| switch (err) { + const dll_path = setup_config_key.getString(gpa, "InprocServer32", "") catch |err| switch (err) { error.NotAString, error.ValueNameNotFound, error.StringNotFound, @@ -648,11 +648,11 @@ const MsvcLibDir = struct { error.OutOfMemory => return error.OutOfMemory, }; - defer allocator.free(dll_path); + defer gpa.free(dll_path); - if (!std.fs.path.isAbsolute(dll_path)) return error.PathNotFound; + if (!Dir.path.isAbsolute(dll_path)) return error.PathNotFound; - var path_it = std.fs.path.componentIterator(dll_path); + var path_it = Dir.path.componentIterator(dll_path); // the .dll filename _ = path_it.last(); const root_path = while (path_it.previous()) |dir_component| { @@ -663,17 +663,17 @@ const MsvcLibDir = struct { return error.PathNotFound; }; - const instances_path = try std.fs.path.join(allocator, &.{ root_path, "Packages", "_Instances" }); - defer allocator.free(instances_path); + const instances_path = try Dir.path.join(gpa, &.{ root_path, "Packages", "_Instances" }); + defer gpa.free(instances_path); - return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return error.PathNotFound; + return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound; } - fn findInstancesDir(allocator: Allocator) error{ OutOfMemory, PathNotFound }!Io.Dir { + fn findInstancesDir(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir { // First, try getting the packages cache path from the registry. // This only seems to exist when the path is different from the default. method1: { - return findInstancesDirViaSetup(allocator) catch |err| switch (err) { + return findInstancesDirViaSetup(gpa) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.PathNotFound => break :method1, }; @@ -681,7 +681,7 @@ const MsvcLibDir = struct { // Otherwise, try to get the path from the .dll that would have been // loaded via COM for SetupConfiguration. method2: { - return findInstancesDirViaCLSID(allocator) catch |err| switch (err) { + return findInstancesDirViaCLSID(gpa, io) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.PathNotFound => break :method2, }; @@ -689,19 +689,19 @@ const MsvcLibDir = struct { // If that can't be found, fall back to manually appending // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA% method3: { - const program_data = std.process.getEnvVarOwned(allocator, "PROGRAMDATA") catch |err| switch (err) { + const program_data = std.process.getEnvVarOwned(gpa, "PROGRAMDATA") catch |err| switch (err) { error.OutOfMemory => |e| return e, error.InvalidWtf8 => unreachable, error.EnvironmentVariableNotFound => break :method3, }; - defer allocator.free(program_data); + defer gpa.free(program_data); - if (!std.fs.path.isAbsolute(program_data)) break :method3; + if (!Dir.path.isAbsolute(program_data)) break :method3; - const instances_path = try std.fs.path.join(allocator, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" }); - defer allocator.free(instances_path); + const instances_path = try Dir.path.join(gpa, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" }); + defer gpa.free(instances_path); - return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch break :method3; + return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch break :method3; } return error.PathNotFound; } @@ -752,17 +752,17 @@ const MsvcLibDir = struct { /// /// The logic in this function is intended to match what ISetupConfiguration does /// under-the-hood, as verified using Procmon. - fn findViaCOM(allocator: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { + fn findViaCOM(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances` // This will contain directories with names of instance IDs like 80a758ca, // which will contain `state.json` files that have the version and // installation directory. - var instances_dir = try findInstancesDir(allocator); + var instances_dir = try findInstancesDir(gpa, io); defer instances_dir.close(io); - var state_subpath_buf: [std.fs.max_name_bytes + 32]u8 = undefined; + var state_subpath_buf: [Dir.max_name_bytes + 32]u8 = undefined; var latest_version_lib_dir: std.ArrayList(u8) = .empty; - errdefer latest_version_lib_dir.deinit(allocator); + errdefer latest_version_lib_dir.deinit(gpa); var latest_version: u64 = 0; var instances_dir_it = instances_dir.iterateAssumeFirstIteration(); @@ -772,13 +772,13 @@ const MsvcLibDir = struct { var writer: Writer = .fixed(&state_subpath_buf); writer.writeAll(entry.name) catch unreachable; - writer.writeByte(std.fs.path.sep) catch unreachable; + writer.writeByte(Dir.path.sep) catch unreachable; writer.writeAll("state.json") catch unreachable; - const json_contents = instances_dir.readFileAlloc(io, writer.buffered(), allocator, .limited(std.math.maxInt(usize))) catch continue; - defer allocator.free(json_contents); + const json_contents = instances_dir.readFileAlloc(io, writer.buffered(), gpa, .limited(std.math.maxInt(usize))) catch continue; + defer gpa.free(json_contents); - var parsed = std.json.parseFromSlice(std.json.Value, allocator, json_contents, .{}) catch continue; + var parsed = std.json.parseFromSlice(std.json.Value, gpa, json_contents, .{}) catch continue; defer parsed.deinit(); if (parsed.value != .object) continue; @@ -795,40 +795,40 @@ const MsvcLibDir = struct { const installation_path = parsed.value.object.get("installationPath") orelse continue; if (installation_path != .string) continue; - const lib_dir_path = libDirFromInstallationPath(allocator, io, installation_path.string, arch) catch |err| switch (err) { + const lib_dir_path = libDirFromInstallationPath(gpa, io, installation_path.string, arch) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.PathNotFound => continue, }; - defer allocator.free(lib_dir_path); + defer gpa.free(lib_dir_path); latest_version_lib_dir.clearRetainingCapacity(); - try latest_version_lib_dir.appendSlice(allocator, lib_dir_path); + try latest_version_lib_dir.appendSlice(gpa, lib_dir_path); latest_version = parsed_version; } if (latest_version_lib_dir.items.len == 0) return error.PathNotFound; - return latest_version_lib_dir.toOwnedSlice(allocator); + return latest_version_lib_dir.toOwnedSlice(gpa); } fn libDirFromInstallationPath( - allocator: Allocator, + gpa: Allocator, io: Io, installation_path: []const u8, arch: std.Target.Cpu.Arch, ) error{ OutOfMemory, PathNotFound }![]const u8 { - var lib_dir_buf = try std.array_list.Managed(u8).initCapacity(allocator, installation_path.len + 64); + var lib_dir_buf = try std.array_list.Managed(u8).initCapacity(gpa, installation_path.len + 64); errdefer lib_dir_buf.deinit(); lib_dir_buf.appendSliceAssumeCapacity(installation_path); - if (!std.fs.path.isSep(lib_dir_buf.getLast())) { + if (!Dir.path.isSep(lib_dir_buf.getLast())) { try lib_dir_buf.append('\\'); } const installation_path_with_trailing_sep_len = lib_dir_buf.items.len; try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); var default_tools_version_buf: [512]u8 = undefined; - const default_tools_version_contents = Io.Dir.cwd().readFile(lib_dir_buf.items, &default_tools_version_buf) catch { + const default_tools_version_contents = Dir.cwd().readFile(lib_dir_buf.items, &default_tools_version_buf) catch { return error.PathNotFound; }; var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n"); @@ -854,64 +854,64 @@ const MsvcLibDir = struct { } // 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 - fn findViaRegistry(allocator: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { + fn findViaRegistry(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { // %localappdata%\Microsoft\VisualStudio\ // %appdata%\Local\Microsoft\VisualStudio\ - const visualstudio_folder_path = std.fs.getAppDataDir(allocator, "Microsoft\\VisualStudio\\") catch return error.PathNotFound; - defer allocator.free(visualstudio_folder_path); + const visualstudio_folder_path = std.fs.getAppDataDir(gpa, "Microsoft\\VisualStudio\\") catch return error.PathNotFound; + defer gpa.free(visualstudio_folder_path); const vs_versions: []const []const u8 = vs_versions: { - if (!std.fs.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound; + if (!Dir.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound; // enumerate folders that contain `privateregistry.bin`, looking for all versions // f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\ - var visualstudio_folder = std.fs.openDirAbsolute(visualstudio_folder_path, .{ + var visualstudio_folder = Dir.openDirAbsolute(io, visualstudio_folder_path, .{ .iterate = true, }) catch return error.PathNotFound; defer visualstudio_folder.close(io); var iterator = visualstudio_folder.iterate(); - break :vs_versions try iterateAndFilterByVersion(&iterator, allocator, ""); + break :vs_versions try iterateAndFilterByVersion(&iterator, gpa, ""); }; defer { - for (vs_versions) |vs_version| allocator.free(vs_version); - allocator.free(vs_versions); + for (vs_versions) |vs_version| gpa.free(vs_version); + gpa.free(vs_versions); } var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined; const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| { - const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue; - defer allocator.free(privateregistry_absolute_path); - if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue; + const privateregistry_absolute_path = Dir.path.join(gpa, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue; + defer gpa.free(privateregistry_absolute_path); + if (!Dir.path.isAbsolute(privateregistry_absolute_path)) continue; const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue; defer visualstudio_registry.closeKey(); const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable; - const source_directories_value = visualstudio_registry.getString(allocator, config_subkey, "Source Directories") catch |err| switch (err) { + const source_directories_value = visualstudio_registry.getString(gpa, config_subkey, "Source Directories") catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => continue, }; - 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 - allocator.free(source_directories_value); + 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 + gpa.free(source_directories_value); continue; } break :source_directories source_directories_value; } else return error.PathNotFound; - defer allocator.free(source_directories); + defer gpa.free(source_directories); var source_directories_split = std.mem.splitScalar(u8, source_directories, ';'); const msvc_dir: []const u8 = msvc_dir: { - const msvc_include_dir_maybe_with_trailing_slash = try allocator.dupe(u8, source_directories_split.first()); + const msvc_include_dir_maybe_with_trailing_slash = try gpa.dupe(u8, source_directories_split.first()); - 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)) { - allocator.free(msvc_include_dir_maybe_with_trailing_slash); + if (msvc_include_dir_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(msvc_include_dir_maybe_with_trailing_slash)) { + gpa.free(msvc_include_dir_maybe_with_trailing_slash); return error.PathNotFound; } - var msvc_dir = std.array_list.Managed(u8).fromOwnedSlice(allocator, msvc_include_dir_maybe_with_trailing_slash); + var msvc_dir = std.array_list.Managed(u8).fromOwnedSlice(gpa, msvc_include_dir_maybe_with_trailing_slash); errdefer msvc_dir.deinit(); // String might contain trailing slash, so trim it here @@ -933,7 +933,7 @@ const MsvcLibDir = struct { const msvc_dir_with_arch = try msvc_dir.toOwnedSlice(); break :msvc_dir msvc_dir_with_arch; }; - errdefer allocator.free(msvc_dir); + errdefer gpa.free(msvc_dir); if (!verifyLibDir(io, msvc_dir)) { return error.PathNotFound; @@ -942,10 +942,10 @@ const MsvcLibDir = struct { return msvc_dir; } - fn findViaVs7Key(allocator: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { + fn findViaVs7Key(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { var base_path: std.array_list.Managed(u8) = base_path: { try_env: { - var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { + var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => break :try_env, }; @@ -953,8 +953,8 @@ const MsvcLibDir = struct { if (env_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| { if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env; - if (!std.fs.path.isAbsolute(VS140COMNTOOLS)) break :try_env; - var list = std.array_list.Managed(u8).init(allocator); + if (!Dir.path.isAbsolute(VS140COMNTOOLS)) break :try_env; + var list = std.array_list.Managed(u8).init(gpa); errdefer list.deinit(); try list.appendSlice(VS140COMNTOOLS); // C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools @@ -968,17 +968,17 @@ const MsvcLibDir = struct { const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", .{ .wow64_32 = true }) catch return error.PathNotFound; defer vs7_key.closeKey(); try_vs7_key: { - const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) { + const path_maybe_with_trailing_slash = vs7_key.getString(gpa, "", "14.0") catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => break :try_vs7_key, }; - if (path_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) { - allocator.free(path_maybe_with_trailing_slash); + if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) { + gpa.free(path_maybe_with_trailing_slash); break :try_vs7_key; } - var path = std.array_list.Managed(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash); + var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash); errdefer path.deinit(); // String might contain trailing slash, so trim it here @@ -1007,9 +1007,9 @@ const MsvcLibDir = struct { } fn verifyLibDir(io: Io, lib_dir_path: []const u8) bool { - std.debug.assert(std.fs.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*` + std.debug.assert(Dir.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*` - var dir = std.fs.openDirAbsolute(lib_dir_path, .{}) catch return false; + var dir = Dir.openDirAbsolute(io, lib_dir_path, .{}) catch return false; defer dir.close(io); const stat = dir.statFile(io, "vcruntime.lib", .{}) catch return false; @@ -1021,18 +1021,18 @@ const MsvcLibDir = struct { /// Find path to MSVC's `lib/` directory. /// Caller owns the result. - pub fn find(allocator: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { - const full_path = MsvcLibDir.findViaCOM(allocator, io, arch) catch |err1| switch (err1) { + pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { + const full_path = MsvcLibDir.findViaCOM(gpa, io, arch) catch |err1| switch (err1) { error.OutOfMemory => return error.OutOfMemory, - error.PathNotFound => MsvcLibDir.findViaRegistry(allocator, io, arch) catch |err2| switch (err2) { + error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch) catch |err2| switch (err2) { error.OutOfMemory => return error.OutOfMemory, - error.PathNotFound => MsvcLibDir.findViaVs7Key(allocator, io, arch) catch |err3| switch (err3) { + error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, arch) catch |err3| switch (err3) { error.OutOfMemory => return error.OutOfMemory, error.PathNotFound => return error.MsvcLibDirNotFound, }, }, }; - errdefer allocator.free(full_path); + errdefer gpa.free(full_path); return full_path; } -- 2.54.0 From 22b0eea3c0a2d2088ea6823d1de4ea5d9da6260d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 12 Dec 2025 16:30:22 -0800 Subject: [PATCH 052/195] std.Io.Dir: improve realPath documentation --- lib/std/Io/Dir.zig | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index cd3018190098e18bbf5b49e3e5edb3143c53c084..5cd3b898d8c0d287aeb337475a3a5255a029cb23 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -789,21 +789,17 @@ pub const RealPathError = error{ UnrecognizedVolume, } || PathNameError || Io.Cancelable || Io.UnexpectedError; -/// This function returns the canonicalized absolute pathname of `pathname` -/// relative to this `Dir`. If `pathname` is absolute, ignores this `Dir` -/// handle and returns the canonicalized absolute pathname of `pathname` -/// argument. +/// This function returns the canonicalized absolute path name of `sub_path` +/// relative to this `Dir`. If `sub_path` is absolute, ignores this `Dir` +/// handle and returns the canonicalized absolute pathname of `sub_path` +/// argument. /// -/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -/// -/// This function is not universally supported by all platforms. Currently -/// supported hosts are: Linux, macOS, and Windows. +/// This function is not universally supported by all platforms, and using this +/// function can lead to unnecessary failures and race conditions. /// /// See also: /// * `realPathAlloc`. +/// * `realPathAbsolute`. pub fn realPath(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathError!usize { return io.vtable.dirRealPath(io.userdata, dir, sub_path, out_buffer); } @@ -811,18 +807,41 @@ pub fn realPath(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPa pub const RealPathAllocError = RealPathError || Allocator.Error; /// Same as `realPath` except allocates result. +/// +/// This function is not universally supported by all platforms, and using this +/// function can lead to unnecessary failures and race conditions. +/// +/// See also: +/// * `realPath`. +/// * `realPathAbsolute`. pub fn realPathAlloc(dir: Dir, io: Io, sub_path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 { var buffer: [max_path_bytes]u8 = undefined; const n = try realPath(dir, io, sub_path, &buffer); return allocator.dupeZ(u8, buffer[0..n]); } +/// Same as `realPath` except `absolute_path` is asserted to be an absolute +/// path. +/// +/// This function is not universally supported by all platforms, and using this +/// function can lead to unnecessary failures and race conditions. +/// +/// See also: +/// * `realPath`. +/// * `realPathAlloc`. pub fn realPathAbsolute(io: Io, absolute_path: []const u8, out_buffer: []u8) RealPathError!usize { assert(path.isAbsolute(absolute_path)); return io.vtable.dirRealPath(io.userdata, .cwd(), absolute_path, out_buffer); } /// Same as `realPathAbsolute` except allocates result. +/// +/// This function is not universally supported by all platforms, and using this +/// function can lead to unnecessary failures and race conditions. +/// +/// See also: +/// * `realPathAbsolute`. +/// * `realPath`. pub fn realPathAbsoluteAlloc(io: Io, absolute_path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 { var buffer: [max_path_bytes]u8 = undefined; const n = try realPathAbsolute(io, absolute_path, &buffer); -- 2.54.0 From 54e4a3456ce2d5a497a166b94737fe5407052921 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 12 Dec 2025 16:30:44 -0800 Subject: [PATCH 053/195] link: update to new file system APIs --- lib/std/Io/File.zig | 9 +- src/link.zig | 56 +++++++++--- src/link/Coff.zig | 47 +++++----- src/link/Dwarf.zig | 55 +++++++----- src/link/Elf.zig | 75 +++++++--------- src/link/Elf/Archive.zig | 10 +-- src/link/Elf/AtomList.zig | 12 ++- src/link/Elf/Object.zig | 44 ++++++---- src/link/Elf/SharedObject.zig | 20 +++-- src/link/Elf/ZigObject.zig | 28 ++++-- src/link/Elf/relocatable.zig | 65 +++++++------- src/link/Lld.zig | 22 +++-- src/link/MachO.zig | 105 +++++++++++++--------- src/link/MachO/Archive.zig | 4 +- src/link/MachO/DebugSymbols.zig | 35 ++++---- src/link/MachO/Dylib.zig | 20 +++-- src/link/MachO/Object.zig | 149 +++++++++++++++++++------------- src/link/MachO/ZigObject.zig | 21 +++-- src/link/MachO/fat.zig | 12 +-- src/link/MachO/hasher.zig | 18 ++-- src/link/MachO/relocatable.zig | 20 ++--- src/link/SpirV.zig | 5 +- src/link/Wasm.zig | 6 +- src/link/Wasm/Flush.zig | 3 +- src/link/tapi.zig | 4 +- src/main.zig | 2 +- 26 files changed, 489 insertions(+), 358 deletions(-) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 3c04909d46df6e40b3a2890b1570f00fad245ef6..ca6929b8ca3afa2179b95542aff1316859935560 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -494,7 +494,14 @@ pub const WritePositionalError = Writer.Error || error{Unseekable}; /// See also: /// * `writer` pub fn writePositional(file: File, io: Io, buffer: []const []const u8, offset: u64) WritePositionalError!usize { - return io.vtable.fileWritePositional(io.userdata, file, buffer, offset); + return io.vtable.fileWritePositional(io.userdata, file, &.{}, buffer, 1, offset); +} + +/// Equivalent to creating a positional writer, writing `bytes`, and then flushing. +pub fn writePositionalAll(file: File, io: Io, bytes: []const u8, offset: u64) WritePositionalError!void { + var index: usize = 0; + while (index < bytes.len) + index += try io.vtable.fileWritePositional(io.userdata, file, &.{}, &.{bytes[index..]}, 1, offset + index); } pub const SeekError = error{ diff --git a/src/link.zig b/src/link.zig index 9914b1af3af46e454fd7e2476defe3517436d34c..e37ba98cd9dcaa8ba3f0afd90847962fd458a70b 100644 --- a/src/link.zig +++ b/src/link.zig @@ -620,7 +620,7 @@ pub const File = struct { emit.sub_path, std.crypto.random.int(u32), }); defer gpa.free(tmp_sub_path); - try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, .{}); + try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, io, .{}); try emit.root_dir.handle.rename(tmp_sub_path, emit.root_dir.handle, emit.sub_path, io); switch (builtin.os.tag) { .linux => std.posix.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| { @@ -852,10 +852,12 @@ pub const File = struct { } } - pub fn releaseLock(self: *File) void { - if (self.lock) |*lock| { - lock.release(); - self.lock = null; + pub fn releaseLock(base: *File) void { + const comp = base.comp; + const io = comp.io; + if (base.lock) |*lock| { + lock.release(io); + base.lock = null; } } @@ -908,6 +910,7 @@ pub const File = struct { /// `arena` has the lifetime of the call to `Compilation.update`. pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void { const comp = base.comp; + const io = comp.io; if (comp.clang_preprocessor_mode == .yes or comp.clang_preprocessor_mode == .pch) { dev.check(.clang_command); const emit = base.emit; @@ -918,12 +921,19 @@ pub const File = struct { assert(comp.c_object_table.count() == 1); const the_key = comp.c_object_table.keys()[0]; const cached_pp_file_path = the_key.status.success.object_path; - cached_pp_file_path.root_dir.handle.copyFile(cached_pp_file_path.sub_path, emit.root_dir.handle, emit.sub_path, .{}) catch |err| { + Io.Dir.copyFile( + cached_pp_file_path.root_dir.handle, + cached_pp_file_path.sub_path, + emit.root_dir.handle, + emit.sub_path, + io, + .{}, + ) catch |err| { const diags = &base.comp.link_diags; - return diags.fail("failed to copy '{f}' to '{f}': {s}", .{ + return diags.fail("failed to copy '{f}' to '{f}': {t}", .{ std.fmt.alt(@as(Path, cached_pp_file_path), .formatEscapeChar), std.fmt.alt(@as(Path, emit), .formatEscapeChar), - @errorName(err), + err, }); }; return; @@ -1119,7 +1129,7 @@ pub const File = struct { const size = std.math.cast(u32, stat.size) orelse return error.FileTooBig; const buf = try gpa.alloc(u8, size); defer gpa.free(buf); - const n = try file.preadAll(buf, 0); + const n = try file.readPositionalAll(io, buf, 0); if (buf.len != n) return error.UnexpectedEndOfFile; var ld_script = try LdScript.parse(gpa, diags, path, buf); defer ld_script.deinit(gpa); @@ -1184,6 +1194,32 @@ pub const File = struct { } } + /// Legacy function for old linker code + pub fn copyRangeAll(base: *File, old_offset: u64, new_offset: u64, size: u64) !void { + const comp = base.comp; + const io = comp.io; + const file = base.file.?; + return copyRangeAll2(io, file, file, old_offset, new_offset, size); + } + + /// Legacy function for old linker code + pub fn copyRangeAll2(io: Io, src_file: Io.File, dst_file: Io.File, old_offset: u64, new_offset: u64, size: u64) !void { + var write_buffer: [2048]u8 = undefined; + var file_reader = src_file.reader(io, &.{}); + file_reader.pos = old_offset; + var file_writer = dst_file.writer(io, &write_buffer); + file_writer.pos = new_offset; + const size_u = std.math.cast(usize, size) orelse return error.Overflow; + const n = file_writer.interface.sendFileAll(&file_reader, .limited(size_u)) catch |err| switch (err) { + error.ReadFailed => return file_reader.err.?, + error.WriteFailed => return file_writer.err.?, + }; + assert(n == size_u); + file_writer.interface.flush() catch |err| switch (err) { + error.WriteFailed => return file_writer.err.?, + }; + } + pub const Tag = enum { coff2, elf, @@ -1243,7 +1279,7 @@ pub const File = struct { // with 0o755 permissions, but it works appropriately if the system is configured // more leniently. As another data point, C's fopen seems to open files with the // 666 mode. - const executable_mode: Io.FilePermissions = if (builtin.target.os.tag == .windows) + const executable_mode: Io.File.Permissions = if (builtin.target.os.tag == .windows) .default_file else .fromMode(0o777); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 2585d43eba9e3f7a9b4b887f0b9bfd094f89279a..8954266e3fe44e5dd3f02e6fa3d6f93ae6b498d4 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1,3 +1,22 @@ +const Coff = @This(); + +const builtin = @import("builtin"); +const native_endian = builtin.cpu.arch.endian(); + +const std = @import("std"); +const assert = std.debug.assert; +const log = std.log.scoped(.link); + +const codegen = @import("../codegen.zig"); +const Compilation = @import("../Compilation.zig"); +const InternPool = @import("../InternPool.zig"); +const link = @import("../link.zig"); +const MappedFile = @import("MappedFile.zig"); +const target_util = @import("../target.zig"); +const Type = @import("../Type.zig"); +const Value = @import("../Value.zig"); +const Zcu = @import("../Zcu.zig"); + base: link.File, mf: MappedFile, nodes: std.MultiArrayList(Node), @@ -1729,22 +1748,20 @@ pub fn flush( const comp = coff.base.comp; if (comp.compiler_rt_dyn_lib) |crt_file| { const gpa = comp.gpa; + const io = comp.io; const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{ std.fs.path.dirname(coff.base.emit.sub_path) orelse "", std.fs.path.basename(crt_file.full_object_path.sub_path), }); defer gpa.free(compiler_rt_sub_path); - crt_file.full_object_path.root_dir.handle.copyFile( + std.Io.Dir.copyFile( + crt_file.full_object_path.root_dir.handle, crt_file.full_object_path.sub_path, coff.base.emit.root_dir.handle, compiler_rt_sub_path, + io, .{}, - ) catch |err| switch (err) { - else => |e| return comp.link_diags.fail("Copy '{s}' failed: {s}", .{ - compiler_rt_sub_path, - @errorName(e), - }), - }; + ) catch |err| return comp.link_diags.fail("copy '{s}' failed: {t}", .{ compiler_rt_sub_path, err }); } } @@ -2461,19 +2478,3 @@ pub fn printNode( } } } - -const assert = std.debug.assert; -const builtin = @import("builtin"); -const codegen = @import("../codegen.zig"); -const Compilation = @import("../Compilation.zig"); -const Coff = @This(); -const InternPool = @import("../InternPool.zig"); -const link = @import("../link.zig"); -const log = std.log.scoped(.link); -const MappedFile = @import("MappedFile.zig"); -const native_endian = builtin.cpu.arch.endian(); -const std = @import("std"); -const target_util = @import("../target.zig"); -const Type = @import("../Type.zig"); -const Value = @import("../Value.zig"); -const Zcu = @import("../Zcu.zig"); diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 8b05272033bddd8e5315a68c8475a3537a87b472..0fda09e385ec03b1d748a0590ad9b7d0ce71ac85 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -48,6 +48,7 @@ pub const UpdateError = error{ EndOfStream, Underflow, UnexpectedEndOfFile, + NonResizable, } || codegen.GenerateSymbolError || Io.File.OpenError || @@ -155,11 +156,14 @@ const DebugInfo = struct { fn declAbbrevCode(debug_info: *DebugInfo, unit: Unit.Index, entry: Entry.Index) !AbbrevCode { const dwarf: *Dwarf = @fieldParentPtr("debug_info", debug_info); + const comp = dwarf.bin_file.comp; + const io = comp.io; const unit_ptr = debug_info.section.getUnit(unit); const entry_ptr = unit_ptr.getEntry(entry); if (entry_ptr.len < AbbrevCode.decl_bytes) return .null; var abbrev_code_buf: [AbbrevCode.decl_bytes]u8 = undefined; - if (try dwarf.getFile().?.preadAll( + if (try dwarf.getFile().?.readPositionalAll( + io, &abbrev_code_buf, debug_info.section.off(dwarf) + unit_ptr.off + unit_ptr.header_len + entry_ptr.off, ) != abbrev_code_buf.len) return error.InputOutput; @@ -639,13 +643,10 @@ const Unit = struct { fn move(unit: *Unit, sec: *Section, dwarf: *Dwarf, new_off: u32) UpdateError!void { if (unit.off == new_off) return; - const n = try dwarf.getFile().?.copyRangeAll( - sec.off(dwarf) + unit.off, - dwarf.getFile().?, - sec.off(dwarf) + new_off, - unit.len, - ); - if (n != unit.len) return error.InputOutput; + const comp = dwarf.bin_file.comp; + const io = comp.io; + const file = dwarf.getFile().?; + try link.File.copyRangeAll2(io, file, file, sec.off(dwarf) + unit.off, sec.off(dwarf) + new_off, unit.len); unit.off = new_off; } @@ -675,10 +676,14 @@ const Unit = struct { fn replaceHeader(unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void { assert(contents.len == unit.header_len); - try dwarf.getFile().?.pwriteAll(contents, sec.off(dwarf) + unit.off); + const comp = dwarf.bin_file.comp; + const io = comp.io; + try dwarf.getFile().?.writePositionalAll(io, contents, sec.off(dwarf) + unit.off); } fn writeTrailer(unit: *Unit, sec: *Section, dwarf: *Dwarf) UpdateError!void { + const comp = dwarf.bin_file.comp; + const io = comp.io; const start = unit.off + unit.header_len + if (unit.last.unwrap()) |last_entry| end: { const last_entry_ptr = unit.getEntry(last_entry); break :end last_entry_ptr.off + last_entry_ptr.len; @@ -708,7 +713,7 @@ const Unit = struct { assert(fw.end == extended_op_bytes + op_len_bytes); fw.writeByte(DW.LNE.padding) catch unreachable; assert(fw.end >= unit.trailer_len and fw.end <= len); - return dwarf.getFile().?.pwriteAll(fw.buffered(), sec.off(dwarf) + start); + return dwarf.getFile().?.writePositionalAll(io, fw.buffered(), sec.off(dwarf) + start); } var trailer_aw: Writer.Allocating = try .initCapacity(dwarf.gpa, len); defer trailer_aw.deinit(); @@ -768,7 +773,7 @@ const Unit = struct { assert(tw.end == unit.trailer_len); tw.splatByteAll(fill_byte, len - unit.trailer_len) catch unreachable; assert(tw.end == len); - try dwarf.getFile().?.pwriteAll(trailer_aw.written(), sec.off(dwarf) + start); + try dwarf.getFile().?.writePositionalAll(io, trailer_aw.written(), sec.off(dwarf) + start); } fn resolveRelocs(unit: *Unit, sec: *Section, dwarf: *Dwarf) RelocError!void { @@ -854,6 +859,8 @@ const Entry = struct { dwarf: *Dwarf, ) (UpdateError || Writer.Error)!void { assert(entry.len > 0); + const comp = dwarf.bin_file.comp; + const io = comp.io; const start = entry.off + entry.len; if (sec == &dwarf.debug_frame.section) { const len = if (entry.next.unwrap()) |next_entry| @@ -863,11 +870,11 @@ const Entry = struct { var unit_len_buf: [8]u8 = undefined; const unit_len_bytes = unit_len_buf[0..dwarf.sectionOffsetBytes()]; dwarf.writeInt(unit_len_bytes, len - dwarf.unitLengthBytes()); - try dwarf.getFile().?.pwriteAll(unit_len_bytes, sec.off(dwarf) + unit.off + unit.header_len + entry.off); + try dwarf.getFile().?.writePositionalAll(io, unit_len_bytes, sec.off(dwarf) + unit.off + unit.header_len + entry.off); const buf = try dwarf.gpa.alloc(u8, len - entry.len); defer dwarf.gpa.free(buf); @memset(buf, DW.CFA.nop); - try dwarf.getFile().?.pwriteAll(buf, sec.off(dwarf) + unit.off + unit.header_len + start); + try dwarf.getFile().?.writePositionalAll(io, buf, sec.off(dwarf) + unit.off + unit.header_len + start); return; } const len = unit.getEntry(entry.next.unwrap() orelse return).off - start; @@ -926,7 +933,7 @@ const Entry = struct { }, } else assert(!sec.pad_entries_to_ideal and len == 0); assert(fw.end <= len); - try dwarf.getFile().?.pwriteAll(fw.buffered(), sec.off(dwarf) + unit.off + unit.header_len + start); + try dwarf.getFile().?.writePositionalAll(io, fw.buffered(), sec.off(dwarf) + unit.off + unit.header_len + start); } fn resize( @@ -969,11 +976,13 @@ const Entry = struct { fn replace(entry_ptr: *Entry, unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void { assert(contents.len == entry_ptr.len); - try dwarf.getFile().?.pwriteAll(contents, sec.off(dwarf) + unit.off + unit.header_len + entry_ptr.off); + const comp = dwarf.bin_file.comp; + const io = comp.io; + try dwarf.getFile().?.writePositionalAll(io, contents, sec.off(dwarf) + unit.off + unit.header_len + entry_ptr.off); if (false) { const buf = try dwarf.gpa.alloc(u8, sec.len); defer dwarf.gpa.free(buf); - _ = try dwarf.getFile().?.preadAll(buf, sec.off(dwarf)); + _ = try dwarf.getFile().?.readPositionalAll(io, buf, sec.off(dwarf)); log.info("Section{{ .first = {}, .last = {}, .off = 0x{x}, .len = 0x{x} }}", .{ @intFromEnum(sec.first), @intFromEnum(sec.last), @@ -4702,6 +4711,8 @@ fn updateContainerTypeWriterError( } pub fn updateLineNumber(dwarf: *Dwarf, zcu: *Zcu, zir_index: InternPool.TrackedInst.Index) UpdateError!void { + const comp = dwarf.bin_file.comp; + const io = comp.io; const ip = &zcu.intern_pool; const inst_info = zir_index.resolveFull(ip).?; @@ -4721,7 +4732,7 @@ pub fn updateLineNumber(dwarf: *Dwarf, zcu: *Zcu, zir_index: InternPool.TrackedI const unit = dwarf.debug_info.section.getUnit(dwarf.getUnitIfExists(file.mod.?) orelse return); const entry = unit.getEntry(dwarf.decls.get(zir_index) orelse return); - try dwarf.getFile().?.pwriteAll(&line_buf, dwarf.debug_info.section.off(dwarf) + unit.off + unit.header_len + entry.off + DebugInfo.declEntryLineOff(dwarf)); + try dwarf.getFile().?.writePositionalAll(io, &line_buf, dwarf.debug_info.section.off(dwarf) + unit.off + unit.header_len + entry.off + DebugInfo.declEntryLineOff(dwarf)); } pub fn freeNav(dwarf: *Dwarf, nav_index: InternPool.Nav.Index) void { @@ -4758,6 +4769,8 @@ pub fn flush(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void { fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Error)!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; + const comp = dwarf.bin_file.comp; + const io = comp.io; { const type_gop = try dwarf.types.getOrPut(dwarf.gpa, .anyerror_type); @@ -4977,7 +4990,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro if (dwarf.debug_str.section.dirty) { const contents = dwarf.debug_str.contents.items; try dwarf.debug_str.section.resize(dwarf, contents.len); - try dwarf.getFile().?.pwriteAll(contents, dwarf.debug_str.section.off(dwarf)); + try dwarf.getFile().?.writePositionalAll(io, contents, dwarf.debug_str.section.off(dwarf)); dwarf.debug_str.section.dirty = false; } if (dwarf.debug_line.section.dirty) { @@ -5089,7 +5102,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro if (dwarf.debug_line_str.section.dirty) { const contents = dwarf.debug_line_str.contents.items; try dwarf.debug_line_str.section.resize(dwarf, contents.len); - try dwarf.getFile().?.pwriteAll(contents, dwarf.debug_line_str.section.off(dwarf)); + try dwarf.getFile().?.writePositionalAll(io, contents, dwarf.debug_line_str.section.off(dwarf)); dwarf.debug_line_str.section.dirty = false; } if (dwarf.debug_loclists.section.dirty) { @@ -6411,9 +6424,11 @@ fn writeInt(dwarf: *Dwarf, buf: []u8, int: u64) void { } fn resolveReloc(dwarf: *Dwarf, source: u64, target: u64, size: u32) RelocError!void { + const comp = dwarf.bin_file.comp; + const io = comp.io; var buf: [8]u8 = undefined; dwarf.writeInt(buf[0..size], target); - try dwarf.getFile().?.pwriteAll(buf[0..size], source); + try dwarf.getFile().?.writePositionalAll(io, buf[0..size], source); } fn unitLengthBytes(dwarf: *Dwarf) u32 { diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 11bf4489579f31e74ab48b9021778e7af4aebf7e..a312138c044c6dd2767e7f5586f2e403622ee4e6 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -582,14 +582,7 @@ pub fn growSection(self: *Elf, shdr_index: u32, needed_size: u64, min_alignment: new_offset, }); - const amt = try self.base.file.?.copyRangeAll( - shdr.sh_offset, - self.base.file.?, - new_offset, - existing_size, - ); - // TODO figure out what to about this error condition - how to communicate it up. - if (amt != existing_size) return error.InputOutput; + try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size); shdr.sh_offset = new_offset; } else if (shdr.sh_offset + allocated_size == std.math.maxInt(u64)) { @@ -745,7 +738,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { .res => unreachable, .dso_exact => @panic("TODO"), .object => |obj| try parseObject(self, obj), - .archive => |obj| try parseArchive(gpa, diags, &self.file_handles, &self.files, target, debug_fmt_strip, default_sym_version, &self.objects, obj, is_static_lib), + .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), .dso => |dso| try parseDso(gpa, io, diags, dso, &self.shared_objects, &self.files, target), } } @@ -1055,9 +1048,11 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void { } pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void { - const diags = &self.base.comp.link_diags; - const obj = link.openObject(path, false, false) catch |err| { - switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) { + const comp = self.base.comp; + const io = comp.io; + const diags = &comp.link_diags; + const obj = link.openObject(io, path, false, false) catch |err| { + switch (diags.failParse(path, "failed to open object: {t}", .{err})) { error.LinkFailure => return, } }; @@ -1065,10 +1060,11 @@ pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void { } fn parseObjectReportingFailure(self: *Elf, obj: link.Input.Object) void { - const diags = &self.base.comp.link_diags; + const comp = self.base.comp; + const diags = &comp.link_diags; self.parseObject(obj) catch |err| switch (err) { error.LinkFailure => return, // already reported - else => |e| diags.addParseError(obj.path, "failed to parse object: {s}", .{@errorName(e)}), + else => |e| diags.addParseError(obj.path, "failed to parse object: {t}", .{e}), }; } @@ -1076,10 +1072,12 @@ fn parseObject(self: *Elf, obj: link.Input.Object) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = self.base.comp.gpa; - const diags = &self.base.comp.link_diags; - const target = &self.base.comp.root_mod.resolved_target.result; - const debug_fmt_strip = self.base.comp.config.debug_format == .strip; + const comp = self.base.comp; + const io = comp.io; + const gpa = comp.gpa; + const diags = &comp.link_diags; + const target = &comp.root_mod.resolved_target.result; + const debug_fmt_strip = comp.config.debug_format == .strip; const default_sym_version = self.default_sym_version; const file_handles = &self.file_handles; @@ -1098,14 +1096,15 @@ fn parseObject(self: *Elf, obj: link.Input.Object) !void { try self.objects.append(gpa, index); const object = self.file(index).?.object; - try object.parseCommon(gpa, diags, obj.path, handle, target); + try object.parseCommon(gpa, io, diags, obj.path, handle, target); if (!self.base.isStaticLib()) { - try object.parse(gpa, diags, obj.path, handle, target, debug_fmt_strip, default_sym_version); + try object.parse(gpa, io, diags, obj.path, handle, target, debug_fmt_strip, default_sym_version); } } fn parseArchive( gpa: Allocator, + io: Io, diags: *Diags, file_handles: *std.ArrayList(File.Handle), files: *std.MultiArrayList(File.Entry), @@ -1120,7 +1119,7 @@ fn parseArchive( defer tracy.end(); const fh = try addFileHandle(gpa, file_handles, obj.file); - var archive = try Archive.parse(gpa, diags, file_handles, obj.path, fh); + var archive = try Archive.parse(gpa, io, diags, file_handles, obj.path, fh); defer archive.deinit(gpa); const init_alive = if (is_static_lib) true else obj.must_link; @@ -1131,9 +1130,9 @@ fn parseArchive( const object = &files.items(.data)[index].object; object.index = index; object.alive = init_alive; - try object.parseCommon(gpa, diags, obj.path, obj.file, target); + try object.parseCommon(gpa, io, diags, obj.path, obj.file, target); if (!is_static_lib) - try object.parse(gpa, diags, obj.path, obj.file, target, debug_fmt_strip, default_sym_version); + try object.parse(gpa, io, diags, obj.path, obj.file, target, debug_fmt_strip, default_sym_version); try objects.append(gpa, index); } } @@ -1153,7 +1152,7 @@ fn parseDso( const handle = dso.file; const stat = Stat.fromFs(try handle.stat(io)); - var header = try SharedObject.parseHeader(gpa, diags, dso.path, handle, stat, target); + var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target); defer header.deinit(gpa); const soname = header.soname() orelse dso.path.basename(); @@ -1167,7 +1166,7 @@ fn parseDso( gop.value_ptr.* = index; - var parsed = try SharedObject.parse(gpa, &header, handle); + var parsed = try SharedObject.parse(gpa, io, &header, handle); errdefer parsed.deinit(gpa); const duped_path: Path = .{ @@ -2897,13 +2896,7 @@ pub fn allocateAllocSections(self: *Elf) !void { if (shdr.sh_offset > 0) { // Get size actually commited to the output file. const existing_size = self.sectionSize(shndx); - const amt = try self.base.file.?.copyRangeAll( - shdr.sh_offset, - self.base.file.?, - new_offset, - existing_size, - ); - if (amt != existing_size) return error.InputOutput; + try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size); } shdr.sh_offset = new_offset; @@ -2939,13 +2932,7 @@ pub fn allocateNonAllocSections(self: *Elf) !void { if (shdr.sh_offset > 0) { const existing_size = self.sectionSize(@intCast(shndx)); - const amt = try self.base.file.?.copyRangeAll( - shdr.sh_offset, - self.base.file.?, - new_offset, - existing_size, - ); - if (amt != existing_size) return error.InputOutput; + try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size); } shdr.sh_offset = new_offset; @@ -4075,10 +4062,10 @@ fn fmtDumpState(self: *Elf, writer: *std.Io.Writer) std.Io.Writer.Error!void { } /// Caller owns the memory. -pub fn preadAllAlloc(allocator: Allocator, handle: Io.File, offset: u64, size: u64) ![]u8 { +pub fn preadAllAlloc(allocator: Allocator, io: Io, io_file: Io.File, offset: u64, size: u64) ![]u8 { const buffer = try allocator.alloc(u8, math.cast(usize, size) orelse return error.Overflow); errdefer allocator.free(buffer); - const amt = try handle.preadAll(buffer, offset); + const amt = try io_file.readPositionalAll(io, buffer, offset); if (amt != size) return error.InputOutput; return buffer; } @@ -4444,10 +4431,10 @@ pub fn stringTableLookup(strtab: []const u8, off: u32) [:0]const u8 { pub fn pwriteAll(elf_file: *Elf, bytes: []const u8, offset: u64) error{LinkFailure}!void { const comp = elf_file.base.comp; + const io = comp.io; const diags = &comp.link_diags; - elf_file.base.file.?.pwriteAll(bytes, offset) catch |err| { - return diags.fail("failed to write: {s}", .{@errorName(err)}); - }; + elf_file.base.file.?.writePositionalAll(io, bytes, offset) catch |err| + return diags.fail("failed to write: {t}", .{err}); } pub fn setLength(elf_file: *Elf, length: u64) error{LinkFailure}!void { diff --git a/src/link/Elf/Archive.zig b/src/link/Elf/Archive.zig index 0f786c1a47a381f27e9ab6f03293cb539b98fb4b..14f2868956f91b7824525d0855632185cd34520e 100644 --- a/src/link/Elf/Archive.zig +++ b/src/link/Elf/Archive.zig @@ -34,17 +34,17 @@ pub fn parse( path: Path, handle_index: File.HandleIndex, ) !Archive { - const handle = file_handles.items[handle_index]; + const file = file_handles.items[handle_index]; var pos: usize = 0; { var magic_buffer: [elf.ARMAG.len]u8 = undefined; - const n = try handle.preadAll(&magic_buffer, pos); + const n = try file.readPositionalAll(io, &magic_buffer, pos); if (n != magic_buffer.len) return error.BadMagic; if (!mem.eql(u8, &magic_buffer, elf.ARMAG)) return error.BadMagic; pos += magic_buffer.len; } - const size = (try handle.stat(io)).size; + const size = (try file.stat(io)).size; var objects: std.ArrayList(Object) = .empty; defer objects.deinit(gpa); @@ -55,7 +55,7 @@ pub fn parse( while (pos < size) { var hdr: elf.ar_hdr = undefined; { - const n = try handle.preadAll(mem.asBytes(&hdr), pos); + const n = try file.readPositionalAll(io, mem.asBytes(&hdr), pos); if (n != @sizeOf(elf.ar_hdr)) return error.UnexpectedEndOfFile; } pos += @sizeOf(elf.ar_hdr); @@ -72,7 +72,7 @@ pub fn parse( if (hdr.isSymtab() or hdr.isSymtab64()) continue; if (hdr.isStrtab()) { try strtab.resize(gpa, obj_size); - const amt = try handle.preadAll(strtab.items, pos); + const amt = try file.readPositionalAll(io, strtab.items, pos); if (amt != obj_size) return error.InputOutput; continue; } diff --git a/src/link/Elf/AtomList.zig b/src/link/Elf/AtomList.zig index 8fdf5551159a0368d9cd0ba97cf9e364bdc598ec..9350f1a276b16382a297a471ae8214b1bcc1be29 100644 --- a/src/link/Elf/AtomList.zig +++ b/src/link/Elf/AtomList.zig @@ -90,7 +90,9 @@ pub fn allocate(list: *AtomList, elf_file: *Elf) !void { } pub fn write(list: AtomList, buffer: *std.Io.Writer.Allocating, undefs: anytype, elf_file: *Elf) !void { - const gpa = elf_file.base.comp.gpa; + const comp = elf_file.base.comp; + const gpa = comp.gpa; + const io = comp.io; const osec = elf_file.sections.items(.shdr)[list.output_section_index]; assert(osec.sh_type != elf.SHT_NOBITS); assert(!list.dirty); @@ -121,12 +123,14 @@ pub fn write(list: AtomList, buffer: *std.Io.Writer.Allocating, undefs: anytype, try atom_ptr.resolveRelocsAlloc(elf_file, out_code); } - try elf_file.base.file.?.pwriteAll(buffer.written(), list.offset(elf_file)); + try elf_file.base.file.?.writePositionalAll(io, buffer.written(), list.offset(elf_file)); buffer.clearRetainingCapacity(); } pub fn writeRelocatable(list: AtomList, buffer: *std.array_list.Managed(u8), elf_file: *Elf) !void { - const gpa = elf_file.base.comp.gpa; + const comp = elf_file.base.comp; + const gpa = comp.gpa; + const io = comp.io; const osec = elf_file.sections.items(.shdr)[list.output_section_index]; assert(osec.sh_type != elf.SHT_NOBITS); @@ -152,7 +156,7 @@ pub fn writeRelocatable(list: AtomList, buffer: *std.array_list.Managed(u8), elf @memcpy(out_code, code); } - try elf_file.base.file.?.pwriteAll(buffer.items, list.offset(elf_file)); + try elf_file.base.file.?.writePositionalAll(io, buffer.items, list.offset(elf_file)); buffer.clearRetainingCapacity(); } diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 7dacfb3a625e0b8449275bc29340d474ce49fe52..ebdd1f20989a897c831738289772906a98afe0fd 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -92,6 +92,7 @@ pub fn deinit(self: *Object, gpa: Allocator) void { pub fn parse( self: *Object, gpa: Allocator, + io: Io, diags: *Diags, /// For error reporting purposes only. path: Path, @@ -105,7 +106,7 @@ pub fn parse( // Allocate atom index 0 to null atom try self.atoms.append(gpa, .{ .extra_index = try self.addAtomExtra(gpa, .{}) }); - try self.initAtoms(gpa, diags, path, handle, debug_fmt_strip, target); + try self.initAtoms(gpa, io, diags, path, handle, debug_fmt_strip, target); try self.initSymbols(gpa, default_sym_version); for (self.shdrs.items, 0..) |shdr, i| { @@ -114,7 +115,7 @@ pub fn parse( if ((target.cpu.arch == .x86_64 and shdr.sh_type == elf.SHT_X86_64_UNWIND) or mem.eql(u8, self.getString(atom_ptr.name_offset), ".eh_frame")) { - try self.parseEhFrame(gpa, handle, @intCast(i), target); + try self.parseEhFrame(gpa, io, handle, @intCast(i), target); } } } @@ -131,7 +132,7 @@ pub fn parseCommon( const offset = if (self.archive) |ar| ar.offset else 0; const file_size = (try handle.stat(io)).size; - const header_buffer = try Elf.preadAllAlloc(gpa, handle, offset, @sizeOf(elf.Elf64_Ehdr)); + const header_buffer = try Elf.preadAllAlloc(gpa, io, handle, offset, @sizeOf(elf.Elf64_Ehdr)); defer gpa.free(header_buffer); self.header = @as(*align(1) const elf.Elf64_Ehdr, @ptrCast(header_buffer)).*; if (!mem.eql(u8, self.header.?.e_ident[0..4], elf.MAGIC)) { @@ -155,7 +156,7 @@ pub fn parseCommon( return diags.failParse(path, "corrupt header: section header table extends past the end of file", .{}); } - const shdrs_buffer = try Elf.preadAllAlloc(gpa, handle, offset + shoff, shsize); + const shdrs_buffer = try Elf.preadAllAlloc(gpa, io, handle, offset + shoff, shsize); defer gpa.free(shdrs_buffer); const shdrs = @as([*]align(1) const elf.Elf64_Shdr, @ptrCast(shdrs_buffer.ptr))[0..shnum]; try self.shdrs.appendUnalignedSlice(gpa, shdrs); @@ -168,7 +169,7 @@ pub fn parseCommon( } } - const shstrtab = try self.preadShdrContentsAlloc(gpa, handle, self.header.?.e_shstrndx); + const shstrtab = try self.preadShdrContentsAlloc(gpa, io, handle, self.header.?.e_shstrndx); defer gpa.free(shstrtab); for (self.shdrs.items) |shdr| { if (shdr.sh_name >= shstrtab.len) { @@ -186,7 +187,7 @@ pub fn parseCommon( const shdr = self.shdrs.items[index]; self.first_global = shdr.sh_info; - const raw_symtab = try self.preadShdrContentsAlloc(gpa, handle, index); + const raw_symtab = try self.preadShdrContentsAlloc(gpa, io, handle, index); defer gpa.free(raw_symtab); const nsyms = math.divExact(usize, raw_symtab.len, @sizeOf(elf.Elf64_Sym)) catch { return diags.failParse(path, "symbol table not evenly divisible", .{}); @@ -194,7 +195,7 @@ pub fn parseCommon( const symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(raw_symtab.ptr))[0..nsyms]; const strtab_bias = @as(u32, @intCast(self.strtab.items.len)); - const strtab = try self.preadShdrContentsAlloc(gpa, handle, shdr.sh_link); + const strtab = try self.preadShdrContentsAlloc(gpa, io, handle, shdr.sh_link); defer gpa.free(strtab); try self.strtab.appendSlice(gpa, strtab); @@ -290,6 +291,7 @@ pub fn validateEFlags( fn initAtoms( self: *Object, gpa: Allocator, + io: Io, diags: *Diags, path: Path, handle: Io.File, @@ -325,7 +327,7 @@ fn initAtoms( }; const shndx: u32 = @intCast(i); - const group_raw_data = try self.preadShdrContentsAlloc(gpa, handle, shndx); + const group_raw_data = try self.preadShdrContentsAlloc(gpa, io, handle, shndx); defer gpa.free(group_raw_data); const group_nmembers = math.divExact(usize, group_raw_data.len, @sizeOf(u32)) catch { return diags.failParse(path, "corrupt section group: not evenly divisible ", .{}); @@ -366,7 +368,7 @@ fn initAtoms( const shndx: u32 = @intCast(i); if (self.skipShdr(shndx, debug_fmt_strip)) continue; const size, const alignment = if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) blk: { - const data = try self.preadShdrContentsAlloc(gpa, handle, shndx); + const data = try self.preadShdrContentsAlloc(gpa, io, handle, shndx); defer gpa.free(data); const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*; break :blk .{ chdr.ch_size, Alignment.fromNonzeroByteUnits(chdr.ch_addralign) }; @@ -387,7 +389,7 @@ fn initAtoms( elf.SHT_REL, elf.SHT_RELA => { const atom_index = self.atoms_indexes.items[shdr.sh_info]; if (self.atom(atom_index)) |atom_ptr| { - const relocs = try self.preadRelocsAlloc(gpa, handle, @intCast(i)); + const relocs = try self.preadRelocsAlloc(gpa, io, handle, @intCast(i)); defer gpa.free(relocs); atom_ptr.relocs_section_index = @intCast(i); const rel_index: u32 = @intCast(self.relocs.items.len); @@ -449,6 +451,7 @@ fn initSymbols( fn parseEhFrame( self: *Object, gpa: Allocator, + io: Io, handle: Io.File, shndx: u32, target: *const std.Target, @@ -458,12 +461,12 @@ fn parseEhFrame( else => {}, } else null; - const raw = try self.preadShdrContentsAlloc(gpa, handle, shndx); + const raw = try self.preadShdrContentsAlloc(gpa, io, handle, shndx); defer gpa.free(raw); const data_start: u32 = @intCast(self.eh_frame_data.items.len); try self.eh_frame_data.appendSlice(gpa, raw); const relocs = if (relocs_shndx) |index| - try self.preadRelocsAlloc(gpa, handle, index) + try self.preadRelocsAlloc(gpa, io, handle, index) else &[0]elf.Elf64_Rela{}; defer gpa.free(relocs); @@ -1132,6 +1135,9 @@ pub fn updateArSize(self: *Object, elf_file: *Elf) !void { } pub fn writeAr(self: Object, elf_file: *Elf, writer: anytype) !void { + const comp = elf_file.base.comp; + const gpa = comp.gpa; + const io = comp.io; const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow; const offset: u64 = if (self.archive) |ar| ar.offset else 0; const name = fs.path.basename(self.path.sub_path); @@ -1144,10 +1150,9 @@ pub fn writeAr(self: Object, elf_file: *Elf, writer: anytype) !void { }); try writer.writeAll(mem.asBytes(&hdr)); const handle = elf_file.fileHandle(self.file_handle); - const gpa = elf_file.base.comp.gpa; const data = try gpa.alloc(u8, size); defer gpa.free(data); - const amt = try handle.preadAll(data, offset); + const amt = try handle.readPositionalAll(io, data, offset); if (amt != size) return error.InputOutput; try writer.writeAll(data); } @@ -1220,11 +1225,12 @@ pub fn writeSymtab(self: *Object, elf_file: *Elf) void { /// Caller owns the memory. pub fn codeDecompressAlloc(self: *Object, elf_file: *Elf, atom_index: Atom.Index) ![]u8 { const comp = elf_file.base.comp; + const io = comp.io; const gpa = comp.gpa; const atom_ptr = self.atom(atom_index).?; const shdr = atom_ptr.inputShdr(elf_file); const handle = elf_file.fileHandle(self.file_handle); - const data = try self.preadShdrContentsAlloc(gpa, handle, atom_ptr.input_section_index); + const data = try self.preadShdrContentsAlloc(gpa, io, handle, atom_ptr.input_section_index); defer if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) gpa.free(data); if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) { @@ -1340,18 +1346,18 @@ fn addString(self: *Object, gpa: Allocator, str: []const u8) !u32 { } /// Caller owns the memory. -fn preadShdrContentsAlloc(self: Object, gpa: Allocator, handle: Io.File, index: u32) ![]u8 { +fn preadShdrContentsAlloc(self: Object, gpa: Allocator, io: Io, handle: Io.File, index: u32) ![]u8 { assert(index < self.shdrs.items.len); const offset = if (self.archive) |ar| ar.offset else 0; const shdr = self.shdrs.items[index]; const sh_offset = math.cast(u64, shdr.sh_offset) orelse return error.Overflow; const sh_size = math.cast(u64, shdr.sh_size) orelse return error.Overflow; - return Elf.preadAllAlloc(gpa, handle, offset + sh_offset, sh_size); + return Elf.preadAllAlloc(gpa, io, handle, offset + sh_offset, sh_size); } /// Caller owns the memory. -fn preadRelocsAlloc(self: Object, gpa: Allocator, handle: Io.File, shndx: u32) ![]align(1) const elf.Elf64_Rela { - const raw = try self.preadShdrContentsAlloc(gpa, handle, shndx); +fn preadRelocsAlloc(self: Object, gpa: Allocator, io: Io, handle: Io.File, shndx: u32) ![]align(1) const elf.Elf64_Rela { + const raw = try self.preadShdrContentsAlloc(gpa, io, handle, shndx); const num = @divExact(raw.len, @sizeOf(elf.Elf64_Rela)); return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num]; } diff --git a/src/link/Elf/SharedObject.zig b/src/link/Elf/SharedObject.zig index 3720fe53d6ff128a9bbacd1badcda27ed0841da1..c97d53a862235ebe37018dbc819dd048b664b4a2 100644 --- a/src/link/Elf/SharedObject.zig +++ b/src/link/Elf/SharedObject.zig @@ -109,16 +109,17 @@ pub const Parsed = struct { pub fn parseHeader( gpa: Allocator, + io: Io, diags: *Diags, file_path: Path, - fs_file: Io.File, + file: Io.File, stat: Stat, target: *const std.Target, ) !Header { var ehdr: elf.Elf64_Ehdr = undefined; { const buf = mem.asBytes(&ehdr); - const amt = try fs_file.preadAll(buf, 0); + const amt = try file.readPositionalAll(io, buf, 0); if (amt != buf.len) return error.UnexpectedEndOfFile; } if (!mem.eql(u8, ehdr.e_ident[0..4], "\x7fELF")) return error.BadMagic; @@ -135,7 +136,7 @@ pub fn parseHeader( errdefer gpa.free(sections); { const buf = mem.sliceAsBytes(sections); - const amt = try fs_file.preadAll(buf, shoff); + const amt = try file.readPositionalAll(io, buf, shoff); if (amt != buf.len) return error.UnexpectedEndOfFile; } @@ -160,7 +161,7 @@ pub fn parseHeader( const dynamic_table = try gpa.alloc(elf.Elf64_Dyn, n); errdefer gpa.free(dynamic_table); const buf = mem.sliceAsBytes(dynamic_table); - const amt = try fs_file.preadAll(buf, shdr.sh_offset); + const amt = try file.readPositionalAll(io, buf, shdr.sh_offset); if (amt != buf.len) return error.UnexpectedEndOfFile; break :dt dynamic_table; } else &.{}; @@ -175,7 +176,7 @@ pub fn parseHeader( const strtab_shdr = sections[dynsym_shdr.sh_link]; const n = std.math.cast(usize, strtab_shdr.sh_size) orelse return error.Overflow; const buf = try strtab.addManyAsSlice(gpa, n); - const amt = try fs_file.preadAll(buf, strtab_shdr.sh_offset); + const amt = try file.readPositionalAll(io, buf, strtab_shdr.sh_offset); if (amt != buf.len) return error.UnexpectedEndOfFile; } @@ -207,9 +208,10 @@ pub fn parseHeader( pub fn parse( gpa: Allocator, + io: Io, /// Moves resources from header. Caller may unconditionally deinit. header: *Header, - fs_file: Io.File, + file: Io.File, ) !Parsed { const symtab = if (header.dynsym_sect_index) |index| st: { const shdr = header.sections[index]; @@ -217,7 +219,7 @@ pub fn parse( const symtab = try gpa.alloc(elf.Elf64_Sym, n); errdefer gpa.free(symtab); const buf = mem.sliceAsBytes(symtab); - const amt = try fs_file.preadAll(buf, shdr.sh_offset); + const amt = try file.readPositionalAll(io, buf, shdr.sh_offset); if (amt != buf.len) return error.UnexpectedEndOfFile; break :st symtab; } else &.{}; @@ -228,7 +230,7 @@ pub fn parse( if (header.verdef_sect_index) |shndx| { const shdr = header.sections[shndx]; - const verdefs = try Elf.preadAllAlloc(gpa, fs_file, shdr.sh_offset, shdr.sh_size); + const verdefs = try Elf.preadAllAlloc(gpa, io, file, shdr.sh_offset, shdr.sh_size); defer gpa.free(verdefs); var offset: u32 = 0; @@ -254,7 +256,7 @@ pub fn parse( const versyms = try gpa.alloc(elf.Versym, symtab.len); errdefer gpa.free(versyms); const buf = mem.sliceAsBytes(versyms); - const amt = try fs_file.preadAll(buf, shdr.sh_offset); + const amt = try file.readPositionalAll(io, buf, shdr.sh_offset); if (amt != buf.len) return error.UnexpectedEndOfFile; break :vs versyms; } else &.{}; diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 1450e3ab9262208f34e7c6142b7f34a291426e2c..588b4e3fc39a2f2af664b996575157558991db75 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -740,7 +740,9 @@ pub fn checkDuplicates(self: *ZigObject, dupes: anytype, elf_file: *Elf) error{O /// We need this so that we can write to an archive. /// TODO implement writing ZigObject data directly to a buffer instead. pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void { - const gpa = elf_file.base.comp.gpa; + const comp = elf_file.base.comp; + const gpa = comp.gpa; + const io = comp.io; const shsize: u64 = switch (elf_file.ptr_width) { .p32 => @sizeOf(elf.Elf32_Shdr), .p64 => @sizeOf(elf.Elf64_Shdr), @@ -753,7 +755,7 @@ pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void { const size = std.math.cast(usize, end_pos) orelse return error.Overflow; try self.data.resize(gpa, size); - const amt = try elf_file.base.file.?.preadAll(self.data.items, 0); + const amt = try elf_file.base.file.?.readPositionalAll(io, self.data.items, 0); if (amt != size) return error.InputOutput; } @@ -901,13 +903,15 @@ pub fn writeSymtab(self: ZigObject, elf_file: *Elf) void { /// Returns atom's code. /// Caller owns the memory. pub fn codeAlloc(self: *ZigObject, elf_file: *Elf, atom_index: Atom.Index) ![]u8 { - const gpa = elf_file.base.comp.gpa; + const comp = elf_file.base.comp; + const gpa = comp.gpa; + const io = comp.io; const atom_ptr = self.atom(atom_index).?; const file_offset = atom_ptr.offset(elf_file); const size = std.math.cast(usize, atom_ptr.size) orelse return error.Overflow; const code = try gpa.alloc(u8, size); errdefer gpa.free(code); - const amt = try elf_file.base.file.?.preadAll(code, file_offset); + const amt = try elf_file.base.file.?.readPositionalAll(io, code, file_offset); if (amt != code.len) { log.err("fetching code for {s} failed", .{atom_ptr.name(elf_file)}); return error.InputOutput; @@ -1365,6 +1369,8 @@ fn updateNavCode( ) link.File.UpdateNavError!void { const zcu = pt.zcu; const gpa = zcu.gpa; + const comp = elf_file.base.comp; + const io = comp.io; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); @@ -1449,8 +1455,8 @@ fn updateNavCode( const shdr = elf_file.sections.items(.shdr)[shdr_index]; if (shdr.sh_type != elf.SHT_NOBITS) { const file_offset = atom_ptr.offset(elf_file); - elf_file.base.file.?.pwriteAll(code, file_offset) catch |err| - return elf_file.base.cgFail(nav_index, "failed to write to output file: {s}", .{@errorName(err)}); + elf_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err| + return elf_file.base.cgFail(nav_index, "failed to write to output file: {t}", .{err}); log.debug("writing {f} from 0x{x} to 0x{x}", .{ nav.fqn.fmt(ip), file_offset, file_offset + code.len }); } } @@ -1467,6 +1473,8 @@ fn updateTlv( const zcu = pt.zcu; const ip = &zcu.intern_pool; const gpa = zcu.gpa; + const comp = elf_file.base.comp; + const io = comp.io; const nav = ip.getNav(nav_index); log.debug("updateTlv {f}({d})", .{ nav.fqn.fmt(ip), nav_index }); @@ -1503,8 +1511,8 @@ fn updateTlv( const shdr = elf_file.sections.items(.shdr)[shndx]; if (shdr.sh_type != elf.SHT_NOBITS) { const file_offset = atom_ptr.offset(elf_file); - elf_file.base.file.?.pwriteAll(code, file_offset) catch |err| - return elf_file.base.cgFail(nav_index, "failed to write to output file: {s}", .{@errorName(err)}); + elf_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err| + return elf_file.base.cgFail(nav_index, "failed to write to output file: {t}", .{err}); log.debug("writing TLV {s} from 0x{x} to 0x{x}", .{ atom_ptr.name(elf_file), file_offset, @@ -2003,6 +2011,8 @@ fn trampolineSize(cpu_arch: std.Target.Cpu.Arch) u64 { } fn writeTrampoline(tr_sym: Symbol, target: Symbol, elf_file: *Elf) !void { + const comp = elf_file.base.comp; + const io = comp.io; const atom_ptr = tr_sym.atom(elf_file).?; const fileoff = atom_ptr.offset(elf_file); const source_addr = tr_sym.address(.{}, elf_file); @@ -2012,7 +2022,7 @@ fn writeTrampoline(tr_sym: Symbol, target: Symbol, elf_file: *Elf) !void { .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf), else => @panic("TODO implement write trampoline for this CPU arch"), }; - try elf_file.base.file.?.pwriteAll(out, fileoff); + try elf_file.base.file.?.writePositionalAll(io, out, fileoff); if (elf_file.base.child_pid) |pid| { switch (builtin.os.tag) { diff --git a/src/link/Elf/relocatable.zig b/src/link/Elf/relocatable.zig index 0627d8586fdbb52fc3bb6cfd8260a1edb79d29e2..ec3ff252fbeb2fe9828530f23b4900962bfdf9ac 100644 --- a/src/link/Elf/relocatable.zig +++ b/src/link/Elf/relocatable.zig @@ -1,3 +1,23 @@ +const std = @import("std"); +const assert = std.debug.assert; +const elf = std.elf; +const math = std.math; +const mem = std.mem; +const Path = std.Build.Cache.Path; +const log = std.log.scoped(.link); +const state_log = std.log.scoped(.link_state); + +const build_options = @import("build_options"); + +const eh_frame = @import("eh_frame.zig"); +const link = @import("../../link.zig"); +const Archive = @import("Archive.zig"); +const Compilation = @import("../../Compilation.zig"); +const Elf = @import("../Elf.zig"); +const File = @import("file.zig").File; +const Object = @import("Object.zig"); +const Symbol = @import("Symbol.zig"); + pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void { const gpa = comp.gpa; const io = comp.io; @@ -127,7 +147,7 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void { assert(writer.buffered().len == total_size); try elf_file.base.file.?.setLength(io, total_size); - try elf_file.base.file.?.pwriteAll(writer.buffered(), 0); + try elf_file.base.file.?.writePositionalAll(io, writer.buffered(), 0); if (diags.hasErrors()) return error.LinkFailure; } @@ -331,13 +351,7 @@ fn allocateAllocSections(elf_file: *Elf) !void { if (shdr.sh_offset > 0) { const existing_size = elf_file.sectionSize(@intCast(shndx)); - const amt = try elf_file.base.file.?.copyRangeAll( - shdr.sh_offset, - elf_file.base.file.?, - new_offset, - existing_size, - ); - if (amt != existing_size) return error.InputOutput; + try elf_file.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size); } shdr.sh_offset = new_offset; @@ -361,7 +375,9 @@ fn writeAtoms(elf_file: *Elf) !void { } fn writeSyntheticSections(elf_file: *Elf) !void { - const gpa = elf_file.base.comp.gpa; + const comp = elf_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; const slice = elf_file.sections.slice(); const SortRelocs = struct { @@ -398,7 +414,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void { shdr.sh_offset + shdr.sh_size, }); - try elf_file.base.file.?.pwriteAll(@ptrCast(relocs.items), shdr.sh_offset); + try elf_file.base.file.?.writePositionalAll(io, @ptrCast(relocs.items), shdr.sh_offset); } if (elf_file.section_indexes.eh_frame) |shndx| { @@ -418,7 +434,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void { shdr.sh_offset + sh_size, }); assert(writer.buffered().len == sh_size - existing_size); - try elf_file.base.file.?.pwriteAll(writer.buffered(), shdr.sh_offset + existing_size); + try elf_file.base.file.?.writePositionalAll(io, writer.buffered(), shdr.sh_offset + existing_size); } if (elf_file.section_indexes.eh_frame_rela) |shndx| { const shdr = slice.items(.shdr)[shndx]; @@ -436,7 +452,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void { shdr.sh_offset, shdr.sh_offset + shdr.sh_size, }); - try elf_file.base.file.?.pwriteAll(@ptrCast(relocs.items), shdr.sh_offset); + try elf_file.base.file.?.writePositionalAll(io, @ptrCast(relocs.items), shdr.sh_offset); } try writeGroups(elf_file); @@ -445,7 +461,9 @@ fn writeSyntheticSections(elf_file: *Elf) !void { } fn writeGroups(elf_file: *Elf) !void { - const gpa = elf_file.base.comp.gpa; + const comp = elf_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; for (elf_file.group_sections.items) |cgs| { const shdr = elf_file.sections.items(.shdr)[cgs.shndx]; const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow; @@ -458,25 +476,6 @@ fn writeGroups(elf_file: *Elf) !void { shdr.sh_offset, shdr.sh_offset + shdr.sh_size, }); - try elf_file.base.file.?.pwriteAll(writer.buffered(), shdr.sh_offset); + try elf_file.base.file.?.writePositionalAll(io, writer.buffered(), shdr.sh_offset); } } - -const assert = std.debug.assert; -const build_options = @import("build_options"); -const eh_frame = @import("eh_frame.zig"); -const elf = std.elf; -const link = @import("../../link.zig"); -const log = std.log.scoped(.link); -const math = std.math; -const mem = std.mem; -const state_log = std.log.scoped(.link_state); -const Path = std.Build.Cache.Path; -const std = @import("std"); - -const Archive = @import("Archive.zig"); -const Compilation = @import("../../Compilation.zig"); -const Elf = @import("../Elf.zig"); -const File = @import("file.zig").File; -const Object = @import("Object.zig"); -const Symbol = @import("Symbol.zig"); diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 91455f3fde70166ca1120e7e2933d2de4d439488..8ba91905a8e09fb1c246df3d801450df6a19c390 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -406,6 +406,7 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { the_object_path.sub_path, directory.handle, base.emit.sub_path, + io, .{}, ); } else { @@ -756,6 +757,7 @@ fn findLib(arena: Allocator, io: Io, name: []const u8, lib_directories: []const fn elfLink(lld: *Lld, arena: Allocator) !void { const comp = lld.base.comp; const gpa = comp.gpa; + const io = comp.io; const diags = &comp.link_diags; const base = &lld.base; const elf = &lld.ofmt.elf; @@ -822,6 +824,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { the_object_path.sub_path, directory.handle, base.emit.sub_path, + io, .{}, ); } else { @@ -1336,6 +1339,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { const wasm = &lld.ofmt.wasm; const gpa = comp.gpa; + const io = comp.io; const directory = base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); @@ -1378,6 +1382,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { the_object_path.sub_path, directory.handle, base.emit.sub_path, + io, .{}, ); } else { @@ -1571,7 +1576,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { comp.config.output_mode == .Exe) { // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. - Io.Dir.cwd().setFilePermissions(full_out_path, .fromMode(0o744), .{}) catch |err| + Io.Dir.cwd().setFilePermissions(io, full_out_path, .fromMode(0o744), .{}) catch |err| return diags.fail("{s}: failed to enable executable permissions: {t}", .{ full_out_path, err }); } } @@ -1579,6 +1584,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !void { const io = comp.io; + const gpa = comp.gpa; if (comp.verbose_link) { // Skip over our own name so that the LLD linker name is the first argv item. @@ -1596,7 +1602,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi } var stderr: []u8 = &.{}; - defer comp.gpa.free(stderr); + defer gpa.free(stderr); var child = std.process.Child.init(argv, arena); const term = (if (comp.clang_passthrough_mode) term: { @@ -1612,8 +1618,8 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi child.spawn(io) catch |err| break :term err; var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); - stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited); - break :term child.wait(); + stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited); + break :term child.wait(io); }) catch |first_err| term: { const err = switch (first_err) { error.NameTooLong => err: { @@ -1622,8 +1628,8 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp"; const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{}); - defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err| - log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) }); + defer comp.dirs.local_cache.handle.deleteFile(io, rsp_path) catch |err| + log.warn("failed to delete response file {s}: {t}", .{ rsp_path, err }); { defer rsp_file.close(io); var rsp_file_buffer: [1024]u8 = undefined; @@ -1662,8 +1668,8 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi rsp_child.spawn(io) catch |err| break :err err; var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{}); - stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited); - break :term rsp_child.wait() catch |err| break :err err; + stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited); + break :term rsp_child.wait(io) catch |err| break :err err; } }, else => first_err, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6c380637eedf1d60aee278494d1378a914b8656f..cd9674a390504b7ccec26dc28ada18e895293a72 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -347,7 +347,8 @@ pub fn flush( const comp = self.base.comp; const gpa = comp.gpa; - const diags = &self.base.comp.link_diags; + const io = comp.io; + const diags = &comp.link_diags; const sub_prog_node = prog_node.start("MachO Flush", 0); defer sub_prog_node.end(); @@ -380,26 +381,26 @@ pub fn flush( // in this set. try positionals.ensureUnusedCapacity(comp.c_object_table.keys().len); for (comp.c_object_table.keys()) |key| { - positionals.appendAssumeCapacity(try link.openObjectInput(diags, key.status.success.object_path)); + positionals.appendAssumeCapacity(try link.openObjectInput(io, diags, key.status.success.object_path)); } - if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); + if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path)); if (comp.config.any_sanitize_thread) { - try positionals.append(try link.openObjectInput(diags, comp.tsan_lib.?.full_object_path)); + try positionals.append(try link.openObjectInput(io, diags, comp.tsan_lib.?.full_object_path)); } if (comp.config.any_fuzz) { - try positionals.append(try link.openArchiveInput(diags, comp.fuzzer_lib.?.full_object_path, false, false)); + try positionals.append(try link.openArchiveInput(io, diags, comp.fuzzer_lib.?.full_object_path, false, false)); } if (comp.ubsan_rt_lib) |crt_file| { const path = crt_file.full_object_path; - self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err| + self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); } else if (comp.ubsan_rt_obj) |crt_file| { const path = crt_file.full_object_path; - self.classifyInputFile(try link.openObjectInput(diags, path)) catch |err| + self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); } @@ -434,7 +435,7 @@ pub fn flush( if (comp.config.link_libc and is_exe_or_dyn_lib) { if (comp.zigc_static_lib) |zigc| { const path = zigc.full_object_path; - self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err| + self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); } } @@ -457,12 +458,12 @@ pub fn flush( for (system_libs.items) |lib| { switch (Compilation.classifyFileExt(lib.path.sub_path)) { .shared_library => { - const dso_input = try link.openDsoInput(diags, lib.path, lib.needed, lib.weak, lib.reexport); + const dso_input = try link.openDsoInput(io, diags, lib.path, lib.needed, lib.weak, lib.reexport); self.classifyInputFile(dso_input) catch |err| diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)}); }, .static_library => { - const archive_input = try link.openArchiveInput(diags, lib.path, lib.must_link, lib.hidden); + const archive_input = try link.openArchiveInput(io, diags, lib.path, lib.must_link, lib.hidden); self.classifyInputFile(archive_input) catch |err| diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)}); }, @@ -473,11 +474,11 @@ pub fn flush( // Finally, link against compiler_rt. if (comp.compiler_rt_lib) |crt_file| { const path = crt_file.full_object_path; - self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err| + self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); } else if (comp.compiler_rt_obj) |crt_file| { const path = crt_file.full_object_path; - self.classifyInputFile(try link.openObjectInput(diags, path)) catch |err| + self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); } @@ -568,7 +569,7 @@ pub fn flush( self.writeLinkeditSectionsToFile() catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, - else => |e| return diags.fail("failed to write linkedit sections to file: {s}", .{@errorName(e)}), + else => |e| return diags.fail("failed to write linkedit sections to file: {t}", .{e}), }; var codesig: ?CodeSignature = if (self.requiresCodeSig()) blk: { @@ -579,8 +580,8 @@ pub fn flush( // where the code signature goes into. var codesig = CodeSignature.init(self.getPageSize()); codesig.code_directory.ident = fs.path.basename(self.base.emit.sub_path); - if (self.entitlements) |path| codesig.addEntitlements(gpa, path) catch |err| - return diags.fail("failed to add entitlements from {s}: {s}", .{ path, @errorName(err) }); + if (self.entitlements) |path| codesig.addEntitlements(gpa, io, path) catch |err| + return diags.fail("failed to add entitlements from {s}: {t}", .{ path, err }); try self.writeCodeSignaturePadding(&codesig); break :blk codesig; } else null; @@ -866,6 +867,9 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { const tracy = trace(@src()); defer tracy.end(); + const comp = self.base.comp; + const io = comp.io; + const path, const file = input.pathAndFile().?; // TODO don't classify now, it's too late. The input file has already been classified log.debug("classifying input file {f}", .{path}); @@ -876,7 +880,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { const fat_arch: ?fat.Arch = try self.parseFatFile(file, path); const offset = if (fat_arch) |fa| fa.offset else 0; - if (readMachHeader(file, offset) catch null) |h| blk: { + if (readMachHeader(io, file, offset) catch null) |h| blk: { if (h.magic != macho.MH_MAGIC_64) break :blk; switch (h.filetype) { macho.MH_OBJECT => try self.addObject(path, fh, offset), @@ -885,7 +889,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { } return; } - if (readArMagic(file, offset, &buffer) catch null) |ar_magic| blk: { + if (readArMagic(io, file, offset, &buffer) catch null) |ar_magic| blk: { if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk; try self.addArchive(input.archive, fh, fat_arch); return; @@ -894,11 +898,13 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { } fn parseFatFile(self: *MachO, file: Io.File, path: Path) !?fat.Arch { - const diags = &self.base.comp.link_diags; - const fat_h = fat.readFatHeader(file) catch return null; + const comp = self.base.comp; + const io = comp.io; + const diags = &comp.link_diags; + const fat_h = fat.readFatHeader(io, file) catch return null; if (fat_h.magic != macho.FAT_MAGIC and fat_h.magic != macho.FAT_MAGIC_64) return null; var fat_archs_buffer: [2]fat.Arch = undefined; - const fat_archs = try fat.parseArchs(file, fat_h, &fat_archs_buffer); + const fat_archs = try fat.parseArchs(io, file, fat_h, &fat_archs_buffer); const cpu_arch = self.getTarget().cpu.arch; for (fat_archs) |arch| { if (arch.tag == cpu_arch) return arch; @@ -906,16 +912,16 @@ fn parseFatFile(self: *MachO, file: Io.File, path: Path) !?fat.Arch { return diags.failParse(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)}); } -pub fn readMachHeader(file: Io.File, offset: usize) !macho.mach_header_64 { +pub fn readMachHeader(io: Io, file: Io.File, offset: usize) !macho.mach_header_64 { var buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined; - const nread = try file.preadAll(&buffer, offset); + const nread = try file.readPositionalAll(io, &buffer, offset); if (nread != buffer.len) return error.InputOutput; const hdr = @as(*align(1) const macho.mach_header_64, @ptrCast(&buffer)).*; return hdr; } -pub fn readArMagic(file: Io.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 { - const nread = try file.preadAll(buffer, offset); +pub fn readArMagic(io: Io, file: Io.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 { + const nread = try file.readPositionalAll(io, buffer, offset); if (nread != buffer.len) return error.InputOutput; return buffer[0..Archive.SARMAG]; } @@ -1212,7 +1218,8 @@ fn parseDependentDylibs(self: *MachO) !void { const rel_path = try fs.path.join(arena, &.{ prefix, path }); try checked_paths.append(rel_path); var buffer: [fs.max_path_bytes]u8 = undefined; - const full_path = fs.realpath(rel_path, &buffer) catch continue; + // TODO don't use realpath + const full_path = buffer[0 .. Io.Dir.realPathAbsolute(io, rel_path, &buffer) catch continue]; break :full_path try arena.dupe(u8, full_path); } } else if (eatPrefix(id.name, "@loader_path/")) |_| { @@ -1225,8 +1232,9 @@ fn parseDependentDylibs(self: *MachO) !void { try checked_paths.append(try arena.dupe(u8, id.name)); var buffer: [fs.max_path_bytes]u8 = undefined; - if (fs.realpath(id.name, &buffer)) |full_path| { - break :full_path try arena.dupe(u8, full_path); + // TODO don't use realpath + if (Io.Dir.realPathAbsolute(io, id.name, &buffer)) |full_path_n| { + break :full_path try arena.dupe(u8, buffer[0..full_path_n]); } else |_| { try self.reportMissingDependencyError( self.getFile(dylib_index).?.dylib.getUmbrella(self).index, @@ -1248,7 +1256,7 @@ fn parseDependentDylibs(self: *MachO) !void { const fat_arch = try self.parseFatFile(file, lib.path); const offset = if (fat_arch) |fa| fa.offset else 0; const file_index = file_index: { - if (readMachHeader(file, offset) catch null) |h| blk: { + if (readMachHeader(io, file, offset) catch null) |h| blk: { if (h.magic != macho.MH_MAGIC_64) break :blk; switch (h.filetype) { macho.MH_DYLIB => break :file_index try self.addDylib(lib, false, fh, offset), @@ -3244,21 +3252,36 @@ pub fn findFreeSpaceVirtual(self: *MachO, object_size: u64, min_alignment: u32) } pub fn copyRangeAll(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void { - const file = self.base.file.?; - const amt = try file.copyRangeAll(old_offset, file, new_offset, size); - if (amt != size) return error.InputOutput; + return self.base.copyRangeAll(old_offset, new_offset, size); } -/// Like File.copyRangeAll but also ensures the source region is zeroed out after copy. +/// Like copyRangeAll but also ensures the source region is zeroed out after copy. /// This is so that we guarantee zeroed out regions for mapping of zerofill sections by the loader. fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void { - const gpa = self.base.comp.gpa; - try self.copyRangeAll(old_offset, new_offset, size); + const comp = self.base.comp; + const io = comp.io; + const file = self.base.file.?; + var write_buffer: [2048]u8 = undefined; + var file_reader = file.reader(io, &.{}); + file_reader.pos = old_offset; + var file_writer = file.writer(io, &write_buffer); + file_writer.pos = new_offset; const size_u = math.cast(usize, size) orelse return error.Overflow; - const zeroes = try gpa.alloc(u8, size_u); // TODO no need to allocate here. - defer gpa.free(zeroes); - @memset(zeroes, 0); - try self.base.file.?.pwriteAll(zeroes, old_offset); + const n = file_writer.interface.sendFileAll(&file_reader, .limited(size_u)) catch |err| switch (err) { + error.ReadFailed => return file_reader.err.?, + error.WriteFailed => return file_writer.err.?, + }; + assert(n == size_u); + file_writer.seekTo(old_offset) catch |err| switch (err) { + error.WriteFailed => return file_writer.err.?, + else => |e| return e, + }; + file_writer.interface.splatByteAll(0, size_u) catch |err| switch (err) { + error.WriteFailed => return file_writer.err.?, + }; + file_writer.interface.flush() catch |err| switch (err) { + error.WriteFailed => return file_writer.err.?, + }; } const InitMetadataOptions = struct { @@ -5355,10 +5378,10 @@ fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool { pub fn pwriteAll(macho_file: *MachO, bytes: []const u8, offset: u64) error{LinkFailure}!void { const comp = macho_file.base.comp; + const io = comp.io; const diags = &comp.link_diags; - macho_file.base.file.?.pwriteAll(bytes, offset) catch |err| { - return diags.fail("failed to write: {s}", .{@errorName(err)}); - }; + macho_file.base.file.?.writePositionalAll(io, bytes, offset) catch |err| + return diags.fail("failed to write: {t}", .{err}); } pub fn setLength(macho_file: *MachO, length: u64) error{LinkFailure}!void { diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index 122a4085339bcd21566b11267d53bb59413c3876..54c00e33eef74c485876e44382b25b3bf2eff262 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -24,7 +24,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File var hdr_buffer: [@sizeOf(ar_hdr)]u8 = undefined; { - const amt = try handle.preadAll(&hdr_buffer, pos); + const amt = try handle.readPositionalAll(io, &hdr_buffer, pos); if (amt != @sizeOf(ar_hdr)) return error.InputOutput; } const hdr = @as(*align(1) const ar_hdr, @ptrCast(&hdr_buffer)).*; @@ -42,7 +42,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File if (try hdr.nameLength()) |len| { hdr_size -= len; const buf = try arena.allocator().alloc(u8, len); - const amt = try handle.preadAll(buf, pos); + const amt = try handle.readPositionalAll(io, buf, pos); if (amt != len) return error.InputOutput; pos += len; const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len; diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 1ec19ecea79c01cf6e2ffdb92b32fde600dea046..3e723bd9d72a535402f0d4e1da1e7481df8eb040 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -135,20 +135,12 @@ pub fn growSection( const new_offset = try self.findFreeSpace(needed_size, 1); log.debug("moving {s} section: {} bytes from 0x{x} to 0x{x}", .{ - sect.sectName(), - existing_size, - sect.offset, - new_offset, + sect.sectName(), existing_size, sect.offset, new_offset, }); if (requires_file_copy) { - const amt = try self.file.?.copyRangeAll( - sect.offset, - self.file.?, - new_offset, - existing_size, - ); - if (amt != existing_size) return error.InputOutput; + const file = self.file.?; + try link.File.copyRangeAll2(io, file, file, sect.offset, new_offset, existing_size); } sect.offset = @intCast(new_offset); @@ -204,6 +196,7 @@ fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64 } pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void { + const io = self.io; const zo = macho_file.getZigObject().?; for (self.relocs.items) |*reloc| { const sym = zo.symbols.items[reloc.target]; @@ -215,12 +208,9 @@ pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void { const sect = &self.sections.items[self.debug_info_section_index.?]; const file_offset = sect.offset + reloc.offset; log.debug("resolving relocation: {d}@{x} ('{s}') at offset {x}", .{ - reloc.target, - addr, - sym_name, - file_offset, + reloc.target, addr, sym_name, file_offset, }); - try self.file.?.pwriteAll(mem.asBytes(&addr), file_offset); + try self.file.?.writePositionalAll(io, mem.asBytes(&addr), file_offset); } self.finalizeDwarfSegment(macho_file); @@ -294,6 +284,7 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void { } fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, usize } { + const io = self.io; const gpa = self.allocator; const needed_size = load_commands.calcLoadCommandsSizeDsym(macho_file, self); const buffer = try gpa.alloc(u8, needed_size); @@ -345,12 +336,13 @@ fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, u assert(writer.end == needed_size); - try self.file.?.pwriteAll(buffer, @sizeOf(macho.mach_header_64)); + try self.file.?.writePositionalAll(io, buffer, @sizeOf(macho.mach_header_64)); return .{ ncmds, buffer.len }; } fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void { + const io = self.io; var header: macho.mach_header_64 = .{}; header.filetype = macho.MH_DSYM; @@ -371,7 +363,7 @@ fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds log.debug("writing Mach-O header {}", .{header}); - try self.file.?.pwriteAll(mem.asBytes(&header), 0); + try self.file.?.writePositionalAll(io, mem.asBytes(&header), 0); } fn allocatedSize(self: *DebugSymbols, start: u64) u64 { @@ -406,6 +398,8 @@ fn writeLinkeditSegmentData(self: *DebugSymbols, macho_file: *MachO) !void { pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 { const tracy = trace(@src()); defer tracy.end(); + + const io = self.io; const gpa = self.allocator; const cmd = &self.symtab_cmd; cmd.nsyms = macho_file.symtab_cmd.nsyms; @@ -429,15 +423,16 @@ pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 { internal.writeSymtab(macho_file, self); } - try self.file.?.pwriteAll(@ptrCast(self.symtab.items), cmd.symoff); + try self.file.?.writePositionalAll(io, @ptrCast(self.symtab.items), cmd.symoff); return off + cmd.nsyms * @sizeOf(macho.nlist_64); } pub fn writeStrtab(self: *DebugSymbols, off: u32) !u32 { + const io = self.io; const cmd = &self.symtab_cmd; cmd.stroff = off; - try self.file.?.pwriteAll(self.strtab.items, cmd.stroff); + try self.file.?.writePositionalAll(io, self.strtab.items, cmd.stroff); return off + cmd.strsize; } diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 69c64b6717d5075f4f524d8940401ef1ee257dba..638630b608b0341c0a88899c5d8f379a7eea967a 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -57,7 +57,9 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; const file = macho_file.getFileHandle(self.file_handle); const offset = self.offset; @@ -65,7 +67,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void { var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined; { - const amt = try file.preadAll(&header_buffer, offset); + const amt = try file.readPositionalAll(io, &header_buffer, offset); if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput; } 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 { const lc_buffer = try gpa.alloc(u8, header.sizeofcmds); defer gpa.free(lc_buffer); { - const amt = try file.preadAll(lc_buffer, offset + @sizeOf(macho.mach_header_64)); + const amt = try file.readPositionalAll(io, lc_buffer, offset + @sizeOf(macho.mach_header_64)); if (amt != lc_buffer.len) return error.InputOutput; } @@ -103,7 +105,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void { const dyld_cmd = cmd.cast(macho.dyld_info_command).?; const data = try gpa.alloc(u8, dyld_cmd.export_size); defer gpa.free(data); - const amt = try file.preadAll(data, dyld_cmd.export_off + offset); + const amt = try file.readPositionalAll(io, data, dyld_cmd.export_off + offset); if (amt != data.len) return error.InputOutput; try self.parseTrie(data, macho_file); }, @@ -111,7 +113,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void { const ld_cmd = cmd.cast(macho.linkedit_data_command).?; const data = try gpa.alloc(u8, ld_cmd.datasize); defer gpa.free(data); - const amt = try file.preadAll(data, ld_cmd.dataoff + offset); + const amt = try file.readPositionalAll(io, data, ld_cmd.dataoff + offset); if (amt != data.len) return error.InputOutput; try self.parseTrie(data, macho_file); }, @@ -238,13 +240,15 @@ fn parseTbd(self: *Dylib, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const gpa = comp.gpa; + const io = comp.io; log.debug("parsing dylib from stub: {f}", .{self.path}); const file = macho_file.getFileHandle(self.file_handle); - var lib_stub = LibStub.loadFromFile(gpa, file) catch |err| { - try macho_file.reportParseError2(self.index, "failed to parse TBD file: {s}", .{@errorName(err)}); + var lib_stub = LibStub.loadFromFile(gpa, io, file) catch |err| { + try macho_file.reportParseError2(self.index, "failed to parse TBD file: {t}", .{err}); return error.MalformedTbd; }; defer lib_stub.deinit(); diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 1a1799f551dc843d189080aa1a38ac777a69b4a9..b9def4568df91bd1891a9110c1912a67be3c5aea 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -1,3 +1,30 @@ +const Object = @This(); + +const trace = @import("../../tracy.zig").trace; +const Archive = @import("Archive.zig"); +const Atom = @import("Atom.zig"); +const Dwarf = @import("Dwarf.zig"); +const File = @import("file.zig").File; +const MachO = @import("../MachO.zig"); +const Relocation = @import("Relocation.zig"); +const Symbol = @import("Symbol.zig"); +const UnwindInfo = @import("UnwindInfo.zig"); + +const std = @import("std"); +const Io = std.Io; +const Writer = std.Io.Writer; +const assert = std.debug.assert; +const log = std.log.scoped(.link); +const macho = std.macho; +const LoadCommandIterator = macho.LoadCommandIterator; +const math = std.math; +const mem = std.mem; +const Allocator = std.mem.Allocator; + +const eh_frame = @import("eh_frame.zig"); +const Cie = eh_frame.Cie; +const Fde = eh_frame.Fde; + /// Non-zero for fat object files or archives offset: u64, /// 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 { log.debug("parsing {f}", .{self.fmtPath()}); - const gpa = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; const handle = macho_file.getFileHandle(self.file_handle); const cpu_arch = macho_file.getTarget().cpu.arch; @@ -84,7 +113,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined; { - const amt = try handle.preadAll(&header_buffer, self.offset); + const amt = try handle.readPositionalAll(io, &header_buffer, self.offset); if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput; } 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 { const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds); defer gpa.free(lc_buffer); { - const amt = try handle.preadAll(lc_buffer, self.offset + @sizeOf(macho.mach_header_64)); + const amt = try handle.readPositionalAll(io, lc_buffer, self.offset + @sizeOf(macho.mach_header_64)); if (amt != self.header.?.sizeofcmds) return error.InputOutput; } @@ -129,14 +158,14 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { const cmd = lc.cast(macho.symtab_command).?; try self.strtab.resize(gpa, cmd.strsize); { - const amt = try handle.preadAll(self.strtab.items, cmd.stroff + self.offset); + const amt = try handle.readPositionalAll(io, self.strtab.items, cmd.stroff + self.offset); if (amt != self.strtab.items.len) return error.InputOutput; } const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64)); defer gpa.free(symtab_buffer); { - const amt = try handle.preadAll(symtab_buffer, cmd.symoff + self.offset); + const amt = try handle.readPositionalAll(io, symtab_buffer, cmd.symoff + self.offset); if (amt != symtab_buffer.len) return error.InputOutput; } 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 { const buffer = try gpa.alloc(u8, cmd.datasize); defer gpa.free(buffer); { - const amt = try handle.preadAll(buffer, self.offset + cmd.dataoff); + const amt = try handle.readPositionalAll(io, buffer, self.offset + cmd.dataoff); if (amt != buffer.len) return error.InputOutput; } 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 const tracy = trace(@src()); defer tracy.end(); + const comp = macho_file.base.comp; + const io = comp.io; const slice = self.sections.slice(); for (slice.items(.header), 0..) |sect, n_sect| { if (!isCstringLiteral(sect)) continue; - const data = try self.readSectionData(allocator, file, @intCast(n_sect)); + const data = try self.readSectionData(allocator, io, file, @intCast(n_sect)); defer allocator.free(data); var count: u32 = 0; @@ -628,7 +659,9 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; const file = macho_file.getFileHandle(self.file_handle); var buffer = std.array_list.Managed(u8).init(gpa); @@ -647,7 +680,7 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO const slice = self.sections.slice(); for (slice.items(.header), slice.items(.subsections), 0..) |header, subs, n_sect| { if (isCstringLiteral(header) or isFixedSizeLiteral(header)) { - const data = try self.readSectionData(gpa, file, @intCast(n_sect)); + const data = try self.readSectionData(gpa, io, file, @intCast(n_sect)); defer gpa.free(data); for (subs.items) |sub| { @@ -682,7 +715,7 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO buffer.resize(target_size) catch unreachable; const gop = try sections_data.getOrPut(target.n_sect); if (!gop.found_existing) { - gop.value_ptr.* = try self.readSectionData(gpa, file, @intCast(target.n_sect)); + gop.value_ptr.* = try self.readSectionData(gpa, io, file, @intCast(target.n_sect)); } const data = gop.value_ptr.*; 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 const sect = slice.items(.header)[sect_id]; const relocs = slice.items(.relocs)[sect_id]; + const comp = macho_file.base.comp; + const io = comp.io; const size = try macho_file.cast(usize, sect.size); try self.eh_frame_data.resize(allocator, size); - const amt = try file.preadAll(self.eh_frame_data.items, sect.offset + self.offset); + const amt = try file.readPositionalAll(io, self.eh_frame_data.items, sect.offset + self.offset); if (amt != self.eh_frame_data.items.len) return error.InputOutput; // 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 } }; + const comp = macho_file.base.comp; + const io = comp.io; const header = self.sections.items(.header)[sect_id]; - const data = try self.readSectionData(allocator, file, sect_id); + const data = try self.readSectionData(allocator, io, file, sect_id); defer allocator.free(data); const nrecs = @divExact(data.len, @sizeOf(macho.compact_unwind_entry)); @@ -1348,7 +1385,9 @@ fn parseDebugInfo(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; const file = macho_file.getFileHandle(self.file_handle); var dwarf: Dwarf = .{}; @@ -1358,18 +1397,18 @@ fn parseDebugInfo(self: *Object, macho_file: *MachO) !void { const n_sect: u8 = @intCast(index); if (sect.attrs() & macho.S_ATTR_DEBUG == 0) continue; if (mem.eql(u8, sect.sectName(), "__debug_info")) { - dwarf.debug_info = try self.readSectionData(gpa, file, n_sect); + dwarf.debug_info = try self.readSectionData(gpa, io, file, n_sect); } if (mem.eql(u8, sect.sectName(), "__debug_abbrev")) { - dwarf.debug_abbrev = try self.readSectionData(gpa, file, n_sect); + dwarf.debug_abbrev = try self.readSectionData(gpa, io, file, n_sect); } if (mem.eql(u8, sect.sectName(), "__debug_str")) { - dwarf.debug_str = try self.readSectionData(gpa, file, n_sect); + dwarf.debug_str = try self.readSectionData(gpa, io, file, n_sect); } // __debug_str_offs[ets] section is a new addition in DWARFv5 and is generally // required in order to correctly parse strings. if (mem.eql(u8, sect.sectName(), "__debug_str_offs")) { - dwarf.debug_str_offsets = try self.readSectionData(gpa, file, n_sect); + dwarf.debug_str_offsets = try self.readSectionData(gpa, io, file, n_sect); } } @@ -1611,12 +1650,14 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; const handle = macho_file.getFileHandle(self.file_handle); var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined; { - const amt = try handle.preadAll(&header_buffer, self.offset); + const amt = try handle.readPositionalAll(io, &header_buffer, self.offset); if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput; } 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 { const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds); defer gpa.free(lc_buffer); { - const amt = try handle.preadAll(lc_buffer, self.offset + @sizeOf(macho.mach_header_64)); + const amt = try handle.readPositionalAll(io, lc_buffer, self.offset + @sizeOf(macho.mach_header_64)); if (amt != self.header.?.sizeofcmds) return error.InputOutput; } @@ -1647,14 +1688,14 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void { const cmd = lc.cast(macho.symtab_command).?; try self.strtab.resize(gpa, cmd.strsize); { - const amt = try handle.preadAll(self.strtab.items, cmd.stroff + self.offset); + const amt = try handle.readPositionalAll(io, self.strtab.items, cmd.stroff + self.offset); if (amt != self.strtab.items.len) return error.InputOutput; } const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64)); defer gpa.free(symtab_buffer); { - const amt = try handle.preadAll(symtab_buffer, cmd.symoff + self.offset); + const amt = try handle.readPositionalAll(io, symtab_buffer, cmd.symoff + self.offset); if (amt != symtab_buffer.len) return error.InputOutput; } const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(symtab_buffer.ptr))[0..cmd.nsyms]; @@ -1697,7 +1738,7 @@ pub fn updateArSize(self: *Object, macho_file: *MachO) !void { }; } -pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writer: anytype) !void { +pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writer: *Writer) !void { // Header const size = try macho_file.cast(usize, self.output_ar_state.size); const basename = std.fs.path.basename(self.path); @@ -1705,10 +1746,12 @@ pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writ // Data const file = macho_file.getFileHandle(self.file_handle); // TODO try using copyRangeAll - const gpa = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; const data = try gpa.alloc(u8, size); defer gpa.free(data); - const amt = try file.preadAll(data, self.offset); + const amt = try file.readPositionalAll(io, data, self.offset); if (amt != size) return error.InputOutput; try writer.writeAll(data); } @@ -1813,7 +1856,9 @@ pub fn writeAtoms(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; const headers = self.sections.items(.header); const sections_data = try gpa.alloc([]const u8, headers.len); defer { @@ -1829,7 +1874,7 @@ pub fn writeAtoms(self: *Object, macho_file: *MachO) !void { if (header.isZerofill()) continue; const size = try macho_file.cast(usize, header.size); const data = try gpa.alloc(u8, size); - const amt = try file.preadAll(data, header.offset + self.offset); + const amt = try file.readPositionalAll(io, data, header.offset + self.offset); if (amt != data.len) return error.InputOutput; sections_data[n_sect] = data; } @@ -1852,7 +1897,9 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; const headers = self.sections.items(.header); const sections_data = try gpa.alloc([]const u8, headers.len); defer { @@ -1868,7 +1915,7 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void { if (header.isZerofill()) continue; const size = try macho_file.cast(usize, header.size); const data = try gpa.alloc(u8, size); - const amt = try file.preadAll(data, header.offset + self.offset); + const amt = try file.readPositionalAll(io, data, header.offset + self.offset); if (amt != data.len) return error.InputOutput; sections_data[n_sect] = data; } @@ -2484,11 +2531,11 @@ pub fn getUnwindRecord(self: *Object, index: UnwindInfo.Record.Index) *UnwindInf } /// Caller owns the memory. -pub fn readSectionData(self: Object, allocator: Allocator, file: File.Handle, n_sect: u8) ![]u8 { +pub fn readSectionData(self: Object, allocator: Allocator, io: Io, file: File.Handle, n_sect: u8) ![]u8 { const header = self.sections.items(.header)[n_sect]; const size = math.cast(usize, header.size) orelse return error.Overflow; const data = try allocator.alloc(u8, size); - const amt = try file.preadAll(data, header.offset + self.offset); + const amt = try file.readPositionalAll(io, data, header.offset + self.offset); errdefer allocator.free(data); if (amt != data.len) return error.InputOutput; return data; @@ -2712,15 +2759,17 @@ const x86_64 = struct { handle: File.Handle, macho_file: *MachO, ) !void { - const gpa = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info)); defer gpa.free(relocs_buffer); - const amt = try handle.preadAll(relocs_buffer, sect.reloff + self.offset); + const amt = try handle.readPositionalAll(io, relocs_buffer, sect.reloff + self.offset); if (amt != relocs_buffer.len) return error.InputOutput; const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(relocs_buffer.ptr))[0..sect.nreloc]; - const code = try self.readSectionData(gpa, handle, n_sect); + const code = try self.readSectionData(gpa, io, handle, n_sect); defer gpa.free(code); try out.ensureTotalCapacityPrecise(gpa, relocs.len); @@ -2879,15 +2928,17 @@ const aarch64 = struct { handle: File.Handle, macho_file: *MachO, ) !void { - const gpa = macho_file.base.comp.gpa; + const comp = macho_file.base.comp; + const io = comp.io; + const gpa = comp.gpa; const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info)); defer gpa.free(relocs_buffer); - const amt = try handle.preadAll(relocs_buffer, sect.reloff + self.offset); + const amt = try handle.readPositionalAll(io, relocs_buffer, sect.reloff + self.offset); if (amt != relocs_buffer.len) return error.InputOutput; const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(relocs_buffer.ptr))[0..sect.nreloc]; - const code = try self.readSectionData(gpa, handle, n_sect); + const code = try self.readSectionData(gpa, io, handle, n_sect); defer gpa.free(code); try out.ensureTotalCapacityPrecise(gpa, relocs.len); @@ -3063,27 +3114,3 @@ const aarch64 = struct { } } }; - -const std = @import("std"); -const assert = std.debug.assert; -const log = std.log.scoped(.link); -const macho = std.macho; -const math = std.math; -const mem = std.mem; -const Allocator = std.mem.Allocator; -const Writer = std.Io.Writer; - -const eh_frame = @import("eh_frame.zig"); -const trace = @import("../../tracy.zig").trace; -const Archive = @import("Archive.zig"); -const Atom = @import("Atom.zig"); -const Cie = eh_frame.Cie; -const Dwarf = @import("Dwarf.zig"); -const Fde = eh_frame.Fde; -const File = @import("file.zig").File; -const LoadCommandIterator = macho.LoadCommandIterator; -const MachO = @import("../MachO.zig"); -const Object = @This(); -const Relocation = @import("Relocation.zig"); -const Symbol = @import("Symbol.zig"); -const UnwindInfo = @import("UnwindInfo.zig"); diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index 5a4ea657906e406eb918cd80aa855bcdb8bf557f..49555c2746a46dc0ee9617aea5f0de67d35ef313 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -171,6 +171,9 @@ pub fn getAtomData(self: ZigObject, macho_file: *MachO, atom: Atom, buffer: []u8 const isec = atom.getInputSection(macho_file); assert(!isec.isZerofill()); + const comp = macho_file.base.comp; + const io = comp.io; + switch (isec.type()) { macho.S_THREAD_LOCAL_REGULAR => { 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 else => { const sect = macho_file.sections.items(.header)[atom.out_n_sect]; const file_offset = sect.offset + atom.value; - const amt = try macho_file.base.file.?.preadAll(buffer, file_offset); + const amt = try macho_file.base.file.?.readPositionalAll(io, buffer, file_offset); if (amt != buffer.len) return error.InputOutput; }, } @@ -290,12 +293,14 @@ pub fn dedupLiterals(self: *ZigObject, lp: MachO.LiteralPool, macho_file: *MachO /// We need this so that we can write to an archive. /// TODO implement writing ZigObject data directly to a buffer instead. pub fn readFileContents(self: *ZigObject, macho_file: *MachO) !void { - const diags = &macho_file.base.comp.link_diags; + const comp = macho_file.base.comp; + const gpa = comp.gpa; + const io = comp.io; + const diags = &comp.link_diags; // Size of the output object file is always the offset + size of the strtab const size = macho_file.symtab_cmd.stroff + macho_file.symtab_cmd.strsize; - const gpa = macho_file.base.comp.gpa; try self.data.resize(gpa, size); - const amt = macho_file.base.file.?.preadAll(self.data.items, 0) catch |err| + const amt = macho_file.base.file.?.readPositionalAll(io, self.data.items, 0) catch |err| return diags.fail("failed to read output file: {s}", .{@errorName(err)}); if (amt != size) return diags.fail("unexpected EOF reading from output file", .{}); @@ -945,6 +950,8 @@ fn updateNavCode( ) link.File.UpdateNavError!void { const zcu = pt.zcu; const gpa = zcu.gpa; + const comp = zcu.comp; + const io = comp.io; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); @@ -1012,8 +1019,8 @@ fn updateNavCode( if (!sect.isZerofill()) { const file_offset = sect.offset + atom.value; - macho_file.base.file.?.pwriteAll(code, file_offset) catch |err| - return macho_file.base.cgFail(nav_index, "failed to write output file: {s}", .{@errorName(err)}); + macho_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err| + return macho_file.base.cgFail(nav_index, "failed to write output file: {t}", .{err}); } } @@ -1493,7 +1500,7 @@ fn writeTrampoline(tr_sym: Symbol, target: Symbol, macho_file: *MachO) !void { .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf), else => @panic("TODO implement write trampoline for this CPU arch"), }; - try macho_file.base.file.?.pwriteAll(out, fileoff); + return macho_file.pwriteAll(out, fileoff); } pub fn getOrCreateMetadataForNav( diff --git a/src/link/MachO/fat.zig b/src/link/MachO/fat.zig index fd9a3025318f2439bf3fd1f3705e592715ffdb9a..73b9c626e83d15b98369217c6f7520670631d868 100644 --- a/src/link/MachO/fat.zig +++ b/src/link/MachO/fat.zig @@ -10,13 +10,13 @@ const mem = std.mem; const MachO = @import("../MachO.zig"); -pub fn readFatHeader(file: Io.File) !macho.fat_header { - return readFatHeaderGeneric(macho.fat_header, file, 0); +pub fn readFatHeader(io: Io, file: Io.File) !macho.fat_header { + return readFatHeaderGeneric(io, macho.fat_header, file, 0); } -fn readFatHeaderGeneric(comptime Hdr: type, file: Io.File, offset: usize) !Hdr { +fn readFatHeaderGeneric(io: Io, comptime Hdr: type, file: Io.File, offset: usize) !Hdr { var buffer: [@sizeOf(Hdr)]u8 = undefined; - const nread = try file.preadAll(&buffer, offset); + const nread = try file.readPositionalAll(io, &buffer, offset); if (nread != buffer.len) return error.InputOutput; var hdr = @as(*align(1) const Hdr, @ptrCast(&buffer)).*; mem.byteSwapAllFields(Hdr, &hdr); @@ -29,12 +29,12 @@ pub const Arch = struct { size: u32, }; -pub fn parseArchs(file: Io.File, fat_header: macho.fat_header, out: *[2]Arch) ![]const Arch { +pub fn parseArchs(io: Io, file: Io.File, fat_header: macho.fat_header, out: *[2]Arch) ![]const Arch { var count: usize = 0; var fat_arch_index: u32 = 0; while (fat_arch_index < fat_header.nfat_arch and count < out.len) : (fat_arch_index += 1) { const offset = @sizeOf(macho.fat_header) + @sizeOf(macho.fat_arch) * fat_arch_index; - const fat_arch = try readFatHeaderGeneric(macho.fat_arch, file, offset); + const fat_arch = try readFatHeaderGeneric(io, macho.fat_arch, file, offset); // If we come across an architecture that we do not know how to handle, that's // fine because we can keep looking for one that might match. const arch: std.Target.Cpu.Arch = switch (fat_arch.cputype) { diff --git a/src/link/MachO/hasher.zig b/src/link/MachO/hasher.zig index 2e0129d24028280ae41e50373d64a5bb7a8ca3bd..822474e3e1d68928a805db93cddb5cfda64fa84f 100644 --- a/src/link/MachO/hasher.zig +++ b/src/link/MachO/hasher.zig @@ -9,7 +9,7 @@ pub fn ParallelHasher(comptime Hasher: type) type { const hash_size = Hasher.digest_length; return struct { - pub fn hash(self: Self, io: Io, file: Io.File, out: [][hash_size]u8, opts: struct { + pub fn hash(gpa: Allocator, io: Io, file: Io.File, out: [][hash_size]u8, opts: struct { chunk_size: u64 = 0x4000, max_file_size: ?u64 = null, }) !void { @@ -22,11 +22,11 @@ pub fn ParallelHasher(comptime Hasher: type) type { }; const chunk_size = std.math.cast(usize, opts.chunk_size) orelse return error.Overflow; - const buffer = try self.allocator.alloc(u8, chunk_size * out.len); - defer self.allocator.free(buffer); + const buffer = try gpa.alloc(u8, chunk_size * out.len); + defer gpa.free(buffer); - const results = try self.allocator.alloc(Io.File.ReadPositionalError!usize, out.len); - defer self.allocator.free(results); + const results = try gpa.alloc(Io.File.ReadPositionalError!usize, out.len); + defer gpa.free(results); { var group: Io.Group = .init; @@ -38,7 +38,8 @@ pub fn ParallelHasher(comptime Hasher: type) type { file_size - fstart else chunk_size; - group.async(worker, .{ + group.async(io, worker, .{ + io, file, fstart, buffer[fstart..][0..fsize], @@ -53,16 +54,15 @@ pub fn ParallelHasher(comptime Hasher: type) type { } fn worker( + io: Io, file: Io.File, fstart: usize, buffer: []u8, out: *[hash_size]u8, err: *Io.File.ReadPositionalError!usize, ) void { - err.* = file.readPositionalAll(buffer, fstart); + err.* = file.readPositionalAll(io, buffer, fstart); Hasher.hash(buffer, out, .{}); } - - const Self = @This(); }; } diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index 6bddfc63897583d1c838e244f4a4fbd375fcda21..13dd35a558101cb52e303084d71b6929bcb38e9b 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -10,10 +10,10 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat positionals.appendSliceAssumeCapacity(comp.link_inputs); for (comp.c_object_table.keys()) |key| { - try positionals.append(try link.openObjectInput(diags, key.status.success.object_path)); + try positionals.append(try link.openObjectInput(io, diags, key.status.success.object_path)); } - if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); + if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path)); if (macho_file.getZigObject() == null and positionals.items.len == 1) { // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all @@ -24,10 +24,8 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) }); const stat = in_file.stat(io) catch |err| return diags.fail("failed to stat {f}: {s}", .{ path, @errorName(err) }); - const amt = in_file.copyRangeAll(0, macho_file.base.file.?, 0, stat.size) catch |err| - return diags.fail("failed to copy range of file {f}: {s}", .{ path, @errorName(err) }); - if (amt != stat.size) - return diags.fail("unexpected short write in copy range of file {f}", .{path}); + link.File.copyRangeAll2(io, in_file, macho_file.base.file.?, 0, 0, stat.size) catch |err| + return diags.fail("failed to copy range of file {f}: {t}", .{ path, err }); return; } @@ -90,17 +88,17 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? positionals.appendSliceAssumeCapacity(comp.link_inputs); for (comp.c_object_table.keys()) |key| { - try positionals.append(try link.openObjectInput(diags, key.status.success.object_path)); + try positionals.append(try link.openObjectInput(io, diags, key.status.success.object_path)); } - if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); + if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path)); if (comp.compiler_rt_strat == .obj) { - try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path)); + try positionals.append(try link.openObjectInput(io, diags, comp.compiler_rt_obj.?.full_object_path)); } if (comp.ubsan_rt_strat == .obj) { - try positionals.append(try link.openObjectInput(diags, comp.ubsan_rt_obj.?.full_object_path)); + try positionals.append(try link.openObjectInput(io, diags, comp.ubsan_rt_obj.?.full_object_path)); } for (positionals.items) |link_input| { @@ -231,7 +229,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? assert(writer.end == total_size); - try macho_file.setLength(io, total_size); + try macho_file.setLength(total_size); try macho_file.pwriteAll(writer.buffered(), 0); if (diags.hasErrors()) return error.LinkFailure; diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index d13caaa315abe9552614c437151d124d963f2fef..4dbdd5c089f8ca57624f7a26bf4168e24d0d81d8 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -246,6 +246,7 @@ pub fn flush( const comp = linker.base.comp; const diags = &comp.link_diags; const gpa = comp.gpa; + const io = comp.io; // We need to export the list of error names somewhere so that we can pretty-print them in the // executor. This is not really an important thing though, so we can just dump it in any old @@ -287,8 +288,8 @@ pub fn flush( }; // TODO endianness bug. use file writer and call writeSliceEndian instead - linker.base.file.?.writeAll(@ptrCast(linked_module)) catch |err| - return diags.fail("failed to write: {s}", .{@errorName(err)}); + linker.base.file.?.writeStreamingAll(io, @ptrCast(linked_module)) catch |err| + return diags.fail("failed to write: {t}", .{err}); } fn linkModule(arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 39e3bb3d5d4def35c3288e3dc7edfd5eaac69cab..a8140327e53540da002992ba7e19812c6553ef25 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3016,8 +3016,10 @@ pub fn createEmpty( } fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void { - const diags = &wasm.base.comp.link_diags; - const obj = link.openObject(path, false, false) catch |err| { + const comp = wasm.base.comp; + const io = comp.io; + const diags = &comp.link_diags; + const obj = link.openObject(io, path, false, false) catch |err| { switch (diags.failParse(path, "failed to open object: {t}", .{err})) { error.LinkFailure => return, } diff --git a/src/link/Wasm/Flush.zig b/src/link/Wasm/Flush.zig index 6f7792f473bb1a6ecc4b04624790cc5c0166ee55..b189e2e60079160b55f2e741618b6078726e7581 100644 --- a/src/link/Wasm/Flush.zig +++ b/src/link/Wasm/Flush.zig @@ -108,6 +108,7 @@ pub fn deinit(f: *Flush, gpa: Allocator) void { pub fn finish(f: *Flush, wasm: *Wasm) !void { const comp = wasm.base.comp; + const io = comp.io; const shared_memory = comp.config.shared_memory; const diags = &comp.link_diags; const gpa = comp.gpa; @@ -1067,7 +1068,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { } // Finally, write the entire binary into the file. - var file_writer = wasm.base.file.?.writer(&.{}); + var file_writer = wasm.base.file.?.writer(io, &.{}); file_writer.interface.writeAll(binary_bytes.items) catch |err| switch (err) { error.WriteFailed => return file_writer.err.?, }; diff --git a/src/link/tapi.zig b/src/link/tapi.zig index 046f43eae2cd8b012c0140a8f2eaee45e3ea7010..33c31a8415084cd279e89a8d3f7e7bbc4fa7572c 100644 --- a/src/link/tapi.zig +++ b/src/link/tapi.zig @@ -130,7 +130,7 @@ pub const Tbd = union(enum) { pub const TapiError = error{ NotLibStub, InputOutput, -} || yaml.YamlError || Io.File.PReadError; +} || yaml.YamlError || Io.File.ReadPositionalError; pub const LibStub = struct { /// Underlying memory for stub's contents. @@ -146,7 +146,7 @@ pub const LibStub = struct { }; const source = try allocator.alloc(u8, filesize); defer allocator.free(source); - const amt = try file.preadAll(source, 0); + const amt = try file.readPositionalAll(io, source, 0); if (amt != filesize) return error.InputOutput; var lib_stub = LibStub{ diff --git a/src/main.zig b/src/main.zig index 47e5af23da80e900b8c9f156a1d36b4e4be1a763..c1f5e3c02689f2952542e2588a6d223bc1e49676 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3677,7 +3677,7 @@ fn buildOutputType( } { - const root_prog_node = std.Progress.start(.{ + const root_prog_node = std.Progress.start(io, .{ .disable_printing = (color == .off), }); defer root_prog_node.end(); -- 2.54.0 From e68ae8d7a1e78a25392e271d7ca894e0f09aa218 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 12 Dec 2025 17:18:29 -0800 Subject: [PATCH 054/195] update uses of std.debug.lockStdErr --- lib/compiler/test_runner.zig | 17 ++++++++++++----- lib/std/debug/simple_panic.zig | 6 +++--- lib/std/process.zig | 24 +++++++++++------------- src/main.zig | 10 +++++----- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index b0ef17a7e9e541385fbd1e4fd407c46cda17139d..f7f59a5bf37c27e3940dafad6219591ca12f465c 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -405,15 +405,22 @@ pub fn fuzz( testOne(ctx, input.toSlice()) catch |err| switch (err) { error.SkipZigTest => return, else => { - std.debug.lockStdErr(); - if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace); - std.debug.print("failed with error.{t}\n", .{err}); + const stderr = std.debug.lockStderrWriter(&.{}); + p: { + if (@errorReturnTrace()) |trace| { + std.debug.writeStackTrace(trace, &stderr.interface, stderr.mode) catch break :p; + } + stderr.interface.print("failed with error.{t}\n", .{err}) catch break :p; + stderr.interface.flush() catch break :p; + } + stderr.interface.flush() catch {}; std.process.exit(1); }, }; if (log_err_count != 0) { - std.debug.lockStdErr(); - std.debug.print("error logs detected\n", .{}); + const stderr = std.debug.lockStderrWriter(&.{}); + stderr.interface.print("error logs detected\n", .{}) catch {}; + stderr.interface.flush() catch {}; std.process.exit(1); } } diff --git a/lib/std/debug/simple_panic.zig b/lib/std/debug/simple_panic.zig index f6ff77e04f561a2f699f2867dcf7bc664c559148..2d0e5abf4e7c4ddee7315b510a7cb52476cef2a5 100644 --- a/lib/std/debug/simple_panic.zig +++ b/lib/std/debug/simple_panic.zig @@ -14,9 +14,9 @@ const std = @import("../std.zig"); pub fn call(msg: []const u8, ra: ?usize) noreturn { @branchHint(.cold); _ = ra; - std.debug.lockStdErr(); - const stderr: std.Io.File = .stderr(); - stderr.writeAll(msg) catch {}; + const stderr = std.debug.lockStderrWriter(&.{}); + stderr.interface.writeAll(msg) catch {}; + stderr.interface.flush(msg) catch {}; @trap(); } diff --git a/lib/std/process.zig b/lib/std/process.zig index 5c6e6f89eb556045b4e03c89892e1a0ff0ef77e9..216e793cbe2a49b8d997d8b8dfccf7d3a5caa5b1 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -1841,21 +1841,19 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 { } } -/// Indicate that we are now terminating with a successful exit code. -/// In debug builds, this is a no-op, so that the calling code's -/// cleanup mechanisms are tested and so that external tools that -/// check for resource leaks can be accurate. In release builds, this -/// calls exit(0), and does not return. -pub fn cleanExit() void { - if (builtin.mode == .Debug) { - return; - } else { - std.debug.lockStdErr(); - exit(0); - } +/// Indicate intent to terminate with a successful exit code. +/// +/// In debug builds, this is a no-op, so that the calling code's cleanup +/// mechanisms are tested and so that external tools checking for resource +/// leaks can be accurate. In release builds, this calls `exit` with code zero, +/// and does not return. +pub fn cleanExit(io: Io) void { + if (builtin.mode == .Debug) return; + _ = io.lockStderrWriter(&.{}); + exit(0); } -/// Raise the open file descriptor limit. +/// Request ability to have more open file descriptors simultaneously. /// /// On some systems, this raises the limit before seeing ProcessFdQuotaExceeded /// errors. On other systems, this does nothing. diff --git a/src/main.zig b/src/main.zig index c1f5e3c02689f2952542e2588a6d223bc1e49676..160b9919eb3af68c0acb6df60e551d97f91b5d2f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4429,12 +4429,12 @@ fn runOrTest( // the error message and invocation below. if (process.can_execv and arg_mode == .run) { // execv releases the locks; no need to destroy the Compilation here. - std.debug.lockStdErr(); + _ = std.debug.lockStderrWriter(&.{}); const err = process.execve(gpa, argv.items, &env_map); - std.debug.unlockStdErr(); + std.debug.unlockStderrWriter(); try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); const cmd = try std.mem.join(arena, " ", argv.items); - fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd }); + fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd }); } else if (process.can_spawn) { var child = std.process.Child.init(argv.items, gpa); child.env_map = &env_map; @@ -4448,8 +4448,8 @@ fn runOrTest( comp_destroyed.* = true; const term_result = t: { - std.debug.lockStdErr(); - defer std.debug.unlockStdErr(); + _ = std.debug.lockStderrWriter(); + defer std.debug.unlockStderrWriter(); break :t child.spawnAndWait(io); }; const term = term_result catch |err| { -- 2.54.0 From ec56696503e702e063af8740a77376b2e7694c29 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 12 Dec 2025 17:20:18 -0800 Subject: [PATCH 055/195] std.process.cleanExit: take an Io parameter In case exit(0) will be called, this provides the opportunity for the application's Io instance to be the one to clear the terminal in case std.Progress or similar was used. --- lib/compiler/libc.zig | 4 ++-- lib/compiler/objcopy.zig | 4 ++-- lib/compiler/reduce.zig | 6 ++--- src/fmt.zig | 2 +- src/main.zig | 50 ++++++++++++++++++++-------------------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/lib/compiler/libc.zig b/lib/compiler/libc.zig index 7a6edf0c44dec4f5a6d2a3e324dfc6c9c11d8b2b..1aa9a587ae32d35405a85dbd627ce6b4ebb18205 100644 --- a/lib/compiler/libc.zig +++ b/lib/compiler/libc.zig @@ -50,7 +50,7 @@ pub fn main() !void { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { try stdout.writeAll(usage_libc); try stdout.flush(); - return std.process.cleanExit(); + return std.process.cleanExit(io); } else if (mem.eql(u8, arg, "-target")) { if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; @@ -110,7 +110,7 @@ pub fn main() !void { try stdout.writeByte('\n'); } try stdout.flush(); - return std.process.cleanExit(); + return std.process.cleanExit(io); } if (input_file) |libc_file| { diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 90fd122294ea7d5c935b8b48e651271376dd1662..2e272b074b83b8fc5c2cacf399303b82d72b325d 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -235,7 +235,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void const hdr = try server.receiveMessage(); switch (hdr.tag) { .exit => { - return std.process.cleanExit(); + return std.process.cleanExit(io); }, .update => { if (seen_update) fatal("zig objcopy only supports 1 update for now", .{}); @@ -250,7 +250,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void } } } - return std.process.cleanExit(); + return std.process.cleanExit(io); } const usage = diff --git a/lib/compiler/reduce.zig b/lib/compiler/reduce.zig index 0c61111d10297b12555f78662a431ead685a9a8d..a93db3a6d7a20ac1e5d491c8dfd494c151ce8567 100644 --- a/lib/compiler/reduce.zig +++ b/lib/compiler/reduce.zig @@ -75,7 +75,7 @@ pub fn main() !void { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { const stdout = Io.File.stdout(); try stdout.writeAll(usage); - return std.process.cleanExit(); + return std.process.cleanExit(io); } else if (mem.eql(u8, arg, "--")) { argv = args[i + 1 ..]; break; @@ -278,10 +278,10 @@ pub fn main() !void { try tree.render(gpa, &rendered.writer, fixups); try Io.Dir.cwd().writeFile(io, .{ .sub_path = root_source_file_path, .data = rendered.written() }); - return std.process.cleanExit(); + return std.process.cleanExit(io); } std.debug.print("no more transformations found\n", .{}); - return std.process.cleanExit(); + return std.process.cleanExit(io); } fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random) void { diff --git a/src/fmt.zig b/src/fmt.zig index bec3496e683507387e4a84f97827b43f8be66dec..55a68d1dc8b5467d7fbcfdad0908703dbd278284 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -60,7 +60,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { try Io.File.stdout().writeStreamingAll(io, usage_fmt); - return process.cleanExit(); + return process.cleanExit(io); } else if (mem.eql(u8, arg, "--color")) { if (i + 1 >= args.len) { fatal("expected [auto|on|off] after --color", .{}); diff --git a/src/main.zig b/src/main.zig index 160b9919eb3af68c0acb6df60e551d97f91b5d2f..115c1b521271d62009e9e413539c4feb4dd444e3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1038,7 +1038,7 @@ fn buildOutputType( } else if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { try Io.File.stdout().writeStreamingAll(io, usage_build_generic); - return cleanExit(); + return cleanExit(io); } else if (mem.eql(u8, arg, "--")) { if (arg_mode == .run) { // args_iter.i is 1, referring the next arg after "--" in ["--", ...] @@ -3646,7 +3646,7 @@ fn buildOutputType( all_args, runtime_args_start, ); - return cleanExit(); + return cleanExit(io); }, .ip4 => |ip4_addr| { const addr: Io.net.IpAddress = .{ .ip4 = ip4_addr }; @@ -3672,7 +3672,7 @@ fn buildOutputType( all_args, runtime_args_start, ); - return cleanExit(); + return cleanExit(io); }, } @@ -3755,7 +3755,7 @@ fn buildOutputType( } // Skip resource deallocation in release builds; let the OS do it. - return cleanExit(); + return cleanExit(io); } const CreateModule = struct { @@ -4176,7 +4176,7 @@ fn serve( defer if (comp.debugIncremental()) ids.mutex.unlock(io); switch (hdr.tag) { - .exit => return cleanExit(), + .exit => return cleanExit(io), .update => { tracy.frameMark(); file_system_inputs.clearRetainingCapacity(); @@ -4462,7 +4462,7 @@ fn runOrTest( switch (term) { .Exited => |code| { if (code == 0) { - return cleanExit(); + return cleanExit(io); } else { process.exit(code); } @@ -4476,7 +4476,7 @@ fn runOrTest( switch (term) { .Exited => |code| { if (code == 0) { - return cleanExit(); + return cleanExit(io); } else { const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd }); @@ -4688,7 +4688,7 @@ fn cmdTranslateC( var file_reader = zig_file.reader(io, &.{}); _ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited); try stdout_writer.interface.flush(); - return cleanExit(); + return cleanExit(io); } } @@ -4735,7 +4735,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! template = .minimal; } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { try Io.File.stdout().writeStreamingAll(io, usage_init); - return cleanExit(); + return cleanExit(io); } else { fatal("unrecognized parameter: '{s}'", .{arg}); } @@ -4780,7 +4780,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! if (ok_count == template_paths.len) { std.log.info("see `zig build --help` for a menu of options", .{}); } - return cleanExit(); + return cleanExit(io); }, .minimal => { writeSimpleTemplateFile(io, Package.Manifest.basename, @@ -4813,11 +4813,11 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! // their `build.zig.zon` *after* writing their `build.zig`. So this one isn't fatal. error.PathAlreadyExists => { std.log.info("successfully populated '{s}', preserving existing '{s}'", .{ Package.Manifest.basename, Package.build_zig_basename }); - return cleanExit(); + return cleanExit(io); }, }; std.log.info("successfully populated '{s}' and '{s}'", .{ Package.Manifest.basename, Package.build_zig_basename }); - return cleanExit(); + return cleanExit(io); }, } } @@ -5284,7 +5284,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) process.exit(1); } - if (fetch_only) return cleanExit(); + if (fetch_only) return cleanExit(io); var source_buf = std.array_list.Managed(u8).init(gpa); defer source_buf.deinit(); @@ -5420,7 +5420,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) switch (term) { .Exited => |code| { - if (code == 0) return cleanExit(); + if (code == 0) return cleanExit(io); // Indicates that the build runner has reported compile errors // and this parent process does not need to report any further // diagnostics. @@ -5691,7 +5691,7 @@ fn jitCmd( .Exited => |code| { if (code == 0) { if (options.capture != null) return; - return cleanExit(); + return cleanExit(io); } const cmd = try std.mem.join(arena, " ", child_argv.items); fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); @@ -6151,7 +6151,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { try Io.File.stdout().writeStreamingAll(io, usage_ast_check); - return cleanExit(); + return cleanExit(io); } else if (mem.eql(u8, arg, "-t")) { want_output_text = true; } else if (mem.eql(u8, arg, "--zon")) { @@ -6222,7 +6222,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { if (zir.hasCompileErrors()) { process.exit(1); } else { - return cleanExit(); + return cleanExit(io); } } if (!build_options.enable_debug_extensions) { @@ -6273,7 +6273,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { if (zir.hasCompileErrors()) { process.exit(1); } else { - return cleanExit(); + return cleanExit(io); } }, .zon => { @@ -6288,7 +6288,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { } if (!want_output_text) { - return cleanExit(); + return cleanExit(io); } if (!build_options.enable_debug_extensions) { @@ -6297,7 +6297,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { try @import("print_zoir.zig").renderToWriter(zoir, arena, stdout_bw); try stdout_bw.flush(); - return cleanExit(); + return cleanExit(io); }, } } @@ -6325,7 +6325,7 @@ fn cmdDetectCpu(io: Io, args: []const []const u8) !void { if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { try Io.File.stdout().writeStreamingAll(io, detect_cpu_usage); - return cleanExit(); + return cleanExit(io); } else if (mem.eql(u8, arg, "--llvm")) { use_llvm = true; } else { @@ -6475,7 +6475,7 @@ fn cmdDumpLlvmInts( } try stdout_bw.flush(); - return cleanExit(); + return cleanExit(io); } /// This is only enabled for debug builds. @@ -6909,7 +6909,7 @@ fn cmdFetch( if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { try Io.File.stdout().writeStreamingAll(io, usage_fetch); - return cleanExit(); + return cleanExit(io); } else if (mem.eql(u8, arg, "--global-cache-dir")) { if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); i += 1; @@ -7020,7 +7020,7 @@ fn cmdFetch( var stdout = Io.File.stdout().writerStreaming(io, &stdout_buffer); try stdout.interface.print("{s}\n", .{package_hash_slice}); try stdout.interface.flush(); - return cleanExit(); + return cleanExit(io); }, .yes, .exact => |name| name: { if (name) |n| break :name n; @@ -7159,7 +7159,7 @@ fn cmdFetch( fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err }); }; - return cleanExit(); + return cleanExit(io); } fn createEmptyDependenciesModule( -- 2.54.0 From 1925e0319f1337b4856bd5a181bf4f6d3ac7d428 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 12 Dec 2025 19:04:35 -0800 Subject: [PATCH 056/195] update lockStderrWriter sites use the application's Io implementation where possible. This correctly makes writing to stderr cancelable, fallible, and participate in the application's event loop. It also removes one more hard-coded dependency on a secondary Io implementation. --- lib/compiler/build_runner.zig | 18 +++--- lib/compiler/resinator/cli.zig | 8 +-- lib/compiler/resinator/errors.zig | 8 +-- lib/compiler/resinator/main.zig | 98 ++++++++++++++++--------------- lib/compiler/std-docs.zig | 2 +- lib/compiler/test_runner.zig | 3 - lib/std/Build.zig | 31 +++++++--- lib/std/Build/Fuzz.zig | 21 +++---- lib/std/Build/Step/Compile.zig | 17 +++--- lib/std/Build/Step/Run.zig | 5 +- lib/std/Build/WebServer.zig | 2 +- lib/std/Progress.zig | 2 +- lib/std/json/dynamic.zig | 5 +- lib/std/process.zig | 2 +- lib/std/testing.zig | 16 +++-- lib/std/zig.zig | 4 +- lib/std/zig/ErrorBundle.zig | 10 ++-- lib/std/zig/parser_test.zig | 30 ++++++---- src/Air/print.zig | 28 +++++---- src/Compilation.zig | 53 +++++++++-------- src/InternPool.zig | 65 ++++++++++---------- src/Sema.zig | 8 ++- src/Zcu/PerThread.zig | 5 +- src/codegen/aarch64/Select.zig | 15 +++-- src/crash_report.zig | 9 +-- src/fmt.zig | 8 +-- src/libs/mingw.zig | 29 +++++---- src/libs/mingw/def.zig | 32 ++++++---- src/link.zig | 2 +- src/link/Coff.zig | 15 +++-- src/link/Elf2.zig | 14 +++-- src/main.zig | 33 ++++++----- 32 files changed, 348 insertions(+), 250 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index acba1a4621a43047250b4f6b73e5961c56cfa777..d284d85747565f371b2cecca856a52d7402a85db 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -522,7 +522,7 @@ pub fn main() !void { // Perhaps in the future there could be an Advanced Options flag // such as --debug-build-runner-leaks which would make this code // return instead of calling exit. - _ = std.debug.lockStderrWriter(&.{}); + _ = io.lockStderrWriter(&.{}) catch {}; process.exit(1); }, else => |e| return e, @@ -554,8 +554,8 @@ pub fn main() !void { } rebuild: while (true) : (if (run.error_style.clearOnUpdate()) { - const stderr = std.debug.lockStderrWriter(&stdio_buffer_allocation); - defer std.debug.unlockStderrWriter(); + const stderr = try io.lockStderrWriter(&stdio_buffer_allocation); + defer io.unlockStderrWriter(); try stderr.writeAllUnescaped("\x1B[2J\x1B[3J\x1B[H"); }) { if (run.web_server) |*ws| ws.startBuild(); @@ -856,8 +856,8 @@ fn runStepNames( .none => break :summary, } - const stderr = std.debug.lockStderrWriter(&stdio_buffer_allocation); - defer std.debug.unlockStderrWriter(); + const stderr = try io.lockStderrWriter(&stdio_buffer_allocation); + defer io.unlockStderrWriter(); const w = &stderr.interface; const fwm = stderr.mode; @@ -954,7 +954,7 @@ fn runStepNames( if (run.error_style.verboseContext()) break :code 1; // failure; print build command break :code 2; // failure; do not print build command }; - _ = std.debug.lockStderrWriter(&.{}); + _ = io.lockStderrWriter(&.{}) catch {}; process.exit(code); } @@ -1369,8 +1369,10 @@ fn workerMakeOneStep( const show_error_msgs = s.result_error_msgs.items.len > 0; const show_stderr = s.result_stderr.len > 0; if (show_error_msgs or show_compile_errors or show_stderr) { - const stderr = std.debug.lockStderrWriter(&stdio_buffer_allocation); - defer std.debug.unlockStderrWriter(); + const stderr = io.lockStderrWriter(&stdio_buffer_allocation) catch |err| switch (err) { + error.Canceled => return, + }; + defer io.unlockStderrWriter(); printErrorMessages(gpa, s, .{}, &stderr.interface, stderr.mode, run.error_style, run.multiline_errors) catch {}; } diff --git a/lib/compiler/resinator/cli.zig b/lib/compiler/resinator/cli.zig index d2dd71b1f6878eaab2cec7a15a225de1fcf7f5c5..08befbe1fa92dd908a7c37f492290075bb31378b 100644 --- a/lib/compiler/resinator/cli.zig +++ b/lib/compiler/resinator/cli.zig @@ -125,10 +125,10 @@ pub const Diagnostics = struct { try self.errors.append(self.allocator, error_details); } - pub fn renderToStdErr(self: *Diagnostics, args: []const []const u8) void { - const stderr, const ttyconf = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - self.renderToWriter(args, stderr, ttyconf) catch return; + pub fn renderToStderr(self: *Diagnostics, io: Io, args: []const []const u8) void { + const stderr = io.lockStderrWriter(&.{}); + defer io.unlockStderrWriter(); + self.renderToWriter(args, &stderr.interface, stderr.mode) catch return; } pub fn renderToWriter(self: *Diagnostics, args: []const []const u8, writer: *std.Io.Writer, config: std.Io.tty.Config) !void { diff --git a/lib/compiler/resinator/errors.zig b/lib/compiler/resinator/errors.zig index 14ff28a697355960dd3e20a314344c42eef84d67..031b5d3574a62241404bb2141e6c556033a88bb8 100644 --- a/lib/compiler/resinator/errors.zig +++ b/lib/compiler/resinator/errors.zig @@ -67,12 +67,12 @@ pub const Diagnostics = struct { return @intCast(index); } - pub fn renderToStdErr(self: *Diagnostics, cwd: Io.Dir, source: []const u8, source_mappings: ?SourceMappings) void { + pub fn renderToStderr(self: *Diagnostics, cwd: Io.Dir, source: []const u8, source_mappings: ?SourceMappings) void { const io = self.io; - const stderr, const ttyconf = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); + const stderr = io.lockStderrWriter(&.{}); + defer io.unlockStderrWriter(); for (self.errors.items) |err_details| { - renderErrorMessage(io, stderr, ttyconf, cwd, err_details, source, self.strings.items, source_mappings) catch return; + renderErrorMessage(io, &stderr.interface, stderr.mode, cwd, err_details, source, self.strings.items, source_mappings) catch return; } } diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 6c12903f06f3bde8918c20b51beca360c3b1a41a..bd2fcc912e32f84d3bb347c38a5ca78a6c85e025 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -24,6 +24,10 @@ pub fn main() !void { defer std.debug.assert(debug_allocator.deinit() == .ok); const gpa = debug_allocator.allocator(); + var threaded: std.Io.Threaded = .init(gpa); + defer threaded.deinit(); + const io = threaded.io(); + var arena_state = std.heap.ArenaAllocator.init(gpa); defer arena_state.deinit(); const arena = arena_state.allocator(); @@ -31,8 +35,8 @@ pub fn main() !void { const args = try std.process.argsAlloc(arena); if (args.len < 2) { - const w, const ttyconf = std.debug.lockStderrWriter(&.{}); - try renderErrorMessage(w, ttyconf, .err, "expected zig lib dir as first argument", .{}); + const stderr = io.lockStderrWriter(&.{}); + try renderErrorMessage(&stderr.interface, stderr.mode, .err, "expected zig lib dir as first argument", .{}); std.process.exit(1); } const zig_lib_dir = args[1]; @@ -45,7 +49,7 @@ pub fn main() !void { } var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; var error_handler: ErrorHandler = switch (zig_integration) { true => .{ @@ -71,24 +75,20 @@ pub fn main() !void { if (!zig_integration) { // print any warnings/notes - cli_diagnostics.renderToStdErr(cli_args); + cli_diagnostics.renderToStderr(io, cli_args); // If there was something printed, then add an extra newline separator // so that there is a clear separation between the cli diagnostics and whatever // gets printed after if (cli_diagnostics.errors.items.len > 0) { - const stderr, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - try stderr.writeByte('\n'); + const stderr = io.lockStderrWriter(&.{}); + defer io.unlockStderrWriter(); + try stderr.interface.writeByte('\n'); } } break :options options; }; defer options.deinit(); - var threaded: std.Io.Threaded = .init(gpa); - defer threaded.deinit(); - const io = threaded.io(); - if (options.print_help_and_exit) { try cli.writeUsage(stdout, "zig rc"); try stdout.flush(); @@ -130,10 +130,10 @@ pub fn main() !void { var stderr_buf: [512]u8 = undefined; var diagnostics: aro.Diagnostics = .{ .output = output: { if (zig_integration) break :output .{ .to_list = .{ .arena = .init(gpa) } }; - const w, const ttyconf = std.debug.lockStderrWriter(&stderr_buf); + const stderr = io.lockStderrWriter(&stderr_buf); break :output .{ .to_writer = .{ - .writer = w, - .color = ttyconf, + .writer = &stderr.interface, + .color = stderr.mode, } }; } }; defer { @@ -175,11 +175,11 @@ pub fn main() !void { std.process.exit(1); }, error.FileTooBig => { - try error_handler.emitMessage(gpa, .err, "failed during preprocessing: maximum file size exceeded", .{}); + try error_handler.emitMessage(gpa, io, .err, "failed during preprocessing: maximum file size exceeded", .{}); std.process.exit(1); }, error.WriteFailed => { - try error_handler.emitMessage(gpa, .err, "failed during preprocessing: error writing the preprocessed output", .{}); + try error_handler.emitMessage(gpa, io, .err, "failed during preprocessing: error writing the preprocessed output", .{}); std.process.exit(1); }, error.OutOfMemory => |e| return e, @@ -191,13 +191,13 @@ pub fn main() !void { .stdio => |file| { var file_reader = file.reader(io, &.{}); break :full_input file_reader.interface.allocRemaining(gpa, .unlimited) catch |err| { - try error_handler.emitMessage(gpa, .err, "unable to read input from stdin: {s}", .{@errorName(err)}); + try error_handler.emitMessage(gpa, io, .err, "unable to read input from stdin: {s}", .{@errorName(err)}); std.process.exit(1); }; }, .filename => |input_filename| { break :full_input Io.Dir.cwd().readFileAlloc(input_filename, gpa, .unlimited) catch |err| { - try error_handler.emitMessage(gpa, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) }); + try error_handler.emitMessage(gpa, io, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) }); std.process.exit(1); }; }, @@ -228,12 +228,12 @@ pub fn main() !void { } else if (options.input_format == .res) IoStream.fromIoSource(options.input_source, .input) catch |err| { - try error_handler.emitMessage(gpa, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) }); + try error_handler.emitMessage(gpa, io, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) }); std.process.exit(1); } else IoStream.fromIoSource(options.output_source, .output) catch |err| { - try error_handler.emitMessage(gpa, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); + try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); std.process.exit(1); }; defer res_stream.deinit(gpa); @@ -246,17 +246,17 @@ pub fn main() !void { var mapping_results = parseAndRemoveLineCommands(gpa, full_input, full_input, .{ .initial_filename = options.input_source.filename }) catch |err| switch (err) { error.InvalidLineCommand => { // TODO: Maybe output the invalid line command - try error_handler.emitMessage(gpa, .err, "invalid line command in the preprocessed source", .{}); + try error_handler.emitMessage(gpa, io, .err, "invalid line command in the preprocessed source", .{}); if (options.preprocess == .no) { - try error_handler.emitMessage(gpa, .note, "line commands must be of the format: #line \"\"", .{}); + try error_handler.emitMessage(gpa, io, .note, "line commands must be of the format: #line \"\"", .{}); } else { - try error_handler.emitMessage(gpa, .note, "this is likely to be a bug, please report it", .{}); + try error_handler.emitMessage(gpa, io, .note, "this is likely to be a bug, please report it", .{}); } std.process.exit(1); }, error.LineNumberOverflow => { // TODO: Better error message - try error_handler.emitMessage(gpa, .err, "line number count exceeded maximum of {}", .{std.math.maxInt(usize)}); + try error_handler.emitMessage(gpa, io, .err, "line number count exceeded maximum of {}", .{std.math.maxInt(usize)}); std.process.exit(1); }, error.OutOfMemory => |e| return e, @@ -306,13 +306,13 @@ pub fn main() !void { // print any warnings/notes if (!zig_integration) { - diagnostics.renderToStdErr(Io.Dir.cwd(), final_input, mapping_results.mappings); + diagnostics.renderToStderr(io, Io.Dir.cwd(), final_input, mapping_results.mappings); } // write the depfile if (options.depfile_path) |depfile_path| { var depfile = Io.Dir.cwd().createFile(io, depfile_path, .{}) catch |err| { - try error_handler.emitMessage(gpa, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) }); + try error_handler.emitMessage(gpa, io, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) }); std.process.exit(1); }; defer depfile.close(io); @@ -340,7 +340,7 @@ pub fn main() !void { if (options.output_format != .coff) return; break :res_data res_stream.source.readAll(gpa, io) catch |err| { - try error_handler.emitMessage(gpa, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) }); + try error_handler.emitMessage(gpa, io, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) }); std.process.exit(1); }; }; @@ -353,14 +353,14 @@ pub fn main() !void { var res_reader: std.Io.Reader = .fixed(res_data.bytes); break :resources cvtres.parseRes(gpa, &res_reader, .{ .max_size = res_data.bytes.len }) catch |err| { // TODO: Better errors - try error_handler.emitMessage(gpa, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) }); + try error_handler.emitMessage(gpa, io, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) }); std.process.exit(1); }; }; defer resources.deinit(); var coff_stream = IoStream.fromIoSource(options.output_source, .output) catch |err| { - try error_handler.emitMessage(gpa, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); + try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); std.process.exit(1); }; defer coff_stream.deinit(gpa); @@ -373,7 +373,7 @@ pub fn main() !void { switch (err) { error.DuplicateResource => { const duplicate_resource = resources.list.items[cvtres_diagnostics.duplicate_resource]; - try error_handler.emitMessage(gpa, .err, "duplicate resource [id: {f}, type: {f}, language: {f}]", .{ + try error_handler.emitMessage(gpa, io, .err, "duplicate resource [id: {f}, type: {f}, language: {f}]", .{ duplicate_resource.name_value, fmtResourceType(duplicate_resource.type_value), duplicate_resource.language, @@ -381,8 +381,8 @@ pub fn main() !void { }, error.ResourceDataTooLong => { const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource]; - try error_handler.emitMessage(gpa, .err, "resource has a data length that is too large to be written into a coff section", .{}); - try error_handler.emitMessage(gpa, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{ + try error_handler.emitMessage(gpa, io, .err, "resource has a data length that is too large to be written into a coff section", .{}); + try error_handler.emitMessage(gpa, io, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{ overflow_resource.name_value, fmtResourceType(overflow_resource.type_value), overflow_resource.language, @@ -390,15 +390,15 @@ pub fn main() !void { }, error.TotalResourceDataTooLong => { const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource]; - try error_handler.emitMessage(gpa, .err, "total resource data exceeds the maximum of the coff 'size of raw data' field", .{}); - try error_handler.emitMessage(gpa, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{ + try error_handler.emitMessage(gpa, io, .err, "total resource data exceeds the maximum of the coff 'size of raw data' field", .{}); + try error_handler.emitMessage(gpa, io, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{ overflow_resource.name_value, fmtResourceType(overflow_resource.type_value), overflow_resource.language, }); }, else => { - try error_handler.emitMessage(gpa, .err, "unable to write coff output file '{s}': {s}", .{ coff_stream.name, @errorName(err) }); + try error_handler.emitMessage(gpa, io, .err, "unable to write coff output file '{s}': {s}", .{ coff_stream.name, @errorName(err) }); }, } // Delete the output file on error @@ -550,16 +550,16 @@ const LazyIncludePaths = struct { else => |e| { switch (e) { error.UnsupportedAutoIncludesMachineType => { - try error_handler.emitMessage(self.arena, .err, "automatic include path detection is not supported for target '{s}'", .{@tagName(self.target_machine_type)}); + try error_handler.emitMessage(self.arena, io, .err, "automatic include path detection is not supported for target '{s}'", .{@tagName(self.target_machine_type)}); }, error.MsvcIncludesNotFound => { - try error_handler.emitMessage(self.arena, .err, "MSVC include paths could not be automatically detected", .{}); + try error_handler.emitMessage(self.arena, io, .err, "MSVC include paths could not be automatically detected", .{}); }, error.MingwIncludesNotFound => { - try error_handler.emitMessage(self.arena, .err, "MinGW include paths could not be automatically detected", .{}); + try error_handler.emitMessage(self.arena, io, .err, "MinGW include paths could not be automatically detected", .{}); }, } - try error_handler.emitMessage(self.arena, .note, "to disable auto includes, use the option /:auto-includes none", .{}); + try error_handler.emitMessage(self.arena, io, .note, "to disable auto includes, use the option /:auto-includes none", .{}); std.process.exit(1); }, }; @@ -664,6 +664,7 @@ const ErrorHandler = union(enum) { pub fn emitCliDiagnostics( self: *ErrorHandler, allocator: Allocator, + io: Io, args: []const []const u8, diagnostics: *cli.Diagnostics, ) !void { @@ -674,7 +675,7 @@ const ErrorHandler = union(enum) { try server.serveErrorBundle(error_bundle); }, - .stderr => diagnostics.renderToStdErr(args), + .stderr => diagnostics.renderToStderr(io, args), } } @@ -684,6 +685,7 @@ const ErrorHandler = union(enum) { fail_msg: []const u8, comp: *aro.Compilation, ) !void { + const io = comp.io; switch (self.*) { .server => |*server| { var error_bundle = try compiler_util.aroDiagnosticsToErrorBundle( @@ -697,9 +699,9 @@ const ErrorHandler = union(enum) { }, .stderr => { // aro errors have already been emitted - const stderr, const ttyconf = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - try renderErrorMessage(stderr, ttyconf, .err, "{s}", .{fail_msg}); + const stderr = io.lockStderrWriter(&.{}); + defer io.unlockStderrWriter(); + try renderErrorMessage(&stderr.interface, stderr.mode, .err, "{s}", .{fail_msg}); }, } } @@ -707,6 +709,7 @@ const ErrorHandler = union(enum) { pub fn emitDiagnostics( self: *ErrorHandler, allocator: Allocator, + io: Io, cwd: Io.Dir, source: []const u8, diagnostics: *Diagnostics, @@ -719,13 +722,14 @@ const ErrorHandler = union(enum) { try server.serveErrorBundle(error_bundle); }, - .stderr => diagnostics.renderToStdErr(cwd, source, mappings), + .stderr => diagnostics.renderToStderr(io, cwd, source, mappings), } } pub fn emitMessage( self: *ErrorHandler, allocator: Allocator, + io: Io, msg_type: @import("utils.zig").ErrorMessageType, comptime format: []const u8, args: anytype, @@ -741,9 +745,9 @@ const ErrorHandler = union(enum) { try server.serveErrorBundle(error_bundle); }, .stderr => { - const stderr, const ttyconf = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - try renderErrorMessage(stderr, ttyconf, msg_type, format, args); + const stderr = io.lockStderrWriter(&.{}); + defer io.unlockStderrWriter(); + try renderErrorMessage(&stderr.interface, stderr.mode, msg_type, format, args); }, } } diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index 538558fe48917ea7bd71d9a435dc1685e02e52ac..9ff199d07bc98726923dbfd90932755989f0654a 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -407,7 +407,7 @@ fn buildWasmBinary( } if (result_error_bundle.errorMessageCount() > 0) { - result_error_bundle.renderToStdErr(.{}, true); + result_error_bundle.renderToStderr(io, .{}, true); std.log.err("the following command failed with {d} compilation errors:\n{s}", .{ result_error_bundle.errorMessageCount(), try std.Build.Step.allocPrintCmd(arena, null, argv.items), diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index f7f59a5bf37c27e3940dafad6219591ca12f465c..3a135ab8fd29c53fa7cb6d009b51305585ce727f 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -411,16 +411,13 @@ pub fn fuzz( std.debug.writeStackTrace(trace, &stderr.interface, stderr.mode) catch break :p; } stderr.interface.print("failed with error.{t}\n", .{err}) catch break :p; - stderr.interface.flush() catch break :p; } - stderr.interface.flush() catch {}; std.process.exit(1); }, }; if (log_err_count != 0) { const stderr = std.debug.lockStderrWriter(&.{}); stderr.interface.print("error logs detected\n", .{}) catch {}; - stderr.interface.flush() catch {}; std.process.exit(1); } } diff --git a/lib/std/Build.zig b/lib/std/Build.zig index b7300cb5df036d3eb04d519fd38faaed60c756cb..de2985d2b517f8a73320a435f1242133ff7107df 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -2238,7 +2238,7 @@ pub const GeneratedFile = struct { /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards. path: ?[]const u8 = null, - /// Deprecated, see `getPath2`. + /// Deprecated, see `getPath3`. pub fn getPath(gen: GeneratedFile) []const u8 { return gen.step.owner.pathFromCwd(gen.path orelse std.debug.panic( "getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?", @@ -2246,11 +2246,18 @@ pub const GeneratedFile = struct { )); } + /// Deprecated, see `getPath3`. pub fn getPath2(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) []const u8 { + return getPath3(gen, src_builder, asking_step) catch |err| switch (err) { + error.Canceled => std.process.exit(1), + }; + } + + pub fn getPath3(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) Io.Cancelable![]const u8 { return gen.path orelse { - const stderr = std.debug.lockStderrWriter(&.{}); + const io = gen.step.owner.graph.io; + const stderr = try io.lockStderrWriter(&.{}); dumpBadGetPathHelp(gen.step, &stderr.interface, stderr.mode, src_builder, asking_step) catch {}; - std.debug.unlockStderrWriter(); @panic("misconfigured build script"); }; } @@ -2425,22 +2432,29 @@ pub const LazyPath = union(enum) { } } - /// Deprecated, see `getPath3`. + /// Deprecated, see `getPath4`. pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 { return getPath2(lazy_path, src_builder, null); } - /// Deprecated, see `getPath3`. + /// Deprecated, see `getPath4`. pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 { const p = getPath3(lazy_path, src_builder, asking_step); return src_builder.pathResolve(&.{ p.root_dir.path orelse ".", p.sub_path }); } + /// Deprecated, see `getPath4`. + pub fn getPath3(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Cache.Path { + return getPath4(lazy_path, src_builder, asking_step) catch |err| switch (err) { + error.Canceled => std.process.exit(1), + }; + } + /// Intended to be used during the make phase only. /// /// `asking_step` is only used for debugging purposes; it's the step being /// run that is asking for the path. - pub fn getPath3(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Cache.Path { + pub fn getPath4(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Io.Cancelable!Cache.Path { switch (lazy_path) { .src_path => |sp| return .{ .root_dir = sp.owner.build_root, @@ -2457,9 +2471,10 @@ pub const LazyPath = union(enum) { var file_path: Cache.Path = .{ .root_dir = Cache.Directory.cwd(), .sub_path = gen.file.path orelse { - const stderr = std.debug.lockStderrWriter(&.{}); + const io = src_builder.graph.io; + const stderr = try io.lockStderrWriter(&.{}); dumpBadGetPathHelp(gen.file.step, &stderr.interface, stderr.mode, src_builder, asking_step) catch {}; - std.debug.unlockStderrWriter(); + io.unlockStderrWriter(); @panic("misconfigured build script"); }, }; diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index c2b7d1e9e9c0e474a44b710ca3a4f3ac8f9d7fc8..4770e03a0c6af95412ab8323500ff4ab1d9c45c6 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -158,6 +158,7 @@ fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, parent_prog_node: std.P } fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) !void { + const io = run.step.owner.graph.io; const compile = run.producer.?; const prog_node = parent_prog_node.start(compile.step.name, 0); defer prog_node.end(); @@ -170,8 +171,8 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_nod if (show_error_msgs or show_compile_errors or show_stderr) { var buf: [256]u8 = undefined; - const stderr = std.debug.lockStderrWriter(&buf); - defer std.debug.unlockStderrWriter(); + const stderr = try io.lockStderrWriter(&buf); + defer io.unlockStderrWriter(); build_runner.printErrorMessages(gpa, &compile.step, .{}, &stderr.interface, stderr.mode, .verbose, .indent) catch {}; } @@ -182,12 +183,10 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_nod run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename); } -fn fuzzWorkerRun( - fuzz: *Fuzz, - run: *Step.Run, - unit_test_index: u32, -) void { - const gpa = run.step.owner.allocator; +fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_index: u32) void { + const owner = run.step.owner; + const gpa = owner.allocator; + const io = owner.graph.io; const test_name = run.cached_test_metadata.?.testName(unit_test_index); const prog_node = fuzz.prog_node.start(test_name, 0); @@ -196,8 +195,10 @@ fn fuzzWorkerRun( run.rerunInFuzzMode(fuzz, unit_test_index, prog_node) catch |err| switch (err) { error.MakeFailed => { var buf: [256]u8 = undefined; - const stderr = std.debug.lockStderrWriter(&buf); - defer std.debug.unlockStderrWriter(); + const stderr = io.lockStderrWriter(&buf) catch |e| switch (e) { + error.Canceled => return, + }; + defer io.unlockStderrWriter(); build_runner.printErrorMessages(gpa, &run.step, .{}, &stderr.interface, stderr.mode, .verbose, .indent) catch {}; return; }, diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index d703e55b8727ad7fe2e575f059e676d100099d59..888775884d8fe682cc195a2e57230a1c2ce3ccfa 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -922,20 +922,23 @@ const CliNamedModules = struct { } }; -fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking_step: ?*Step) []const u8 { +fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking_step: ?*Step) ![]const u8 { + const step = &compile.step; + const b = step.owner; + const io = b.graph.io; const maybe_path: ?*GeneratedFile = @field(compile, tag_name); const generated_file = maybe_path orelse { - const stderr = std.debug.lockStderrWriter(&.{}); + const stderr = try io.lockStderrWriter(&.{}); std.Build.dumpBadGetPathHelp(&compile.step, &stderr.interface, stderr.mode, compile.step.owner, asking_step) catch {}; - std.debug.unlockStderrWriter(); + io.unlockStderrWriter(); @panic("missing emit option for " ++ tag_name); }; const path = generated_file.path orelse { - const stderr = std.debug.lockStderrWriter(&.{}); + const stderr = try io.lockStderrWriter(&.{}); std.Build.dumpBadGetPathHelp(&compile.step, &stderr.interface, stderr.mode, compile.step.owner, asking_step) catch {}; - std.debug.unlockStderrWriter(); + io.unlockStderrWriter(); @panic(tag_name ++ " is null. Is there a missing step dependency?"); }; @@ -1149,9 +1152,9 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { // For everything else, we directly link // against the library file. const full_path_lib = if (other_produces_implib) - other.getGeneratedFilePath("generated_implib", &compile.step) + try other.getGeneratedFilePath("generated_implib", &compile.step) else - other.getGeneratedFilePath("generated_bin", &compile.step); + try other.getGeneratedFilePath("generated_bin", &compile.step); try zig_args.append(full_path_lib); total_linker_objects += 1; diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index f1e5313905f3ccc8786fb04c6ea3302a1c02896b..67cf201374322ed41657eb5c263f8a0d2b4472df 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1559,6 +1559,7 @@ fn spawnChildAndCollect( ) !?EvalGenericResult { const b = run.step.owner; const arena = b.allocator; + const io = b.graph.io; if (fuzz_context != null) { assert(!has_side_effects); @@ -1625,10 +1626,10 @@ fn spawnChildAndCollect( child.progress_node = options.progress_node; } if (inherit) { - const stderr = std.debug.lockStderrWriter(&.{}); + const stderr = try io.lockStderrWriter(&.{}); try setColorEnvironmentVariables(run, env_map, stderr.mode); } - defer if (inherit) std.debug.unlockStderrWriter(); + defer if (inherit) io.unlockStderrWriter(); var timer = try std.time.Timer.start(); const res = try evalGeneric(run, &child); run.step.result_duration_ns = timer.read(); diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index ccb715919207b4e9dfa5a1893b2e7411ed24659c..a2b35e35229aa716799bfe46a1c53dc2b452f2c7 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -655,7 +655,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim } if (result_error_bundle.errorMessageCount() > 0) { - result_error_bundle.renderToStdErr(.{}, .auto); + try result_error_bundle.renderToStderr(io, .{}, .auto); log.err("the following command failed with {d} compilation errors:\n{s}", .{ result_error_bundle.errorMessageCount(), try Build.Step.allocPrintCmd(arena, null, argv.items), diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 603f53091e1bc51747fdcd4c387bc9ef1863e3db..6edbf9e47109a9129a3b19c394a32c4902f3c549 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -764,7 +764,7 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { } } -pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) anyerror!void { +pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error!void { if (noop_impl or !global_progress.need_clear) return; try file_writer.writeAllUnescaped(clear ++ progress_remove); global_progress.need_clear = false; diff --git a/lib/std/json/dynamic.zig b/lib/std/json/dynamic.zig index c3cccd1a9157f318fc6c498ab42a1cf7f3914117..f85916670b663c63a3233f460e63c644043f9a9d 100644 --- a/lib/std/json/dynamic.zig +++ b/lib/std/json/dynamic.zig @@ -47,10 +47,9 @@ pub const Value = union(enum) { } pub fn dump(v: Value) void { - const w, _ = std.debug.lockStderrWriter(&.{}); + const stderr = std.debug.lockStderrWriter(&.{}); defer std.debug.unlockStderrWriter(); - - json.Stringify.value(v, .{}, w) catch return; + json.Stringify.value(v, .{}, &stderr.interface) catch return; } pub fn jsonStringify(value: @This(), jws: anytype) !void { diff --git a/lib/std/process.zig b/lib/std/process.zig index 216e793cbe2a49b8d997d8b8dfccf7d3a5caa5b1..4b4c27fe200a1064793ec77c165f343cf55de11f 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -1849,7 +1849,7 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 { /// and does not return. pub fn cleanExit(io: Io) void { if (builtin.mode == .Debug) return; - _ = io.lockStderrWriter(&.{}); + _ = io.lockStderrWriter(&.{}) catch {}; exit(0); } diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 092b65d71ac9bb4f06842882086fdbf8453a3509..64ea3d1a4d6faec23d61c151892970c3eff59b9a 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -368,13 +368,21 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const break :diff_index if (expected.len == actual.len) return else shortest; }; if (!backend_can_print) return error.TestExpectedEqual; - const stderr = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - failEqualSlices(T, expected, actual, diff_index, &stderr.interface, stderr.mode) catch {}; + if (io.lockStderrWriter(&.{})) |stderr| { + defer io.unlockStderrWriter(); + failEqualSlices(T, expected, actual, diff_index, &stderr.interface, stderr.mode) catch {}; + } else |_| {} return error.TestExpectedEqual; } -fn failEqualSlices(comptime T: type, expected: []const T, actual: []const T, diff_index: usize, w: *Io.Writer, fwm: Io.File.Writer.Mode) !void { +fn failEqualSlices( + comptime T: type, + expected: []const T, + actual: []const T, + diff_index: usize, + w: *Io.Writer, + fwm: Io.File.Writer.Mode, +) !void { try w.print("slices differ. first difference occurs at index {d} (0x{X})\n", .{ diff_index, diff_index }); // TODO: Should this be configurable by the caller? diff --git a/lib/std/zig.zig b/lib/std/zig.zig index b6203326f26d9cc538b4909ebd84c43c370ec814..a094dee83f6174c8571138bc67237c163efc6bde 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -639,7 +639,7 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![ return buffer.toOwnedSliceSentinel(gpa, 0); } -pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void { +pub fn printAstErrorsToStderr(gpa: Allocator, io: Io, tree: Ast, path: []const u8, color: Color) !void { var wip_errors: std.zig.ErrorBundle.Wip = undefined; try wip_errors.init(gpa); defer wip_errors.deinit(); @@ -648,7 +648,7 @@ pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(.{}, color); + error_bundle.renderToStderr(io, .{}, color); } pub fn putAstErrorsIntoBundle( diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index cc6182c8aa67064e77b44ebdcac1391d2577841e..0342874cf9e24f04833e7d8e5625fde04a9ec51b 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -162,11 +162,13 @@ pub const RenderOptions = struct { include_log_text: bool = true, }; -pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions, color: std.zig.Color) void { +pub const RenderToStderrError = Io.Cancelable || Io.File.Writer.Mode.SetColorError; + +pub fn renderToStderr(eb: ErrorBundle, io: Io, options: RenderOptions, color: std.zig.Color) RenderToStderrError!void { var buffer: [256]u8 = undefined; - const stderr = std.debug.lockStderrWriter(&buffer); - defer std.debug.unlockStderrWriter(); - renderToWriter(eb, options, &stderr.interface, color.getTtyConf(stderr.mode)) catch return; + const stderr = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); + try renderToWriter(eb, options, &stderr.interface, color.getTtyConf(stderr.mode)); } pub fn renderToWriter( diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 9d9a6a48cedb0a02219f48e98c4f5cac3703cb75..5318eab2df3265ceb2751e83f4ccc191ca71612b 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const mem = std.mem; -const print = std.debug.print; -const maxInt = std.math.maxInt; +const Io = std.Io; +const Allocator = std.mem.Allocator; test "zig fmt: remove extra whitespace at start and end of file with comment between" { try testTransform( @@ -6332,10 +6331,10 @@ test "ampersand" { var fixed_buffer_mem: [100 * 1024]u8 = undefined; -fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 { +fn testParse(io: Io, source: [:0]const u8, allocator: Allocator, anything_changed: *bool) ![]u8 { var buffer: [64]u8 = undefined; - const stderr = std.debug.lockStderrWriter(&buffer); - defer std.debug.unlockStderrWriter(); + const stderr = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); var tree = try std.zig.Ast.parse(allocator, source, .zig); defer tree.deinit(allocator); @@ -6359,27 +6358,36 @@ fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: * } const formatted = try tree.renderAlloc(allocator); - anything_changed.* = !mem.eql(u8, formatted, source); + anything_changed.* = !std.mem.eql(u8, formatted, source); return formatted; } -fn testTransformImpl(allocator: mem.Allocator, fba: *std.heap.FixedBufferAllocator, source: [:0]const u8, expected_source: []const u8) !void { +fn testTransformImpl( + io: Io, + allocator: Allocator, + fba: *std.heap.FixedBufferAllocator, + source: [:0]const u8, + expected_source: []const u8, +) !void { // reset the fixed buffer allocator each run so that it can be re-used for each // iteration of the failing index fba.reset(); var anything_changed: bool = undefined; - const result_source = try testParse(source, allocator, &anything_changed); + const result_source = try testParse(io, source, allocator, &anything_changed); try std.testing.expectEqualStrings(expected_source, result_source); const changes_expected = source.ptr != expected_source.ptr; if (anything_changed != changes_expected) { - print("std.zig.render returned {} instead of {}\n", .{ anything_changed, changes_expected }); + std.debug.print("std.zig.render returned {} instead of {}\n", .{ anything_changed, changes_expected }); return error.TestFailed; } try std.testing.expect(anything_changed == changes_expected); allocator.free(result_source); } fn testTransform(source: [:0]const u8, expected_source: []const u8) !void { + const io = std.testing.io; var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); - return std.testing.checkAllAllocationFailures(fixed_allocator.allocator(), testTransformImpl, .{ &fixed_allocator, source, expected_source }); + return std.testing.checkAllAllocationFailures(fixed_allocator.allocator(), testTransformImpl, .{ + io, &fixed_allocator, source, expected_source, + }); } fn testCanonical(source: [:0]const u8) !void { return testTransform(source, source); diff --git a/src/Air/print.zig b/src/Air/print.zig index 95c8a1fcdacf625e86412c5728342585d22f5200..05614a0ed3700217cb2d31f147f8abb8da89b2aa 100644 --- a/src/Air/print.zig +++ b/src/Air/print.zig @@ -9,7 +9,7 @@ const Type = @import("../Type.zig"); const Air = @import("../Air.zig"); const InternPool = @import("../InternPool.zig"); -pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air.Liveness) void { +pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air.Liveness) !void { comptime assert(build_options.enable_debug_extensions); const instruction_bytes = air.instructions.len * // 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 liveness_special_bytes + tomb_bytes; // zig fmt: off - stream.print( + try stream.print( \\# Total AIR+Liveness bytes: {Bi} \\# AIR Instructions: {d} ({Bi}) \\# AIR Extra Data: {d} ({Bi}) @@ -39,7 +39,7 @@ pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air tomb_bytes, if (liveness) |l| l.extra.len else 0, liveness_extra_bytes, if (liveness) |l| l.special.count() else 0, liveness_special_bytes, - }) catch return; + }); // zig fmt: on var writer: Writer = .{ @@ -50,7 +50,7 @@ pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air .indent = 2, .skip_body = false, }; - writer.writeBody(stream, air.getMainBody()) catch return; + try writer.writeBody(stream, air.getMainBody()); } pub fn writeInst( @@ -73,15 +73,23 @@ pub fn writeInst( } pub fn dump(air: Air, pt: Zcu.PerThread, liveness: ?Air.Liveness) void { - const stderr_bw, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - air.write(stderr_bw, pt, liveness); + const comp = pt.zcu.comp; + const io = comp.io; + var buffer: [512]u8 = undefined; + const stderr = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); + const w = &stderr.interface; + air.write(w, pt, liveness); } pub fn dumpInst(air: Air, inst: Air.Inst.Index, pt: Zcu.PerThread, liveness: ?Air.Liveness) void { - const stderr_bw, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - air.writeInst(stderr_bw, inst, pt, liveness); + const comp = pt.zcu.comp; + const io = comp.io; + var buffer: [512]u8 = undefined; + const stderr = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); + const w = &stderr.interface; + air.writeInst(w, inst, pt, liveness); } const Writer = struct { diff --git a/src/Compilation.zig b/src/Compilation.zig index 9e76b8fecaef97b6285fa1c4defe36b4571e6aa3..47098317b5d1e976f8b95e06036bcec24336fbb0 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2088,12 +2088,13 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, if (options.verbose_llvm_cpu_features) { if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: { - const stderr_w, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - stderr_w.print("compilation: {s}\n", .{options.root_name}) catch break :print; - stderr_w.print(" target: {s}\n", .{try target.zigTriple(arena)}) catch break :print; - stderr_w.print(" cpu: {s}\n", .{target.cpu.model.name}) catch break :print; - stderr_w.print(" features: {s}\n", .{cf}) catch {}; + const stderr = try io.lockStderrWriter(&.{}); + defer io.unlockStderrWriter(); + const w = &stderr.interface; + w.print("compilation: {s}\n", .{options.root_name}) catch break :print; + w.print(" target: {s}\n", .{try target.zigTriple(arena)}) catch break :print; + w.print(" cpu: {s}\n", .{target.cpu.model.name}) catch break :print; + w.print(" features: {s}\n", .{cf}) catch {}; } } @@ -4257,12 +4258,13 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { // However, we haven't reported any such error. // This is a compiler bug. print_ctx: { - var stderr_w, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - stderr_w.writeAll("referenced transitive analysis errors, but none actually emitted\n") catch break :print_ctx; - stderr_w.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}) catch break :print_ctx; + const stderr = try io.lockStderrWriter(&.{}); + defer io.unlockStderrWriter(); + const w = &stderr.interface; + w.writeAll("referenced transitive analysis errors, but none actually emitted\n") catch break :print_ctx; + w.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}) catch break :print_ctx; while (ref) |r| { - stderr_w.print("referenced by: {f}{s}\n", .{ + w.print("referenced by: {f}{s}\n", .{ zcu.fmtAnalUnit(r.referencer), if (zcu.transitive_failed_analysis.contains(r.referencer)) " [transitive failure]" else "", }) catch break :print_ctx; @@ -5756,7 +5758,7 @@ pub fn translateC( try argv.appendSlice(comp.global_cc_argv); try argv.appendSlice(owner_mod.cc_argv); try argv.appendSlice(&.{ source_path, "-o", translated_path }); - if (comp.verbose_cimport) dump_argv(argv.items); + if (comp.verbose_cimport) dumpArgv(io, argv.items); } var stdout: []u8 = undefined; @@ -6264,7 +6266,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr } if (comp.verbose_cc) { - dump_argv(argv.items); + dumpArgv(io, argv.items); } const err = std.process.execv(arena, argv.items); @@ -6310,7 +6312,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr } if (comp.verbose_cc) { - dump_argv(argv.items); + dumpArgv(io, argv.items); } // Just to save disk space, we delete the files that are never needed again. @@ -7773,17 +7775,22 @@ pub fn lockAndSetMiscFailure( return setMiscFailure(comp, tag, format, args); } -pub fn dump_argv(argv: []const []const u8) void { +pub fn dumpArgv(io: Io, argv: []const []const u8) Io.Cancelable!void { var buffer: [64]u8 = undefined; - const stderr, _ = std.debug.lockStderrWriter(&buffer); - defer std.debug.unlockStderrWriter(); - nosuspend { - for (argv, 0..) |arg, i| { - if (i != 0) stderr.writeByte(' ') catch return; - stderr.writeAll(arg) catch return; - } - stderr.writeByte('\n') catch return; + const stderr = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); + const w = &stderr.interface; + return dumpArgvWriter(w, argv) catch |err| switch (err) { + error.WriteFailed => return stderr.err.?, + }; +} + +fn dumpArgvWriter(w: *Io.Writer, argv: []const []const u8) Io.Writer.Error!void { + for (argv, 0..) |arg, i| { + if (i != 0) try w.writeByte(' '); + try w.writeAll(arg); } + try w.writeByte('\n'); } pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend { diff --git a/src/InternPool.zig b/src/InternPool.zig index 5568c493d93a098b2eb7ffed7240383ebb88ac34..539cb441c5b9200f8919004ce4dcf734dba2dd62 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -1,8 +1,11 @@ //! All interned objects have both a value and a type. //! This data structure is self-contained. +const InternPool = @This(); const builtin = @import("builtin"); + const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; const assert = std.debug.assert; const BigIntConst = std.math.big.int.Const; @@ -11,11 +14,10 @@ const Cache = std.Build.Cache; const Io = std.Io; const Limb = std.math.big.Limb; const Hash = std.hash.Wyhash; - -const InternPool = @This(); -const Zcu = @import("Zcu.zig"); const Zir = std.zig.Zir; +const Zcu = @import("Zcu.zig"); + /// One item per thread, indexed by `tid`, which is dense and unique per thread. locals: []Local, /// Length must be a power of two and represents the number of simultaneous @@ -11165,12 +11167,16 @@ pub fn mutateVarInit(ip: *InternPool, io: Io, index: Index, init_index: Index) v @atomicStore(u32, &extra_items[item.data + std.meta.fieldIndex(Tag.Variable, "init").?], @intFromEnum(init_index), .release); } -pub fn dump(ip: *const InternPool) void { - dumpStatsFallible(ip, std.heap.page_allocator) catch return; - dumpAllFallible(ip) catch return; +pub fn dump(ip: *const InternPool, io: Io) Io.Cancelable!void { + var buffer: [4096]u8 = undefined; + const stderr_writer = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); + const w = &stderr_writer.interface; + try dumpStatsFallible(ip, w, std.heap.page_allocator); + try dumpAllFallible(ip, w); } -fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void { +fn dumpStatsFallible(ip: *const InternPool, w: *Io.Writer, arena: Allocator) anyerror!void { var items_len: usize = 0; var extra_len: usize = 0; var limbs_len: usize = 0; @@ -11423,18 +11429,13 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void { }; counts.sort(SortContext{ .map = &counts }); const len = @min(50, counts.count()); - std.debug.print(" top 50 tags:\n", .{}); + w.print(" top 50 tags:\n", .{}); for (counts.keys()[0..len], counts.values()[0..len]) |tag, stats| { - std.debug.print(" {s}: {d} occurrences, {d} total bytes\n", .{ - @tagName(tag), stats.count, stats.bytes, - }); + w.print(" {t}: {d} occurrences, {d} total bytes\n", .{ tag, stats.count, stats.bytes }); } } -fn dumpAllFallible(ip: *const InternPool) anyerror!void { - var buffer: [4096]u8 = undefined; - const stderr_bw, _ = std.debug.lockStderrWriter(&buffer); - defer std.debug.unlockStderrWriter(); +fn dumpAllFallible(ip: *const InternPool, w: *Io.Writer) anyerror!void { for (ip.locals, 0..) |*local, tid| { const items = local.shared.items.view(); for ( @@ -11443,12 +11444,12 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void { 0.., ) |tag, data, index| { const i = Index.Unwrapped.wrap(.{ .tid = @enumFromInt(tid), .index = @intCast(index) }, ip); - try stderr_bw.print("${d} = {s}(", .{ i, @tagName(tag) }); + try w.print("${d} = {s}(", .{ i, @tagName(tag) }); switch (tag) { .removed => {}, - .simple_type => try stderr_bw.print("{s}", .{@tagName(@as(SimpleType, @enumFromInt(@intFromEnum(i))))}), - .simple_value => try stderr_bw.print("{s}", .{@tagName(@as(SimpleValue, @enumFromInt(@intFromEnum(i))))}), + .simple_type => try w.print("{s}", .{@tagName(@as(SimpleType, @enumFromInt(@intFromEnum(i))))}), + .simple_value => try w.print("{s}", .{@tagName(@as(SimpleValue, @enumFromInt(@intFromEnum(i))))}), .type_int_signed, .type_int_unsigned, @@ -11521,23 +11522,27 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void { .func_coerced, .union_value, .memoized_call, - => try stderr_bw.print("{d}", .{data}), + => try w.print("{d}", .{data}), .opt_null, .type_slice, .only_possible_value, - => try stderr_bw.print("${d}", .{data}), + => try w.print("${d}", .{data}), } - try stderr_bw.writeAll(")\n"); + try w.writeAll(")\n"); } } } -pub fn dumpGenericInstances(ip: *const InternPool, allocator: Allocator) void { - ip.dumpGenericInstancesFallible(allocator) catch return; +pub fn dumpGenericInstances(ip: *const InternPool, io: Io, allocator: Allocator) Io.Cancelable!void { + var buffer: [4096]u8 = undefined; + const stderr_writer = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); + const w = &stderr_writer.interface; + try ip.dumpGenericInstancesFallible(allocator, w); } -pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator) anyerror!void { +pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator, w: *Io.Writer) !void { var arena_allocator = std.heap.ArenaAllocator.init(allocator); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); @@ -11564,10 +11569,6 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator) } } - var buffer: [4096]u8 = undefined; - const stderr_bw, _ = std.debug.lockStderrWriter(&buffer); - defer std.debug.unlockStderrWriter(); - const SortContext = struct { values: []std.ArrayList(Index), pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool { @@ -11579,19 +11580,19 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator) var it = instances.iterator(); while (it.next()) |entry| { const generic_fn_owner_nav = ip.getNav(ip.funcDeclInfo(entry.key_ptr.*).owner_nav); - try stderr_bw.print("{f} ({d}): \n", .{ generic_fn_owner_nav.name.fmt(ip), entry.value_ptr.items.len }); + try w.print("{f} ({d}): \n", .{ generic_fn_owner_nav.name.fmt(ip), entry.value_ptr.items.len }); for (entry.value_ptr.items) |index| { const unwrapped_index = index.unwrap(ip); const func = ip.extraFuncInstance(unwrapped_index.tid, unwrapped_index.getExtra(ip), unwrapped_index.getData(ip)); const owner_nav = ip.getNav(func.owner_nav); - try stderr_bw.print(" {f}: (", .{owner_nav.name.fmt(ip)}); + try w.print(" {f}: (", .{owner_nav.name.fmt(ip)}); for (func.comptime_args.get(ip)) |arg| { if (arg != .none) { const key = ip.indexToKey(arg); - try stderr_bw.print(" {} ", .{key}); + try w.print(" {} ", .{key}); } } - try stderr_bw.writeAll(")\n"); + try w.writeAll(")\n"); } } } diff --git a/src/Sema.zig b/src/Sema.zig index fec6850c4c8de84ec748d494d865ade8cd78d1db..1f6a577f6056ad1a3c42918771d35a2d76e4ff5f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2668,16 +2668,18 @@ fn failWithTypeMismatch(sema: *Sema, block: *Block, src: LazySrcLoc, expected: T pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg) error{ AnalysisFail, OutOfMemory } { @branchHint(.cold); - const gpa = sema.gpa; const zcu = sema.pt.zcu; + const comp = zcu.comp; + const gpa = comp.gpa; + const io = comp.io; - if (build_options.enable_debug_extensions and zcu.comp.debug_compile_errors) { + if (build_options.enable_debug_extensions and comp.debug_compile_errors) { var wip_errors: std.zig.ErrorBundle.Wip = undefined; wip_errors.init(gpa) catch @panic("out of memory"); Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory"); std.debug.print("compile error during Sema:\n", .{}); var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory"); - error_bundle.renderToStdErr(.{}, .auto); + error_bundle.renderToStderr(io, .{}, .auto); std.debug.panicExtra(@returnAddress(), "unexpected compile error occurred", .{}); } diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 2333d7f286c6db98ae09c945a3b4cdfc7a5477fc..408f16bd74af58b18716db69f8e1a519e3b67742 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -4556,8 +4556,9 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e defer if (liveness) |*l| l.deinit(gpa); if (build_options.enable_debug_extensions and comp.verbose_air) { - const stderr, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); + const io = comp.io; + const stderr = try io.lockStderrWriter(&.{}); + defer io.unlockStderrWriter(); stderr.print("# Begin Function AIR: {f}:\n", .{fqn.fmt(ip)}) catch {}; air.write(stderr, pt, liveness); stderr.print("# End Function AIR: {f}\n\n", .{fqn.fmt(ip)}) catch {}; diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig index f390d83f03bf0ac27050f8bcff918e2f3c9418bb..138c70fecff33cfb548b6239aec565516de64b1d 100644 --- a/src/codegen/aarch64/Select.zig +++ b/src/codegen/aarch64/Select.zig @@ -11273,14 +11273,17 @@ fn initValueAdvanced( return @enumFromInt(isel.values.items.len); } pub fn dumpValues(isel: *Select, which: enum { only_referenced, all }) void { + const zcu = isel.pt.zcu; + const io = zcu.comp.io; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + const nav = ip.getNav(isel.nav_index); + errdefer |err| @panic(@errorName(err)); - const stderr, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - const zcu = isel.pt.zcu; - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const nav = ip.getNav(isel.nav_index); + const stderr_writer = io.lockStderrWriter(&.{}) catch return; + defer io.unlockStderrWriter(); + const stderr = &stderr_writer.interface; var reverse_live_values: std.AutoArrayHashMapUnmanaged(Value.Index, std.ArrayList(Air.Inst.Index)) = .empty; defer { diff --git a/src/crash_report.zig b/src/crash_report.zig index 76503fdc536ee3ed98dafb633bb58ed0d7aac0ab..f23806b75876739487f170f9df32d5e00d8968b3 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -97,17 +97,18 @@ fn dumpCrashContext() Io.Writer.Error!void { // and the actual panic printing, which would be quite confusing. const stderr = std.debug.lockStderrWriter(&.{}); defer std.debug.unlockStderrWriter(); + const w = &stderr.interface; - try stderr.interface.writeAll("Compiler crash context:\n"); + try w.writeAll("Compiler crash context:\n"); if (CodegenFunc.current) |*cg| { const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav; const func_fqn = cg.zcu.intern_pool.getNav(func_nav).fqn; - try stderr.interface.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)}); + try w.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)}); } else if (AnalyzeBody.current) |anal| { - try dumpCrashContextSema(anal, &stderr.interface, &S.crash_heap); + try dumpCrashContextSema(anal, w, &S.crash_heap); } else { - try stderr.interface.writeAll("(no context)\n\n"); + try w.writeAll("(no context)\n\n"); } } fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void { diff --git a/src/fmt.zig b/src/fmt.zig index 55a68d1dc8b5467d7fbcfdad0908703dbd278284..b9b1cc136304747e5b4b9facb9c51c4795815274 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -124,7 +124,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! try wip_errors.addZirErrorMessages(zir, tree, source_code, ""); var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(.{}, color); + error_bundle.renderToStderr(io, .{}, color); process.exit(2); } } else { @@ -138,7 +138,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! try wip_errors.addZoirErrorMessages(zoir, tree, source_code, ""); var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(.{}, color); + error_bundle.renderToStderr(io, .{}, color); process.exit(2); } } @@ -319,7 +319,7 @@ fn fmtPathFile( try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path); var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(.{}, fmt.color); + error_bundle.renderToStderr(io, .{}, fmt.color); fmt.any_error = true; } }, @@ -334,7 +334,7 @@ fn fmtPathFile( try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path); var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(.{}, fmt.color); + error_bundle.renderToStderr(io, .{}, fmt.color); fmt.any_error = true; } }, diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 05a9de71e790e1926f5a1082a339866ac4f84cfc..a76fec92377e0ffa19e7882ef5ffe83fad8f908b 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -312,11 +312,17 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { const include_dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "mingw", "def-include" }); - if (comp.verbose_cc) print: { - var stderr, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - nosuspend stderr.print("def file: {s}\n", .{def_file_path}) catch break :print; - nosuspend stderr.print("include dir: {s}\n", .{include_dir}) catch break :print; + if (comp.verbose_cc) { + var buffer: [256]u8 = undefined; + const stderr = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); + const w = &stderr.interface; + w.print("def file: {s}\n", .{def_file_path}) catch |err| switch (err) { + error.WriteFailed => return stderr.err.?, + }; + w.print("include dir: {s}\n", .{include_dir}) catch |err| switch (err) { + error.WriteFailed => return stderr.err.?, + }; } try aro_comp.search_path.append(gpa, .{ .path = include_dir, .kind = .normal }); @@ -333,11 +339,13 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { if (aro_comp.diagnostics.output.to_list.messages.items.len != 0) { var buffer: [64]u8 = undefined; - const w, const ttyconf = std.debug.lockStderrWriter(&buffer); - defer std.debug.unlockStderrWriter(); + const stderr = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); for (aro_comp.diagnostics.output.to_list.messages.items) |msg| { if (msg.kind == .@"fatal error" or msg.kind == .@"error") { - msg.write(w, ttyconf, true) catch {}; + msg.write(&stderr.interface, stderr.mode, true) catch |err| switch (err) { + error.WriteFailed => return stderr.err.?, + }; return error.AroPreprocessorFailed; } } @@ -357,8 +365,9 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { error.OutOfMemory => |e| return e, error.ParseError => { var buffer: [64]u8 = undefined; - const w, _ = std.debug.lockStderrWriter(&buffer); - defer std.debug.unlockStderrWriter(); + const stderr = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); + const w = &stderr.interface; try w.writeAll("error: "); try def_diagnostics.writeMsg(w, input); try w.writeByte('\n'); diff --git a/src/libs/mingw/def.zig b/src/libs/mingw/def.zig index 24dc95c13cc3d53474329459e8011301c5319d46..9a105b61821d736ea572c5e75bfa215ece2d573e 100644 --- a/src/libs/mingw/def.zig +++ b/src/libs/mingw/def.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; pub const ModuleDefinitionType = enum { mingw, @@ -663,7 +664,9 @@ test parse { \\ ; - try testParse(.AMD64, source, "foo.dll", &[_]ModuleDefinition.Export{ + const io = std.testing.io; + + try testParse(io, .AMD64, source, "foo.dll", &[_]ModuleDefinition.Export{ .{ .name = "foo", .mangled_symbol_name = null, @@ -743,7 +746,7 @@ test parse { }, }); - try testParse(.I386, source, "foo.dll", &[_]ModuleDefinition.Export{ + try testParse(io, .I386, source, "foo.dll", &[_]ModuleDefinition.Export{ .{ .name = "_foo", .mangled_symbol_name = null, @@ -823,7 +826,7 @@ test parse { }, }); - try testParse(.ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{ + try testParse(io, .ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{ .{ .name = "foo", .mangled_symbol_name = null, @@ -903,7 +906,7 @@ test parse { }, }); - try testParse(.ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{ + try testParse(io, .ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{ .{ .name = "foo", .mangled_symbol_name = null, @@ -997,7 +1000,9 @@ test "ntdll" { \\RtlActivateActivationContextUnsafeFast@0 ; - try testParse(.AMD64, source, "ntdll.dll", &[_]ModuleDefinition.Export{ + const io = std.testing.io; + + try testParse(io, .AMD64, source, "ntdll.dll", &[_]ModuleDefinition.Export{ .{ .name = "RtlDispatchAPC@12", .mangled_symbol_name = null, @@ -1023,15 +1028,22 @@ test "ntdll" { }); } -fn testParse(machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8, expected_module_name: []const u8, expected_exports: []const ModuleDefinition.Export) !void { +fn testParse( + io: Io, + machine_type: std.coff.IMAGE.FILE.MACHINE, + source: [:0]const u8, + expected_module_name: []const u8, + expected_exports: []const ModuleDefinition.Export, +) !void { var diagnostics: Diagnostics = undefined; const module = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.ParseError => { - const stderr, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - try diagnostics.writeMsg(stderr, source); - try stderr.writeByte('\n'); + const stderr = try io.lockStderrWriter(&.{}); + defer io.unlockStderrWriter(); + const w = &stderr.interface; + try diagnostics.writeMsg(w, source); + try w.writeByte('\n'); return err; }, }; diff --git a/src/link.zig b/src/link.zig index e37ba98cd9dcaa8ba3f0afd90847962fd458a70b..a4729f296ccce4154e6286dcaa52de29c96f8af0 100644 --- a/src/link.zig +++ b/src/link.zig @@ -2246,7 +2246,7 @@ fn resolvePathInputLib( var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(.{}, color); + error_bundle.renderToStderr(io, .{}, color); std.process.exit(1); } diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 8954266e3fe44e5dd3f02e6fa3d6f93ae6b498d4..d3796696139c171b18134621fb81dbbeda511512 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -4,6 +4,7 @@ const builtin = @import("builtin"); const native_endian = builtin.cpu.arch.endian(); const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const log = std.log.scoped(.link); @@ -2377,10 +2378,16 @@ pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTe _ = name; } -pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) void { - const w, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - coff.printNode(tid, w, .root, 0) catch {}; +pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) Io.Cancelable!void { + const comp = coff.base.comp; + const io = comp.io; + var buffer: [512]u8 = undefined; + const stderr = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); + const w = &stderr.interface; + coff.printNode(tid, w, .root, 0) catch |err| switch (err) { + error.WriteFailed => return stderr.err.?, + }; } pub fn printNode( diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index fc4794a7b02499db756e2515e3f5dc443ccbbf24..3c511f1ee925a569c1610ad7f0a5b3d0346382c4 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -3729,10 +3729,16 @@ pub fn deleteExport(elf: *Elf, exported: Zcu.Exported, name: InternPool.NullTerm _ = name; } -pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) void { - const w, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - elf.printNode(tid, w, .root, 0) catch {}; +pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) Io.Cancelable!void { + const comp = elf.base.comp; + const io = comp.io; + var buffer: [512]u8 = undefined; + const stderr = try io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); + const w = &stderr.interface; + elf.printNode(tid, w, .root, 0) catch |err| switch (err) { + error.WriteFailed => return stderr.err.?, + }; } pub fn printNode( diff --git a/src/main.zig b/src/main.zig index 115c1b521271d62009e9e413539c4feb4dd444e3..f735f671c6840f959c837955f841c5897a2aa67e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4429,9 +4429,9 @@ fn runOrTest( // the error message and invocation below. if (process.can_execv and arg_mode == .run) { // execv releases the locks; no need to destroy the Compilation here. - _ = std.debug.lockStderrWriter(&.{}); + _ = try io.lockStderrWriter(&.{}); const err = process.execve(gpa, argv.items, &env_map); - std.debug.unlockStderrWriter(); + io.unlockStderrWriter(); try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd }); @@ -4448,8 +4448,8 @@ fn runOrTest( comp_destroyed.* = true; const term_result = t: { - _ = std.debug.lockStderrWriter(); - defer std.debug.unlockStderrWriter(); + _ = try io.lockStderrWriter(&.{}); + defer io.unlockStderrWriter(); break :t child.spawnAndWait(io); }; const term = term_result catch |err| { @@ -4606,7 +4606,8 @@ fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node) defer errors.deinit(comp.gpa); if (errors.errorMessageCount() > 0) { - errors.renderToStdErr(.{}, color); + const io = comp.io; + errors.renderToStderr(io, .{}, color); return error.CompileErrorsReported; } } @@ -4659,7 +4660,7 @@ fn cmdTranslateC( return; } else { const color: Color = .auto; - result.errors.renderToStdErr(.{}, color); + result.errors.renderToStderr(io, .{}, color); process.exit(1); } } @@ -5280,7 +5281,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) if (fetch.error_bundle.root_list.items.len > 0) { var errors = try fetch.error_bundle.toOwnedBundle(""); - errors.renderToStdErr(.{}, color); + errors.renderToStderr(io, .{}, color); process.exit(1); } @@ -5412,8 +5413,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) child.stderr_behavior = .Inherit; const term = t: { - _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); + _ = try io.lockStderrWriter(&.{}); + defer io.unlockStderrWriter(); break :t child.spawnAndWait(io) catch |err| fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err }); }; @@ -6212,7 +6213,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { try wip_errors.init(arena); try wip_errors.addZirErrorMessages(zir, tree, source, display_path); var error_bundle = try wip_errors.toOwnedBundle(""); - error_bundle.renderToStdErr(.{}, color); + error_bundle.renderToStderr(io, .{}, color); if (zir.loweringFailed()) { process.exit(1); } @@ -6283,7 +6284,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { try wip_errors.init(arena); try wip_errors.addZoirErrorMessages(zoir, tree, source, display_path); var error_bundle = try wip_errors.toOwnedBundle(""); - error_bundle.renderToStdErr(.{}, color); + error_bundle.renderToStderr(io, .{}, color); process.exit(1); } @@ -6557,7 +6558,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { try wip_errors.init(arena); try wip_errors.addZirErrorMessages(old_zir, old_tree, old_source, old_source_path); var error_bundle = try wip_errors.toOwnedBundle(""); - error_bundle.renderToStdErr(.{}, color); + error_bundle.renderToStderr(io, .{}, color); process.exit(1); } @@ -6569,7 +6570,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { try wip_errors.init(arena); try wip_errors.addZirErrorMessages(new_zir, new_tree, new_source, new_source_path); var error_bundle = try wip_errors.toOwnedBundle(""); - error_bundle.renderToStdErr(.{}, color); + error_bundle.renderToStderr(io, .{}, color); process.exit(1); } @@ -7005,7 +7006,7 @@ fn cmdFetch( if (fetch.error_bundle.root_list.items.len > 0) { var errors = try fetch.error_bundle.toOwnedBundle(""); - errors.renderToStdErr(.{}, color); + errors.renderToStderr(io, .{}, color); process.exit(1); } @@ -7345,7 +7346,7 @@ fn loadManifest( errdefer ast.deinit(gpa); if (ast.errors.len > 0) { - try std.zig.printAstErrorsToStderr(gpa, ast, Package.Manifest.basename, options.color); + try std.zig.printAstErrorsToStderr(gpa, io, ast, Package.Manifest.basename, options.color); process.exit(2); } @@ -7362,7 +7363,7 @@ fn loadManifest( var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(.{}, options.color); + error_bundle.renderToStderr(io, .{}, options.color); process.exit(2); } -- 2.54.0 From f82e7dfbc0374945e8e00884a7ae40743cdf25da Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 14 Dec 2025 16:18:50 -0800 Subject: [PATCH 057/195] std.os.linux: move statx docs to proper location --- lib/std/os/linux.zig | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index c8e9180dbfe9ef0268042d86681c9dda7469c378..cd12b137735cad643bff1dbfacc2b46733ad071b 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -6891,10 +6891,6 @@ pub const utsname = extern struct { }; pub const HOST_NAME_MAX = 64; -/// Flags used to request specific members in `Statx` be filled out. -/// The `Statx.mask` member will be updated with what information the kernel -/// returned. Callers must check this field since support varies by kernel -/// version and filesystem. pub const STATX = packed struct(u32) { /// Want `mode & S.IFMT`. TYPE: bool = false, @@ -6982,7 +6978,9 @@ pub const statx_timestamp = extern struct { /// Renamed to `Statx` to not conflict with the `statx` function. pub const Statx = extern struct { - /// Mask of bits indicating filled fields. + /// Mask of bits indicating filled fields. Updated with what information + /// the kernel returned. Callers must check this field since support varies + /// by kernel version and filesystem. mask: STATX, /// Block size for filesystem I/O. blksize: u32, -- 2.54.0 From 2a40c1b556d7ed7811d3d2432dce8ba5d7c2e753 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 14 Dec 2025 16:19:00 -0800 Subject: [PATCH 058/195] std.Io.Threaded: update call to statx --- lib/std/Io/Threaded.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index d4c4da3990260f7c5a23a12f423608a1eb53bc3e..f4f42cc39f7c9f12ecbdc2fe9deaaaeac8bb3094 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1997,7 +1997,7 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 { try current_thread.beginSyscall(); while (true) { var statx = std.mem.zeroes(linux.Statx); - switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_SIZE, &statx))) { + switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .{ .SIZE = true }, &statx))) { .SUCCESS => { current_thread.endSyscall(); return statx.size; -- 2.54.0 From 4458e423bf2d2cf485031d1f527e407bfc9113df Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 14 Dec 2025 16:46:08 -0800 Subject: [PATCH 059/195] link.MappedFile: update statx usage --- lib/std/Io/Threaded.zig | 18 ++++++++++---- lib/std/testing.zig | 7 +++--- lib/std/zig/ErrorBundle.zig | 5 +++- lib/std/zig/parser_test.zig | 4 ++-- src/link/MappedFile.zig | 48 +++++++++++++++++++++++++++---------- src/main.zig | 18 +++++++------- 6 files changed, 69 insertions(+), 31 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f4f42cc39f7c9f12ecbdc2fe9deaaaeac8bb3094..d9ef36cfda903c45be9eda8ced0980a10e5c0e46 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1822,6 +1822,11 @@ fn dirStatFileLinux( const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const linux = std.os.linux; + const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) + .{ .major = 30, .minor = 0, .patch = 0 } + else + .{ .major = 2, .minor = 28, .patch = 0 }); + const sys = if (use_c) std.c else std.os.linux; var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); @@ -1832,14 +1837,14 @@ fn dirStatFileLinux( try current_thread.beginSyscall(); while (true) { var statx = std.mem.zeroes(linux.Statx); - const rc = linux.statx( + const rc = sys.statx( dir.handle, sub_path_posix, flags, .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true }, &statx, ); - switch (linux.errno(rc)) { + switch (sys.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); assert(statx.mask.TYPE); @@ -2076,18 +2081,23 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const linux = std.os.linux; + const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) + .{ .major = 30, .minor = 0, .patch = 0 } + else + .{ .major = 2, .minor = 28, .patch = 0 }); + const sys = if (use_c) std.c else std.os.linux; try current_thread.beginSyscall(); while (true) { var statx = std.mem.zeroes(linux.Statx); - const rc = linux.statx( + const rc = sys.statx( file.handle, "", linux.AT.EMPTY_PATH, .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true }, &statx, ); - switch (linux.errno(rc)) { + switch (sys.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); assert(statx.mask.TYPE); diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 64ea3d1a4d6faec23d61c151892970c3eff59b9a..be6f31680499fc197462f744182b288f255dd816 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -1148,9 +1148,10 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime break :x failing_allocator_inst.alloc_index; }; - var fail_index: usize = 0; - while (fail_index < needed_alloc_count) : (fail_index += 1) { - var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ .fail_index = fail_index }); + for (0..needed_alloc_count) |fail_index| { + var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ + .fail_index = fail_index, + }); args.@"0" = failing_allocator_inst.allocator(); if (@call(.auto, test_fn, args)) |_| { diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 0342874cf9e24f04833e7d8e5625fde04a9ec51b..907256c2e4e53096c7077a710d1fc6c8bb4a9c5e 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -168,7 +168,10 @@ pub fn renderToStderr(eb: ErrorBundle, io: Io, options: RenderOptions, color: st var buffer: [256]u8 = undefined; const stderr = try io.lockStderrWriter(&buffer); defer io.unlockStderrWriter(); - try renderToWriter(eb, options, &stderr.interface, color.getTtyConf(stderr.mode)); + renderToWriter(eb, options, &stderr.interface, color.getTtyConf(stderr.mode)) catch |err| switch (err) { + error.WriteFailed => return stderr.interface.err.?, + else => |e| return e, + }; } pub fn renderToWriter( diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 5318eab2df3265ceb2751e83f4ccc191ca71612b..6c11c3021b2f16efa75c511e44b1e1fa77a64081 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6362,9 +6362,9 @@ fn testParse(io: Io, source: [:0]const u8, allocator: Allocator, anything_change return formatted; } fn testTransformImpl( - io: Io, allocator: Allocator, fba: *std.heap.FixedBufferAllocator, + io: Io, source: [:0]const u8, expected_source: []const u8, ) !void { @@ -6386,7 +6386,7 @@ fn testTransform(source: [:0]const u8, expected_source: []const u8) !void { const io = std.testing.io; var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); return std.testing.checkAllAllocationFailures(fixed_allocator.allocator(), testTransformImpl, .{ - io, &fixed_allocator, source, expected_source, + &fixed_allocator, io, source, expected_source, }); } fn testCanonical(source: [:0]const u8) !void { diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 06767b3709bf53ee6e662734a0fc15803c6c2c21..dca3dca503beb11992595de3cabcd9a541e34921 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -1,3 +1,4 @@ +/// TODO add a mapped file abstraction to std.Io const MappedFile = @This(); const builtin = @import("builtin"); @@ -74,18 +75,41 @@ pub fn init(file: std.Io.File, gpa: std.mem.Allocator, io: Io) !MappedFile { }; } if (is_linux) { - const statx = try linux.wrapped.statx( - mf.file.handle, - "", - std.posix.AT.EMPTY_PATH, - .{ .TYPE = true, .SIZE = true, .BLOCKS = true }, - ); - assert(statx.mask.TYPE); - assert(statx.mask.SIZE); - assert(statx.mask.BLOCKS); - - if (!std.posix.S.ISREG(statx.mode)) return error.PathAlreadyExists; - break :stat .{ statx.size, @max(std.heap.pageSize(), statx.blksize) }; + const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) + .{ .major = 30, .minor = 0, .patch = 0 } + else + .{ .major = 2, .minor = 28, .patch = 0 }); + const sys = if (use_c) std.c else std.os.linux; + while (true) { + var statx = std.mem.zeroes(linux.Statx); + const rc = sys.statx( + mf.file.handle, + "", + std.posix.AT.EMPTY_PATH, + .{ .TYPE = true, .SIZE = true, .BLOCKS = true }, + &statx, + ); + switch (sys.errno(rc)) { + .SUCCESS => { + assert(statx.mask.TYPE); + assert(statx.mask.SIZE); + assert(statx.mask.BLOCKS); + if (!std.posix.S.ISREG(statx.mode)) return error.PathAlreadyExists; + break :stat .{ statx.size, @max(std.heap.pageSize(), statx.blksize) }; + }, + .INTR => continue, + .ACCES => return error.AccessDenied, + .BADF => if (std.debug.runtime_safety) unreachable else return error.Unexpected, + .FAULT => if (std.debug.runtime_safety) unreachable else return error.Unexpected, + .INVAL => if (std.debug.runtime_safety) unreachable else return error.Unexpected, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.FileNotFound, + .NOMEM => return error.SystemResources, + else => |err| return std.posix.unexpectedErrno(err), + } + } } const stat = try std.posix.fstat(mf.file.handle); if (!std.posix.S.ISREG(stat.mode)) return error.PathAlreadyExists; diff --git a/src/main.zig b/src/main.zig index f735f671c6840f959c837955f841c5897a2aa67e..56a014bb9257834220e3dc87ec8b681695f2f699 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4607,7 +4607,7 @@ fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node) if (errors.errorMessageCount() > 0) { const io = comp.io; - errors.renderToStderr(io, .{}, color); + try errors.renderToStderr(io, .{}, color); return error.CompileErrorsReported; } } @@ -4660,7 +4660,7 @@ fn cmdTranslateC( return; } else { const color: Color = .auto; - result.errors.renderToStderr(io, .{}, color); + result.errors.renderToStderr(io, .{}, color) catch {}; process.exit(1); } } @@ -5281,7 +5281,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) if (fetch.error_bundle.root_list.items.len > 0) { var errors = try fetch.error_bundle.toOwnedBundle(""); - errors.renderToStderr(io, .{}, color); + errors.renderToStderr(io, .{}, color) catch {}; process.exit(1); } @@ -6213,7 +6213,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { try wip_errors.init(arena); try wip_errors.addZirErrorMessages(zir, tree, source, display_path); var error_bundle = try wip_errors.toOwnedBundle(""); - error_bundle.renderToStderr(io, .{}, color); + try error_bundle.renderToStderr(io, .{}, color); if (zir.loweringFailed()) { process.exit(1); } @@ -6284,7 +6284,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void { try wip_errors.init(arena); try wip_errors.addZoirErrorMessages(zoir, tree, source, display_path); var error_bundle = try wip_errors.toOwnedBundle(""); - error_bundle.renderToStderr(io, .{}, color); + error_bundle.renderToStderr(io, .{}, color) catch {}; process.exit(1); } @@ -6558,7 +6558,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { try wip_errors.init(arena); try wip_errors.addZirErrorMessages(old_zir, old_tree, old_source, old_source_path); var error_bundle = try wip_errors.toOwnedBundle(""); - error_bundle.renderToStderr(io, .{}, color); + error_bundle.renderToStderr(io, .{}, color) catch {}; process.exit(1); } @@ -6570,7 +6570,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void { try wip_errors.init(arena); try wip_errors.addZirErrorMessages(new_zir, new_tree, new_source, new_source_path); var error_bundle = try wip_errors.toOwnedBundle(""); - error_bundle.renderToStderr(io, .{}, color); + error_bundle.renderToStderr(io, .{}, color) catch {}; process.exit(1); } @@ -7006,7 +7006,7 @@ fn cmdFetch( if (fetch.error_bundle.root_list.items.len > 0) { var errors = try fetch.error_bundle.toOwnedBundle(""); - errors.renderToStderr(io, .{}, color); + errors.renderToStderr(io, .{}, color) catch {}; process.exit(1); } @@ -7363,7 +7363,7 @@ fn loadManifest( var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStderr(io, .{}, options.color); + error_bundle.renderToStderr(io, .{}, options.color) catch {}; process.exit(2); } -- 2.54.0 From 16bd2e137e56f842a9ff5e015425f9e08eeb97fd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 14 Dec 2025 23:35:33 -0800 Subject: [PATCH 060/195] compiler: fix most compilation errors from std.fs changes --- lib/compiler/aro/aro/Diagnostics.zig | 2 +- lib/compiler/aro/aro/Preprocessor.zig | 6 +- lib/std/Build/Cache/Path.zig | 3 +- lib/std/Io/Dir.zig | 2 +- lib/std/crypto/Certificate/Bundle.zig | 3 +- lib/std/fs/test.zig | 2 +- lib/std/zig.zig | 2 +- lib/std/zig/ErrorBundle.zig | 4 +- lib/std/zig/LibCDirs.zig | 19 ++-- lib/std/zig/LibCInstallation.zig | 6 +- src/Builtin.zig | 2 +- src/Compilation.zig | 138 +++++++++++++------------- src/Package/Fetch.zig | 37 +++---- src/Package/Fetch/git.zig | 2 +- src/Sema.zig | 2 +- src/Zcu.zig | 17 ++-- src/Zcu/PerThread.zig | 50 +++++++--- src/fmt.zig | 12 +-- src/libs/freebsd.zig | 4 +- src/libs/glibc.zig | 4 +- src/libs/netbsd.zig | 4 +- src/link.zig | 5 +- src/main.zig | 7 +- 23 files changed, 177 insertions(+), 156 deletions(-) diff --git a/lib/compiler/aro/aro/Diagnostics.zig b/lib/compiler/aro/aro/Diagnostics.zig index 9cbc134f021941d35234d54f729b28b10cee425e..bebcf4e3d2fb786a35d7ecb9717c421d077d2a7d 100644 --- a/lib/compiler/aro/aro/Diagnostics.zig +++ b/lib/compiler/aro/aro/Diagnostics.zig @@ -24,7 +24,7 @@ pub const Message = struct { @"fatal error", }; - pub fn write(msg: Message, w: *std.Io.Writer, config: std.Io.tty.Config, details: bool) std.Io.tty.Config.SetColorError!void { + pub fn write(msg: Message, w: *std.Io.Writer, config: std.Io.File.Writer.Mode, details: bool) std.Io.tty.Config.SetColorError!void { try config.setColor(w, .bold); if (msg.location) |loc| { try w.print("{s}:{d}:{d}: ", .{ loc.path, loc.line_no, loc.col }); diff --git a/lib/compiler/aro/aro/Preprocessor.zig b/lib/compiler/aro/aro/Preprocessor.zig index e8343dc83a0d0a16a2fc4b44f83d6ca4ffa9552c..6e36703df14a100feca2b200e1998962d2d7c99f 100644 --- a/lib/compiler/aro/aro/Preprocessor.zig +++ b/lib/compiler/aro/aro/Preprocessor.zig @@ -1065,11 +1065,13 @@ fn fatalNotFound(pp: *Preprocessor, tok: TokenWithExpansionLocs, filename: []con fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) void { @branchHint(.cold); - const source = pp.comp.getSource(raw.source); + const comp = pp.comp; + const io = comp.io; + const source = comp.getSource(raw.source); const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start }); var stderr_buf: [4096]u8 = undefined; - var stderr = Io.File.stderr().writer(&stderr_buf); + var stderr = Io.File.stderr().writer(io, &stderr_buf); const w = &stderr.interface; w.print("{s}:{d}:{d}: ", .{ source.path, line_col.line_no, line_col.col }) catch return; diff --git a/lib/std/Build/Cache/Path.zig b/lib/std/Build/Cache/Path.zig index 51f9e7aecd15b5527f005460d17acc86d62f6f20..3ef4dec658a6ff676ff36200cacfe44c45e0ab33 100644 --- a/lib/std/Build/Cache/Path.zig +++ b/lib/std/Build/Cache/Path.zig @@ -106,6 +106,7 @@ pub fn statFile(p: Path, io: Io, sub_path: []const u8) !Io.Dir.Stat { pub fn atomicFile( p: Path, + io: Io, sub_path: []const u8, options: Io.Dir.AtomicFileOptions, buf: *[fs.max_path_bytes]u8, @@ -115,7 +116,7 @@ pub fn atomicFile( p.sub_path, sub_path, }) catch return error.NameTooLong; }; - return p.root_dir.handle.atomicFile(joined_path, options); + return p.root_dir.handle.atomicFile(io, joined_path, options); } pub fn access(p: Path, io: Io, sub_path: []const u8, flags: Io.Dir.AccessOptions) !void { diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 5cd3b898d8c0d287aeb337475a3a5255a029cb23..94c159d0bfb4b0c5bf6c82283a1e4830454f3185 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -1645,7 +1645,7 @@ pub fn copyFile( .permissions = permissions, .write_buffer = &buffer, }); - defer atomic_file.deinit(io); + defer atomic_file.deinit(); _ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { error.ReadFailed => return file_reader.err.?, diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index a4af82b2f269a7b48bbd4ae3fc89b687d14d58c7..385ef23c9c7ee205a24263472b08ee107ed95234 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -202,7 +202,7 @@ pub const AddCertsFromDirError = AddCertsFromFilePathError; pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, iterable_dir: Io.Dir) AddCertsFromDirError!void { var it = iterable_dir.iterate(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { switch (entry.kind) { .file, .sym_link => {}, else => continue, @@ -243,6 +243,7 @@ pub fn addCertsFromFilePath( pub const AddCertsFromFileError = Allocator.Error || Io.File.Reader.Error || + Io.File.Reader.SizeError || ParseCertError || std.base64.Error || error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker, Streaming }; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index fa3eaed3fa9515badc978106420307df1890048e..53612e4f960a8442ebdf3f18f7af015f7fd9aef4 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1598,7 +1598,7 @@ test "AtomicFile" { { var buffer: [100]u8 = undefined; - var af = try ctx.dir.atomicFile(test_out_file, .{ .write_buffer = &buffer }); + var af = try ctx.dir.atomicFile(io, test_out_file, .{ .write_buffer = &buffer }); defer af.deinit(); try af.file_writer.interface.writeAll(test_content); try af.finish(); diff --git a/lib/std/zig.zig b/lib/std/zig.zig index a094dee83f6174c8571138bc67237c163efc6bde..6e89c75d919c6f33b909a436d27885ec68b5be79 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -648,7 +648,7 @@ pub fn printAstErrorsToStderr(gpa: Allocator, io: Io, tree: Ast, path: []const u var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStderr(io, .{}, color); + return error_bundle.renderToStderr(io, .{}, color); } pub fn putAstErrorsIntoBundle( diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 907256c2e4e53096c7077a710d1fc6c8bb4a9c5e..556b1167a33c7fa762ec022d43fce454bbd50477 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -162,14 +162,14 @@ pub const RenderOptions = struct { include_log_text: bool = true, }; -pub const RenderToStderrError = Io.Cancelable || Io.File.Writer.Mode.SetColorError; +pub const RenderToStderrError = Io.Cancelable || Io.File.Writer.Error; pub fn renderToStderr(eb: ErrorBundle, io: Io, options: RenderOptions, color: std.zig.Color) RenderToStderrError!void { var buffer: [256]u8 = undefined; const stderr = try io.lockStderrWriter(&buffer); defer io.unlockStderrWriter(); renderToWriter(eb, options, &stderr.interface, color.getTtyConf(stderr.mode)) catch |err| switch (err) { - error.WriteFailed => return stderr.interface.err.?, + error.WriteFailed => return stderr.err.?, else => |e| return e, }; } diff --git a/lib/std/zig/LibCDirs.zig b/lib/std/zig/LibCDirs.zig index fa297cd53aae147c1eac5267ce4adbfe4bffca95..e05ccde5895c7eae0426a64ff1b5c176490065df 100644 --- a/lib/std/zig/LibCDirs.zig +++ b/lib/std/zig/LibCDirs.zig @@ -1,3 +1,11 @@ +const LibCDirs = @This(); +const builtin = @import("builtin"); + +const std = @import("../std.zig"); +const Io = std.Io; +const LibCInstallation = std.zig.LibCInstallation; +const Allocator = std.mem.Allocator; + libc_include_dir_list: []const []const u8, libc_installation: ?*const LibCInstallation, libc_framework_dir_list: []const []const u8, @@ -14,6 +22,7 @@ pub const DarwinSdkLayout = enum { pub fn detect( arena: Allocator, + io: Io, zig_lib_dir: []const u8, target: *const std.Target, is_native_abi: bool, @@ -38,7 +47,7 @@ pub fn detect( // using the system libc installation. if (is_native_abi and !target.isMinGW()) { const libc = try arena.create(LibCInstallation); - libc.* = LibCInstallation.findNative(.{ .allocator = arena, .target = target }) catch |err| switch (err) { + libc.* = LibCInstallation.findNative(arena, io, .{ .target = target }) catch |err| switch (err) { error.CCompilerExitCode, error.CCompilerCrashed, error.CCompilerCannotFindHeaders, @@ -75,7 +84,7 @@ pub fn detect( if (use_system_abi) { const libc = try arena.create(LibCInstallation); - libc.* = try LibCInstallation.findNative(.{ .allocator = arena, .verbose = true, .target = target }); + libc.* = try LibCInstallation.findNative(arena, io, .{ .verbose = true, .target = target }); return detectFromInstallation(arena, target, libc); } @@ -265,9 +274,3 @@ fn libCGenericName(target: *const std.Target) [:0]const u8 { => unreachable, } } - -const LibCDirs = @This(); -const builtin = @import("builtin"); -const std = @import("../std.zig"); -const LibCInstallation = std.zig.LibCInstallation; -const Allocator = std.mem.Allocator; diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index 6051eecb948ec5dccbb9f1ef93bcaf445502714b..c1fc8fccaa511a29122cfca595ac8ce2abe81034 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -204,7 +204,7 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib try self.findNativeIncludeDirWindows(gpa, io, args, sdk); try self.findNativeCrtDirWindows(gpa, io, args.target, sdk); } else if (is_haiku) { - try self.findNativeIncludeDirPosix(args); + try self.findNativeIncludeDirPosix(gpa, io, args); try self.findNativeGccDirHaiku(gpa, io, args); self.crt_dir = try gpa.dupeZ(u8, "/system/develop/lib"); } else if (builtin.target.os.tag == .illumos) { @@ -213,7 +213,7 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib self.sys_include_dir = try gpa.dupeZ(u8, "/usr/include"); self.crt_dir = try gpa.dupeZ(u8, "/usr/lib/64"); } else if (std.process.can_spawn) { - try self.findNativeIncludeDirPosix(args); + try self.findNativeIncludeDirPosix(gpa, io, args); switch (builtin.target.os.tag) { .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try gpa.dupeZ(u8, "/usr/lib"), .linux => try self.findNativeCrtDirPosix(gpa, io, args), @@ -335,7 +335,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar defer search_dir.close(io); if (self.include_dir == null) { - if (search_dir.access(include_dir_example_file, .{})) |_| { + if (search_dir.access(io, include_dir_example_file, .{})) |_| { self.include_dir = try gpa.dupeZ(u8, search_path); } else |err| switch (err) { error.FileNotFound => {}, diff --git a/src/Builtin.zig b/src/Builtin.zig index 9e4fae8e6a1832e6cfba0eea1ec68963021c1c4a..a097e8873456d6ce8752a2bb25418a1dbc1e9ca5 100644 --- a/src/Builtin.zig +++ b/src/Builtin.zig @@ -343,7 +343,7 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void { } // `make_path` matters because the dir hasn't actually been created yet. - var af = try root_dir.atomicFile(sub_path, .{ .make_path = true, .write_buffer = &.{} }); + var af = try root_dir.atomicFile(io, sub_path, .{ .make_path = true, .write_buffer = &.{} }); defer af.deinit(); try af.file_writer.interface.writeAll(file.source.?); af.finish() catch |err| switch (err) { diff --git a/src/Compilation.zig b/src/Compilation.zig index 47098317b5d1e976f8b95e06036bcec24336fbb0..37e15ab17130e0cf414dbbb87558e4a4f735e6f4 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -771,8 +771,8 @@ pub const Directories = struct { const zig_lib: Cache.Directory = d: { if (override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib"); if (wasi) break :d openWasiPreopen(wasi_preopens, "/lib"); - break :d introspect.findZigLibDirFromSelfExe(arena, cwd, self_exe_path) catch |err| { - fatal("unable to find zig installation directory '{s}': {s}", .{ self_exe_path, @errorName(err) }); + break :d introspect.findZigLibDirFromSelfExe(arena, io, cwd, self_exe_path) catch |err| { + fatal("unable to find zig installation directory '{s}': {t}", .{ self_exe_path, err }); }; }; @@ -780,7 +780,7 @@ pub const Directories = struct { if (override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache"); if (wasi) break :d openWasiPreopen(wasi_preopens, "/cache"); const path = introspect.resolveGlobalCacheDir(arena) catch |err| { - fatal("unable to resolve zig cache directory: {s}", .{@errorName(err)}); + fatal("unable to resolve zig cache directory: {t}", .{err}); }; break :d openUnresolved(arena, io, cwd, path, .@"global cache"); }; @@ -789,7 +789,7 @@ pub const Directories = struct { .override => |path| openUnresolved(arena, io, cwd, path, .@"local cache"), .search => d: { const maybe_path = introspect.resolveSuitableLocalCacheDir(arena, io, cwd) catch |err| { - fatal("unable to resolve zig cache directory: {s}", .{@errorName(err)}); + fatal("unable to resolve zig cache directory: {t}", .{err}); }; const path = maybe_path orelse break :d global_cache; break :d openUnresolved(arena, io, cwd, path, .@"local cache"); @@ -919,8 +919,8 @@ pub const CrtFile = struct { lock: Cache.Lock, full_object_path: Cache.Path, - pub fn deinit(self: *CrtFile, gpa: Allocator) void { - self.lock.release(); + pub fn deinit(self: *CrtFile, gpa: Allocator, io: Io) void { + self.lock.release(io); gpa.free(self.full_object_path.sub_path); self.* = undefined; } @@ -1317,7 +1317,7 @@ pub const CObject = struct { }; /// Returns if there was failure. - pub fn clearStatus(self: *CObject, gpa: Allocator) bool { + pub fn clearStatus(self: *CObject, gpa: Allocator, io: Io) bool { switch (self.status) { .new => return false, .failure, .failure_retryable => { @@ -1326,15 +1326,15 @@ pub const CObject = struct { }, .success => |*success| { gpa.free(success.object_path.sub_path); - success.lock.release(); + success.lock.release(io); self.status = .new; return false; }, } } - pub fn destroy(self: *CObject, gpa: Allocator) void { - _ = self.clearStatus(gpa); + pub fn destroy(self: *CObject, gpa: Allocator, io: Io) void { + _ = self.clearStatus(gpa, io); gpa.destroy(self); } }; @@ -1364,7 +1364,7 @@ pub const Win32Resource = struct { }, /// Returns true if there was failure. - pub fn clearStatus(self: *Win32Resource, gpa: Allocator) bool { + pub fn clearStatus(self: *Win32Resource, gpa: Allocator, io: Io) bool { switch (self.status) { .new => return false, .failure, .failure_retryable => { @@ -1373,15 +1373,15 @@ pub const Win32Resource = struct { }, .success => |*success| { gpa.free(success.res_path); - success.lock.release(); + success.lock.release(io); self.status = .new; return false; }, } } - pub fn destroy(self: *Win32Resource, gpa: Allocator) void { - _ = self.clearStatus(gpa); + pub fn destroy(self: *Win32Resource, gpa: Allocator, io: Io) void { + _ = self.clearStatus(gpa, io); gpa.destroy(self); } }; @@ -1610,9 +1610,9 @@ const CacheUse = union(CacheMode) { /// Prevents other processes from clobbering files in the output directory. lock: ?Cache.Lock, - fn releaseLock(whole: *Whole) void { + fn releaseLock(whole: *Whole, io: Io) void { if (whole.lock) |*lock| { - lock.release(); + lock.release(io); whole.lock = null; } } @@ -1634,7 +1634,7 @@ const CacheUse = union(CacheMode) { }, .whole => |whole| { assert(whole.tmp_artifact_directory == null); - whole.releaseLock(); + whole.releaseLock(io); }, } } @@ -1903,13 +1903,17 @@ pub const CreateDiagnostic = union(enum) { return error.CreateFail; } }; -pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, options: CreateOptions) error{ + +pub const CreateError = error{ OutOfMemory, + Canceled, Unexpected, CurrentWorkingDirectoryUnlinked, /// An error has been stored to `diag`. CreateFail, -}!*Compilation { +}; + +pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, options: CreateOptions) CreateError!*Compilation { const output_mode = options.config.output_mode; const is_dyn_lib = switch (output_mode) { .Obj, .Exe => false, @@ -1957,6 +1961,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, const libc_dirs = std.zig.LibCDirs.detect( arena, + io, options.dirs.zig_lib.path.?, target, options.root_mod.resolved_target.is_native_abi, @@ -2701,7 +2706,7 @@ pub fn destroy(comp: *Compilation) void { if (comp.bin_file) |lf| lf.destroy(); if (comp.zcu) |zcu| zcu.deinit(); - comp.cache_use.deinit(); + comp.cache_use.deinit(io); for (&comp.work_queues) |*work_queue| work_queue.deinit(gpa); comp.c_object_work_queue.deinit(gpa); @@ -2714,36 +2719,36 @@ pub fn destroy(comp: *Compilation) void { var it = comp.crt_files.iterator(); while (it.next()) |entry| { gpa.free(entry.key_ptr.*); - entry.value_ptr.deinit(gpa); + entry.value_ptr.deinit(gpa, io); } comp.crt_files.deinit(gpa); } - if (comp.libcxx_static_lib) |*crt_file| crt_file.deinit(gpa); - if (comp.libcxxabi_static_lib) |*crt_file| crt_file.deinit(gpa); - if (comp.libunwind_static_lib) |*crt_file| crt_file.deinit(gpa); - if (comp.tsan_lib) |*crt_file| crt_file.deinit(gpa); - if (comp.ubsan_rt_lib) |*crt_file| crt_file.deinit(gpa); - if (comp.ubsan_rt_obj) |*crt_file| crt_file.deinit(gpa); - if (comp.zigc_static_lib) |*crt_file| crt_file.deinit(gpa); - if (comp.compiler_rt_lib) |*crt_file| crt_file.deinit(gpa); - if (comp.compiler_rt_obj) |*crt_file| crt_file.deinit(gpa); - if (comp.compiler_rt_dyn_lib) |*crt_file| crt_file.deinit(gpa); - if (comp.fuzzer_lib) |*crt_file| crt_file.deinit(gpa); + if (comp.libcxx_static_lib) |*crt_file| crt_file.deinit(gpa, io); + if (comp.libcxxabi_static_lib) |*crt_file| crt_file.deinit(gpa, io); + if (comp.libunwind_static_lib) |*crt_file| crt_file.deinit(gpa, io); + if (comp.tsan_lib) |*crt_file| crt_file.deinit(gpa, io); + if (comp.ubsan_rt_lib) |*crt_file| crt_file.deinit(gpa, io); + if (comp.ubsan_rt_obj) |*crt_file| crt_file.deinit(gpa, io); + if (comp.zigc_static_lib) |*crt_file| crt_file.deinit(gpa, io); + if (comp.compiler_rt_lib) |*crt_file| crt_file.deinit(gpa, io); + if (comp.compiler_rt_obj) |*crt_file| crt_file.deinit(gpa, io); + if (comp.compiler_rt_dyn_lib) |*crt_file| crt_file.deinit(gpa, io); + if (comp.fuzzer_lib) |*crt_file| crt_file.deinit(gpa, io); if (comp.glibc_so_files) |*glibc_file| { - glibc_file.deinit(gpa); + glibc_file.deinit(gpa, io); } if (comp.freebsd_so_files) |*freebsd_file| { - freebsd_file.deinit(gpa); + freebsd_file.deinit(gpa, io); } if (comp.netbsd_so_files) |*netbsd_file| { - netbsd_file.deinit(gpa); + netbsd_file.deinit(gpa, io); } for (comp.c_object_table.keys()) |key| { - key.destroy(gpa); + key.destroy(gpa, io); } comp.c_object_table.deinit(gpa); @@ -2753,7 +2758,7 @@ pub fn destroy(comp: *Compilation) void { comp.failed_c_objects.deinit(gpa); for (comp.win32_resource_table.keys()) |key| { - key.destroy(gpa); + key.destroy(gpa, io); } comp.win32_resource_table.deinit(gpa); @@ -2906,7 +2911,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE .whole => |whole| { assert(comp.bin_file == null); // We are about to obtain this lock, so here we give other processes a chance first. - whole.releaseLock(); + whole.releaseLock(io); man = comp.cache_parent.obtain(); whole.cache_manifest = &man; @@ -3092,17 +3097,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE } if (build_options.enable_debug_extensions and comp.verbose_intern_pool) { - std.debug.print("intern pool stats for '{s}':\n", .{ - comp.root_name, - }); - zcu.intern_pool.dump(); + std.debug.print("intern pool stats for '{s}':\n", .{comp.root_name}); + zcu.intern_pool.dump(io); } if (build_options.enable_debug_extensions and comp.verbose_generic_instances) { - std.debug.print("generic instances for '{s}:0x{x}':\n", .{ - comp.root_name, - @intFromPtr(zcu), - }); + std.debug.print("generic instances for '{s}:0x{x}':\n", .{ comp.root_name, @intFromPtr(zcu) }); zcu.intern_pool.dumpGenericInstances(gpa); } } @@ -3680,6 +3680,7 @@ pub fn saveState(comp: *Compilation) !void { const lf = comp.bin_file orelse return; const gpa = comp.gpa; + const io = comp.io; var bufs = std.array_list.Managed([]const u8).init(gpa); defer bufs.deinit(); @@ -3900,7 +3901,7 @@ pub fn saveState(comp: *Compilation) !void { // Using an atomic file prevents a crash or power failure from corrupting // the previous incremental compilation state. var write_buffer: [1024]u8 = undefined; - var af = try lf.emit.root_dir.handle.atomicFile(basename, .{ .write_buffer = &write_buffer }); + var af = try lf.emit.root_dir.handle.atomicFile(io, basename, .{ .write_buffer = &write_buffer }); defer af.deinit(); try af.file_writer.interface.writeVecAll(bufs.items); try af.finish(); @@ -4258,8 +4259,8 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { // However, we haven't reported any such error. // This is a compiler bug. print_ctx: { - const stderr = try io.lockStderrWriter(&.{}); - defer io.unlockStderrWriter(); + const stderr = std.debug.lockStderrWriter(&.{}); + defer std.debug.unlockStderrWriter(); const w = &stderr.interface; w.writeAll("referenced transitive analysis errors, but none actually emitted\n") catch break :print_ctx; w.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}) catch break :print_ctx; @@ -5219,13 +5220,10 @@ fn processOneJob( } } -fn createDepFile( - comp: *Compilation, - depfile: []const u8, - binfile: Cache.Path, -) anyerror!void { +fn createDepFile(comp: *Compilation, depfile: []const u8, binfile: Cache.Path) anyerror!void { + const io = comp.io; var buf: [4096]u8 = undefined; - var af = try Io.Dir.cwd().atomicFile(depfile, .{ .write_buffer = &buf }); + var af = try Io.Dir.cwd().atomicFile(io, depfile, .{ .write_buffer = &buf }); defer af.deinit(); comp.writeDepFile(binfile, &af.file_writer.interface) catch return af.file_writer.err.?; @@ -5280,13 +5278,8 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { for (&[_][]const u8{ "docs/main.js", "docs/index.html" }) |sub_path| { const basename = fs.path.basename(sub_path); - comp.dirs.zig_lib.handle.copyFile(sub_path, out_dir, basename, .{}) catch |err| { - comp.lockAndSetMiscFailure(.docs_copy, "unable to copy {s}: {s}", .{ - sub_path, - @errorName(err), - }); - return; - }; + comp.dirs.zig_lib.handle.copyFile(sub_path, out_dir, basename, io, .{}) catch |err| + return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy {s}: {t}", .{ sub_path, err }); } var tar_file = out_dir.createFile(io, "sources.tar", .{}) catch |err| { @@ -5350,7 +5343,7 @@ fn docsCopyModule( var buffer: [1024]u8 = undefined; - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { switch (entry.kind) { .file => { if (!std.mem.endsWith(u8, entry.basename, ".zig")) continue; @@ -5505,7 +5498,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU try comp.updateSubCompilation(sub_compilation, .docs_wasm, prog_node); var crt_file = try sub_compilation.toCrtFile(); - defer crt_file.deinit(gpa); + defer crt_file.deinit(gpa, io); const docs_bin_file = crt_file.full_object_path; assert(docs_bin_file.sub_path.len > 0); // emitted binary is not a directory @@ -5521,10 +5514,12 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU }; defer out_dir.close(io); - crt_file.full_object_path.root_dir.handle.copyFile( + Io.Dir.copyFile( + crt_file.full_object_path.root_dir.handle, crt_file.full_object_path.sub_path, out_dir, "main.wasm", + io, .{}, ) catch |err| { comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{f}' to '{f}': {t}", .{ @@ -5758,7 +5753,7 @@ pub fn translateC( try argv.appendSlice(comp.global_cc_argv); try argv.appendSlice(owner_mod.cc_argv); try argv.appendSlice(&.{ source_path, "-o", translated_path }); - if (comp.verbose_cimport) dumpArgv(io, argv.items); + if (comp.verbose_cimport) try dumpArgv(io, argv.items); } var stdout: []u8 = undefined; @@ -6153,7 +6148,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr const gpa = comp.gpa; const io = comp.io; - if (c_object.clearStatus(gpa)) { + if (c_object.clearStatus(gpa, io)) { // There was previous failure. comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); @@ -6500,7 +6495,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - if (win32_resource.clearStatus(comp.gpa)) { + if (win32_resource.clearStatus(comp.gpa, io)) { // There was previous failure. comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); @@ -6768,7 +6763,7 @@ fn spawnZigRc( // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace) const stderr = poller.reader(.stderr); - const term = child.wait() catch |err| { + const term = child.wait(io) catch |err| { return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv[0], @errorName(err) }); }; @@ -7781,7 +7776,10 @@ pub fn dumpArgv(io: Io, argv: []const []const u8) Io.Cancelable!void { defer io.unlockStderrWriter(); const w = &stderr.interface; return dumpArgvWriter(w, argv) catch |err| switch (err) { - error.WriteFailed => return stderr.err.?, + error.WriteFailed => switch (stderr.err.?) { + error.Canceled => return error.Canceled, + else => return, + }, }; } diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 33214a47dbf0d74f1af158b227fa71b6828b5942..823015574b52ccd54c89dab55586a6dfc2f39bdf 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -501,7 +501,7 @@ fn runResource( .path = tmp_directory_path, .handle = handle: { const dir = cache_root.handle.makeOpenPath(io, tmp_dir_sub_path, .{ - .iterate = true, + .open_options = .{ .iterate = true }, }) catch |err| { try eb.addRootErrorMessage(.{ .msg = try eb.printString("unable to create temporary directory '{s}': {t}", .{ @@ -525,7 +525,7 @@ fn runResource( // https://github.com/ziglang/zig/issues/17095 pkg_path.root_dir.handle.close(io); pkg_path.root_dir.handle = cache_root.handle.makeOpenPath(io, tmp_dir_sub_path, .{ - .iterate = true, + .open_options = .{ .iterate = true }, }) catch @panic("btrfs workaround failed"); } @@ -1334,7 +1334,7 @@ fn unzip( f.location_tok, try eb.printString("failed writing temporary zip file: {t}", .{err}), ); - break :b zip_file_writer.moveToReader(io); + break :b zip_file_writer.moveToReader(); }; var diagnostics: std.zip.Diagnostics = .{ .allocator = f.arena.allocator() }; @@ -1376,7 +1376,7 @@ fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!U const fetch_reader = &resource.fetch_stream.reader; _ = try fetch_reader.streamRemaining(&pack_file_writer.interface); try pack_file_writer.interface.flush(); - break :b pack_file_writer.moveToReader(io); + break :b pack_file_writer.moveToReader(); }; var index_file = try pack_dir.createFile(io, "pkg.idx", .{ .read = true }); @@ -1421,26 +1421,21 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void // Recursive directory copy. var it = try dir.walk(gpa); defer it.deinit(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { switch (entry.kind) { .directory => {}, // omit empty directories .file => { - dir.copyFile( - entry.path, - tmp_dir, - entry.path, - .{}, - ) catch |err| switch (err) { + dir.copyFile(entry.path, tmp_dir, entry.path, io, .{}) catch |err| switch (err) { error.FileNotFound => { if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(io, dirname); - try dir.copyFile(entry.path, tmp_dir, entry.path, .{}); + try dir.copyFile(entry.path, tmp_dir, entry.path, io, .{}); }, else => |e| return e, }; }, .sym_link => { var buf: [fs.max_path_bytes]u8 = undefined; - const link_name = try dir.readLink(io, entry.path, &buf); + const link_name = buf[0..try dir.readLink(io, entry.path, &buf)]; // TODO: if this would create a symlink to outside // the destination directory, fail with an error instead. tmp_dir.symLink(io, link_name, entry.path, .{}) catch |err| switch (err) { @@ -1524,7 +1519,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute var group: Io.Group = .init; defer group.wait(io); - while (walker.next() catch |err| { + while (walker.next(io) catch |err| { try eb.addRootErrorMessage(.{ .msg = try eb.printString( "unable to walk temporary directory '{f}': {s}", .{ pkg_path, @errorName(err) }, @@ -1575,7 +1570,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute .failure = undefined, // to be populated by the worker .size = undefined, // to be populated by the worker }; - group.async(io, workerHashFile, .{ root_dir, hashed_file }); + group.async(io, workerHashFile, .{ io, root_dir, hashed_file }); try all_files.append(hashed_file); } } @@ -1643,7 +1638,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute assert(!f.job_queue.recursive); // Print something to stdout that can be text diffed to figure out why // the package hash is different. - dumpHashInfo(all_files.items) catch |err| { + dumpHashInfo(io, all_files.items) catch |err| { std.debug.print("unable to write to stdout: {s}\n", .{@errorName(err)}); std.process.exit(1); }; @@ -1655,9 +1650,9 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute }; } -fn dumpHashInfo(all_files: []const *const HashedFile) !void { +fn dumpHashInfo(io: Io, all_files: []const *const HashedFile) !void { var stdout_buffer: [1024]u8 = undefined; - var stdout_writer: Io.File.Writer = .initStreaming(.stdout(), &stdout_buffer); + var stdout_writer: Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer); const w = &stdout_writer.interface; for (all_files) |hashed_file| { try w.print("{t}: {x}: {s}\n", .{ hashed_file.kind, &hashed_file.hash, hashed_file.normalized_path }); @@ -1665,8 +1660,8 @@ fn dumpHashInfo(all_files: []const *const HashedFile) !void { try w.flush(); } -fn workerHashFile(dir: Io.Dir, hashed_file: *HashedFile) void { - hashed_file.failure = hashFileFallible(dir, hashed_file); +fn workerHashFile(io: Io, dir: Io.Dir, hashed_file: *HashedFile) void { + hashed_file.failure = hashFileFallible(io, dir, hashed_file); } fn workerDeleteFile(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) void { @@ -1745,7 +1740,7 @@ const HashedFile = struct { Io.File.OpenError || Io.File.Reader.Error || Io.File.StatError || - Io.File.ChmodError || + Io.File.SetPermissionsError || Io.Dir.ReadLinkError; const Kind = enum { file, link }; diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index f53c96cc3d4ae9cfdeb46c897af5b9713687b221..fd48b0a9b198d96da6627fd54ea65e0cad4446ea 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -274,7 +274,7 @@ pub const Repository = struct { continue; }; defer file.close(io); - try file.writeAll(file_object.data); + try file.writePositionalAll(io, file_object.data, 0); }, .symlink => { try repository.odb.seekOid(entry.oid); diff --git a/src/Sema.zig b/src/Sema.zig index 1f6a577f6056ad1a3c42918771d35a2d76e4ff5f..298de783b84cb8630f3cd5d9ac9acde9e0d7818a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2679,7 +2679,7 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory"); std.debug.print("compile error during Sema:\n", .{}); var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory"); - error_bundle.renderToStderr(io, .{}, .auto); + error_bundle.renderToStderr(io, .{}, .auto) catch @panic("failed to print to stderr"); std.debug.panicExtra(@returnAddress(), "unexpected compile error occurred", .{}); } diff --git a/src/Zcu.zig b/src/Zcu.zig index e0e254d807a93db553c1bbb6bb4be19fd9b317b4..07fb1bdc94c75e6013ef564b7097d9972acf44e9 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -2988,11 +2988,10 @@ pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_br: *Io.Reader pub fn saveZirCache( gpa: Allocator, - io: Io, - cache_file: Io.File, + cache_file_writer: *Io.File.Writer, stat: Io.File.Stat, zir: Zir, -) (Io.File.WriteError || Allocator.Error)!void { +) (Io.File.Writer.Error || Allocator.Error)!void { const safety_buffer = if (data_has_safety_tag) try gpa.alloc([8]u8, zir.instructions.len) else @@ -3026,13 +3025,12 @@ pub fn saveZirCache( zir.string_bytes, @ptrCast(zir.extra), }; - var cache_fw = cache_file.writer(io, &.{}); - cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) { - error.WriteFailed => return cache_fw.err.?, + cache_file_writer.interface.writeVecAll(&vecs) catch |err| switch (err) { + error.WriteFailed => return cache_file_writer.err.?, }; } -pub fn saveZoirCache(io: Io, cache_file: Io.File, stat: Io.File.Stat, zoir: Zoir) Io.File.WriteError!void { +pub fn saveZoirCache(cache_file_writer: *Io.File.Writer, stat: Io.File.Stat, zoir: Zoir) Io.File.Writer.Error!void { const header: Zoir.Header = .{ .nodes_len = @intCast(zoir.nodes.len), .extra_len = @intCast(zoir.extra.len), @@ -3056,9 +3054,8 @@ pub fn saveZoirCache(io: Io, cache_file: Io.File, stat: Io.File.Stat, zoir: Zoir @ptrCast(zoir.compile_errors), @ptrCast(zoir.error_notes), }; - var cache_fw = cache_file.writer(io, &.{}); - cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) { - error.WriteFailed => return cache_fw.err.?, + cache_file_writer.interface.writeVecAll(&vecs) catch |err| switch (err) { + error.WriteFailed => return cache_file_writer.err.?, }; } diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 408f16bd74af58b18716db69f8e1a519e3b67742..76ab3e229c828e5d29046882e2931973bc0daac0 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -238,18 +238,13 @@ pub fn updateFile( if (builtin.os.tag == .wasi or lock == .exclusive) break true; // Otherwise, unlock to give someone a chance to get the exclusive lock // and then upgrade to an exclusive lock. - cache_file.unlock(); + cache_file.unlock(io); lock = .exclusive; - try cache_file.lock(lock); + try cache_file.lock(io, lock); }; if (need_update) { - // The cache is definitely stale so delete the contents to avoid an underwrite later. - cache_file.setLength(io, 0) catch |err| switch (err) { - error.FileTooBig => unreachable, // 0 is not too big - else => |e| return e, - }; - try cache_file.seekTo(0); + var cache_file_writer: Io.File.Writer = .init(cache_file, io, &.{}); if (stat.size > std.math.maxInt(u32)) return error.FileTooBig; @@ -278,7 +273,7 @@ pub fn updateFile( switch (file.getMode()) { .zig => { file.zir = try AstGen.generate(gpa, file.tree.?); - Zcu.saveZirCache(gpa, io, cache_file, stat, file.zir.?) catch |err| switch (err) { + Zcu.saveZirCache(gpa, &cache_file_writer, stat, file.zir.?) catch |err| switch (err) { error.OutOfMemory => |e| return e, else => log.warn("unable to write cached ZIR code for {f} to {f}{s}: {t}", .{ file.path.fmt(comp), cache_directory, &hex_digest, err, @@ -287,13 +282,19 @@ pub fn updateFile( }, .zon => { file.zoir = try ZonGen.generate(gpa, file.tree.?, .{}); - Zcu.saveZoirCache(io, cache_file, stat, file.zoir.?) catch |err| { + Zcu.saveZoirCache(&cache_file_writer, stat, file.zoir.?) catch |err| { log.warn("unable to write cached ZOIR code for {f} to {f}{s}: {t}", .{ file.path.fmt(comp), cache_directory, &hex_digest, err, }); }; }, } + + cache_file_writer.end() catch |err| switch (err) { + error.WriteFailed => return cache_file_writer.err.?, + else => |e| return e, + }; + if (timer.finish()) |ns_astgen| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); @@ -4524,12 +4525,14 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Ru .stage2_llvm, => {}, }, + error.Canceled => |e| return e, } return error.AlreadyReported; }; } fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) error{ OutOfMemory, + Canceled, CodegenFail, NoLinkFile, BackendDoesNotProduceMir, @@ -4555,13 +4558,16 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e null; defer if (liveness) |*l| l.deinit(gpa); - if (build_options.enable_debug_extensions and comp.verbose_air) { + if (build_options.enable_debug_extensions and comp.verbose_air) p: { const io = comp.io; const stderr = try io.lockStderrWriter(&.{}); defer io.unlockStderrWriter(); - stderr.print("# Begin Function AIR: {f}:\n", .{fqn.fmt(ip)}) catch {}; - air.write(stderr, pt, liveness); - stderr.print("# End Function AIR: {f}\n\n", .{fqn.fmt(ip)}) catch {}; + printVerboseAir(pt, liveness, fqn, air, &stderr.interface) catch |err| switch (err) { + error.WriteFailed => switch (stderr.err.?) { + error.Canceled => |e| return e, + else => break :p, + }, + }; } if (std.debug.runtime_safety) verify_liveness: { @@ -4576,7 +4582,7 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e verify.verify() catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - else => return zcu.codegenFail(nav, "invalid liveness: {s}", .{@errorName(err)}), + else => return zcu.codegenFail(nav, "invalid liveness: {t}", .{err}), }; } @@ -4612,3 +4618,17 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e => return zcu.codegenFail(nav, "unable to codegen: {s}", .{@errorName(err)}), }; } + +fn printVerboseAir( + pt: Zcu.PerThread, + liveness: ?Air.Liveness, + fqn: InternPool.NullTerminatedString, + air: *const Air, + w: *Io.Writer, +) Io.Writer.Error!void { + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + try w.print("# Begin Function AIR: {f}:\n", .{fqn.fmt(ip)}); + try air.write(w, pt, liveness); + try w.print("# End Function AIR: {f}\n\n", .{fqn.fmt(ip)}); +} diff --git a/src/fmt.zig b/src/fmt.zig index b9b1cc136304747e5b4b9facb9c51c4795815274..ca6885d24e01d6a3215f8b7a8739660f7bdb287a 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -124,7 +124,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! try wip_errors.addZirErrorMessages(zir, tree, source_code, ""); var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStderr(io, .{}, color); + error_bundle.renderToStderr(io, .{}, color) catch {}; process.exit(2); } } else { @@ -138,12 +138,12 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! try wip_errors.addZoirErrorMessages(zoir, tree, source_code, ""); var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStderr(io, .{}, color); + error_bundle.renderToStderr(io, .{}, color) catch {}; process.exit(2); } } } else if (tree.errors.len != 0) { - try std.zig.printAstErrorsToStderr(gpa, tree, "", color); + std.zig.printAstErrorsToStderr(gpa, io, tree, "", color) catch {}; process.exit(2); } const formatted = try tree.renderAlloc(gpa); @@ -298,7 +298,7 @@ fn fmtPathFile( defer tree.deinit(gpa); if (tree.errors.len != 0) { - try std.zig.printAstErrorsToStderr(gpa, tree, file_path, fmt.color); + try std.zig.printAstErrorsToStderr(gpa, io, tree, file_path, fmt.color); fmt.any_error = true; return; } @@ -319,7 +319,7 @@ fn fmtPathFile( try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path); var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStderr(io, .{}, fmt.color); + try error_bundle.renderToStderr(io, .{}, fmt.color); fmt.any_error = true; } }, @@ -334,7 +334,7 @@ fn fmtPathFile( try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path); var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStderr(io, .{}, fmt.color); + try error_bundle.renderToStderr(io, .{}, fmt.color); fmt.any_error = true; } }, diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index ee638718b7c3934ea1ea1136162b2a65125c0041..44676007e2faee7ee91d79a7cf6ccffb85415da3 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -401,8 +401,8 @@ pub const BuiltSharedObjects = struct { lock: Cache.Lock, dir_path: Path, - pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void { - self.lock.release(); + pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void { + self.lock.release(io); gpa.free(self.dir_path.sub_path); self.* = undefined; } diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index de86413cfd630e72bb8b092db7374fd8243fc6c2..8371b3288d2293da2221ae6fd7839b1010d0328b 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -640,8 +640,8 @@ pub const BuiltSharedObjects = struct { lock: Cache.Lock, dir_path: Path, - pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void { - self.lock.release(); + pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void { + self.lock.release(io); gpa.free(self.dir_path.sub_path); self.* = undefined; } diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index 33dd62d851ba0f789ddddaaa7aa28d1863c9eddf..9c09c35b0a106dc84ba6f2848e4621ce15340825 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -346,8 +346,8 @@ pub const BuiltSharedObjects = struct { lock: Cache.Lock, dir_path: Path, - pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void { - self.lock.release(); + pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void { + self.lock.release(io); gpa.free(self.dir_path.sub_path); self.* = undefined; } diff --git a/src/link.zig b/src/link.zig index a4729f296ccce4154e6286dcaa52de29c96f8af0..3b822b2e691f1a987a770ba20a2511f8204ad8cb 100644 --- a/src/link.zig +++ b/src/link.zig @@ -2246,13 +2246,12 @@ fn resolvePathInputLib( var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); - error_bundle.renderToStderr(io, .{}, color); - + error_bundle.renderToStderr(io, .{}, color) catch {}; std.process.exit(1); } var ld_script = ld_script_result catch |err| - fatal("{f}: failed to parse linker script: {s}", .{ test_path, @errorName(err) }); + fatal("{f}: failed to parse linker script: {t}", .{ test_path, err }); defer ld_script.deinit(gpa); try unresolved_inputs.ensureUnusedCapacity(gpa, ld_script.args.len); diff --git a/src/main.zig b/src/main.zig index 56a014bb9257834220e3dc87ec8b681695f2f699..c5ed278921dc7d16da9d5f5f41dbb27588c62d91 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4598,6 +4598,8 @@ const UpdateModuleError = Compilation.UpdateError || error{ /// The update caused compile errors. The error bundle has already been /// reported to the user by being rendered to stderr. CompileErrorsReported, + /// Error occurred printing compilation errors to stderr. + PrintingErrorsFailed, }; fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node) UpdateModuleError!void { try comp.update(prog_node); @@ -4607,7 +4609,10 @@ fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node) if (errors.errorMessageCount() > 0) { const io = comp.io; - try errors.renderToStderr(io, .{}, color); + errors.renderToStderr(io, .{}, color) catch |err| switch (err) { + error.Canceled => |e| return e, + else => return error.PrintingErrorsFailed, + }; return error.CompileErrorsReported; } } -- 2.54.0 From 9407ad8516378c9e7cb739a7e8ef39965ba02d45 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 15 Dec 2025 15:33:52 -0800 Subject: [PATCH 061/195] std.c.utimensat: add const --- lib/std/c.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 656bdc35bbf02199c6a71ed2cab60f025b7cb0c4..30a3ac132c44a258e8544afd5fa8ef9dde552270 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -10740,7 +10740,7 @@ pub extern "c" fn free(?*anyopaque) void; pub extern "c" fn futimes(fd: fd_t, times: ?*[2]timeval) c_int; pub extern "c" fn utimes(path: [*:0]const u8, times: ?*[2]timeval) c_int; -pub extern "c" fn utimensat(dirfd: fd_t, pathname: [*:0]const u8, times: ?*[2]timespec, flags: u32) c_int; +pub extern "c" fn utimensat(dirfd: fd_t, pathname: [*:0]const u8, times: ?*const [2]timespec, flags: u32) c_int; pub extern "c" fn futimens(fd: fd_t, times: ?*const [2]timespec) c_int; pub extern "c" fn pthread_create( -- 2.54.0 From 22afd168d95b1d6be4a99196e07573bed8f4b472 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 15 Dec 2025 15:34:14 -0800 Subject: [PATCH 062/195] fix build runner compilation errors on macOS --- lib/std/Build/Watch/FsEvents.zig | 4 +-- lib/std/Io/Dir.zig | 2 ++ lib/std/Io/Threaded.zig | 44 +++++++++++++++-------------- lib/std/debug/SelfInfo/MachO.zig | 12 ++++---- lib/std/dynamic_library.zig | 4 +-- lib/std/zig/system.zig | 2 +- lib/std/zig/system/darwin/macos.zig | 4 +-- 7 files changed, 38 insertions(+), 34 deletions(-) diff --git a/lib/std/Build/Watch/FsEvents.zig b/lib/std/Build/Watch/FsEvents.zig index 59238c8725f8f1de161db4f9bd6bc976ba0f972b..2a48534b3accd009b42efa9abcb64d2da725e66f 100644 --- a/lib/std/Build/Watch/FsEvents.zig +++ b/lib/std/Build/Watch/FsEvents.zig @@ -78,10 +78,10 @@ const ResolvedSymbols = struct { kCFAllocatorUseContext: *const CFAllocatorRef, }; -pub fn init(io: Io) error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents { +pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents { var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch return error.OpenFrameworkFailed; - errdefer core_services.close(io); + errdefer core_services.close(); var resolved_symbols: ResolvedSymbols = undefined; inline for (@typeInfo(ResolvedSymbols).@"struct".fields) |f| { diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 94c159d0bfb4b0c5bf6c82283a1e4830454f3185..142ea8c2cfa97bf43ddd37698cd60a47652d5c14 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -377,6 +377,8 @@ pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker { pub const Handle = std.posix.fd_t; pub const PathNameError = error{ + /// Returned when an insufficient buffer is provided that cannot fit the + /// path name. NameTooLong, /// File system cannot encode the requested file name bytes. /// Could be due to invalid WTF-8 on Windows, invalid UTF-8 on WASI, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index d9ef36cfda903c45be9eda8ced0980a10e5c0e46..f291af645ebb397ac80092cc61d114a987b5384c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2033,7 +2033,7 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 { // TODO call NtQueryInformationFile and ask for only the size instead of "all" } - const stat = try fileStat(ioBasic(t), file); + const stat = try fileStat(t, file); return stat.size; } @@ -3429,7 +3429,7 @@ fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Di const dents_buffer = dr.buffer[header_end..]; try current_thread.beginSyscall(); const n: usize = while (true) { - const rc = posix.system.getdirentries(dr.dir.fd, dents_buffer.ptr, dents_buffer.len, &header.seek); + const rc = posix.system.getdirentries(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, &header.seek); switch (posix.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); @@ -3735,9 +3735,11 @@ fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_b if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true; if (@hasField(posix.O, "PATH")) flags.PATH = true; + const mode: posix.mode_t = 0; + try current_thread.beginSyscall(); const fd: posix.fd_t = while (true) { - const rc = openat_sym(dir.handle, sub_path_posix, flags, 0); + const rc = openat_sym(dir.handle, sub_path_posix, flags, mode); switch (posix.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); @@ -3784,10 +3786,11 @@ fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_b .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { // On macOS, we can use F.GETPATH fcntl command to query the OS for // the path to the file descriptor. - @memset(out_buffer, 0); + var sufficient_buffer: [posix.PATH_MAX]u8 = undefined; + @memset(&sufficient_buffer, 0); try current_thread.beginSyscall(); while (true) { - switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) { + switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, &sufficient_buffer))) { .SUCCESS => { current_thread.endSyscall(); break; @@ -3803,14 +3806,15 @@ fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_b .BADF => return error.FileNotFound, .NOSPC => return error.NameTooLong, .NOENT => return error.FileNotFound, - // TODO man pages for fcntl on macOS don't really tell you what - // errno values to expect when command is F.GETPATH... else => |err| return posix.unexpectedErrno(err), } }, } } - return std.mem.indexOfScalar(u8, &out_buffer, 0) orelse out_buffer.len; + const n = std.mem.indexOfScalar(u8, &sufficient_buffer, 0) orelse sufficient_buffer.len; + if (n > out_buffer.len) return error.NameTooLong; + @memcpy(out_buffer[0..n], sufficient_buffer[0..n]); + return n; }, .linux, .serenity, .illumos => { var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined; @@ -6578,7 +6582,6 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.ExecutablePathError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); - const max_path_bytes = std.fs.max_path_bytes; switch (native_os) { .driverkit, @@ -6591,20 +6594,19 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex => { // Note that _NSGetExecutablePath() will return "a path" to // the executable not a "real path" to the executable. - var symlink_path_buf: [max_path_bytes:0]u8 = undefined; - var u32_len: u32 = max_path_bytes + 1; // include the sentinel + var symlink_path_buf: [posix.PATH_MAX:0]u8 = undefined; + var u32_len: u32 = posix.PATH_MAX + 1; // include the sentinel const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len); if (rc != 0) return error.NameTooLong; - var real_path_buf: [max_path_bytes]u8 = undefined; - const real_path = Io.Dir.realPathAbsolute(ioBasic(t), &symlink_path_buf, &real_path_buf) catch |err| switch (err) { + var real_path_buf: [posix.PATH_MAX]u8 = undefined; + const n = Io.Dir.realPathAbsolute(ioBasic(t), &symlink_path_buf, &real_path_buf) catch |err| switch (err) { error.NetworkNotFound => unreachable, // Windows-only else => |e| return e, }; - if (real_path.len > out_buffer.len) return error.NameTooLong; - const result = out_buffer[0..real_path.len]; - @memcpy(result, real_path); - return result.len; + if (n > out_buffer.len) return error.NameTooLong; + @memcpy(out_buffer[0..n], real_path_buf[0..n]); + return n; }, .linux, .serenity => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/exe", out_buffer) catch |err| switch (err) { error.UnsupportedReparsePointType => unreachable, // Windows-only @@ -6640,7 +6642,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex const argv0 = std.mem.span(std.os.argv[0]); if (std.mem.indexOf(u8, argv0, "/") != null) { // argv[0] is a path (relative or absolute): use realpath(3) directly - var real_path_buf: [max_path_bytes]u8 = undefined; + var real_path_buf: [posix.PATH_MAX]u8 = undefined; const real_path = Io.Dir.realPathAbsolute(ioBasic(t), std.os.argv[0], &real_path_buf) catch |err| switch (err) { error.NetworkNotFound => unreachable, // Windows-only else => |e| return e, @@ -6655,12 +6657,12 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; var path_it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter); while (path_it.next()) |a_path| { - var resolved_path_buf: [max_path_bytes - 1:0]u8 = undefined; + var resolved_path_buf: [posix.PATH_MAX - 1:0]u8 = undefined; const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ a_path, std.os.argv[0], }, 0) catch continue; - var real_path_buf: [max_path_bytes]u8 = undefined; + var real_path_buf: [posix.PATH_MAX]u8 = undefined; if (Io.Dir.realPathAbsolute(ioBasic(t), resolved_path, &real_path_buf)) |real_path| { // found a file, and hope it is the right file if (real_path.len > out_buffer.len) @@ -7566,7 +7568,7 @@ fn fileWriteFilePositional( current_thread.endSyscall(); assert(error.Unexpected == switch (e) { .NOMEM => return error.SystemResources, - .INVAL => |err| posix.errnoBug(err), + .INVAL => |err| errnoBug(err), else => |err| posix.unexpectedErrno(err), }); return 0; diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig index 714ff539f1c42073a7f9e5582a0d39311e1c0b49..d09493adb0feb2aaa1647b8aa9ae213da40c85a2 100644 --- a/lib/std/debug/SelfInfo/MachO.zig +++ b/lib/std/debug/SelfInfo/MachO.zig @@ -21,11 +21,10 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void { } pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { - _ = io; const module = try si.findModule(gpa, address); defer si.mutex.unlock(); - const file = try module.getFile(gpa); + const file = try module.getFile(gpa, io); // This is not necessarily the same as the vmaddr_slide that dyld would report. This is // 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 const vaddr = address - vaddr_offset; - const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, vaddr) catch { + const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, io, vaddr) catch { // Return at least the symbol name if available. return .{ .name = try file.lookupSymbolName(vaddr), @@ -107,7 +106,8 @@ pub const UnwindContext = std.debug.Dwarf.SelfUnwinder; /// Unwind a frame using MachO compact unwind info (from `__unwind_info`). /// If the compact encoding can't encode a way to unwind a frame, it will /// defer unwinding to DWARF, in which case `__eh_frame` will be used if available. -pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { +pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize { + _ = io; return unwindFrameInner(si, gpa, context) catch |err| switch (err) { error.InvalidDebugInfo, error.MissingDebugInfo, @@ -546,12 +546,12 @@ const Module = struct { }; } - fn getFile(module: *Module, gpa: Allocator) Error!*MachOFile { + fn getFile(module: *Module, gpa: Allocator, io: Io) Error!*MachOFile { if (module.file == null) { const path = std.mem.span( std.c.dyld_image_path_containing_address(@ptrFromInt(module.text_base)).?, ); - module.file = MachOFile.load(gpa, path, builtin.cpu.arch) catch |err| switch (err) { + module.file = MachOFile.load(gpa, io, path, builtin.cpu.arch) catch |err| switch (err) { error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e, }; diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index f02caa1f5ba025372dde22682bb6eb28c2ae3879..a52d64ac3ffbabb1b7d421b2330dbe22afbc1ca0 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -40,8 +40,8 @@ pub const DynLib = struct { } /// Trusts the file. - pub fn close(self: *DynLib, io: Io) void { - return self.inner.close(io); + pub fn close(self: *DynLib) void { + return self.inner.close(); } pub fn lookup(self: *DynLib, comptime T: type, name: [:0]const u8) ?T { diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index feed21efec24601bc8577a36134f4f91329e2506..92c23a2c18b7878395853f4576bf47d24f748d38 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -248,7 +248,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { os.version_range.windows.min = detected_version; os.version_range.windows.max = detected_version; }, - .macos => try darwin.macos.detect(&os), + .macos => try darwin.macos.detect(io, &os), .freebsd, .netbsd, .dragonfly => { const key = switch (builtin.target.os.tag) { .freebsd => "kern.osreldate", diff --git a/lib/std/zig/system/darwin/macos.zig b/lib/std/zig/system/darwin/macos.zig index 4ff6846a099e752ffeef831e24a7c42bc1a44ff8..7d80c2b588e6a6587ae7437d454666a9d8d2fed4 100644 --- a/lib/std/zig/system/darwin/macos.zig +++ b/lib/std/zig/system/darwin/macos.zig @@ -9,7 +9,7 @@ const Target = std.Target; /// Detect macOS version. /// `target_os` is not modified in case of error. -pub fn detect(target_os: *Target.Os) !void { +pub fn detect(io: Io, target_os: *Target.Os) !void { // Drop use of osproductversion sysctl because: // 1. only available 10.13.4 High Sierra and later // 2. when used from a binary built against < SDK 11.0 it returns 10.16 and masks Big Sur 11.x version @@ -55,7 +55,7 @@ pub fn detect(target_os: *Target.Os) !void { // approx. 4 times historical file size var buf: [2048]u8 = undefined; - if (Io.Dir.cwd().readFile(path, &buf)) |bytes| { + if (Io.Dir.cwd().readFile(io, path, &buf)) |bytes| { if (parseSystemVersion(bytes)) |ver| { // never return non-canonical `10.(16+)` if (!(ver.major == 10 and ver.minor >= 16)) { -- 2.54.0 From 97f106f949891870433bfcc6b7cf4c2a6709402e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 15 Dec 2025 16:35:08 -0800 Subject: [PATCH 063/195] std.Io: fix dir iteration on macOS --- lib/std/Io/Dir.zig | 2 +- lib/std/Io/Threaded.zig | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 142ea8c2cfa97bf43ddd37698cd60a47652d5c14..f482f72afdd6aa983250fb4c2c1c1939ceeb4fd3 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -95,7 +95,7 @@ pub const Reader = struct { dir: Dir, state: State, /// Stores I/O implementation specific data. - buffer: []u8 align(@alignOf(usize)), + buffer: []align(@alignOf(usize)) u8, /// Index of next entry in `buffer`. index: usize, /// Fill position of `buffer`. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f291af645ebb397ac80092cc61d114a987b5384c..f375bbcb7cb3e7fb235b6ccd61e90aeb0ddeddd1 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3406,11 +3406,12 @@ fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Di const Header = extern struct { seek: i64, }; - const header: *Header = @ptrCast(&dr.buffer); + const header: *Header = @ptrCast(dr.buffer.ptr); const header_end: usize = @sizeOf(Header); if (dr.index < header_end) { // Initialize header. dr.index = header_end; + dr.end = header_end; header.* = .{ .seek = 0 }; } var buffer_index: usize = 0; -- 2.54.0 From aa57793b680b3da05f1d888b4df15807905e57c8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Dec 2025 15:47:33 -0800 Subject: [PATCH 064/195] std: rework locking stderr --- lib/std/Io.zig | 46 ++++--- lib/std/Io/File/Reader.zig | 34 ++--- lib/std/Io/File/Writer.zig | 250 +------------------------------------ lib/std/Io/Terminal.zig | 154 +++++++++++++++++++++++ lib/std/Io/Threaded.zig | 83 +++++++----- lib/std/Io/Writer.zig | 2 +- lib/std/Progress.zig | 36 +++--- lib/std/debug.zig | 232 +++++++++++++++++----------------- lib/std/log.zig | 34 +++-- lib/std/process.zig | 7 ++ 10 files changed, 409 insertions(+), 469 deletions(-) create mode 100644 lib/std/Io/Terminal.zig diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 83a2e940bf389922d756f9bb0bb2c8d3570a46a2..6fa05b16d284e2db7ebf3df61f10e6a30031a841 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -557,13 +557,6 @@ pub const net = @import("Io/net.zig"); userdata: ?*anyopaque, vtable: *const VTable, -/// This is the global, process-wide protection to coordinate stderr writes. -/// -/// The primary motivation for recursive mutex here is so that a panic while -/// stderr mutex is held still dumps the stack trace and other debug -/// information. -pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; - pub const VTable = struct { /// If it returns `null` it means `result` has been already populated and /// `await` will be a no-op. @@ -719,9 +712,9 @@ pub const VTable = struct { processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File, processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize, - lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*File.Writer, - tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*File.Writer, - unlockStderrWriter: *const fn (?*anyopaque) void, + lockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!LockedStderr, + tryLockStderr: *const fn (?*anyopaque, buffer: []u8) Cancelable!?LockedStderr, + unlockStderr: *const fn (?*anyopaque) void, now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, sleep: *const fn (?*anyopaque, Timeout) SleepError!void, @@ -763,6 +756,7 @@ pub const UnexpectedError = error{ pub const Dir = @import("Io/Dir.zig"); pub const File = @import("Io/File.zig"); +pub const Terminal = @import("Io/Terminal.zig"); pub const Clock = enum { /// A settable system-wide clock that measures real (i.e. wall-clock) @@ -2177,22 +2171,34 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) { } } +pub const LockedStderr = struct { + file_writer: *File.Writer, + terminal_mode: Terminal.Mode, + + pub fn terminal(ls: LockedStderr) Terminal { + return .{ + .writer = &ls.file_writer.interface, + .mode = ls.terminal_mode, + }; + } +}; + /// For doing application-level writes to the standard error stream. /// Coordinates also with debug-level writes that are ignorant of Io interface -/// and implementations. When this returns, `stderr_thread_mutex` will be -/// locked. +/// and implementations. When this returns, `std.process.stderr_thread_mutex` +/// will be locked. /// /// See also: -/// * `tryLockStderrWriter` -pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer { - return io.vtable.lockStderrWriter(io.userdata, buffer); +/// * `tryLockStderr` +pub fn lockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!LockedStderr { + return io.vtable.lockStderr(io.userdata, buffer, terminal_mode); } -/// Same as `lockStderrWriter` but uncancelable and non-blocking. -pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer { - return io.vtable.tryLockStderrWriter(io.userdata, buffer); +/// Same as `lockStderr` but non-blocking. +pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!?LockedStderr { + return io.vtable.tryLockStderr(io.userdata, buffer, terminal_mode); } -pub fn unlockStderrWriter(io: Io) void { - return io.vtable.unlockStderrWriter(io.userdata); +pub fn unlockStderr(io: Io) void { + return io.vtable.unlockStderr(io.userdata); } diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig index 8b2074a0aa6d52566e0b1b25c1b470ba4e039d1e..792b20a7163b769f2981a0261d918cea209078ec 100644 --- a/lib/std/Io/File/Reader.zig +++ b/lib/std/Io/File/Reader.zig @@ -64,24 +64,24 @@ pub const Mode = enum { streaming, positional, /// Avoid syscalls other than `read` and `readv`. - streaming_reading, + streaming_simple, /// Avoid syscalls other than `pread` and `preadv`. - positional_reading, + positional_simple, /// Indicates reading cannot continue because of a seek failure. failure, pub fn toStreaming(m: @This()) @This() { return switch (m) { .positional, .streaming => .streaming, - .positional_reading, .streaming_reading => .streaming_reading, + .positional_simple, .streaming_simple => .streaming_simple, .failure => .failure, }; } - pub fn toReading(m: @This()) @This() { + pub fn toSimple(m: @This()) @This() { return switch (m) { - .positional, .positional_reading => .positional_reading, - .streaming, .streaming_reading => .streaming_reading, + .positional, .positional_simple => .positional_simple, + .streaming, .streaming_simple => .streaming_simple, .failure => .failure, }; } @@ -153,10 +153,10 @@ pub fn getSize(r: *Reader) SizeError!u64 { pub fn seekBy(r: *Reader, offset: i64) SeekError!void { const io = r.io; switch (r.mode) { - .positional, .positional_reading => { + .positional, .positional_simple => { setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); }, - .streaming, .streaming_reading => { + .streaming, .streaming_simple => { const seek_err = r.seek_err orelse e: { if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| { setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); @@ -183,10 +183,10 @@ pub fn seekBy(r: *Reader, offset: i64) SeekError!void { pub fn seekTo(r: *Reader, offset: u64) SeekError!void { const io = r.io; switch (r.mode) { - .positional, .positional_reading => { + .positional, .positional_simple => { setLogicalPos(r, offset); }, - .streaming, .streaming_reading => { + .streaming, .streaming_simple => { const logical_pos = logicalPos(r); if (offset >= logical_pos) return seekBy(r, @intCast(offset - logical_pos)); if (r.seek_err) |err| return err; @@ -225,19 +225,19 @@ pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Rea switch (mode) { .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) { error.Unimplemented => { - r.mode = r.mode.toReading(); + r.mode = r.mode.toSimple(); return 0; }, else => |e| return e, }, - .positional_reading => { + .positional_simple => { const dest = limit.slice(try w.writableSliceGreedy(1)); var data: [1][]u8 = .{dest}; const n = try readVecPositional(r, &data); w.advance(n); return n; }, - .streaming_reading => { + .streaming_simple => { const dest = limit.slice(try w.writableSliceGreedy(1)); var data: [1][]u8 = .{dest}; const n = try readVecStreaming(r, &data); @@ -251,8 +251,8 @@ pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Rea fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); switch (r.mode) { - .positional, .positional_reading => return readVecPositional(r, data), - .streaming, .streaming_reading => return readVecStreaming(r, data), + .positional, .positional_simple => return readVecPositional(r, data), + .streaming, .streaming_simple => return readVecStreaming(r, data), .failure => return error.ReadFailed, } } @@ -320,7 +320,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { const io = r.io; const file = r.file; switch (r.mode) { - .positional, .positional_reading => { + .positional, .positional_simple => { const size = r.getSize() catch { r.mode = r.mode.toStreaming(); return 0; @@ -330,7 +330,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { setLogicalPos(r, logical_pos + delta); return delta; }, - .streaming, .streaming_reading => { + .streaming, .streaming_simple => { // Unfortunately we can't seek forward without knowing the // size because the seek syscalls provided to us will not // return the true end position if a seek would exceed the diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 03e4ad8a1509ae06c6849249a236cebf2e7f8010..1d0494c6743a4ae5a2ce5837509acd7ce8ec4684 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -18,172 +18,7 @@ write_file_err: ?WriteFileError = null, seek_err: ?SeekError = null, interface: Io.Writer, -pub const Mode = union(enum) { - /// Uses `Io.VTable.fileWriteFileStreaming` if possible. Not a terminal. - /// `setColor` does nothing. - streaming, - /// Uses `Io.VTable.fileWriteFilePositional` if possible. Not a terminal. - /// `setColor` does nothing. - positional, - /// Avoids `Io.VTable.fileWriteFileStreaming`. Not a terminal. `setColor` - /// does nothing. - streaming_simple, - /// Avoids `Io.VTable.fileWriteFilePositional`. Not a terminal. `setColor` - /// does nothing. - positional_simple, - /// It's a terminal. Writes are escaped so as to strip escape sequences. - /// Color is enabled. - terminal_escaped, - /// It's a terminal. Colors are enabled via calling - /// SetConsoleTextAttribute. Writes are not escaped. - terminal_winapi: TerminalWinapi, - /// Indicates writing cannot continue because of a seek failure. - failure, - - pub fn toStreaming(m: @This()) @This() { - return switch (m) { - .positional, .streaming => .streaming, - .positional_simple, .streaming_simple => .streaming_simple, - inline else => |_, x| x, - }; - } - - pub fn toSimple(m: @This()) @This() { - return switch (m) { - .positional, .positional_simple => .positional_simple, - .streaming, .streaming_simple => .streaming_simple, - inline else => |_, x| x, - }; - } - - pub fn toUnescaped(m: @This()) @This() { - return switch (m) { - .terminal_escaped => .streaming_simple, - inline else => |_, x| x, - }; - } - - pub const TerminalWinapi = if (!is_windows) noreturn else struct { - handle: File.Handle, - reset_attributes: u16, - }; - - /// Detect suitable TTY configuration options for the given file (commonly - /// stdout/stderr). - /// - /// Will attempt to enable ANSI escape code support if necessary/possible. - pub fn detect(io: Io, file: File, want_color: bool, fallback: Mode) Io.Cancelable!Mode { - if (!want_color) return if (try file.isTty(io)) .terminal_escaped else fallback; - - if (file.enableAnsiEscapeCodes(io)) |_| { - return .terminal_escaped; - } else |err| switch (err) { - error.Canceled => return error.Canceled, - error.NotTerminalDevice, error.Unexpected => {}, - } - - if (is_windows and file.isTty(io)) { - const windows = std.os.windows; - var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) { - return .{ .terminal_winapi = .{ - .handle = file.handle, - .reset_attributes = info.wAttributes, - } }; - } - return .terminal_escaped; - } - - return fallback; - } - - pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; - - pub fn setColor(mode: Mode, io_w: *Io.Writer, color: Color) SetColorError!void { - switch (mode) { - .streaming, .positional, .streaming_simple, .positional_simple, .failure => return, - .terminal_escaped => { - const color_string = switch (color) { - .black => "\x1b[30m", - .red => "\x1b[31m", - .green => "\x1b[32m", - .yellow => "\x1b[33m", - .blue => "\x1b[34m", - .magenta => "\x1b[35m", - .cyan => "\x1b[36m", - .white => "\x1b[37m", - .bright_black => "\x1b[90m", - .bright_red => "\x1b[91m", - .bright_green => "\x1b[92m", - .bright_yellow => "\x1b[93m", - .bright_blue => "\x1b[94m", - .bright_magenta => "\x1b[95m", - .bright_cyan => "\x1b[96m", - .bright_white => "\x1b[97m", - .bold => "\x1b[1m", - .dim => "\x1b[2m", - .reset => "\x1b[0m", - }; - try io_w.writeAll(color_string); - }, - .terminal_winapi => |ctx| { - const windows = std.os.windows; - const attributes: windows.WORD = switch (color) { - .black => 0, - .red => windows.FOREGROUND_RED, - .green => windows.FOREGROUND_GREEN, - .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN, - .blue => windows.FOREGROUND_BLUE, - .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE, - .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, - .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, - .bright_black => windows.FOREGROUND_INTENSITY, - .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY, - .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, - .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, - .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, - .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, - .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, - .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, - // "dim" is not supported using basic character attributes, but let's still make it do *something*. - // This matches the old behavior of TTY.Color before the bright variants were added. - .dim => windows.FOREGROUND_INTENSITY, - .reset => ctx.reset_attributes, - }; - try io_w.flush(); - try windows.SetConsoleTextAttribute(ctx.handle, attributes); - }, - } - } - - fn DecorateArgs(comptime Args: type) type { - const fields = @typeInfo(Args).@"struct".fields; - var new_fields: [fields.len]type = undefined; - for (fields, &new_fields) |old, *new| { - if (old.type == std.debug.FormatStackTrace) { - new.* = std.debug.FormatStackTrace.Decorated; - } else { - new.* = old.type; - } - } - return @Tuple(&new_fields); - } - - pub fn decorateArgs(file_writer_mode: std.Io.File.Writer.Mode, args: anytype) DecorateArgs(@TypeOf(args)) { - var new_args: DecorateArgs(@TypeOf(args)) = undefined; - inline for (args, &new_args) |old, *new| { - if (@TypeOf(old) == std.debug.FormatStackTrace) { - new.* = .{ - .stack_trace = old.stack_trace, - .file_writer_mode = file_writer_mode, - }; - } else { - new.* = old; - } - } - return new_args; - } -}; +pub const Mode = File.Reader.Mode; pub const Error = error{ DiskQuota, @@ -277,8 +112,7 @@ pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); switch (w.mode) { .positional, .positional_simple => return drainPositional(w, data, splat), - .streaming, .streaming_simple, .terminal_winapi => return drainStreaming(w, data, splat), - .terminal_escaped => return drainEscaping(w, data, splat), + .streaming, .streaming_simple => return drainStreaming(w, data, splat), .failure => return error.WriteFailed, } } @@ -319,38 +153,13 @@ fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer. return w.interface.consume(n); } -fn findTerminalEscape(buffer: []const u8) ?usize { - return std.mem.findScalar(u8, buffer, 0x1b); -} - -fn drainEscaping(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { - const io = w.io; - const header = w.interface.buffered(); - if (findTerminalEscape(header)) |i| { - _ = i; - // TODO strip terminal escape sequences here - } - for (data) |d| { - if (findTerminalEscape(d)) |i| { - _ = i; - // TODO strip terminal escape sequences here - } - } - const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return w.interface.consume(n); -} - pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); switch (w.mode) { .positional => return sendFilePositional(w, file_reader, limit), .positional_simple => return error.Unimplemented, .streaming => return sendFileStreaming(w, file_reader, limit), - .streaming_simple, .terminal_escaped, .terminal_winapi => return error.Unimplemented, + .streaming_simple => return error.Unimplemented, .failure => return error.WriteFailed, } } @@ -454,60 +263,7 @@ pub fn end(w: *Writer) EndError!void { .streaming, .streaming_simple, - .terminal_escaped, - .terminal_winapi, .failure, => {}, } } - -pub const Color = enum { - black, - red, - green, - yellow, - blue, - magenta, - cyan, - white, - bright_black, - bright_red, - bright_green, - bright_yellow, - bright_blue, - bright_magenta, - bright_cyan, - bright_white, - dim, - bold, - reset, -}; - -pub fn setColor(w: *Writer, color: Color) Io.Writer.Error!void { - return w.mode.setColor(&w.interface, color) catch |err| switch (err) { - error.WriteFailed => |e| return e, - else => |e| w.err = e, - }; -} - -pub fn disableEscape(w: *Writer) Mode { - const prev = w.mode; - w.mode = w.mode.toUnescaped(); - return prev; -} - -pub fn restoreEscape(w: *Writer, mode: Mode) void { - w.mode = mode; -} - -pub fn writeAllUnescaped(w: *Writer, bytes: []const u8) Io.Writer.Error!void { - const prev_mode = w.disableEscape(); - defer w.restoreEscape(prev_mode); - return w.interface.writeAll(bytes); -} - -pub fn printUnescaped(w: *Writer, comptime fmt: []const u8, args: anytype) Io.Writer.Error!void { - const prev_mode = w.disableEscape(); - defer w.restoreEscape(prev_mode); - return w.interface.print(fmt, args); -} diff --git a/lib/std/Io/Terminal.zig b/lib/std/Io/Terminal.zig new file mode 100644 index 0000000000000000000000000000000000000000..cd0ec56caa77a8baeb2f031003ac8eb8f4250d5f --- /dev/null +++ b/lib/std/Io/Terminal.zig @@ -0,0 +1,154 @@ +/// Abstraction for writing to a stream that might support terminal escape +/// codes. +const Terminal = @This(); + +const builtin = @import("builtin"); +const is_windows = builtin.os.tag == .windows; + +const std = @import("std"); +const Io = std.Io; +const File = std.Io.File; + +writer: *Io.Writer, +mode: Mode, + +pub const Color = enum { + black, + red, + green, + yellow, + blue, + magenta, + cyan, + white, + bright_black, + bright_red, + bright_green, + bright_yellow, + bright_blue, + bright_magenta, + bright_cyan, + bright_white, + dim, + bold, + reset, +}; + +pub const Mode = union(enum) { + no_color, + escape_codes, + windows_api: WindowsApi, + + pub const WindowsApi = if (!is_windows) noreturn else struct { + handle: File.Handle, + reset_attributes: u16, + }; + + /// Detect suitable TTY configuration options for the given file (commonly + /// stdout/stderr). + /// + /// Will attempt to enable ANSI escape code support if necessary/possible. + pub fn detect(io: Io, file: File) Io.Cancelable!Mode { + if (file.enableAnsiEscapeCodes(io)) |_| { + return .escape_codes; + } else |err| switch (err) { + error.Canceled => return error.Canceled, + error.NotTerminalDevice, error.Unexpected => {}, + } + + if (is_windows and file.isTty(io)) { + const windows = std.os.windows; + var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; + if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != 0) { + return .{ .terminal_winapi = .{ + .handle = file.handle, + .reset_attributes = info.wAttributes, + } }; + } + return .escape_codes; + } + + return .no_color; + } +}; + +pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; + +pub fn setColor(t: Terminal, color: Color) Io.Writer.Error!void { + switch (t.mode) { + .no_color => return, + .escape_codes => { + const color_string = switch (color) { + .black => "\x1b[30m", + .red => "\x1b[31m", + .green => "\x1b[32m", + .yellow => "\x1b[33m", + .blue => "\x1b[34m", + .magenta => "\x1b[35m", + .cyan => "\x1b[36m", + .white => "\x1b[37m", + .bright_black => "\x1b[90m", + .bright_red => "\x1b[91m", + .bright_green => "\x1b[92m", + .bright_yellow => "\x1b[93m", + .bright_blue => "\x1b[94m", + .bright_magenta => "\x1b[95m", + .bright_cyan => "\x1b[96m", + .bright_white => "\x1b[97m", + .bold => "\x1b[1m", + .dim => "\x1b[2m", + .reset => "\x1b[0m", + }; + try t.writer.writeAll(color_string); + }, + .windows_api => |wa| { + const windows = std.os.windows; + const attributes: windows.WORD = switch (color) { + .black => 0, + .red => windows.FOREGROUND_RED, + .green => windows.FOREGROUND_GREEN, + .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN, + .blue => windows.FOREGROUND_BLUE, + .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE, + .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, + .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, + .bright_black => windows.FOREGROUND_INTENSITY, + .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY, + .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, + .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, + .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + // "dim" is not supported using basic character attributes, but let's still make it do *something*. + // This matches the old behavior of TTY.Color before the bright variants were added. + .dim => windows.FOREGROUND_INTENSITY, + .reset => wa.reset_attributes, + }; + try t.writer.flush(); + try windows.SetConsoleTextAttribute(wa.handle, attributes); + }, + } +} + +pub fn disableEscape(t: *Terminal) Mode { + const prev = t.mode; + t.mode = t.mode.toUnescaped(); + return prev; +} + +pub fn restoreEscape(t: *Terminal, mode: Mode) void { + t.mode = mode; +} + +pub fn writeAllUnescaped(t: *Terminal, bytes: []const u8) Io.Writer.Error!void { + const prev_mode = t.disableEscape(); + defer t.restoreEscape(prev_mode); + return t.interface.writeAll(bytes); +} + +pub fn printUnescaped(t: *Terminal, comptime fmt: []const u8, args: anytype) Io.Writer.Error!void { + const prev_mode = t.disableEscape(); + defer t.restoreEscape(prev_mode); + return t.interface.print(fmt, args); +} diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f375bbcb7cb3e7fb235b6ccd61e90aeb0ddeddd1..3e7b6950d402ed55f71689e5759449a21ad4f8df 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -82,8 +82,8 @@ stderr_writer: File.Writer = .{ .io = undefined, .interface = Io.File.Writer.initInterface(&.{}), .file = if (is_windows) undefined else .stderr(), - .mode = undefined, }, +stderr_mode: Io.Terminal.Mode = .no_color, stderr_writer_initialized: bool = false, pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { @@ -755,9 +755,9 @@ pub fn io(t: *Threaded) Io { .processExecutableOpen = processExecutableOpen, .processExecutablePath = processExecutablePath, - .lockStderrWriter = lockStderrWriter, - .tryLockStderrWriter = tryLockStderrWriter, - .unlockStderrWriter = unlockStderrWriter, + .lockStderr = lockStderr, + .tryLockStderr = tryLockStderr, + .unlockStderr = unlockStderr, .now = now, .sleep = sleep, @@ -887,9 +887,9 @@ pub fn ioBasic(t: *Threaded) Io { .processExecutableOpen = processExecutableOpen, .processExecutablePath = processExecutablePath, - .lockStderrWriter = lockStderrWriter, - .tryLockStderrWriter = tryLockStderrWriter, - .unlockStderrWriter = unlockStderrWriter, + .lockStderr = lockStderr, + .tryLockStderr = tryLockStderr, + .unlockStderr = unlockStderr, .now = now, .sleep = sleep, @@ -10090,47 +10090,70 @@ fn netLookupFallible( return error.OptionUnsupported; } -fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Writer { +fn lockStderr( + userdata: ?*anyopaque, + buffer: []u8, + terminal_mode: ?Io.Terminal.Mode, +) Io.Cancelable!Io.LockedStderr { const t: *Threaded = @ptrCast(@alignCast(userdata)); // Only global mutex since this is Threaded. - Io.stderr_thread_mutex.lock(); - if (!t.stderr_writer_initialized) { - const io_t = ioBasic(t); - if (is_windows) t.stderr_writer.file = .stderr(); - t.stderr_writer.io = io_t; - t.stderr_writer.mode = try .detect(io_t, t.stderr_writer.file, true, .streaming_simple); - t.stderr_writer_initialized = true; - } - std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; - t.stderr_writer.interface.flush() catch {}; - t.stderr_writer.interface.buffer = buffer; - return &t.stderr_writer; + std.process.stderr_thread_mutex.lock(); + return initLockedStderr(t, buffer, terminal_mode); } -fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer { +fn tryLockStderr( + userdata: ?*anyopaque, + buffer: []u8, + terminal_mode: ?Io.Terminal.Mode, +) Io.Cancelable!?Io.LockedStderr { const t: *Threaded = @ptrCast(@alignCast(userdata)); // Only global mutex since this is Threaded. - if (!Io.stderr_thread_mutex.tryLock()) return null; + if (!std.process.stderr_thread_mutex.tryLock()) return null; + return try initLockedStderr(t, buffer, terminal_mode); +} + +fn initLockedStderr( + t: *Threaded, + buffer: []u8, + terminal_mode: ?Io.Terminal.Mode, +) Io.Cancelable!Io.LockedStderr { if (!t.stderr_writer_initialized) { const io_t = ioBasic(t); if (is_windows) t.stderr_writer.file = .stderr(); t.stderr_writer.io = io_t; - t.stderr_writer.mode = File.Writer.Mode.detect(io_t, t.stderr_writer.file, true, .streaming_simple) catch - return null; t.stderr_writer_initialized = true; + t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file); } - std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; - t.stderr_writer.interface.flush() catch {}; + std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch |err| switch (err) { + error.WriteFailed => switch (t.stderr_writer.err.?) { + error.Canceled => |e| return e, + else => {}, + }, + }; + t.stderr_writer.interface.flush() catch |err| switch (err) { + error.WriteFailed => switch (t.stderr_writer.err.?) { + error.Canceled => |e| return e, + else => {}, + }, + }; t.stderr_writer.interface.buffer = buffer; - return &t.stderr_writer; + return .{ + .file_writer = &t.stderr_writer, + .terminal_mode = t.stderr_mode, + }; } -fn unlockStderrWriter(userdata: ?*anyopaque) void { +fn unlockStderr(userdata: ?*anyopaque) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - t.stderr_writer.interface.flush() catch {}; + t.stderr_writer.interface.flush() catch |err| switch (err) { + error.WriteFailed => switch (t.stderr_writer.err.?) { + error.Canceled => @panic("TODO make this uncancelable"), + else => {}, + }, + }; t.stderr_writer.interface.end = 0; t.stderr_writer.interface.buffer = &.{}; - Io.stderr_thread_mutex.unlock(); + std.process.stderr_thread_mutex.unlock(); } pub const PosixAddress = extern union { diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 63ae6d93a0eceee110b5887113381ea4c59106c9..5349eef104415b0c0d7e278d2084c3f53f9a51bf 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -961,7 +961,7 @@ pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllE const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) { error.EndOfStream => break, error.Unimplemented => { - file_reader.mode = file_reader.mode.toReading(); + file_reader.mode = file_reader.mode.toSimple(); remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining)); break; }, diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 6edbf9e47109a9129a3b19c394a32c4902f3c549..54b52d48af11fc06cba1c7a26ca3a2a9198158b4 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -565,10 +565,10 @@ fn updateThreadRun(io: Io) void { maybeUpdateSize(resize_flag); const buffer, _ = computeRedraw(&serialized_buffer); - if (io.tryLockStderrWriter(&.{})) |fw| { - defer io.unlockStderrWriter(); + if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { + defer io.unlockStderr(); global_progress.need_clear = true; - fw.writeAllUnescaped(buffer) catch return; + locked_stderr.file_writer.interface.writeAll(buffer) catch return; } } @@ -576,18 +576,18 @@ fn updateThreadRun(io: Io) void { const resize_flag = wait(io, global_progress.refresh_rate_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) { - const fw = io.lockStderrWriter(&.{}) catch return; - defer io.unlockStderrWriter(); - return clearWrittenWithEscapeCodes(fw) catch {}; + const stderr = io.lockStderr(&.{}, null) catch return; + defer io.unlockStderr(); + return clearWrittenWithEscapeCodes(stderr.file_writer) catch {}; } maybeUpdateSize(resize_flag); const buffer, _ = computeRedraw(&serialized_buffer); - if (io.tryLockStderrWriter(&.{})) |fw| { - defer io.unlockStderrWriter(); + if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { + defer io.unlockStderr(); global_progress.need_clear = true; - fw.writeAllUnescaped(buffer) catch return; + locked_stderr.file_writer.interface.writeAll(buffer) catch return; } } } @@ -609,11 +609,11 @@ fn windowsApiUpdateThreadRun(io: Io) void { maybeUpdateSize(resize_flag); const buffer, const nl_n = computeRedraw(&serialized_buffer); - if (io.tryLockStderrWriter()) |fw| { - defer io.unlockStderrWriter(); + if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { + defer io.unlockStderr(); windowsApiWriteMarker(); global_progress.need_clear = true; - fw.writeAllUnescaped(buffer) catch return; + locked_stderr.file_writer.interface.writeAll(buffer) catch return; windowsApiMoveToMarker(nl_n) catch return; } } @@ -622,20 +622,20 @@ fn windowsApiUpdateThreadRun(io: Io) void { const resize_flag = wait(io, global_progress.refresh_rate_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) { - _ = io.lockStderrWriter() catch return; - defer io.unlockStderrWriter(); + _ = io.lockStderr(&.{}, null) catch return; + defer io.unlockStderr(); return clearWrittenWindowsApi() catch {}; } maybeUpdateSize(resize_flag); const buffer, const nl_n = computeRedraw(&serialized_buffer); - if (io.tryLockStderrWriter()) |fw| { - defer io.unlockStderrWriter(); + if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { + defer io.unlockStderr(); clearWrittenWindowsApi() catch return; windowsApiWriteMarker(); global_progress.need_clear = true; - fw.writeAllUnescaped(buffer) catch return; + locked_stderr.file_writer.interface.writeAll(buffer) catch return; windowsApiMoveToMarker(nl_n) catch return; } } @@ -766,7 +766,7 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error!void { if (noop_impl or !global_progress.need_clear) return; - try file_writer.writeAllUnescaped(clear ++ progress_remove); + try file_writer.interface.writeAll(clear ++ progress_remove); global_progress.need_clear = false; } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 67f7d3a9fefc6ec156ef35dcf49e4b67ca11fb2e..fd3a456ff7811bbaf0d8b208ef97586c05f57262 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -265,29 +265,34 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { /// separate from the application's `Io` instance. var static_single_threaded_io: Io.Threaded = .init_single_threaded; -/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called. +/// Allows the caller to freely write to stderr until `unlockStderr` is called. /// /// During the lock, any `std.Progress` information is cleared from the terminal. /// -/// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple -/// times. The primary motivation is that this allows the panic handler to safely dump the stack -/// trace and panic message even if the mutex was held at the panic site. +/// The lock is recursive, so it is valid for the same thread to call +/// `lockStderr` multiple times, allowing the panic handler to safely +/// dump the stack trace and panic message even if the mutex was held at the +/// panic site. /// /// The returned `Writer` does not need to be manually flushed: flushing is -/// performed automatically when the matching `unlockStderrWriter` call occurs. +/// performed automatically when the matching `unlockStderr` call occurs. /// /// This is a low-level debugging primitive that bypasses the `Io` interface, /// writing directly to stderr using the most basic syscalls available. This /// function does not switch threads, switch stacks, or suspend. /// -/// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with -/// the application's chosen `Io` implementation. -pub fn lockStderrWriter(buffer: []u8) *File.Writer { - return static_single_threaded_io.ioBasic().lockStderrWriter(buffer) catch unreachable; +/// Alternatively, use the higher-level `Io.lockStderr` to integrate with the +/// application's chosen `Io` implementation. +pub fn lockStderr(buffer: []u8) Io.Terminal { + return (static_single_threaded_io.ioBasic().lockStderr(buffer, null) catch |err| switch (err) { + // Impossible to cancel because no calls to cancel using + // `static_single_threaded_io` exist. + error.Canceled => unreachable, + }).terminal(); } -pub fn unlockStderrWriter() void { - static_single_threaded_io.ioBasic().unlockStderrWriter(); +pub fn unlockStderr() void { + static_single_threaded_io.ioBasic().unlockStderr(); } /// Writes to stderr, ignoring errors. @@ -299,14 +304,14 @@ pub fn unlockStderrWriter() void { /// Uses a 64-byte buffer for formatted printing which is flushed before this /// function returns. /// -/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to +/// Alternatively, use the higher-level `std.log` or `Io.lockStderr` to /// integrate with the application's chosen `Io` implementation. pub fn print(comptime fmt: []const u8, args: anytype) void { nosuspend { var buffer: [64]u8 = undefined; - const stderr = lockStderrWriter(&buffer); - defer unlockStderrWriter(); - stderr.interface.print(fmt, stderr.mode.decorateArgs(args)) catch return; + const stderr = lockStderr(&buffer); + defer unlockStderr(); + stderr.writer.print(fmt, args) catch return; } } @@ -322,43 +327,44 @@ pub inline fn getSelfDebugInfo() !*SelfInfo { /// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned. /// Obtains the stderr mutex while dumping. pub fn dumpHex(bytes: []const u8) void { - const bw, const ttyconf = lockStderrWriter(&.{}); - defer unlockStderrWriter(); - dumpHexFallible(bw, ttyconf, bytes) catch {}; + const stderr = lockStderr(&.{}); + defer unlockStderr(); + dumpHexFallible(stderr, bytes) catch {}; } /// Prints a hexadecimal view of the bytes, returning any error that occurs. -pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !void { +pub fn dumpHexFallible(t: Io.Terminal, bytes: []const u8) !void { + const w = t.writer; var chunks = mem.window(u8, bytes, 16, 16); while (chunks.next()) |window| { // 1. Print the address. const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10; - try fwm.setColor(bw, .dim); + try t.setColor(.dim); // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more. // Also, make sure all lines are aligned by padding the address. - try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); - try fwm.setColor(bw, .reset); + try w.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); + try t.setColor(.reset); // 2. Print the bytes. for (window, 0..) |byte, index| { - try bw.print("{X:0>2} ", .{byte}); - if (index == 7) try bw.writeByte(' '); + try w.print("{X:0>2} ", .{byte}); + if (index == 7) try w.writeByte(' '); } - try bw.writeByte(' '); + try w.writeByte(' '); if (window.len < 16) { var missing_columns = (16 - window.len) * 3; if (window.len < 8) missing_columns += 1; - try bw.splatByteAll(' ', missing_columns); + try w.splatByteAll(' ', missing_columns); } // 3. Print the characters. for (window) |byte| { if (std.ascii.isPrint(byte)) { - try bw.writeByte(byte); + try w.writeByte(byte); } else { // Related: https://github.com/ziglang/zig/issues/7600 - if (fwm == .terminal_winapi) { - try bw.writeByte('.'); + if (t.mode == .windows_api) { + try w.writeByte('.'); continue; } @@ -366,14 +372,14 @@ pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !v // We don't want to do this for all control codes because most control codes apart from // the ones that Zig has escape sequences for are likely not very useful to print as symbols. switch (byte) { - '\n' => try bw.writeAll("␊"), - '\r' => try bw.writeAll("␍"), - '\t' => try bw.writeAll("␉"), - else => try bw.writeByte('.'), + '\n' => try w.writeAll("␊"), + '\r' => try w.writeAll("␍"), + '\t' => try w.writeAll("␉"), + else => try w.writeByte('.'), } } } - try bw.writeByte('\n'); + try w.writeByte('\n'); } } @@ -545,26 +551,27 @@ pub fn defaultPanic( _ = panicking.fetchAdd(1, .seq_cst); trace: { - const stderr = lockStderrWriter(&.{}); - defer unlockStderrWriter(); + const stderr = lockStderr(&.{}); + defer unlockStderr(); + const writer = stderr.writer; if (builtin.single_threaded) { - stderr.interface.print("panic: ", .{}) catch break :trace; + writer.print("panic: ", .{}) catch break :trace; } else { const current_thread_id = std.Thread.getCurrentId(); - stderr.interface.print("thread {d} panic: ", .{current_thread_id}) catch break :trace; + writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace; } - stderr.interface.print("{s}\n", .{msg}) catch break :trace; + writer.print("{s}\n", .{msg}) catch break :trace; if (@errorReturnTrace()) |t| if (t.index > 0) { - stderr.interface.writeAll("error return context:\n") catch break :trace; - writeStackTrace(t, &stderr.interface, stderr.mode) catch break :trace; - stderr.interface.writeAll("\nstack trace:\n") catch break :trace; + writer.writeAll("error return context:\n") catch break :trace; + writeStackTrace(t, stderr) catch break :trace; + writer.writeAll("\nstack trace:\n") catch break :trace; }; writeCurrentStackTrace(.{ .first_address = first_trace_addr orelse @returnAddress(), .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! - }, &stderr.interface, stderr.mode) catch break :trace; + }, stderr) catch break :trace; } waitForOtherThreadToFinishPanicking(); @@ -574,8 +581,8 @@ pub fn defaultPanic( // A panic happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). - const stderr = lockStderrWriter(&.{}); - stderr.interface.writeAll("aborting due to recursive panic\n") catch {}; + const stderr = lockStderr(&.{}); + stderr.writer.writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. } @@ -656,28 +663,29 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: /// Write the current stack trace to `writer`, annotated with source locations. /// /// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing. -pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void { +pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void { + const writer = t.writer; if (!std.options.allow_stack_tracing) { - fwm.setColor(writer, .dim) catch {}; + t.setColor(.dim) catch {}; try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; return; } const di_gpa = getDebugInfoAllocator(); const di = getSelfDebugInfo() catch |err| switch (err) { error.UnsupportedTarget => { - fwm.setColor(writer, .dim) catch {}; + t.setColor(.dim) catch {}; try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{}); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; return; }, }; var it: StackIterator = .init(options.context); defer it.deinit(); if (!it.stratOk(options.allow_unsafe_unwind)) { - fwm.setColor(writer, .dim) catch {}; + t.setColor(.dim) catch {}; try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{}); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; return; } var total_frames: usize = 0; @@ -701,31 +709,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri error.Unexpected => "unexpected error", }; if (it.stratOk(options.allow_unsafe_unwind)) { - fwm.setColor(writer, .dim) catch {}; + t.setColor(.dim) catch {}; try writer.print( "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n", .{ module_name, unwind_error.address, caption }, ); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; } else { - fwm.setColor(writer, .dim) catch {}; + t.setColor(.dim) catch {}; try writer.print( "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n", .{ module_name, unwind_error.address, caption }, ); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; return; } }, .end => break, .frame => |ret_addr| { if (total_frames > 10_000) { - fwm.setColor(writer, .dim) catch {}; + t.setColor(.dim) catch {}; try writer.print( "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n", .{total_frames}, ); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; return; } total_frames += 1; @@ -735,7 +743,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri } // `ret_addr` is the return address, which is *after* the function call. // Subtract 1 to get an address *in* the function call for a better source location. - try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm); + try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset); printed_any_frame = true; }, }; @@ -743,8 +751,8 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri } /// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors. pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { - const stderr = lockStderrWriter(&.{}); - defer unlockStderrWriter(); + const stderr = lockStderr(&.{}); + defer unlockStderr(); writeCurrentStackTrace(.{ .first_address = a: { if (options.first_address) |a| break :a a; @@ -753,38 +761,28 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { }, .context = options.context, .allow_unsafe_unwind = options.allow_unsafe_unwind, - }, &stderr.interface, stderr.mode) catch |err| switch (err) { + }, stderr) catch |err| switch (err) { error.WriteFailed => {}, }; } pub const FormatStackTrace = struct { stack_trace: StackTrace, + terminal_mode: Io.Terminal.Mode = .no_color, - pub const Decorated = struct { - stack_trace: StackTrace, - file_writer_mode: File.Writer.Mode, - - pub fn format(decorated: Decorated, writer: *Writer) Writer.Error!void { - try writer.writeByte('\n'); - try writeStackTrace(&decorated.stack_trace, writer, decorated.file_writer_mode); - } - }; - - pub fn format(context: FormatStackTrace, writer: *Writer) Writer.Error!void { - return Decorated.format(.{ - .stack_trace = context.stack_trace, - .file_writer_mode = .streaming, - }, writer); + pub fn format(fst: FormatStackTrace, writer: *Writer) Writer.Error!void { + try writer.writeByte('\n'); + try writeStackTrace(&fst.stack_trace, .{ .writer = writer, .mode = fst.terminal_mode }); } }; /// Write a previously captured stack trace to `writer`, annotated with source locations. -pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void { +pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void { + const writer = t.writer; if (!std.options.allow_stack_tracing) { - fwm.setColor(writer, .dim) catch {}; + t.setColor(.dim) catch {}; try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; return; } @@ -795,9 +793,9 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer. const di_gpa = getDebugInfoAllocator(); const di = getSelfDebugInfo() catch |err| switch (err) { error.UnsupportedTarget => { - fwm.setColor(writer, .dim) catch {}; + t.setColor(.dim) catch {}; try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{}); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; return; }, }; @@ -806,19 +804,19 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer. for (st.instruction_addresses[0..captured_frames]) |ret_addr| { // `ret_addr` is the return address, which is *after* the function call. // Subtract 1 to get an address *in* the function call for a better source location. - try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm); + try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset); } if (n_frames > captured_frames) { - fwm.setColor(writer, .bold) catch {}; + t.setColor(.bold) catch {}; try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames}); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; } } /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. pub fn dumpStackTrace(st: *const StackTrace) void { - const stderr = lockStderrWriter(&.{}); - defer unlockStderrWriter(); - writeStackTrace(st, &stderr.interface, stderr.mode) catch |err| switch (err) { + const stderr = lockStderr(&.{}); + defer unlockStderr(); + writeStackTrace(st, stderr) catch |err| switch (err) { error.WriteFailed => {}, }; } @@ -1117,9 +1115,8 @@ fn printSourceAtAddress( gpa: Allocator, io: Io, debug_info: *SelfInfo, - writer: *Writer, + t: Io.Terminal, address: usize, - fwm: File.Writer.Mode, ) Writer.Error!void { const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) { error.MissingDebugInfo, @@ -1127,40 +1124,39 @@ fn printSourceAtAddress( error.InvalidDebugInfo, => .unknown, error.ReadFailed, error.Unexpected, error.Canceled => s: { - fwm.setColor(writer, .dim) catch {}; - try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.dim) catch {}; + try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); + t.setColor(.reset) catch {}; break :s .unknown; }, error.OutOfMemory => s: { - fwm.setColor(writer, .dim) catch {}; - try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.dim) catch {}; + try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); + t.setColor(.reset) catch {}; break :s .unknown; }, }; defer if (symbol.source_location) |sl| gpa.free(sl.file_name); return printLineInfo( io, - writer, + t, symbol.source_location, address, symbol.name orelse "???", symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???", - fwm, ); } fn printLineInfo( io: Io, - writer: *Writer, + t: Io.Terminal, source_location: ?SourceLocation, address: usize, symbol_name: []const u8, compile_unit_name: []const u8, - fwm: File.Writer.Mode, ) Writer.Error!void { nosuspend { - fwm.setColor(writer, .bold) catch {}; + const writer = t.writer; + t.setColor(.bold) catch {}; if (source_location) |*sl| { try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); @@ -1168,11 +1164,11 @@ fn printLineInfo( try writer.writeAll("???:?:?"); } - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; try writer.writeAll(": "); - fwm.setColor(writer, .dim) catch {}; + t.setColor(.dim) catch {}; try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; try writer.writeAll("\n"); // Show the matching source code line if possible @@ -1183,9 +1179,9 @@ fn printLineInfo( const space_needed = @as(usize, @intCast(sl.column - 1)); try writer.splatByteAll(' ', space_needed); - fwm.setColor(writer, .green) catch {}; + t.setColor(.green) catch {}; try writer.writeAll("^"); - fwm.setColor(writer, .reset) catch {}; + t.setColor(.reset) catch {}; } try writer.writeAll("\n"); } else |_| { @@ -1554,19 +1550,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex _ = panicking.fetchAdd(1, .seq_cst); trace: { - const stderr = lockStderrWriter(&.{}); - defer unlockStderrWriter(); + const stderr = lockStderr(&.{}); + defer unlockStderr(); if (addr) |a| { - stderr.interface.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; + stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; } else { - stderr.interface.print("{s} (no address available)\n", .{name}) catch break :trace; + stderr.writer.print("{s} (no address available)\n", .{name}) catch break :trace; } if (opt_ctx) |context| { writeCurrentStackTrace(.{ .context = context, .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! - }, &stderr.interface, stderr.mode) catch break :trace; + }, stderr) catch break :trace; } } }, @@ -1575,8 +1571,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex // A segfault happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). - const stderr = lockStderrWriter(&.{}); - stderr.interface.writeAll("aborting due to recursive panic\n") catch {}; + const stderr = lockStderr(&.{}); + stderr.writer.writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. } @@ -1682,21 +1678,21 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize pub fn dump(t: @This()) void { if (!enabled) return; - const stderr = lockStderrWriter(&.{}); - defer unlockStderrWriter(); + const stderr = lockStderr(&.{}); + defer unlockStderr(); const end = @min(t.index, size); for (t.addrs[0..end], 0..) |frames_array, i| { - stderr.interface.print("{s}:\n", .{t.notes[i]}) catch return; + stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return; var frames_array_mutable = frames_array; const frames = mem.sliceTo(frames_array_mutable[0..], 0); const stack_trace: StackTrace = .{ .index = frames.len, .instruction_addresses = frames, }; - writeStackTrace(&stack_trace, &stderr.interface, stderr.mode) catch return; + writeStackTrace(&stack_trace, stderr) catch return; } if (t.index > end) { - stderr.interface.print("{d} more traces not shown; consider increasing trace size\n", .{ + stderr.writer.print("{d} more traces not shown; consider increasing trace size\n", .{ t.index - end, }) catch return; } diff --git a/lib/std/log.zig b/lib/std/log.zig index b75bada5803d93199e1d4c2ce47fa77c4b33e886..8579bb04e00bf4195a9a3de84aef2ea7eb7c07a6 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -92,35 +92,33 @@ pub fn defaultLog( args: anytype, ) void { var buffer: [64]u8 = undefined; - const stderr = std.debug.lockStderrWriter(&buffer); - defer std.debug.unlockStderrWriter(); - return defaultLogFileWriter(level, scope, format, args, stderr); + const stderr = std.debug.lockStderr(&buffer); + defer std.debug.unlockStderr(); + return defaultLogFileTerminal(level, scope, format, args, stderr) catch {}; } -pub fn defaultLogFileWriter( +pub fn defaultLogFileTerminal( comptime level: Level, comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype, - fw: *std.Io.File.Writer, -) void { - fw.setColor(switch (level) { + t: std.Io.Terminal, +) std.Io.Writer.Error!void { + t.setColor(switch (level) { .err => .red, .warn => .yellow, .info => .green, .debug => .magenta, }) catch {}; - fw.setColor(.bold) catch {}; - fw.interface.writeAll(level.asText()) catch return; - fw.setColor(.reset) catch {}; - fw.setColor(.dim) catch {}; - fw.setColor(.bold) catch {}; - if (scope != .default) { - fw.interface.print("({s})", .{@tagName(scope)}) catch return; - } - fw.interface.writeAll(": ") catch return; - fw.setColor(.reset) catch {}; - fw.interface.print(format ++ "\n", fw.mode.decorateArgs(args)) catch return; + t.setColor(.bold) catch {}; + try t.writer.writeAll(level.asText()); + t.setColor(.reset) catch {}; + t.setColor(.dim) catch {}; + t.setColor(.bold) catch {}; + if (scope != .default) try t.writer.print("({t})", .{scope}); + try t.writer.writeAll(": "); + t.setColor(.reset) catch {}; + try t.writer.print(format ++ "\n", args); } /// Returns a scoped logging namespace that logs all messages using the scope diff --git a/lib/std/process.zig b/lib/std/process.zig index 4b4c27fe200a1064793ec77c165f343cf55de11f..45cd5751336b1dc7337d8cadde627716da414005 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -21,6 +21,13 @@ pub const changeCurDirZ = posix.chdirZ; pub const GetCwdError = posix.GetCwdError; +/// This is the global, process-wide protection to coordinate stderr writes. +/// +/// The primary motivation for recursive mutex here is so that a panic while +/// stderr mutex is held still dumps the stack trace and other debug +/// information. +pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; + /// The result is a slice of `out_buffer`, from index `0`. /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -- 2.54.0 From 608145c2f07d90c46cdaa8bc2013f31b965a5b8b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Dec 2025 17:00:41 -0800 Subject: [PATCH 065/195] fix more fallout from locking stderr --- lib/compiler/aro/aro/Diagnostics.zig | 28 ++- lib/compiler/build_runner.zig | 338 +++++++++++++-------------- lib/compiler/resinator/cli.zig | 10 +- lib/compiler/resinator/errors.zig | 6 +- lib/compiler/resinator/main.zig | 31 ++- lib/compiler/test_runner.zig | 8 +- lib/std/Build.zig | 73 +++--- lib/std/Build/Fuzz.zig | 18 +- lib/std/Build/Step.zig | 9 +- lib/std/Build/Step/Compile.zig | 17 +- lib/std/Build/Step/Run.zig | 22 +- lib/std/Io.zig | 2 +- lib/std/Io/File/Writer.zig | 2 +- lib/std/Io/Terminal.zig | 22 -- lib/std/Io/Threaded.zig | 6 +- lib/std/debug.zig | 24 +- lib/std/debug/simple_panic.zig | 6 +- lib/std/json/dynamic.zig | 6 +- lib/std/log.zig | 2 +- lib/std/process.zig | 2 +- lib/std/testing.zig | 21 +- lib/std/zig.zig | 21 +- lib/std/zig/ErrorBundle.zig | 72 +++--- lib/std/zig/parser_test.zig | 16 +- src/Air/print.zig | 12 +- src/Compilation.zig | 45 ++-- src/InternPool.zig | 10 +- src/Zcu/PerThread.zig | 8 +- src/codegen/aarch64/Select.zig | 7 +- src/crash_report.zig | 6 +- src/libs/mingw.zig | 24 +- src/libs/mingw/def.zig | 6 +- src/link/Coff.zig | 6 +- src/link/Elf2.zig | 6 +- src/main.zig | 12 +- 35 files changed, 446 insertions(+), 458 deletions(-) diff --git a/lib/compiler/aro/aro/Diagnostics.zig b/lib/compiler/aro/aro/Diagnostics.zig index bebcf4e3d2fb786a35d7ecb9717c421d077d2a7d..b33efa3f85dddc21e86f7631a90477b4bb5028dd 100644 --- a/lib/compiler/aro/aro/Diagnostics.zig +++ b/lib/compiler/aro/aro/Diagnostics.zig @@ -24,20 +24,21 @@ pub const Message = struct { @"fatal error", }; - pub fn write(msg: Message, w: *std.Io.Writer, config: std.Io.File.Writer.Mode, details: bool) std.Io.tty.Config.SetColorError!void { - try config.setColor(w, .bold); + pub fn write(msg: Message, t: std.Io.Terminal, details: bool) std.Io.Terminal.SetColorError!void { + const w = t.writer; + try t.setColor(.bold); if (msg.location) |loc| { try w.print("{s}:{d}:{d}: ", .{ loc.path, loc.line_no, loc.col }); } switch (msg.effective_kind) { - .@"fatal error", .@"error" => try config.setColor(w, .bright_red), - .note => try config.setColor(w, .bright_cyan), - .warning => try config.setColor(w, .bright_magenta), + .@"fatal error", .@"error" => try t.setColor(.bright_red), + .note => try t.setColor(.bright_cyan), + .warning => try t.setColor(.bright_magenta), .off => unreachable, } try w.print("{s}: ", .{@tagName(msg.effective_kind)}); - try config.setColor(w, .white); + try t.setColor(.white); try w.writeAll(msg.text); if (msg.opt) |some| { if (msg.effective_kind == .@"error" and msg.kind != .@"error") { @@ -55,17 +56,17 @@ pub const Message = struct { if (!details or msg.location == null) { try w.writeAll("\n"); - try config.setColor(w, .reset); + try t.setColor(.reset); } else { const loc = msg.location.?; const trailer = if (loc.end_with_splice) "\\ " else ""; - try config.setColor(w, .reset); + try t.setColor(.reset); try w.print("\n{s}{s}\n", .{ loc.line, trailer }); try w.splatByteAll(' ', loc.width); - try config.setColor(w, .bold); - try config.setColor(w, .bright_green); + try t.setColor(.bold); + try t.setColor(.bright_green); try w.writeAll("^\n"); - try config.setColor(w, .reset); + try t.setColor(.reset); } try w.flush(); } @@ -290,10 +291,7 @@ pub const State = struct { const Diagnostics = @This(); output: union(enum) { - to_writer: struct { - writer: *std.Io.Writer, - color: std.Io.File.Writer.Mode, - }, + to_writer: std.Io.Terminal, to_list: struct { messages: std.ArrayList(Message) = .empty, arena: std.heap.ArenaAllocator, diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index d284d85747565f371b2cecca856a52d7402a85db..1c30ecaadce2a4c863214f42bf5bd74385bf0174 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -286,8 +286,8 @@ pub fn main() !void { const next_arg = nextArg(args, &arg_idx) orelse fatalWithHint("expected u16 after '{s}'", .{arg}); debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| { - fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {s}\n", .{ - next_arg, @errorName(err), + fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {t}\n", .{ + next_arg, err, }); }; } else if (mem.eql(u8, arg, "--webui")) { @@ -429,6 +429,12 @@ pub fn main() !void { } } + graph.stderr_mode = switch (color) { + .auto => try .detect(io, .stderr()), + .on => .escape_codes, + .off => .no_color, + }; + if (webui_listen != null) { if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{}); if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{}); @@ -522,7 +528,7 @@ pub fn main() !void { // Perhaps in the future there could be an Advanced Options flag // such as --debug-build-runner-leaks which would make this code // return instead of calling exit. - _ = io.lockStderrWriter(&.{}) catch {}; + _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; process.exit(1); }, else => |e| return e, @@ -554,9 +560,9 @@ pub fn main() !void { } rebuild: while (true) : (if (run.error_style.clearOnUpdate()) { - const stderr = try io.lockStderrWriter(&stdio_buffer_allocation); - defer io.unlockStderrWriter(); - try stderr.writeAllUnescaped("\x1B[2J\x1B[3J\x1B[H"); + const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); + defer io.unlockStderr(); + try stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H"); }) { if (run.web_server) |*ws| ws.startBuild(); @@ -730,7 +736,8 @@ fn runStepNames( fuzz: ?std.Build.Fuzz.Mode, ) !void { const gpa = run.gpa; - const io = b.graph.io; + const graph = b.graph; + const io = graph.io; const step_stack = &run.step_stack; { @@ -856,20 +863,19 @@ fn runStepNames( .none => break :summary, } - const stderr = try io.lockStderrWriter(&stdio_buffer_allocation); - defer io.unlockStderrWriter(); - - const w = &stderr.interface; - const fwm = stderr.mode; + const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); + defer io.unlockStderr(); + const t = stderr.terminal(); + const w = &stderr.file_writer.interface; const total_count = success_count + failure_count + pending_count + skipped_count; - fwm.setColor(w, .cyan) catch {}; - fwm.setColor(w, .bold) catch {}; + t.setColor(.cyan) catch {}; + t.setColor(.bold) catch {}; w.writeAll("Build Summary: ") catch {}; - fwm.setColor(w, .reset) catch {}; + t.setColor(.reset) catch {}; w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {}; { - fwm.setColor(w, .dim) catch {}; + t.setColor(.dim) catch {}; var first = true; if (skipped_count > 0) { w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {}; @@ -880,12 +886,12 @@ fn runStepNames( first = false; } if (!first) w.writeByte(')') catch {}; - fwm.setColor(w, .reset) catch {}; + t.setColor(.reset) catch {}; } if (test_count > 0) { w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {}; - fwm.setColor(w, .dim) catch {}; + t.setColor(.dim) catch {}; var first = true; if (test_skip_count > 0) { w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {}; @@ -904,7 +910,7 @@ fn runStepNames( first = false; } if (!first) w.writeByte(')') catch {}; - fwm.setColor(w, .reset) catch {}; + t.setColor(.reset) catch {}; } w.writeAll("\n") catch {}; @@ -918,7 +924,7 @@ fn runStepNames( var print_node: PrintNode = .{ .parent = null }; if (step_names.len == 0) { print_node.last = true; - printTreeStep(b, b.default_step, run, w, fwm, &print_node, &step_stack_copy) catch {}; + printTreeStep(b, b.default_step, run, t, &print_node, &step_stack_copy) catch {}; } else { const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: { var i: usize = step_names.len; @@ -937,7 +943,7 @@ fn runStepNames( for (step_names, 0..) |step_name, i| { const tls = b.top_level_steps.get(step_name).?; print_node.last = i + 1 == last_index; - printTreeStep(b, &tls.step, run, w, fwm, &print_node, &step_stack_copy) catch {}; + printTreeStep(b, &tls.step, run, t, &print_node, &step_stack_copy) catch {}; } } w.writeByte('\n') catch {}; @@ -954,7 +960,7 @@ fn runStepNames( if (run.error_style.verboseContext()) break :code 1; // failure; print build command break :code 2; // failure; do not print build command }; - _ = io.lockStderrWriter(&.{}) catch {}; + _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; process.exit(code); } @@ -963,33 +969,30 @@ const PrintNode = struct { last: bool = false, }; -fn printPrefix(node: *PrintNode, w: *Writer, fwm: File.Writer.Mode) !void { +fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void { const parent = node.parent orelse return; + const writer = stderr.writer; if (parent.parent == null) return; - try printPrefix(parent, w, fwm); + try printPrefix(parent, stderr); if (parent.last) { - try w.writeAll(" "); + try writer.writeAll(" "); } else { - try w.writeAll(switch (fwm) { - .terminal_escaped => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ + try writer.writeAll(switch (stderr.mode) { + .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ else => "| ", }); } } -fn printChildNodePrefix(w: *Writer, fwm: File.Writer.Mode) !void { - try w.writeAll(switch (fwm) { - .terminal_escaped => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─ +fn printChildNodePrefix(stderr: Io.Terminal) !void { + try stderr.writer.writeAll(switch (stderr.mode) { + .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─ else => "+- ", }); } -fn printStepStatus( - s: *Step, - stderr: *Writer, - fwm: File.Writer.Mode, - run: *const Run, -) !void { +fn printStepStatus(s: *Step, stderr: Io.Terminal, run: *const Run) !void { + const writer = stderr.writer; switch (s.state) { .precheck_unstarted => unreachable, .precheck_started => unreachable, @@ -997,139 +1000,135 @@ fn printStepStatus( .running => unreachable, .dependency_failure => { - try fwm.setColor(stderr, .dim); - try stderr.writeAll(" transitive failure\n"); - try fwm.setColor(stderr, .reset); + try stderr.setColor(.dim); + try writer.writeAll(" transitive failure\n"); + try stderr.setColor(.reset); }, .success => { - try fwm.setColor(stderr, .green); + try stderr.setColor(.green); if (s.result_cached) { - try stderr.writeAll(" cached"); + try writer.writeAll(" cached"); } else if (s.test_results.test_count > 0) { const pass_count = s.test_results.passCount(); assert(s.test_results.test_count == pass_count + s.test_results.skip_count); - try stderr.print(" {d} pass", .{pass_count}); + try writer.print(" {d} pass", .{pass_count}); if (s.test_results.skip_count > 0) { - try fwm.setColor(stderr, .reset); - try stderr.writeAll(", "); - try fwm.setColor(stderr, .yellow); - try stderr.print("{d} skip", .{s.test_results.skip_count}); + try stderr.setColor(.reset); + try writer.writeAll(", "); + try stderr.setColor(.yellow); + try writer.print("{d} skip", .{s.test_results.skip_count}); } - try fwm.setColor(stderr, .reset); - try stderr.print(" ({d} total)", .{s.test_results.test_count}); + try stderr.setColor(.reset); + try writer.print(" ({d} total)", .{s.test_results.test_count}); } else { - try stderr.writeAll(" success"); + try writer.writeAll(" success"); } - try fwm.setColor(stderr, .reset); + try stderr.setColor(.reset); if (s.result_duration_ns) |ns| { - try fwm.setColor(stderr, .dim); + try stderr.setColor(.dim); if (ns >= std.time.ns_per_min) { - try stderr.print(" {d}m", .{ns / std.time.ns_per_min}); + try writer.print(" {d}m", .{ns / std.time.ns_per_min}); } else if (ns >= std.time.ns_per_s) { - try stderr.print(" {d}s", .{ns / std.time.ns_per_s}); + try writer.print(" {d}s", .{ns / std.time.ns_per_s}); } else if (ns >= std.time.ns_per_ms) { - try stderr.print(" {d}ms", .{ns / std.time.ns_per_ms}); + try writer.print(" {d}ms", .{ns / std.time.ns_per_ms}); } else if (ns >= std.time.ns_per_us) { - try stderr.print(" {d}us", .{ns / std.time.ns_per_us}); + try writer.print(" {d}us", .{ns / std.time.ns_per_us}); } else { - try stderr.print(" {d}ns", .{ns}); + try writer.print(" {d}ns", .{ns}); } - try fwm.setColor(stderr, .reset); + try stderr.setColor(.reset); } if (s.result_peak_rss != 0) { const rss = s.result_peak_rss; - try fwm.setColor(stderr, .dim); + try stderr.setColor(.dim); if (rss >= 1000_000_000) { - try stderr.print(" MaxRSS:{d}G", .{rss / 1000_000_000}); + try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000}); } else if (rss >= 1000_000) { - try stderr.print(" MaxRSS:{d}M", .{rss / 1000_000}); + try writer.print(" MaxRSS:{d}M", .{rss / 1000_000}); } else if (rss >= 1000) { - try stderr.print(" MaxRSS:{d}K", .{rss / 1000}); + try writer.print(" MaxRSS:{d}K", .{rss / 1000}); } else { - try stderr.print(" MaxRSS:{d}B", .{rss}); + try writer.print(" MaxRSS:{d}B", .{rss}); } - try fwm.setColor(stderr, .reset); + try stderr.setColor(.reset); } - try stderr.writeAll("\n"); + try writer.writeAll("\n"); }, .skipped, .skipped_oom => |skip| { - try fwm.setColor(stderr, .yellow); - try stderr.writeAll(" skipped"); + try stderr.setColor(.yellow); + try writer.writeAll(" skipped"); if (skip == .skipped_oom) { - try stderr.writeAll(" (not enough memory)"); - try fwm.setColor(stderr, .dim); - try stderr.print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss }); - try fwm.setColor(stderr, .yellow); + try writer.writeAll(" (not enough memory)"); + try stderr.setColor(.dim); + try writer.print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss }); + try stderr.setColor(.yellow); } - try stderr.writeAll("\n"); - try fwm.setColor(stderr, .reset); + try writer.writeAll("\n"); + try stderr.setColor(.reset); }, .failure => { - try printStepFailure(s, stderr, fwm, false); - try fwm.setColor(stderr, .reset); + try printStepFailure(s, stderr, false); + try stderr.setColor(.reset); }, } } -fn printStepFailure( - s: *Step, - stderr: *Writer, - fwm: File.Writer.Mode, - dim: bool, -) !void { +fn printStepFailure(s: *Step, stderr: Io.Terminal, dim: bool) !void { + const w = stderr.writer; if (s.result_error_bundle.errorMessageCount() > 0) { - try fwm.setColor(stderr, .red); - try stderr.print(" {d} errors\n", .{ + try stderr.setColor(.red); + try w.print(" {d} errors\n", .{ s.result_error_bundle.errorMessageCount(), }); } else if (!s.test_results.isSuccess()) { // These first values include all of the test "statuses". Every test is either passsed, // skipped, failed, crashed, or timed out. - try fwm.setColor(stderr, .green); - try stderr.print(" {d} pass", .{s.test_results.passCount()}); - try fwm.setColor(stderr, .reset); - if (dim) try fwm.setColor(stderr, .dim); + try stderr.setColor(.green); + try w.print(" {d} pass", .{s.test_results.passCount()}); + try stderr.setColor(.reset); + if (dim) try stderr.setColor(.dim); if (s.test_results.skip_count > 0) { - try stderr.writeAll(", "); - try fwm.setColor(stderr, .yellow); - try stderr.print("{d} skip", .{s.test_results.skip_count}); - try fwm.setColor(stderr, .reset); - if (dim) try fwm.setColor(stderr, .dim); + try w.writeAll(", "); + try stderr.setColor(.yellow); + try w.print("{d} skip", .{s.test_results.skip_count}); + try stderr.setColor(.reset); + if (dim) try stderr.setColor(.dim); } if (s.test_results.fail_count > 0) { - try stderr.writeAll(", "); - try fwm.setColor(stderr, .red); - try stderr.print("{d} fail", .{s.test_results.fail_count}); - try fwm.setColor(stderr, .reset); - if (dim) try fwm.setColor(stderr, .dim); + try w.writeAll(", "); + try stderr.setColor(.red); + try w.print("{d} fail", .{s.test_results.fail_count}); + try stderr.setColor(.reset); + if (dim) try stderr.setColor(.dim); } if (s.test_results.crash_count > 0) { - try stderr.writeAll(", "); - try fwm.setColor(stderr, .red); - try stderr.print("{d} crash", .{s.test_results.crash_count}); - try fwm.setColor(stderr, .reset); - if (dim) try fwm.setColor(stderr, .dim); + try w.writeAll(", "); + try stderr.setColor(.red); + try w.print("{d} crash", .{s.test_results.crash_count}); + try stderr.setColor(.reset); + if (dim) try stderr.setColor(.dim); } if (s.test_results.timeout_count > 0) { - try stderr.writeAll(", "); - try fwm.setColor(stderr, .red); - try stderr.print("{d} timeout", .{s.test_results.timeout_count}); - try fwm.setColor(stderr, .reset); - if (dim) try fwm.setColor(stderr, .dim); + try w.writeAll(", "); + try stderr.setColor(.red); + try w.print("{d} timeout", .{s.test_results.timeout_count}); + try stderr.setColor(.reset); + if (dim) try stderr.setColor(.dim); } - try stderr.print(" ({d} total)", .{s.test_results.test_count}); + try w.print(" ({d} total)", .{s.test_results.test_count}); // Memory leaks are intentionally written after the total, because is isn't a test *status*, // but just a flag that any tests -- even passed ones -- can have. We also use a different // separator, so it looks like: // 2 pass, 1 skip, 2 fail (5 total); 2 leaks if (s.test_results.leak_count > 0) { - try stderr.writeAll("; "); - try fwm.setColor(stderr, .red); - try stderr.print("{d} leaks", .{s.test_results.leak_count}); - try fwm.setColor(stderr, .reset); - if (dim) try fwm.setColor(stderr, .dim); + try w.writeAll("; "); + try stderr.setColor(.red); + try w.print("{d} leaks", .{s.test_results.leak_count}); + try stderr.setColor(.reset); + if (dim) try stderr.setColor(.dim); } // It's usually not helpful to know how many error logs there were because they tend to @@ -1142,21 +1141,21 @@ fn printStepFailure( break :show alt_results.isSuccess(); }; if (show_err_logs) { - try stderr.writeAll("; "); - try fwm.setColor(stderr, .red); - try stderr.print("{d} error logs", .{s.test_results.log_err_count}); - try fwm.setColor(stderr, .reset); - if (dim) try fwm.setColor(stderr, .dim); + try w.writeAll("; "); + try stderr.setColor(.red); + try w.print("{d} error logs", .{s.test_results.log_err_count}); + try stderr.setColor(.reset); + if (dim) try stderr.setColor(.dim); } - try stderr.writeAll("\n"); + try w.writeAll("\n"); } else if (s.result_error_msgs.items.len > 0) { - try fwm.setColor(stderr, .red); - try stderr.writeAll(" failure\n"); + try stderr.setColor(.red); + try w.writeAll(" failure\n"); } else { assert(s.result_stderr.len > 0); - try fwm.setColor(stderr, .red); - try stderr.writeAll(" stderr\n"); + try stderr.setColor(.red); + try w.writeAll(" w\n"); } } @@ -1164,11 +1163,11 @@ fn printTreeStep( b: *std.Build, s: *Step, run: *const Run, - stderr: *Writer, - fwm: File.Writer.Mode, + stderr: Io.Terminal, parent_node: *PrintNode, step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), ) !void { + const writer = stderr.writer; const first = step_stack.swapRemove(s); const summary = run.summary; const skip = switch (summary) { @@ -1178,26 +1177,26 @@ fn printTreeStep( .failures => s.state == .success, }; if (skip) return; - try printPrefix(parent_node, stderr, fwm); + try printPrefix(parent_node, stderr); if (parent_node.parent != null) { if (parent_node.last) { - try printChildNodePrefix(stderr, fwm); + try printChildNodePrefix(stderr); } else { - try stderr.writeAll(switch (fwm) { - .terminal_escaped => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ + try writer.writeAll(switch (stderr.mode) { + .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ else => "+- ", }); } } - if (!first) try fwm.setColor(stderr, .dim); + if (!first) try stderr.setColor(.dim); // dep_prefix omitted here because it is redundant with the tree. - try stderr.writeAll(s.name); + try writer.writeAll(s.name); if (first) { - try printStepStatus(s, stderr, fwm, run); + try printStepStatus(s, stderr, run); const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: { var i: usize = s.dependencies.items.len; @@ -1219,17 +1218,17 @@ fn printTreeStep( .parent = parent_node, .last = i == last_index, }; - try printTreeStep(b, dep, run, stderr, fwm, &print_node, step_stack); + try printTreeStep(b, dep, run, stderr, &print_node, step_stack); } } else { if (s.dependencies.items.len == 0) { - try stderr.writeAll(" (reused)\n"); + try writer.writeAll(" (reused)\n"); } else { - try stderr.print(" (+{d} more reused dependencies)\n", .{ + try writer.print(" (+{d} more reused dependencies)\n", .{ s.dependencies.items.len, }); } - try fwm.setColor(stderr, .reset); + try stderr.setColor(.reset); } } @@ -1300,7 +1299,8 @@ fn workerMakeOneStep( prog_node: std.Progress.Node, run: *Run, ) void { - const io = b.graph.io; + const graph = b.graph; + const io = graph.io; const gpa = run.gpa; // First, check the conditions for running this step. If they are not met, @@ -1369,11 +1369,11 @@ fn workerMakeOneStep( const show_error_msgs = s.result_error_msgs.items.len > 0; const show_stderr = s.result_stderr.len > 0; if (show_error_msgs or show_compile_errors or show_stderr) { - const stderr = io.lockStderrWriter(&stdio_buffer_allocation) catch |err| switch (err) { + const stderr = io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode) catch |err| switch (err) { error.Canceled => return, }; - defer io.unlockStderrWriter(); - printErrorMessages(gpa, s, .{}, &stderr.interface, stderr.mode, run.error_style, run.multiline_errors) catch {}; + defer io.unlockStderr(); + printErrorMessages(gpa, s, .{}, stderr.terminal(), run.error_style, run.multiline_errors) catch {}; } handle_result: { @@ -1440,11 +1440,11 @@ pub fn printErrorMessages( gpa: Allocator, failing_step: *Step, options: std.zig.ErrorBundle.RenderOptions, - stderr: *Writer, - fwm: File.Writer.Mode, + stderr: Io.Terminal, error_style: ErrorStyle, multiline_errors: MultilineErrors, ) !void { + const writer = stderr.writer; if (error_style.verboseContext()) { // Provide context for where these error messages are coming from by // printing the corresponding Step subtree. @@ -1456,70 +1456,70 @@ pub fn printErrorMessages( } // Now, `step_stack` has the subtree that we want to print, in reverse order. - try fwm.setColor(stderr, .dim); + try stderr.setColor(.dim); var indent: usize = 0; while (step_stack.pop()) |s| : (indent += 1) { if (indent > 0) { - try stderr.splatByteAll(' ', (indent - 1) * 3); - try printChildNodePrefix(stderr, fwm); + try writer.splatByteAll(' ', (indent - 1) * 3); + try printChildNodePrefix(stderr); } - try stderr.writeAll(s.name); + try writer.writeAll(s.name); if (s == failing_step) { - try printStepFailure(s, stderr, fwm, true); + try printStepFailure(s, stderr, true); } else { - try stderr.writeAll("\n"); + try writer.writeAll("\n"); } } - try fwm.setColor(stderr, .reset); + try stderr.setColor(.reset); } else { // Just print the failing step itself. - try fwm.setColor(stderr, .dim); - try stderr.writeAll(failing_step.name); - try printStepFailure(failing_step, stderr, fwm, true); - try fwm.setColor(stderr, .reset); + try stderr.setColor(.dim); + try writer.writeAll(failing_step.name); + try printStepFailure(failing_step, stderr, true); + try stderr.setColor(.reset); } if (failing_step.result_stderr.len > 0) { - try stderr.writeAll(failing_step.result_stderr); + try writer.writeAll(failing_step.result_stderr); if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) { - try stderr.writeAll("\n"); + try writer.writeAll("\n"); } } - try failing_step.result_error_bundle.renderToWriter(options, stderr, fwm); + try failing_step.result_error_bundle.renderToTerminal(options, stderr); for (failing_step.result_error_msgs.items) |msg| { - try fwm.setColor(stderr, .red); - try stderr.writeAll("error:"); - try fwm.setColor(stderr, .reset); + try stderr.setColor(.red); + try writer.writeAll("error:"); + try stderr.setColor(.reset); if (std.mem.indexOfScalar(u8, msg, '\n') == null) { - try stderr.print(" {s}\n", .{msg}); + try writer.print(" {s}\n", .{msg}); } else switch (multiline_errors) { .indent => { var it = std.mem.splitScalar(u8, msg, '\n'); - try stderr.print(" {s}\n", .{it.first()}); + try writer.print(" {s}\n", .{it.first()}); while (it.next()) |line| { - try stderr.print(" {s}\n", .{line}); + try writer.print(" {s}\n", .{line}); } }, - .newline => try stderr.print("\n{s}\n", .{msg}), - .none => try stderr.print(" {s}\n", .{msg}), + .newline => try writer.print("\n{s}\n", .{msg}), + .none => try writer.print(" {s}\n", .{msg}), } } if (error_style.verboseContext()) { if (failing_step.result_failed_command) |cmd_str| { - try fwm.setColor(stderr, .red); - try stderr.writeAll("failed command: "); - try fwm.setColor(stderr, .reset); - try stderr.writeAll(cmd_str); - try stderr.writeByte('\n'); + try stderr.setColor(.red); + try writer.writeAll("failed command: "); + try stderr.setColor(.reset); + try writer.writeAll(cmd_str); + try writer.writeByte('\n'); } } - try stderr.writeByte('\n'); + try writer.writeByte('\n'); } fn printSteps(builder: *std.Build, w: *Writer) !void { diff --git a/lib/compiler/resinator/cli.zig b/lib/compiler/resinator/cli.zig index 08befbe1fa92dd908a7c37f492290075bb31378b..ddf3ca18b71dba8ba78a94fd3ad8b8faddd5bf0a 100644 --- a/lib/compiler/resinator/cli.zig +++ b/lib/compiler/resinator/cli.zig @@ -126,14 +126,14 @@ pub const Diagnostics = struct { } pub fn renderToStderr(self: *Diagnostics, io: Io, args: []const []const u8) void { - const stderr = io.lockStderrWriter(&.{}); - defer io.unlockStderrWriter(); - self.renderToWriter(args, &stderr.interface, stderr.mode) catch return; + const stderr = io.lockStderr(&.{}, null); + defer io.unlockStderr(); + self.renderToWriter(args, stderr.terminal()) catch return; } - pub fn renderToWriter(self: *Diagnostics, args: []const []const u8, writer: *std.Io.Writer, config: std.Io.tty.Config) !void { + pub fn renderToWriter(self: *Diagnostics, args: []const []const u8, t: Io.Terminal) !void { for (self.errors.items) |err_details| { - try renderErrorMessage(writer, config, err_details, args); + try renderErrorMessage(t, err_details, args); } } diff --git a/lib/compiler/resinator/errors.zig b/lib/compiler/resinator/errors.zig index 031b5d3574a62241404bb2141e6c556033a88bb8..6146f777cd4b9151bbd87dcc66e2ff559fafbcde 100644 --- a/lib/compiler/resinator/errors.zig +++ b/lib/compiler/resinator/errors.zig @@ -69,10 +69,10 @@ pub const Diagnostics = struct { pub fn renderToStderr(self: *Diagnostics, cwd: Io.Dir, source: []const u8, source_mappings: ?SourceMappings) void { const io = self.io; - const stderr = io.lockStderrWriter(&.{}); - defer io.unlockStderrWriter(); + const stderr = io.lockStderr(&.{}, null); + defer io.unlockStderr(); for (self.errors.items) |err_details| { - renderErrorMessage(io, &stderr.interface, stderr.mode, cwd, err_details, source, self.strings.items, source_mappings) catch return; + renderErrorMessage(io, stderr.terminal(), cwd, err_details, source, self.strings.items, source_mappings) catch return; } } diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index bd2fcc912e32f84d3bb347c38a5ca78a6c85e025..81a3a8689a45ffcb2029c9a284ccc9c23a35111d 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -35,8 +35,8 @@ pub fn main() !void { const args = try std.process.argsAlloc(arena); if (args.len < 2) { - const stderr = io.lockStderrWriter(&.{}); - try renderErrorMessage(&stderr.interface, stderr.mode, .err, "expected zig lib dir as first argument", .{}); + const stderr = try io.lockStderr(&.{}, null); + try renderErrorMessage(stderr.terminal(), .err, "expected zig lib dir as first argument", .{}); std.process.exit(1); } const zig_lib_dir = args[1]; @@ -80,9 +80,9 @@ pub fn main() !void { // so that there is a clear separation between the cli diagnostics and whatever // gets printed after if (cli_diagnostics.errors.items.len > 0) { - const stderr = io.lockStderrWriter(&.{}); - defer io.unlockStderrWriter(); - try stderr.interface.writeByte('\n'); + const stderr = try io.lockStderr(&.{}, null); + defer io.unlockStderr(); + try stderr.file_writer.interface.writeByte('\n'); } } break :options options; @@ -130,15 +130,12 @@ pub fn main() !void { var stderr_buf: [512]u8 = undefined; var diagnostics: aro.Diagnostics = .{ .output = output: { if (zig_integration) break :output .{ .to_list = .{ .arena = .init(gpa) } }; - const stderr = io.lockStderrWriter(&stderr_buf); - break :output .{ .to_writer = .{ - .writer = &stderr.interface, - .color = stderr.mode, - } }; + const stderr = try io.lockStderr(&stderr_buf, null); + break :output .{ .to_writer = stderr.terminal() }; } }; defer { diagnostics.deinit(); - if (!zig_integration) std.debug.unlockStderrWriter(); + if (!zig_integration) std.debug.unlockStderr(); } var comp = aro.Compilation.init(aro_arena, aro_arena, io, &diagnostics, Io.Dir.cwd()); @@ -699,9 +696,9 @@ const ErrorHandler = union(enum) { }, .stderr => { // aro errors have already been emitted - const stderr = io.lockStderrWriter(&.{}); - defer io.unlockStderrWriter(); - try renderErrorMessage(&stderr.interface, stderr.mode, .err, "{s}", .{fail_msg}); + const stderr = io.lockStderr(&.{}, null); + defer io.unlockStderr(); + try renderErrorMessage(stderr.terminal(), .err, "{s}", .{fail_msg}); }, } } @@ -745,9 +742,9 @@ const ErrorHandler = union(enum) { try server.serveErrorBundle(error_bundle); }, .stderr => { - const stderr = io.lockStderrWriter(&.{}); - defer io.unlockStderrWriter(); - try renderErrorMessage(&stderr.interface, stderr.mode, msg_type, format, args); + const stderr = try io.lockStderr(&.{}, null); + defer io.unlockStderr(); + try renderErrorMessage(stderr.terminal(), msg_type, format, args); }, } } diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 3a135ab8fd29c53fa7cb6d009b51305585ce727f..3d97944a7f600dba65f131bbbee0bf97a8017992 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -405,18 +405,18 @@ pub fn fuzz( testOne(ctx, input.toSlice()) catch |err| switch (err) { error.SkipZigTest => return, else => { - const stderr = std.debug.lockStderrWriter(&.{}); + const stderr = std.debug.lockStderr(&.{}, null).terminal(); p: { if (@errorReturnTrace()) |trace| { - std.debug.writeStackTrace(trace, &stderr.interface, stderr.mode) catch break :p; + std.debug.writeStackTrace(trace, stderr) catch break :p; } - stderr.interface.print("failed with error.{t}\n", .{err}) catch break :p; + stderr.writer.print("failed with error.{t}\n", .{err}) catch break :p; } std.process.exit(1); }, }; if (log_err_count != 0) { - const stderr = std.debug.lockStderrWriter(&.{}); + const stderr = std.debug.lockStderr(&.{}, .no_color); stderr.interface.print("error logs detected\n", .{}) catch {}; std.process.exit(1); } diff --git a/lib/std/Build.zig b/lib/std/Build.zig index de2985d2b517f8a73320a435f1242133ff7107df..cf0b9e5b0deea8b9bf3a4a0b4e8acfd43aea4ed7 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -129,6 +129,9 @@ pub const Graph = struct { dependency_cache: InitializedDepMap = .empty, allow_so_scripts: ?bool = null, time_report: bool, + /// Similar to the `Io.Terminal.Mode` returned by `Io.lockStderr`, but also + /// respects the '--color' flag. + stderr_mode: ?Io.Terminal.Mode = null, }; const AvailableDeps = []const struct { []const u8, []const u8 }; @@ -2255,9 +2258,10 @@ pub const GeneratedFile = struct { pub fn getPath3(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) Io.Cancelable![]const u8 { return gen.path orelse { - const io = gen.step.owner.graph.io; - const stderr = try io.lockStderrWriter(&.{}); - dumpBadGetPathHelp(gen.step, &stderr.interface, stderr.mode, src_builder, asking_step) catch {}; + const graph = gen.step.owner.graph; + const io = graph.io; + const stderr = try io.lockStderr(&.{}, graph.stderr_mode); + dumpBadGetPathHelp(gen.step, stderr.terminal(), src_builder, asking_step) catch {}; @panic("misconfigured build script"); }; } @@ -2468,13 +2472,15 @@ pub const LazyPath = union(enum) { // TODO make gen.file.path not be absolute and use that as the // basis for not traversing up too many directories. + const graph = src_builder.graph; + var file_path: Cache.Path = .{ .root_dir = Cache.Directory.cwd(), .sub_path = gen.file.path orelse { - const io = src_builder.graph.io; - const stderr = try io.lockStderrWriter(&.{}); - dumpBadGetPathHelp(gen.file.step, &stderr.interface, stderr.mode, src_builder, asking_step) catch {}; - io.unlockStderrWriter(); + const io = graph.io; + const stderr = try io.lockStderr(&.{}, graph.stderr_mode); + dumpBadGetPathHelp(gen.file.step, stderr.terminal(), src_builder, asking_step) catch {}; + io.unlockStderr(); @panic("misconfigured build script"); }, }; @@ -2564,43 +2570,36 @@ fn dumpBadDirnameHelp( comptime msg: []const u8, args: anytype, ) anyerror!void { - const stderr = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - - const w = &stderr.interface; - const fwm = stderr.mode; + const stderr = std.debug.lockStderr(&.{}).terminal(); + defer std.debug.unlockStderr(); + const w = stderr.writer; try w.print(msg, args); if (fail_step) |s| { - fwm.setColor(w, .red) catch {}; + stderr.setColor(.red) catch {}; try w.writeAll(" The step was created by this stack trace:\n"); - fwm.setColor(w, .reset) catch {}; + stderr.setColor(.reset) catch {}; - s.dump(w, fwm); + s.dump(stderr); } if (asking_step) |as| { - fwm.setColor(w, .red) catch {}; + stderr.setColor(.red) catch {}; try w.print(" The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name}); - fwm.setColor(w, .reset) catch {}; + stderr.setColor(.reset) catch {}; - as.dump(w, fwm); + as.dump(stderr); } - fwm.setColor(w, .red) catch {}; - try w.writeAll(" Hope that helps. Proceeding to panic.\n"); - fwm.setColor(w, .reset) catch {}; + stderr.setColor(.red) catch {}; + try w.writeAll(" Proceeding to panic.\n"); + stderr.setColor(.reset) catch {}; } /// In this function the stderr mutex has already been locked. -pub fn dumpBadGetPathHelp( - s: *Step, - w: *Io.Writer, - fwm: File.Writer.Mode, - src_builder: *Build, - asking_step: ?*Step, -) anyerror!void { +pub fn dumpBadGetPathHelp(s: *Step, t: Io.Terminal, src_builder: *Build, asking_step: ?*Step) anyerror!void { + const w = t.writer; try w.print( \\getPath() was called on a GeneratedFile that wasn't built yet. \\ source package path: {s} @@ -2611,21 +2610,21 @@ pub fn dumpBadGetPathHelp( s.name, }); - fwm.setColor(w, .red) catch {}; + t.setColor(.red) catch {}; try w.writeAll(" The step was created by this stack trace:\n"); - fwm.setColor(w, .reset) catch {}; + t.setColor(.reset) catch {}; - s.dump(w, fwm); + s.dump(t); if (asking_step) |as| { - fwm.setColor(w, .red) catch {}; + t.setColor(.red) catch {}; try w.print(" The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name}); - fwm.setColor(w, .reset) catch {}; + t.setColor(.reset) catch {}; - as.dump(w, fwm); + as.dump(t); } - fwm.setColor(w, .red) catch {}; - try w.writeAll(" Hope that helps. Proceeding to panic.\n"); - fwm.setColor(w, .reset) catch {}; + t.setColor(.red) catch {}; + try w.writeAll(" Proceeding to panic.\n"); + t.setColor(.reset) catch {}; } pub const InstallDir = union(enum) { diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index 4770e03a0c6af95412ab8323500ff4ab1d9c45c6..d308efdf7022f8451e99674da3a7e83fed3273c2 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -158,7 +158,8 @@ fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, parent_prog_node: std.P } fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) !void { - const io = run.step.owner.graph.io; + const graph = run.step.owner.graph; + const io = graph.io; const compile = run.producer.?; const prog_node = parent_prog_node.start(compile.step.name, 0); defer prog_node.end(); @@ -171,9 +172,9 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_nod if (show_error_msgs or show_compile_errors or show_stderr) { var buf: [256]u8 = undefined; - const stderr = try io.lockStderrWriter(&buf); - defer io.unlockStderrWriter(); - build_runner.printErrorMessages(gpa, &compile.step, .{}, &stderr.interface, stderr.mode, .verbose, .indent) catch {}; + const stderr = try io.lockStderr(&buf, graph.stderr_mode); + defer io.unlockStderr(); + build_runner.printErrorMessages(gpa, &compile.step, .{}, stderr.terminal(), .verbose, .indent) catch {}; } const rebuilt_bin_path = result catch |err| switch (err) { @@ -186,7 +187,8 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_nod fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_index: u32) void { const owner = run.step.owner; const gpa = owner.allocator; - const io = owner.graph.io; + const graph = owner.graph; + const io = graph.io; const test_name = run.cached_test_metadata.?.testName(unit_test_index); const prog_node = fuzz.prog_node.start(test_name, 0); @@ -195,11 +197,11 @@ fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_index: u32) void { run.rerunInFuzzMode(fuzz, unit_test_index, prog_node) catch |err| switch (err) { error.MakeFailed => { var buf: [256]u8 = undefined; - const stderr = io.lockStderrWriter(&buf) catch |e| switch (e) { + const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) { error.Canceled => return, }; - defer io.unlockStderrWriter(); - build_runner.printErrorMessages(gpa, &run.step, .{}, &stderr.interface, stderr.mode, .verbose, .indent) catch {}; + defer io.unlockStderr(); + build_runner.printErrorMessages(gpa, &run.step, .{}, stderr.terminal(), .verbose, .indent) catch {}; return; }, else => { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 0df58b24b78a7a0ab1be7efdf514406fafd57786..74b41634a73ba36e8130ff9cda652db6aa372f0c 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -328,16 +328,17 @@ pub fn cast(step: *Step, comptime T: type) ?*T { } /// For debugging purposes, prints identifying information about this Step. -pub fn dump(step: *Step, w: *Io.Writer, fwm: Io.File.Writer.Mode) void { +pub fn dump(step: *Step, t: Io.Terminal) void { + const w = t.writer; if (step.debug_stack_trace.instruction_addresses.len > 0) { w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {}; - std.debug.writeStackTrace(&step.debug_stack_trace, w, fwm) catch {}; + std.debug.writeStackTrace(&step.debug_stack_trace, t) catch {}; } else { const field = "debug_stack_frames_count"; comptime assert(@hasField(Build, field)); - fwm.setColor(w, .yellow) catch {}; + t.setColor(.yellow) catch {}; w.print("name: '{s}'. no stack trace collected for this step, see std.Build." ++ field ++ "\n", .{step.name}) catch {}; - fwm.setColor(w, .reset) catch {}; + t.setColor(.reset) catch {}; } } diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 888775884d8fe682cc195a2e57230a1c2ce3ccfa..4752046089a4d6918937ecb6351f4a1a2dcce0d3 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -925,20 +925,21 @@ const CliNamedModules = struct { fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking_step: ?*Step) ![]const u8 { const step = &compile.step; const b = step.owner; - const io = b.graph.io; + const graph = b.graph; + const io = graph.io; const maybe_path: ?*GeneratedFile = @field(compile, tag_name); const generated_file = maybe_path orelse { - const stderr = try io.lockStderrWriter(&.{}); - std.Build.dumpBadGetPathHelp(&compile.step, &stderr.interface, stderr.mode, compile.step.owner, asking_step) catch {}; - io.unlockStderrWriter(); + const stderr = try io.lockStderr(&.{}, graph.stderr_mode); + std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {}; + io.unlockStderr(); @panic("missing emit option for " ++ tag_name); }; const path = generated_file.path orelse { - const stderr = try io.lockStderrWriter(&.{}); - std.Build.dumpBadGetPathHelp(&compile.step, &stderr.interface, stderr.mode, compile.step.owner, asking_step) catch {}; - io.unlockStderrWriter(); + const stderr = try io.lockStderr(&.{}, graph.stderr_mode); + std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {}; + io.unlockStderr(); @panic(tag_name ++ " is null. Is there a missing step dependency?"); }; @@ -1907,7 +1908,7 @@ fn checkCompileErrors(compile: *Compile) !void { try actual_eb.renderToWriter(.{ .include_reference_trace = false, .include_source_line = false, - }, &aw.writer, .streaming); + }, &aw.writer); break :ae try aw.toOwnedSlice(); }; diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 67cf201374322ed41657eb5c263f8a0d2b4472df..157a0292e70b44d16357c6aa0609d73d03854614 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1559,7 +1559,8 @@ fn spawnChildAndCollect( ) !?EvalGenericResult { const b = run.step.owner; const arena = b.allocator; - const io = b.graph.io; + const graph = b.graph; + const io = graph.io; if (fuzz_context != null) { assert(!has_side_effects); @@ -1625,11 +1626,12 @@ fn spawnChildAndCollect( if (!run.disable_zig_progress and !inherit) { child.progress_node = options.progress_node; } - if (inherit) { - const stderr = try io.lockStderrWriter(&.{}); - try setColorEnvironmentVariables(run, env_map, stderr.mode); - } - defer if (inherit) io.unlockStderrWriter(); + const terminal_mode: Io.Terminal.Mode = if (inherit) m: { + const stderr = try io.lockStderr(&.{}, graph.stderr_mode); + break :m stderr.terminal_mode; + } else .no_color; + defer if (inherit) io.unlockStderr(); + try setColorEnvironmentVariables(run, env_map, terminal_mode); var timer = try std.time.Timer.start(); const res = try evalGeneric(run, &child); run.step.result_duration_ns = timer.read(); @@ -1637,7 +1639,7 @@ fn spawnChildAndCollect( } } -fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, fwm: Io.File.Writer.Mode) !void { +fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, terminal_mode: Io.Terminal.Mode) !void { color: switch (run.color) { .manual => {}, .enable => { @@ -1648,9 +1650,9 @@ fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, fwm: Io.File.Writer try env_map.put("NO_COLOR", "1"); env_map.remove("CLICOLOR_FORCE"); }, - .inherit => switch (fwm) { - .terminal_escaped => continue :color .enable, - else => continue :color .disable, + .inherit => switch (terminal_mode) { + .no_color, .windows_api => continue :color .disable, + .escape_codes => continue :color .enable, }, .auto => { const capture_stderr = run.captured_stderr != null or switch (run.stdio) { diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 6fa05b16d284e2db7ebf3df61f10e6a30031a841..78049633a00703971ab55f7f756a86c48862d2b7 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -713,7 +713,7 @@ pub const VTable = struct { processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File, processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize, lockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!LockedStderr, - tryLockStderr: *const fn (?*anyopaque, buffer: []u8) Cancelable!?LockedStderr, + tryLockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!?LockedStderr, unlockStderr: *const fn (?*anyopaque) void, now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 1d0494c6743a4ae5a2ce5837509acd7ce8ec4684..56a1c09340649c566db5a172eb765c96021da80c 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -229,7 +229,7 @@ pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void { .positional, .positional_simple => { w.pos = offset; }, - .streaming, .streaming_simple, .terminal_escaped, .terminal_winapi => { + .streaming, .streaming_simple => { if (w.seek_err) |err| return err; io.vtable.fileSeekTo(io.userdata, w.file, offset) catch |err| { w.seek_err = err; diff --git a/lib/std/Io/Terminal.zig b/lib/std/Io/Terminal.zig index cd0ec56caa77a8baeb2f031003ac8eb8f4250d5f..a39f2175d238f7ad8ee446853ce9a0010227d55a 100644 --- a/lib/std/Io/Terminal.zig +++ b/lib/std/Io/Terminal.zig @@ -130,25 +130,3 @@ pub fn setColor(t: Terminal, color: Color) Io.Writer.Error!void { }, } } - -pub fn disableEscape(t: *Terminal) Mode { - const prev = t.mode; - t.mode = t.mode.toUnescaped(); - return prev; -} - -pub fn restoreEscape(t: *Terminal, mode: Mode) void { - t.mode = mode; -} - -pub fn writeAllUnescaped(t: *Terminal, bytes: []const u8) Io.Writer.Error!void { - const prev_mode = t.disableEscape(); - defer t.restoreEscape(prev_mode); - return t.interface.writeAll(bytes); -} - -pub fn printUnescaped(t: *Terminal, comptime fmt: []const u8, args: anytype) Io.Writer.Error!void { - const prev_mode = t.disableEscape(); - defer t.restoreEscape(prev_mode); - return t.interface.print(fmt, args); -} diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 3e7b6950d402ed55f71689e5759449a21ad4f8df..b07a9bb9a87a8bd4d2e798a4a1f0e2513a3742b3 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7178,7 +7178,7 @@ fn fileWriteFileStreaming( break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; }, .streaming => .{ null, limit.minInt(max_count) }, - .streaming_reading, .positional_reading => break :sf, + .streaming_simple, .positional_simple => break :sf, .failure => return error.ReadFailed, }; const current_thread = Thread.getCurrent(t); @@ -7251,7 +7251,7 @@ fn fileWriteFileStreaming( } var off_in: i64 = undefined; const off_in_ptr: ?*i64 = switch (file_reader.mode) { - .positional_reading, .streaming_reading => return error.Unimplemented, + .positional_simple, .streaming_simple => return error.Unimplemented, .positional => p: { off_in = @intCast(file_reader.pos); break :p &off_in; @@ -7427,7 +7427,7 @@ fn fileWriteFilePositional( } var off_in: i64 = undefined; const off_in_ptr: ?*i64 = switch (file_reader.mode) { - .positional_reading, .streaming_reading => return error.Unimplemented, + .positional_simple, .streaming_simple => return error.Unimplemented, .positional => p: { off_in = @intCast(file_reader.pos); break :p &off_in; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index fd3a456ff7811bbaf0d8b208ef97586c05f57262..e33a2bd575694508b1d06cfaac839816f7a31b64 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -283,12 +283,12 @@ var static_single_threaded_io: Io.Threaded = .init_single_threaded; /// /// Alternatively, use the higher-level `Io.lockStderr` to integrate with the /// application's chosen `Io` implementation. -pub fn lockStderr(buffer: []u8) Io.Terminal { - return (static_single_threaded_io.ioBasic().lockStderr(buffer, null) catch |err| switch (err) { +pub fn lockStderr(buffer: []u8) Io.LockedStderr { + return static_single_threaded_io.ioBasic().lockStderr(buffer, null) catch |err| switch (err) { // Impossible to cancel because no calls to cancel using // `static_single_threaded_io` exist. error.Canceled => unreachable, - }).terminal(); + }; } pub fn unlockStderr() void { @@ -311,7 +311,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { var buffer: [64]u8 = undefined; const stderr = lockStderr(&buffer); defer unlockStderr(); - stderr.writer.print(fmt, args) catch return; + stderr.file_writer.interface.print(fmt, args) catch return; } } @@ -327,7 +327,7 @@ pub inline fn getSelfDebugInfo() !*SelfInfo { /// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned. /// Obtains the stderr mutex while dumping. pub fn dumpHex(bytes: []const u8) void { - const stderr = lockStderr(&.{}); + const stderr = lockStderr(&.{}).terminal(); defer unlockStderr(); dumpHexFallible(stderr, bytes) catch {}; } @@ -551,7 +551,7 @@ pub fn defaultPanic( _ = panicking.fetchAdd(1, .seq_cst); trace: { - const stderr = lockStderr(&.{}); + const stderr = lockStderr(&.{}).terminal(); defer unlockStderr(); const writer = stderr.writer; @@ -581,7 +581,7 @@ pub fn defaultPanic( // A panic happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). - const stderr = lockStderr(&.{}); + const stderr = lockStderr(&.{}).terminal(); stderr.writer.writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. @@ -751,7 +751,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin } /// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors. pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { - const stderr = lockStderr(&.{}); + const stderr = lockStderr(&.{}).terminal(); defer unlockStderr(); writeCurrentStackTrace(.{ .first_address = a: { @@ -814,7 +814,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void } /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. pub fn dumpStackTrace(st: *const StackTrace) void { - const stderr = lockStderr(&.{}); + const stderr = lockStderr(&.{}).terminal(); defer unlockStderr(); writeStackTrace(st, stderr) catch |err| switch (err) { error.WriteFailed => {}, @@ -1550,7 +1550,7 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex _ = panicking.fetchAdd(1, .seq_cst); trace: { - const stderr = lockStderr(&.{}); + const stderr = lockStderr(&.{}).terminal(); defer unlockStderr(); if (addr) |a| { @@ -1571,7 +1571,7 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex // A segfault happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). - const stderr = lockStderr(&.{}); + const stderr = lockStderr(&.{}).terminal(); stderr.writer.writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. @@ -1678,7 +1678,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize pub fn dump(t: @This()) void { if (!enabled) return; - const stderr = lockStderr(&.{}); + const stderr = lockStderr(&.{}).terminal(); defer unlockStderr(); const end = @min(t.index, size); for (t.addrs[0..end], 0..) |frames_array, i| { diff --git a/lib/std/debug/simple_panic.zig b/lib/std/debug/simple_panic.zig index 2d0e5abf4e7c4ddee7315b510a7cb52476cef2a5..34e8f7b43c8e7dcaaa6a55107757481dc3c6c678 100644 --- a/lib/std/debug/simple_panic.zig +++ b/lib/std/debug/simple_panic.zig @@ -14,9 +14,9 @@ const std = @import("../std.zig"); pub fn call(msg: []const u8, ra: ?usize) noreturn { @branchHint(.cold); _ = ra; - const stderr = std.debug.lockStderrWriter(&.{}); - stderr.interface.writeAll(msg) catch {}; - stderr.interface.flush(msg) catch {}; + const stderr_writer = std.debug.lockStderr(&.{}, null).terminal().writer; + stderr_writer.writeAll(msg) catch {}; + stderr_writer.flush(msg) catch {}; @trap(); } diff --git a/lib/std/json/dynamic.zig b/lib/std/json/dynamic.zig index f85916670b663c63a3233f460e63c644043f9a9d..e38ea9cb17e19d3d251813dbb9b0970f353b9cb5 100644 --- a/lib/std/json/dynamic.zig +++ b/lib/std/json/dynamic.zig @@ -47,9 +47,9 @@ pub const Value = union(enum) { } pub fn dump(v: Value) void { - const stderr = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - json.Stringify.value(v, .{}, &stderr.interface) catch return; + const stderr = std.debug.lockStderr(&.{}, null); + defer std.debug.unlockStderr(); + json.Stringify.value(v, .{}, &stderr.file_writer.interface) catch return; } pub fn jsonStringify(value: @This(), jws: anytype) !void { diff --git a/lib/std/log.zig b/lib/std/log.zig index 8579bb04e00bf4195a9a3de84aef2ea7eb7c07a6..81bfc4ebb6c94d75f58194e0b16bb5f1a21a2c34 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -92,7 +92,7 @@ pub fn defaultLog( args: anytype, ) void { var buffer: [64]u8 = undefined; - const stderr = std.debug.lockStderr(&buffer); + const stderr = std.debug.lockStderr(&buffer).terminal(); defer std.debug.unlockStderr(); return defaultLogFileTerminal(level, scope, format, args, stderr) catch {}; } diff --git a/lib/std/process.zig b/lib/std/process.zig index 45cd5751336b1dc7337d8cadde627716da414005..23a3d92e9b8e39789548bd7c33ba36e49c955fb6 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -1856,7 +1856,7 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 { /// and does not return. pub fn cleanExit(io: Io) void { if (builtin.mode == .Debug) return; - _ = io.lockStderrWriter(&.{}) catch {}; + _ = io.lockStderr(&.{}, .no_color) catch {}; exit(0); } diff --git a/lib/std/testing.zig b/lib/std/testing.zig index be6f31680499fc197462f744182b288f255dd816..98fca2f2d5b21664dd09e8750184e2fcaca338d6 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -368,8 +368,8 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const break :diff_index if (expected.len == actual.len) return else shortest; }; if (!backend_can_print) return error.TestExpectedEqual; - if (io.lockStderrWriter(&.{})) |stderr| { - defer io.unlockStderrWriter(); + if (io.lockStderr(&.{}, null)) |stderr| { + defer io.unlockStderr(); failEqualSlices(T, expected, actual, diff_index, &stderr.interface, stderr.mode) catch {}; } else |_| {} return error.TestExpectedEqual; @@ -381,7 +381,7 @@ fn failEqualSlices( actual: []const T, diff_index: usize, w: *Io.Writer, - fwm: Io.File.Writer.Mode, + terminal_mode: Io.Terminal.Mode, ) !void { try w.print("slices differ. first difference occurs at index {d} (0x{X})\n", .{ diff_index, diff_index }); @@ -404,12 +404,12 @@ fn failEqualSlices( var differ = if (T == u8) BytesDiffer{ .expected = expected_window, .actual = actual_window, - .file_writer_mode = fwm, + .terminal_mode = terminal_mode, } else SliceDiffer(T){ .start_index = window_start, .expected = expected_window, .actual = actual_window, - .file_writer_mode = fwm, + .terminal_mode = terminal_mode, }; // Print indexes as hex for slices of u8 since it's more likely to be binary data where @@ -466,21 +466,22 @@ fn SliceDiffer(comptime T: type) type { start_index: usize, expected: []const T, actual: []const T, - file_writer_mode: Io.File.Writer.Mode, + terminal_mode: Io.Terminal.Mode, const Self = @This(); pub fn write(self: Self, writer: *Io.Writer) !void { + const t: Io.Terminal = .{ .writer = writer, .mode = self.terminal_mode }; for (self.expected, 0..) |value, i| { const full_index = self.start_index + i; const diff = if (i < self.actual.len) !std.meta.eql(self.actual[i], value) else true; - if (diff) try self.file_writer_mode.setColor(writer, .red); + if (diff) try t.setColor(writer, .red); if (@typeInfo(T) == .pointer) { try writer.print("[{}]{*}: {any}\n", .{ full_index, value, value }); } else { try writer.print("[{}]: {any}\n", .{ full_index, value }); } - if (diff) try self.file_writer_mode.setColor(writer, .reset); + if (diff) try t.setColor(writer, .reset); } } }; @@ -489,7 +490,7 @@ fn SliceDiffer(comptime T: type) type { const BytesDiffer = struct { expected: []const u8, actual: []const u8, - file_writer_mode: Io.File.Writer.Mode, + terminal_mode: Io.Terminal.Mode, pub fn write(self: BytesDiffer, writer: *Io.Writer) !void { var expected_iterator = std.mem.window(u8, self.expected, 16, 16); @@ -516,7 +517,7 @@ const BytesDiffer = struct { try self.writeDiff(writer, "{c}", .{byte}, diff); } else { // TODO: remove this `if` when https://github.com/ziglang/zig/issues/7600 is fixed - if (self.file_writer_mode == .terminal_winapi) { + if (self.terminal_mode == .windows_api) { try self.writeDiff(writer, ".", .{}, diff); continue; } diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 6e89c75d919c6f33b909a436d27885ec68b5be79..62122640053b93064df7966af0eea312e90f5bdc 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -46,25 +46,18 @@ pub const SrcHasher = std.crypto.hash.Blake3; pub const SrcHash = [16]u8; pub const Color = enum { - /// Determine whether stderr is a terminal or not automatically. + /// Auto-detect whether stream supports terminal colors. auto, - /// Assume stderr is not a terminal. + /// Force-enable colors. off, - /// Assume stderr is a terminal. + /// Suppress colors. on, - pub fn getTtyConf(color: Color, detected: Io.File.Writer.Mode) Io.File.Writer.Mode { + pub fn terminalMode(color: Color) ?Io.Terminal.Mode { return switch (color) { - .auto => detected, - .on => .terminal_escaped, - .off => .streaming, - }; - } - pub fn detectTtyConf(color: Color, io: Io) Io.File.Writer.Mode { - return switch (color) { - .auto => .detect(io, .stderr()), - .on => .terminal_escaped, - .off => .streaming, + .auto => null, + .on => .escape_codes, + .off => .no_color, }; } }; diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 556b1167a33c7fa762ec022d43fce454bbd50477..c5275729ae5f1a6efd58ff65b813cc98863b49ff 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -166,51 +166,53 @@ pub const RenderToStderrError = Io.Cancelable || Io.File.Writer.Error; pub fn renderToStderr(eb: ErrorBundle, io: Io, options: RenderOptions, color: std.zig.Color) RenderToStderrError!void { var buffer: [256]u8 = undefined; - const stderr = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); - renderToWriter(eb, options, &stderr.interface, color.getTtyConf(stderr.mode)) catch |err| switch (err) { - error.WriteFailed => return stderr.err.?, + const stderr = try io.lockStderr(&buffer, color.terminalMode()); + defer io.unlockStderr(); + renderToTerminal(eb, options, stderr.terminal()) catch |err| switch (err) { + error.WriteFailed => return stderr.file_writer.err.?, else => |e| return e, }; } -pub fn renderToWriter( - eb: ErrorBundle, - options: RenderOptions, - w: *Writer, - fwm: Io.File.Writer.Mode, -) Io.File.Writer.Mode.SetColorError!void { +pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, w: *Writer) Writer.Error!void { + return renderToTerminal(eb, options, .{ .writer = w, .mode = .no_color }) catch |err| switch (err) { + error.WriteFailed => |e| return e, + else => unreachable, + }; +} + +pub fn renderToTerminal(eb: ErrorBundle, options: RenderOptions, t: Io.Terminal) Io.Terminal.SetColorError!void { if (eb.extra.len == 0) return; for (eb.getMessages()) |err_msg| { - try renderErrorMessageToWriter(eb, options, err_msg, w, fwm, "error", .red, 0); + try renderErrorMessage(eb, options, err_msg, t, "error", .red, 0); } if (options.include_log_text) { const log_text = eb.getCompileLogOutput(); if (log_text.len != 0) { - try w.writeAll("\nCompile Log Output:\n"); - try w.writeAll(log_text); + try t.writer.writeAll("\nCompile Log Output:\n"); + try t.writer.writeAll(log_text); } } } -fn renderErrorMessageToWriter( +fn renderErrorMessage( eb: ErrorBundle, options: RenderOptions, err_msg_index: MessageIndex, - w: *Writer, - fwm: Io.File.Writer.Mode, + t: Io.Terminal, kind: []const u8, - color: Io.File.Writer.Color, + color: Io.Terminal.Color, indent: usize, -) Io.File.Writer.Mode.SetColorError!void { +) Io.Terminal.SetColorError!void { + const w = t.writer; const err_msg = eb.getErrorMessage(err_msg_index); if (err_msg.src_loc != .none) { const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc)); var prefix: Writer.Discarding = .init(&.{}); try w.splatByteAll(' ', indent); prefix.count += indent; - try fwm.setColor(w, .bold); + try t.setColor(.bold); try w.print("{s}:{d}:{d}: ", .{ eb.nullTerminatedString(src.data.src_path), src.data.line + 1, @@ -221,7 +223,7 @@ fn renderErrorMessageToWriter( src.data.line + 1, src.data.column + 1, }); - try fwm.setColor(w, color); + try t.setColor(color); try w.writeAll(kind); prefix.count += kind.len; try w.writeAll(": "); @@ -229,17 +231,17 @@ fn renderErrorMessageToWriter( // This is the length of the part before the error message: // e.g. "file.zig:4:5: error: " const prefix_len: usize = @intCast(prefix.count); - try fwm.setColor(w, .reset); - try fwm.setColor(w, .bold); + try t.setColor(.reset); + try t.setColor(.bold); if (err_msg.count == 1) { try writeMsg(eb, err_msg, w, prefix_len); try w.writeByte('\n'); } else { try writeMsg(eb, err_msg, w, prefix_len); - try fwm.setColor(w, .dim); + try t.setColor(.dim); try w.print(" ({d} times)\n", .{err_msg.count}); } - try fwm.setColor(w, .reset); + try t.setColor(.reset); if (src.data.source_line != 0 and options.include_source_line) { const line = eb.nullTerminatedString(src.data.source_line); for (line) |b| switch (b) { @@ -252,19 +254,19 @@ fn renderErrorMessageToWriter( // -1 since span.main includes the caret const after_caret = src.data.span_end -| src.data.span_main -| 1; try w.splatByteAll(' ', src.data.column - before_caret); - try fwm.setColor(w, .green); + try t.setColor(.green); try w.splatByteAll('~', before_caret); try w.writeByte('^'); try w.splatByteAll('~', after_caret); try w.writeByte('\n'); - try fwm.setColor(w, .reset); + try t.setColor(.reset); } for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, options, note, w, fwm, "note", .cyan, indent); + try renderErrorMessage(eb, options, note, t, "note", .cyan, indent); } if (src.data.reference_trace_len > 0 and options.include_reference_trace) { - try fwm.setColor(w, .reset); - try fwm.setColor(w, .dim); + try t.setColor(.reset); + try t.setColor(.dim); try w.print("referenced by:\n", .{}); var ref_index = src.end; for (0..src.data.reference_trace_len) |_| { @@ -291,25 +293,25 @@ fn renderErrorMessageToWriter( ); } } - try fwm.setColor(w, .reset); + try t.setColor(.reset); } } else { - try fwm.setColor(w, color); + try t.setColor(color); try w.splatByteAll(' ', indent); try w.writeAll(kind); try w.writeAll(": "); - try fwm.setColor(w, .reset); + try t.setColor(.reset); const msg = eb.nullTerminatedString(err_msg.msg); if (err_msg.count == 1) { try w.print("{s}\n", .{msg}); } else { try w.print("{s}", .{msg}); - try fwm.setColor(w, .dim); + try t.setColor(.dim); try w.print(" ({d} times)\n", .{err_msg.count}); } - try fwm.setColor(w, .reset); + try t.setColor(.reset); for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, options, note, w, fwm, "note", .cyan, indent + 4); + try renderErrorMessage(eb, options, note, t, "note", .cyan, indent + 4); } } } diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 6c11c3021b2f16efa75c511e44b1e1fa77a64081..94d877b4066af8bf210d92d69661f3b4e8f29c5c 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6333,25 +6333,25 @@ var fixed_buffer_mem: [100 * 1024]u8 = undefined; fn testParse(io: Io, source: [:0]const u8, allocator: Allocator, anything_changed: *bool) ![]u8 { var buffer: [64]u8 = undefined; - const stderr = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); + const stderr = try io.lockStderr(&buffer, null); + defer io.unlockStderr(); var tree = try std.zig.Ast.parse(allocator, source, .zig); defer tree.deinit(allocator); for (tree.errors) |parse_error| { const loc = tree.tokenLocation(0, parse_error.token); - try stderr.printUnescaped("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); - try tree.renderError(parse_error, &stderr.interface); - try stderr.interface.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); + try stderr.writer.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); + try tree.renderError(parse_error, stderr.writer); + try stderr.writer.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); { var i: usize = 0; while (i < loc.column) : (i += 1) { - try stderr.writeAllUnescaped(" "); + try stderr.writer.writeAll(" "); } - try stderr.writeAllUnescaped("^"); + try stderr.writer.writeAll("^"); } - try stderr.writeAllUnescaped("\n"); + try stderr.writer.writeAll("\n"); } if (tree.errors.len != 0) { return error.ParseError; diff --git a/src/Air/print.zig b/src/Air/print.zig index 05614a0ed3700217cb2d31f147f8abb8da89b2aa..98b0a0b242ab7b7c41976c35f76756a5efbd7dec 100644 --- a/src/Air/print.zig +++ b/src/Air/print.zig @@ -76,9 +76,9 @@ pub fn dump(air: Air, pt: Zcu.PerThread, liveness: ?Air.Liveness) void { const comp = pt.zcu.comp; const io = comp.io; var buffer: [512]u8 = undefined; - const stderr = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); - const w = &stderr.interface; + const stderr = try io.lockStderr(&buffer, null); + defer io.unlockStderr(); + const w = &stderr.file_writer.interface; air.write(w, pt, liveness); } @@ -86,9 +86,9 @@ pub fn dumpInst(air: Air, inst: Air.Inst.Index, pt: Zcu.PerThread, liveness: ?Ai const comp = pt.zcu.comp; const io = comp.io; var buffer: [512]u8 = undefined; - const stderr = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); - const w = &stderr.interface; + const stderr = try io.lockStderr(&buffer, null); + defer io.unlockStderr(); + const w = &stderr.file_writer.interface; air.writeInst(w, inst, pt, liveness); } diff --git a/src/Compilation.zig b/src/Compilation.zig index 37e15ab17130e0cf414dbbb87558e4a4f735e6f4..f3fcef40a014d326f6509188b79ef7f726b2d11c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2092,14 +2092,16 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, } if (options.verbose_llvm_cpu_features) { - if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: { - const stderr = try io.lockStderrWriter(&.{}); - defer io.unlockStderrWriter(); - const w = &stderr.interface; - w.print("compilation: {s}\n", .{options.root_name}) catch break :print; - w.print(" target: {s}\n", .{try target.zigTriple(arena)}) catch break :print; - w.print(" cpu: {s}\n", .{target.cpu.model.name}) catch break :print; - w.print(" features: {s}\n", .{cf}) catch {}; + if (options.root_mod.resolved_target.llvm_cpu_features) |cf| { + const stderr = try io.lockStderr(&.{}, null); + defer io.unlockStderr(); + const w = &stderr.file_writer.interface; + printVerboseLlvmCpuFeatures(w, arena, options.root_name, target, cf) catch |err| switch (err) { + error.WriteFailed => switch (stderr.file_writer.err.?) { + error.Canceled => |e| return e, + else => {}, + }, + }; } } @@ -2700,6 +2702,19 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, return comp; } +fn printVerboseLlvmCpuFeatures( + w: *Writer, + arena: Allocator, + root_name: []const u8, + target: *const std.Target, + cf: [*:0]const u8, +) Writer.Error!void { + try w.print("compilation: {s}\n", .{root_name}); + try w.print(" target: {s}\n", .{try target.zigTriple(arena)}); + try w.print(" cpu: {s}\n", .{target.cpu.model.name}); + try w.print(" features: {s}\n", .{cf}); +} + pub fn destroy(comp: *Compilation) void { const gpa = comp.gpa; const io = comp.io; @@ -4259,9 +4274,9 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { // However, we haven't reported any such error. // This is a compiler bug. print_ctx: { - const stderr = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - const w = &stderr.interface; + const stderr = std.debug.lockStderr(&.{}).terminal(); + defer std.debug.unlockStderr(); + const w = stderr.writer; w.writeAll("referenced transitive analysis errors, but none actually emitted\n") catch break :print_ctx; w.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}) catch break :print_ctx; while (ref) |r| { @@ -7772,11 +7787,11 @@ pub fn lockAndSetMiscFailure( pub fn dumpArgv(io: Io, argv: []const []const u8) Io.Cancelable!void { var buffer: [64]u8 = undefined; - const stderr = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); - const w = &stderr.interface; + const stderr = try io.lockStderr(&buffer); + defer io.unlockStderr(); + const w = &stderr.file_writer.interface; return dumpArgvWriter(w, argv) catch |err| switch (err) { - error.WriteFailed => switch (stderr.err.?) { + error.WriteFailed => switch (stderr.file_writer.err.?) { error.Canceled => return error.Canceled, else => return, }, diff --git a/src/InternPool.zig b/src/InternPool.zig index 539cb441c5b9200f8919004ce4dcf734dba2dd62..98bde244c5a01382b4b94ece3f47bf211e95c140 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -11169,9 +11169,9 @@ pub fn mutateVarInit(ip: *InternPool, io: Io, index: Index, init_index: Index) v pub fn dump(ip: *const InternPool, io: Io) Io.Cancelable!void { var buffer: [4096]u8 = undefined; - const stderr_writer = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); - const w = &stderr_writer.interface; + const stderr = try io.lockStderr(&buffer, null); + defer io.unlockStderr(); + const w = &stderr.file_writer.interface; try dumpStatsFallible(ip, w, std.heap.page_allocator); try dumpAllFallible(ip, w); } @@ -11536,8 +11536,8 @@ fn dumpAllFallible(ip: *const InternPool, w: *Io.Writer) anyerror!void { pub fn dumpGenericInstances(ip: *const InternPool, io: Io, allocator: Allocator) Io.Cancelable!void { var buffer: [4096]u8 = undefined; - const stderr_writer = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); + const stderr_writer = try io.lockStderr(&buffer, null); + defer io.unlockStderr(); const w = &stderr_writer.interface; try ip.dumpGenericInstancesFallible(allocator, w); } diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 76ab3e229c828e5d29046882e2931973bc0daac0..103cbaaaaec0e1b14c0a281fa752ccbcfda26101 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -4560,10 +4560,10 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e if (build_options.enable_debug_extensions and comp.verbose_air) p: { const io = comp.io; - const stderr = try io.lockStderrWriter(&.{}); - defer io.unlockStderrWriter(); - printVerboseAir(pt, liveness, fqn, air, &stderr.interface) catch |err| switch (err) { - error.WriteFailed => switch (stderr.err.?) { + const stderr = try io.lockStderr(&.{}, null); + defer io.unlockStderr(); + printVerboseAir(pt, liveness, fqn, air, &stderr.file_writer.interface) catch |err| switch (err) { + error.WriteFailed => switch (stderr.file_writer.err.?) { error.Canceled => |e| return e, else => break :p, }, diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig index 138c70fecff33cfb548b6239aec565516de64b1d..93a6e0a7687f5f28dd8a30b344dcaa223701eeb8 100644 --- a/src/codegen/aarch64/Select.zig +++ b/src/codegen/aarch64/Select.zig @@ -11274,16 +11274,15 @@ fn initValueAdvanced( } pub fn dumpValues(isel: *Select, which: enum { only_referenced, all }) void { const zcu = isel.pt.zcu; - const io = zcu.comp.io; const gpa = zcu.gpa; const ip = &zcu.intern_pool; const nav = ip.getNav(isel.nav_index); errdefer |err| @panic(@errorName(err)); - const stderr_writer = io.lockStderrWriter(&.{}) catch return; - defer io.unlockStderrWriter(); - const stderr = &stderr_writer.interface; + const locked_stderr = std.debug.lockStderr(&.{}, null); + defer std.debug.unlockStderr(); + const stderr = &locked_stderr.file_writer.interface; var reverse_live_values: std.AutoArrayHashMapUnmanaged(Value.Index, std.ArrayList(Air.Inst.Index)) = .empty; defer { diff --git a/src/crash_report.zig b/src/crash_report.zig index f23806b75876739487f170f9df32d5e00d8968b3..e56bc7cec53e8a2e057ab52cf1c4055b1539fd59 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -95,9 +95,9 @@ fn dumpCrashContext() Io.Writer.Error!void { // TODO: this does mean that a different thread could grab the stderr mutex between the context // and the actual panic printing, which would be quite confusing. - const stderr = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); - const w = &stderr.interface; + const stderr = std.debug.lockStderr(&.{}); + defer std.debug.unlockStderr(); + const w = &stderr.file_writer.interface; try w.writeAll("Compiler crash context:\n"); diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index a76fec92377e0ffa19e7882ef5ffe83fad8f908b..8cf1dfa3033e5d157120deabd1b46b370b05cb18 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -314,14 +314,14 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { if (comp.verbose_cc) { var buffer: [256]u8 = undefined; - const stderr = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); - const w = &stderr.interface; + const stderr = try io.lockStderr(&buffer, null); + defer io.unlockStderr(); + const w = &stderr.file_writer.interface; w.print("def file: {s}\n", .{def_file_path}) catch |err| switch (err) { - error.WriteFailed => return stderr.err.?, + error.WriteFailed => return stderr.file_writer.err.?, }; w.print("include dir: {s}\n", .{include_dir}) catch |err| switch (err) { - error.WriteFailed => return stderr.err.?, + error.WriteFailed => return stderr.file_writer.err.?, }; } @@ -339,12 +339,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { if (aro_comp.diagnostics.output.to_list.messages.items.len != 0) { var buffer: [64]u8 = undefined; - const stderr = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); + const stderr = try io.lockStderr(&buffer, null); + defer io.unlockStderr(); for (aro_comp.diagnostics.output.to_list.messages.items) |msg| { if (msg.kind == .@"fatal error" or msg.kind == .@"error") { - msg.write(&stderr.interface, stderr.mode, true) catch |err| switch (err) { - error.WriteFailed => return stderr.err.?, + msg.write(stderr.terminal(), true) catch |err| switch (err) { + error.WriteFailed => return stderr.file_writer.err.?, }; return error.AroPreprocessorFailed; } @@ -365,9 +365,9 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { error.OutOfMemory => |e| return e, error.ParseError => { var buffer: [64]u8 = undefined; - const stderr = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); - const w = &stderr.interface; + const stderr = try io.lockStderr(&buffer, null); + defer io.unlockStderr(); + const w = &stderr.file_writer.interface; try w.writeAll("error: "); try def_diagnostics.writeMsg(w, input); try w.writeByte('\n'); diff --git a/src/libs/mingw/def.zig b/src/libs/mingw/def.zig index 9a105b61821d736ea572c5e75bfa215ece2d573e..f1c112d16e0e49249faeaaf159f7e2b79e277861 100644 --- a/src/libs/mingw/def.zig +++ b/src/libs/mingw/def.zig @@ -1039,9 +1039,9 @@ fn testParse( const module = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.ParseError => { - const stderr = try io.lockStderrWriter(&.{}); - defer io.unlockStderrWriter(); - const w = &stderr.interface; + const stderr = try io.lockStderr(&.{}, null); + defer io.unlockStderr(); + const w = &stderr.file_writer.interface; try diagnostics.writeMsg(w, source); try w.writeByte('\n'); return err; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index d3796696139c171b18134621fb81dbbeda511512..03b757f5b4c9e079cb61c277e305ccc4bdd81472 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -2382,9 +2382,9 @@ pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) Io.Cancelable!void { const comp = coff.base.comp; const io = comp.io; var buffer: [512]u8 = undefined; - const stderr = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); - const w = &stderr.interface; + const stderr = try io.lockStderr(&buffer, null); + defer io.unlockStderr(); + const w = &stderr.file_writer.interface; coff.printNode(tid, w, .root, 0) catch |err| switch (err) { error.WriteFailed => return stderr.err.?, }; diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 3c511f1ee925a569c1610ad7f0a5b3d0346382c4..bbdb4393856d0f230f6f87086bf2b60d7e0fca9b 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -3733,9 +3733,9 @@ pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) Io.Cancelable!void { const comp = elf.base.comp; const io = comp.io; var buffer: [512]u8 = undefined; - const stderr = try io.lockStderrWriter(&buffer); - defer io.unlockStderrWriter(); - const w = &stderr.interface; + const stderr = try io.lockStderr(&buffer, null); + defer io.lockStderr(); + const w = &stderr.file_writer.interface; elf.printNode(tid, w, .root, 0) catch |err| switch (err) { error.WriteFailed => return stderr.err.?, }; diff --git a/src/main.zig b/src/main.zig index c5ed278921dc7d16da9d5f5f41dbb27588c62d91..8e30febb8195402164ab64f6ccb9a27b4882fb88 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4429,9 +4429,9 @@ fn runOrTest( // the error message and invocation below. if (process.can_execv and arg_mode == .run) { // execv releases the locks; no need to destroy the Compilation here. - _ = try io.lockStderrWriter(&.{}); + _ = try io.lockStderr(&.{}, .no_color); const err = process.execve(gpa, argv.items, &env_map); - io.unlockStderrWriter(); + io.unlockStderr(); try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd }); @@ -4448,8 +4448,8 @@ fn runOrTest( comp_destroyed.* = true; const term_result = t: { - _ = try io.lockStderrWriter(&.{}); - defer io.unlockStderrWriter(); + _ = try io.lockStderr(&.{}, .no_color); + defer io.unlockStderr(); break :t child.spawnAndWait(io); }; const term = term_result catch |err| { @@ -5418,8 +5418,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) child.stderr_behavior = .Inherit; const term = t: { - _ = try io.lockStderrWriter(&.{}); - defer io.unlockStderrWriter(); + _ = try io.lockStderr(&.{}, .no_color); + defer io.unlockStderr(); break :t child.spawnAndWait(io) catch |err| fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err }); }; -- 2.54.0 From a5b719e9eb0a196a43a88af8f62c897d1ecfa04f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Dec 2025 23:12:11 -0800 Subject: [PATCH 066/195] compiler: fix build failures from std.Io-fs --- lib/compiler/aro/aro/Diagnostics.zig | 10 ++++----- lib/std/Io/Dir.zig | 2 +- lib/std/Io/Threaded.zig | 2 +- lib/std/zig/llvm/Builder.zig | 4 ++-- src/Compilation.zig | 32 +++++++++++----------------- src/InternPool.zig | 26 +++++++++++----------- src/Package/Fetch.zig | 15 ++++++------- src/codegen/aarch64/Select.zig | 2 +- src/codegen/llvm.zig | 18 ++++++++-------- src/libs/mingw.zig | 1 + src/link.zig | 3 +-- src/link/Elf.zig | 5 +++-- src/link/Lld.zig | 2 +- src/link/MachO.zig | 8 ++++--- src/link/MappedFile.zig | 4 ++-- src/link/Queue.zig | 1 + src/link/Wasm.zig | 3 ++- 17 files changed, 67 insertions(+), 71 deletions(-) diff --git a/lib/compiler/aro/aro/Diagnostics.zig b/lib/compiler/aro/aro/Diagnostics.zig index b33efa3f85dddc21e86f7631a90477b4bb5028dd..993b5d93af652c56eb7206545d5b1f4c915ac05a 100644 --- a/lib/compiler/aro/aro/Diagnostics.zig +++ b/lib/compiler/aro/aro/Diagnostics.zig @@ -541,11 +541,11 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { switch (d.output) { .ignore => {}, - .to_writer => |writer| { - var config = writer.color; - if (d.color == false) config = .no_color; - if (d.color == true and config == .no_color) config = .escape_codes; - msg.write(writer.writer, config, d.details) catch { + .to_writer => |t| { + var new_mode = t.mode; + if (d.color == false) new_mode = .no_color; + if (d.color == true and new_mode == .no_color) new_mode = .escape_codes; + msg.write(.{ .writer = t.writer, .mode = new_mode }, d.details) catch { return error.FatalError; }; }, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index f482f72afdd6aa983250fb4c2c1c1939ceeb4fd3..12fcea6576769a9f569915d1ea3439e1f6f56505 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -1739,7 +1739,7 @@ pub fn setFilePermissions( new_permissions: File.Permissions, options: SetFilePermissionsOptions, ) SetFilePermissionsError!void { - return io.vtable.dirSetFilePermissions(io.userdata, sub_path, dir, new_permissions, options); + return io.vtable.dirSetFilePermissions(io.userdata, dir, sub_path, new_permissions, options); } pub const SetOwnerError = File.SetOwnerError; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index b07a9bb9a87a8bd4d2e798a4a1f0e2513a3742b3..ca10272b333cf917bba41afadb79ddbc05b26613 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -10139,7 +10139,7 @@ fn initLockedStderr( t.stderr_writer.interface.buffer = buffer; return .{ .file_writer = &t.stderr_writer, - .terminal_mode = t.stderr_mode, + .terminal_mode = terminal_mode orelse t.stderr_mode, }; } diff --git a/lib/std/zig/llvm/Builder.zig b/lib/std/zig/llvm/Builder.zig index 251c87defc21b0dfd19b0d3a71c0b66b0d32673e..66d20df34810c62e2741ed066906a519032af6ef 100644 --- a/lib/std/zig/llvm/Builder.zig +++ b/lib/std/zig/llvm/Builder.zig @@ -9576,10 +9576,10 @@ pub fn asmValue( return (try self.asmConst(ty, info, assembly, constraints)).toValue(); } -pub fn dump(b: *Builder) void { +pub fn dump(b: *Builder, io: Io) void { var buffer: [4000]u8 = undefined; const stderr: Io.File = .stderr(); - b.printToFile(stderr, &buffer) catch {}; + b.printToFile(io, stderr, &buffer) catch {}; } pub fn printToFilePath(b: *Builder, io: Io, dir: Io.Dir, path: []const u8) !void { diff --git a/src/Compilation.zig b/src/Compilation.zig index f3fcef40a014d326f6509188b79ef7f726b2d11c..a78bf19e666a6e7027741dd7ca430c77f553ef2a 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2101,6 +2101,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, error.Canceled => |e| return e, else => {}, }, + error.OutOfMemory => |e| return e, }; } } @@ -2708,7 +2709,7 @@ fn printVerboseLlvmCpuFeatures( root_name: []const u8, target: *const std.Target, cf: [*:0]const u8, -) Writer.Error!void { +) (Writer.Error || Allocator.Error)!void { try w.print("compilation: {s}\n", .{root_name}); try w.print(" target: {s}\n", .{try target.zigTriple(arena)}); try w.print(" cpu: {s}\n", .{target.cpu.model.name}); @@ -3113,7 +3114,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE if (build_options.enable_debug_extensions and comp.verbose_intern_pool) { std.debug.print("intern pool stats for '{s}':\n", .{comp.root_name}); - zcu.intern_pool.dump(io); + zcu.intern_pool.dump(); } if (build_options.enable_debug_extensions and comp.verbose_generic_instances) { @@ -3320,11 +3321,8 @@ pub fn resolveEmitPathFlush( }, } } -fn flush( - comp: *Compilation, - arena: Allocator, - tid: Zcu.PerThread.Id, -) Allocator.Error!void { + +fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancelable || Allocator.Error)!void { const io = comp.io; if (comp.zcu) |zcu| { if (zcu.llvm_object) |llvm_object| { @@ -3390,7 +3388,7 @@ fn flush( // This is needed before reading the error flags. lf.flush(arena, tid, comp.link_prog_node) catch |err| switch (err) { error.LinkFailure => {}, // Already reported. - error.OutOfMemory => return error.OutOfMemory, + error.OutOfMemory, error.Canceled => |e| return e, }; } if (comp.zcu) |zcu| { @@ -3614,6 +3612,7 @@ fn emitFromCObject( new_ext: []const u8, unresolved_emit_path: []const u8, ) Allocator.Error!void { + const io = comp.io; // The dirname and stem (i.e. everything but the extension), of the sub path of the C object. // We'll append `new_ext` to it to get the path to the right thing (asm, LLVM IR, etc). const c_obj_dir_and_stem: []const u8 = p: { @@ -3623,23 +3622,18 @@ fn emitFromCObject( }; const src_path: Cache.Path = .{ .root_dir = c_obj_path.root_dir, - .sub_path = try std.fmt.allocPrint(arena, "{s}{s}", .{ - c_obj_dir_and_stem, - new_ext, - }), + .sub_path = try std.fmt.allocPrint(arena, "{s}{s}", .{ c_obj_dir_and_stem, new_ext }), }; const emit_path = comp.resolveEmitPath(unresolved_emit_path); - src_path.root_dir.handle.copyFile( + Io.Dir.copyFile( + src_path.root_dir.handle, src_path.sub_path, emit_path.root_dir.handle, emit_path.sub_path, + io, .{}, - ) catch |err| log.err("unable to copy '{f}' to '{f}': {s}", .{ - src_path, - emit_path, - @errorName(err), - }); + ) catch |err| log.err("unable to copy '{f}' to '{f}': {t}", .{ src_path, emit_path, err }); } /// Having the file open for writing is problematic as far as executing the @@ -7787,7 +7781,7 @@ pub fn lockAndSetMiscFailure( pub fn dumpArgv(io: Io, argv: []const []const u8) Io.Cancelable!void { var buffer: [64]u8 = undefined; - const stderr = try io.lockStderr(&buffer); + const stderr = try io.lockStderr(&buffer, null); defer io.unlockStderr(); const w = &stderr.file_writer.interface; return dumpArgvWriter(w, argv) catch |err| switch (err) { diff --git a/src/InternPool.zig b/src/InternPool.zig index 98bde244c5a01382b4b94ece3f47bf211e95c140..3076aa2cf36490aba4f400e581485edbc1e20b98 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -11167,16 +11167,16 @@ pub fn mutateVarInit(ip: *InternPool, io: Io, index: Index, init_index: Index) v @atomicStore(u32, &extra_items[item.data + std.meta.fieldIndex(Tag.Variable, "init").?], @intFromEnum(init_index), .release); } -pub fn dump(ip: *const InternPool, io: Io) Io.Cancelable!void { +pub fn dump(ip: *const InternPool) void { var buffer: [4096]u8 = undefined; - const stderr = try io.lockStderr(&buffer, null); - defer io.unlockStderr(); + const stderr = std.debug.lockStderr(&buffer); + defer std.debug.unlockStderr(); const w = &stderr.file_writer.interface; - try dumpStatsFallible(ip, w, std.heap.page_allocator); - try dumpAllFallible(ip, w); + dumpStatsFallible(ip, w, std.heap.page_allocator) catch return; + dumpAllFallible(ip, w) catch return; } -fn dumpStatsFallible(ip: *const InternPool, w: *Io.Writer, arena: Allocator) anyerror!void { +fn dumpStatsFallible(ip: *const InternPool, w: *Io.Writer, arena: Allocator) !void { var items_len: usize = 0; var extra_len: usize = 0; var limbs_len: usize = 0; @@ -11429,9 +11429,9 @@ fn dumpStatsFallible(ip: *const InternPool, w: *Io.Writer, arena: Allocator) any }; counts.sort(SortContext{ .map = &counts }); const len = @min(50, counts.count()); - w.print(" top 50 tags:\n", .{}); + try w.print(" top 50 tags:\n", .{}); for (counts.keys()[0..len], counts.values()[0..len]) |tag, stats| { - w.print(" {t}: {d} occurrences, {d} total bytes\n", .{ tag, stats.count, stats.bytes }); + try w.print(" {t}: {d} occurrences, {d} total bytes\n", .{ tag, stats.count, stats.bytes }); } } @@ -11534,12 +11534,12 @@ fn dumpAllFallible(ip: *const InternPool, w: *Io.Writer) anyerror!void { } } -pub fn dumpGenericInstances(ip: *const InternPool, io: Io, allocator: Allocator) Io.Cancelable!void { +pub fn dumpGenericInstances(ip: *const InternPool, allocator: Allocator) void { var buffer: [4096]u8 = undefined; - const stderr_writer = try io.lockStderr(&buffer, null); - defer io.unlockStderr(); - const w = &stderr_writer.interface; - try ip.dumpGenericInstancesFallible(allocator, w); + const stderr = std.debug.lockStderr(&buffer); + defer std.debug.unlockStderr(); + const w = &stderr.file_writer.interface; + ip.dumpGenericInstancesFallible(allocator, w) catch return; } pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator, w: *Io.Writer) !void { diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 823015574b52ccd54c89dab55586a6dfc2f39bdf..b17b39cd8389dd84e86d42ea7a25f9b59f800952 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1682,18 +1682,18 @@ fn hashFileFallible(io: Io, dir: Io.Dir, hashed_file: *HashedFile) HashedFile.Er hasher.update(&.{ 0, 0 }); var file_header: FileHeader = .{}; while (true) { - const bytes_read = try file.read(&buf); + const bytes_read = try file.readPositional(io, &.{&buf}, file_size); if (bytes_read == 0) break; file_size += bytes_read; hasher.update(buf[0..bytes_read]); file_header.update(buf[0..bytes_read]); } if (file_header.isExecutable()) { - try setExecutable(file); + try setExecutable(io, file); } }, .link => { - const link_name = try dir.readLink(io, hashed_file.fs_path, &buf); + const link_name = buf[0..try dir.readLink(io, hashed_file.fs_path, &buf)]; if (fs.path.sep != canonical_sep) { // Package hashes are intended to be consistent across // platforms which means we must normalize path separators @@ -1711,12 +1711,9 @@ fn deleteFileFallible(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) DeletedFi try dir.deleteFile(io, deleted_file.fs_path); } -fn setExecutable(file: Io.File) !void { +fn setExecutable(io: Io, file: Io.File) !void { if (!Io.File.Permissions.has_executable_bit) return; - - const S = std.posix.S; - const mode = Io.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH; - try file.chmod(mode); + try file.setPermissions(io, .executable_file); } const DeletedFile = struct { @@ -1738,7 +1735,7 @@ const HashedFile = struct { const Error = Io.File.OpenError || - Io.File.Reader.Error || + Io.File.ReadPositionalError || Io.File.StatError || Io.File.SetPermissionsError || Io.Dir.ReadLinkError; diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig index 93a6e0a7687f5f28dd8a30b344dcaa223701eeb8..49de055b47b6b55a3119ce52065888af61d8b0f8 100644 --- a/src/codegen/aarch64/Select.zig +++ b/src/codegen/aarch64/Select.zig @@ -11280,7 +11280,7 @@ pub fn dumpValues(isel: *Select, which: enum { only_referenced, all }) void { errdefer |err| @panic(@errorName(err)); - const locked_stderr = std.debug.lockStderr(&.{}, null); + const locked_stderr = std.debug.lockStderr(&.{}); defer std.debug.unlockStderr(); const stderr = &locked_stderr.file_writer.interface; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4fc58c2c4b0d59e93d65e6a327dc5d1b29f35fd3..fca89ea4fcf126c34dea9f705987687ffc1e143a 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -965,10 +965,10 @@ pub const Object = struct { const context, const module = emit: { if (options.pre_ir_path) |path| { if (std.mem.eql(u8, path, "-")) { - o.builder.dump(); + o.builder.dump(io); } else { - o.builder.printToFilePath(Io.Dir.cwd(), path) catch |err| { - log.err("failed printing LLVM module to \"{s}\": {s}", .{ path, @errorName(err) }); + o.builder.printToFilePath(io, Io.Dir.cwd(), path) catch |err| { + log.err("failed printing LLVM module to \"{s}\": {t}", .{ path, err }); }; } } @@ -982,12 +982,12 @@ pub const Object = struct { if (options.pre_bc_path) |path| { var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| - return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) }); + return diags.fail("failed to create '{s}': {t}", .{ path, err }); defer file.close(io); const ptr: [*]const u8 = @ptrCast(bitcode.ptr); - file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err| - return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) }); + file.writeStreamingAll(io, ptr[0..(bitcode.len * 4)]) catch |err| + return diags.fail("failed to write to '{s}': {t}", .{ path, err }); } if (options.asm_path == null and options.bin_path == null and @@ -995,12 +995,12 @@ pub const Object = struct { if (options.post_bc_path) |path| { var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| - return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) }); + return diags.fail("failed to create '{s}': {t}", .{ path, err }); defer file.close(io); const ptr: [*]const u8 = @ptrCast(bitcode.ptr); - file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err| - return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) }); + file.writeStreamingAll(io, ptr[0..(bitcode.len * 4)]) catch |err| + return diags.fail("failed to write to '{s}': {t}", .{ path, err }); } if (!build_options.have_llvm or !comp.config.use_lib_llvm) { diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 8cf1dfa3033e5d157120deabd1b46b370b05cb18..c4b002f983ad66b7db3f8d1679ade064d169cd8b 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -345,6 +345,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { if (msg.kind == .@"fatal error" or msg.kind == .@"error") { msg.write(stderr.terminal(), true) catch |err| switch (err) { error.WriteFailed => return stderr.file_writer.err.?, + error.Unexpected => |e| return e, }; return error.AroPreprocessorFailed; } diff --git a/src/link.zig b/src/link.zig index 3b822b2e691f1a987a770ba20a2511f8204ad8cb..13306b90a4ca0e06d8401aa7a17f5a3e5fa3554d 100644 --- a/src/link.zig +++ b/src/link.zig @@ -900,10 +900,9 @@ pub const File = struct { } } - pub const FlushError = error{ + pub const FlushError = Io.Cancelable || Allocator.Error || error{ /// Indicates an error will be present in `Compilation.link_diags`. LinkFailure, - OutOfMemory, }; /// Commit pending changes and write headers. Takes into account final output mode. diff --git a/src/link/Elf.zig b/src/link/Elf.zig index a312138c044c6dd2767e7f5586f2e403622ee4e6..85f37f88ce8b55c984f6d1e5c97c689b80659d4b 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -748,9 +748,10 @@ pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std defer tracy.end(); const comp = self.base.comp; + const io = comp.io; const diags = &comp.link_diags; - if (comp.verbose_link) Compilation.dump_argv(self.dump_argv_list.items); + if (comp.verbose_link) try Compilation.dumpArgv(io, self.dump_argv_list.items); const sub_prog_node = prog_node.start("ELF Flush", 0); defer sub_prog_node.end(); @@ -758,7 +759,7 @@ pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std return flushInner(self, arena, tid) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, - else => |e| return diags.fail("ELF flush failed: {s}", .{@errorName(e)}), + else => |e| return diags.fail("ELF flush failed: {t}", .{e}), }; } diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 8ba91905a8e09fb1c246df3d801450df6a19c390..b2a0f6e396399307fddbd2e04ea7255461163f56 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1588,7 +1588,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi if (comp.verbose_link) { // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv[1..]); + try Compilation.dumpArgv(io, argv[1..]); } // If possible, we run LLD as a child process because it does not always diff --git a/src/link/MachO.zig b/src/link/MachO.zig index cd9674a390504b7ccec26dc28ada18e895293a72..b3c62db2dfecab9a57d931458fad4d1f4a5b712a 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -618,14 +618,16 @@ pub fn flush( }; const emit = self.base.emit; invalidateKernelCache(emit.root_dir.handle, emit.sub_path) catch |err| switch (err) { - else => |e| return diags.fail("failed to invalidate kernel cache: {s}", .{@errorName(e)}), + else => |e| return diags.fail("failed to invalidate kernel cache: {t}", .{e}), }; } } /// --verbose-link output fn dumpArgv(self: *MachO, comp: *Compilation) !void { - const gpa = self.base.comp.gpa; + const gpa = comp.gpa; + const io = comp.io; + var arena_allocator = std.heap.ArenaAllocator.init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); @@ -820,7 +822,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { if (comp.ubsan_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); } - Compilation.dump_argv(argv.items); + try Compilation.dumpArgv(io, argv.items); } /// TODO delete this, libsystem must be resolved when setting up the compilation pipeline diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index dca3dca503beb11992595de3cabcd9a541e34921..2986e27e242dfaff97ce0bc9334aed1e617cb6e4 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -476,8 +476,8 @@ pub const Node = extern struct { return n; }, .streaming, - .streaming_reading, - .positional_reading, + .streaming_simple, + .positional_simple, .failure, => { const dest = limit.slice(interface.unusedCapacitySlice()); diff --git a/src/link/Queue.zig b/src/link/Queue.zig index e8e7700695955ce6999efb771a1a8d292772ab94..caa2cc189816f0a0e4565eec8de66fdffcb059e3 100644 --- a/src/link/Queue.zig +++ b/src/link/Queue.zig @@ -175,6 +175,7 @@ fn runLinkTasks(q: *Queue, comp: *Compilation) void { lf.post_prelink = true; } else |err| switch (err) { error.OutOfMemory => comp.link_diags.setAllocFailure(), + error.Canceled => @panic("TODO"), error.LinkFailure => {}, } } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index a8140327e53540da002992ba7e19812c6553ef25..216a641db3bfdf96f6c41d1b79ecf956fa6dc21a 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3828,8 +3828,9 @@ pub fn flush( const comp = wasm.base.comp; const diags = &comp.link_diags; const gpa = comp.gpa; + const io = comp.io; - if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items); + if (comp.verbose_link) try Compilation.dumpArgv(io, wasm.dump_argv_list.items); if (wasm.base.zcu_object_basename) |raw| { const zcu_obj_path: Path = try comp.resolveEmitPathFlush(arena, .temp, raw); -- 2.54.0 From 54865e0483bf02bc1ace9f06c00071fce32534cc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Dec 2025 23:23:32 -0800 Subject: [PATCH 067/195] compiler: fix compilation when linking libc --- lib/compiler/test_runner.zig | 2 +- lib/std/Io/Threaded.zig | 4 ++-- lib/std/testing.zig | 15 ++++++++++----- src/Compilation.zig | 6 +++--- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 3d97944a7f600dba65f131bbbee0bf97a8017992..d16fc3ae82f83160aa60fff434b73f50427a4c73 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -224,7 +224,7 @@ fn mainTerminal() void { var skip_count: usize = 0; var fail_count: usize = 0; var fuzz_count: usize = 0; - const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(.{ + const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(runner_threaded_io.io(), .{ .root_name = "Test", .estimated_total_items = test_fn_list.len, }); diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ca10272b333cf917bba41afadb79ddbc05b26613..36118ca81879917b5b4c0d85ce6c609ef55b6672 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -6804,7 +6804,7 @@ fn fileWritePositional( try current_thread.beginSyscall(); while (true) { - const rc = pwritev_sym(file.handle, &iovecs, iovlen, @bitCast(offset)); + const rc = pwritev_sym(file.handle, &iovecs, @intCast(iovlen), @bitCast(offset)); switch (posix.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); @@ -6923,7 +6923,7 @@ fn fileWriteStreaming( try current_thread.beginSyscall(); while (true) { - const rc = posix.system.writev(file.handle, &iovecs, iovlen); + const rc = posix.system.writev(file.handle, &iovecs, @intCast(iovlen)); switch (posix.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 98fca2f2d5b21664dd09e8750184e2fcaca338d6..f3dd114af3c0c4a47ac7d1c707943b5876e4be91 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -370,7 +370,8 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const if (!backend_can_print) return error.TestExpectedEqual; if (io.lockStderr(&.{}, null)) |stderr| { defer io.unlockStderr(); - failEqualSlices(T, expected, actual, diff_index, &stderr.interface, stderr.mode) catch {}; + const w = &stderr.file_writer.interface; + failEqualSlices(T, expected, actual, diff_index, w, stderr.terminal_mode) catch {}; } else |_| {} return error.TestExpectedEqual; } @@ -475,13 +476,13 @@ fn SliceDiffer(comptime T: type) type { for (self.expected, 0..) |value, i| { const full_index = self.start_index + i; const diff = if (i < self.actual.len) !std.meta.eql(self.actual[i], value) else true; - if (diff) try t.setColor(writer, .red); + if (diff) try t.setColor(.red); if (@typeInfo(T) == .pointer) { try writer.print("[{}]{*}: {any}\n", .{ full_index, value, value }); } else { try writer.print("[{}]: {any}\n", .{ full_index, value }); } - if (diff) try t.setColor(writer, .reset); + if (diff) try t.setColor(.reset); } } }; @@ -538,10 +539,14 @@ const BytesDiffer = struct { } } + fn terminal(self: *const BytesDiffer, writer: *Io.Writer) Io.Terminal { + return .{ .writer = writer, .mode = self.terminal_mode }; + } + fn writeDiff(self: BytesDiffer, writer: *Io.Writer, comptime fmt: []const u8, args: anytype, diff: bool) !void { - if (diff) try self.file_writer_mode.setColor(writer, .red); + if (diff) try self.terminal(writer).setColor(.red); try writer.print(fmt, args); - if (diff) try self.file_writer_mode.setColor(writer, .reset); + if (diff) try self.terminal(writer).setColor(.reset); } }; diff --git a/src/Compilation.zig b/src/Compilation.zig index a78bf19e666a6e7027741dd7ca430c77f553ef2a..dc254a36c51f46d3d6df6d0e96cc929f8dfb6c2d 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -6270,7 +6270,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr } if (comp.verbose_cc) { - dumpArgv(io, argv.items); + try dumpArgv(io, argv.items); } const err = std.process.execv(arena, argv.items); @@ -6316,7 +6316,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr } if (comp.verbose_cc) { - dumpArgv(io, argv.items); + try dumpArgv(io, argv.items); } // Just to save disk space, we delete the files that are never needed again. @@ -6358,7 +6358,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); - const term = child.wait() catch |err| { + const term = child.wait(io) catch |err| { return comp.failCObj(c_object, "failed to spawn zig clang {s}: {s}", .{ argv.items[0], @errorName(err) }); }; -- 2.54.0 From 1264469a41e89e72635c3b60b4cd3dca17876964 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 09:50:07 -0800 Subject: [PATCH 068/195] compiler: fix build from source on macOS --- lib/std/zig/LibCInstallation.zig | 4 ++-- lib/std/zig/system/NativePaths.zig | 13 +++++++------ src/link/MachO.zig | 6 +++--- src/main.zig | 5 ++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index c1fc8fccaa511a29122cfca595ac8ce2abe81034..83ed23be042fe19c7473ae1fc3bf5d32a3dac43c 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -177,9 +177,9 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib var self: LibCInstallation = .{}; if (is_darwin and args.target.os.tag.isDarwin()) { - if (!std.zig.system.darwin.isSdkInstalled(gpa)) + if (!std.zig.system.darwin.isSdkInstalled(gpa, io)) return error.DarwinSdkNotFound; - const sdk = std.zig.system.darwin.getSdk(gpa, args.target) orelse + const sdk = std.zig.system.darwin.getSdk(gpa, io, args.target) orelse return error.DarwinSdkNotFound; defer gpa.free(sdk); diff --git a/lib/std/zig/system/NativePaths.zig b/lib/std/zig/system/NativePaths.zig index eea12ee2721bfea18be6381fa1cee1a5391aabd6..bf66f912ae360e1798a8981a21c4a68ccc2181f0 100644 --- a/lib/std/zig/system/NativePaths.zig +++ b/lib/std/zig/system/NativePaths.zig @@ -1,11 +1,12 @@ -const std = @import("../../std.zig"); +const NativePaths = @This(); const builtin = @import("builtin"); + +const std = @import("../../std.zig"); +const Io = std.Io; const Allocator = std.mem.Allocator; const process = std.process; const mem = std.mem; -const NativePaths = @This(); - arena: Allocator, include_dirs: std.ArrayList([]const u8) = .empty, lib_dirs: std.ArrayList([]const u8) = .empty, @@ -13,7 +14,7 @@ framework_dirs: std.ArrayList([]const u8) = .empty, rpaths: std.ArrayList([]const u8) = .empty, warnings: std.ArrayList([]const u8) = .empty, -pub fn detect(arena: Allocator, native_target: *const std.Target) !NativePaths { +pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !NativePaths { var self: NativePaths = .{ .arena = arena }; var is_nix = false; 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 { // TODO: consider also adding macports paths if (builtin.target.os.tag.isDarwin()) { - if (std.zig.system.darwin.isSdkInstalled(arena)) sdk: { - const sdk = std.zig.system.darwin.getSdk(arena, native_target) orelse break :sdk; + if (std.zig.system.darwin.isSdkInstalled(arena, io)) sdk: { + const sdk = std.zig.system.darwin.getSdk(arena, io, native_target) orelse break :sdk; try self.addLibDir(try std.fs.path.join(arena, &.{ sdk, "usr/lib" })); try self.addFrameworkDir(try std.fs.path.join(arena, &.{ sdk, "System/Library/Frameworks" })); try self.addIncludeDir(try std.fs.path.join(arena, &.{ sdk, "usr/include" })); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index b3c62db2dfecab9a57d931458fad4d1f4a5b712a..513c857dbf62bcbc051dec536d96c5ebeef10982 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -617,7 +617,7 @@ pub fn flush( else => |e| return diags.fail("failed to write code signature: {s}", .{@errorName(e)}), }; const emit = self.base.emit; - invalidateKernelCache(emit.root_dir.handle, emit.sub_path) catch |err| switch (err) { + invalidateKernelCache(io, emit.root_dir.handle, emit.sub_path) catch |err| switch (err) { else => |e| return diags.fail("failed to invalidate kernel cache: {t}", .{e}), }; } @@ -3621,11 +3621,11 @@ pub fn getTarget(self: *const MachO) *const std.Target { /// into a new inode, remove the original file, and rename the copy to match /// the original file. This is super messy, but there doesn't seem any other /// way to please the XNU. -pub fn invalidateKernelCache(dir: Io.Dir, sub_path: []const u8) !void { +pub fn invalidateKernelCache(io: Io, dir: Io.Dir, sub_path: []const u8) !void { const tracy = trace(@src()); defer tracy.end(); if (builtin.target.os.tag.isDarwin() and builtin.target.cpu.arch == .aarch64) { - try dir.copyFile(sub_path, dir, sub_path, .{}); + try dir.copyFile(sub_path, dir, sub_path, io, .{}); } } diff --git a/src/main.zig b/src/main.zig index 8e30febb8195402164ab64f6ccb9a27b4882fb88..fb2a8cfe0fc0a82b885402763734f8c7efeef151 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3985,9 +3985,8 @@ fn createModule( resolved_target.is_native_os and resolved_target.is_native_abi and create_module.want_native_include_dirs) { - var paths = std.zig.system.NativePaths.detect(arena, target) catch |err| { - fatal("unable to detect native system paths: {s}", .{@errorName(err)}); - }; + var paths = std.zig.system.NativePaths.detect(arena, io, target) catch |err| + fatal("unable to detect native system paths: {t}", .{err}); for (paths.warnings.items) |warning| { warn("{s}", .{warning}); } -- 2.54.0 From d72983da44b7ebe967868ba5236a2c4b05e84510 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Tue, 16 Dec 2025 20:02:05 -0800 Subject: [PATCH 069/195] File.OpenFlags: Add `allow_directory` and default it to true This is one way of addressing/closing https://github.com/ziglang/zig/issues/16738 Previously, there was a mismatch between the default behaviors on Windows vs other platforms, where Windows was implicitly using .NON_DIRECTORY_FILE for its `openFile` implementation which caused `error.IsDir` when opening a directory, while on other platforms there is no equivalent flag for the `open` syscall. This meant that `openFile` on a path of a directory would fail on Windows but succeed on other platforms. Adding `allow_directory` to `File.OpenFlags` serves two purposes: 1. It provides a cross-platform way to get the `.NON_DIRECTORY_FILE` behavior in the most efficient available way for the platform (on Windows, no extra syscalls are required, on other systems, an extra `fstat` is required) 2. It allows `statFile` to be implemented on top of `openFile` on Windows while still allowing `statFile` to work on directory paths. Before this commit, `statFile` on a directory path on Windows failed with `error.IsDir` Note: The second purpose could have been addressed in different ways (bespoke call to NtCreateFile in the `statFile` implementation to avoid passing `NON_DIRECTORY_FILE`, or just never pass `NON_DIRECTORY_FILE` in the `openFile` implementation), so the first purpose is the more relevant/motivating force behind this change. The default being `true` is intended to cut down on the number of syscalls as much as possible when using the default flags. --- lib/std/Io/File.zig | 16 +++++++++++- lib/std/Io/Threaded.zig | 58 +++++++++++++++++++++++++---------------- lib/std/fs/test.zig | 29 ++++++++++++++++----- lib/std/os/windows.zig | 16 +++++------- 4 files changed, 81 insertions(+), 38 deletions(-) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index ca6929b8ca3afa2179b95542aff1316859935560..3658252499ec3ecabe3f9a2e2b4a0bd863995488 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -100,6 +100,18 @@ pub const Lock = enum { pub const OpenFlags = struct { mode: OpenMode = .read_only, + /// Determines the behavior when opening a path that refers to a directory. + /// If set to true, directories may be opened, but `error.IsDir` is still + /// possible in certain scenarios, e.g. attempting to open a directory with + /// write permissions. + /// If set to false, `error.IsDir` will always be returned when opening a directory. + /// + /// When set to false: + /// * On Windows, the behavior is implemented without any extra syscalls. + /// * On other operating systems, the behavior is implemented with an additional + /// `fstat` syscall. + allow_directory: bool = true, + /// Open the file with an advisory lock to coordinate with other processes /// accessing it at the same time. An exclusive lock will prevent other /// processes from acquiring a lock. A shared lock will prevent other @@ -226,7 +238,9 @@ pub const OpenError = error{ /// The file is too large to be opened. This error is unreachable /// for 64-bit targets, as well as when opening directories. FileTooBig, - /// The path refers to directory but the `DIRECTORY` flag was not provided. + /// Either: + /// * The path refers to a directory and write permissions were requested. + /// * The path refers to a directory and `allow_directory` was set to false. IsDir, /// A new path cannot be created because the device has no room for the new file. /// This error is only reachable when the `CREAT` flag is provided. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 36118ca81879917b5b4c0d85ce6c609ef55b6672..e38c56ac1fc4ed82c7775b3a75895f541689fce1 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1621,14 +1621,8 @@ fn dirMakePath( // stat the file and return an error if it's not a directory // this is important because otherwise a dangling symlink // could cause an infinite loop - check_dir: { - // workaround for windows, see https://github.com/ziglang/zig/issues/16738 - const fstat = dirStatFile(t, dir, component.path, .{}) catch |stat_err| switch (stat_err) { - error.IsDir => break :check_dir, - else => |e| return e, - }; - if (fstat.kind != .directory) return error.NotDir; - } + const fstat = dirStatFile(t, dir, component.path, .{}); + if (fstat.kind != .directory) return error.NotDir; }, error.FileNotFound => |e| { component = it.previous() orelse return e; @@ -1750,16 +1744,10 @@ fn dirMakeOpenPathWindows( // stat the file and return an error if it's not a directory // this is important because otherwise a dangling symlink // could cause an infinite loop - check_dir: { - // workaround for windows, see https://github.com/ziglang/zig/issues/16738 - const fstat = dirStatFileWindows(t, dir, component.path, .{ - .follow_symlinks = options.follow_symlinks, - }) catch |stat_err| switch (stat_err) { - error.IsDir => break :check_dir, - else => |e| return e, - }; - if (fstat.kind != .directory) return error.NotDir; - } + const fstat = dirStatFileWindows(t, dir, component.path, .{ + .follow_symlinks = options.follow_symlinks, + }); + if (fstat.kind != .directory) return error.NotDir; component = it.next().?; continue; @@ -2791,6 +2779,18 @@ fn dirOpenFilePosix( }; errdefer posix.close(fd); + if (!flags.allow_directory) { + const is_dir = is_dir: { + const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) { + // The directory-ness is either unknown or unknowable + error.Streaming => break :is_dir false, + else => |e| return e, + }; + break :is_dir stat.kind == .directory; + }; + if (is_dir) return error.IsDir; + } + if (have_flock and !have_flock_open_flags and flags.lock != .none) { const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0; const lock_flags = switch (flags.lock) { @@ -2936,7 +2936,7 @@ pub fn dirOpenFileWtf16( .OPEN, .{ .IO = if (flags.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, - .NON_DIRECTORY_FILE = true, + .NON_DIRECTORY_FILE = !flags.allow_directory, .OPEN_REPARSE_POINT = !flags.follow_symlinks, }, null, @@ -3052,9 +3052,8 @@ fn dirOpenFileWasi( while (true) { switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { .SUCCESS => { - errdefer posix.close(fd); current_thread.endSyscall(); - return .{ .handle = fd }; + break; }, .INTR => { try current_thread.checkCancel(); @@ -3088,6 +3087,21 @@ fn dirOpenFileWasi( }, } } + errdefer posix.close(fd); + + if (!flags.allow_directory) { + const is_dir = is_dir: { + const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) { + // The directory-ness is either unknown or unknowable + error.Streaming => break :is_dir false, + else => |e| return e, + }; + break :is_dir stat.kind == .directory; + }; + if (is_dir) return error.IsDir; + } + + return .{ .handle = fd }; } const dirOpenDir = switch (native_os) { @@ -4577,7 +4591,7 @@ fn dirSymLinkWindows( .access_mask = w.SYNCHRONIZE | w.GENERIC_READ | w.GENERIC_WRITE, .dir = dir, .creation = w.FILE_CREATE, - .filter = if (flags.is_directory) .dir_only else .file_only, + .filter = if (flags.is_directory) .dir_only else .non_directory_only, }) catch |err| switch (err) { error.IsDir => return error.PathAlreadyExists, error.NotDir => return error.Unexpected, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 53612e4f960a8442ebdf3f18f7af015f7fd9aef4..0a81efbca5ab8f465b598cbf09141557341df945 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -750,14 +750,26 @@ test "Dir.statFile" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { const io = ctx.io; - const test_file_name = try ctx.transformPath("test_file"); + { + const test_file_name = try ctx.transformPath("test_file"); - try expectError(error.FileNotFound, ctx.dir.statFile(io, test_file_name, .{})); + try expectError(error.FileNotFound, ctx.dir.statFile(io, test_file_name, .{})); - try ctx.dir.writeFile(io, .{ .sub_path = test_file_name, .data = "" }); + try ctx.dir.writeFile(io, .{ .sub_path = test_file_name, .data = "" }); - const stat = try ctx.dir.statFile(io, test_file_name, .{}); - try expectEqual(File.Kind.file, stat.kind); + const stat = try ctx.dir.statFile(io, test_file_name, .{}); + try expectEqual(.file, stat.kind); + } + { + const test_dir_name = try ctx.transformPath("test_dir"); + + try expectError(error.FileNotFound, ctx.dir.statFile(io, test_dir_name, .{})); + + try ctx.dir.makeDir(io, test_dir_name); + + const stat = try ctx.dir.statFile(io, test_dir_name, .{}); + try expectEqual(.directory, stat.kind); + } } }.impl); } @@ -840,10 +852,15 @@ test "file operations on directories" { handle.close(io); } else { // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms. - // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732 try expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .mode = .read_write })); } + { + const handle = try ctx.dir.openFile(io, test_dir_name, .{ .allow_directory = true, .mode = .read_only }); + handle.close(io); + } + try expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .allow_directory = false, .mode = .read_only })); + if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { try expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{})); try expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name)); diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index ba5282256fd97eebf6257a8ee68b8be006139a7a..7a2b2aaf50a9ac10740a9e86523322f399f0ccb5 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2310,17 +2310,15 @@ pub const OpenFileOptions = struct { sa: ?*SECURITY_ATTRIBUTES = null, share_access: FILE.SHARE = .VALID_FLAGS, creation: FILE.CREATE_DISPOSITION, - /// If true, tries to open path as a directory. - /// Defaults to false. - filter: Filter = .file_only, + filter: Filter = .non_directory_only, /// If false, tries to open path as a reparse point without dereferencing it. /// Defaults to true. follow_symlinks: bool = true, pub const Filter = enum { /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory. - file_only, - /// Causes `OpenFile` to return `error.NotDir` if the opened handle would be a file. + non_directory_only, + /// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory. dir_only, /// `OpenFile` does not discriminate between opening files and directories. any, @@ -2328,10 +2326,10 @@ pub const OpenFileOptions = struct { }; pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE { - if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .file_only) { + if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .non_directory_only) { return error.IsDir; } - if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .file_only) { + if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .non_directory_only) { return error.IsDir; } @@ -2366,7 +2364,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN options.creation, .{ .DIRECTORY_FILE = options.filter == .dir_only, - .NON_DIRECTORY_FILE = options.filter == .file_only, + .NON_DIRECTORY_FILE = options.filter == .non_directory_only, .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, .OPEN_REPARSE_POINT = !options.follow_symlinks, }, @@ -3040,7 +3038,7 @@ pub fn CreateSymbolicLink( }, .dir = dir, .creation = .CREATE, - .filter = if (is_directory) .dir_only else .file_only, + .filter = if (is_directory) .dir_only else .non_directory_only, }) catch |err| switch (err) { error.IsDir => return error.PathAlreadyExists, error.NotDir => return error.Unexpected, -- 2.54.0 From a2416c685a83788780fec1c379008a2d795f7bd2 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 18 Dec 2025 02:00:34 -0800 Subject: [PATCH 070/195] Implement dirReadWindows --- lib/std/Io/Dir.zig | 5 +- lib/std/Io/Threaded.zig | 145 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 145 insertions(+), 5 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 12fcea6576769a9f569915d1ea3439e1f6f56505..970cddc82e488e82539dc2f3ff75f9f8b7aee2ea 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -101,7 +101,10 @@ pub const Reader = struct { /// Fill position of `buffer`. end: usize, - pub const min_buffer_len = 32; + pub const min_buffer_len = switch (native_os) { + .windows => std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)), + else => 32, // TODO: what is this based on? + }; pub const State = enum { /// Indicates the next call to `read` should rewind and start over the diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e38c56ac1fc4ed82c7775b3a75895f541689fce1..fc66345785502d925e3d85a15437198282394896 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3674,10 +3674,147 @@ fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir } fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { - _ = userdata; - _ = dr; - _ = buffer; - @panic("TODO"); + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + const w = windows; + + // We want to be able to use the `dr.buffer` for both the NtQueryDirectoryFile call (which + // returns WTF-16 names) *and* as a buffer for storing those WTF-16 names as WTF-8 to be able + // to return them in `Dir.Entry.name`. However, the problem that needs to be overcome in order to do + // that is that each WTF-16 code unit can be encoded as a maximum of 3 WTF-8 bytes, which means + // that it's not guaranteed that the memory used for the WTF-16 name will be sufficient + // for the WTF-8 encoding of the same name (for example, € is encoded as one WTF-16 code unit, + // [2 bytes] but encoded in WTF-8 as 3 bytes). + // + // The approach taken here is to "reserve" enough space in the `dr.buffer` to ensure that + // at least one entry with the maximum possible WTF-8 name length can be stored without clobbering + // any entries that follow it. That is, we determine how much space is needed to allow that, + // and then only provide the remaining portion of `dr.buffer` to the NtQueryDirectoryFile + // call. The WTF-16 names can then be safely converted using the full `dr.buffer` slice, making + // sure that each name can only potentially overwrite the data of its own entry. + // + // The worst case, where an entry's name is both the maximum length of a component and + // made up entirely of code points that are encoded as one WTF-16 code unit/three WTF-8 bytes, + // would therefore look like the diagram below, and only one entry would be able to be returned: + // + // | reserved | remaining unreserved buffer | + // | entry 1 | entry 2 | ... | + // | wtf-8 name of entry 1 | + // + // However, in the average case we will be able to store more than one WTF-8 name at a time in the + // available buffer and therefore we will be able to populate more than one `Dir.Entry` at a time. + // That might look something like this (where name 1, name 2, etc are the converted WTF-8 names): + // + // | reserved | remaining unreserved buffer | + // | entry 1 | entry 2 | ... | + // | name 1 | name 2 | name 3 | name 4 | ... | + // + // Note: More than the minimum amount of space could be reserved to make the "worst case" + // less likely, but since the worst-case also requires a maximum length component to matter, + // it's unlikely for it to become a problem in normal scenarios even if all names on the filesystem + // are made up of non-ASCII characters that have the "one WTF-16 code unit <-> three WTF-8 bytes" + // property (e.g. code points >= U+0800 and <= U+FFFF), as it's unlikely for a significant + // number of components to be maximum length. + + // We need `3 * NAME_MAX` bytes to store a max-length component as WTF-8 safely. + // Because needing to store a max-length component depends on a `FileName` *with* the maximum + // component length, we know that the corresponding populated `FILE_BOTH_DIR_INFORMATION` will + // be of size `@sizeOf(w.FILE_BOTH_DIR_INFORMATION) + 2 * NAME_MAX` bytes, so we only need to + // reserve enough to get us to up to having `3 * NAME_MAX` bytes available when taking into account + // that we have the ability to write over top of the reserved memory + the full footprint of that + // particular `FILE_BOTH_DIR_INFORMATION`. + const reserve_needed = w.NAME_MAX - @sizeOf(w.FILE_BOTH_DIR_INFORMATION); + const unreserved_start = std.mem.alignForward(usize, reserve_needed, @alignOf(usize)); + const unreserved_buffer = dr.buffer[unreserved_start..]; + // This is enforced by `Dir.Reader` + assert(unreserved_buffer.len >= @sizeOf(w.FILE_BOTH_DIR_INFORMATION) + w.NAME_MAX * 2); + + var name_index: usize = 0; + var buffer_index: usize = 0; + while (buffer.len - buffer_index != 0) { + if (dr.end - dr.index == 0) { + // Refill the buffer, unless we've already created references to + // buffered data. + if (buffer_index != 0) break; + + try current_thread.checkCancel(); + var io_status_block: w.IO_STATUS_BLOCK = undefined; + const rc = w.ntdll.NtQueryDirectoryFile( + dr.dir.handle, + null, + null, + null, + &io_status_block, + unreserved_buffer.ptr, + unreserved_buffer.len, + .BothDirectory, + w.FALSE, + null, + @intFromBool(dr.state == .reset), + ); + dr.state = .reading; + if (io_status_block.Information == 0) { + dr.state = .finished; + return 0; + } + dr.index = 0; + dr.end = io_status_block.Information; + switch (rc) { + .SUCCESS => {}, + .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability + else => return w.unexpectedStatus(rc), + } + } + + // While the official API docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly + // this may not always be the case (e.g. due to faulty VM/sandboxing tools) + const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&unreserved_buffer[dr.index])); + const backtrack_index = dr.index; + if (dir_info.NextEntryOffset != 0) { + dr.index += dir_info.NextEntryOffset; + } else { + dr.index = dr.end; + } + + const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2]; + + if (std.mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or std.mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' })) { + continue; + } + + // Read any relevant information from the `dir_info` now since it's possible the WTF-8 + // name will overwrite it. + const kind: File.Kind = blk: { + const attrs = dir_info.FileAttributes; + if (attrs.REPARSE_POINT) break :blk .sym_link; + if (attrs.DIRECTORY) break :blk .directory; + break :blk .file; + }; + const inode: File.INode = dir_info.FileIndex; + + // If there's no more space for WTF-8 names without bleeding over into + // the remaining unprocessed entries, then backtrack and return what we have so far. + if (name_index + std.unicode.calcWtf8Len(name_wtf16le) > unreserved_start + dr.index) { + // We should always be able to fit at least one entry into the buffer no matter what + std.debug.assert(buffer_index != 0); + dr.index = backtrack_index; + break; + } + + const name_buf = dr.buffer[name_index..]; + const name_wtf8_len = std.unicode.wtf16LeToWtf8(name_buf, name_wtf16le); + const name_wtf8 = name_buf[0..name_wtf8_len]; + name_index += name_wtf8_len; + + buffer[buffer_index] = .{ + .name = name_wtf8, + .kind = kind, + .inode = inode, + }; + buffer_index += 1; + } + + return buffer_index; } fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { -- 2.54.0 From 446c145ca86b014d6743f5666e9ee1d671b56045 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 11:31:23 -0800 Subject: [PATCH 071/195] std.Io.Threaded: fix compilation errors on posix --- lib/std/Io/Dir.zig | 15 ++++++++++----- lib/std/Io/Threaded.zig | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 970cddc82e488e82539dc2f3ff75f9f8b7aee2ea..0bdaf369c1403e161f0fec767cbd5f02d8898ba6 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -101,10 +101,8 @@ pub const Reader = struct { /// Fill position of `buffer`. end: usize, - pub const min_buffer_len = switch (native_os) { - .windows => std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)), - else => 32, // TODO: what is this based on? - }; + /// A length for `buffer` that allows all implementations to function. + pub const min_buffer_len = std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)); pub const State = enum { /// Indicates the next call to `read` should rewind and start over the @@ -120,6 +118,7 @@ pub const Reader = struct { SystemResources, } || Io.UnexpectedError || Io.Cancelable; + /// Asserts that `buffer` has length at least `min_buffer_len`. pub fn init(dir: Dir, buffer: []align(@alignOf(usize)) u8) Reader { assert(buffer.len >= min_buffer_len); return .{ @@ -164,7 +163,13 @@ pub const Reader = struct { /// see `Walker`. pub const Iterator = struct { reader: Reader, - reader_buffer: [2048]u8 align(@alignOf(usize)), + reader_buffer: [reader_buffer_len]u8 align(@alignOf(usize)), + + pub const reader_buffer_len = 2048; + + comptime { + assert(reader_buffer_len >= Reader.min_buffer_len); + } pub const Error = Reader.Error; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index fc66345785502d925e3d85a15437198282394896..10f6c2d507674c4a821bca2bc40c511b6b9bc4c3 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1621,7 +1621,7 @@ fn dirMakePath( // stat the file and return an error if it's not a directory // this is important because otherwise a dangling symlink // could cause an infinite loop - const fstat = dirStatFile(t, dir, component.path, .{}); + const fstat = try dirStatFile(t, dir, component.path, .{}); if (fstat.kind != .directory) return error.NotDir; }, error.FileNotFound => |e| { @@ -3796,7 +3796,7 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D // the remaining unprocessed entries, then backtrack and return what we have so far. if (name_index + std.unicode.calcWtf8Len(name_wtf16le) > unreserved_start + dr.index) { // We should always be able to fit at least one entry into the buffer no matter what - std.debug.assert(buffer_index != 0); + assert(buffer_index != 0); dr.index = backtrack_index; break; } -- 2.54.0 From f3723b42e1f05d14479d6f5507943cd1905e0fcf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 13:49:56 -0800 Subject: [PATCH 072/195] std.Io: add unimplemented hard link API to File and Dir --- lib/std/Io/Dir.zig | 33 +++++++++++++++++++++++++++++ lib/std/Io/File.zig | 6 +++++- lib/std/Io/Threaded.zig | 46 +++++++++++++++++++++++++++-------------- lib/std/c.zig | 2 +- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 0bdaf369c1403e161f0fec767cbd5f02d8898ba6..31cb59ae29da703e5f14089b84f613c9ad1e353f 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -989,6 +989,39 @@ pub fn renameAbsolute(io: Io, old_path: []const u8, new_path: []const u8) Rename return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path); } +pub const HardLinkOptions = struct { + follow_symlinks: bool = true, +}; + +pub const HardLinkError = error{ + AccessDenied, + PermissionDenied, + DiskQuota, + PathAlreadyExists, + HardwareFailure, + /// Either the OS or the filesystem does not support hard links. + OperationUnsupported, + SymLinkLoop, + LinkQuotaExceeded, + FileNotFound, + SystemResources, + NoSpaceLeft, + ReadOnlyFileSystem, + NotSameFileSystem, + NotDir, +} || Io.Cancelable || PathNameError || Io.UnexpectedError; + +pub fn hardLink( + old_dir: Dir, + old_sub_path: []const u8, + new_dir: Dir, + new_sub_path: []const u8, + io: Io, + options: HardLinkOptions, +) HardLinkError!void { + return io.vtable.dirHardLink(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path, options); +} + /// Use with `symLink`, `symLinkAtomic`, and `symLinkAbsolute` to /// specify whether the symlink will point to a file or a directory. This value /// is ignored on all hosts except Windows where creating symlinks to different diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 3658252499ec3ecabe3f9a2e2b4a0bd863995488..e5bf5849e9ea64fa0a990645a7b6a7b3164b80d9 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -17,6 +17,7 @@ pub const Atomic = @import("File/Atomic.zig"); pub const Handle = std.posix.fd_t; pub const INode = std.posix.ino_t; +pub const NLink = std.posix.nlink_t; pub const Uid = std.posix.uid_t; pub const Gid = std.posix.gid_t; @@ -47,6 +48,7 @@ pub const Stat = struct { /// The FileIndex on Windows is similar. It is a number for a file that /// is unique to each filesystem. inode: INode, + nlink: NLink, size: u64, permissions: Permissions, kind: Kind, @@ -101,9 +103,11 @@ pub const OpenFlags = struct { mode: OpenMode = .read_only, /// Determines the behavior when opening a path that refers to a directory. + /// /// If set to true, directories may be opened, but `error.IsDir` is still /// possible in certain scenarios, e.g. attempting to open a directory with /// write permissions. + /// /// If set to false, `error.IsDir` will always be returned when opening a directory. /// /// When set to false: @@ -289,7 +293,7 @@ pub fn sync(file: File, io: Io) SyncError!void { return io.vtable.fileSync(io.userdata, file); } -/// Test whether the file refers to a terminal. +/// Test whether the file refers to a terminal (similar to libc "isatty"). /// /// See also: /// * `enableAnsiEscapeCodes` diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 10f6c2d507674c4a821bca2bc40c511b6b9bc4c3..da15a70903b0250f8f056c7e48b72409f20bdeaa 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1829,19 +1829,21 @@ fn dirStatFileLinux( dir.handle, sub_path_posix, flags, - .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true }, + .{ + .TYPE = true, + .MODE = true, + .ATIME = true, + .MTIME = true, + .CTIME = true, + .INO = true, + .SIZE = true, + .NLINK = true, + }, &statx, ); switch (sys.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); - assert(statx.mask.TYPE); - assert(statx.mask.MODE); - assert(statx.mask.ATIME); - assert(statx.mask.MTIME); - assert(statx.mask.CTIME); - assert(statx.mask.INO); - assert(statx.mask.SIZE); return statFromLinux(&statx); }, .INTR => { @@ -2082,19 +2084,21 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { file.handle, "", linux.AT.EMPTY_PATH, - .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true }, + .{ + .TYPE = true, + .MODE = true, + .ATIME = true, + .MTIME = true, + .CTIME = true, + .INO = true, + .SIZE = true, + .NLINK = true, + }, &statx, ); switch (sys.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); - assert(statx.mask.TYPE); - assert(statx.mask.MODE); - assert(statx.mask.ATIME); - assert(statx.mask.MTIME); - assert(statx.mask.CTIME); - assert(statx.mask.INO); - assert(statx.mask.SIZE); return statFromLinux(&statx); }, .INTR => { @@ -10499,11 +10503,20 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t { } fn statFromLinux(stx: *const std.os.linux.Statx) File.Stat { + assert(stx.mask.TYPE); + assert(stx.mask.MODE); + assert(stx.mask.ATIME); + assert(stx.mask.MTIME); + assert(stx.mask.CTIME); + assert(stx.mask.INO); + assert(stx.mask.SIZE); + assert(stx.mask.NLINK); const atime = stx.atime; const mtime = stx.mtime; const ctime = stx.ctime; return .{ .inode = stx.ino, + .nlink = stx.nlink, .size = stx.size, .permissions = .fromMode(stx.mode), .kind = switch (stx.mode & std.os.linux.S.IFMT) { @@ -10528,6 +10541,7 @@ fn statFromPosix(st: *const posix.Stat) File.Stat { const ctime = st.ctime(); return .{ .inode = st.ino, + .nlink = st.nlink, .size = @bitCast(st.size), .permissions = .fromMode(st.mode), .kind = k: { diff --git a/lib/std/c.zig b/lib/std/c.zig index 30a3ac132c44a258e8544afd5fa8ef9dde552270..0627882e3c37a5d4d7f7c85de1753931ce315eff 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -162,7 +162,7 @@ pub const nlink_t = switch (native_os) { .freebsd, .serenity => u64, .openbsd, .netbsd, .illumos => u32, .haiku => i32, - else => void, + else => u0, }; pub const uid_t = switch (native_os) { -- 2.54.0 From b4bfd501aedb4abe4257b7f54a225a9654c4e08e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 13:50:39 -0800 Subject: [PATCH 073/195] std: move some tests from posix to fs --- lib/std/fs/test.zig | 521 ++++++++++++++++++++++++++---------- lib/std/posix.zig | 157 ----------- lib/std/posix/test.zig | 422 ++++------------------------- lib/std/zig/parser_test.zig | 13 +- 4 files changed, 443 insertions(+), 670 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 0a81efbca5ab8f465b598cbf09141557341df945..87d28b2ad02064dbffa106f3ca26c1df50d7ee12 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -3,7 +3,6 @@ const native_os = builtin.os.tag; const std = @import("../std.zig"); const Io = std.Io; -const fs = std.fs; const mem = std.mem; const wasi = std.os.wasi; const windows = std.os.windows; @@ -14,11 +13,11 @@ const SymLinkFlags = std.Io.Dir.SymLinkFlags; const testing = std.testing; const expect = std.testing.expect; -const expectError = std.testing.expectError; const expectEqual = std.testing.expectEqual; -const tmpDir = std.testing.tmpDir; -const expectEqualStrings = std.testing.expectEqualStrings; const expectEqualSlices = std.testing.expectEqualSlices; +const expectEqualStrings = std.testing.expectEqualStrings; +const expectError = std.testing.expectError; +const tmpDir = std.testing.tmpDir; const PathType = enum { relative, @@ -33,7 +32,7 @@ const PathType = enum { }; } - pub const TransformError = Io.Dir.RealPathError || error{OutOfMemory}; + pub const TransformError = Dir.RealPathError || error{OutOfMemory}; pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8; pub fn getTransformFn(comptime path_type: PathType) TransformFn { @@ -49,24 +48,24 @@ const PathType = enum { fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { // The final path may not actually exist which would cause realpath to fail. // So instead, we get the path of the dir and join it with the relative path. - var fd_path_buf: [fs.max_path_bytes]u8 = undefined; + var fd_path_buf: [Dir.max_path_bytes]u8 = undefined; const dir_path = try std.os.getFdPath(dir.handle, &fd_path_buf); - return fs.path.joinZ(allocator, &.{ dir_path, relative_path }); + return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }); } }.transform, .unc => return struct { fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { // Any drive absolute path (C:\foo) can be converted into a UNC path by // using '127.0.0.1' as the server name and '$' as the share name. - var fd_path_buf: [fs.max_path_bytes]u8 = undefined; + var fd_path_buf: [Dir.max_path_bytes]u8 = undefined; const dir_path = try std.os.getFdPath(dir.handle, &fd_path_buf); const windows_path_type = windows.getWin32PathType(u8, dir_path); switch (windows_path_type) { - .unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }), + .unc_absolute => return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }), .drive_absolute => { // `C:\<...>` -> `\\127.0.0.1\C$\<...>` const prepended = "\\\\127.0.0.1\\"; - var path = try fs.path.joinZ(allocator, &.{ prepended, dir_path, relative_path }); + var path = try Dir.path.joinZ(allocator, &.{ prepended, dir_path, relative_path }); path[prepended.len + 1] = '$'; return path; }, @@ -84,7 +83,7 @@ const TestContext = struct { path_sep: u8, arena: ArenaAllocator, tmp: testing.TmpDir, - dir: Io.Dir, + dir: Dir, transform_fn: *const PathType.TransformFn, pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext { @@ -154,7 +153,7 @@ fn testWithAllSupportedPathTypes(test_func: anytype) !void { fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep: u8, test_func: anytype) !void { if (!(comptime path_type.isSupported(builtin.os))) return; - if (!(comptime fs.path.isSep(path_sep))) return; + if (!(comptime Dir.path.isSep(path_sep))) return; var ctx = TestContext.init(path_type, path_sep, testing.allocator, path_type.getTransformFn()); defer ctx.deinit(); @@ -174,8 +173,8 @@ fn setupSymlink(io: Io, dir: Dir, target: []const u8, link: []const u8, flags: S // For use in test setup. If the symlink creation fails on Windows with // AccessDenied, then make the test failure silent (it is not a Zig failure). -fn setupSymlinkAbsolute(target: []const u8, link: []const u8, flags: SymLinkFlags) !void { - return fs.symLinkAbsolute(target, link, flags) catch |err| switch (err) { +fn setupSymlinkAbsolute(io: Io, target: []const u8, link: []const u8, flags: SymLinkFlags) !void { + return Dir.symLinkAbsolute(io, target, link, flags) catch |err| switch (err) { error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err, else => return err, }; @@ -211,7 +210,7 @@ test "Dir.readLink" { } // test 3: relative path symlink - const parent_file = ".." ++ fs.path.sep_str ++ "target.txt"; + const parent_file = ".." ++ Dir.path.sep_str ++ "target.txt"; const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file); var subdir = try ctx.dir.makeOpenPath(io, "subdir", .{}); defer subdir.close(io); @@ -251,7 +250,7 @@ test "Dir.readLink on non-symlinks" { } fn testReadLink(io: Io, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { - var buffer: [fs.max_path_bytes]u8 = undefined; + var buffer: [Dir.max_path_bytes]u8 = undefined; const actual = try dir.readLink(io, symlink_path, &buffer); try expectEqualStrings(target_path, actual); } @@ -267,9 +266,9 @@ fn testReadLinkW(allocator: mem.Allocator, dir: Dir, target_path: []const u8, sy try expectEqualSlices(u16, target_path_w, actual); } -fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { - var buffer: [fs.max_path_bytes]u8 = undefined; - const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]); +fn testReadLinkAbsolute(io: Io, target_path: []const u8, symlink_path: []const u8) !void { + var buffer: [Dir.max_path_bytes]u8 = undefined; + const given = try Dir.readLinkAbsolute(io, symlink_path, buffer[0..]); try expectEqualStrings(target_path, given); } @@ -309,7 +308,7 @@ test "openDir" { try ctx.dir.makeDir(io, subdir_path, .default_dir); for ([_][]const u8{ "", ".", ".." }) |sub_path| { - const dir_path = try fs.path.join(allocator, &.{ subdir_path, sub_path }); + const dir_path = try Dir.path.join(allocator, &.{ subdir_path, sub_path }); var dir = try ctx.dir.openDir(io, dir_path, .{}); defer dir.close(io); } @@ -321,13 +320,16 @@ test "accessAbsolute" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .openbsd) return error.SkipZigTest; + const io = testing.io; + const gpa = testing.allocator; + var tmp = tmpDir(.{}); defer tmp.cleanup(); - const base_path = try tmp.dir.realpathAlloc(testing.allocator, "."); - defer testing.allocator.free(base_path); + const base_path = try tmp.dir.realPathAlloc(io, ".", gpa); + defer gpa.free(base_path); - try fs.accessAbsolute(base_path, .{}); + try Dir.accessAbsolute(io, base_path, .{}); } test "openDirAbsolute" { @@ -335,6 +337,7 @@ test "openDirAbsolute" { if (native_os == .openbsd) return error.SkipZigTest; const io = testing.io; + const gpa = testing.allocator; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -342,8 +345,8 @@ test "openDirAbsolute" { const tmp_ino = (try tmp.dir.stat(io)).inode; try tmp.dir.makeDir(io, "subdir", .default_dir); - const sub_path = try tmp.dir.realpathAlloc(testing.allocator, "subdir"); - defer testing.allocator.free(sub_path); + const sub_path = try tmp.dir.realPathAlloc(io, "subdir", gpa); + defer gpa.free(sub_path); // Can open sub_path var tmp_sub = try Dir.openDirAbsolute(io, sub_path, .{}); @@ -353,7 +356,7 @@ test "openDirAbsolute" { { // Can open sub_path + ".." - const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".." }); + const dir_path = try Dir.path.join(testing.allocator, &.{ sub_path, ".." }); defer testing.allocator.free(dir_path); var dir = try Dir.openDirAbsolute(io, dir_path, .{}); @@ -365,7 +368,7 @@ test "openDirAbsolute" { { // Can open sub_path + "." - const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, "." }); + const dir_path = try Dir.path.join(testing.allocator, &.{ sub_path, "." }); defer testing.allocator.free(dir_path); var dir = try Dir.openDirAbsolute(io, dir_path, .{}); @@ -377,7 +380,7 @@ test "openDirAbsolute" { { // Can open subdir + "..", with some extra "." - const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".", "..", "." }); + const dir_path = try Dir.path.join(testing.allocator, &.{ sub_path, ".", "..", "." }); defer testing.allocator.free(dir_path); var dir = try Dir.openDirAbsolute(io, dir_path, .{}); @@ -391,7 +394,7 @@ test "openDirAbsolute" { test "openDir cwd parent '..'" { const io = testing.io; - var dir = Io.Dir.cwd().openDir(io, "..", .{}) catch |err| { + var dir = Dir.cwd().openDir(io, "..", .{}) catch |err| { if (native_os == .wasi and err == error.PermissionDenied) { return; // This is okay. WASI disallows escaping from the fs sandbox } @@ -407,6 +410,7 @@ test "openDir non-cwd parent '..'" { } const io = testing.io; + const gpa = testing.allocator; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -417,11 +421,11 @@ test "openDir non-cwd parent '..'" { var dir = try subdir.openDir(io, "..", .{}); defer dir.close(io); - const expected_path = try tmp.dir.realpathAlloc(testing.allocator, "."); - defer testing.allocator.free(expected_path); + const expected_path = try tmp.dir.realPathAlloc(io, ".", gpa); + defer gpa.free(expected_path); - const actual_path = try dir.realpathAlloc(testing.allocator, "."); - defer testing.allocator.free(actual_path); + const actual_path = try dir.realPathAlloc(io, ".", gpa); + defer gpa.free(actual_path); try expectEqualStrings(expected_path, actual_path); } @@ -440,27 +444,27 @@ test "readLinkAbsolute" { try tmp.dir.makeDir(io, "subdir", .default_dir); // Get base abs path - var arena = ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - const allocator = arena.allocator(); + var arena_allocator = ArenaAllocator.init(testing.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); - const base_path = try tmp.dir.realpathAlloc(allocator, "."); + const base_path = try tmp.dir.realPathAlloc(io, ".", arena); { - const target_path = try fs.path.join(allocator, &.{ base_path, "file.txt" }); - const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" }); + const target_path = try Dir.path.join(arena, &.{ base_path, "file.txt" }); + const symlink_path = try Dir.path.join(arena, &.{ base_path, "symlink1" }); // Create symbolic link by path - try setupSymlinkAbsolute(target_path, symlink_path, .{}); - try testReadLinkAbsolute(target_path, symlink_path); + try setupSymlinkAbsolute(io, target_path, symlink_path, .{}); + try testReadLinkAbsolute(io, target_path, symlink_path); } { - const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" }); - const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" }); + const target_path = try Dir.path.join(arena, &.{ base_path, "subdir" }); + const symlink_path = try Dir.path.join(arena, &.{ base_path, "symlink2" }); // Create symbolic link to a directory by path - try setupSymlinkAbsolute(target_path, symlink_path, .{ .is_directory = true }); - try testReadLinkAbsolute(target_path, symlink_path); + try setupSymlinkAbsolute(io, target_path, symlink_path, .{ .is_directory = true }); + try testReadLinkAbsolute(io, target_path, symlink_path); } } @@ -492,8 +496,8 @@ test "Dir.Iterator" { } try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' - try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); - try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); + try expect(contains(&entries, .{ .name = "some_file", .kind = .file })); + try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); } test "Dir.Iterator many entries" { @@ -529,7 +533,7 @@ test "Dir.Iterator many entries" { i = 0; while (i < num) : (i += 1) { const name = try std.fmt.bufPrint(&buf, "{}", .{i}); - try testing.expect(contains(&entries, .{ .name = name, .kind = .file })); + try expect(contains(&entries, .{ .name = name, .kind = .file })); } } @@ -563,8 +567,8 @@ test "Dir.Iterator twice" { } try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' - try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); - try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); + try expect(contains(&entries, .{ .name = "some_file", .kind = .file })); + try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); } } @@ -599,8 +603,8 @@ test "Dir.Iterator reset" { } try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' - try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file })); - try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); + try expect(contains(&entries, .{ .name = "some_file", .kind = .file })); + try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); iter.reset(); } @@ -619,7 +623,7 @@ test "Dir.Iterator but dir is deleted during iteration" { var iterator = subdir.iterate(); // Create something to iterate over within the subdir - try tmp.dir.makePath(io, "subdir" ++ fs.path.sep_str ++ "b"); + try tmp.dir.makePath(io, "subdir" ++ Dir.path.sep_str ++ "b"); // Then, before iterating, delete the directory that we're iterating. // This is a contrived reproduction, but this could happen outside of the program, in another thread, etc. @@ -629,7 +633,7 @@ test "Dir.Iterator but dir is deleted during iteration" { // Now, when we try to iterate, the next call should return null immediately. const entry = try iterator.next(); - try std.testing.expect(entry == null); + try std.expect(entry == null); // On Linux, we can opt-in to receiving a more specific error by calling `nextLinux` if (native_os == .linux) { @@ -657,25 +661,25 @@ test "Dir.realpath smoke test" { const allocator = ctx.arena.allocator(); const test_file_path = try ctx.transformPath("test_file"); const test_dir_path = try ctx.transformPath("test_dir"); - var buf: [fs.max_path_bytes]u8 = undefined; + var buf: [Dir.max_path_bytes]u8 = undefined; // FileNotFound if the path doesn't exist - try expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_file_path)); - try expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf)); - try expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_dir_path)); - try expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf)); + try expectError(error.FileNotFound, ctx.dir.realPathAlloc(io, test_file_path, allocator)); + try expectError(error.FileNotFound, ctx.dir.realPath(io, test_file_path, &buf)); + try expectError(error.FileNotFound, ctx.dir.realPathAlloc(io, test_dir_path, allocator)); + try expectError(error.FileNotFound, ctx.dir.realPath(io, test_dir_path, &buf)); // Now create the file and dir try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); try ctx.dir.makeDir(io, test_dir_path, .default_dir); const base_path = try ctx.transformPath("."); - const base_realpath = try ctx.dir.realpathAlloc(allocator, base_path); - const expected_file_path = try fs.path.join( + const base_realpath = try ctx.dir.realPathAlloc(io, base_path, allocator); + const expected_file_path = try Dir.path.join( allocator, &.{ base_realpath, "test_file" }, ); - const expected_dir_path = try fs.path.join( + const expected_dir_path = try Dir.path.join( allocator, &.{ base_realpath, "test_dir" }, ); @@ -691,10 +695,10 @@ test "Dir.realpath smoke test" { // Next, test alloc version { - const file_path = try ctx.dir.realpathAlloc(allocator, test_file_path); + const file_path = try ctx.dir.realPathAlloc(io, test_file_path, allocator); try expectEqualStrings(expected_file_path, file_path); - const dir_path = try ctx.dir.realpathAlloc(allocator, test_dir_path); + const dir_path = try ctx.dir.realPathAlloc(io, test_dir_path, allocator); try expectEqualStrings(expected_dir_path, dir_path); } } @@ -715,7 +719,7 @@ test "readFileAlloc" { try expectEqualStrings("", buf1); const write_buf: []const u8 = "this is a test.\nthis is a test.\nthis is a test.\nthis is a test.\n"; - try file.writeAll(write_buf); + try file.writeStreamingAll(io, write_buf); { // max_bytes > file_size @@ -779,7 +783,7 @@ test "statFile on dangling symlink" { fn impl(ctx: *TestContext) !void { const io = ctx.io; const symlink_name = try ctx.transformPath("dangling-symlink"); - const symlink_target = "." ++ fs.path.sep_str ++ "doesnotexist"; + const symlink_target = "." ++ Dir.path.sep_str ++ "doesnotexist"; try setupSymlink(io, ctx.dir, symlink_target, symlink_name, .{}); @@ -803,8 +807,8 @@ test "directory operations on files" { try expectError(error.NotDir, ctx.dir.deleteDir(io, test_file_name)); if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { - try expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name)); - try expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name)); + try expectError(error.PathAlreadyExists, Dir.makeDirAbsolute(io, test_file_name)); + try expectError(error.NotDir, Dir.deleteDirAbsolute(io, test_file_name)); } // ensure the file still exists and is a file as a sanity check @@ -862,8 +866,8 @@ test "file operations on directories" { try expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .allow_directory = false, .mode = .read_only })); if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { - try expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{})); - try expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name)); + try expectError(error.IsDir, Dir.createFileAbsolute(io, test_dir_name, .{})); + try expectError(error.IsDir, Dir.deleteFileAbsolute(io, test_dir_name)); } // ensure the directory still exists as a sanity check @@ -892,7 +896,7 @@ test "deleteDir" { fn impl(ctx: *TestContext) !void { const io = ctx.io; const test_dir_path = try ctx.transformPath("test_dir"); - const test_file_path = try ctx.transformPath("test_dir" ++ fs.path.sep_str ++ "test_file"); + const test_file_path = try ctx.transformPath("test_dir" ++ Dir.path.sep_str ++ "test_file"); // deleting a non-existent directory try expectError(error.FileNotFound, ctx.dir.deleteDir(io, test_dir_path)); @@ -1097,11 +1101,12 @@ test "renameAbsolute" { defer arena.deinit(); const allocator = arena.allocator(); - const base_path = try tmp_dir.dir.realpathAlloc(allocator, "."); + const base_path = try tmp_dir.dir.realPathAlloc(io, ".", allocator); - try expectError(error.FileNotFound, fs.renameAbsolute( - try fs.path.join(allocator, &.{ base_path, "missing_file_name" }), - try fs.path.join(allocator, &.{ base_path, "something_else" }), + try expectError(error.FileNotFound, Dir.renameAbsolute( + io, + try Dir.path.join(allocator, &.{ base_path, "missing_file_name" }), + try Dir.path.join(allocator, &.{ base_path, "something_else" }), )); // Renaming files @@ -1109,9 +1114,10 @@ test "renameAbsolute" { const renamed_test_file_name = "test_file_renamed"; var file = try tmp_dir.dir.createFile(io, test_file_name, .{ .read = true }); file.close(io); - try fs.renameAbsolute( - try fs.path.join(allocator, &.{ base_path, test_file_name }), - try fs.path.join(allocator, &.{ base_path, renamed_test_file_name }), + try Dir.renameAbsolute( + io, + try Dir.path.join(allocator, &.{ base_path, test_file_name }), + try Dir.path.join(allocator, &.{ base_path, renamed_test_file_name }), ); // ensure the file was renamed @@ -1125,9 +1131,10 @@ test "renameAbsolute" { const test_dir_name = "test_dir"; const renamed_test_dir_name = "test_dir_renamed"; try tmp_dir.dir.makeDir(io, test_dir_name, .default_dir); - try fs.renameAbsolute( - try fs.path.join(allocator, &.{ base_path, test_dir_name }), - try fs.path.join(allocator, &.{ base_path, renamed_test_dir_name }), + try Dir.renameAbsolute( + io, + try Dir.path.join(allocator, &.{ base_path, test_dir_name }), + try Dir.path.join(allocator, &.{ base_path, renamed_test_dir_name }), ); // ensure the directory was renamed @@ -1149,7 +1156,7 @@ test "executablePath" { if (native_os == .wasi) return error.SkipZigTest; const io = testing.io; - var buf: [fs.max_path_bytes]u8 = undefined; + var buf: [Dir.max_path_bytes]u8 = undefined; const buf_self_exe_path = try std.process.executablePath(io, &buf); const alloc_self_exe_path = try std.process.executablePathAlloc(io, testing.allocator); defer testing.allocator.free(alloc_self_exe_path); @@ -1206,13 +1213,13 @@ test "makePath, put some files in it, deleteTree" { const allocator = ctx.arena.allocator(); const dir_path = try ctx.transformPath("os_test_tmp"); - try ctx.dir.makePath(io, try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); + try ctx.dir.makePath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); try ctx.dir.writeFile(io, .{ - .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), + .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), .data = "nonsense", }); try ctx.dir.writeFile(io, .{ - .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), + .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), .data = "blah", }); @@ -1229,13 +1236,13 @@ test "makePath, put some files in it, deleteTreeMinStackSize" { const allocator = ctx.arena.allocator(); const dir_path = try ctx.transformPath("os_test_tmp"); - try ctx.dir.makePath(io, try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); + try ctx.dir.makePath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); try ctx.dir.writeFile(io, .{ - .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), + .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), .data = "nonsense", }); try ctx.dir.writeFile(io, .{ - .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), + .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }), .data = "blah", }); @@ -1285,7 +1292,7 @@ test "makepath existing directories" { defer tmpA.close(io); try tmpA.makeDir(io, "B", .default_dir); - const testPath = "A" ++ fs.path.sep_str ++ "B" ++ fs.path.sep_str ++ "C"; + const testPath = "A" ++ Dir.path.sep_str ++ "B" ++ Dir.path.sep_str ++ "C"; try tmp.dir.makePath(io, testPath); try expectDir(io, tmp.dir, testPath); @@ -1298,11 +1305,11 @@ test "makepath through existing valid symlink" { defer tmp.cleanup(); try tmp.dir.makeDir(io, "realfolder", .default_dir); - try setupSymlink(io, tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{}); + try setupSymlink(io, tmp.dir, "." ++ Dir.path.sep_str ++ "realfolder", "working-symlink", .{}); - try tmp.dir.makePath(io, "working-symlink" ++ fs.path.sep_str ++ "in-realfolder"); + try tmp.dir.makePath(io, "working-symlink" ++ Dir.path.sep_str ++ "in-realfolder"); - try expectDir(io, tmp.dir, "realfolder" ++ fs.path.sep_str ++ "in-realfolder"); + try expectDir(io, tmp.dir, "realfolder" ++ Dir.path.sep_str ++ "in-realfolder"); } test "makepath relative walks" { @@ -1311,7 +1318,7 @@ test "makepath relative walks" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - const relPath = try fs.path.join(testing.allocator, &.{ + const relPath = try Dir.path.join(testing.allocator, &.{ "first", "..", "second", "..", "third", "..", "first", "A", "..", "B", "..", "C", }); defer testing.allocator.free(relPath); @@ -1323,14 +1330,14 @@ test "makepath relative walks" { .windows => { // On Windows, .. is resolved before passing the path to NtCreateFile, // meaning everything except `first/C` drops out. - try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "C"); + try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "C"); try expectError(error.FileNotFound, tmp.dir.access(io, "second", .{})); try expectError(error.FileNotFound, tmp.dir.access(io, "third", .{})); }, else => { - try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "A"); - try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "B"); - try expectDir(io, tmp.dir, "first" ++ fs.path.sep_str ++ "C"); + try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "A"); + try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "B"); + try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "C"); try expectDir(io, tmp.dir, "second"); try expectDir(io, tmp.dir, "third"); }, @@ -1344,13 +1351,13 @@ test "makepath ignores '.'" { defer tmp.cleanup(); // Path to create, with "." elements: - const dotPath = try fs.path.join(testing.allocator, &.{ + const dotPath = try Dir.path.join(testing.allocator, &.{ "first", ".", "second", ".", "third", }); defer testing.allocator.free(dotPath); // Path to expect to find: - const expectedPath = try fs.path.join(testing.allocator, &.{ + const expectedPath = try Dir.path.join(testing.allocator, &.{ "first", "second", "third", }); defer testing.allocator.free(expectedPath); @@ -1473,7 +1480,7 @@ test "access file" { fn impl(ctx: *TestContext) !void { const io = ctx.io; const dir_path = try ctx.transformPath("os_test_tmp"); - const file_path = try ctx.transformPath("os_test_tmp" ++ fs.path.sep_str ++ "file.txt"); + const file_path = try ctx.transformPath("os_test_tmp" ++ Dir.path.sep_str ++ "file.txt"); try ctx.dir.makePath(io, dir_path); try expectError(error.FileNotFound, ctx.dir.access(io, file_path, .{})); @@ -1546,7 +1553,7 @@ test "sendfile with buffered data" { var src_file = try dir.createFile(io, "sendfile1.txt", .{ .read = true }); defer src_file.close(io); - try src_file.writeAll("AAAABBBB"); + try src_file.writeStreamingAll(io, "AAAABBBB"); var dest_file = try dir.createFile(io, "sendfile2.txt", .{ .read = true }); defer dest_file.close(io); @@ -1691,7 +1698,7 @@ test "open file with exclusive lock twice, make sure second lock waits" { errdefer file.close(io); const S = struct { - fn checkFn(dir: *Io.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { + fn checkFn(dir: *Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { started.set(); const file1 = try dir.createFile(io, path, .{ .lock = .exclusive }); @@ -1731,8 +1738,8 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { var random_bytes: [12]u8 = undefined; std.crypto.random.bytes(&random_bytes); - var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined; - _ = fs.base64_encoder.encode(&random_b64, &random_bytes); + var random_b64: [std.fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined; + _ = std.fs.base64_encoder.encode(&random_b64, &random_bytes); const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt"; @@ -1741,16 +1748,16 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { const cwd = try std.process.getCwdAlloc(gpa); defer gpa.free(cwd); - const filename = try fs.path.resolve(gpa, &.{ cwd, sub_path }); + const filename = try Dir.path.resolve(gpa, &.{ cwd, sub_path }); defer gpa.free(filename); - defer fs.deleteFileAbsolute(filename) catch {}; // createFileAbsolute can leave files on failures - const file1 = try fs.createFileAbsolute(filename, .{ + defer Dir.deleteFileAbsolute(io, filename) catch {}; // createFileAbsolute can leave files on failures + const file1 = try Dir.createFileAbsolute(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true, }); - const file2 = fs.createFileAbsolute(filename, .{ + const file2 = Dir.createFileAbsolute(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true, }); @@ -1802,9 +1809,9 @@ test "walker" { .{ "dir2", 1 }, .{ "dir3", 1 }, .{ "dir4", 1 }, - .{ "dir3" ++ fs.path.sep_str ++ "sub1", 2 }, - .{ "dir3" ++ fs.path.sep_str ++ "sub2", 2 }, - .{ "dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1", 3 }, + .{ "dir3" ++ Dir.path.sep_str ++ "sub1", 2 }, + .{ "dir3" ++ Dir.path.sep_str ++ "sub2", 2 }, + .{ "dir3" ++ Dir.path.sep_str ++ "sub2" ++ Dir.path.sep_str ++ "subsub1", 3 }, }); const expected_basenames = std.StaticStringMap(void).initComptime(.{ @@ -1826,11 +1833,11 @@ test "walker" { var num_walked: usize = 0; while (try walker.next()) |entry| { - testing.expect(expected_basenames.has(entry.basename)) catch |err| { + expect(expected_basenames.has(entry.basename)) catch |err| { std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); return err; }; - testing.expect(expected_paths.has(entry.path)) catch |err| { + expect(expected_paths.has(entry.path)) catch |err| { std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); return err; }; @@ -1863,11 +1870,11 @@ test "selective walker, skip entries that start with ." { const expected_paths = std.StaticStringMap(usize).initComptime(.{ .{ "dir1", 1 }, - .{ "dir1" ++ fs.path.sep_str ++ "foo", 2 }, + .{ "dir1" ++ Dir.path.sep_str ++ "foo", 2 }, .{ "a", 1 }, - .{ "a" ++ fs.path.sep_str ++ "b", 2 }, - .{ "a" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c", 3 }, - .{ "a" ++ fs.path.sep_str ++ "baz", 2 }, + .{ "a" ++ Dir.path.sep_str ++ "b", 2 }, + .{ "a" ++ Dir.path.sep_str ++ "b" ++ Dir.path.sep_str ++ "c", 3 }, + .{ "a" ++ Dir.path.sep_str ++ "baz", 2 }, }); const expected_basenames = std.StaticStringMap(void).initComptime(.{ @@ -1893,11 +1900,11 @@ test "selective walker, skip entries that start with ." { try walker.enter(entry); } - testing.expect(expected_basenames.has(entry.basename)) catch |err| { + expect(expected_basenames.has(entry.basename)) catch |err| { std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); return err; }; - testing.expect(expected_paths.has(entry.path)) catch |err| { + expect(expected_paths.has(entry.path)) catch |err| { std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); return err; }; @@ -1937,7 +1944,7 @@ test "walker without fully iterating" { try expectEqual(@as(usize, 1), num_walked); } -test "'.' and '..' in Io.Dir functions" { +test "'.' and '..' in Dir functions" { if (native_os == .windows and builtin.cpu.arch == .aarch64) { // https://github.com/ziglang/zig/issues/17134 return error.SkipZigTest; @@ -1970,7 +1977,7 @@ test "'.' and '..' in Io.Dir functions" { try ctx.dir.writeFile(io, .{ .sub_path = update_path, .data = "something" }); var dir = ctx.dir; const prev_status = try dir.updateFile(io, file_path, dir, update_path, .{}); - try expectEqual(Io.Dir.PrevStatus.stale, prev_status); + try expectEqual(Dir.PrevStatus.stale, prev_status); try ctx.dir.deleteDir(io, subdir_path); } @@ -1990,28 +1997,28 @@ test "'.' and '..' in absolute functions" { defer arena.deinit(); const allocator = arena.allocator(); - const base_path = try tmp.dir.realpathAlloc(allocator, "."); + const base_path = try tmp.dir.realPathAlloc(io, ".", allocator); - const subdir_path = try fs.path.join(allocator, &.{ base_path, "./subdir" }); - try fs.makeDirAbsolute(subdir_path); - try fs.accessAbsolute(subdir_path, .{}); + const subdir_path = try Dir.path.join(allocator, &.{ base_path, "./subdir" }); + try Dir.makeDirAbsolute(io, subdir_path); + try Dir.accessAbsolute(io, subdir_path, .{}); var created_subdir = try Dir.openDirAbsolute(io, subdir_path, .{}); created_subdir.close(io); - const created_file_path = try fs.path.join(allocator, &.{ subdir_path, "../file" }); - const created_file = try fs.createFileAbsolute(created_file_path, .{}); + const created_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../file" }); + const created_file = try Dir.createFileAbsolute(io, created_file_path, .{}); created_file.close(io); - try fs.accessAbsolute(created_file_path, .{}); + try Dir.accessAbsolute(io, created_file_path, .{}); - const copied_file_path = try fs.path.join(allocator, &.{ subdir_path, "../copy" }); - try fs.copyFileAbsolute(created_file_path, copied_file_path, .{}); - const renamed_file_path = try fs.path.join(allocator, &.{ subdir_path, "../rename" }); - try fs.renameAbsolute(copied_file_path, renamed_file_path); + const copied_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../copy" }); + try Dir.copyFileAbsolute(io, created_file_path, copied_file_path, .{}); + const renamed_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../rename" }); + try Dir.renameAbsolute(io, copied_file_path, renamed_file_path); const renamed_file = try Dir.openFileAbsolute(renamed_file_path, .{}); renamed_file.close(io); - try fs.deleteFileAbsolute(renamed_file_path); + try Dir.deleteFileAbsolute(io, renamed_file_path); - try fs.deleteDirAbsolute(subdir_path); + try Dir.deleteDirAbsolute(io, subdir_path); } test "chmod" { @@ -2114,8 +2121,8 @@ test "invalid UTF-8/WTF-8 paths" { try expectError(expected_err, ctx.dir.statFile(invalid_path)); if (native_os != .wasi) { - try expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{})); - try expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path)); + try expectError(expected_err, ctx.dir.realPath(io, invalid_path, &[_]u8{})); + try expectError(expected_err, ctx.dir.realPathAlloc(io, invalid_path, testing.allocator)); } try expectError(expected_err, Dir.rename(ctx.dir, invalid_path, ctx.dir, invalid_path, io)); @@ -2133,7 +2140,7 @@ test "invalid UTF-8/WTF-8 paths" { var readlink_buf: [Dir.max_path_bytes]u8 = undefined; try expectError(expected_err, Dir.readLinkAbsolute(invalid_path, &readlink_buf)); try expectError(expected_err, Dir.symLinkAbsolute(invalid_path, invalid_path, .{})); - try expectError(expected_err, Dir.realpathAlloc(testing.allocator, invalid_path)); + try expectError(expected_err, Dir.realPathAlloc(io, invalid_path, testing.allocator)); } } }.impl); @@ -2303,7 +2310,7 @@ test "readlink on Windows" { } fn testReadLinkWindows(io: Io, target_path: []const u8, symlink_path: []const u8) !void { - var buffer: [fs.max_path_bytes]u8 = undefined; + var buffer: [Dir.max_path_bytes]u8 = undefined; const given = try Dir.readLinkAbsolute(io, symlink_path, &buffer); try expect(mem.eql(u8, target_path, given)); } @@ -2326,7 +2333,7 @@ test "readlinkat" { }; // read the link - var buffer: [fs.max_path_bytes]u8 = undefined; + var buffer: [Dir.max_path_bytes]u8 = undefined; const read_link = try tmp.dir.readLink(io, "link", &buffer); try expectEqualStrings("file.txt", read_link); } @@ -2387,3 +2394,239 @@ fn expectMode(io: Io, dir: Dir, file: []const u8, permissions: File.Permissions) const st = try dir.statFile(io, file, .{ .follow_symlinks = false }); try expectEqual(mode, st.mode & 0b111_111_111); } + +test "isatty" { + const io = testing.io; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + var file = try tmp.dir.createFile(io, "foo", .{}); + defer file.close(io); + + try expectEqual(false, try file.isTty(io)); +} + +test "read positional empty buffer" { + const io = testing.io; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + var file = try tmp.dir.createFile(io, "pread_empty", .{ .read = true }); + defer file.close(io); + + var buffer: [0]u8 = undefined; + try expectEqual(0, try file.readPositional(io, &buffer, 0)); +} + +test "write streaming empty buffer" { + const io = testing.io; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + var file = try tmp.dir.createFile(io, "write_empty", .{}); + defer file.close(io); + + var buffer: [0]u8 = &.{}; + try expectEqual(0, try file.writeStreaming(io, &buffer)); +} + +test "write positional empty buffer" { + const io = testing.io; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + var file = try tmp.dir.createFile(io, "pwrite_empty", .{}); + defer file.close(io); + + var buffer: [0]u8 = &.{}; + try expectEqual(0, try file.writePositional(io, &buffer, 0)); +} + +test "access smoke test" { + if (native_os == .wasi) return error.SkipZigTest; + if (native_os == .windows) return error.SkipZigTest; + if (native_os == .openbsd) return error.SkipZigTest; + + const io = testing.io; + const gpa = testing.allocator; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + const base_path = try tmp.dir.realPathAlloc(io, ".", gpa); + defer gpa.free(base_path); + + { + // Create some file using `open`. + const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); + defer gpa.free(file_path); + const file = Dir.cwd().createFile(io, file_path, .{ .read = true, .exclusive = true }); + file.close(io); + } + + { + // Try to access() the file + const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); + defer gpa.free(file_path); + if (native_os == .windows) { + try Dir.cwd().access(io, file_path, .{}); + } else { + try Dir.cwd().access(io, file_path, .{ .read = true, .write = true }); + } + } + + { + // Try to access() a non-existent file - should fail with error.FileNotFound + const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" }); + defer gpa.free(file_path); + try expectError(error.FileNotFound, Dir.cwd().access(io, file_path, .{})); + } + + { + // Create some directory + const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); + defer gpa.free(file_path); + try Dir.makeDir(io, file_path, .default_file); + } + + { + // Try to access() the directory + const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); + defer gpa.free(file_path); + + try Dir.access(io, file_path, .{}); + } +} + +test "write streaming a long vector" { + const io = testing.io; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + var file = try tmp.dir.createFile(io, "pwritev", .{}); + defer file.close(io); + + var vecs: [2000][]const u8 = undefined; + for (&vecs) |*v| v.* = "a"; + + const n = try file.writePositional(io, &vecs, 0); + try expect(n <= vecs.len); +} + +test "open smoke test" { + if (native_os == .wasi) return error.SkipZigTest; + if (native_os == .windows) return error.SkipZigTest; + if (native_os == .openbsd) return error.SkipZigTest; + + // TODO verify file attributes using `fstat` + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + const io = testing.io; + + { + // Create some file using `open`. + const file = try tmp.dir.createFile(io, "some_file", .{ .exclusive = true }); + file.close(io); + } + + // Try this again with the same flags. This op should fail with error.PathAlreadyExists. + try expectError( + error.PathAlreadyExists, + tmp.dir.createFile(io, "some_file", .{ .exclusive = true }), + ); + + { + // Try opening without exclusive flag. + const file = try tmp.dir.createFile(io, "some_file", .{}); + file.close(io); + } + + try expectError(error.NotDir, tmp.dir.openDir(io, "some_file", .{})); + try tmp.dir.makeDir(io, "some_dir", .default_dir); + + { + const dir = try tmp.dir.openDir("some_dir", .{}); + dir.close(io); + } + + // Try opening as file which should fail. + try expectError(error.IsDir, tmp.dir.openFile("some_dir", .{})); +} + +test "hard link with different directories" { + const io = testing.io; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + const target_name = "link-target"; + const link_name = "newlink"; + + const subdir = try tmp.dir.makeOpenPath(io, "subdir", .{}); + + defer tmp.dir.deleteFile(io, target_name) catch {}; + try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" }); + + // Test 1: link from file in subdir back up to target in parent directory + tmp.dir.hardLink(target_name, subdir, link_name, 0) catch |err| switch (err) { + error.OperationUnsupported => return error.SkipZigTest, + else => |e| return e, + }; + + const efd = try tmp.dir.openFile(io, target_name, .{}); + defer efd.close(io); + + const nfd = try subdir.openFile(io, link_name, .{}); + defer nfd.close(io); + + { + const e_stat = try efd.stat(io); + const n_stat = try nfd.stat(io); + + try expectEqual(e_stat.inode, n_stat.inode); + try expectEqual(2, e_stat.nlink); + try expectEqual(2, n_stat.nlink); + } + + // Test 2: remove link + try subdir.deleteFile(io, link_name, .{}); + const e_stat = try efd.stat(io); + try expectEqual(1, e_stat.nlink); +} + +test "stat smoke test" { + if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; + + const io = testing.io; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + // create dummy file + const contents = "nonsense"; + try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = contents }); + + // fetch file's info on the opened fd directly + const file = try tmp.dir.openFile(io, "file.txt", .{}); + const stat = try file.stat(io); + defer file.close(io); + + // now repeat but using directory handle instead + const statat = try tmp.dir.statFile(io, "file.txt", .{ .follow_symlinks = false }); + + try expectEqual(stat.inode, statat.inode); + try expectEqual(stat.nlink, statat.nlink); + try expectEqual(stat.size, statat.size); + try expectEqual(stat.permissions, statat.permissions); + try expectEqual(stat.kind, statat.kind); + try expectEqual(stat.atime, statat.atime); + try expectEqual(stat.mtime, statat.mtime); + try expectEqual(stat.ctime, statat.ctime); +} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index da2b0e8786ce434a1995acead4e3f0339789cac8..dff9639578b024d6147e377d6c52070f159b913e 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1034,163 +1034,6 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { } } -pub const LinkError = UnexpectedError || error{ - AccessDenied, - PermissionDenied, - DiskQuota, - PathAlreadyExists, - FileSystem, - SymLinkLoop, - LinkQuotaExceeded, - NameTooLong, - FileNotFound, - SystemResources, - NoSpaceLeft, - ReadOnlyFileSystem, - NotSameFileSystem, - BadPathName, -}; - -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8) LinkError!void { - if (native_os == .wasi and !builtin.link_libc) { - return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0)); - } - switch (errno(system.link(oldpath, newpath))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => unreachable, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.NotSameFileSystem, - .INVAL => unreachable, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn link(oldpath: []const u8, newpath: []const u8) LinkError!void { - if (native_os == .wasi and !builtin.link_libc) { - return linkat(AT.FDCWD, oldpath, AT.FDCWD, newpath, 0) catch |err| switch (err) { - error.NotDir => unreachable, // link() does not support directories - else => |e| return e, - }; - } - const old = try toPosixPath(oldpath); - const new = try toPosixPath(newpath); - return try linkZ(&old, &new); -} - -pub const LinkatError = LinkError || error{NotDir}; - -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn linkatZ( - olddir: fd_t, - oldpath: [*:0]const u8, - newdir: fd_t, - newpath: [*:0]const u8, - flags: i32, -) LinkatError!void { - if (native_os == .wasi and !builtin.link_libc) { - return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags); - } - switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => unreachable, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.NotSameFileSystem, - .INVAL => unreachable, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. -pub fn linkat( - olddir: fd_t, - oldpath: []const u8, - newdir: fd_t, - newpath: []const u8, - flags: i32, -) LinkatError!void { - if (native_os == .wasi and !builtin.link_libc) { - const old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath }; - const new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath }; - const old_flags: wasi.lookupflags_t = .{ - .SYMLINK_FOLLOW = (flags & AT.SYMLINK_FOLLOW) != 0, - }; - switch (wasi.path_link( - old.dir_fd, - old_flags, - old.relative_path.ptr, - old.relative_path.len, - new.dir_fd, - new.relative_path.ptr, - new.relative_path.len, - )) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => unreachable, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.NotSameFileSystem, - .INVAL => unreachable, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } - } - const old = try toPosixPath(oldpath); - const new = try toPosixPath(newpath); - return try linkatZ(olddir, &old, newdir, &new, flags); -} - -/// An fd-relative file path -/// -/// This is currently only used for WASI-specific functionality, but the concept -/// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions. -const RelativePathWasi = struct { - /// Handle to directory - dir_fd: fd_t, - /// Path to resource within `dir_fd`. - relative_path: []const u8, -}; - /// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_dir_path` should be encoded as valid UTF-8. /// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding. diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 64511ff4a22b404ba8ce50b22c639772d76fc5b3..a129eb1e8dc824262195681c0a1c0488d1f91898 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -1,25 +1,24 @@ const builtin = @import("builtin"); const native_os = builtin.target.os.tag; +const AtomicRmwOp = std.builtin.AtomicRmwOp; +const AtomicOrder = std.builtin.AtomicOrder; const std = @import("../std.zig"); const Io = std.Io; +const Dir = std.Io.Dir; const posix = std.posix; +const mem = std.mem; +const elf = std.elf; +const linux = std.os.linux; +const AT = std.posix.AT; + const testing = std.testing; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; +const expectEqualSlices = std.testing.expectEqualSlices; +const expectEqualStrings = std.testing.expectEqualStrings; const expectError = std.testing.expectError; -const fs = std.fs; -const mem = std.mem; -const elf = std.elf; -const linux = std.os.linux; -const a = std.testing.allocator; -const AtomicRmwOp = std.builtin.AtomicRmwOp; -const AtomicOrder = std.builtin.AtomicOrder; const tmpDir = std.testing.tmpDir; -const AT = posix.AT; - -// NOTE: several additional tests are in test/standalone/posix/. Any tests that mutate -// process-wide POSIX state (cwd, signals, etc) cannot be Zig unit tests and should be over there. // https://github.com/ziglang/zig/issues/20288 test "WTF-8 to WTF-16 conversion buffer overflows" { @@ -43,165 +42,6 @@ test "check WASI CWD" { } } -test "open smoke test" { - if (native_os == .wasi) return error.SkipZigTest; - if (native_os == .windows) return error.SkipZigTest; - if (native_os == .openbsd) return error.SkipZigTest; - - // TODO verify file attributes using `fstat` - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - const base_path = try tmp.dir.realpathAlloc(a, "."); - defer a.free(base_path); - - const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; - - { - // Create some file using `open`. - const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); - defer a.free(file_path); - const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); - posix.close(fd); - } - - { - // Try this again with the same flags. This op should fail with error.PathAlreadyExists. - const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); - defer a.free(file_path); - try expectError(error.PathAlreadyExists, posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode)); - } - - { - // Try opening without `EXCL` flag. - const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); - defer a.free(file_path); - const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true }, mode); - posix.close(fd); - } - - { - // Try opening as a directory which should fail. - const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); - defer a.free(file_path); - try expectError(error.NotDir, posix.open(file_path, .{ .ACCMODE = .RDWR, .DIRECTORY = true }, mode)); - } - - { - // Create some directory - const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); - defer a.free(file_path); - try posix.mkdir(file_path, mode); - } - - { - // Open dir using `open` - const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); - defer a.free(file_path); - const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); - posix.close(fd); - } - - { - // Try opening as file which should fail. - const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); - defer a.free(file_path); - try expectError(error.IsDir, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode)); - } -} - -fn getLinkInfo(fd: posix.fd_t) !struct { posix.ino_t, posix.nlink_t } { - if (native_os == .linux) { - const stx = try linux.wrapped.statx( - fd, - "", - posix.AT.EMPTY_PATH, - .{ .INO = true, .NLINK = true }, - ); - std.debug.assert(stx.mask.INO); - std.debug.assert(stx.mask.NLINK); - return .{ stx.ino, stx.nlink }; - } - - const st = try posix.fstat(fd); - return .{ st.ino, st.nlink }; -} - -test "linkat with different directories" { - switch (native_os) { - .wasi, .linux, .illumos => {}, - else => return error.SkipZigTest, - } - - const io = testing.io; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - const target_name = "link-target"; - const link_name = "newlink"; - - const subdir = try tmp.dir.makeOpenPath(io, "subdir", .{}); - - defer tmp.dir.deleteFile(io, target_name) catch {}; - try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" }); - - // Test 1: link from file in subdir back up to target in parent directory - try posix.linkat(tmp.dir.handle, target_name, subdir.handle, link_name, 0); - - const efd = try tmp.dir.openFile(io, target_name, .{}); - defer efd.close(io); - - const nfd = try subdir.openFile(io, link_name, .{}); - defer nfd.close(io); - - { - const eino, _ = try getLinkInfo(efd.handle); - const nino, const nlink = try getLinkInfo(nfd.handle); - try testing.expectEqual(eino, nino); - try testing.expectEqual(@as(posix.nlink_t, 2), nlink); - } - - // Test 2: remove link - try posix.unlinkat(subdir.handle, link_name, 0); - _, const elink = try getLinkInfo(efd.handle); - try testing.expectEqual(@as(posix.nlink_t, 1), elink); -} - -test "fstatat" { - if (posix.Stat == void) return error.SkipZigTest; - if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - // create dummy file - const contents = "nonsense"; - try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = contents }); - - // fetch file's info on the opened fd directly - const file = try tmp.dir.openFile("file.txt", .{}); - const stat = try posix.fstat(file.handle); - defer file.close(); - - // now repeat but using `fstatat` instead - const statat = try posix.fstatat(tmp.dir.fd, "file.txt", posix.AT.SYMLINK_NOFOLLOW); - - try expectEqual(stat.dev, statat.dev); - try expectEqual(stat.ino, statat.ino); - try expectEqual(stat.nlink, statat.nlink); - try expectEqual(stat.mode, statat.mode); - try expectEqual(stat.uid, statat.uid); - try expectEqual(stat.gid, statat.gid); - try expectEqual(stat.rdev, statat.rdev); - try expectEqual(stat.size, statat.size); - try expectEqual(stat.blksize, statat.blksize); - // The stat.blocks/statat.blocks count is managed by the filesystem and may - // change if the file is stored in a journal or "inline". - // try expectEqual(stat.blocks, statat.blocks); -} - test "getrandom" { var buf_a: [50]u8 = undefined; var buf_b: [50]u8 = undefined; @@ -232,7 +72,7 @@ test "sigaltstack" { // Setting a stack size less than MINSIGSTKSZ returns ENOMEM st.flags = 0; st.size = 1; - try testing.expectError(error.SizeTooSmall, posix.sigaltstack(&st, null)); + try expectError(error.SizeTooSmall, posix.sigaltstack(&st, null)); } // If the type is not available use void to avoid erroring out when `iter_fn` is @@ -304,7 +144,7 @@ test "pipe" { try expect((try posix.write(fds[1], "hello")) == 5); var buf: [16]u8 = undefined; try expect((try posix.read(fds[0], buf[0..])) == 5); - try testing.expectEqualSlices(u8, buf[0..5], "hello"); + try expectEqualSlices(u8, buf[0..5], "hello"); posix.close(fds[1]); posix.close(fds[0]); } @@ -315,6 +155,8 @@ test "argsAlloc" { } test "memfd_create" { + const io = testing.io; + // memfd_create is only supported by linux and freebsd. switch (native_os) { .linux => {}, @@ -325,15 +167,14 @@ test "memfd_create" { else => return error.SkipZigTest, } - const fd = try posix.memfd_create("test", 0); - defer posix.close(fd); - try expect((try posix.write(fd, "test")) == 4); - try posix.lseek_SET(fd, 0); + const file: Io.File = .{ .handle = try posix.memfd_create("test", 0) }; + defer file.close(io); + try file.writePositionalAll(io, "test", 0); var buf: [10]u8 = undefined; - const bytes_read = try posix.read(fd, &buf); + const bytes_read = try file.readPositionalAll(io, &buf, 0); try expect(bytes_read == 4); - try expect(mem.eql(u8, buf[0..4], "test")); + try expectEqualStrings("test", buf[0..4]); } test "mmap" { @@ -357,14 +198,14 @@ test "mmap" { ); defer posix.munmap(data); - try testing.expectEqual(@as(usize, 1234), data.len); + try expectEqual(@as(usize, 1234), data.len); // By definition the data returned by mmap is zero-filled - try testing.expect(mem.eql(u8, data, &[_]u8{0x00} ** 1234)); + try expect(mem.eql(u8, data, &[_]u8{0x00} ** 1234)); // Make sure the memory is writeable as requested @memset(data, 0x55); - try testing.expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234)); + try expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234)); } const test_out_file = "os_tmp_test"; @@ -403,7 +244,7 @@ test "mmap" { var i: usize = 0; while (i < alloc_size / @sizeOf(u32)) : (i += 1) { - try testing.expectEqual(i, try stream.takeInt(u32, .little)); + try expectEqual(i, try stream.takeInt(u32, .little)); } } @@ -428,7 +269,7 @@ test "mmap" { var i: usize = alloc_size / 2 / @sizeOf(u32); while (i < alloc_size / @sizeOf(u32)) : (i += 1) { - try testing.expectEqual(i, try stream.takeInt(u32, .little)); + try expectEqual(i, try stream.takeInt(u32, .little)); } } } @@ -498,7 +339,7 @@ test "fsync" { const file = try tmp.dir.createFile(io, test_out_file, .{}); defer file.close(io); - try posix.fsync(file.handle); + try file.sync(io); try posix.fdatasync(file.handle); } @@ -536,9 +377,9 @@ test "sigrtmin/max" { return error.SkipZigTest; } - try std.testing.expect(posix.sigrtmin() >= 32); - try std.testing.expect(posix.sigrtmin() >= posix.system.sigrtmin()); - try std.testing.expect(posix.sigrtmin() < posix.system.sigrtmax()); + try expect(posix.sigrtmin() >= 32); + try expect(posix.sigrtmin() >= posix.system.sigrtmin()); + try expect(posix.sigrtmin() < posix.system.sigrtmax()); } test "sigset empty/full" { @@ -622,18 +463,18 @@ test "dup & dup2" { var duped = Io.File{ .handle = try posix.dup(file.handle) }; defer duped.close(io); - try duped.writeAll("dup"); + try duped.writeStreamingAll(io, "dup"); // Tests aren't run in parallel so using the next fd shouldn't be an issue. const new_fd = duped.handle + 1; try posix.dup2(file.handle, new_fd); var dup2ed = Io.File{ .handle = new_fd }; defer dup2ed.close(io); - try dup2ed.writeAll("dup2"); + try dup2ed.writeStreamingAll(io, "dup2"); } var buffer: [8]u8 = undefined; - try testing.expectEqualStrings("dupdup2", try tmp.dir.readFile("os_dup_test", &buffer)); + try expectEqualStrings("dupdup2", try tmp.dir.readFile(io, "os_dup_test", &buffer)); } test "getpid" { @@ -651,147 +492,78 @@ test "getppid" { try expect(posix.getppid() >= 0); } -test "writev longer than IOV_MAX" { - if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; - - const io = testing.io; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - var file = try tmp.dir.createFile(io, "pwritev", .{}); - defer file.close(io); - - const iovecs = [_]posix.iovec_const{.{ .base = "a", .len = 1 }} ** (posix.IOV_MAX + 1); - const amt = try file.writev(&iovecs); - try testing.expectEqual(@as(usize, posix.IOV_MAX), amt); -} - test "rename smoke test" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .windows) return error.SkipZigTest; if (native_os == .openbsd) return error.SkipZigTest; + const io = testing.io; + const gpa = testing.allocator; + var tmp = tmpDir(.{}); defer tmp.cleanup(); - const base_path = try tmp.dir.realpathAlloc(a, "."); - defer a.free(base_path); + const base_path = try tmp.dir.realPathAlloc(io, ".", gpa); + defer gpa.free(base_path); const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; { // Create some file using `open`. - const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); - defer a.free(file_path); + const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); + defer gpa.free(file_path); const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); posix.close(fd); // Rename the file - const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_file" }); - defer a.free(new_file_path); - try Io.Dir.renameAbsolute(file_path, new_file_path); + const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" }); + defer gpa.free(new_file_path); + try Io.Dir.renameAbsolute(io, file_path, new_file_path); } { // Try opening renamed file - const file_path = try fs.path.join(a, &.{ base_path, "some_other_file" }); - defer a.free(file_path); + const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" }); + defer gpa.free(file_path); const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode); posix.close(fd); } { // Try opening original file - should fail with error.FileNotFound - const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); - defer a.free(file_path); + const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); + defer gpa.free(file_path); try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode)); } { // Create some directory - const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); - defer a.free(file_path); + const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); + defer gpa.free(file_path); try posix.mkdir(file_path, mode); // Rename the directory - const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" }); - defer a.free(new_file_path); - try Io.Dir.renameAbsolute(file_path, new_file_path); + const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" }); + defer gpa.free(new_file_path); + try Io.Dir.renameAbsolute(io, file_path, new_file_path); } { // Try opening renamed directory - const file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" }); - defer a.free(file_path); + const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" }); + defer gpa.free(file_path); const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); posix.close(fd); } { // Try opening original directory - should fail with error.FileNotFound - const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); - defer a.free(file_path); + const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); + defer gpa.free(file_path); try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode)); } } -test "access smoke test" { - if (native_os == .wasi) return error.SkipZigTest; - if (native_os == .windows) return error.SkipZigTest; - if (native_os == .openbsd) return error.SkipZigTest; - - const io = testing.io; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - const base_path = try tmp.dir.realpathAlloc(a, "."); - defer a.free(base_path); - - const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; - { - // Create some file using `open`. - const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); - defer a.free(file_path); - const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); - posix.close(fd); - } - - { - // Try to access() the file - const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); - defer a.free(file_path); - if (native_os == .windows) { - try posix.access(io, file_path, posix.F_OK); - } else { - try posix.access(io, file_path, posix.F_OK | posix.W_OK | posix.R_OK); - } - } - - { - // Try to access() a non-existent file - should fail with error.FileNotFound - const file_path = try fs.path.join(a, &.{ base_path, "some_other_file" }); - defer a.free(file_path); - try expectError(error.FileNotFound, posix.access(io, file_path, posix.F_OK)); - } - - { - // Create some directory - const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); - defer a.free(file_path); - try posix.mkdir(file_path, mode); - } - - { - // Try to access() the directory - const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); - defer a.free(file_path); - - try posix.access(io, file_path, posix.F_OK); - } -} - test "timerfd" { if (native_os != .linux) return error.SkipZigTest; @@ -809,89 +581,3 @@ test "timerfd" { const expect_disarmed_timer: linux.itimerspec = .{ .it_interval = .{ .sec = 0, .nsec = 0 }, .it_value = .{ .sec = 0, .nsec = 0 } }; try expectEqual(expect_disarmed_timer, git); } - -test "isatty" { - const io = testing.io; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - var file = try tmp.dir.createFile(io, "foo", .{}); - defer file.close(io); - - try expectEqual(posix.isatty(file.handle), false); -} - -test "pread with empty buffer" { - const io = testing.io; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - var file = try tmp.dir.createFile(io, "pread_empty", .{ .read = true }); - defer file.close(io); - - const bytes = try a.alloc(u8, 0); - defer a.free(bytes); - - const rc = try posix.pread(file.handle, bytes, 0); - try expectEqual(rc, 0); -} - -test "write with empty buffer" { - const io = testing.io; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - var file = try tmp.dir.createFile(io, "write_empty", .{}); - defer file.close(io); - - const bytes = try a.alloc(u8, 0); - defer a.free(bytes); - - const rc = try posix.write(file.handle, bytes); - try expectEqual(rc, 0); -} - -test "pwrite with empty buffer" { - const io = testing.io; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - var file = try tmp.dir.createFile(io, "pwrite_empty", .{}); - defer file.close(io); - - const bytes = try a.alloc(u8, 0); - defer a.free(bytes); - - const rc = try posix.pwrite(file.handle, bytes, 0); - try expectEqual(rc, 0); -} - -const CommonOpenFlags = packed struct { - ACCMODE: posix.ACCMODE = .RDONLY, - CREAT: bool = false, - EXCL: bool = false, - LARGEFILE: bool = false, - DIRECTORY: bool = false, - CLOEXEC: bool = false, - NONBLOCK: bool = false, - - pub fn lower(cof: CommonOpenFlags) posix.O { - var result: posix.O = if (native_os == .wasi) .{ - .read = cof.ACCMODE != .WRONLY, - .write = cof.ACCMODE != .RDONLY, - } else .{ - .ACCMODE = cof.ACCMODE, - }; - result.CREAT = cof.CREAT; - result.EXCL = cof.EXCL; - result.DIRECTORY = cof.DIRECTORY; - result.NONBLOCK = cof.NONBLOCK; - if (@hasField(posix.O, "CLOEXEC")) result.CLOEXEC = cof.CLOEXEC; - if (@hasField(posix.O, "LARGEFILE")) result.LARGEFILE = cof.LARGEFILE; - return result; - } -}; diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 94d877b4066af8bf210d92d69661f3b4e8f29c5c..7feca08e87704975485d42931b31e78ad1dd9041 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6335,23 +6335,24 @@ fn testParse(io: Io, source: [:0]const u8, allocator: Allocator, anything_change var buffer: [64]u8 = undefined; const stderr = try io.lockStderr(&buffer, null); defer io.unlockStderr(); + const writer = &stderr.file_writer.interface; var tree = try std.zig.Ast.parse(allocator, source, .zig); defer tree.deinit(allocator); for (tree.errors) |parse_error| { const loc = tree.tokenLocation(0, parse_error.token); - try stderr.writer.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); - try tree.renderError(parse_error, stderr.writer); - try stderr.writer.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); + try writer.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); + try tree.renderError(parse_error, writer); + try writer.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); { var i: usize = 0; while (i < loc.column) : (i += 1) { - try stderr.writer.writeAll(" "); + try writer.writeAll(" "); } - try stderr.writer.writeAll("^"); + try writer.writeAll("^"); } - try stderr.writer.writeAll("\n"); + try writer.writeAll("\n"); } if (tree.errors.len != 0) { return error.ParseError; -- 2.54.0 From e205b13ffbfe0e57e39bb324799785450c9a1da5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 14:19:49 -0800 Subject: [PATCH 074/195] std.os.linux.IoUring: update for new fs API and split out tests into a separate file --- lib/std/os/linux/IoUring.zig | 2752 +---------------------------- lib/std/os/linux/IoUring/test.zig | 2691 ++++++++++++++++++++++++++++ lib/std/os/linux/test.zig | 4 +- 3 files changed, 2707 insertions(+), 2740 deletions(-) create mode 100644 lib/std/os/linux/IoUring/test.zig diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index 72b8369d681dc766572a4c041705ba656a18965e..b3d6994275e971a1162a7d6d626349c68cb30c93 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -3,15 +3,15 @@ const IoUring = @This(); const builtin = @import("builtin"); const is_linux = builtin.os.tag == .linux; -const std = @import("std"); +const std = @import("../../std.zig"); const Io = std.Io; +const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const mem = std.mem; -const net = std.Io.net; const posix = std.posix; const linux = std.os.linux; const testing = std.testing; const page_size_min = std.heap.page_size_min; +const createSocketTestHarness = @import("IoUring/test.zig").createSocketTestHarness; fd: linux.fd_t = -1, sq: SubmissionQueue, @@ -25,7 +25,7 @@ features: u32, /// see https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L8027-L8050. /// Matches the interface of io_uring_queue_init() in liburing. pub fn init(entries: u16, flags: u32) !IoUring { - var params = mem.zeroInit(linux.io_uring_params, .{ + var params = std.mem.zeroInit(linux.io_uring_params, .{ .flags = flags, .sq_thread_idle = 1000, }); @@ -1312,7 +1312,7 @@ pub fn unregister_buffers(self: *IoUring) !void { /// io_uring subsystem of the running kernel. The io_uring_probe contains the /// list of supported operations. pub fn get_probe(self: *IoUring) !linux.io_uring_probe { - var probe = mem.zeroInit(linux.io_uring_probe, .{}); + var probe = std.mem.zeroInit(linux.io_uring_probe, .{}); const res = linux.io_uring_register(self.fd, .REGISTER_PROBE, &probe, probe.ops.len); try handle_register_buf_ring_result(res); return probe; @@ -1639,7 +1639,7 @@ pub const BufferGroup = struct { pub fn init( ring: *IoUring, - allocator: mem.Allocator, + allocator: Allocator, group_id: u16, buffer_size: u32, buffers_count: u16, @@ -1673,7 +1673,7 @@ pub const BufferGroup = struct { }; } - pub fn deinit(self: *BufferGroup, allocator: mem.Allocator) void { + pub fn deinit(self: *BufferGroup, allocator: Allocator) void { free_buf_ring(self.ring.fd, self.br, self.buffers_count, self.group_id); allocator.free(self.buffers); allocator.free(self.heads); @@ -1698,7 +1698,7 @@ pub const BufferGroup = struct { } // Get buffer by id. - fn get_by_id(self: *BufferGroup, buffer_id: u16) []u8 { + pub fn get_by_id(self: *BufferGroup, buffer_id: u16) []u8 { const pos = self.buffer_size * buffer_id; return self.buffers[pos .. pos + self.buffer_size][self.heads[buffer_id]..]; } @@ -1767,7 +1767,7 @@ fn register_buf_ring( group_id: u16, flags: linux.io_uring_buf_reg.Flags, ) !void { - var reg = mem.zeroInit(linux.io_uring_buf_reg, .{ + var reg = std.mem.zeroInit(linux.io_uring_buf_reg, .{ .ring_addr = addr, .ring_entries = entries, .bgid = group_id, @@ -1784,7 +1784,7 @@ fn register_buf_ring( } fn unregister_buf_ring(fd: linux.fd_t, group_id: u16) !void { - var reg = mem.zeroInit(linux.io_uring_buf_reg, .{ + var reg = std.mem.zeroInit(linux.io_uring_buf_reg, .{ .bgid = group_id, }); const res = linux.io_uring_register( @@ -1851,2272 +1851,11 @@ pub fn buf_ring_advance(br: *linux.io_uring_buf_ring, count: u16) void { @atomicStore(u16, &br.tail, tail, .release); } -test "structs/offsets/entries" { - if (!is_linux) return error.SkipZigTest; - - try testing.expectEqual(@as(usize, 120), @sizeOf(linux.io_uring_params)); - try testing.expectEqual(@as(usize, 64), @sizeOf(linux.io_uring_sqe)); - try testing.expectEqual(@as(usize, 16), @sizeOf(linux.io_uring_cqe)); - - try testing.expectEqual(0, linux.IORING_OFF_SQ_RING); - try testing.expectEqual(0x8000000, linux.IORING_OFF_CQ_RING); - try testing.expectEqual(0x10000000, linux.IORING_OFF_SQES); - - try testing.expectError(error.EntriesZero, IoUring.init(0, 0)); - try testing.expectError(error.EntriesNotPowerOfTwo, IoUring.init(3, 0)); -} - -test "nop" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer { - ring.deinit(); - testing.expectEqual(@as(linux.fd_t, -1), ring.fd) catch @panic("test failed"); - } - - const sqe = try ring.nop(0xaaaaaaaa); - try testing.expectEqual(linux.io_uring_sqe{ - .opcode = .NOP, - .flags = 0, - .ioprio = 0, - .fd = 0, - .off = 0, - .addr = 0, - .len = 0, - .rw_flags = 0, - .user_data = 0xaaaaaaaa, - .buf_index = 0, - .personality = 0, - .splice_fd_in = 0, - .addr3 = 0, - .resv = 0, - }, sqe.*); - - try testing.expectEqual(@as(u32, 0), ring.sq.sqe_head); - try testing.expectEqual(@as(u32, 1), ring.sq.sqe_tail); - try testing.expectEqual(@as(u32, 0), ring.sq.tail.*); - try testing.expectEqual(@as(u32, 0), ring.cq.head.*); - try testing.expectEqual(@as(u32, 1), ring.sq_ready()); - try testing.expectEqual(@as(u32, 0), ring.cq_ready()); - - try testing.expectEqual(@as(u32, 1), try ring.submit()); - try testing.expectEqual(@as(u32, 1), ring.sq.sqe_head); - try testing.expectEqual(@as(u32, 1), ring.sq.sqe_tail); - try testing.expectEqual(@as(u32, 1), ring.sq.tail.*); - try testing.expectEqual(@as(u32, 0), ring.cq.head.*); - try testing.expectEqual(@as(u32, 0), ring.sq_ready()); - - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xaaaaaaaa, - .res = 0, - .flags = 0, - }, try ring.copy_cqe()); - try testing.expectEqual(@as(u32, 1), ring.cq.head.*); - try testing.expectEqual(@as(u32, 0), ring.cq_ready()); - - const sqe_barrier = try ring.nop(0xbbbbbbbb); - sqe_barrier.flags |= linux.IOSQE_IO_DRAIN; - try testing.expectEqual(@as(u32, 1), try ring.submit()); - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xbbbbbbbb, - .res = 0, - .flags = 0, - }, try ring.copy_cqe()); - try testing.expectEqual(@as(u32, 2), ring.sq.sqe_head); - try testing.expectEqual(@as(u32, 2), ring.sq.sqe_tail); - try testing.expectEqual(@as(u32, 2), ring.sq.tail.*); - try testing.expectEqual(@as(u32, 2), ring.cq.head.*); -} - -test "readv" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer posix.close(fd); - - // Linux Kernel 5.4 supports IORING_REGISTER_FILES but not sparse fd sets (i.e. an fd of -1). - // Linux Kernel 5.5 adds support for sparse fd sets. - // Compare: - // https://github.com/torvalds/linux/blob/v5.4/fs/io_uring.c#L3119-L3124 vs - // https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L6687-L6691 - // We therefore avoid stressing sparse fd sets here: - var registered_fds = [_]linux.fd_t{0} ** 1; - const fd_index = 0; - registered_fds[fd_index] = fd; - try ring.register_files(registered_fds[0..]); - - var buffer = [_]u8{42} ** 128; - var iovecs = [_]posix.iovec{posix.iovec{ .base = &buffer, .len = buffer.len }}; - const sqe = try ring.read(0xcccccccc, fd_index, .{ .iovecs = iovecs[0..] }, 0); - try testing.expectEqual(linux.IORING_OP.READV, sqe.opcode); - sqe.flags |= linux.IOSQE_FIXED_FILE; - - try testing.expectError(error.SubmissionQueueFull, ring.nop(0)); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xcccccccc, - .res = buffer.len, - .flags = 0, - }, try ring.copy_cqe()); - try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]); - - try ring.unregister_files(); -} - -test "writev/fsync/readv" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(4, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const path = "test_io_uring_writev_fsync_readv"; - const file = try tmp.dir.createFile(io, path, .{ .read = true, .truncate = true }); - defer file.close(io); - const fd = file.handle; - - const buffer_write = [_]u8{42} ** 128; - const iovecs_write = [_]posix.iovec_const{ - posix.iovec_const{ .base = &buffer_write, .len = buffer_write.len }, - }; - var buffer_read = [_]u8{0} ** 128; - var iovecs_read = [_]posix.iovec{ - posix.iovec{ .base = &buffer_read, .len = buffer_read.len }, - }; - - const sqe_writev = try ring.writev(0xdddddddd, fd, iovecs_write[0..], 17); - try testing.expectEqual(linux.IORING_OP.WRITEV, sqe_writev.opcode); - try testing.expectEqual(@as(u64, 17), sqe_writev.off); - sqe_writev.flags |= linux.IOSQE_IO_LINK; - - const sqe_fsync = try ring.fsync(0xeeeeeeee, fd, 0); - try testing.expectEqual(linux.IORING_OP.FSYNC, sqe_fsync.opcode); - try testing.expectEqual(fd, sqe_fsync.fd); - sqe_fsync.flags |= linux.IOSQE_IO_LINK; - - const sqe_readv = try ring.read(0xffffffff, fd, .{ .iovecs = iovecs_read[0..] }, 17); - try testing.expectEqual(linux.IORING_OP.READV, sqe_readv.opcode); - try testing.expectEqual(@as(u64, 17), sqe_readv.off); - - try testing.expectEqual(@as(u32, 3), ring.sq_ready()); - try testing.expectEqual(@as(u32, 3), try ring.submit_and_wait(3)); - try testing.expectEqual(@as(u32, 0), ring.sq_ready()); - try testing.expectEqual(@as(u32, 3), ring.cq_ready()); - - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xdddddddd, - .res = buffer_write.len, - .flags = 0, - }, try ring.copy_cqe()); - try testing.expectEqual(@as(u32, 2), ring.cq_ready()); - - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xeeeeeeee, - .res = 0, - .flags = 0, - }, try ring.copy_cqe()); - try testing.expectEqual(@as(u32, 1), ring.cq_ready()); - - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xffffffff, - .res = buffer_read.len, - .flags = 0, - }, try ring.copy_cqe()); - try testing.expectEqual(@as(u32, 0), ring.cq_ready()); - - try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); -} - -test "write/read" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(2, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - const path = "test_io_uring_write_read"; - const file = try tmp.dir.createFile(io, path, .{ .read = true, .truncate = true }); - defer file.close(io); - const fd = file.handle; - - const buffer_write = [_]u8{97} ** 20; - var buffer_read = [_]u8{98} ** 20; - const sqe_write = try ring.write(0x11111111, fd, buffer_write[0..], 10); - try testing.expectEqual(linux.IORING_OP.WRITE, sqe_write.opcode); - try testing.expectEqual(@as(u64, 10), sqe_write.off); - sqe_write.flags |= linux.IOSQE_IO_LINK; - const sqe_read = try ring.read(0x22222222, fd, .{ .buffer = buffer_read[0..] }, 10); - try testing.expectEqual(linux.IORING_OP.READ, sqe_read.opcode); - try testing.expectEqual(@as(u64, 10), sqe_read.off); - try testing.expectEqual(@as(u32, 2), try ring.submit()); - - const cqe_write = try ring.copy_cqe(); - const cqe_read = try ring.copy_cqe(); - // Prior to Linux Kernel 5.6 this is the only way to test for read/write support: - // https://lwn.net/Articles/809820/ - if (cqe_write.err() == .INVAL) return error.SkipZigTest; - if (cqe_read.err() == .INVAL) return error.SkipZigTest; - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x11111111, - .res = buffer_write.len, - .flags = 0, - }, cqe_write); - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x22222222, - .res = buffer_read.len, - .flags = 0, - }, cqe_read); - try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); -} - -test "splice/read" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(4, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var tmp = std.testing.tmpDir(.{}); - const path_src = "test_io_uring_splice_src"; - const file_src = try tmp.dir.createFile(io, path_src, .{ .read = true, .truncate = true }); - defer file_src.close(io); - const fd_src = file_src.handle; - - const path_dst = "test_io_uring_splice_dst"; - const file_dst = try tmp.dir.createFile(io, path_dst, .{ .read = true, .truncate = true }); - defer file_dst.close(io); - const fd_dst = file_dst.handle; - - const buffer_write = [_]u8{97} ** 20; - var buffer_read = [_]u8{98} ** 20; - _ = try file_src.write(&buffer_write); - - const fds = try posix.pipe(); - const pipe_offset: u64 = std.math.maxInt(u64); - - const sqe_splice_to_pipe = try ring.splice(0x11111111, fd_src, 0, fds[1], pipe_offset, buffer_write.len); - try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_to_pipe.opcode); - try testing.expectEqual(@as(u64, 0), sqe_splice_to_pipe.addr); - try testing.expectEqual(pipe_offset, sqe_splice_to_pipe.off); - sqe_splice_to_pipe.flags |= linux.IOSQE_IO_LINK; - - const sqe_splice_from_pipe = try ring.splice(0x22222222, fds[0], pipe_offset, fd_dst, 10, buffer_write.len); - try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_from_pipe.opcode); - try testing.expectEqual(pipe_offset, sqe_splice_from_pipe.addr); - try testing.expectEqual(@as(u64, 10), sqe_splice_from_pipe.off); - sqe_splice_from_pipe.flags |= linux.IOSQE_IO_LINK; - - const sqe_read = try ring.read(0x33333333, fd_dst, .{ .buffer = buffer_read[0..] }, 10); - try testing.expectEqual(linux.IORING_OP.READ, sqe_read.opcode); - try testing.expectEqual(@as(u64, 10), sqe_read.off); - try testing.expectEqual(@as(u32, 3), try ring.submit()); - - const cqe_splice_to_pipe = try ring.copy_cqe(); - const cqe_splice_from_pipe = try ring.copy_cqe(); - const cqe_read = try ring.copy_cqe(); - // Prior to Linux Kernel 5.6 this is the only way to test for splice/read support: - // https://lwn.net/Articles/809820/ - if (cqe_splice_to_pipe.err() == .INVAL) return error.SkipZigTest; - if (cqe_splice_from_pipe.err() == .INVAL) return error.SkipZigTest; - if (cqe_read.err() == .INVAL) return error.SkipZigTest; - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x11111111, - .res = buffer_write.len, - .flags = 0, - }, cqe_splice_to_pipe); - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x22222222, - .res = buffer_write.len, - .flags = 0, - }, cqe_splice_from_pipe); - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x33333333, - .res = buffer_read.len, - .flags = 0, - }, cqe_read); - try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); -} - -test "write_fixed/read_fixed" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(2, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const path = "test_io_uring_write_read_fixed"; - const file = try tmp.dir.createFile(io, path, .{ .read = true, .truncate = true }); - defer file.close(io); - const fd = file.handle; - - var raw_buffers: [2][11]u8 = undefined; - // First buffer will be written to the file. - @memset(&raw_buffers[0], 'z'); - raw_buffers[0][0.."foobar".len].* = "foobar".*; - - var buffers = [2]posix.iovec{ - .{ .base = &raw_buffers[0], .len = raw_buffers[0].len }, - .{ .base = &raw_buffers[1], .len = raw_buffers[1].len }, - }; - ring.register_buffers(&buffers) catch |err| switch (err) { - error.SystemResources => { - // See https://github.com/ziglang/zig/issues/15362 - return error.SkipZigTest; - }, - else => |e| return e, - }; - - const sqe_write = try ring.write_fixed(0x45454545, fd, &buffers[0], 3, 0); - try testing.expectEqual(linux.IORING_OP.WRITE_FIXED, sqe_write.opcode); - try testing.expectEqual(@as(u64, 3), sqe_write.off); - sqe_write.flags |= linux.IOSQE_IO_LINK; - - const sqe_read = try ring.read_fixed(0x12121212, fd, &buffers[1], 0, 1); - try testing.expectEqual(linux.IORING_OP.READ_FIXED, sqe_read.opcode); - try testing.expectEqual(@as(u64, 0), sqe_read.off); - - try testing.expectEqual(@as(u32, 2), try ring.submit()); - - const cqe_write = try ring.copy_cqe(); - const cqe_read = try ring.copy_cqe(); - - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x45454545, - .res = @as(i32, @intCast(buffers[0].len)), - .flags = 0, - }, cqe_write); - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x12121212, - .res = @as(i32, @intCast(buffers[1].len)), - .flags = 0, - }, cqe_read); - - try testing.expectEqualSlices(u8, "\x00\x00\x00", buffers[1].base[0..3]); - try testing.expectEqualSlices(u8, "foobar", buffers[1].base[3..9]); - try testing.expectEqualSlices(u8, "zz", buffers[1].base[9..11]); -} - -test "openat" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const path = "test_io_uring_openat"; - - // Workaround for LLVM bug: https://github.com/ziglang/zig/issues/12014 - const path_addr = if (builtin.zig_backend == .stage2_llvm) p: { - var workaround = path; - _ = &workaround; - break :p @intFromPtr(workaround); - } else @intFromPtr(path); - - const flags: linux.O = .{ .CLOEXEC = true, .ACCMODE = .RDWR, .CREAT = true }; - const mode: posix.mode_t = 0o666; - const sqe_openat = try ring.openat(0x33333333, tmp.dir.fd, path, flags, mode); - try testing.expectEqual(linux.io_uring_sqe{ - .opcode = .OPENAT, - .flags = 0, - .ioprio = 0, - .fd = tmp.dir.fd, - .off = 0, - .addr = path_addr, - .len = mode, - .rw_flags = @bitCast(flags), - .user_data = 0x33333333, - .buf_index = 0, - .personality = 0, - .splice_fd_in = 0, - .addr3 = 0, - .resv = 0, - }, sqe_openat.*); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe_openat = try ring.copy_cqe(); - try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data); - if (cqe_openat.err() == .INVAL) return error.SkipZigTest; - if (cqe_openat.err() == .BADF) return error.SkipZigTest; - if (cqe_openat.res <= 0) std.debug.print("\ncqe_openat.res={}\n", .{cqe_openat.res}); - try testing.expect(cqe_openat.res > 0); - try testing.expectEqual(@as(u32, 0), cqe_openat.flags); - - posix.close(cqe_openat.res); -} - -test "close" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const path = "test_io_uring_close"; - const file = try tmp.dir.createFile(io, path, .{}); - errdefer file.close(io); - - const sqe_close = try ring.close(0x44444444, file.handle); - try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode); - try testing.expectEqual(file.handle, sqe_close.fd); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe_close = try ring.copy_cqe(); - if (cqe_close.err() == .INVAL) return error.SkipZigTest; - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x44444444, - .res = 0, - .flags = 0, - }, cqe_close); -} - -test "accept/connect/send/recv" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(16, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const socket_test_harness = try createSocketTestHarness(&ring); - defer socket_test_harness.close(io); - - const buffer_send = [_]u8{ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 }; - var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 }; - - const sqe_send = try ring.send(0xeeeeeeee, socket_test_harness.client, buffer_send[0..], 0); - sqe_send.flags |= linux.IOSQE_IO_LINK; - _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0); - try testing.expectEqual(@as(u32, 2), try ring.submit()); - - const cqe_send = try ring.copy_cqe(); - if (cqe_send.err() == .INVAL) return error.SkipZigTest; - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xeeeeeeee, - .res = buffer_send.len, - .flags = 0, - }, cqe_send); - - const cqe_recv = try ring.copy_cqe(); - if (cqe_recv.err() == .INVAL) return error.SkipZigTest; - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xffffffff, - .res = buffer_recv.len, - // ignore IORING_CQE_F_SOCK_NONEMPTY since it is only set on some systems - .flags = cqe_recv.flags & linux.IORING_CQE_F_SOCK_NONEMPTY, - }, cqe_recv); - - try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]); -} - -test "sendmsg/recvmsg" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(2, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var address_server: linux.sockaddr.in = .{ - .port = 0, - .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), - }; - - const server = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0); - defer posix.close(server); - try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1))); - try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(server, addrAny(&address_server), @sizeOf(linux.sockaddr.in)); - - // set address_server to the OS-chosen IP/port. - var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); - try posix.getsockname(server, addrAny(&address_server), &slen); - - const client = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0); - defer posix.close(client); - - const buffer_send = [_]u8{42} ** 128; - const iovecs_send = [_]posix.iovec_const{ - posix.iovec_const{ .base = &buffer_send, .len = buffer_send.len }, - }; - const msg_send: linux.msghdr_const = .{ - .name = addrAny(&address_server), - .namelen = @sizeOf(linux.sockaddr.in), - .iov = &iovecs_send, - .iovlen = 1, - .control = null, - .controllen = 0, - .flags = 0, - }; - const sqe_sendmsg = try ring.sendmsg(0x11111111, client, &msg_send, 0); - sqe_sendmsg.flags |= linux.IOSQE_IO_LINK; - try testing.expectEqual(linux.IORING_OP.SENDMSG, sqe_sendmsg.opcode); - try testing.expectEqual(client, sqe_sendmsg.fd); - - var buffer_recv = [_]u8{0} ** 128; - var iovecs_recv = [_]posix.iovec{ - posix.iovec{ .base = &buffer_recv, .len = buffer_recv.len }, - }; - var address_recv: linux.sockaddr.in = .{ - .port = 0, - .addr = 0, - }; - var msg_recv: linux.msghdr = .{ - .name = addrAny(&address_recv), - .namelen = @sizeOf(linux.sockaddr.in), - .iov = &iovecs_recv, - .iovlen = 1, - .control = null, - .controllen = 0, - .flags = 0, - }; - const sqe_recvmsg = try ring.recvmsg(0x22222222, server, &msg_recv, 0); - try testing.expectEqual(linux.IORING_OP.RECVMSG, sqe_recvmsg.opcode); - try testing.expectEqual(server, sqe_recvmsg.fd); - - try testing.expectEqual(@as(u32, 2), ring.sq_ready()); - try testing.expectEqual(@as(u32, 2), try ring.submit_and_wait(2)); - try testing.expectEqual(@as(u32, 0), ring.sq_ready()); - try testing.expectEqual(@as(u32, 2), ring.cq_ready()); - - const cqe_sendmsg = try ring.copy_cqe(); - if (cqe_sendmsg.res == -@as(i32, @intFromEnum(linux.E.INVAL))) return error.SkipZigTest; - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x11111111, - .res = buffer_send.len, - .flags = 0, - }, cqe_sendmsg); - - const cqe_recvmsg = try ring.copy_cqe(); - if (cqe_recvmsg.res == -@as(i32, @intFromEnum(linux.E.INVAL))) return error.SkipZigTest; - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x22222222, - .res = buffer_recv.len, - // ignore IORING_CQE_F_SOCK_NONEMPTY since it is set non-deterministically - .flags = cqe_recvmsg.flags & linux.IORING_CQE_F_SOCK_NONEMPTY, - }, cqe_recvmsg); - - try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]); -} - -test "timeout (after a relative time)" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const ms = 10; - const margin = 5; - const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 }; - - const started = try std.Io.Clock.awake.now(io); - const sqe = try ring.timeout(0x55555555, &ts, 0, 0); - try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - const cqe = try ring.copy_cqe(); - const stopped = try std.Io.Clock.awake.now(io); - - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x55555555, - .res = -@as(i32, @intFromEnum(linux.E.TIME)), - .flags = 0, - }, cqe); - - // Tests should not depend on timings: skip test if outside margin. - const ms_elapsed = started.durationTo(stopped).toMilliseconds(); - if (ms_elapsed > margin) return error.SkipZigTest; -} - -test "timeout (after a number of completions)" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(2, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const ts: linux.kernel_timespec = .{ .sec = 3, .nsec = 0 }; - const count_completions: u64 = 1; - const sqe_timeout = try ring.timeout(0x66666666, &ts, count_completions, 0); - try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode); - try testing.expectEqual(count_completions, sqe_timeout.off); - _ = try ring.nop(0x77777777); - try testing.expectEqual(@as(u32, 2), try ring.submit()); - - const cqe_nop = try ring.copy_cqe(); - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x77777777, - .res = 0, - .flags = 0, - }, cqe_nop); - - const cqe_timeout = try ring.copy_cqe(); - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x66666666, - .res = 0, - .flags = 0, - }, cqe_timeout); -} - -test "timeout_remove" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(2, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const ts: linux.kernel_timespec = .{ .sec = 3, .nsec = 0 }; - const sqe_timeout = try ring.timeout(0x88888888, &ts, 0, 0); - try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode); - try testing.expectEqual(@as(u64, 0x88888888), sqe_timeout.user_data); - - const sqe_timeout_remove = try ring.timeout_remove(0x99999999, 0x88888888, 0); - try testing.expectEqual(linux.IORING_OP.TIMEOUT_REMOVE, sqe_timeout_remove.opcode); - try testing.expectEqual(@as(u64, 0x88888888), sqe_timeout_remove.addr); - try testing.expectEqual(@as(u64, 0x99999999), sqe_timeout_remove.user_data); - - try testing.expectEqual(@as(u32, 2), try ring.submit()); - - // The order in which the CQE arrive is not clearly documented and it changed with kernel 5.18: - // * kernel 5.10 gives user data 0x88888888 first, 0x99999999 second - // * kernel 5.18 gives user data 0x99999999 first, 0x88888888 second - - var cqes: [2]linux.io_uring_cqe = undefined; - cqes[0] = try ring.copy_cqe(); - cqes[1] = try ring.copy_cqe(); - - for (cqes) |cqe| { - // IORING_OP_TIMEOUT_REMOVE is not supported by this kernel version: - // Timeout remove operations set the fd to -1, which results in EBADF before EINVAL. - // We use IORING_FEAT_RW_CUR_POS as a safety check here to make sure we are at least pre-5.6. - // We don't want to skip this test for newer kernels. - if (cqe.user_data == 0x99999999 and - cqe.err() == .BADF and - (ring.features & linux.IORING_FEAT_RW_CUR_POS) == 0) - { - return error.SkipZigTest; - } - - try testing.expect(cqe.user_data == 0x88888888 or cqe.user_data == 0x99999999); - - if (cqe.user_data == 0x88888888) { - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x88888888, - .res = -@as(i32, @intFromEnum(linux.E.CANCELED)), - .flags = 0, - }, cqe); - } else if (cqe.user_data == 0x99999999) { - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x99999999, - .res = 0, - .flags = 0, - }, cqe); - } - } -} - -test "accept/connect/recv/link_timeout" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(16, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const socket_test_harness = try createSocketTestHarness(&ring); - defer socket_test_harness.close(io); - - var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 }; - - const sqe_recv = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0); - sqe_recv.flags |= linux.IOSQE_IO_LINK; - - const ts = linux.kernel_timespec{ .sec = 0, .nsec = 1000000 }; - _ = try ring.link_timeout(0x22222222, &ts, 0); - - const nr_wait = try ring.submit(); - try testing.expectEqual(@as(u32, 2), nr_wait); - - var i: usize = 0; - while (i < nr_wait) : (i += 1) { - const cqe = try ring.copy_cqe(); - switch (cqe.user_data) { - 0xffffffff => { - if (cqe.res != -@as(i32, @intFromEnum(linux.E.INTR)) and - cqe.res != -@as(i32, @intFromEnum(linux.E.CANCELED))) - { - std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res }); - try testing.expect(false); - } - }, - 0x22222222 => { - if (cqe.res != -@as(i32, @intFromEnum(linux.E.ALREADY)) and - cqe.res != -@as(i32, @intFromEnum(linux.E.TIME))) - { - std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res }); - try testing.expect(false); - } - }, - else => @panic("should not happen"), - } - } -} - -test "fallocate" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const path = "test_io_uring_fallocate"; - const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); - defer file.close(io); - - try testing.expectEqual(@as(u64, 0), (try file.stat(io)).size); - - const len: u64 = 65536; - const sqe = try ring.fallocate(0xaaaaaaaa, file.handle, 0, 0, len); - try testing.expectEqual(linux.IORING_OP.FALLOCATE, sqe.opcode); - try testing.expectEqual(file.handle, sqe.fd); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - // This kernel's io_uring does not yet implement fallocate(): - .INVAL => return error.SkipZigTest, - // This kernel does not implement fallocate(): - .NOSYS => return error.SkipZigTest, - // The filesystem containing the file referred to by fd does not support this operation; - // or the mode is not supported by the filesystem containing the file referred to by fd: - .OPNOTSUPP => return error.SkipZigTest, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xaaaaaaaa, - .res = 0, - .flags = 0, - }, cqe); - - try testing.expectEqual(len, (try file.stat(io)).size); -} - -test "statx" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - const path = "test_io_uring_statx"; - const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); - defer file.close(io); - - try testing.expectEqual(@as(u64, 0), (try file.stat(io)).size); - - try file.writeAll("foobar"); - - var buf: linux.Statx = undefined; - const sqe = try ring.statx( - 0xaaaaaaaa, - tmp.dir.fd, - path, - 0, - .{ .SIZE = true }, - &buf, - ); - try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode); - try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - // This kernel's io_uring does not yet implement statx(): - .INVAL => return error.SkipZigTest, - // This kernel does not implement statx(): - .NOSYS => return error.SkipZigTest, - // The filesystem containing the file referred to by fd does not support this operation; - // or the mode is not supported by the filesystem containing the file referred to by fd: - .OPNOTSUPP => return error.SkipZigTest, - // not supported on older kernels (5.4) - .BADF => return error.SkipZigTest, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xaaaaaaaa, - .res = 0, - .flags = 0, - }, cqe); - - try testing.expect(buf.mask.SIZE); - try testing.expectEqual(@as(u64, 6), buf.size); -} - -test "accept/connect/recv/cancel" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(16, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const socket_test_harness = try createSocketTestHarness(&ring); - defer socket_test_harness.close(io); - - var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 }; - - _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const sqe_cancel = try ring.cancel(0x99999999, 0xffffffff, 0); - try testing.expectEqual(linux.IORING_OP.ASYNC_CANCEL, sqe_cancel.opcode); - try testing.expectEqual(@as(u64, 0xffffffff), sqe_cancel.addr); - try testing.expectEqual(@as(u64, 0x99999999), sqe_cancel.user_data); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - var cqe_recv = try ring.copy_cqe(); - if (cqe_recv.err() == .INVAL) return error.SkipZigTest; - var cqe_cancel = try ring.copy_cqe(); - if (cqe_cancel.err() == .INVAL) return error.SkipZigTest; - - // The recv/cancel CQEs may arrive in any order, the recv CQE will sometimes come first: - if (cqe_recv.user_data == 0x99999999 and cqe_cancel.user_data == 0xffffffff) { - const a = cqe_recv; - const b = cqe_cancel; - cqe_recv = b; - cqe_cancel = a; - } - - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xffffffff, - .res = -@as(i32, @intFromEnum(linux.E.CANCELED)), - .flags = 0, - }, cqe_recv); - - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x99999999, - .res = 0, - .flags = 0, - }, cqe_cancel); -} - -test "register_files_update" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer posix.close(fd); - - var registered_fds = [_]linux.fd_t{0} ** 2; - const fd_index = 0; - const fd_index2 = 1; - registered_fds[fd_index] = fd; - registered_fds[fd_index2] = -1; - - ring.register_files(registered_fds[0..]) catch |err| switch (err) { - // Happens when the kernel doesn't support sparse entry (-1) in the file descriptors array. - error.FileDescriptorInvalid => return error.SkipZigTest, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - }; - - // Test IORING_REGISTER_FILES_UPDATE - // Only available since Linux 5.5 - - const fd2 = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer posix.close(fd2); - - registered_fds[fd_index] = fd2; - registered_fds[fd_index2] = -1; - try ring.register_files_update(0, registered_fds[0..]); - - var buffer = [_]u8{42} ** 128; - { - const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0); - try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); - sqe.flags |= linux.IOSQE_FIXED_FILE; - - try testing.expectEqual(@as(u32, 1), try ring.submit()); - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xcccccccc, - .res = buffer.len, - .flags = 0, - }, try ring.copy_cqe()); - try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]); - } - - // Test with a non-zero offset - - registered_fds[fd_index] = -1; - registered_fds[fd_index2] = -1; - try ring.register_files_update(1, registered_fds[1..]); - - { - // Next read should still work since fd_index in the registered file descriptors hasn't been updated yet. - const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0); - try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); - sqe.flags |= linux.IOSQE_FIXED_FILE; - - try testing.expectEqual(@as(u32, 1), try ring.submit()); - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xcccccccc, - .res = buffer.len, - .flags = 0, - }, try ring.copy_cqe()); - try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]); - } - - try ring.register_files_update(0, registered_fds[0..]); - - { - // Now this should fail since both fds are sparse (-1) - const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0); - try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); - sqe.flags |= linux.IOSQE_FIXED_FILE; - - try testing.expectEqual(@as(u32, 1), try ring.submit()); - const cqe = try ring.copy_cqe(); - try testing.expectEqual(linux.E.BADF, cqe.err()); - } - - try ring.unregister_files(); -} - -test "shutdown" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(16, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var address: linux.sockaddr.in = .{ - .port = 0, - .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), - }; - - // Socket bound, expect shutdown to work - { - const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - defer posix.close(server); - try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(server, addrAny(&address), @sizeOf(linux.sockaddr.in)); - try posix.listen(server, 1); - - // set address to the OS-chosen IP/port. - var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); - try posix.getsockname(server, addrAny(&address), &slen); - - const shutdown_sqe = try ring.shutdown(0x445445445, server, linux.SHUT.RD); - try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode); - try testing.expectEqual(@as(i32, server), shutdown_sqe.fd); - - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - // This kernel's io_uring does not yet implement shutdown (kernel version < 5.11) - .INVAL => return error.SkipZigTest, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x445445445, - .res = 0, - .flags = 0, - }, cqe); - } - - // Socket not bound, expect to fail with ENOTCONN - { - const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - defer posix.close(server); - - const shutdown_sqe = ring.shutdown(0x445445445, server, linux.SHUT.RD) catch |err| switch (err) { - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - }; - try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode); - try testing.expectEqual(@as(i32, server), shutdown_sqe.fd); - - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - try testing.expectEqual(@as(u64, 0x445445445), cqe.user_data); - try testing.expectEqual(linux.E.NOTCONN, cqe.err()); - } -} - -test "renameat" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const old_path = "test_io_uring_renameat_old"; - const new_path = "test_io_uring_renameat_new"; - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - // Write old file with data - - const old_file = try tmp.dir.createFile(io, old_path, .{ .truncate = true, .mode = 0o666 }); - defer old_file.close(io); - try old_file.writeAll("hello"); - - // Submit renameat - - const sqe = try ring.renameat( - 0x12121212, - tmp.dir.fd, - old_path, - tmp.dir.fd, - new_path, - 0, - ); - try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode); - try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); - try testing.expectEqual(@as(i32, tmp.dir.fd), @as(i32, @bitCast(sqe.len))); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - // This kernel's io_uring does not yet implement renameat (kernel version < 5.11) - .BADF, .INVAL => return error.SkipZigTest, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x12121212, - .res = 0, - .flags = 0, - }, cqe); - - // Validate that the old file doesn't exist anymore - try testing.expectError(error.FileNotFound, tmp.dir.openFile(io, old_path, .{})); - - // Validate that the new file exists with the proper content - var new_file_data: [16]u8 = undefined; - try testing.expectEqualStrings("hello", try tmp.dir.readFile(new_path, &new_file_data)); -} - -test "unlinkat" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const path = "test_io_uring_unlinkat"; - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - // Write old file with data - - const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); - defer file.close(io); - - // Submit unlinkat - - const sqe = try ring.unlinkat( - 0x12121212, - tmp.dir.fd, - path, - 0, - ); - try testing.expectEqual(linux.IORING_OP.UNLINKAT, sqe.opcode); - try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - // This kernel's io_uring does not yet implement unlinkat (kernel version < 5.11) - .BADF, .INVAL => return error.SkipZigTest, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x12121212, - .res = 0, - .flags = 0, - }, cqe); - - // Validate that the file doesn't exist anymore - _ = tmp.dir.openFile(io, path, .{}) catch |err| switch (err) { - error.FileNotFound => {}, - else => std.debug.panic("unexpected error: {}", .{err}), - }; -} - -test "mkdirat" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const path = "test_io_uring_mkdirat"; - - // Submit mkdirat - - const sqe = try ring.mkdirat( - 0x12121212, - tmp.dir.fd, - path, - 0o0755, - ); - try testing.expectEqual(linux.IORING_OP.MKDIRAT, sqe.opcode); - try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - // This kernel's io_uring does not yet implement mkdirat (kernel version < 5.15) - .BADF, .INVAL => return error.SkipZigTest, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x12121212, - .res = 0, - .flags = 0, - }, cqe); - - // Validate that the directory exist - _ = try tmp.dir.openDir(io, path, .{}); -} - -test "symlinkat" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const path = "test_io_uring_symlinkat"; - const link_path = "test_io_uring_symlinkat_link"; - - const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); - defer file.close(io); - - // Submit symlinkat - - const sqe = try ring.symlinkat( - 0x12121212, - path, - tmp.dir.fd, - link_path, - ); - try testing.expectEqual(linux.IORING_OP.SYMLINKAT, sqe.opcode); - try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - // This kernel's io_uring does not yet implement symlinkat (kernel version < 5.15) - .BADF, .INVAL => return error.SkipZigTest, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x12121212, - .res = 0, - .flags = 0, - }, cqe); - - // Validate that the symlink exist - _ = try tmp.dir.openFile(io, link_path, .{}); -} - -test "linkat" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const first_path = "test_io_uring_linkat_first"; - const second_path = "test_io_uring_linkat_second"; - - // Write file with data - - const first_file = try tmp.dir.createFile(io, first_path, .{ .truncate = true, .mode = 0o666 }); - defer first_file.close(io); - try first_file.writeAll("hello"); - - // Submit linkat - - const sqe = try ring.linkat( - 0x12121212, - tmp.dir.fd, - first_path, - tmp.dir.fd, - second_path, - 0, - ); - try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode); - try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); - try testing.expectEqual(@as(i32, tmp.dir.fd), @as(i32, @bitCast(sqe.len))); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - // This kernel's io_uring does not yet implement linkat (kernel version < 5.15) - .BADF, .INVAL => return error.SkipZigTest, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0x12121212, - .res = 0, - .flags = 0, - }, cqe); - - // Validate the second file - var second_file_data: [16]u8 = undefined; - try testing.expectEqualStrings("hello", try tmp.dir.readFile(second_path, &second_file_data)); -} - -test "provide_buffers: read" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer posix.close(fd); - - const group_id = 1337; - const buffer_id = 0; - - const buffer_len = 128; - - var buffers: [4][buffer_len]u8 = undefined; - - // Provide 4 buffers - - { - const sqe = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id); - try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode); - try testing.expectEqual(@as(i32, buffers.len), sqe.fd); - try testing.expectEqual(@as(u32, buffers[0].len), sqe.len); - try testing.expectEqual(@as(u16, group_id), sqe.buf_index); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - // Happens when the kernel is < 5.7 - .INVAL, .BADF => return error.SkipZigTest, - .SUCCESS => {}, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data); - } - - // Do 4 reads which should consume all buffers - - var i: usize = 0; - while (i < buffers.len) : (i += 1) { - const sqe = try ring.read(0xdededede, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); - try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); - try testing.expectEqual(@as(i32, fd), sqe.fd); - try testing.expectEqual(@as(u64, 0), sqe.addr); - try testing.expectEqual(@as(u32, buffer_len), sqe.len); - try testing.expectEqual(@as(u16, group_id), sqe.buf_index); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - - try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); - const used_buffer_id = cqe.flags >> 16; - try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3); - try testing.expectEqual(@as(i32, buffer_len), cqe.res); - - try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data); - try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]); - } - - // This read should fail - - { - const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); - try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); - try testing.expectEqual(@as(i32, fd), sqe.fd); - try testing.expectEqual(@as(u64, 0), sqe.addr); - try testing.expectEqual(@as(u32, buffer_len), sqe.len); - try testing.expectEqual(@as(u16, group_id), sqe.buf_index); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - // Expected - .NOBUFS => {}, - .SUCCESS => std.debug.panic("unexpected success", .{}), - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data); - } - - // Provide 1 buffer again - - // Deliberately put something we don't expect in the buffers - @memset(mem.sliceAsBytes(&buffers), 42); - - const reprovided_buffer_id = 2; - - { - _ = try ring.provide_buffers(0xabababab, @as([*]u8, @ptrCast(&buffers[reprovided_buffer_id])), buffer_len, 1, group_id, reprovided_buffer_id); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - } - - // Final read which should work - - { - const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); - try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); - try testing.expectEqual(@as(i32, fd), sqe.fd); - try testing.expectEqual(@as(u64, 0), sqe.addr); - try testing.expectEqual(@as(u32, buffer_len), sqe.len); - try testing.expectEqual(@as(u16, group_id), sqe.buf_index); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - - try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); - const used_buffer_id = cqe.flags >> 16; - try testing.expectEqual(used_buffer_id, reprovided_buffer_id); - try testing.expectEqual(@as(i32, buffer_len), cqe.res); - try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data); - try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]); - } -} - -test "remove_buffers" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer posix.close(fd); - - const group_id = 1337; - const buffer_id = 0; - - const buffer_len = 128; - - var buffers: [4][buffer_len]u8 = undefined; - - // Provide 4 buffers - - { - _ = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .INVAL, .BADF => return error.SkipZigTest, - .SUCCESS => {}, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data); - } - - // Remove 3 buffers - - { - const sqe = try ring.remove_buffers(0xbababababa, 3, group_id); - try testing.expectEqual(linux.IORING_OP.REMOVE_BUFFERS, sqe.opcode); - try testing.expectEqual(@as(i32, 3), sqe.fd); - try testing.expectEqual(@as(u64, 0), sqe.addr); - try testing.expectEqual(@as(u16, group_id), sqe.buf_index); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(@as(u64, 0xbababababa), cqe.user_data); - } - - // This read should work - - { - _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - - try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); - const used_buffer_id = cqe.flags >> 16; - try testing.expect(used_buffer_id >= 0 and used_buffer_id < 4); - try testing.expectEqual(@as(i32, buffer_len), cqe.res); - try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data); - try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]); - } - - // Final read should _not_ work - - { - _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - // Expected - .NOBUFS => {}, - .SUCCESS => std.debug.panic("unexpected success", .{}), - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - } -} - -test "provide_buffers: accept/connect/send/recv" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(16, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const group_id = 1337; - const buffer_id = 0; - - const buffer_len = 128; - var buffers: [4][buffer_len]u8 = undefined; - - // Provide 4 buffers - - { - const sqe = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id); - try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode); - try testing.expectEqual(@as(i32, buffers.len), sqe.fd); - try testing.expectEqual(@as(u32, buffer_len), sqe.len); - try testing.expectEqual(@as(u16, group_id), sqe.buf_index); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - // Happens when the kernel is < 5.7 - .INVAL => return error.SkipZigTest, - // Happens on the kernel 5.4 - .BADF => return error.SkipZigTest, - .SUCCESS => {}, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data); - } - - const socket_test_harness = try createSocketTestHarness(&ring); - defer socket_test_harness.close(io); - - // Do 4 send on the socket - - { - var i: usize = 0; - while (i < buffers.len) : (i += 1) { - _ = try ring.send(0xdeaddead, socket_test_harness.server, &([_]u8{'z'} ** buffer_len), 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - } - - var cqes: [4]linux.io_uring_cqe = undefined; - try testing.expectEqual(@as(u32, 4), try ring.copy_cqes(&cqes, 4)); - } - - // Do 4 recv which should consume all buffers - - // Deliberately put something we don't expect in the buffers - @memset(mem.sliceAsBytes(&buffers), 1); - - var i: usize = 0; - while (i < buffers.len) : (i += 1) { - const sqe = try ring.recv(0xdededede, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); - try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode); - try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd); - try testing.expectEqual(@as(u64, 0), sqe.addr); - try testing.expectEqual(@as(u32, buffer_len), sqe.len); - try testing.expectEqual(@as(u16, group_id), sqe.buf_index); - try testing.expectEqual(@as(u32, 0), sqe.rw_flags); - try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - - try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); - const used_buffer_id = cqe.flags >> 16; - try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3); - try testing.expectEqual(@as(i32, buffer_len), cqe.res); - - try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data); - const buffer = buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]; - try testing.expectEqualSlices(u8, &([_]u8{'z'} ** buffer_len), buffer); - } - - // This recv should fail - - { - const sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); - try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode); - try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd); - try testing.expectEqual(@as(u64, 0), sqe.addr); - try testing.expectEqual(@as(u32, buffer_len), sqe.len); - try testing.expectEqual(@as(u16, group_id), sqe.buf_index); - try testing.expectEqual(@as(u32, 0), sqe.rw_flags); - try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - // Expected - .NOBUFS => {}, - .SUCCESS => std.debug.panic("unexpected success", .{}), - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data); - } - - // Provide 1 buffer again - - const reprovided_buffer_id = 2; - - { - _ = try ring.provide_buffers(0xabababab, @as([*]u8, @ptrCast(&buffers[reprovided_buffer_id])), buffer_len, 1, group_id, reprovided_buffer_id); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - } - - // Redo 1 send on the server socket - - { - _ = try ring.send(0xdeaddead, socket_test_harness.server, &([_]u8{'w'} ** buffer_len), 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - _ = try ring.copy_cqe(); - } - - // Final recv which should work - - // Deliberately put something we don't expect in the buffers - @memset(mem.sliceAsBytes(&buffers), 1); - - { - const sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); - try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode); - try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd); - try testing.expectEqual(@as(u64, 0), sqe.addr); - try testing.expectEqual(@as(u32, buffer_len), sqe.len); - try testing.expectEqual(@as(u16, group_id), sqe.buf_index); - try testing.expectEqual(@as(u32, 0), sqe.rw_flags); - try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - const cqe = try ring.copy_cqe(); - switch (cqe.err()) { - .SUCCESS => {}, - else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), - } - - try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); - const used_buffer_id = cqe.flags >> 16; - try testing.expectEqual(used_buffer_id, reprovided_buffer_id); - try testing.expectEqual(@as(i32, buffer_len), cqe.res); - try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data); - const buffer = buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]; - try testing.expectEqualSlices(u8, &([_]u8{'w'} ** buffer_len), buffer); - } -} - -/// Used for testing server/client interactions. -const SocketTestHarness = struct { - listener: posix.socket_t, - server: posix.socket_t, - client: posix.socket_t, - - fn close(self: SocketTestHarness) void { - posix.close(self.client); - posix.close(self.listener); - } -}; - -fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness { - // Create a TCP server socket - var address: linux.sockaddr.in = .{ - .port = 0, - .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), - }; - const listener_socket = try createListenerSocket(&address); - errdefer posix.close(listener_socket); - - // Submit 1 accept - var accept_addr: posix.sockaddr = undefined; - var accept_addr_len: posix.socklen_t = @sizeOf(@TypeOf(accept_addr)); - _ = try ring.accept(0xaaaaaaaa, listener_socket, &accept_addr, &accept_addr_len, 0); - - // Create a TCP client socket - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - errdefer posix.close(client); - _ = try ring.connect(0xcccccccc, client, addrAny(&address), @sizeOf(linux.sockaddr.in)); - - try testing.expectEqual(@as(u32, 2), try ring.submit()); - - var cqe_accept = try ring.copy_cqe(); - if (cqe_accept.err() == .INVAL) return error.SkipZigTest; - var cqe_connect = try ring.copy_cqe(); - if (cqe_connect.err() == .INVAL) return error.SkipZigTest; - - // The accept/connect CQEs may arrive in any order, the connect CQE will sometimes come first: - if (cqe_accept.user_data == 0xcccccccc and cqe_connect.user_data == 0xaaaaaaaa) { - const a = cqe_accept; - const b = cqe_connect; - cqe_accept = b; - cqe_connect = a; - } - - try testing.expectEqual(@as(u64, 0xaaaaaaaa), cqe_accept.user_data); - if (cqe_accept.res <= 0) std.debug.print("\ncqe_accept.res={}\n", .{cqe_accept.res}); - try testing.expect(cqe_accept.res > 0); - try testing.expectEqual(@as(u32, 0), cqe_accept.flags); - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xcccccccc, - .res = 0, - .flags = 0, - }, cqe_connect); - - // All good - - return SocketTestHarness{ - .listener = listener_socket, - .server = cqe_accept.res, - .client = client, - }; -} - -fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t { - const kernel_backlog = 1; - const listener_socket = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - errdefer posix.close(listener_socket); - - try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); - try posix.bind(listener_socket, addrAny(address), @sizeOf(linux.sockaddr.in)); - try posix.listen(listener_socket, kernel_backlog); - - // set address to the OS-chosen IP/port. - var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); - try posix.getsockname(listener_socket, addrAny(address), &slen); - - return listener_socket; -} - -test "accept multishot" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(16, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var address: linux.sockaddr.in = .{ - .port = 0, - .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), - }; - const listener_socket = try createListenerSocket(&address); - defer posix.close(listener_socket); - - // submit multishot accept operation - var addr: posix.sockaddr = undefined; - var addr_len: posix.socklen_t = @sizeOf(@TypeOf(addr)); - const userdata: u64 = 0xaaaaaaaa; - _ = try ring.accept_multishot(userdata, listener_socket, &addr, &addr_len, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - var nr: usize = 4; // number of clients to connect - while (nr > 0) : (nr -= 1) { - // connect client - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - errdefer posix.close(client); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); - - // test accept completion - var cqe = try ring.copy_cqe(); - if (cqe.err() == .INVAL) return error.SkipZigTest; - try testing.expect(cqe.res > 0); - try testing.expect(cqe.user_data == userdata); - try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE > 0); // more flag is set - - posix.close(client); - } -} - -test "accept/connect/send_zc/recv" { - try skipKernelLessThan(.{ .major = 6, .minor = 0, .patch = 0 }); - - const io = testing.io; - - var ring = IoUring.init(16, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const socket_test_harness = try createSocketTestHarness(&ring); - defer socket_test_harness.close(io); - - const buffer_send = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe }; - var buffer_recv = [_]u8{0} ** 10; - - // zero-copy send - const sqe_send = try ring.send_zc(0xeeeeeeee, socket_test_harness.client, buffer_send[0..], 0, 0); - sqe_send.flags |= linux.IOSQE_IO_LINK; - _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0); - try testing.expectEqual(@as(u32, 2), try ring.submit()); - - var cqe_send = try ring.copy_cqe(); - // First completion of zero-copy send. - // IORING_CQE_F_MORE, means that there - // will be a second completion event / notification for the - // request, with the user_data field set to the same value. - // buffer_send must be keep alive until second cqe. - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xeeeeeeee, - .res = buffer_send.len, - .flags = linux.IORING_CQE_F_MORE, - }, cqe_send); - - cqe_send, const cqe_recv = brk: { - const cqe1 = try ring.copy_cqe(); - const cqe2 = try ring.copy_cqe(); - break :brk if (cqe1.user_data == 0xeeeeeeee) .{ cqe1, cqe2 } else .{ cqe2, cqe1 }; - }; - - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xffffffff, - .res = buffer_recv.len, - .flags = cqe_recv.flags & linux.IORING_CQE_F_SOCK_NONEMPTY, - }, cqe_recv); - try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]); - - // Second completion of zero-copy send. - // IORING_CQE_F_NOTIF in flags signals that kernel is done with send_buffer - try testing.expectEqual(linux.io_uring_cqe{ - .user_data = 0xeeeeeeee, - .res = 0, - .flags = linux.IORING_CQE_F_NOTIF, - }, cqe_send); -} - -test "accept_direct" { - try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 }); - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - var address: linux.sockaddr.in = .{ - .port = 0, - .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), - }; - - // register direct file descriptors - var registered_fds = [_]linux.fd_t{-1} ** 2; - try ring.register_files(registered_fds[0..]); - - const listener_socket = try createListenerSocket(&address); - defer posix.close(listener_socket); - - const accept_userdata: u64 = 0xaaaaaaaa; - const read_userdata: u64 = 0xbbbbbbbb; - const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe }; - - for (0..2) |_| { - for (registered_fds, 0..) |_, i| { - var buffer_recv = [_]u8{0} ** 16; - const buffer_send: []const u8 = data[0 .. data.len - i]; // make it different at each loop - - // submit accept, will chose registered fd and return index in cqe - _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - // connect - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); - defer posix.close(client); - - // accept completion - const cqe_accept = try ring.copy_cqe(); - try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err()); - const fd_index = cqe_accept.res; - try testing.expect(fd_index < registered_fds.len); - try testing.expect(cqe_accept.user_data == accept_userdata); - - // send data - _ = try posix.send(client, buffer_send, 0); - - // Example of how to use registered fd: - // Submit receive to fixed file returned by accept (fd_index). - // Fd field is set to registered file index, returned by accept. - // Flag linux.IOSQE_FIXED_FILE must be set. - const recv_sqe = try ring.recv(read_userdata, fd_index, .{ .buffer = &buffer_recv }, 0); - recv_sqe.flags |= linux.IOSQE_FIXED_FILE; - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - // accept receive - const recv_cqe = try ring.copy_cqe(); - try testing.expect(recv_cqe.user_data == read_userdata); - try testing.expect(recv_cqe.res == buffer_send.len); - try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]); - } - // no more available fds, accept will get NFILE error - { - // submit accept - _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - // connect - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); - defer posix.close(client); - // completion with error - const cqe_accept = try ring.copy_cqe(); - try testing.expect(cqe_accept.user_data == accept_userdata); - try testing.expectEqual(posix.E.NFILE, cqe_accept.err()); - } - // return file descriptors to kernel - try ring.register_files_update(0, registered_fds[0..]); - } - try ring.unregister_files(); -} - -test "accept_multishot_direct" { - try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 }); - - if (builtin.cpu.arch == .riscv64) { - // https://github.com/ziglang/zig/issues/25734 - return error.SkipZigTest; - } - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var address: linux.sockaddr.in = .{ - .port = 0, - .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), - }; - - var registered_fds = [_]linux.fd_t{-1} ** 2; - try ring.register_files(registered_fds[0..]); - - const listener_socket = try createListenerSocket(&address); - defer posix.close(listener_socket); - - const accept_userdata: u64 = 0xaaaaaaaa; - - for (0..2) |_| { - // submit multishot accept - // Will chose registered fd and return index of the selected registered file in cqe. - _ = try ring.accept_multishot_direct(accept_userdata, listener_socket, null, null, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - for (registered_fds) |_| { - // connect - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); - defer posix.close(client); - - // accept completion - const cqe_accept = try ring.copy_cqe(); - const fd_index = cqe_accept.res; - try testing.expect(fd_index < registered_fds.len); - try testing.expect(cqe_accept.user_data == accept_userdata); - try testing.expect(cqe_accept.flags & linux.IORING_CQE_F_MORE > 0); // has more is set - } - // No more available fds, accept will get NFILE error. - // Multishot is terminated (more flag is not set). - { - // connect - const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); - defer posix.close(client); - // completion with error - const cqe_accept = try ring.copy_cqe(); - try testing.expect(cqe_accept.user_data == accept_userdata); - try testing.expectEqual(posix.E.NFILE, cqe_accept.err()); - try testing.expect(cqe_accept.flags & linux.IORING_CQE_F_MORE == 0); // has more is not set - } - // return file descriptors to kernel - try ring.register_files_update(0, registered_fds[0..]); - } - try ring.unregister_files(); -} - -test "socket" { - try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 }); - - var ring = IoUring.init(1, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - // prepare, submit socket operation - _ = try ring.socket(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - // test completion - var cqe = try ring.copy_cqe(); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - const fd: linux.fd_t = @intCast(cqe.res); - try testing.expect(fd > 2); - - posix.close(fd); -} - -test "socket_direct/socket_direct_alloc/close_direct" { - try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 }); - - var ring = IoUring.init(2, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var registered_fds = [_]linux.fd_t{-1} ** 3; - try ring.register_files(registered_fds[0..]); - - // create socket in registered file descriptor at index 0 (last param) - _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - var cqe_socket = try ring.copy_cqe(); - try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err()); - try testing.expect(cqe_socket.res == 0); - - // create socket in registered file descriptor at index 1 (last param) - _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 1); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - cqe_socket = try ring.copy_cqe(); - try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err()); - try testing.expect(cqe_socket.res == 0); // res is 0 when index is specified - - // create socket in kernel chosen file descriptor index (_alloc version) - // completion res has index from registered files - _ = try ring.socket_direct_alloc(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - cqe_socket = try ring.copy_cqe(); - try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err()); - try testing.expect(cqe_socket.res == 2); // returns registered file index - - // use sockets from registered_fds in connect operation - var address: linux.sockaddr.in = .{ - .port = 0, - .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), - }; - const listener_socket = try createListenerSocket(&address); - defer posix.close(listener_socket); - const accept_userdata: u64 = 0xaaaaaaaa; - const connect_userdata: u64 = 0xbbbbbbbb; - const close_userdata: u64 = 0xcccccccc; - for (registered_fds, 0..) |_, fd_index| { - // prepare accept - _ = try ring.accept(accept_userdata, listener_socket, null, null, 0); - // prepare connect with fixed socket - const connect_sqe = try ring.connect(connect_userdata, @intCast(fd_index), addrAny(&address), @sizeOf(linux.sockaddr.in)); - connect_sqe.flags |= linux.IOSQE_FIXED_FILE; // fd is fixed file index - // submit both - try testing.expectEqual(@as(u32, 2), try ring.submit()); - // get completions - var cqe_connect = try ring.copy_cqe(); - var cqe_accept = try ring.copy_cqe(); - // ignore order - if (cqe_connect.user_data == accept_userdata and cqe_accept.user_data == connect_userdata) { - const a = cqe_accept; - const b = cqe_connect; - cqe_accept = b; - cqe_connect = a; - } - // test connect completion - try testing.expect(cqe_connect.user_data == connect_userdata); - try testing.expectEqual(posix.E.SUCCESS, cqe_connect.err()); - // test accept completion - try testing.expect(cqe_accept.user_data == accept_userdata); - try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err()); - - // submit and test close_direct - _ = try ring.close_direct(close_userdata, @intCast(fd_index)); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - var cqe_close = try ring.copy_cqe(); - try testing.expect(cqe_close.user_data == close_userdata); - try testing.expectEqual(posix.E.SUCCESS, cqe_close.err()); - } - - try ring.unregister_files(); -} - -test "openat_direct/close_direct" { - try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 }); - - var ring = IoUring.init(2, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - var registered_fds = [_]linux.fd_t{-1} ** 3; - try ring.register_files(registered_fds[0..]); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - const path = "test_io_uring_close_direct"; - const flags: linux.O = .{ .ACCMODE = .RDWR, .CREAT = true }; - const mode: posix.mode_t = 0o666; - const user_data: u64 = 0; - - // use registered file at index 0 (last param) - _ = try ring.openat_direct(user_data, tmp.dir.fd, path, flags, mode, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - var cqe = try ring.copy_cqe(); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - try testing.expect(cqe.res == 0); - - // use registered file at index 1 - _ = try ring.openat_direct(user_data, tmp.dir.fd, path, flags, mode, 1); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - cqe = try ring.copy_cqe(); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - try testing.expect(cqe.res == 0); // res is 0 when we specify index - - // let kernel choose registered file index - _ = try ring.openat_direct(user_data, tmp.dir.fd, path, flags, mode, linux.IORING_FILE_INDEX_ALLOC); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - cqe = try ring.copy_cqe(); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - try testing.expect(cqe.res == 2); // chosen index is in res - - // close all open file descriptors - for (registered_fds, 0..) |_, fd_index| { - _ = try ring.close_direct(user_data, @intCast(fd_index)); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - var cqe_close = try ring.copy_cqe(); - try testing.expectEqual(posix.E.SUCCESS, cqe_close.err()); - } - try ring.unregister_files(); -} - -/// For use in tests. Returns SkipZigTest if kernel version is less than required. -inline fn skipKernelLessThan(required: std.SemanticVersion) !void { - if (!is_linux) return error.SkipZigTest; - - var uts: linux.utsname = undefined; - const res = linux.uname(&uts); - switch (linux.errno(res)) { - .SUCCESS => {}, - else => |errno| return posix.unexpectedErrno(errno), - } - - const release = mem.sliceTo(&uts.release, 0); - // Strips potential extra, as kernel version might not be semver compliant, example "6.8.9-300.fc40.x86_64" - const extra_index = std.mem.findAny(u8, release, "-+"); - const stripped = release[0..(extra_index orelse release.len)]; - // Make sure the input don't rely on the extra we just stripped - try testing.expect(required.pre == null and required.build == null); - - var current = try std.SemanticVersion.parse(stripped); - current.pre = null; // don't check pre field - if (required.order(current) == .gt) return error.SkipZigTest; -} - test BufferGroup { if (!is_linux) return error.SkipZigTest; const io = testing.io; + _ = io; // Init IoUring var ring = IoUring.init(16, 0) catch |err| switch (err) { @@ -4145,7 +1884,7 @@ test BufferGroup { // Create client/server fds const fds = try createSocketTestHarness(&ring); - defer fds.close(io); + defer fds.close(); const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe }; // Client sends data @@ -4180,469 +1919,6 @@ test BufferGroup { } } -test "ring mapped buffers recv" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(16, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - // init buffer group - const group_id: u16 = 1; // buffers group id - const buffers_count: u16 = 2; // number of buffers in buffer group - const buffer_size: usize = 4; // size of each buffer in group - var buf_grp = BufferGroup.init( - &ring, - testing.allocator, - group_id, - buffer_size, - buffers_count, - ) catch |err| switch (err) { - // kernel older than 5.19 - error.ArgumentsInvalid => return error.SkipZigTest, - else => return err, - }; - defer buf_grp.deinit(testing.allocator); - - // create client/server fds - const fds = try createSocketTestHarness(&ring); - defer fds.close(io); - - // for random user_data in sqe/cqe - var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed); - var rnd = Rnd.random(); - - var round: usize = 4; // repeat send/recv cycle round times - while (round > 0) : (round -= 1) { - // client sends data - const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe }; - { - const user_data = rnd.int(u64); - _ = try ring.send(user_data, fds.client, data[0..], 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - const cqe_send = try ring.copy_cqe(); - if (cqe_send.err() == .INVAL) return error.SkipZigTest; - try testing.expectEqual(linux.io_uring_cqe{ .user_data = user_data, .res = data.len, .flags = 0 }, cqe_send); - } - var pos: usize = 0; - - // read first chunk - const cqe1 = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64)); - var buf = try buf_grp.get(cqe1); - try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf); - pos += buf.len; - // second chunk - const cqe2 = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64)); - buf = try buf_grp.get(cqe2); - try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf); - pos += buf.len; - - // both buffers provided to the kernel are used so we get error - // 'no more buffers', until we put buffers to the kernel - { - const user_data = rnd.int(u64); - _ = try buf_grp.recv(user_data, fds.server, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - const cqe = try ring.copy_cqe(); - try testing.expectEqual(user_data, cqe.user_data); - try testing.expect(cqe.res < 0); // fail - try testing.expectEqual(posix.E.NOBUFS, cqe.err()); - try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only - try testing.expectError(error.NoBufferSelected, cqe.buffer_id()); - } - - // put buffers back to the kernel - try buf_grp.put(cqe1); - try buf_grp.put(cqe2); - - // read remaining data - while (pos < data.len) { - const cqe = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64)); - buf = try buf_grp.get(cqe); - try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf); - pos += buf.len; - try buf_grp.put(cqe); - } - } -} - -test "ring mapped buffers multishot recv" { - if (!is_linux) return error.SkipZigTest; - - const io = testing.io; - - var ring = IoUring.init(16, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - // init buffer group - const group_id: u16 = 1; // buffers group id - const buffers_count: u16 = 2; // number of buffers in buffer group - const buffer_size: usize = 4; // size of each buffer in group - var buf_grp = BufferGroup.init( - &ring, - testing.allocator, - group_id, - buffer_size, - buffers_count, - ) catch |err| switch (err) { - // kernel older than 5.19 - error.ArgumentsInvalid => return error.SkipZigTest, - else => return err, - }; - defer buf_grp.deinit(testing.allocator); - - // create client/server fds - const fds = try createSocketTestHarness(&ring); - defer fds.close(io); - - // for random user_data in sqe/cqe - var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed); - var rnd = Rnd.random(); - - var round: usize = 4; // repeat send/recv cycle round times - while (round > 0) : (round -= 1) { - // client sends data - const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf }; - { - const user_data = rnd.int(u64); - _ = try ring.send(user_data, fds.client, data[0..], 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - const cqe_send = try ring.copy_cqe(); - if (cqe_send.err() == .INVAL) return error.SkipZigTest; - try testing.expectEqual(linux.io_uring_cqe{ .user_data = user_data, .res = data.len, .flags = 0 }, cqe_send); - } - - // start multishot recv - var recv_user_data = rnd.int(u64); - _ = try buf_grp.recv_multishot(recv_user_data, fds.server, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit - - // server reads data into provided buffers - // there are 2 buffers of size 4, so each read gets only chunk of data - // we read four chunks of 4, 4, 4, 4 bytes each - var chunk: []const u8 = data[0..buffer_size]; // first chunk - const cqe1 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk); - try testing.expect(cqe1.flags & linux.IORING_CQE_F_MORE > 0); - - chunk = data[buffer_size .. buffer_size * 2]; // second chunk - const cqe2 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk); - try testing.expect(cqe2.flags & linux.IORING_CQE_F_MORE > 0); - - // both buffers provided to the kernel are used so we get error - // 'no more buffers', until we put buffers to the kernel - { - const cqe = try ring.copy_cqe(); - try testing.expectEqual(recv_user_data, cqe.user_data); - try testing.expect(cqe.res < 0); // fail - try testing.expectEqual(posix.E.NOBUFS, cqe.err()); - try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only - // has more is not set - // indicates that multishot is finished - try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE == 0); - try testing.expectError(error.NoBufferSelected, cqe.buffer_id()); - } - - // put buffers back to the kernel - try buf_grp.put(cqe1); - try buf_grp.put(cqe2); - - // restart multishot - recv_user_data = rnd.int(u64); - _ = try buf_grp.recv_multishot(recv_user_data, fds.server, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit - - chunk = data[buffer_size * 2 .. buffer_size * 3]; // third chunk - const cqe3 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk); - try testing.expect(cqe3.flags & linux.IORING_CQE_F_MORE > 0); - try buf_grp.put(cqe3); - - chunk = data[buffer_size * 3 ..]; // last chunk - const cqe4 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk); - try testing.expect(cqe4.flags & linux.IORING_CQE_F_MORE > 0); - try buf_grp.put(cqe4); - - // cancel pending multishot recv operation - { - const cancel_user_data = rnd.int(u64); - _ = try ring.cancel(cancel_user_data, recv_user_data, 0); - try testing.expectEqual(@as(u32, 1), try ring.submit()); - - // expect completion of cancel operation and completion of recv operation - var cqe_cancel = try ring.copy_cqe(); - if (cqe_cancel.err() == .INVAL) return error.SkipZigTest; - var cqe_recv = try ring.copy_cqe(); - if (cqe_recv.err() == .INVAL) return error.SkipZigTest; - - // don't depend on order of completions - if (cqe_cancel.user_data == recv_user_data and cqe_recv.user_data == cancel_user_data) { - const a = cqe_cancel; - const b = cqe_recv; - cqe_cancel = b; - cqe_recv = a; - } - - // Note on different kernel results: - // on older kernel (tested with v6.0.16, v6.1.57, v6.2.12, v6.4.16) - // cqe_cancel.err() == .NOENT - // cqe_recv.err() == .NOBUFS - // on kernel (tested with v6.5.0, v6.5.7) - // cqe_cancel.err() == .SUCCESS - // cqe_recv.err() == .CANCELED - // Upstream reference: https://github.com/axboe/liburing/issues/984 - - // cancel operation is success (or NOENT on older kernels) - try testing.expectEqual(cancel_user_data, cqe_cancel.user_data); - try testing.expect(cqe_cancel.err() == .NOENT or cqe_cancel.err() == .SUCCESS); - - // recv operation is failed with err CANCELED (or NOBUFS on older kernels) - try testing.expectEqual(recv_user_data, cqe_recv.user_data); - try testing.expect(cqe_recv.res < 0); - try testing.expect(cqe_recv.err() == .NOBUFS or cqe_recv.err() == .CANCELED); - try testing.expect(cqe_recv.flags & linux.IORING_CQE_F_MORE == 0); - } - } -} - -// Prepare, submit recv and get cqe using buffer group. -fn buf_grp_recv_submit_get_cqe( - ring: *IoUring, - buf_grp: *BufferGroup, - fd: linux.fd_t, - user_data: u64, -) !linux.io_uring_cqe { - // prepare and submit recv - const sqe = try buf_grp.recv(user_data, fd, 0); - try testing.expect(sqe.flags & linux.IOSQE_BUFFER_SELECT == linux.IOSQE_BUFFER_SELECT); - try testing.expect(sqe.buf_index == buf_grp.group_id); - try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit - // get cqe, expect success - const cqe = try ring.copy_cqe(); - try testing.expectEqual(user_data, cqe.user_data); - try testing.expect(cqe.res >= 0); // success - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); // IORING_CQE_F_BUFFER flag is set - - return cqe; -} - -fn expect_buf_grp_cqe( - ring: *IoUring, - buf_grp: *BufferGroup, - user_data: u64, - expected: []const u8, -) !linux.io_uring_cqe { - // get cqe - const cqe = try ring.copy_cqe(); - try testing.expectEqual(user_data, cqe.user_data); - try testing.expect(cqe.res >= 0); // success - try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); // IORING_CQE_F_BUFFER flag is set - try testing.expectEqual(expected.len, @as(usize, @intCast(cqe.res))); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - - // get buffer from pool - const buffer_id = try cqe.buffer_id(); - const len = @as(usize, @intCast(cqe.res)); - const buf = buf_grp.get_by_id(buffer_id)[0..len]; - try testing.expectEqualSlices(u8, expected, buf); - - return cqe; -} - -test "copy_cqes with wrapping sq.cqes buffer" { - if (!is_linux) return error.SkipZigTest; - - var ring = IoUring.init(2, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - try testing.expectEqual(2, ring.sq.sqes.len); - try testing.expectEqual(4, ring.cq.cqes.len); - - // submit 2 entries, receive 2 completions - var cqes: [8]linux.io_uring_cqe = undefined; - { - for (0..2) |_| { - const sqe = try ring.get_sqe(); - sqe.prep_timeout(&.{ .sec = 0, .nsec = 10000 }, 0, 0); - try testing.expect(try ring.submit() == 1); - } - var cqe_count: u32 = 0; - while (cqe_count < 2) { - cqe_count += try ring.copy_cqes(&cqes, 2 - cqe_count); - } - } - - try testing.expectEqual(2, ring.cq.head.*); - - // sq.sqes len is 4, starting at position 2 - // every 4 entries submit wraps completion buffer - // we are reading ring.cq.cqes at indexes 2,3,0,1 - for (1..1024) |i| { - for (0..4) |_| { - const sqe = try ring.get_sqe(); - sqe.prep_timeout(&.{ .sec = 0, .nsec = 10000 }, 0, 0); - try testing.expect(try ring.submit() == 1); - } - var cqe_count: u32 = 0; - while (cqe_count < 4) { - cqe_count += try ring.copy_cqes(&cqes, 4 - cqe_count); - } - try testing.expectEqual(4, cqe_count); - try testing.expectEqual(2 + 4 * i, ring.cq.head.*); - } -} - -test "bind/listen/connect" { - if (builtin.cpu.arch == .s390x) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25956 - - var ring = IoUring.init(4, 0) catch |err| switch (err) { - error.SystemOutdated => return error.SkipZigTest, - error.PermissionDenied => return error.SkipZigTest, - else => return err, - }; - defer ring.deinit(); - - const probe = ring.get_probe() catch return error.SkipZigTest; - // LISTEN is higher required operation - if (!probe.is_supported(.LISTEN)) return error.SkipZigTest; - - var addr: linux.sockaddr.in = .{ - .port = 0, - .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), - }; - const proto: u32 = if (addr.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP; - - const listen_fd = brk: { - // Create socket - _ = try ring.socket(1, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0); - try testing.expectEqual(1, try ring.submit()); - var cqe = try ring.copy_cqe(); - try testing.expectEqual(1, cqe.user_data); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - const listen_fd: linux.fd_t = @intCast(cqe.res); - try testing.expect(listen_fd > 2); - - // Prepare: set socket option * 2, bind, listen - var optval: u32 = 1; - (try ring.setsockopt(2, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval))).link_next(); - (try ring.setsockopt(3, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEPORT, mem.asBytes(&optval))).link_next(); - (try ring.bind(4, listen_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in), 0)).link_next(); - _ = try ring.listen(5, listen_fd, 1, 0); - // Submit 4 operations - try testing.expectEqual(4, try ring.submit()); - // Expect all to succeed - for (2..6) |user_data| { - cqe = try ring.copy_cqe(); - try testing.expectEqual(user_data, cqe.user_data); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - } - - // Check that socket option is set - optval = 0; - _ = try ring.getsockopt(5, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval)); - try testing.expectEqual(1, try ring.submit()); - cqe = try ring.copy_cqe(); - try testing.expectEqual(5, cqe.user_data); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - try testing.expectEqual(1, optval); - - // Read system assigned port into addr - var addr_len: posix.socklen_t = @sizeOf(linux.sockaddr.in); - try posix.getsockname(listen_fd, addrAny(&addr), &addr_len); - - break :brk listen_fd; - }; - - const connect_fd = brk: { - // Create connect socket - _ = try ring.socket(6, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0); - try testing.expectEqual(1, try ring.submit()); - const cqe = try ring.copy_cqe(); - try testing.expectEqual(6, cqe.user_data); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - // Get connect socket fd - const connect_fd: linux.fd_t = @intCast(cqe.res); - try testing.expect(connect_fd > 2 and connect_fd != listen_fd); - break :brk connect_fd; - }; - - // Prepare accept/connect operations - _ = try ring.accept(7, listen_fd, null, null, 0); - _ = try ring.connect(8, connect_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in)); - try testing.expectEqual(2, try ring.submit()); - // Get listener accepted socket - var accept_fd: posix.socket_t = 0; - for (0..2) |_| { - const cqe = try ring.copy_cqe(); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - if (cqe.user_data == 7) { - accept_fd = @intCast(cqe.res); - } else { - try testing.expectEqual(8, cqe.user_data); - } - } - try testing.expect(accept_fd > 2 and accept_fd != listen_fd and accept_fd != connect_fd); - - // Communicate - try testSendRecv(&ring, connect_fd, accept_fd); - try testSendRecv(&ring, accept_fd, connect_fd); - - // Shutdown and close all sockets - for ([_]posix.socket_t{ connect_fd, accept_fd, listen_fd }) |fd| { - (try ring.shutdown(9, fd, posix.SHUT.RDWR)).link_next(); - _ = try ring.close(10, fd); - try testing.expectEqual(2, try ring.submit()); - for (0..2) |i| { - const cqe = try ring.copy_cqe(); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - try testing.expectEqual(9 + i, cqe.user_data); - } - } -} - -fn testSendRecv(ring: *IoUring, send_fd: posix.socket_t, recv_fd: posix.socket_t) !void { - const buffer_send = "0123456789abcdf" ** 10; - var buffer_recv: [buffer_send.len * 2]u8 = undefined; - - // 2 sends - _ = try ring.send(1, send_fd, buffer_send, linux.MSG.WAITALL); - _ = try ring.send(2, send_fd, buffer_send, linux.MSG.WAITALL); - try testing.expectEqual(2, try ring.submit()); - for (0..2) |i| { - const cqe = try ring.copy_cqe(); - try testing.expectEqual(1 + i, cqe.user_data); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - try testing.expectEqual(buffer_send.len, @as(usize, @intCast(cqe.res))); - } - - // receive - var recv_len: usize = 0; - while (recv_len < buffer_send.len * 2) { - _ = try ring.recv(3, recv_fd, .{ .buffer = buffer_recv[recv_len..] }, 0); - try testing.expectEqual(1, try ring.submit()); - const cqe = try ring.copy_cqe(); - try testing.expectEqual(3, cqe.user_data); - try testing.expectEqual(posix.E.SUCCESS, cqe.err()); - recv_len += @intCast(cqe.res); - } - - // inspect recv buffer - try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]); - try testing.expectEqualSlices(u8, buffer_send, buffer_recv[buffer_send.len..]); -} - -fn addrAny(addr: *linux.sockaddr.in) *linux.sockaddr { - return @ptrCast(addr); +test { + if (is_linux) _ = @import("IoUring/test.zig"); } diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig new file mode 100644 index 0000000000000000000000000000000000000000..251ba14a8b1a04875b39614bd687f46b81f30f29 --- /dev/null +++ b/lib/std/os/linux/IoUring/test.zig @@ -0,0 +1,2691 @@ +const builtin = @import("builtin"); + +const std = @import("../../../std.zig"); +const Io = std.Io; +const mem = std.mem; +const assert = std.debug.assert; +const testing = std.testing; +const linux = std.os.linux; + +const IoUring = std.os.linux.IoUring; +const BufferGroup = IoUring.BufferGroup; + +const posix = std.posix; +const iovec = posix.iovec; +const iovec_const = posix.iovec_const; + +comptime { + assert(builtin.os.tag == .linux); +} + +test "structs/offsets/entries" { + try testing.expectEqual(@as(usize, 120), @sizeOf(linux.io_uring_params)); + try testing.expectEqual(@as(usize, 64), @sizeOf(linux.io_uring_sqe)); + try testing.expectEqual(@as(usize, 16), @sizeOf(linux.io_uring_cqe)); + + try testing.expectEqual(0, linux.IORING_OFF_SQ_RING); + try testing.expectEqual(0x8000000, linux.IORING_OFF_CQ_RING); + try testing.expectEqual(0x10000000, linux.IORING_OFF_SQES); + + try testing.expectError(error.EntriesZero, IoUring.init(0, 0)); + try testing.expectError(error.EntriesNotPowerOfTwo, IoUring.init(3, 0)); +} + +test "nop" { + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer { + ring.deinit(); + testing.expectEqual(@as(linux.fd_t, -1), ring.fd) catch @panic("test failed"); + } + + const sqe = try ring.nop(0xaaaaaaaa); + try testing.expectEqual(linux.io_uring_sqe{ + .opcode = .NOP, + .flags = 0, + .ioprio = 0, + .fd = 0, + .off = 0, + .addr = 0, + .len = 0, + .rw_flags = 0, + .user_data = 0xaaaaaaaa, + .buf_index = 0, + .personality = 0, + .splice_fd_in = 0, + .addr3 = 0, + .resv = 0, + }, sqe.*); + + try testing.expectEqual(@as(u32, 0), ring.sq.sqe_head); + try testing.expectEqual(@as(u32, 1), ring.sq.sqe_tail); + try testing.expectEqual(@as(u32, 0), ring.sq.tail.*); + try testing.expectEqual(@as(u32, 0), ring.cq.head.*); + try testing.expectEqual(@as(u32, 1), ring.sq_ready()); + try testing.expectEqual(@as(u32, 0), ring.cq_ready()); + + try testing.expectEqual(@as(u32, 1), try ring.submit()); + try testing.expectEqual(@as(u32, 1), ring.sq.sqe_head); + try testing.expectEqual(@as(u32, 1), ring.sq.sqe_tail); + try testing.expectEqual(@as(u32, 1), ring.sq.tail.*); + try testing.expectEqual(@as(u32, 0), ring.cq.head.*); + try testing.expectEqual(@as(u32, 0), ring.sq_ready()); + + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xaaaaaaaa, + .res = 0, + .flags = 0, + }, try ring.copy_cqe()); + try testing.expectEqual(@as(u32, 1), ring.cq.head.*); + try testing.expectEqual(@as(u32, 0), ring.cq_ready()); + + const sqe_barrier = try ring.nop(0xbbbbbbbb); + sqe_barrier.flags |= linux.IOSQE_IO_DRAIN; + try testing.expectEqual(@as(u32, 1), try ring.submit()); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xbbbbbbbb, + .res = 0, + .flags = 0, + }, try ring.copy_cqe()); + try testing.expectEqual(@as(u32, 2), ring.sq.sqe_head); + try testing.expectEqual(@as(u32, 2), ring.sq.sqe_tail); + try testing.expectEqual(@as(u32, 2), ring.sq.tail.*); + try testing.expectEqual(@as(u32, 2), ring.cq.head.*); +} + +test "readv" { + const io = testing.io; + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{}); + defer file.close(io); + + // Linux Kernel 5.4 supports IORING_REGISTER_FILES but not sparse fd sets (i.e. an fd of -1). + // Linux Kernel 5.5 adds support for sparse fd sets. + // Compare: + // https://github.com/torvalds/linux/blob/v5.4/fs/io_uring.c#L3119-L3124 vs + // https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L6687-L6691 + // We therefore avoid stressing sparse fd sets here: + var registered_fds = [_]linux.fd_t{0} ** 1; + const fd_index = 0; + registered_fds[fd_index] = file.handle; + try ring.register_files(registered_fds[0..]); + + var buffer = [_]u8{42} ** 128; + var iovecs = [_]iovec{iovec{ .base = &buffer, .len = buffer.len }}; + const sqe = try ring.read(0xcccccccc, fd_index, .{ .iovecs = iovecs[0..] }, 0); + try testing.expectEqual(linux.IORING_OP.READV, sqe.opcode); + sqe.flags |= linux.IOSQE_FIXED_FILE; + + try testing.expectError(error.SubmissionQueueFull, ring.nop(0)); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xcccccccc, + .res = buffer.len, + .flags = 0, + }, try ring.copy_cqe()); + try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]); + + try ring.unregister_files(); +} + +test "writev/fsync/readv" { + const io = testing.io; + + var ring = IoUring.init(4, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const path = "test_io_uring_writev_fsync_readv"; + const file = try tmp.dir.createFile(io, path, .{ .read = true }); + defer file.close(io); + const fd = file.handle; + + const buffer_write = [_]u8{42} ** 128; + const iovecs_write = [_]iovec_const{ + iovec_const{ .base = &buffer_write, .len = buffer_write.len }, + }; + var buffer_read = [_]u8{0} ** 128; + var iovecs_read = [_]iovec{ + iovec{ .base = &buffer_read, .len = buffer_read.len }, + }; + + const sqe_writev = try ring.writev(0xdddddddd, fd, iovecs_write[0..], 17); + try testing.expectEqual(linux.IORING_OP.WRITEV, sqe_writev.opcode); + try testing.expectEqual(@as(u64, 17), sqe_writev.off); + sqe_writev.flags |= linux.IOSQE_IO_LINK; + + const sqe_fsync = try ring.fsync(0xeeeeeeee, fd, 0); + try testing.expectEqual(linux.IORING_OP.FSYNC, sqe_fsync.opcode); + try testing.expectEqual(fd, sqe_fsync.fd); + sqe_fsync.flags |= linux.IOSQE_IO_LINK; + + const sqe_readv = try ring.read(0xffffffff, fd, .{ .iovecs = iovecs_read[0..] }, 17); + try testing.expectEqual(linux.IORING_OP.READV, sqe_readv.opcode); + try testing.expectEqual(@as(u64, 17), sqe_readv.off); + + try testing.expectEqual(@as(u32, 3), ring.sq_ready()); + try testing.expectEqual(@as(u32, 3), try ring.submit_and_wait(3)); + try testing.expectEqual(@as(u32, 0), ring.sq_ready()); + try testing.expectEqual(@as(u32, 3), ring.cq_ready()); + + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xdddddddd, + .res = buffer_write.len, + .flags = 0, + }, try ring.copy_cqe()); + try testing.expectEqual(@as(u32, 2), ring.cq_ready()); + + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xeeeeeeee, + .res = 0, + .flags = 0, + }, try ring.copy_cqe()); + try testing.expectEqual(@as(u32, 1), ring.cq_ready()); + + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xffffffff, + .res = buffer_read.len, + .flags = 0, + }, try ring.copy_cqe()); + try testing.expectEqual(@as(u32, 0), ring.cq_ready()); + + try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); +} + +test "write/read" { + const io = testing.io; + + var ring = IoUring.init(2, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = "test_io_uring_write_read"; + const file = try tmp.dir.createFile(io, path, .{ .read = true }); + defer file.close(io); + const fd = file.handle; + + const buffer_write = [_]u8{97} ** 20; + var buffer_read = [_]u8{98} ** 20; + const sqe_write = try ring.write(0x11111111, fd, buffer_write[0..], 10); + try testing.expectEqual(linux.IORING_OP.WRITE, sqe_write.opcode); + try testing.expectEqual(@as(u64, 10), sqe_write.off); + sqe_write.flags |= linux.IOSQE_IO_LINK; + const sqe_read = try ring.read(0x22222222, fd, .{ .buffer = buffer_read[0..] }, 10); + try testing.expectEqual(linux.IORING_OP.READ, sqe_read.opcode); + try testing.expectEqual(@as(u64, 10), sqe_read.off); + try testing.expectEqual(@as(u32, 2), try ring.submit()); + + const cqe_write = try ring.copy_cqe(); + const cqe_read = try ring.copy_cqe(); + // Prior to Linux Kernel 5.6 this is the only way to test for read/write support: + // https://lwn.net/Articles/809820/ + if (cqe_write.err() == .INVAL) return error.SkipZigTest; + if (cqe_read.err() == .INVAL) return error.SkipZigTest; + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x11111111, + .res = buffer_write.len, + .flags = 0, + }, cqe_write); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x22222222, + .res = buffer_read.len, + .flags = 0, + }, cqe_read); + try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); +} + +test "splice/read" { + const io = testing.io; + + var ring = IoUring.init(4, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var tmp = std.testing.tmpDir(.{}); + const path_src = "test_io_uring_splice_src"; + const file_src = try tmp.dir.createFile(io, path_src, .{ .read = true }); + defer file_src.close(io); + const fd_src = file_src.handle; + + const path_dst = "test_io_uring_splice_dst"; + const file_dst = try tmp.dir.createFile(io, path_dst, .{ .read = true }); + defer file_dst.close(io); + const fd_dst = file_dst.handle; + + const buffer_write = [_]u8{97} ** 20; + var buffer_read = [_]u8{98} ** 20; + try file_src.writeStreamingAll(io, &buffer_write); + + const fds = try posix.pipe(); + const pipe_offset: u64 = std.math.maxInt(u64); + + const sqe_splice_to_pipe = try ring.splice(0x11111111, fd_src, 0, fds[1], pipe_offset, buffer_write.len); + try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_to_pipe.opcode); + try testing.expectEqual(@as(u64, 0), sqe_splice_to_pipe.addr); + try testing.expectEqual(pipe_offset, sqe_splice_to_pipe.off); + sqe_splice_to_pipe.flags |= linux.IOSQE_IO_LINK; + + const sqe_splice_from_pipe = try ring.splice(0x22222222, fds[0], pipe_offset, fd_dst, 10, buffer_write.len); + try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_from_pipe.opcode); + try testing.expectEqual(pipe_offset, sqe_splice_from_pipe.addr); + try testing.expectEqual(@as(u64, 10), sqe_splice_from_pipe.off); + sqe_splice_from_pipe.flags |= linux.IOSQE_IO_LINK; + + const sqe_read = try ring.read(0x33333333, fd_dst, .{ .buffer = buffer_read[0..] }, 10); + try testing.expectEqual(linux.IORING_OP.READ, sqe_read.opcode); + try testing.expectEqual(@as(u64, 10), sqe_read.off); + try testing.expectEqual(@as(u32, 3), try ring.submit()); + + const cqe_splice_to_pipe = try ring.copy_cqe(); + const cqe_splice_from_pipe = try ring.copy_cqe(); + const cqe_read = try ring.copy_cqe(); + // Prior to Linux Kernel 5.6 this is the only way to test for splice/read support: + // https://lwn.net/Articles/809820/ + if (cqe_splice_to_pipe.err() == .INVAL) return error.SkipZigTest; + if (cqe_splice_from_pipe.err() == .INVAL) return error.SkipZigTest; + if (cqe_read.err() == .INVAL) return error.SkipZigTest; + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x11111111, + .res = buffer_write.len, + .flags = 0, + }, cqe_splice_to_pipe); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x22222222, + .res = buffer_write.len, + .flags = 0, + }, cqe_splice_from_pipe); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x33333333, + .res = buffer_read.len, + .flags = 0, + }, cqe_read); + try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); +} + +test "write_fixed/read_fixed" { + const io = testing.io; + + var ring = IoUring.init(2, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const path = "test_io_uring_write_read_fixed"; + const file = try tmp.dir.createFile(io, path, .{ .read = true }); + defer file.close(io); + const fd = file.handle; + + var raw_buffers: [2][11]u8 = undefined; + // First buffer will be written to the file. + @memset(&raw_buffers[0], 'z'); + raw_buffers[0][0.."foobar".len].* = "foobar".*; + + var buffers = [2]iovec{ + .{ .base = &raw_buffers[0], .len = raw_buffers[0].len }, + .{ .base = &raw_buffers[1], .len = raw_buffers[1].len }, + }; + ring.register_buffers(&buffers) catch |err| switch (err) { + error.SystemResources => { + // See https://github.com/ziglang/zig/issues/15362 + return error.SkipZigTest; + }, + else => |e| return e, + }; + + const sqe_write = try ring.write_fixed(0x45454545, fd, &buffers[0], 3, 0); + try testing.expectEqual(linux.IORING_OP.WRITE_FIXED, sqe_write.opcode); + try testing.expectEqual(@as(u64, 3), sqe_write.off); + sqe_write.flags |= linux.IOSQE_IO_LINK; + + const sqe_read = try ring.read_fixed(0x12121212, fd, &buffers[1], 0, 1); + try testing.expectEqual(linux.IORING_OP.READ_FIXED, sqe_read.opcode); + try testing.expectEqual(@as(u64, 0), sqe_read.off); + + try testing.expectEqual(@as(u32, 2), try ring.submit()); + + const cqe_write = try ring.copy_cqe(); + const cqe_read = try ring.copy_cqe(); + + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x45454545, + .res = @as(i32, @intCast(buffers[0].len)), + .flags = 0, + }, cqe_write); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x12121212, + .res = @as(i32, @intCast(buffers[1].len)), + .flags = 0, + }, cqe_read); + + try testing.expectEqualSlices(u8, "\x00\x00\x00", buffers[1].base[0..3]); + try testing.expectEqualSlices(u8, "foobar", buffers[1].base[3..9]); + try testing.expectEqualSlices(u8, "zz", buffers[1].base[9..11]); +} + +test "openat" { + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const path = "test_io_uring_openat"; + + // Workaround for LLVM bug: https://github.com/ziglang/zig/issues/12014 + const path_addr = if (builtin.zig_backend == .stage2_llvm) p: { + var workaround = path; + _ = &workaround; + break :p @intFromPtr(workaround); + } else @intFromPtr(path); + + const flags: linux.O = .{ .CLOEXEC = true, .ACCMODE = .RDWR, .CREAT = true }; + const mode: posix.mode_t = 0o666; + const sqe_openat = try ring.openat(0x33333333, tmp.dir.handle, path, flags, mode); + try testing.expectEqual(linux.io_uring_sqe{ + .opcode = .OPENAT, + .flags = 0, + .ioprio = 0, + .fd = tmp.dir.handle, + .off = 0, + .addr = path_addr, + .len = mode, + .rw_flags = @bitCast(flags), + .user_data = 0x33333333, + .buf_index = 0, + .personality = 0, + .splice_fd_in = 0, + .addr3 = 0, + .resv = 0, + }, sqe_openat.*); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe_openat = try ring.copy_cqe(); + try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data); + if (cqe_openat.err() == .INVAL) return error.SkipZigTest; + if (cqe_openat.err() == .BADF) return error.SkipZigTest; + if (cqe_openat.res <= 0) std.debug.print("\ncqe_openat.res={}\n", .{cqe_openat.res}); + try testing.expect(cqe_openat.res > 0); + try testing.expectEqual(@as(u32, 0), cqe_openat.flags); + + posix.close(cqe_openat.res); +} + +test "close" { + const io = testing.io; + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const path = "test_io_uring_close"; + const file = try tmp.dir.createFile(io, path, .{}); + errdefer file.close(io); + + const sqe_close = try ring.close(0x44444444, file.handle); + try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode); + try testing.expectEqual(file.handle, sqe_close.fd); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe_close = try ring.copy_cqe(); + if (cqe_close.err() == .INVAL) return error.SkipZigTest; + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x44444444, + .res = 0, + .flags = 0, + }, cqe_close); +} + +test "accept/connect/send/recv" { + const io = testing.io; + _ = io; + + var ring = IoUring.init(16, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const socket_test_harness = try createSocketTestHarness(&ring); + defer socket_test_harness.close(); + + const buffer_send = [_]u8{ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 }; + var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 }; + + const sqe_send = try ring.send(0xeeeeeeee, socket_test_harness.client, buffer_send[0..], 0); + sqe_send.flags |= linux.IOSQE_IO_LINK; + _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0); + try testing.expectEqual(@as(u32, 2), try ring.submit()); + + const cqe_send = try ring.copy_cqe(); + if (cqe_send.err() == .INVAL) return error.SkipZigTest; + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xeeeeeeee, + .res = buffer_send.len, + .flags = 0, + }, cqe_send); + + const cqe_recv = try ring.copy_cqe(); + if (cqe_recv.err() == .INVAL) return error.SkipZigTest; + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xffffffff, + .res = buffer_recv.len, + // ignore IORING_CQE_F_SOCK_NONEMPTY since it is only set on some systems + .flags = cqe_recv.flags & linux.IORING_CQE_F_SOCK_NONEMPTY, + }, cqe_recv); + + try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]); +} + +test "sendmsg/recvmsg" { + var ring = IoUring.init(2, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var address_server: linux.sockaddr.in = .{ + .port = 0, + .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), + }; + + const server = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0); + defer posix.close(server); + try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1))); + try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try posix.bind(server, addrAny(&address_server), @sizeOf(linux.sockaddr.in)); + + // set address_server to the OS-chosen IP/port. + var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); + try posix.getsockname(server, addrAny(&address_server), &slen); + + const client = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0); + defer posix.close(client); + + const buffer_send = [_]u8{42} ** 128; + const iovecs_send = [_]iovec_const{ + iovec_const{ .base = &buffer_send, .len = buffer_send.len }, + }; + const msg_send: linux.msghdr_const = .{ + .name = addrAny(&address_server), + .namelen = @sizeOf(linux.sockaddr.in), + .iov = &iovecs_send, + .iovlen = 1, + .control = null, + .controllen = 0, + .flags = 0, + }; + const sqe_sendmsg = try ring.sendmsg(0x11111111, client, &msg_send, 0); + sqe_sendmsg.flags |= linux.IOSQE_IO_LINK; + try testing.expectEqual(linux.IORING_OP.SENDMSG, sqe_sendmsg.opcode); + try testing.expectEqual(client, sqe_sendmsg.fd); + + var buffer_recv = [_]u8{0} ** 128; + var iovecs_recv = [_]iovec{ + iovec{ .base = &buffer_recv, .len = buffer_recv.len }, + }; + var address_recv: linux.sockaddr.in = .{ + .port = 0, + .addr = 0, + }; + var msg_recv: linux.msghdr = .{ + .name = addrAny(&address_recv), + .namelen = @sizeOf(linux.sockaddr.in), + .iov = &iovecs_recv, + .iovlen = 1, + .control = null, + .controllen = 0, + .flags = 0, + }; + const sqe_recvmsg = try ring.recvmsg(0x22222222, server, &msg_recv, 0); + try testing.expectEqual(linux.IORING_OP.RECVMSG, sqe_recvmsg.opcode); + try testing.expectEqual(server, sqe_recvmsg.fd); + + try testing.expectEqual(@as(u32, 2), ring.sq_ready()); + try testing.expectEqual(@as(u32, 2), try ring.submit_and_wait(2)); + try testing.expectEqual(@as(u32, 0), ring.sq_ready()); + try testing.expectEqual(@as(u32, 2), ring.cq_ready()); + + const cqe_sendmsg = try ring.copy_cqe(); + if (cqe_sendmsg.res == -@as(i32, @intFromEnum(linux.E.INVAL))) return error.SkipZigTest; + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x11111111, + .res = buffer_send.len, + .flags = 0, + }, cqe_sendmsg); + + const cqe_recvmsg = try ring.copy_cqe(); + if (cqe_recvmsg.res == -@as(i32, @intFromEnum(linux.E.INVAL))) return error.SkipZigTest; + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x22222222, + .res = buffer_recv.len, + // ignore IORING_CQE_F_SOCK_NONEMPTY since it is set non-deterministically + .flags = cqe_recvmsg.flags & linux.IORING_CQE_F_SOCK_NONEMPTY, + }, cqe_recvmsg); + + try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]); +} + +test "timeout (after a relative time)" { + const io = testing.io; + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const ms = 10; + const margin = 5; + const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 }; + + const started = try std.Io.Clock.awake.now(io); + const sqe = try ring.timeout(0x55555555, &ts, 0, 0); + try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + const cqe = try ring.copy_cqe(); + const stopped = try std.Io.Clock.awake.now(io); + + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x55555555, + .res = -@as(i32, @intFromEnum(linux.E.TIME)), + .flags = 0, + }, cqe); + + // Tests should not depend on timings: skip test if outside margin. + const ms_elapsed = started.durationTo(stopped).toMilliseconds(); + if (ms_elapsed > margin) return error.SkipZigTest; +} + +test "timeout (after a number of completions)" { + var ring = IoUring.init(2, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const ts: linux.kernel_timespec = .{ .sec = 3, .nsec = 0 }; + const count_completions: u64 = 1; + const sqe_timeout = try ring.timeout(0x66666666, &ts, count_completions, 0); + try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode); + try testing.expectEqual(count_completions, sqe_timeout.off); + _ = try ring.nop(0x77777777); + try testing.expectEqual(@as(u32, 2), try ring.submit()); + + const cqe_nop = try ring.copy_cqe(); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x77777777, + .res = 0, + .flags = 0, + }, cqe_nop); + + const cqe_timeout = try ring.copy_cqe(); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x66666666, + .res = 0, + .flags = 0, + }, cqe_timeout); +} + +test "timeout_remove" { + var ring = IoUring.init(2, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const ts: linux.kernel_timespec = .{ .sec = 3, .nsec = 0 }; + const sqe_timeout = try ring.timeout(0x88888888, &ts, 0, 0); + try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode); + try testing.expectEqual(@as(u64, 0x88888888), sqe_timeout.user_data); + + const sqe_timeout_remove = try ring.timeout_remove(0x99999999, 0x88888888, 0); + try testing.expectEqual(linux.IORING_OP.TIMEOUT_REMOVE, sqe_timeout_remove.opcode); + try testing.expectEqual(@as(u64, 0x88888888), sqe_timeout_remove.addr); + try testing.expectEqual(@as(u64, 0x99999999), sqe_timeout_remove.user_data); + + try testing.expectEqual(@as(u32, 2), try ring.submit()); + + // The order in which the CQE arrive is not clearly documented and it changed with kernel 5.18: + // * kernel 5.10 gives user data 0x88888888 first, 0x99999999 second + // * kernel 5.18 gives user data 0x99999999 first, 0x88888888 second + + var cqes: [2]linux.io_uring_cqe = undefined; + cqes[0] = try ring.copy_cqe(); + cqes[1] = try ring.copy_cqe(); + + for (cqes) |cqe| { + // IORING_OP_TIMEOUT_REMOVE is not supported by this kernel version: + // Timeout remove operations set the fd to -1, which results in EBADF before EINVAL. + // We use IORING_FEAT_RW_CUR_POS as a safety check here to make sure we are at least pre-5.6. + // We don't want to skip this test for newer kernels. + if (cqe.user_data == 0x99999999 and + cqe.err() == .BADF and + (ring.features & linux.IORING_FEAT_RW_CUR_POS) == 0) + { + return error.SkipZigTest; + } + + try testing.expect(cqe.user_data == 0x88888888 or cqe.user_data == 0x99999999); + + if (cqe.user_data == 0x88888888) { + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x88888888, + .res = -@as(i32, @intFromEnum(linux.E.CANCELED)), + .flags = 0, + }, cqe); + } else if (cqe.user_data == 0x99999999) { + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x99999999, + .res = 0, + .flags = 0, + }, cqe); + } + } +} + +test "accept/connect/recv/link_timeout" { + const io = testing.io; + _ = io; + + var ring = IoUring.init(16, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const socket_test_harness = try createSocketTestHarness(&ring); + defer socket_test_harness.close(); + + var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 }; + + const sqe_recv = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0); + sqe_recv.flags |= linux.IOSQE_IO_LINK; + + const ts = linux.kernel_timespec{ .sec = 0, .nsec = 1000000 }; + _ = try ring.link_timeout(0x22222222, &ts, 0); + + const nr_wait = try ring.submit(); + try testing.expectEqual(@as(u32, 2), nr_wait); + + var i: usize = 0; + while (i < nr_wait) : (i += 1) { + const cqe = try ring.copy_cqe(); + switch (cqe.user_data) { + 0xffffffff => { + if (cqe.res != -@as(i32, @intFromEnum(linux.E.INTR)) and + cqe.res != -@as(i32, @intFromEnum(linux.E.CANCELED))) + { + std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res }); + try testing.expect(false); + } + }, + 0x22222222 => { + if (cqe.res != -@as(i32, @intFromEnum(linux.E.ALREADY)) and + cqe.res != -@as(i32, @intFromEnum(linux.E.TIME))) + { + std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res }); + try testing.expect(false); + } + }, + else => @panic("should not happen"), + } + } +} + +test "fallocate" { + const io = testing.io; + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const path = "test_io_uring_fallocate"; + const file = try tmp.dir.createFile(io, path, .{}); + defer file.close(io); + + try testing.expectEqual(@as(u64, 0), (try file.stat(io)).size); + + const len: u64 = 65536; + const sqe = try ring.fallocate(0xaaaaaaaa, file.handle, 0, 0, len); + try testing.expectEqual(linux.IORING_OP.FALLOCATE, sqe.opcode); + try testing.expectEqual(file.handle, sqe.fd); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + // This kernel's io_uring does not yet implement fallocate(): + .INVAL => return error.SkipZigTest, + // This kernel does not implement fallocate(): + .NOSYS => return error.SkipZigTest, + // The filesystem containing the file referred to by fd does not support this operation; + // or the mode is not supported by the filesystem containing the file referred to by fd: + .OPNOTSUPP => return error.SkipZigTest, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xaaaaaaaa, + .res = 0, + .flags = 0, + }, cqe); + + try testing.expectEqual(len, (try file.stat(io)).size); +} + +test "statx" { + const io = testing.io; + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = "test_io_uring_statx"; + const file = try tmp.dir.createFile(io, path, .{}); + defer file.close(io); + + try testing.expectEqual(@as(u64, 0), (try file.stat(io)).size); + + try file.writeStreamingAll(io, "foobar"); + + var buf: linux.Statx = undefined; + const sqe = try ring.statx( + 0xaaaaaaaa, + tmp.dir.handle, + path, + 0, + .{ .SIZE = true }, + &buf, + ); + try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode); + try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + // This kernel's io_uring does not yet implement statx(): + .INVAL => return error.SkipZigTest, + // This kernel does not implement statx(): + .NOSYS => return error.SkipZigTest, + // The filesystem containing the file referred to by fd does not support this operation; + // or the mode is not supported by the filesystem containing the file referred to by fd: + .OPNOTSUPP => return error.SkipZigTest, + // not supported on older kernels (5.4) + .BADF => return error.SkipZigTest, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xaaaaaaaa, + .res = 0, + .flags = 0, + }, cqe); + + try testing.expect(buf.mask.SIZE); + try testing.expectEqual(@as(u64, 6), buf.size); +} + +test "accept/connect/recv/cancel" { + const io = testing.io; + _ = io; + + var ring = IoUring.init(16, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const socket_test_harness = try createSocketTestHarness(&ring); + defer socket_test_harness.close(); + + var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 }; + + _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const sqe_cancel = try ring.cancel(0x99999999, 0xffffffff, 0); + try testing.expectEqual(linux.IORING_OP.ASYNC_CANCEL, sqe_cancel.opcode); + try testing.expectEqual(@as(u64, 0xffffffff), sqe_cancel.addr); + try testing.expectEqual(@as(u64, 0x99999999), sqe_cancel.user_data); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + var cqe_recv = try ring.copy_cqe(); + if (cqe_recv.err() == .INVAL) return error.SkipZigTest; + var cqe_cancel = try ring.copy_cqe(); + if (cqe_cancel.err() == .INVAL) return error.SkipZigTest; + + // The recv/cancel CQEs may arrive in any order, the recv CQE will sometimes come first: + if (cqe_recv.user_data == 0x99999999 and cqe_cancel.user_data == 0xffffffff) { + const a = cqe_recv; + const b = cqe_cancel; + cqe_recv = b; + cqe_cancel = a; + } + + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xffffffff, + .res = -@as(i32, @intFromEnum(linux.E.CANCELED)), + .flags = 0, + }, cqe_recv); + + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x99999999, + .res = 0, + .flags = 0, + }, cqe_cancel); +} + +test "register_files_update" { + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + defer posix.close(fd); + + var registered_fds = [_]linux.fd_t{0} ** 2; + const fd_index = 0; + const fd_index2 = 1; + registered_fds[fd_index] = fd; + registered_fds[fd_index2] = -1; + + ring.register_files(registered_fds[0..]) catch |err| switch (err) { + // Happens when the kernel doesn't support sparse entry (-1) in the file descriptors array. + error.FileDescriptorInvalid => return error.SkipZigTest, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + }; + + // Test IORING_REGISTER_FILES_UPDATE + // Only available since Linux 5.5 + + const fd2 = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + defer posix.close(fd2); + + registered_fds[fd_index] = fd2; + registered_fds[fd_index2] = -1; + try ring.register_files_update(0, registered_fds[0..]); + + var buffer = [_]u8{42} ** 128; + { + const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0); + try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); + sqe.flags |= linux.IOSQE_FIXED_FILE; + + try testing.expectEqual(@as(u32, 1), try ring.submit()); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xcccccccc, + .res = buffer.len, + .flags = 0, + }, try ring.copy_cqe()); + try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]); + } + + // Test with a non-zero offset + + registered_fds[fd_index] = -1; + registered_fds[fd_index2] = -1; + try ring.register_files_update(1, registered_fds[1..]); + + { + // Next read should still work since fd_index in the registered file descriptors hasn't been updated yet. + const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0); + try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); + sqe.flags |= linux.IOSQE_FIXED_FILE; + + try testing.expectEqual(@as(u32, 1), try ring.submit()); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xcccccccc, + .res = buffer.len, + .flags = 0, + }, try ring.copy_cqe()); + try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]); + } + + try ring.register_files_update(0, registered_fds[0..]); + + { + // Now this should fail since both fds are sparse (-1) + const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0); + try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); + sqe.flags |= linux.IOSQE_FIXED_FILE; + + try testing.expectEqual(@as(u32, 1), try ring.submit()); + const cqe = try ring.copy_cqe(); + try testing.expectEqual(linux.E.BADF, cqe.err()); + } + + try ring.unregister_files(); +} + +test "shutdown" { + var ring = IoUring.init(16, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var address: linux.sockaddr.in = .{ + .port = 0, + .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), + }; + + // Socket bound, expect shutdown to work + { + const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + defer posix.close(server); + try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try posix.bind(server, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try posix.listen(server, 1); + + // set address to the OS-chosen IP/port. + var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); + try posix.getsockname(server, addrAny(&address), &slen); + + const shutdown_sqe = try ring.shutdown(0x445445445, server, linux.SHUT.RD); + try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode); + try testing.expectEqual(@as(i32, server), shutdown_sqe.fd); + + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + // This kernel's io_uring does not yet implement shutdown (kernel version < 5.11) + .INVAL => return error.SkipZigTest, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x445445445, + .res = 0, + .flags = 0, + }, cqe); + } + + // Socket not bound, expect to fail with ENOTCONN + { + const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + defer posix.close(server); + + const shutdown_sqe = ring.shutdown(0x445445445, server, linux.SHUT.RD) catch |err| switch (err) { + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + }; + try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode); + try testing.expectEqual(@as(i32, server), shutdown_sqe.fd); + + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + try testing.expectEqual(@as(u64, 0x445445445), cqe.user_data); + try testing.expectEqual(linux.E.NOTCONN, cqe.err()); + } +} + +test "renameat" { + const io = testing.io; + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const old_path = "test_io_uring_renameat_old"; + const new_path = "test_io_uring_renameat_new"; + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + // Write old file with data + + const old_file = try tmp.dir.createFile(io, old_path, .{}); + defer old_file.close(io); + try old_file.writeStreamingAll(io, "hello"); + + // Submit renameat + + const sqe = try ring.renameat( + 0x12121212, + tmp.dir.handle, + old_path, + tmp.dir.handle, + new_path, + 0, + ); + try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode); + try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd); + try testing.expectEqual(@as(i32, tmp.dir.handle), @as(i32, @bitCast(sqe.len))); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + // This kernel's io_uring does not yet implement renameat (kernel version < 5.11) + .BADF, .INVAL => return error.SkipZigTest, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x12121212, + .res = 0, + .flags = 0, + }, cqe); + + // Validate that the old file doesn't exist anymore + try testing.expectError(error.FileNotFound, tmp.dir.openFile(io, old_path, .{})); + + // Validate that the new file exists with the proper content + var new_file_data: [16]u8 = undefined; + try testing.expectEqualStrings("hello", try tmp.dir.readFile(io, new_path, &new_file_data)); +} + +test "unlinkat" { + const io = testing.io; + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const path = "test_io_uring_unlinkat"; + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + // Write old file with data + + const file = try tmp.dir.createFile(io, path, .{}); + defer file.close(io); + + // Submit unlinkat + + const sqe = try ring.unlinkat( + 0x12121212, + tmp.dir.handle, + path, + 0, + ); + try testing.expectEqual(linux.IORING_OP.UNLINKAT, sqe.opcode); + try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + // This kernel's io_uring does not yet implement unlinkat (kernel version < 5.11) + .BADF, .INVAL => return error.SkipZigTest, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x12121212, + .res = 0, + .flags = 0, + }, cqe); + + // Validate that the file doesn't exist anymore + _ = tmp.dir.openFile(io, path, .{}) catch |err| switch (err) { + error.FileNotFound => {}, + else => std.debug.panic("unexpected error: {}", .{err}), + }; +} + +test "mkdirat" { + const io = testing.io; + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const path = "test_io_uring_mkdirat"; + + // Submit mkdirat + + const sqe = try ring.mkdirat( + 0x12121212, + tmp.dir.handle, + path, + 0o0755, + ); + try testing.expectEqual(linux.IORING_OP.MKDIRAT, sqe.opcode); + try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + // This kernel's io_uring does not yet implement mkdirat (kernel version < 5.15) + .BADF, .INVAL => return error.SkipZigTest, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x12121212, + .res = 0, + .flags = 0, + }, cqe); + + // Validate that the directory exist + _ = try tmp.dir.openDir(io, path, .{}); +} + +test "symlinkat" { + const io = testing.io; + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const path = "test_io_uring_symlinkat"; + const link_path = "test_io_uring_symlinkat_link"; + + const file = try tmp.dir.createFile(io, path, .{}); + defer file.close(io); + + // Submit symlinkat + + const sqe = try ring.symlinkat( + 0x12121212, + path, + tmp.dir.handle, + link_path, + ); + try testing.expectEqual(linux.IORING_OP.SYMLINKAT, sqe.opcode); + try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + // This kernel's io_uring does not yet implement symlinkat (kernel version < 5.15) + .BADF, .INVAL => return error.SkipZigTest, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x12121212, + .res = 0, + .flags = 0, + }, cqe); + + // Validate that the symlink exist + _ = try tmp.dir.openFile(io, link_path, .{}); +} + +test "linkat" { + const io = testing.io; + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const first_path = "test_io_uring_linkat_first"; + const second_path = "test_io_uring_linkat_second"; + + // Write file with data + + const first_file = try tmp.dir.createFile(io, first_path, .{}); + defer first_file.close(io); + try first_file.writeStreamingAll(io, "hello"); + + // Submit linkat + + const sqe = try ring.linkat( + 0x12121212, + tmp.dir.handle, + first_path, + tmp.dir.handle, + second_path, + 0, + ); + try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode); + try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd); + try testing.expectEqual(@as(i32, tmp.dir.handle), @as(i32, @bitCast(sqe.len))); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + // This kernel's io_uring does not yet implement linkat (kernel version < 5.15) + .BADF, .INVAL => return error.SkipZigTest, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x12121212, + .res = 0, + .flags = 0, + }, cqe); + + // Validate the second file + var second_file_data: [16]u8 = undefined; + try testing.expectEqualStrings("hello", try tmp.dir.readFile(io, second_path, &second_file_data)); +} + +test "provide_buffers: read" { + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + defer posix.close(fd); + + const group_id = 1337; + const buffer_id = 0; + + const buffer_len = 128; + + var buffers: [4][buffer_len]u8 = undefined; + + // Provide 4 buffers + + { + const sqe = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id); + try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode); + try testing.expectEqual(@as(i32, buffers.len), sqe.fd); + try testing.expectEqual(@as(u32, buffers[0].len), sqe.len); + try testing.expectEqual(@as(u16, group_id), sqe.buf_index); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + // Happens when the kernel is < 5.7 + .INVAL, .BADF => return error.SkipZigTest, + .SUCCESS => {}, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data); + } + + // Do 4 reads which should consume all buffers + + var i: usize = 0; + while (i < buffers.len) : (i += 1) { + const sqe = try ring.read(0xdededede, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); + try testing.expectEqual(@as(i32, fd), sqe.fd); + try testing.expectEqual(@as(u64, 0), sqe.addr); + try testing.expectEqual(@as(u32, buffer_len), sqe.len); + try testing.expectEqual(@as(u16, group_id), sqe.buf_index); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + + try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); + const used_buffer_id = cqe.flags >> 16; + try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3); + try testing.expectEqual(@as(i32, buffer_len), cqe.res); + + try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data); + try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]); + } + + // This read should fail + + { + const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); + try testing.expectEqual(@as(i32, fd), sqe.fd); + try testing.expectEqual(@as(u64, 0), sqe.addr); + try testing.expectEqual(@as(u32, buffer_len), sqe.len); + try testing.expectEqual(@as(u16, group_id), sqe.buf_index); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + // Expected + .NOBUFS => {}, + .SUCCESS => std.debug.panic("unexpected success", .{}), + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data); + } + + // Provide 1 buffer again + + // Deliberately put something we don't expect in the buffers + @memset(mem.sliceAsBytes(&buffers), 42); + + const reprovided_buffer_id = 2; + + { + _ = try ring.provide_buffers(0xabababab, @as([*]u8, @ptrCast(&buffers[reprovided_buffer_id])), buffer_len, 1, group_id, reprovided_buffer_id); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + } + + // Final read which should work + + { + const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode); + try testing.expectEqual(@as(i32, fd), sqe.fd); + try testing.expectEqual(@as(u64, 0), sqe.addr); + try testing.expectEqual(@as(u32, buffer_len), sqe.len); + try testing.expectEqual(@as(u16, group_id), sqe.buf_index); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + + try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); + const used_buffer_id = cqe.flags >> 16; + try testing.expectEqual(used_buffer_id, reprovided_buffer_id); + try testing.expectEqual(@as(i32, buffer_len), cqe.res); + try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data); + try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]); + } +} + +test "remove_buffers" { + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); + defer posix.close(fd); + + const group_id = 1337; + const buffer_id = 0; + + const buffer_len = 128; + + var buffers: [4][buffer_len]u8 = undefined; + + // Provide 4 buffers + + { + _ = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .INVAL, .BADF => return error.SkipZigTest, + .SUCCESS => {}, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data); + } + + // Remove 3 buffers + + { + const sqe = try ring.remove_buffers(0xbababababa, 3, group_id); + try testing.expectEqual(linux.IORING_OP.REMOVE_BUFFERS, sqe.opcode); + try testing.expectEqual(@as(i32, 3), sqe.fd); + try testing.expectEqual(@as(u64, 0), sqe.addr); + try testing.expectEqual(@as(u16, group_id), sqe.buf_index); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(@as(u64, 0xbababababa), cqe.user_data); + } + + // This read should work + + { + _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + + try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); + const used_buffer_id = cqe.flags >> 16; + try testing.expect(used_buffer_id >= 0 and used_buffer_id < 4); + try testing.expectEqual(@as(i32, buffer_len), cqe.res); + try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data); + try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]); + } + + // Final read should _not_ work + + { + _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + // Expected + .NOBUFS => {}, + .SUCCESS => std.debug.panic("unexpected success", .{}), + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + } +} + +test "provide_buffers: accept/connect/send/recv" { + const io = testing.io; + _ = io; + + var ring = IoUring.init(16, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const group_id = 1337; + const buffer_id = 0; + + const buffer_len = 128; + var buffers: [4][buffer_len]u8 = undefined; + + // Provide 4 buffers + + { + const sqe = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id); + try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode); + try testing.expectEqual(@as(i32, buffers.len), sqe.fd); + try testing.expectEqual(@as(u32, buffer_len), sqe.len); + try testing.expectEqual(@as(u16, group_id), sqe.buf_index); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + // Happens when the kernel is < 5.7 + .INVAL => return error.SkipZigTest, + // Happens on the kernel 5.4 + .BADF => return error.SkipZigTest, + .SUCCESS => {}, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data); + } + + const socket_test_harness = try createSocketTestHarness(&ring); + defer socket_test_harness.close(); + + // Do 4 send on the socket + + { + var i: usize = 0; + while (i < buffers.len) : (i += 1) { + _ = try ring.send(0xdeaddead, socket_test_harness.server, &([_]u8{'z'} ** buffer_len), 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + } + + var cqes: [4]linux.io_uring_cqe = undefined; + try testing.expectEqual(@as(u32, 4), try ring.copy_cqes(&cqes, 4)); + } + + // Do 4 recv which should consume all buffers + + // Deliberately put something we don't expect in the buffers + @memset(mem.sliceAsBytes(&buffers), 1); + + var i: usize = 0; + while (i < buffers.len) : (i += 1) { + const sqe = try ring.recv(0xdededede, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode); + try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd); + try testing.expectEqual(@as(u64, 0), sqe.addr); + try testing.expectEqual(@as(u32, buffer_len), sqe.len); + try testing.expectEqual(@as(u16, group_id), sqe.buf_index); + try testing.expectEqual(@as(u32, 0), sqe.rw_flags); + try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + + try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); + const used_buffer_id = cqe.flags >> 16; + try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3); + try testing.expectEqual(@as(i32, buffer_len), cqe.res); + + try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data); + const buffer = buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]; + try testing.expectEqualSlices(u8, &([_]u8{'z'} ** buffer_len), buffer); + } + + // This recv should fail + + { + const sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode); + try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd); + try testing.expectEqual(@as(u64, 0), sqe.addr); + try testing.expectEqual(@as(u32, buffer_len), sqe.len); + try testing.expectEqual(@as(u16, group_id), sqe.buf_index); + try testing.expectEqual(@as(u32, 0), sqe.rw_flags); + try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + // Expected + .NOBUFS => {}, + .SUCCESS => std.debug.panic("unexpected success", .{}), + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data); + } + + // Provide 1 buffer again + + const reprovided_buffer_id = 2; + + { + _ = try ring.provide_buffers(0xabababab, @as([*]u8, @ptrCast(&buffers[reprovided_buffer_id])), buffer_len, 1, group_id, reprovided_buffer_id); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + } + + // Redo 1 send on the server socket + + { + _ = try ring.send(0xdeaddead, socket_test_harness.server, &([_]u8{'w'} ** buffer_len), 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + _ = try ring.copy_cqe(); + } + + // Final recv which should work + + // Deliberately put something we don't expect in the buffers + @memset(mem.sliceAsBytes(&buffers), 1); + + { + const sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0); + try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode); + try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd); + try testing.expectEqual(@as(u64, 0), sqe.addr); + try testing.expectEqual(@as(u32, buffer_len), sqe.len); + try testing.expectEqual(@as(u16, group_id), sqe.buf_index); + try testing.expectEqual(@as(u32, 0), sqe.rw_flags); + try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (cqe.err()) { + .SUCCESS => {}, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + + try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); + const used_buffer_id = cqe.flags >> 16; + try testing.expectEqual(used_buffer_id, reprovided_buffer_id); + try testing.expectEqual(@as(i32, buffer_len), cqe.res); + try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data); + const buffer = buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]; + try testing.expectEqualSlices(u8, &([_]u8{'w'} ** buffer_len), buffer); + } +} + +test "accept multishot" { + var ring = IoUring.init(16, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var address: linux.sockaddr.in = .{ + .port = 0, + .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), + }; + const listener_socket = try createListenerSocket(&address); + defer posix.close(listener_socket); + + // submit multishot accept operation + var addr: posix.sockaddr = undefined; + var addr_len: posix.socklen_t = @sizeOf(@TypeOf(addr)); + const userdata: u64 = 0xaaaaaaaa; + _ = try ring.accept_multishot(userdata, listener_socket, &addr, &addr_len, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + var nr: usize = 4; // number of clients to connect + while (nr > 0) : (nr -= 1) { + // connect client + const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + errdefer posix.close(client); + try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + + // test accept completion + var cqe = try ring.copy_cqe(); + if (cqe.err() == .INVAL) return error.SkipZigTest; + try testing.expect(cqe.res > 0); + try testing.expect(cqe.user_data == userdata); + try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE > 0); // more flag is set + + posix.close(client); + } +} + +test "accept/connect/send_zc/recv" { + try skipKernelLessThan(.{ .major = 6, .minor = 0, .patch = 0 }); + + const io = testing.io; + _ = io; + + var ring = IoUring.init(16, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const socket_test_harness = try createSocketTestHarness(&ring); + defer socket_test_harness.close(); + + const buffer_send = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe }; + var buffer_recv = [_]u8{0} ** 10; + + // zero-copy send + const sqe_send = try ring.send_zc(0xeeeeeeee, socket_test_harness.client, buffer_send[0..], 0, 0); + sqe_send.flags |= linux.IOSQE_IO_LINK; + _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0); + try testing.expectEqual(@as(u32, 2), try ring.submit()); + + var cqe_send = try ring.copy_cqe(); + // First completion of zero-copy send. + // IORING_CQE_F_MORE, means that there + // will be a second completion event / notification for the + // request, with the user_data field set to the same value. + // buffer_send must be keep alive until second cqe. + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xeeeeeeee, + .res = buffer_send.len, + .flags = linux.IORING_CQE_F_MORE, + }, cqe_send); + + cqe_send, const cqe_recv = brk: { + const cqe1 = try ring.copy_cqe(); + const cqe2 = try ring.copy_cqe(); + break :brk if (cqe1.user_data == 0xeeeeeeee) .{ cqe1, cqe2 } else .{ cqe2, cqe1 }; + }; + + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xffffffff, + .res = buffer_recv.len, + .flags = cqe_recv.flags & linux.IORING_CQE_F_SOCK_NONEMPTY, + }, cqe_recv); + try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]); + + // Second completion of zero-copy send. + // IORING_CQE_F_NOTIF in flags signals that kernel is done with send_buffer + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xeeeeeeee, + .res = 0, + .flags = linux.IORING_CQE_F_NOTIF, + }, cqe_send); +} + +test "accept_direct" { + try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 }); + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + var address: linux.sockaddr.in = .{ + .port = 0, + .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), + }; + + // register direct file descriptors + var registered_fds = [_]linux.fd_t{-1} ** 2; + try ring.register_files(registered_fds[0..]); + + const listener_socket = try createListenerSocket(&address); + defer posix.close(listener_socket); + + const accept_userdata: u64 = 0xaaaaaaaa; + const read_userdata: u64 = 0xbbbbbbbb; + const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe }; + + for (0..2) |_| { + for (registered_fds, 0..) |_, i| { + var buffer_recv = [_]u8{0} ** 16; + const buffer_send: []const u8 = data[0 .. data.len - i]; // make it different at each loop + + // submit accept, will chose registered fd and return index in cqe + _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + // connect + const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + defer posix.close(client); + + // accept completion + const cqe_accept = try ring.copy_cqe(); + try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err()); + const fd_index = cqe_accept.res; + try testing.expect(fd_index < registered_fds.len); + try testing.expect(cqe_accept.user_data == accept_userdata); + + // send data + _ = try posix.send(client, buffer_send, 0); + + // Example of how to use registered fd: + // Submit receive to fixed file returned by accept (fd_index). + // Fd field is set to registered file index, returned by accept. + // Flag linux.IOSQE_FIXED_FILE must be set. + const recv_sqe = try ring.recv(read_userdata, fd_index, .{ .buffer = &buffer_recv }, 0); + recv_sqe.flags |= linux.IOSQE_FIXED_FILE; + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + // accept receive + const recv_cqe = try ring.copy_cqe(); + try testing.expect(recv_cqe.user_data == read_userdata); + try testing.expect(recv_cqe.res == buffer_send.len); + try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]); + } + // no more available fds, accept will get NFILE error + { + // submit accept + _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + // connect + const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + defer posix.close(client); + // completion with error + const cqe_accept = try ring.copy_cqe(); + try testing.expect(cqe_accept.user_data == accept_userdata); + try testing.expectEqual(posix.E.NFILE, cqe_accept.err()); + } + // return file descriptors to kernel + try ring.register_files_update(0, registered_fds[0..]); + } + try ring.unregister_files(); +} + +test "accept_multishot_direct" { + try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 }); + + if (builtin.cpu.arch == .riscv64) { + // https://github.com/ziglang/zig/issues/25734 + return error.SkipZigTest; + } + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var address: linux.sockaddr.in = .{ + .port = 0, + .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), + }; + + var registered_fds = [_]linux.fd_t{-1} ** 2; + try ring.register_files(registered_fds[0..]); + + const listener_socket = try createListenerSocket(&address); + defer posix.close(listener_socket); + + const accept_userdata: u64 = 0xaaaaaaaa; + + for (0..2) |_| { + // submit multishot accept + // Will chose registered fd and return index of the selected registered file in cqe. + _ = try ring.accept_multishot_direct(accept_userdata, listener_socket, null, null, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + for (registered_fds) |_| { + // connect + const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + defer posix.close(client); + + // accept completion + const cqe_accept = try ring.copy_cqe(); + const fd_index = cqe_accept.res; + try testing.expect(fd_index < registered_fds.len); + try testing.expect(cqe_accept.user_data == accept_userdata); + try testing.expect(cqe_accept.flags & linux.IORING_CQE_F_MORE > 0); // has more is set + } + // No more available fds, accept will get NFILE error. + // Multishot is terminated (more flag is not set). + { + // connect + const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + defer posix.close(client); + // completion with error + const cqe_accept = try ring.copy_cqe(); + try testing.expect(cqe_accept.user_data == accept_userdata); + try testing.expectEqual(posix.E.NFILE, cqe_accept.err()); + try testing.expect(cqe_accept.flags & linux.IORING_CQE_F_MORE == 0); // has more is not set + } + // return file descriptors to kernel + try ring.register_files_update(0, registered_fds[0..]); + } + try ring.unregister_files(); +} + +test "socket" { + try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 }); + + var ring = IoUring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + // prepare, submit socket operation + _ = try ring.socket(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + // test completion + var cqe = try ring.copy_cqe(); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + const fd: linux.fd_t = @intCast(cqe.res); + try testing.expect(fd > 2); + + posix.close(fd); +} + +test "socket_direct/socket_direct_alloc/close_direct" { + try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 }); + + var ring = IoUring.init(2, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var registered_fds = [_]linux.fd_t{-1} ** 3; + try ring.register_files(registered_fds[0..]); + + // create socket in registered file descriptor at index 0 (last param) + _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + var cqe_socket = try ring.copy_cqe(); + try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err()); + try testing.expect(cqe_socket.res == 0); + + // create socket in registered file descriptor at index 1 (last param) + _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 1); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + cqe_socket = try ring.copy_cqe(); + try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err()); + try testing.expect(cqe_socket.res == 0); // res is 0 when index is specified + + // create socket in kernel chosen file descriptor index (_alloc version) + // completion res has index from registered files + _ = try ring.socket_direct_alloc(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + cqe_socket = try ring.copy_cqe(); + try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err()); + try testing.expect(cqe_socket.res == 2); // returns registered file index + + // use sockets from registered_fds in connect operation + var address: linux.sockaddr.in = .{ + .port = 0, + .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), + }; + const listener_socket = try createListenerSocket(&address); + defer posix.close(listener_socket); + const accept_userdata: u64 = 0xaaaaaaaa; + const connect_userdata: u64 = 0xbbbbbbbb; + const close_userdata: u64 = 0xcccccccc; + for (registered_fds, 0..) |_, fd_index| { + // prepare accept + _ = try ring.accept(accept_userdata, listener_socket, null, null, 0); + // prepare connect with fixed socket + const connect_sqe = try ring.connect(connect_userdata, @intCast(fd_index), addrAny(&address), @sizeOf(linux.sockaddr.in)); + connect_sqe.flags |= linux.IOSQE_FIXED_FILE; // fd is fixed file index + // submit both + try testing.expectEqual(@as(u32, 2), try ring.submit()); + // get completions + var cqe_connect = try ring.copy_cqe(); + var cqe_accept = try ring.copy_cqe(); + // ignore order + if (cqe_connect.user_data == accept_userdata and cqe_accept.user_data == connect_userdata) { + const a = cqe_accept; + const b = cqe_connect; + cqe_accept = b; + cqe_connect = a; + } + // test connect completion + try testing.expect(cqe_connect.user_data == connect_userdata); + try testing.expectEqual(posix.E.SUCCESS, cqe_connect.err()); + // test accept completion + try testing.expect(cqe_accept.user_data == accept_userdata); + try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err()); + + // submit and test close_direct + _ = try ring.close_direct(close_userdata, @intCast(fd_index)); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + var cqe_close = try ring.copy_cqe(); + try testing.expect(cqe_close.user_data == close_userdata); + try testing.expectEqual(posix.E.SUCCESS, cqe_close.err()); + } + + try ring.unregister_files(); +} + +test "openat_direct/close_direct" { + try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 }); + + var ring = IoUring.init(2, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + var registered_fds = [_]linux.fd_t{-1} ** 3; + try ring.register_files(registered_fds[0..]); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = "test_io_uring_close_direct"; + const flags: linux.O = .{ .ACCMODE = .RDWR, .CREAT = true }; + const mode: posix.mode_t = 0o666; + const user_data: u64 = 0; + + // use registered file at index 0 (last param) + _ = try ring.openat_direct(user_data, tmp.dir.handle, path, flags, mode, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + var cqe = try ring.copy_cqe(); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + try testing.expect(cqe.res == 0); + + // use registered file at index 1 + _ = try ring.openat_direct(user_data, tmp.dir.handle, path, flags, mode, 1); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + cqe = try ring.copy_cqe(); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + try testing.expect(cqe.res == 0); // res is 0 when we specify index + + // let kernel choose registered file index + _ = try ring.openat_direct(user_data, tmp.dir.handle, path, flags, mode, linux.IORING_FILE_INDEX_ALLOC); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + cqe = try ring.copy_cqe(); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + try testing.expect(cqe.res == 2); // chosen index is in res + + // close all open file descriptors + for (registered_fds, 0..) |_, fd_index| { + _ = try ring.close_direct(user_data, @intCast(fd_index)); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + var cqe_close = try ring.copy_cqe(); + try testing.expectEqual(posix.E.SUCCESS, cqe_close.err()); + } + try ring.unregister_files(); +} + +test "ring mapped buffers recv" { + const io = testing.io; + _ = io; + + var ring = IoUring.init(16, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + // init buffer group + const group_id: u16 = 1; // buffers group id + const buffers_count: u16 = 2; // number of buffers in buffer group + const buffer_size: usize = 4; // size of each buffer in group + var buf_grp = BufferGroup.init( + &ring, + testing.allocator, + group_id, + buffer_size, + buffers_count, + ) catch |err| switch (err) { + // kernel older than 5.19 + error.ArgumentsInvalid => return error.SkipZigTest, + else => return err, + }; + defer buf_grp.deinit(testing.allocator); + + // create client/server fds + const fds = try createSocketTestHarness(&ring); + defer fds.close(); + + // for random user_data in sqe/cqe + var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed); + var rnd = Rnd.random(); + + var round: usize = 4; // repeat send/recv cycle round times + while (round > 0) : (round -= 1) { + // client sends data + const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe }; + { + const user_data = rnd.int(u64); + _ = try ring.send(user_data, fds.client, data[0..], 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + const cqe_send = try ring.copy_cqe(); + if (cqe_send.err() == .INVAL) return error.SkipZigTest; + try testing.expectEqual(linux.io_uring_cqe{ .user_data = user_data, .res = data.len, .flags = 0 }, cqe_send); + } + var pos: usize = 0; + + // read first chunk + const cqe1 = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64)); + var buf = try buf_grp.get(cqe1); + try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf); + pos += buf.len; + // second chunk + const cqe2 = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64)); + buf = try buf_grp.get(cqe2); + try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf); + pos += buf.len; + + // both buffers provided to the kernel are used so we get error + // 'no more buffers', until we put buffers to the kernel + { + const user_data = rnd.int(u64); + _ = try buf_grp.recv(user_data, fds.server, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + const cqe = try ring.copy_cqe(); + try testing.expectEqual(user_data, cqe.user_data); + try testing.expect(cqe.res < 0); // fail + try testing.expectEqual(posix.E.NOBUFS, cqe.err()); + try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only + try testing.expectError(error.NoBufferSelected, cqe.buffer_id()); + } + + // put buffers back to the kernel + try buf_grp.put(cqe1); + try buf_grp.put(cqe2); + + // read remaining data + while (pos < data.len) { + const cqe = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64)); + buf = try buf_grp.get(cqe); + try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf); + pos += buf.len; + try buf_grp.put(cqe); + } + } +} + +test "ring mapped buffers multishot recv" { + const io = testing.io; + _ = io; + + var ring = IoUring.init(16, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + // init buffer group + const group_id: u16 = 1; // buffers group id + const buffers_count: u16 = 2; // number of buffers in buffer group + const buffer_size: usize = 4; // size of each buffer in group + var buf_grp = BufferGroup.init( + &ring, + testing.allocator, + group_id, + buffer_size, + buffers_count, + ) catch |err| switch (err) { + // kernel older than 5.19 + error.ArgumentsInvalid => return error.SkipZigTest, + else => return err, + }; + defer buf_grp.deinit(testing.allocator); + + // create client/server fds + const fds = try createSocketTestHarness(&ring); + defer fds.close(); + + // for random user_data in sqe/cqe + var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed); + var rnd = Rnd.random(); + + var round: usize = 4; // repeat send/recv cycle round times + while (round > 0) : (round -= 1) { + // client sends data + const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf }; + { + const user_data = rnd.int(u64); + _ = try ring.send(user_data, fds.client, data[0..], 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + const cqe_send = try ring.copy_cqe(); + if (cqe_send.err() == .INVAL) return error.SkipZigTest; + try testing.expectEqual(linux.io_uring_cqe{ .user_data = user_data, .res = data.len, .flags = 0 }, cqe_send); + } + + // start multishot recv + var recv_user_data = rnd.int(u64); + _ = try buf_grp.recv_multishot(recv_user_data, fds.server, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit + + // server reads data into provided buffers + // there are 2 buffers of size 4, so each read gets only chunk of data + // we read four chunks of 4, 4, 4, 4 bytes each + var chunk: []const u8 = data[0..buffer_size]; // first chunk + const cqe1 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk); + try testing.expect(cqe1.flags & linux.IORING_CQE_F_MORE > 0); + + chunk = data[buffer_size .. buffer_size * 2]; // second chunk + const cqe2 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk); + try testing.expect(cqe2.flags & linux.IORING_CQE_F_MORE > 0); + + // both buffers provided to the kernel are used so we get error + // 'no more buffers', until we put buffers to the kernel + { + const cqe = try ring.copy_cqe(); + try testing.expectEqual(recv_user_data, cqe.user_data); + try testing.expect(cqe.res < 0); // fail + try testing.expectEqual(posix.E.NOBUFS, cqe.err()); + try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only + // has more is not set + // indicates that multishot is finished + try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE == 0); + try testing.expectError(error.NoBufferSelected, cqe.buffer_id()); + } + + // put buffers back to the kernel + try buf_grp.put(cqe1); + try buf_grp.put(cqe2); + + // restart multishot + recv_user_data = rnd.int(u64); + _ = try buf_grp.recv_multishot(recv_user_data, fds.server, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit + + chunk = data[buffer_size * 2 .. buffer_size * 3]; // third chunk + const cqe3 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk); + try testing.expect(cqe3.flags & linux.IORING_CQE_F_MORE > 0); + try buf_grp.put(cqe3); + + chunk = data[buffer_size * 3 ..]; // last chunk + const cqe4 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk); + try testing.expect(cqe4.flags & linux.IORING_CQE_F_MORE > 0); + try buf_grp.put(cqe4); + + // cancel pending multishot recv operation + { + const cancel_user_data = rnd.int(u64); + _ = try ring.cancel(cancel_user_data, recv_user_data, 0); + try testing.expectEqual(@as(u32, 1), try ring.submit()); + + // expect completion of cancel operation and completion of recv operation + var cqe_cancel = try ring.copy_cqe(); + if (cqe_cancel.err() == .INVAL) return error.SkipZigTest; + var cqe_recv = try ring.copy_cqe(); + if (cqe_recv.err() == .INVAL) return error.SkipZigTest; + + // don't depend on order of completions + if (cqe_cancel.user_data == recv_user_data and cqe_recv.user_data == cancel_user_data) { + const a = cqe_cancel; + const b = cqe_recv; + cqe_cancel = b; + cqe_recv = a; + } + + // Note on different kernel results: + // on older kernel (tested with v6.0.16, v6.1.57, v6.2.12, v6.4.16) + // cqe_cancel.err() == .NOENT + // cqe_recv.err() == .NOBUFS + // on kernel (tested with v6.5.0, v6.5.7) + // cqe_cancel.err() == .SUCCESS + // cqe_recv.err() == .CANCELED + // Upstream reference: https://github.com/axboe/liburing/issues/984 + + // cancel operation is success (or NOENT on older kernels) + try testing.expectEqual(cancel_user_data, cqe_cancel.user_data); + try testing.expect(cqe_cancel.err() == .NOENT or cqe_cancel.err() == .SUCCESS); + + // recv operation is failed with err CANCELED (or NOBUFS on older kernels) + try testing.expectEqual(recv_user_data, cqe_recv.user_data); + try testing.expect(cqe_recv.res < 0); + try testing.expect(cqe_recv.err() == .NOBUFS or cqe_recv.err() == .CANCELED); + try testing.expect(cqe_recv.flags & linux.IORING_CQE_F_MORE == 0); + } + } +} + +test "copy_cqes with wrapping sq.cqes buffer" { + var ring = IoUring.init(2, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + try testing.expectEqual(2, ring.sq.sqes.len); + try testing.expectEqual(4, ring.cq.cqes.len); + + // submit 2 entries, receive 2 completions + var cqes: [8]linux.io_uring_cqe = undefined; + { + for (0..2) |_| { + const sqe = try ring.get_sqe(); + sqe.prep_timeout(&.{ .sec = 0, .nsec = 10000 }, 0, 0); + try testing.expect(try ring.submit() == 1); + } + var cqe_count: u32 = 0; + while (cqe_count < 2) { + cqe_count += try ring.copy_cqes(&cqes, 2 - cqe_count); + } + } + + try testing.expectEqual(2, ring.cq.head.*); + + // sq.sqes len is 4, starting at position 2 + // every 4 entries submit wraps completion buffer + // we are reading ring.cq.cqes at indexes 2,3,0,1 + for (1..1024) |i| { + for (0..4) |_| { + const sqe = try ring.get_sqe(); + sqe.prep_timeout(&.{ .sec = 0, .nsec = 10000 }, 0, 0); + try testing.expect(try ring.submit() == 1); + } + var cqe_count: u32 = 0; + while (cqe_count < 4) { + cqe_count += try ring.copy_cqes(&cqes, 4 - cqe_count); + } + try testing.expectEqual(4, cqe_count); + try testing.expectEqual(2 + 4 * i, ring.cq.head.*); + } +} + +test "bind/listen/connect" { + if (builtin.cpu.arch == .s390x) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25956 + + var ring = IoUring.init(4, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const probe = ring.get_probe() catch return error.SkipZigTest; + // LISTEN is higher required operation + if (!probe.is_supported(.LISTEN)) return error.SkipZigTest; + + var addr: linux.sockaddr.in = .{ + .port = 0, + .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), + }; + const proto: u32 = if (addr.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP; + + const listen_fd = brk: { + // Create socket + _ = try ring.socket(1, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0); + try testing.expectEqual(1, try ring.submit()); + var cqe = try ring.copy_cqe(); + try testing.expectEqual(1, cqe.user_data); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + const listen_fd: linux.fd_t = @intCast(cqe.res); + try testing.expect(listen_fd > 2); + + // Prepare: set socket option * 2, bind, listen + var optval: u32 = 1; + (try ring.setsockopt(2, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval))).link_next(); + (try ring.setsockopt(3, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEPORT, mem.asBytes(&optval))).link_next(); + (try ring.bind(4, listen_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in), 0)).link_next(); + _ = try ring.listen(5, listen_fd, 1, 0); + // Submit 4 operations + try testing.expectEqual(4, try ring.submit()); + // Expect all to succeed + for (2..6) |user_data| { + cqe = try ring.copy_cqe(); + try testing.expectEqual(user_data, cqe.user_data); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + } + + // Check that socket option is set + optval = 0; + _ = try ring.getsockopt(5, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval)); + try testing.expectEqual(1, try ring.submit()); + cqe = try ring.copy_cqe(); + try testing.expectEqual(5, cqe.user_data); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + try testing.expectEqual(1, optval); + + // Read system assigned port into addr + var addr_len: posix.socklen_t = @sizeOf(linux.sockaddr.in); + try posix.getsockname(listen_fd, addrAny(&addr), &addr_len); + + break :brk listen_fd; + }; + + const connect_fd = brk: { + // Create connect socket + _ = try ring.socket(6, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0); + try testing.expectEqual(1, try ring.submit()); + const cqe = try ring.copy_cqe(); + try testing.expectEqual(6, cqe.user_data); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + // Get connect socket fd + const connect_fd: linux.fd_t = @intCast(cqe.res); + try testing.expect(connect_fd > 2 and connect_fd != listen_fd); + break :brk connect_fd; + }; + + // Prepare accept/connect operations + _ = try ring.accept(7, listen_fd, null, null, 0); + _ = try ring.connect(8, connect_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in)); + try testing.expectEqual(2, try ring.submit()); + // Get listener accepted socket + var accept_fd: posix.socket_t = 0; + for (0..2) |_| { + const cqe = try ring.copy_cqe(); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + if (cqe.user_data == 7) { + accept_fd = @intCast(cqe.res); + } else { + try testing.expectEqual(8, cqe.user_data); + } + } + try testing.expect(accept_fd > 2 and accept_fd != listen_fd and accept_fd != connect_fd); + + // Communicate + try testSendRecv(&ring, connect_fd, accept_fd); + try testSendRecv(&ring, accept_fd, connect_fd); + + // Shutdown and close all sockets + for ([_]posix.socket_t{ connect_fd, accept_fd, listen_fd }) |fd| { + (try ring.shutdown(9, fd, posix.SHUT.RDWR)).link_next(); + _ = try ring.close(10, fd); + try testing.expectEqual(2, try ring.submit()); + for (0..2) |i| { + const cqe = try ring.copy_cqe(); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + try testing.expectEqual(9 + i, cqe.user_data); + } + } +} + +// Prepare, submit recv and get cqe using buffer group. +fn buf_grp_recv_submit_get_cqe( + ring: *IoUring, + buf_grp: *BufferGroup, + fd: linux.fd_t, + user_data: u64, +) !linux.io_uring_cqe { + // prepare and submit recv + const sqe = try buf_grp.recv(user_data, fd, 0); + try testing.expect(sqe.flags & linux.IOSQE_BUFFER_SELECT == linux.IOSQE_BUFFER_SELECT); + try testing.expect(sqe.buf_index == buf_grp.group_id); + try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit + // get cqe, expect success + const cqe = try ring.copy_cqe(); + try testing.expectEqual(user_data, cqe.user_data); + try testing.expect(cqe.res >= 0); // success + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); // IORING_CQE_F_BUFFER flag is set + + return cqe; +} + +fn expect_buf_grp_cqe( + ring: *IoUring, + buf_grp: *BufferGroup, + user_data: u64, + expected: []const u8, +) !linux.io_uring_cqe { + // get cqe + const cqe = try ring.copy_cqe(); + try testing.expectEqual(user_data, cqe.user_data); + try testing.expect(cqe.res >= 0); // success + try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); // IORING_CQE_F_BUFFER flag is set + try testing.expectEqual(expected.len, @as(usize, @intCast(cqe.res))); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + + // get buffer from pool + const buffer_id = try cqe.buffer_id(); + const len = @as(usize, @intCast(cqe.res)); + const buf = buf_grp.get_by_id(buffer_id)[0..len]; + try testing.expectEqualSlices(u8, expected, buf); + + return cqe; +} + +fn testSendRecv(ring: *IoUring, send_fd: posix.socket_t, recv_fd: posix.socket_t) !void { + const buffer_send = "0123456789abcdf" ** 10; + var buffer_recv: [buffer_send.len * 2]u8 = undefined; + + // 2 sends + _ = try ring.send(1, send_fd, buffer_send, linux.MSG.WAITALL); + _ = try ring.send(2, send_fd, buffer_send, linux.MSG.WAITALL); + try testing.expectEqual(2, try ring.submit()); + for (0..2) |i| { + const cqe = try ring.copy_cqe(); + try testing.expectEqual(1 + i, cqe.user_data); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + try testing.expectEqual(buffer_send.len, @as(usize, @intCast(cqe.res))); + } + + // receive + var recv_len: usize = 0; + while (recv_len < buffer_send.len * 2) { + _ = try ring.recv(3, recv_fd, .{ .buffer = buffer_recv[recv_len..] }, 0); + try testing.expectEqual(1, try ring.submit()); + const cqe = try ring.copy_cqe(); + try testing.expectEqual(3, cqe.user_data); + try testing.expectEqual(posix.E.SUCCESS, cqe.err()); + recv_len += @intCast(cqe.res); + } + + // inspect recv buffer + try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]); + try testing.expectEqualSlices(u8, buffer_send, buffer_recv[buffer_send.len..]); +} + +/// Used for testing server/client interactions. +pub const SocketTestHarness = struct { + listener: posix.socket_t, + server: posix.socket_t, + client: posix.socket_t, + + pub fn close(self: SocketTestHarness) void { + posix.close(self.client); + posix.close(self.listener); + } +}; + +pub fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness { + // Create a TCP server socket + var address: linux.sockaddr.in = .{ + .port = 0, + .addr = @bitCast([4]u8{ 127, 0, 0, 1 }), + }; + const listener_socket = try createListenerSocket(&address); + errdefer posix.close(listener_socket); + + // Submit 1 accept + var accept_addr: posix.sockaddr = undefined; + var accept_addr_len: posix.socklen_t = @sizeOf(@TypeOf(accept_addr)); + _ = try ring.accept(0xaaaaaaaa, listener_socket, &accept_addr, &accept_addr_len, 0); + + // Create a TCP client socket + const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + errdefer posix.close(client); + _ = try ring.connect(0xcccccccc, client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + + try testing.expectEqual(@as(u32, 2), try ring.submit()); + + var cqe_accept = try ring.copy_cqe(); + if (cqe_accept.err() == .INVAL) return error.SkipZigTest; + var cqe_connect = try ring.copy_cqe(); + if (cqe_connect.err() == .INVAL) return error.SkipZigTest; + + // The accept/connect CQEs may arrive in any order, the connect CQE will sometimes come first: + if (cqe_accept.user_data == 0xcccccccc and cqe_connect.user_data == 0xaaaaaaaa) { + const a = cqe_accept; + const b = cqe_connect; + cqe_accept = b; + cqe_connect = a; + } + + try testing.expectEqual(@as(u64, 0xaaaaaaaa), cqe_accept.user_data); + if (cqe_accept.res <= 0) std.debug.print("\ncqe_accept.res={}\n", .{cqe_accept.res}); + try testing.expect(cqe_accept.res > 0); + try testing.expectEqual(@as(u32, 0), cqe_accept.flags); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xcccccccc, + .res = 0, + .flags = 0, + }, cqe_connect); + + // All good + + return SocketTestHarness{ + .listener = listener_socket, + .server = cqe_accept.res, + .client = client, + }; +} + +fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t { + const kernel_backlog = 1; + const listener_socket = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + errdefer posix.close(listener_socket); + + try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try posix.bind(listener_socket, addrAny(address), @sizeOf(linux.sockaddr.in)); + try posix.listen(listener_socket, kernel_backlog); + + // set address to the OS-chosen IP/port. + var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in); + try posix.getsockname(listener_socket, addrAny(address), &slen); + + return listener_socket; +} + +/// For use in tests. Returns SkipZigTest if kernel version is less than required. +inline fn skipKernelLessThan(required: std.SemanticVersion) !void { + var uts: linux.utsname = undefined; + const res = linux.uname(&uts); + switch (linux.errno(res)) { + .SUCCESS => {}, + else => |errno| return posix.unexpectedErrno(errno), + } + + const release = mem.sliceTo(&uts.release, 0); + // Strips potential extra, as kernel version might not be semver compliant, example "6.8.9-300.fc40.x86_64" + const extra_index = std.mem.indexOfAny(u8, release, "-+"); + const stripped = release[0..(extra_index orelse release.len)]; + // Make sure the input don't rely on the extra we just stripped + try testing.expect(required.pre == null and required.build == null); + + var current = try std.SemanticVersion.parse(stripped); + current.pre = null; // don't check pre field + if (required.order(current) == .gt) return error.SkipZigTest; +} + +fn addrAny(addr: *linux.sockaddr.in) *linux.sockaddr { + return @ptrCast(addr); +} diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index cfafebe82648a79dda36c2e1564bc6004ff6a352..974ce0a25c7993f1dff45471e40fe9f2d1b5d287 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -18,7 +18,7 @@ test "fallocate" { defer tmp.cleanup(); const path = "test_fallocate"; - const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .mode = 0o666 }); + const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .permissions = .fromMode(0o666) }); defer file.close(io); try expect((try file.stat(io)).size == 0); @@ -125,7 +125,7 @@ test "fadvise" { defer file.close(io); var buf: [2048]u8 = undefined; - try file.writeAll(&buf); + try file.writeStreamingAll(io, &buf); const ret = linux.fadvise(file.handle, 0, 0, linux.POSIX_FADV.SEQUENTIAL); try expectEqual(@as(usize, 0), ret); -- 2.54.0 From d8b1cc953eef07079968093babcd1ce3a2020c73 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 14:36:50 -0800 Subject: [PATCH 075/195] std.Io.Dir.renameAbsolute: consistent parameter order --- lib/std/Io/Dir.zig | 2 +- lib/std/fs/test.zig | 112 ++++++++++++++++++----------------------- lib/std/posix/test.zig | 4 +- 3 files changed, 52 insertions(+), 66 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 31cb59ae29da703e5f14089b84f613c9ad1e353f..b67c9d048d2999c3985a0788de5290cf45620a57 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -982,7 +982,7 @@ pub fn rename( return io.vtable.dirRename(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path); } -pub fn renameAbsolute(io: Io, old_path: []const u8, new_path: []const u8) RenameError!void { +pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) RenameError!void { assert(path.isAbsolute(old_path)); assert(path.isAbsolute(new_path)); const my_cwd = cwd(); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 87d28b2ad02064dbffa106f3ca26c1df50d7ee12..dea562578a23ad627c31b35eab8e8917ac23e14a 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -251,7 +251,7 @@ test "Dir.readLink on non-symlinks" { fn testReadLink(io: Io, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { var buffer: [Dir.max_path_bytes]u8 = undefined; - const actual = try dir.readLink(io, symlink_path, &buffer); + const actual = buffer[0..try dir.readLink(io, symlink_path, &buffer)]; try expectEqualStrings(target_path, actual); } @@ -289,7 +289,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { try setupSymlink(io, ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); - var symlink: Dir = try ctx.dir.openDir("symlink", .{ .follow_symlinks = false }); + var symlink: Dir = try ctx.dir.openDir(io, "symlink", .{ .follow_symlinks = false }); defer symlink.close(io); const stat = try symlink.stat(io); @@ -807,7 +807,7 @@ test "directory operations on files" { try expectError(error.NotDir, ctx.dir.deleteDir(io, test_file_name)); if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { - try expectError(error.PathAlreadyExists, Dir.makeDirAbsolute(io, test_file_name)); + try expectError(error.PathAlreadyExists, Dir.makeDirAbsolute(io, test_file_name, .default_dir)); try expectError(error.NotDir, Dir.deleteDirAbsolute(io, test_file_name)); } @@ -1104,9 +1104,9 @@ test "renameAbsolute" { const base_path = try tmp_dir.dir.realPathAlloc(io, ".", allocator); try expectError(error.FileNotFound, Dir.renameAbsolute( - io, try Dir.path.join(allocator, &.{ base_path, "missing_file_name" }), try Dir.path.join(allocator, &.{ base_path, "something_else" }), + io, )); // Renaming files @@ -1115,9 +1115,9 @@ test "renameAbsolute" { var file = try tmp_dir.dir.createFile(io, test_file_name, .{ .read = true }); file.close(io); try Dir.renameAbsolute( - io, try Dir.path.join(allocator, &.{ base_path, test_file_name }), try Dir.path.join(allocator, &.{ base_path, renamed_test_file_name }), + io, ); // ensure the file was renamed @@ -1132,9 +1132,9 @@ test "renameAbsolute" { const renamed_test_dir_name = "test_dir_renamed"; try tmp_dir.dir.makeDir(io, test_dir_name, .default_dir); try Dir.renameAbsolute( - io, try Dir.path.join(allocator, &.{ base_path, test_dir_name }), try Dir.path.join(allocator, &.{ base_path, renamed_test_dir_name }), + io, ); // ensure the directory was renamed @@ -1430,7 +1430,7 @@ test "writev, readv" { var src_file = try tmp.dir.createFile(io, "test.txt", .{ .read = true }); defer src_file.close(io); - var writer = src_file.writerStreaming(&.{}); + var writer = src_file.writerStreaming(io, &.{}); try writer.interface.writeVecAll(&write_vecs); try writer.interface.flush(); @@ -1590,10 +1590,10 @@ test "copyFile" { try ctx.dir.writeFile(io, .{ .sub_path = src_file, .data = data }); defer ctx.dir.deleteFile(io, src_file) catch {}; - try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{}); + try ctx.dir.copyFile(src_file, ctx.dir, dest_file, io, .{}); defer ctx.dir.deleteFile(io, dest_file) catch {}; - try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode }); + try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, io, .{ .override_mode = File.default_mode }); defer ctx.dir.deleteFile(io, dest_file2) catch {}; try expectFileContents(io, ctx.dir, dest_file, data); @@ -1968,7 +1968,7 @@ test "'.' and '..' in Dir functions" { created_file.close(io); try ctx.dir.access(io, file_path, .{}); - try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{}); + try ctx.dir.copyFile(file_path, ctx.dir, copy_path, io, .{}); try ctx.dir.rename(copy_path, ctx.dir, rename_path, io); const renamed_file = try ctx.dir.openFile(io, rename_path, .{}); renamed_file.close(io); @@ -2000,7 +2000,7 @@ test "'.' and '..' in absolute functions" { const base_path = try tmp.dir.realPathAlloc(io, ".", allocator); const subdir_path = try Dir.path.join(allocator, &.{ base_path, "./subdir" }); - try Dir.makeDirAbsolute(io, subdir_path); + try Dir.makeDirAbsolute(io, subdir_path, .default_dir); try Dir.accessAbsolute(io, subdir_path, .{}); var created_subdir = try Dir.openDirAbsolute(io, subdir_path, .{}); created_subdir.close(io); @@ -2011,10 +2011,10 @@ test "'.' and '..' in absolute functions" { try Dir.accessAbsolute(io, created_file_path, .{}); const copied_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../copy" }); - try Dir.copyFileAbsolute(io, created_file_path, copied_file_path, .{}); + try Dir.copyFileAbsolute(created_file_path, copied_file_path, io, .{}); const renamed_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../rename" }); - try Dir.renameAbsolute(io, copied_file_path, renamed_file_path); - const renamed_file = try Dir.openFileAbsolute(renamed_file_path, .{}); + try Dir.renameAbsolute(copied_file_path, renamed_file_path, io); + const renamed_file = try Dir.openFileAbsolute(io, renamed_file_path, .{}); renamed_file.close(io); try Dir.deleteFileAbsolute(io, renamed_file_path); @@ -2044,7 +2044,7 @@ test "chmod" { try expectEqual(0o700, (try dir.stat(io)).permissions.toMode() & 0o7777); } -test "chown" { +test "change ownership" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; @@ -2055,13 +2055,13 @@ test "chown" { const file = try tmp.dir.createFile(io, "test_file", .{}); defer file.close(io); - try file.chown(null, null); + try file.setOwner(io, null, null); try tmp.dir.makeDir(io, "test_dir", .default_dir); var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); defer dir.close(io); - try dir.chown(null, null); + try dir.setOwner(io, null, null); } test "invalid UTF-8/WTF-8 paths" { @@ -2116,7 +2116,7 @@ test "invalid UTF-8/WTF-8 paths" { var dir = ctx.dir; try expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{})); - try expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{})); + try expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, io, .{})); try expectError(expected_err, ctx.dir.statFile(invalid_path)); @@ -2128,12 +2128,12 @@ test "invalid UTF-8/WTF-8 paths" { try expectError(expected_err, Dir.rename(ctx.dir, invalid_path, ctx.dir, invalid_path, io)); if (native_os != .wasi and ctx.path_type != .relative) { - try expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, .{})); - try expectError(expected_err, Dir.makeDirAbsolute(invalid_path)); + try expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, io, .{})); + try expectError(expected_err, Dir.makeDirAbsolute(io, invalid_path, .default_dir)); try expectError(expected_err, Dir.deleteDirAbsolute(invalid_path)); - try expectError(expected_err, Dir.renameAbsolute(invalid_path, invalid_path)); + try expectError(expected_err, Dir.renameAbsolute(invalid_path, invalid_path, io)); try expectError(expected_err, Dir.openDirAbsolute(io, invalid_path, .{})); - try expectError(expected_err, Dir.openFileAbsolute(invalid_path, .{})); + try expectError(expected_err, Dir.openFileAbsolute(io, invalid_path, .{})); try expectError(expected_err, Dir.accessAbsolute(invalid_path, .{})); try expectError(expected_err, Dir.createFileAbsolute(invalid_path, .{})); try expectError(expected_err, Dir.deleteFileAbsolute(invalid_path)); @@ -2157,7 +2157,7 @@ test "read file non vectored" { const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); defer file.close(io); { - var file_writer: File.Writer = .init(file, &.{}); + var file_writer: File.Writer = .init(file, io, &.{}); try file_writer.interface.writeAll(contents); try file_writer.interface.flush(); } @@ -2189,7 +2189,7 @@ test "seek keeping partial buffer" { const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); defer file.close(io); { - var file_writer: File.Writer = .init(file, &.{}); + var file_writer: File.Writer = .init(file, io, &.{}); try file_writer.interface.writeAll(contents); try file_writer.interface.flush(); } @@ -2251,7 +2251,7 @@ test "seekTo flushes buffered data" { defer file.close(io); { var buf: [16]u8 = undefined; - var file_writer = file.writer(io, file, &buf); + var file_writer = file.writer(io, &buf); try file_writer.interface.writeAll(contents); try file_writer.seekTo(8); @@ -2285,7 +2285,7 @@ test "File.Writer sendfile with buffered contents" { try in_r.interface.fill(2); var out_buf: [1]u8 = undefined; - var out_w = out.writerStreaming(&out_buf); + var out_w = out.writerStreaming(io, &out_buf); try out_w.interface.writeByte('a'); try expectEqual(3, try out_w.interface.sendFileAll(&in_r, .unlimited)); try out_w.interface.flush(); @@ -2325,16 +2325,17 @@ test "readlinkat" { try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = "nonsense" }); // create a symbolic link - tmp.dir.symLink("file.txt", "link", .{}) catch |err| switch (err) { + tmp.dir.symLink(io, "file.txt", "link", .{}) catch |err| switch (err) { error.AccessDenied => { // Symlink requires admin privileges on windows, so this test can legitimately fail. if (native_os == .windows) return error.SkipZigTest; }, + else => |e| return e, }; // read the link var buffer: [Dir.max_path_bytes]u8 = undefined; - const read_link = try tmp.dir.readLink(io, "link", &buffer); + const read_link = buffer[0..try tmp.dir.readLink(io, "link", &buffer)]; try expectEqualStrings("file.txt", read_link); } @@ -2351,7 +2352,7 @@ test "fchmodat smoke test" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try expectError(error.FileNotFound, tmp.dir.setPermissions(io, "regfile", 0o666, .{})); + try expectError(error.FileNotFound, tmp.dir.setFilePermissions(io, "regfile", .fromMode(0o666), .{})); const file = try tmp.dir.createFile(io, "regfile", .{ .exclusive = true, .permissions = .fromMode(0o644), @@ -2384,15 +2385,15 @@ test "fchmodat smoke test" { error.OperationNotSupported => test_link = false, else => |e| return e, }; - if (test_link) - try expectMode(tmp.dir.handle, "symlink", 0o600); - try expectMode(tmp.dir.handle, "regfile", 0o640); + if (test_link) try expectMode(io, tmp.dir, "symlink", .fromMode(0o600)); + try expectMode(io, tmp.dir, "regfile", .fromMode(0o640)); } fn expectMode(io: Io, dir: Dir, file: []const u8, permissions: File.Permissions) !void { const mode = permissions.toMode(); const st = try dir.statFile(io, file, .{ .follow_symlinks = false }); - try expectEqual(mode, st.mode & 0b111_111_111); + const found_mode = st.permissions.toMode(); + try expectEqual(mode, found_mode & 0b111_111_111); } test "isatty" { @@ -2417,7 +2418,7 @@ test "read positional empty buffer" { defer file.close(io); var buffer: [0]u8 = undefined; - try expectEqual(0, try file.readPositional(io, &buffer, 0)); + try expectEqual(0, try file.readPositional(io, &.{&buffer}, 0)); } test "write streaming empty buffer" { @@ -2429,8 +2430,8 @@ test "write streaming empty buffer" { var file = try tmp.dir.createFile(io, "write_empty", .{}); defer file.close(io); - var buffer: [0]u8 = &.{}; - try expectEqual(0, try file.writeStreaming(io, &buffer)); + const buffer: [0]u8 = .{}; + try file.writeStreamingAll(io, &buffer); } test "write positional empty buffer" { @@ -2442,8 +2443,8 @@ test "write positional empty buffer" { var file = try tmp.dir.createFile(io, "pwrite_empty", .{}); defer file.close(io); - var buffer: [0]u8 = &.{}; - try expectEqual(0, try file.writePositional(io, &buffer, 0)); + const buffer: [0]u8 = .{}; + try expectEqual(0, try file.writePositional(io, &.{&buffer}, 0)); } test "access smoke test" { @@ -2452,53 +2453,38 @@ test "access smoke test" { if (native_os == .openbsd) return error.SkipZigTest; const io = testing.io; - const gpa = testing.allocator; var tmp = tmpDir(.{}); defer tmp.cleanup(); - const base_path = try tmp.dir.realPathAlloc(io, ".", gpa); - defer gpa.free(base_path); - { // Create some file using `open`. - const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); - defer gpa.free(file_path); - const file = Dir.cwd().createFile(io, file_path, .{ .read = true, .exclusive = true }); + const file = try tmp.dir.createFile(io, "some_file", .{ .read = true, .exclusive = true }); file.close(io); } { // Try to access() the file - const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); - defer gpa.free(file_path); if (native_os == .windows) { - try Dir.cwd().access(io, file_path, .{}); + try tmp.dir.access(io, "some_file", .{}); } else { - try Dir.cwd().access(io, file_path, .{ .read = true, .write = true }); + try tmp.dir.access(io, "some_file", .{ .read = true, .write = true }); } } { // Try to access() a non-existent file - should fail with error.FileNotFound - const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" }); - defer gpa.free(file_path); - try expectError(error.FileNotFound, Dir.cwd().access(io, file_path, .{})); + try expectError(error.FileNotFound, tmp.dir.access(io, "some_other_file", .{})); } { // Create some directory - const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); - defer gpa.free(file_path); - try Dir.makeDir(io, file_path, .default_file); + try tmp.dir.makeDir(io, "some_dir", .default_file); } { // Try to access() the directory - const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); - defer gpa.free(file_path); - - try Dir.access(io, file_path, .{}); + try tmp.dir.access(io, "some_dir", .{}); } } @@ -2552,12 +2538,12 @@ test "open smoke test" { try tmp.dir.makeDir(io, "some_dir", .default_dir); { - const dir = try tmp.dir.openDir("some_dir", .{}); + const dir = try tmp.dir.openDir(io, "some_dir", .{}); dir.close(io); } // Try opening as file which should fail. - try expectError(error.IsDir, tmp.dir.openFile("some_dir", .{})); + try expectError(error.IsDir, tmp.dir.openFile(io, "some_dir", .{})); } test "hard link with different directories" { @@ -2575,7 +2561,7 @@ test "hard link with different directories" { try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" }); // Test 1: link from file in subdir back up to target in parent directory - tmp.dir.hardLink(target_name, subdir, link_name, 0) catch |err| switch (err) { + tmp.dir.hardLink(target_name, subdir, link_name, io, .{}) catch |err| switch (err) { error.OperationUnsupported => return error.SkipZigTest, else => |e| return e, }; @@ -2596,7 +2582,7 @@ test "hard link with different directories" { } // Test 2: remove link - try subdir.deleteFile(io, link_name, .{}); + try subdir.deleteFile(io, link_name); const e_stat = try efd.stat(io); try expectEqual(1, e_stat.nlink); } diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index a129eb1e8dc824262195681c0a1c0488d1f91898..0048431beccee490b84916cbc2107fc1eb97f642 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -518,7 +518,7 @@ test "rename smoke test" { // Rename the file const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" }); defer gpa.free(new_file_path); - try Io.Dir.renameAbsolute(io, file_path, new_file_path); + try Io.Dir.renameAbsolute(file_path, new_file_path, io); } { @@ -545,7 +545,7 @@ test "rename smoke test" { // Rename the directory const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" }); defer gpa.free(new_file_path); - try Io.Dir.renameAbsolute(io, file_path, new_file_path); + try Io.Dir.renameAbsolute(file_path, new_file_path, io); } { -- 2.54.0 From 3466056478d2ad988b117a89d7cbe3cc1557425a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 14:37:45 -0800 Subject: [PATCH 076/195] std.Io.Dir.CopyFileOptions: rename override_permissions to more simply `permissions`. In master branch this was `override_mode`. --- lib/std/Io/Dir.zig | 6 +++--- lib/std/fs/test.zig | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index b67c9d048d2999c3985a0788de5290cf45620a57..72b4a00cdd8795e18d4d8d38e9e5ba644aa75d7f 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -564,7 +564,7 @@ pub fn updateFile( defer src_file.close(io); const src_stat = try src_file.stat(io); - const actual_permissions = options.override_permissions orelse src_stat.permissions; + const actual_permissions = options.permissions orelse src_stat.permissions; check_dest_stat: { const dest_stat = blk: { var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) { @@ -1645,7 +1645,7 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin pub const CopyFileOptions = struct { /// When this is `null` the permissions are copied from the source file. - override_permissions: ?File.Permissions = null, + permissions: ?File.Permissions = null, }; pub const CopyFileError = File.OpenError || File.StatError || @@ -1677,7 +1677,7 @@ pub fn copyFile( var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{}); defer file_reader.file.close(io); - const permissions = options.override_permissions orelse blk: { + const permissions = options.permissions orelse blk: { const st = try file_reader.file.stat(io); file_reader.size = st.size; break :blk st.permissions; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index dea562578a23ad627c31b35eab8e8917ac23e14a..4e0fba82edd38093341c49efa2ff1dae7b0d4ccc 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1593,7 +1593,7 @@ test "copyFile" { try ctx.dir.copyFile(src_file, ctx.dir, dest_file, io, .{}); defer ctx.dir.deleteFile(io, dest_file) catch {}; - try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, io, .{ .override_mode = File.default_mode }); + try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, io, .{ .permissions = File.default_mode }); defer ctx.dir.deleteFile(io, dest_file2) catch {}; try expectFileContents(io, ctx.dir, dest_file, data); -- 2.54.0 From 1edae601a1d35caa78eddc4b18f7d613c4af70f9 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 18 Dec 2025 16:25:57 -0800 Subject: [PATCH 077/195] Test Dir.Reader.min_buffer_len via "max file name component lengths" test --- lib/std/fs/test.zig | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 4e0fba82edd38093341c49efa2ff1dae7b0d4ccc..40ba20c9a84a2bb74b1bdd2a7e7b6ee009560f32 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1368,13 +1368,29 @@ test "makepath ignores '.'" { } fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8) !void { - // setup, create a dir and a nested file both with maxed filenames, and walk the dir + // create a file, a dir, and a nested file all with maxed filenames { + try iterable_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" }); + var maxed_dir = try iterable_dir.makeOpenPath(io, maxed_filename, .{}); defer maxed_dir.close(io); try maxed_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" }); + } + // Low level API with minimum buffer length + { + var reader_buf: [Dir.Reader.min_buffer_len]u8 align(@alignOf(usize)) = undefined; + var reader: Dir.Reader = .init(iterable_dir, &reader_buf); + var count: usize = 0; + while (try reader.next(io)) |entry| { + try expectEqualStrings(maxed_filename, entry.name); + count += 1; + } + try expectEqual(@as(usize, 2), count); + } + // High level walk API + { var walker = try iterable_dir.walk(testing.allocator); defer walker.deinit(); @@ -1383,7 +1399,7 @@ fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8) !vo try expectEqualStrings(maxed_filename, entry.basename); count += 1; } - try expectEqual(@as(usize, 2), count); + try expectEqual(@as(usize, 3), count); } // ensure that we can delete the tree -- 2.54.0 From f078c7138f0dfbab95d8963e6c067515c6a54460 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 18 Dec 2025 18:32:25 -0800 Subject: [PATCH 078/195] std: Update/fix some usages/implementations of std.Io APIs --- lib/std/Io/Terminal.zig | 4 +- lib/std/Io/Threaded.zig | 186 +++++++++++++---------------- lib/std/debug/SelfInfo/Windows.zig | 3 +- lib/std/fs/test.zig | 153 +++++++++++++----------- lib/std/os/windows.zig | 6 +- 5 files changed, 172 insertions(+), 180 deletions(-) diff --git a/lib/std/Io/Terminal.zig b/lib/std/Io/Terminal.zig index a39f2175d238f7ad8ee446853ce9a0010227d55a..d1b257b9975dbd9ba49f2cbd2ff7e1bb8f651688 100644 --- a/lib/std/Io/Terminal.zig +++ b/lib/std/Io/Terminal.zig @@ -56,7 +56,7 @@ pub const Mode = union(enum) { error.NotTerminalDevice, error.Unexpected => {}, } - if (is_windows and file.isTty(io)) { + if (is_windows and try file.isTty(io)) { const windows = std.os.windows; var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != 0) { @@ -74,7 +74,7 @@ pub const Mode = union(enum) { pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; -pub fn setColor(t: Terminal, color: Color) Io.Writer.Error!void { +pub fn setColor(t: Terminal, color: Color) SetColorError!void { switch (t.mode) { .no_color => return, .escape_codes => { diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index da15a70903b0250f8f056c7e48b72409f20bdeaa..e0fa45d73c050f8b9eba628dd91b60781d65fa33 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1744,7 +1744,7 @@ fn dirMakeOpenPathWindows( // stat the file and return an error if it's not a directory // this is important because otherwise a dangling symlink // could cause an infinite loop - const fstat = dirStatFileWindows(t, dir, component.path, .{ + const fstat = try dirStatFileWindows(t, dir, component.path, .{ .follow_symlinks = options.follow_symlinks, }); if (fstat.kind != .directory) return error.NotDir; @@ -2146,7 +2146,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { return .{ .inode = info.InternalInformation.IndexNumber, .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)), - .mode = 0, + .permissions = .default_file, .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: { var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined; const tag_rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), .AttributeTag); @@ -2168,6 +2168,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime), .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime), .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime), + .nlink = {}, }; } @@ -2584,26 +2585,33 @@ fn dirCreateFileWindows( .OPEN_IF, }); errdefer w.CloseHandle(handle); + var io_status_block: w.IO_STATUS_BLOCK = undefined; - const range_off: w.LARGE_INTEGER = 0; - const range_len: w.LARGE_INTEGER = 1; const exclusive = switch (flags.lock) { .none => return .{ .handle = handle }, .shared => false, .exclusive => true, }; - try w.LockFile( + const status = w.ntdll.NtLockFile( handle, null, null, null, &io_status_block, - &range_off, - &range_len, + &windows_lock_range_off, + &windows_lock_range_len, null, @intFromBool(flags.lock_nonblocking), @intFromBool(exclusive), ); + switch (status) { + .SUCCESS => {}, + .INSUFFICIENT_RESOURCES => return error.SystemResources, + .LOCK_NOT_GRANTED => return error.WouldBlock, + .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer + else => return windows.unexpectedStatus(status), + } + return .{ .handle = handle }; } @@ -2992,25 +3000,30 @@ pub fn dirOpenFileWtf16( }; errdefer w.CloseHandle(handle); - const range_off: w.LARGE_INTEGER = 0; - const range_len: w.LARGE_INTEGER = 1; const exclusive = switch (flags.lock) { .none => return .{ .handle = handle }, .shared => false, .exclusive => true, }; - try w.LockFile( + const status = w.ntdll.NtLockFile( handle, null, null, null, &io_status_block, - &range_off, - &range_len, + &windows_lock_range_off, + &windows_lock_range_len, null, @intFromBool(flags.lock_nonblocking), @intFromBool(exclusive), ); + switch (status) { + .SUCCESS => {}, + .INSUFFICIENT_RESOURCES => return error.SystemResources, + .LOCK_NOT_GRANTED => return error.WouldBlock, + .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer + else => return windows.unexpectedStatus(status), + } return .{ .handle = handle }; } @@ -3750,7 +3763,7 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D null, &io_status_block, unreserved_buffer.ptr, - unreserved_buffer.len, + std.math.cast(w.ULONG, unreserved_buffer.len) orelse std.math.maxInt(w.ULONG), .BothDirectory, w.FALSE, null, @@ -3849,15 +3862,14 @@ fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out var path_name_w = try w.sliceToPrefixedFileW(dir.handle, sub_path); - const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; - const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE; - const creation = w.FILE_OPEN; const h_file = blk: { const res = w.OpenFile(path_name_w.span(), .{ .dir = dir.handle, - .access_mask = access_mask, - .share_access = share_access, - .creation = creation, + .access_mask = .{ + .GENERIC = .{ .READ = true }, + .STANDARD = .{ .SYNCHRONIZE = true }, + }, + .creation = .OPEN, .filter = .any, }) catch |err| switch (err) { error.WouldBlock => unreachable, @@ -4220,7 +4232,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov try current_thread.checkCancel(); - const sub_path_w = try w.sliceToPrefixedFileW(dir.handle, sub_path); + const sub_path_w_buf = try w.sliceToPrefixedFileW(dir.handle, sub_path); + const sub_path_w = sub_path_w_buf.span(); const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2)); var nt_name: w.UNICODE_STRING = .{ @@ -4239,31 +4252,32 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov return error.FileBusy; } - const create_options_flags: w.ULONG = if (remove_dir) - w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT - else - w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT; - - var attr: w.OBJECT_ATTRIBUTES = .{ - .Length = @sizeOf(w.OBJECT_ATTRIBUTES), - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .Attributes = w.OBJ_CASE_INSENSITIVE, - .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }; var io_status_block: w.IO_STATUS_BLOCK = undefined; var tmp_handle: w.HANDLE = undefined; var rc = w.ntdll.NtCreateFile( &tmp_handle, - w.SYNCHRONIZE | w.DELETE, - &attr, + .{ .STANDARD = .{ + .RIGHTS = .{ .DELETE = true }, + .SYNCHRONIZE = true, + } }, + &.{ + .Length = @sizeOf(w.OBJECT_ATTRIBUTES), + .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .Attributes = .{}, + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }, &io_status_block, null, - 0, - w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE, - w.FILE_OPEN, - create_options_flags, + .{}, + .VALID_FLAGS, + .OPEN, + .{ + .DIRECTORY_FILE = remove_dir, + .NON_DIRECTORY_FILE = !remove_dir, + .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead? + }, null, 0, ); @@ -4463,16 +4477,24 @@ fn dirRenameWindows( const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); - const old_path_w = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path); - const new_path_w = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path); + const old_path_w_buf = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path); + const old_path_w = old_path_w_buf.span(); + const new_path_w_buf = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path); + const new_path_w = new_path_w_buf.span(); const replace_if_exists = true; try current_thread.checkCancel(); const src_fd = w.OpenFile(old_path_w, .{ .dir = old_dir.handle, - .access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | w.DELETE, - .creation = w.FILE_OPEN, + .access_mask = .{ + .GENERIC = .{ .WRITE = true }, + .STANDARD = .{ + .RIGHTS = .{ .DELETE = true }, + .SYNCHRONIZE = true, + }, + }, + .creation = .OPEN, .filter = .any, // This function is supposed to rename both files and directories. .follow_symlinks = false, }) catch |err| switch (err) { @@ -4729,9 +4751,12 @@ fn dirSymLinkWindows( }; const symlink_handle = w.OpenFile(sym_link_path_w.span(), .{ - .access_mask = w.SYNCHRONIZE | w.GENERIC_READ | w.GENERIC_WRITE, + .access_mask = .{ + .GENERIC = .{ .READ = true, .WRITE = true }, + .STANDARD = .{ .SYNCHRONIZE = true }, + }, .dir = dir, - .creation = w.FILE_CREATE, + .creation = .CREATE, .filter = if (flags.is_directory) .dir_only else .non_directory_only, }) catch |err| switch (err) { error.IsDir => return error.PathAlreadyExists, @@ -4913,55 +4938,14 @@ fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buf try current_thread.checkCancel(); - var sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); + var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path); - const result_handle = w.OpenFile(sub_path_w.span(), .{ - .access_mask = w.FILE_READ_ATTRIBUTES | w.SYNCHRONIZE, - .dir = dir, - .creation = w.FILE_OPEN, - .follow_symlinks = false, - .filter = .any, - }) catch |err| switch (err) { - error.IsDir, error.NotDir => return error.Unexpected, // filter = .any - error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN - error.WouldBlock => return error.Unexpected, - error.NoDevice => return error.FileNotFound, - error.PipeBusy => return error.AccessDenied, - else => |e| return e, - }; - defer w.CloseHandle(result_handle); + const result_w = try w.ReadLink(dir.handle, sub_path_w_buf.span(), &sub_path_w_buf.data); - var reparse_buf: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(w.REPARSE_DATA_BUFFER)) = undefined; - _ = w.DeviceIoControl(result_handle, w.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]) catch |err| switch (err) { - error.AccessDenied => return error.Unexpected, - error.UnrecognizedVolume => return error.Unexpected, - else => |e| return e, - }; - - const reparse_struct: *const w.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0])); - const wide_result = switch (reparse_struct.ReparseTag) { - w.IO_REPARSE_TAG_SYMLINK => r: { - const buf: *const w.SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); - const offset = buf.SubstituteNameOffset >> 1; - const len = buf.SubstituteNameLength >> 1; - const path_buf: [*]const u16 = &buf.PathBuffer; - const is_relative = buf.Flags & w.SYMLINK_FLAG_RELATIVE != 0; - break :r try w.parseReadLinkPath(path_buf[offset..][0..len], is_relative, buffer); - }, - w.IO_REPARSE_TAG_MOUNT_POINT => r: { - const buf: *const w.MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); - const offset = buf.SubstituteNameOffset >> 1; - const len = buf.SubstituteNameLength >> 1; - const path_buf: [*]const u16 = &buf.PathBuffer; - break :r try w.parseReadLinkPath(path_buf[offset..][0..len], false, buffer); - }, - else => return error.UnsupportedReparsePointType, - }; - - const len = std.unicode.calcWtf8Len(wide_result); + const len = std.unicode.calcWtf8Len(result_w); if (len > buffer.len) return error.NameTooLong; - return std.unicode.wtf16LeToWtf8(buffer, wide_result); + return std.unicode.wtf16LeToWtf8(buffer, result_w); } fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { @@ -6061,10 +6045,9 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro fn fileUnlock(userdata: ?*anyopaque, file: File) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - const current_thread = Thread.getCurrent(t); + _ = t; if (is_windows) { - try current_thread.checkCancel(); var io_status_block: windows.IO_STATUS_BLOCK = undefined; const status = windows.ntdll.NtUnlockFile( file.handle, @@ -6131,7 +6114,7 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError! &io_status_block, &windows_lock_range_off, &windows_lock_range_len, - null, + 0, ); if (is_debug) switch (status) { .SUCCESS => {}, @@ -6841,17 +6824,16 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex // If ImagePathName is a symlink, then it will contain the path of the // symlink, not the path that the symlink points to. We want the path // that the symlink points to, though, so we need to get the realpath. - var path_name_w = try w.wToPrefixedFileW(null, image_path_name); + var path_name_w_buf = try w.wToPrefixedFileW(null, image_path_name); - const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; - const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE; - const creation = w.FILE_OPEN; const h_file = blk: { - const res = w.OpenFile(path_name_w.span(), .{ + const res = w.OpenFile(path_name_w_buf.span(), .{ .dir = null, - .access_mask = access_mask, - .share_access = share_access, - .creation = creation, + .access_mask = .{ + .GENERIC = .{ .READ = true }, + .STANDARD = .{ .SYNCHRONIZE = true }, + }, + .creation = .OPEN, .filter = .any, }) catch |err| switch (err) { error.WouldBlock => unreachable, @@ -6861,7 +6843,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex }; defer w.CloseHandle(h_file); - const wide_slice = w.GetFinalPathNameByHandle(h_file, .{}, out_buffer); + const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); const len = std.unicode.calcWtf8Len(wide_slice); if (len > out_buffer.len) diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index 9874efd4974c8f127e3ef520fca864a9333faf17..c296b287026758f57f6ee8c53a0e22648e43062f 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -222,7 +222,7 @@ const Module = struct { pdb.file_reader.file.close(io); pdb.deinit(); } - if (di.mapped_file) |*mf| mf.deinit(); + if (di.mapped_file) |*mf| mf.deinit(io); var arena = di.arena.promote(gpa); arena.deinit(); @@ -331,7 +331,6 @@ const Module = struct { error.SystemResources, error.WouldBlock, error.AccessDenied, - error.ProcessNotFound, error.PermissionDenied, error.NoSpaceLeft, error.DeviceBusy, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 40ba20c9a84a2bb74b1bdd2a7e7b6ee009560f32..48eb733455913186c9206a8bef61039001fda9cb 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -27,7 +27,7 @@ const PathType = enum { pub fn isSupported(self: PathType, target_os: std.Target.Os) bool { return switch (self) { .relative => true, - .absolute => std.os.isGetFdPathSupportedOnTarget(target_os), + .absolute => target_os.tag == .windows, // TODO: implement getPathForHandle for other targets .unc => target_os.tag == .windows, }; } @@ -49,7 +49,7 @@ const PathType = enum { // The final path may not actually exist which would cause realpath to fail. // So instead, we get the path of the dir and join it with the relative path. var fd_path_buf: [Dir.max_path_bytes]u8 = undefined; - const dir_path = try std.os.getFdPath(dir.handle, &fd_path_buf); + const dir_path = try getPathForHandle(dir.handle, &fd_path_buf); return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }); } }.transform, @@ -58,7 +58,7 @@ const PathType = enum { // Any drive absolute path (C:\foo) can be converted into a UNC path by // using '127.0.0.1' as the server name and '$' as the share name. var fd_path_buf: [Dir.max_path_bytes]u8 = undefined; - const dir_path = try std.os.getFdPath(dir.handle, &fd_path_buf); + const dir_path = try getPathForHandle(dir.handle, &fd_path_buf); const windows_path_type = windows.getWin32PathType(u8, dir_path); switch (windows_path_type) { .unc_absolute => return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }), @@ -77,6 +77,19 @@ const PathType = enum { } }; +fn getPathForHandle(handle: File.Handle, out_buffer: *[Dir.max_path_bytes]u8) ![]u8 { + switch (native_os) { + .windows => { + var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try windows.GetFinalPathNameByHandle(handle, .{}, wide_buf[0..]); + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; + }, + else => @compileError("TODO or unsupported"), + } +} + const TestContext = struct { io: Io, path_type: PathType, @@ -488,16 +501,16 @@ test "Dir.Iterator" { // Create iterator. var iter = tmp_dir.dir.iterate(); - while (try iter.next()) |entry| { + while (try iter.next(io)) |entry| { // We cannot just store `entry` as on Windows, we're re-using the name buffer // which means we'll actually share the `name` pointer between entries! const name = try allocator.dupe(u8, entry.name); - try entries.append(Dir.Entry{ .name = name, .kind = entry.kind }); + try entries.append(Dir.Entry{ .name = name, .kind = entry.kind, .inode = 0 }); } try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' - try expect(contains(&entries, .{ .name = "some_file", .kind = .file })); - try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); + try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 })); + try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 })); } test "Dir.Iterator many entries" { @@ -523,17 +536,17 @@ test "Dir.Iterator many entries" { // Create iterator. var iter = tmp_dir.dir.iterate(); - while (try iter.next()) |entry| { + while (try iter.next(io)) |entry| { // We cannot just store `entry` as on Windows, we're re-using the name buffer // which means we'll actually share the `name` pointer between entries! const name = try allocator.dupe(u8, entry.name); - try entries.append(.{ .name = name, .kind = entry.kind }); + try entries.append(.{ .name = name, .kind = entry.kind, .inode = 0 }); } i = 0; while (i < num) : (i += 1) { const name = try std.fmt.bufPrint(&buf, "{}", .{i}); - try expect(contains(&entries, .{ .name = name, .kind = .file })); + try expect(contains(&entries, .{ .name = name, .kind = .file, .inode = 0 })); } } @@ -559,16 +572,16 @@ test "Dir.Iterator twice" { // Create iterator. var iter = tmp_dir.dir.iterate(); - while (try iter.next()) |entry| { + while (try iter.next(io)) |entry| { // We cannot just store `entry` as on Windows, we're re-using the name buffer // which means we'll actually share the `name` pointer between entries! const name = try allocator.dupe(u8, entry.name); - try entries.append(Dir.Entry{ .name = name, .kind = entry.kind }); + try entries.append(Dir.Entry{ .name = name, .kind = entry.kind, .inode = 0 }); } try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' - try expect(contains(&entries, .{ .name = "some_file", .kind = .file })); - try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); + try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 })); + try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 })); } } @@ -595,18 +608,18 @@ test "Dir.Iterator reset" { while (i < 2) : (i += 1) { var entries = std.array_list.Managed(Dir.Entry).init(allocator); - while (try iter.next()) |entry| { + while (try iter.next(io)) |entry| { // We cannot just store `entry` as on Windows, we're re-using the name buffer // which means we'll actually share the `name` pointer between entries! const name = try allocator.dupe(u8, entry.name); - try entries.append(.{ .name = name, .kind = entry.kind }); + try entries.append(.{ .name = name, .kind = entry.kind, .inode = 0 }); } try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..' - try expect(contains(&entries, .{ .name = "some_file", .kind = .file })); - try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory })); + try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 })); + try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 })); - iter.reset(); + iter.reader.reset(); } } @@ -617,7 +630,7 @@ test "Dir.Iterator but dir is deleted during iteration" { defer tmp.cleanup(); // Create directory and setup an iterator for it - var subdir = try tmp.dir.makeOpenPath(io, "subdir", .{ .iterate = true }); + var subdir = try tmp.dir.makeOpenPath(io, "subdir", .{ .open_options = .{ .iterate = true } }); defer subdir.close(io); var iterator = subdir.iterate(); @@ -632,8 +645,8 @@ test "Dir.Iterator but dir is deleted during iteration" { tmp.dir.deleteTree(io, "subdir") catch return error.SkipZigTest; // Now, when we try to iterate, the next call should return null immediately. - const entry = try iterator.next(); - try std.expect(entry == null); + const entry = try iterator.next(io); + try testing.expect(entry == null); // On Linux, we can opt-in to receiving a more specific error by calling `nextLinux` if (native_os == .linux) { @@ -652,8 +665,8 @@ fn contains(entries: *const std.array_list.Managed(Dir.Entry), el: Dir.Entry) bo return false; } -test "Dir.realpath smoke test" { - if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest; +test "Dir.realPath smoke test" { + if (native_os == .wasi) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -686,10 +699,10 @@ test "Dir.realpath smoke test" { // First, test non-alloc version { - const file_path = try ctx.dir.realpath(test_file_path, &buf); + const file_path = try ctx.dir.realPath(io, test_file_path, &buf); try expectEqualStrings(expected_file_path, file_path); - const dir_path = try ctx.dir.realpath(test_dir_path, &buf); + const dir_path = try ctx.dir.realPath(io, test_dir_path, &buf); try expectEqualStrings(expected_dir_path, dir_path); } @@ -769,7 +782,7 @@ test "Dir.statFile" { try expectError(error.FileNotFound, ctx.dir.statFile(io, test_dir_name, .{})); - try ctx.dir.makeDir(io, test_dir_name); + try ctx.dir.makeDir(io, test_dir_name, .default_dir); const stat = try ctx.dir.statFile(io, test_dir_name, .{}); try expectEqual(.directory, stat.kind); @@ -1148,7 +1161,7 @@ test "openExecutable" { const io = testing.io; - const self_exe_file = try std.fs.openExecutable(.{}); + const self_exe_file = try std.process.openExecutable(io, .{}); self_exe_file.close(io); } @@ -1157,7 +1170,8 @@ test "executablePath" { const io = testing.io; var buf: [Dir.max_path_bytes]u8 = undefined; - const buf_self_exe_path = try std.process.executablePath(io, &buf); + const len = try std.process.executablePath(io, &buf); + const buf_self_exe_path = buf[0..len]; const alloc_self_exe_path = try std.process.executablePathAlloc(io, testing.allocator); defer testing.allocator.free(alloc_self_exe_path); try expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path); @@ -1246,7 +1260,7 @@ test "makePath, put some files in it, deleteTreeMinStackSize" { .data = "blah", }); - try ctx.dir.deleteTreeMinStackSize(dir_path); + try ctx.dir.deleteTreeMinStackSize(io, dir_path); try expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{})); } }.impl); @@ -1395,7 +1409,7 @@ fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8) !vo defer walker.deinit(); var count: usize = 0; - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { try expectEqualStrings(maxed_filename, entry.basename); count += 1; } @@ -1452,7 +1466,7 @@ test "writev, readv" { try writer.interface.flush(); try expectEqual(@as(u64, line1.len + line2.len), try src_file.length(io)); - var reader = writer.moveToReader(io); + var reader = writer.moveToReader(); try reader.seekTo(0); try reader.interface.readVecAll(&read_vecs); try expectEqualStrings(&buf1, "line2\n"); @@ -1483,7 +1497,7 @@ test "pwritev, preadv" { try writer.interface.flush(); try expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.length(io)); - var reader = writer.moveToReader(io); + var reader = writer.moveToReader(); try reader.seekTo(16); try reader.interface.readVecAll(&read_vecs); try expectEqualStrings(&buf1, "line2\n"); @@ -1549,7 +1563,7 @@ test "sendfile" { try expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10))); try file_writer.interface.writeVecAll(&trailers); try file_writer.interface.flush(); - var fr = file_writer.moveToReader(io); + var fr = file_writer.moveToReader(); try fr.seekTo(0); const amt = try fr.interface.readSliceShort(&written_buf); try expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]); @@ -1586,7 +1600,7 @@ test "sendfile with buffered data" { try expectEqual(4, try file_writer.interface.sendFileAll(&file_reader, .limited(4))); var written_buf: [8]u8 = undefined; - var fr = file_writer.moveToReader(io); + var fr = file_writer.moveToReader(); try fr.seekTo(0); const amt = try fr.interface.readSliceShort(&written_buf); @@ -1609,7 +1623,7 @@ test "copyFile" { try ctx.dir.copyFile(src_file, ctx.dir, dest_file, io, .{}); defer ctx.dir.deleteFile(io, dest_file) catch {}; - try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, io, .{ .permissions = File.default_mode }); + try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, io, .{ .permissions = .default_file }); defer ctx.dir.deleteFile(io, dest_file2) catch {}; try expectFileContents(io, ctx.dir, dest_file, data); @@ -1714,12 +1728,12 @@ test "open file with exclusive lock twice, make sure second lock waits" { errdefer file.close(io); const S = struct { - fn checkFn(dir: *Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { + fn checkFn(inner_ctx: *TestContext, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { started.set(); - const file1 = try dir.createFile(io, path, .{ .lock = .exclusive }); + const file1 = try inner_ctx.dir.createFile(inner_ctx.io, path, .{ .lock = .exclusive }); locked.set(); - file1.close(io); + file1.close(inner_ctx.io); } }; @@ -1727,7 +1741,7 @@ test "open file with exclusive lock twice, make sure second lock waits" { var locked: std.Thread.ResetEvent = .unset; const t = try std.Thread.spawn(.{}, S.checkFn, .{ - &ctx.dir, + ctx, filename, &started, &locked, @@ -1848,7 +1862,7 @@ test "walker" { defer walker.deinit(); var num_walked: usize = 0; - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { expect(expected_basenames.has(entry.basename)) catch |err| { std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); return err; @@ -1910,10 +1924,10 @@ test "selective walker, skip entries that start with ." { defer walker.deinit(); var num_walked: usize = 0; - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { if (entry.basename[0] == '.') continue; if (entry.kind == .directory) { - try walker.enter(entry); + try walker.enter(io, entry); } expect(expected_basenames.has(entry.basename)) catch |err| { @@ -1953,7 +1967,7 @@ test "walker without fully iterating" { try tmp.dir.makePath(io, "b"); var num_walked: usize = 0; - while (try walker.next()) |_| { + while (try walker.next(io)) |_| { num_walked += 1; break; } @@ -2093,48 +2107,42 @@ test "invalid UTF-8/WTF-8 paths" { // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte const invalid_path = try ctx.transformPath("\xFF"); - try expectError(expected_err, ctx.dir.openFile(invalid_path, .{})); + try expectError(expected_err, ctx.dir.openFile(io, invalid_path, .{})); - try expectError(expected_err, ctx.dir.createFile(invalid_path, .{})); + try expectError(expected_err, ctx.dir.createFile(io, invalid_path, .{})); - try expectError(expected_err, ctx.dir.makeDir(invalid_path, .default_dir)); + try expectError(expected_err, ctx.dir.makeDir(io, invalid_path, .default_dir)); - try expectError(expected_err, ctx.dir.makePath(invalid_path)); - try expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{})); + try expectError(expected_err, ctx.dir.makePath(io, invalid_path)); + try expectError(expected_err, ctx.dir.makeOpenPath(io, invalid_path, .{})); - try expectError(expected_err, ctx.dir.openDir(invalid_path, .{})); + try expectError(expected_err, ctx.dir.openDir(io, invalid_path, .{})); - try expectError(expected_err, ctx.dir.deleteFile(invalid_path)); + try expectError(expected_err, ctx.dir.deleteFile(io, invalid_path)); try expectError(expected_err, ctx.dir.deleteDir(io, invalid_path)); try expectError(expected_err, ctx.dir.rename(invalid_path, ctx.dir, invalid_path, io)); try expectError(expected_err, ctx.dir.symLink(io, invalid_path, invalid_path, .{})); - if (native_os == .wasi) { - try expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{})); - } try expectError(expected_err, ctx.dir.readLink(io, invalid_path, &[_]u8{})); - if (native_os == .wasi) { - try expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{})); - } - try expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{})); - try expectError(expected_err, ctx.dir.readFileAlloc(invalid_path, testing.allocator, .limited(0))); + try expectError(expected_err, ctx.dir.readFile(io, invalid_path, &[_]u8{})); + try expectError(expected_err, ctx.dir.readFileAlloc(io, invalid_path, testing.allocator, .limited(0))); try expectError(expected_err, ctx.dir.deleteTree(io, invalid_path)); - try expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path)); + try expectError(expected_err, ctx.dir.deleteTreeMinStackSize(io, invalid_path)); try expectError(expected_err, ctx.dir.writeFile(io, .{ .sub_path = invalid_path, .data = "" })); - try expectError(expected_err, ctx.dir.access(invalid_path, .{})); + try expectError(expected_err, ctx.dir.access(io, invalid_path, .{})); var dir = ctx.dir; try expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{})); try expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, io, .{})); - try expectError(expected_err, ctx.dir.statFile(invalid_path)); + try expectError(expected_err, ctx.dir.statFile(io, invalid_path, .{})); if (native_os != .wasi) { try expectError(expected_err, ctx.dir.realPath(io, invalid_path, &[_]u8{})); @@ -2146,17 +2154,17 @@ test "invalid UTF-8/WTF-8 paths" { if (native_os != .wasi and ctx.path_type != .relative) { try expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, io, .{})); try expectError(expected_err, Dir.makeDirAbsolute(io, invalid_path, .default_dir)); - try expectError(expected_err, Dir.deleteDirAbsolute(invalid_path)); + try expectError(expected_err, Dir.deleteDirAbsolute(io, invalid_path)); try expectError(expected_err, Dir.renameAbsolute(invalid_path, invalid_path, io)); try expectError(expected_err, Dir.openDirAbsolute(io, invalid_path, .{})); try expectError(expected_err, Dir.openFileAbsolute(io, invalid_path, .{})); - try expectError(expected_err, Dir.accessAbsolute(invalid_path, .{})); - try expectError(expected_err, Dir.createFileAbsolute(invalid_path, .{})); - try expectError(expected_err, Dir.deleteFileAbsolute(invalid_path)); + try expectError(expected_err, Dir.accessAbsolute(io, invalid_path, .{})); + try expectError(expected_err, Dir.createFileAbsolute(io, invalid_path, .{})); + try expectError(expected_err, Dir.deleteFileAbsolute(io, invalid_path)); var readlink_buf: [Dir.max_path_bytes]u8 = undefined; - try expectError(expected_err, Dir.readLinkAbsolute(invalid_path, &readlink_buf)); - try expectError(expected_err, Dir.symLinkAbsolute(invalid_path, invalid_path, .{})); - try expectError(expected_err, Dir.realPathAlloc(io, invalid_path, testing.allocator)); + try expectError(expected_err, Dir.readLinkAbsolute(io, invalid_path, &readlink_buf)); + try expectError(expected_err, Dir.symLinkAbsolute(io, invalid_path, invalid_path, .{})); + try expectError(expected_err, Dir.realPathAbsoluteAlloc(io, invalid_path, testing.allocator)); } } }.impl); @@ -2327,7 +2335,8 @@ test "readlink on Windows" { fn testReadLinkWindows(io: Io, target_path: []const u8, symlink_path: []const u8) !void { var buffer: [Dir.max_path_bytes]u8 = undefined; - const given = try Dir.readLinkAbsolute(io, symlink_path, &buffer); + const len = try Dir.readLinkAbsolute(io, symlink_path, &buffer); + const given = buffer[0..len]; try expect(mem.eql(u8, target_path, given)); } @@ -2495,7 +2504,7 @@ test "access smoke test" { { // Create some directory - try tmp.dir.makeDir(io, "some_dir", .default_file); + try tmp.dir.makeDir(io, "some_dir", .default_dir); } { @@ -2563,6 +2572,8 @@ test "open smoke test" { } test "hard link with different directories" { + if (native_os == .wasi or native_os == .windows) return error.SkipZigTest; + const io = testing.io; var tmp = tmpDir(.{}); diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 7a2b2aaf50a9ac10740a9e86523322f399f0ccb5..02357d6d2af24845c1ece2394d97822ad15a5b91 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -3582,7 +3582,7 @@ test QueryObjectName { //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const handle = tmp.dir.fd; + const handle = tmp.dir.handle; var out_buffer: [PATH_MAX_WIDE]u16 = undefined; const result_path = try QueryObjectName(handle, &out_buffer); @@ -3845,7 +3845,7 @@ test GetFinalPathNameByHandle { //any file will do var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const handle = tmp.dir.fd; + const handle = tmp.dir.handle; var buffer: [PATH_MAX_WIDE]u16 = undefined; //check with sufficient size @@ -4638,7 +4638,7 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE } // We can also skip GetFinalPathNameByHandle if the handle matches // the handle returned by Io.Dir.cwd() - if (dir.? == Io.Dir.cwd().fd) { + if (dir.? == Io.Dir.cwd().handle) { break :path_to_get path; } // At this point, we know we have a relative path that had too many -- 2.54.0 From 9bbb8e0d8e8ce413ad2a9125b1eb9ddbf32b6c87 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 19:57:01 -0800 Subject: [PATCH 079/195] std.posix: make nlink_t on unsupported systems u0 instead of void --- lib/std/Io/Threaded.zig | 4 ++-- lib/std/Io/Writer.zig | 6 +++--- lib/std/fs/test.zig | 17 ++++++----------- lib/std/posix.zig | 1 + 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e0fa45d73c050f8b9eba628dd91b60781d65fa33..a6a3c01a738449539c1582951fe9ffdab5fde022 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2168,7 +2168,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime), .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime), .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime), - .nlink = {}, + .nlink = 0, }; } @@ -3763,7 +3763,7 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D null, &io_status_block, unreserved_buffer.ptr, - std.math.cast(w.ULONG, unreserved_buffer.len) orelse std.math.maxInt(w.ULONG), + std.math.lossyCast(w.ULONG, unreserved_buffer.len), .BothDirectory, w.FALSE, null, diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 5349eef104415b0c0d7e278d2084c3f53f9a51bf..fab40001a5c9c4d72081312fe0acd6c41a15ed89 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -2842,7 +2842,7 @@ test "discarding sendFile" { try file_writer.interface.writeByte('h'); try file_writer.interface.flush(); - var file_reader = file_writer.moveToReader(io); + var file_reader = file_writer.moveToReader(); try file_reader.seekTo(0); var w_buffer: [256]u8 = undefined; @@ -2864,7 +2864,7 @@ test "allocating sendFile" { try file_writer.interface.writeAll("abcd"); try file_writer.interface.flush(); - var file_reader = file_writer.moveToReader(io); + var file_reader = file_writer.moveToReader(); try file_reader.seekTo(0); try file_reader.interface.fill(2); @@ -2888,7 +2888,7 @@ test sendFileReading { try file_writer.interface.writeAll("abcd"); try file_writer.interface.flush(); - var file_reader = file_writer.moveToReader(io); + var file_reader = file_writer.moveToReader(); try file_reader.seekTo(0); try file_reader.interface.fill(2); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 48eb733455913186c9206a8bef61039001fda9cb..6f3b48b408e54043ba06ee4e24790c61869c24c4 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -24,7 +24,7 @@ const PathType = enum { absolute, unc, - pub fn isSupported(self: PathType, target_os: std.Target.Os) bool { + fn isSupported(self: PathType, target_os: std.Target.Os) bool { return switch (self) { .relative => true, .absolute => target_os.tag == .windows, // TODO: implement getPathForHandle for other targets @@ -32,10 +32,10 @@ const PathType = enum { }; } - pub const TransformError = Dir.RealPathError || error{OutOfMemory}; - pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8; + const TransformError = Dir.RealPathError || error{OutOfMemory}; + const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8; - pub fn getTransformFn(comptime path_type: PathType) TransformFn { + fn getTransformFn(comptime path_type: PathType) TransformFn { switch (path_type) { .relative => return struct { fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { @@ -1623,7 +1623,7 @@ test "copyFile" { try ctx.dir.copyFile(src_file, ctx.dir, dest_file, io, .{}); defer ctx.dir.deleteFile(io, dest_file) catch {}; - try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, io, .{ .permissions = .default_file }); + try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, io, .{}); defer ctx.dir.deleteFile(io, dest_file2) catch {}; try expectFileContents(io, ctx.dir, dest_file, data); @@ -1740,12 +1740,7 @@ test "open file with exclusive lock twice, make sure second lock waits" { var started: std.Thread.ResetEvent = .unset; var locked: std.Thread.ResetEvent = .unset; - const t = try std.Thread.spawn(.{}, S.checkFn, .{ - ctx, - filename, - &started, - &locked, - }); + const t = try std.Thread.spawn(.{}, S.checkFn, .{ ctx, filename, &started, &locked }); defer t.join(); // Wait for the spawned thread to start trying to acquire the exclusive file lock. diff --git a/lib/std/posix.zig b/lib/std/posix.zig index dff9639578b024d6147e377d6c52070f159b913e..370e6bde33df10f20b63c7c291bb41a4252cb179 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -54,6 +54,7 @@ else switch (native_os) { pub const uid_t = void; pub const gid_t = void; pub const mode_t = u0; + pub const nlink_t = u0; pub const ino_t = void; pub const IFNAMESIZE = {}; pub const SIG = void; -- 2.54.0 From 8632a28ca95a5a491bde57fdc45d9a0ce3cf597e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 20:55:47 -0800 Subject: [PATCH 080/195] std: add support for realpath on file handle and rename OperationNotSupported to OperationUnsupported --- lib/std/Io.zig | 4 +- lib/std/Io/Dir.zig | 115 +++++++++++++++++----------------------- lib/std/Io/File.zig | 49 +++++++++++++++++ lib/std/Io/Threaded.zig | 66 +++++++++++++++++------ lib/std/fs/test.zig | 110 ++++++++++++++++++-------------------- lib/std/os/windows.zig | 2 + lib/std/posix.zig | 24 ++++----- lib/std/posix/test.zig | 2 +- 8 files changed, 220 insertions(+), 152 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 78049633a00703971ab55f7f756a86c48862d2b7..0959ce06e7df7b6b28f0949b41eea0e27fb99c67 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -670,7 +670,8 @@ pub const VTable = struct { dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File, dirClose: *const fn (?*anyopaque, []const Dir) void, dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize, - dirRealPath: *const fn (?*anyopaque, Dir, path_name: []const u8, out_buffer: []u8) Dir.RealPathError!usize, + dirRealPath: *const fn (?*anyopaque, Dir, out_buffer: []u8) Dir.RealPathError!usize, + dirRealPathFile: *const fn (?*anyopaque, Dir, path_name: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize, dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void, dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void, dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void, @@ -709,6 +710,7 @@ pub const VTable = struct { fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool, fileUnlock: *const fn (?*anyopaque, File) void, fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void, + fileRealPath: *const fn (?*anyopaque, File, out_buffer: []u8) File.RealPathError!usize, processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File, processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 72b4a00cdd8795e18d4d8d38e9e5ba644aa75d7f..ae817676a71f5ab3962bc65fb736848b1584f35e 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -766,95 +766,80 @@ pub fn statFile(dir: Dir, io: Io, sub_path: []const u8, options: StatFileOptions return io.vtable.dirStatFile(io.userdata, dir, sub_path, options); } -pub const RealPathError = error{ - FileNotFound, - AccessDenied, - PermissionDenied, - NotSupported, - NotDir, - SymLinkLoop, - InputOutput, - FileTooBig, - IsDir, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - NoDevice, - SystemResources, - NoSpaceLeft, - FileSystem, - DeviceBusy, - SharingViolation, - PipeBusy, - /// On Windows, `\\server` or `\\server\share` was not found. - NetworkNotFound, - PathAlreadyExists, - /// On Windows, antivirus software is enabled by default. It can be - /// disabled, but Windows Update sometimes ignores the user's preference - /// and re-enables it. When enabled, antivirus software on Windows - /// intercepts file system operations and makes them significantly slower - /// in addition to possibly failing with this error code. - AntivirusInterference, - /// On Windows, the volume does not contain a recognized file system. File - /// system drivers might not be loaded, or the volume may be corrupt. - UnrecognizedVolume, -} || PathNameError || Io.Cancelable || Io.UnexpectedError; +pub const RealPathError = File.RealPathError; -/// This function returns the canonicalized absolute path name of `sub_path` -/// relative to this `Dir`. If `sub_path` is absolute, ignores this `Dir` -/// handle and returns the canonicalized absolute pathname of `sub_path` -/// argument. +/// Obtains the canonicalized absolute path name of `sub_path` relative to this +/// `Dir`. If `sub_path` is absolute, ignores this `Dir` handle and obtains the +/// canonicalized absolute pathname of `sub_path` argument. /// -/// This function is not universally supported by all platforms, and using this -/// function can lead to unnecessary failures and race conditions. +/// This function has limited platform support, and using it can lead to +/// unnecessary failures and race conditions. It is generally advisable to +/// avoid this function entirely. +pub fn realPath(dir: Dir, io: Io, out_buffer: []u8) RealPathError!usize { + return io.vtable.dirRealPath(io.userdata, dir, out_buffer); +} + +pub const RealPathFileError = RealPathError || PathNameError; + +/// Obtains the canonicalized absolute path name of `sub_path` relative to this +/// `Dir`. If `sub_path` is absolute, ignores this `Dir` handle and obtains the +/// canonicalized absolute pathname of `sub_path` argument. +/// +/// This function has limited platform support, and using it can lead to +/// unnecessary failures and race conditions. It is generally advisable to +/// avoid this function entirely. /// /// See also: -/// * `realPathAlloc`. -/// * `realPathAbsolute`. -pub fn realPath(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathError!usize { - return io.vtable.dirRealPath(io.userdata, dir, sub_path, out_buffer); +/// * `realPathFileAlloc`. +/// * `realPathFileAbsolute`. +pub fn realPathFile(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathFileError!usize { + return io.vtable.dirRealPathFile(io.userdata, dir, sub_path, out_buffer); } -pub const RealPathAllocError = RealPathError || Allocator.Error; +pub const RealPathFileAllocError = RealPathFileError || Allocator.Error; -/// Same as `realPath` except allocates result. +/// Same as `realPathFile` except allocates result. /// -/// This function is not universally supported by all platforms, and using this -/// function can lead to unnecessary failures and race conditions. +/// This function has limited platform support, and using it can lead to +/// unnecessary failures and race conditions. It is generally advisable to +/// avoid this function entirely. /// /// See also: -/// * `realPath`. -/// * `realPathAbsolute`. -pub fn realPathAlloc(dir: Dir, io: Io, sub_path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 { +/// * `realPathFile`. +/// * `realPathFileAbsolute`. +pub fn realPathFileAlloc(dir: Dir, io: Io, sub_path: []const u8, allocator: Allocator) RealPathFileAllocError![:0]u8 { var buffer: [max_path_bytes]u8 = undefined; - const n = try realPath(dir, io, sub_path, &buffer); + const n = try realPathFile(dir, io, sub_path, &buffer); return allocator.dupeZ(u8, buffer[0..n]); } -/// Same as `realPath` except `absolute_path` is asserted to be an absolute +/// Same as `realPathFile` except `absolute_path` is asserted to be an absolute /// path. /// -/// This function is not universally supported by all platforms, and using this -/// function can lead to unnecessary failures and race conditions. +/// This function has limited platform support, and using it can lead to +/// unnecessary failures and race conditions. It is generally advisable to +/// avoid this function entirely. /// /// See also: -/// * `realPath`. -/// * `realPathAlloc`. -pub fn realPathAbsolute(io: Io, absolute_path: []const u8, out_buffer: []u8) RealPathError!usize { +/// * `realPathFile`. +/// * `realPathFileAlloc`. +pub fn realPathFileAbsolute(io: Io, absolute_path: []const u8, out_buffer: []u8) RealPathFileError!usize { assert(path.isAbsolute(absolute_path)); - return io.vtable.dirRealPath(io.userdata, .cwd(), absolute_path, out_buffer); + return io.vtable.dirRealPathFile(io.userdata, .cwd(), absolute_path, out_buffer); } -/// Same as `realPathAbsolute` except allocates result. +/// Same as `realPathFileAbsolute` except allocates result. /// -/// This function is not universally supported by all platforms, and using this -/// function can lead to unnecessary failures and race conditions. +/// This function has limited platform support, and using it can lead to +/// unnecessary failures and race conditions. It is generally advisable to +/// avoid this function entirely. /// /// See also: -/// * `realPathAbsolute`. -/// * `realPath`. -pub fn realPathAbsoluteAlloc(io: Io, absolute_path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 { +/// * `realPathFileAbsolute`. +/// * `realPathFile`. +pub fn realPathFileAbsoluteAlloc(io: Io, absolute_path: []const u8, allocator: Allocator) RealPathFileAllocError![:0]u8 { var buffer: [max_path_bytes]u8 = undefined; - const n = try realPathAbsolute(io, absolute_path, &buffer); + const n = try realPathFileAbsolute(io, absolute_path, &buffer); return allocator.dupeZ(u8, buffer[0..n]); } @@ -1765,7 +1750,7 @@ pub const SetFilePermissionsError = PathNameError || SetPermissionsError || erro SystemFdQuotaExceeded, /// `SetFilePermissionsOptions.follow_symlinks` was set to false, which is /// not allowed by the file system or operating system. - OperationNotSupported, + OperationUnsupported, }; pub const SetFilePermissionsOptions = struct { diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index e5bf5849e9ea64fa0a990645a7b6a7b3164b80d9..e06f2ba40ded4cdcce1aaafba0c5395c981d6847 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -627,6 +627,55 @@ pub fn downgradeLock(file: File, io: Io) LockError!void { return io.vtable.fileDowngradeLock(io.userdata, file); } +pub const RealPathError = error{ + /// This operating system, file system, or `Io` implementation does not + /// support realpath operations. + OperationUnsupported, + /// The full file system path could not fit into the provided buffer, or + /// due to its length could not be obtained via realpath functions no + /// matter the buffer size provided. + NameTooLong, + FileNotFound, + AccessDenied, + PermissionDenied, + NotDir, + SymLinkLoop, + InputOutput, + FileTooBig, + IsDir, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + NoSpaceLeft, + FileSystem, + DeviceBusy, + SharingViolation, + PipeBusy, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, + PathAlreadyExists, + /// On Windows, antivirus software is enabled by default. It can be + /// disabled, but Windows Update sometimes ignores the user's preference + /// and re-enables it. When enabled, antivirus software on Windows + /// intercepts file system operations and makes them significantly slower + /// in addition to possibly failing with this error code. + AntivirusInterference, + /// On Windows, the volume does not contain a recognized file system. File + /// system drivers might not be loaded, or the volume may be corrupt. + UnrecognizedVolume, +} || Io.Cancelable || Io.UnexpectedError; + +/// Obtains the canonicalized absolute path name corresponding to an open file +/// handle. +/// +/// This function has limited platform support, and using it can lead to +/// unnecessary failures and race conditions. It is generally advisable to +/// avoid this function entirely. +pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize { + return io.vtable.fileRealPath(io.userdata, file, out_buffer); +} + test { _ = Reader; _ = Writer; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index a6a3c01a738449539c1582951fe9ffdab5fde022..a018795c21386ae6832098ff55f7c2298670c23a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -716,6 +716,7 @@ pub fn io(t: *Threaded) Io { .dirClose = dirClose, .dirRead = dirRead, .dirRealPath = dirRealPath, + .dirRealPathFile = dirRealPathFile, .dirDeleteFile = dirDeleteFile, .dirDeleteDir = dirDeleteDir, .dirRename = dirRename, @@ -752,6 +753,7 @@ pub fn io(t: *Threaded) Io { .fileTryLock = fileTryLock, .fileUnlock = fileUnlock, .fileDowngradeLock = fileDowngradeLock, + .fileRealPath = fileRealPath, .processExecutableOpen = processExecutableOpen, .processExecutablePath = processExecutablePath, @@ -848,6 +850,7 @@ pub fn ioBasic(t: *Threaded) Io { .dirClose = dirClose, .dirRead = dirRead, .dirRealPath = dirRealPath, + .dirRealPathFile = dirRealPathFile, .dirDeleteFile = dirDeleteFile, .dirDeleteDir = dirDeleteDir, .dirRename = dirRename, @@ -884,6 +887,7 @@ pub fn ioBasic(t: *Threaded) Io { .fileTryLock = fileTryLock, .fileUnlock = fileUnlock, .fileDowngradeLock = fileDowngradeLock, + .fileRealPath = fileRealPath, .processExecutableOpen = processExecutableOpen, .processExecutablePath = processExecutablePath, @@ -3848,22 +3852,21 @@ fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: return error.Unimplemented; } -const dirRealPath = switch (native_os) { - .windows => dirRealPathWindows, - else => dirRealPathPosix, +const dirRealPathFile = switch (native_os) { + .windows => dirRealPathFileWindows, + else => dirRealPathFilePosix, }; -fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathError!usize { +fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); - const w = windows; const current_thread = Thread.getCurrent(t); try current_thread.checkCancel(); - var path_name_w = try w.sliceToPrefixedFileW(dir.handle, sub_path); + var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); const h_file = blk: { - const res = w.OpenFile(path_name_w.span(), .{ + const res = windows.OpenFile(path_name_w.span(), .{ .dir = dir.handle, .access_mask = .{ .GENERIC = .{ .READ = true }, @@ -3877,9 +3880,13 @@ fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out }; break :blk res; }; - defer w.CloseHandle(h_file); + defer windows.CloseHandle(h_file); + return realPathWindows(current_thread, h_file, out_buffer); +} - const wide_slice = w.GetFinalPathNameByHandle(h_file, .{}, out_buffer); +fn realPathWindows(current_thread: *Thread, h_file: windows.HANDLE, out_buffer: []u8) Dir.RealPathFileError { + _ = current_thread; // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks + const wide_slice = windows.GetFinalPathNameByHandle(h_file, .{}, out_buffer); const len = std.unicode.calcWtf8Len(wide_slice); if (len > out_buffer.len) @@ -3888,9 +3895,8 @@ fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); } -fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathError!usize { - if (native_os == .wasi) @compileError("unsupported operating system"); - const max_path_bytes = std.fs.max_path_bytes; +fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize { + if (native_os == .wasi) return error.OperationUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -3949,7 +3955,35 @@ fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_b } }; errdefer posix.close(fd); + return realPathPosix(current_thread, fd, out_buffer); +} +const dirRealPath = switch (native_os) { + .windows => dirRealPathWindows, + else => dirRealPathPosix, +}; + +fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize { + if (native_os == .wasi) return error.OperationUnsupported; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + return realPathPosix(current_thread, dir.handle, out_buffer); +} + +fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + return realPathWindows(current_thread, dir.handle, out_buffer); +} + +fn fileRealPath(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize { + if (native_os == .wasi) return error.OperationUnsupported; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + return realPathPosix(current_thread, file.handle, out_buffer); +} + +fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize { switch (native_os) { .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { // On macOS, we can use F.GETPATH fcntl command to query the OS for @@ -4049,7 +4083,7 @@ fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_b return len; }, .netbsd, .dragonfly => { - @memset(out_buffer[0..max_path_bytes], 0); + @memset(out_buffer[0..Dir.max_path_bytes], 0); try current_thread.beginSyscall(); while (true) { switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) { @@ -4077,7 +4111,7 @@ fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_b } return std.mem.indexOfScalar(u8, &out_buffer, 0) orelse out_buffer.len; }, - else => @compileError("unsupported OS"), + else => return error.OperationUnsupported, } comptime unreachable; } @@ -5108,7 +5142,7 @@ fn posixFchmodat( .NOENT => return error.FileNotFound, .NOTDIR => return error.FileNotFound, .NOMEM => return error.SystemResources, - .OPNOTSUPP => return error.OperationNotSupported, + .OPNOTSUPP => return error.OperationUnsupported, .PERM => return error.PermissionDenied, .ROFS => return error.ReadOnlyFileSystem, else => |err| return posix.unexpectedErrno(err), @@ -5144,7 +5178,7 @@ fn posixFchmodat( .NOENT => return error.FileNotFound, .NOMEM => return error.SystemResources, .NOTDIR => return error.FileNotFound, - .OPNOTSUPP => return error.OperationNotSupported, + .OPNOTSUPP => return error.OperationUnsupported, .PERM => return error.PermissionDenied, .ROFS => return error.ReadOnlyFileSystem, .NOSYS => { diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 6f3b48b408e54043ba06ee4e24790c61869c24c4..92d3630ac7dcffb31fb90599a6db9cb16dada9e3 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -4,6 +4,7 @@ const native_os = builtin.os.tag; const std = @import("../std.zig"); const Io = std.Io; const mem = std.mem; +const Allocator = std.mem.Allocator; const wasi = std.os.wasi; const windows = std.os.windows; const ArenaAllocator = std.heap.ArenaAllocator; @@ -27,38 +28,57 @@ const PathType = enum { fn isSupported(self: PathType, target_os: std.Target.Os) bool { return switch (self) { .relative => true, - .absolute => target_os.tag == .windows, // TODO: implement getPathForHandle for other targets + .absolute => switch (target_os.tag) { + .windows, + .driverkit, + .ios, + .maccatalyst, + .macos, + .tvos, + .visionos, + .watchos, + .linux, + .illumos, + .freebsd, + .serenity, + => true, + + .dragonfly => target_os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt, + .netbsd => target_os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt, + else => false, + }, .unc => target_os.tag == .windows, }; } const TransformError = Dir.RealPathError || error{OutOfMemory}; - const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8; + const TransformFn = fn (Allocator, Io, Dir, relative_path: [:0]const u8) TransformError![:0]const u8; fn getTransformFn(comptime path_type: PathType) TransformFn { switch (path_type) { .relative => return struct { - fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { + fn transform(allocator: Allocator, io: Io, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { _ = allocator; + _ = io; _ = dir; return relative_path; } }.transform, .absolute => return struct { - fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { + fn transform(allocator: Allocator, io: Io, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { // The final path may not actually exist which would cause realpath to fail. // So instead, we get the path of the dir and join it with the relative path. var fd_path_buf: [Dir.max_path_bytes]u8 = undefined; - const dir_path = try getPathForHandle(dir.handle, &fd_path_buf); + const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)]; return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }); } }.transform, .unc => return struct { - fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { + fn transform(allocator: Allocator, io: Io, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { // Any drive absolute path (C:\foo) can be converted into a UNC path by // using '127.0.0.1' as the server name and '$' as the share name. var fd_path_buf: [Dir.max_path_bytes]u8 = undefined; - const dir_path = try getPathForHandle(dir.handle, &fd_path_buf); + const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)]; const windows_path_type = windows.getWin32PathType(u8, dir_path); switch (windows_path_type) { .unc_absolute => return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }), @@ -77,19 +97,6 @@ const PathType = enum { } }; -fn getPathForHandle(handle: File.Handle, out_buffer: *[Dir.max_path_bytes]u8) ![]u8 { - switch (native_os) { - .windows => { - var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try windows.GetFinalPathNameByHandle(handle, .{}, wide_buf[0..]); - - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); - return out_buffer[0..end_index]; - }, - else => @compileError("TODO or unsupported"), - } -} - const TestContext = struct { io: Io, path_type: PathType, @@ -99,7 +106,7 @@ const TestContext = struct { dir: Dir, transform_fn: *const PathType.TransformFn, - pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext { + pub fn init(path_type: PathType, path_sep: u8, allocator: Allocator, transform_fn: *const PathType.TransformFn) TestContext { const tmp = tmpDir(.{ .iterate = true }); return .{ .io = testing.io, @@ -123,7 +130,7 @@ const TestContext = struct { /// `TestContext.deinit`. pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 { const allocator = self.arena.allocator(); - const transformed_path = try self.transform_fn(allocator, self.dir, relative_path); + const transformed_path = try self.transform_fn(allocator, self.io, self.dir, relative_path); if (native_os == .windows) { const transformed_sep_path = try allocator.dupeZ(u8, transformed_path); std.mem.replaceScalar(u8, transformed_sep_path, switch (self.path_sep) { @@ -268,7 +275,7 @@ fn testReadLink(io: Io, dir: Dir, target_path: []const u8, symlink_path: []const try expectEqualStrings(target_path, actual); } -fn testReadLinkW(allocator: mem.Allocator, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { +fn testReadLinkW(allocator: Allocator, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { const target_path_w = try std.unicode.wtf8ToWtf16LeAlloc(allocator, target_path); defer allocator.free(target_path_w); // Calling the W functions directly requires the path to be NT-prefixed @@ -281,7 +288,7 @@ fn testReadLinkW(allocator: mem.Allocator, dir: Dir, target_path: []const u8, sy fn testReadLinkAbsolute(io: Io, target_path: []const u8, symlink_path: []const u8) !void { var buffer: [Dir.max_path_bytes]u8 = undefined; - const given = try Dir.readLinkAbsolute(io, symlink_path, buffer[0..]); + const given = buffer[0..try Dir.readLinkAbsolute(io, symlink_path, &buffer)]; try expectEqualStrings(target_path, given); } @@ -339,7 +346,7 @@ test "accessAbsolute" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - const base_path = try tmp.dir.realPathAlloc(io, ".", gpa); + const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa); defer gpa.free(base_path); try Dir.accessAbsolute(io, base_path, .{}); @@ -358,7 +365,7 @@ test "openDirAbsolute" { const tmp_ino = (try tmp.dir.stat(io)).inode; try tmp.dir.makeDir(io, "subdir", .default_dir); - const sub_path = try tmp.dir.realPathAlloc(io, "subdir", gpa); + const sub_path = try tmp.dir.realPathFileAlloc(io, "subdir", gpa); defer gpa.free(sub_path); // Can open sub_path @@ -434,10 +441,10 @@ test "openDir non-cwd parent '..'" { var dir = try subdir.openDir(io, "..", .{}); defer dir.close(io); - const expected_path = try tmp.dir.realPathAlloc(io, ".", gpa); + const expected_path = try tmp.dir.realPathFileAlloc(io, ".", gpa); defer gpa.free(expected_path); - const actual_path = try dir.realPathAlloc(io, ".", gpa); + const actual_path = try dir.realPathFileAlloc(io, ".", gpa); defer gpa.free(actual_path); try expectEqualStrings(expected_path, actual_path); @@ -461,7 +468,7 @@ test "readLinkAbsolute" { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const base_path = try tmp.dir.realPathAlloc(io, ".", arena); + const base_path = try tmp.dir.realPathFileAlloc(io, ".", arena); { const target_path = try Dir.path.join(arena, &.{ base_path, "file.txt" }); @@ -647,11 +654,6 @@ test "Dir.Iterator but dir is deleted during iteration" { // Now, when we try to iterate, the next call should return null immediately. const entry = try iterator.next(io); try testing.expect(entry == null); - - // On Linux, we can opt-in to receiving a more specific error by calling `nextLinux` - if (native_os == .linux) { - try expectError(error.DirNotFound, iterator.nextLinux()); - } } fn entryEql(lhs: Dir.Entry, rhs: Dir.Entry) bool { @@ -671,47 +673,41 @@ test "Dir.realPath smoke test" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { const io = ctx.io; - const allocator = ctx.arena.allocator(); + const arena = ctx.arena.allocator(); const test_file_path = try ctx.transformPath("test_file"); const test_dir_path = try ctx.transformPath("test_dir"); var buf: [Dir.max_path_bytes]u8 = undefined; // FileNotFound if the path doesn't exist - try expectError(error.FileNotFound, ctx.dir.realPathAlloc(io, test_file_path, allocator)); - try expectError(error.FileNotFound, ctx.dir.realPath(io, test_file_path, &buf)); - try expectError(error.FileNotFound, ctx.dir.realPathAlloc(io, test_dir_path, allocator)); - try expectError(error.FileNotFound, ctx.dir.realPath(io, test_dir_path, &buf)); + try expectError(error.FileNotFound, ctx.dir.realPathFileAlloc(io, test_file_path, arena)); + try expectError(error.FileNotFound, ctx.dir.realPathFile(io, test_file_path, &buf)); + try expectError(error.FileNotFound, ctx.dir.realPathFileAlloc(io, test_dir_path, arena)); + try expectError(error.FileNotFound, ctx.dir.realPathFile(io, test_dir_path, &buf)); // Now create the file and dir try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); try ctx.dir.makeDir(io, test_dir_path, .default_dir); const base_path = try ctx.transformPath("."); - const base_realpath = try ctx.dir.realPathAlloc(io, base_path, allocator); - const expected_file_path = try Dir.path.join( - allocator, - &.{ base_realpath, "test_file" }, - ); - const expected_dir_path = try Dir.path.join( - allocator, - &.{ base_realpath, "test_dir" }, - ); + const base_realpath = try ctx.dir.realPathFileAlloc(io, base_path, arena); + const expected_file_path = try Dir.path.join(arena, &.{ base_realpath, "test_file" }); + const expected_dir_path = try Dir.path.join(arena, &.{ base_realpath, "test_dir" }); // First, test non-alloc version { - const file_path = try ctx.dir.realPath(io, test_file_path, &buf); + const file_path = buf[0..try ctx.dir.realPathFile(io, test_file_path, &buf)]; try expectEqualStrings(expected_file_path, file_path); - const dir_path = try ctx.dir.realPath(io, test_dir_path, &buf); + const dir_path = buf[0..try ctx.dir.realPathFile(io, test_dir_path, &buf)]; try expectEqualStrings(expected_dir_path, dir_path); } // Next, test alloc version { - const file_path = try ctx.dir.realPathAlloc(io, test_file_path, allocator); + const file_path = try ctx.dir.realPathFileAlloc(io, test_file_path, arena); try expectEqualStrings(expected_file_path, file_path); - const dir_path = try ctx.dir.realPathAlloc(io, test_dir_path, allocator); + const dir_path = try ctx.dir.realPathFileAlloc(io, test_dir_path, arena); try expectEqualStrings(expected_dir_path, dir_path); } } @@ -1114,7 +1110,7 @@ test "renameAbsolute" { defer arena.deinit(); const allocator = arena.allocator(); - const base_path = try tmp_dir.dir.realPathAlloc(io, ".", allocator); + const base_path = try tmp_dir.dir.realPathFileAlloc(io, ".", allocator); try expectError(error.FileNotFound, Dir.renameAbsolute( try Dir.path.join(allocator, &.{ base_path, "missing_file_name" }), @@ -2022,7 +2018,7 @@ test "'.' and '..' in absolute functions" { defer arena.deinit(); const allocator = arena.allocator(); - const base_path = try tmp.dir.realPathAlloc(io, ".", allocator); + const base_path = try tmp.dir.realPathFileAlloc(io, ".", allocator); const subdir_path = try Dir.path.join(allocator, &.{ base_path, "./subdir" }); try Dir.makeDirAbsolute(io, subdir_path, .default_dir); @@ -2140,8 +2136,8 @@ test "invalid UTF-8/WTF-8 paths" { try expectError(expected_err, ctx.dir.statFile(io, invalid_path, .{})); if (native_os != .wasi) { - try expectError(expected_err, ctx.dir.realPath(io, invalid_path, &[_]u8{})); - try expectError(expected_err, ctx.dir.realPathAlloc(io, invalid_path, testing.allocator)); + try expectError(expected_err, ctx.dir.realPathFile(io, invalid_path, &[_]u8{})); + try expectError(expected_err, ctx.dir.realPathFileAlloc(io, invalid_path, testing.allocator)); } try expectError(expected_err, Dir.rename(ctx.dir, invalid_path, ctx.dir, invalid_path, io)); @@ -2402,7 +2398,7 @@ test "fchmodat smoke test" { var test_link = true; tmp.dir.setFilePermissions(io, "symlink", .fromMode(0o600), .{ .follow_symlinks = false }) catch |err| switch (err) { - error.OperationNotSupported => test_link = false, + error.OperationUnsupported => test_link = false, else => |e| return e, }; if (test_link) try expectMode(io, tmp.dir, "symlink", .fromMode(0o600)); diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 02357d6d2af24845c1ece2394d97822ad15a5b91..f2ff2a1c1b7609997cbaf718f0f2e9fdf07bed4a 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -3620,6 +3620,8 @@ pub const GetFinalPathNameByHandleFormat = struct { /// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`). /// If DOS volume name format is selected, note that this function does *not* prepend /// `\\?\` prefix to the resultant path. +/// +/// TODO move this function into std.Io.Threaded and add cancelation checks pub fn GetFinalPathNameByHandle( hFile: HANDLE, fmt: GetFinalPathNameByHandleFormat, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 370e6bde33df10f20b63c7c291bb41a4252cb179..61ef8bb3af2e4a32da6e067daeb6ae4984683337 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1527,7 +1527,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi pub const ListenError = error{ FileDescriptorNotASocket, - OperationNotSupported, + OperationUnsupported, } || std.Io.net.IpAddress.ListenError || std.Io.net.UnixAddress.ListenError; pub fn listen(sock: socket_t, backlog: u31) ListenError!void { @@ -1540,7 +1540,7 @@ pub fn listen(sock: socket_t, backlog: u31) ListenError!void { .ADDRINUSE => return error.AddressInUse, .BADF => unreachable, .NOTSOCK => return error.FileDescriptorNotASocket, - .OPNOTSUPP => return error.OperationNotSupported, + .OPNOTSUPP => return error.OperationUnsupported, else => |err| return unexpectedErrno(err), } } @@ -2202,7 +2202,7 @@ pub const FanotifyMarkError = error{ SystemResources, UserMarkQuotaExceeded, NotDir, - OperationNotSupported, + OperationUnsupported, PermissionDenied, NotSameFileSystem, NameTooLong, @@ -2242,7 +2242,7 @@ pub fn fanotify_markZ( .NOMEM => return error.SystemResources, .NOSPC => return error.UserMarkQuotaExceeded, .NOTDIR => return error.NotDir, - .OPNOTSUPP => return error.OperationNotSupported, + .OPNOTSUPP => return error.OperationUnsupported, .PERM => return error.PermissionDenied, .XDEV => return error.NotSameFileSystem, else => |err| return unexpectedErrno(err), @@ -3466,7 +3466,7 @@ pub const SetSockOptError = error{ /// Setting the socket option requires more elevated permissions. PermissionDenied, - OperationNotSupported, + OperationUnsupported, NetworkDown, FileDescriptorNotASocket, SocketNotBound, @@ -3502,7 +3502,7 @@ pub fn setsockopt(fd: socket_t, level: i32, optname: u32, opt: []const u8) SetSo .NOBUFS => return error.SystemResources, .PERM => return error.PermissionDenied, .NODEV => return error.NoDevice, - .OPNOTSUPP => return error.OperationNotSupported, + .OPNOTSUPP => return error.OperationUnsupported, else => |err| return unexpectedErrno(err), } } @@ -3712,7 +3712,7 @@ pub const PrctlError = error{ /// or PR_MPX_DISABLE_MANAGEMENT UnsupportedFeature, /// Can only occur with PR_SET_FP_MODE - OperationNotSupported, + OperationUnsupported, PermissionDenied, } || UnexpectedError; @@ -3736,7 +3736,7 @@ pub fn prctl(option: PR, args: anytype) PrctlError!u31 { .FAULT => return error.InvalidAddress, .INVAL => unreachable, .NODEV, .NXIO => return error.UnsupportedFeature, - .OPNOTSUPP => return error.OperationNotSupported, + .OPNOTSUPP => return error.OperationUnsupported, .PERM, .BUSY => return error.PermissionDenied, .RANGE => unreachable, else => |err| return unexpectedErrno(err), @@ -3995,7 +3995,7 @@ pub const PtraceError = error{ DeviceBusy, InputOutput, NameTooLong, - OperationNotSupported, + OperationUnsupported, OutOfMemory, ProcessNotFound, PermissionDenied, @@ -4097,7 +4097,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo .INVAL => unreachable, .PERM => error.PermissionDenied, .BUSY => error.DeviceBusy, - .NOTSUP => error.OperationNotSupported, + .NOTSUP => error.OperationUnsupported, else => |err| return unexpectedErrno(err), }, @@ -4108,7 +4108,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo pub const NameToFileHandleAtError = error{ FileNotFound, NotDir, - OperationNotSupported, + OperationUnsupported, NameTooLong, Unexpected, }; @@ -4137,7 +4137,7 @@ pub fn name_to_handle_atZ( .INVAL => unreachable, // bad flags, or handle_bytes too big .NOENT => return error.FileNotFound, .NOTDIR => return error.NotDir, - .OPNOTSUPP => return error.OperationNotSupported, + .OPNOTSUPP => return error.OperationUnsupported, .OVERFLOW => return error.NameTooLong, else => |err| return unexpectedErrno(err), } diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 0048431beccee490b84916cbc2107fc1eb97f642..648f284131b1e1e35d7dfe761da9ad80e8a0af95 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -503,7 +503,7 @@ test "rename smoke test" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - const base_path = try tmp.dir.realPathAlloc(io, ".", gpa); + const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa); defer gpa.free(base_path); const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; -- 2.54.0 From 21d0264c61ac29724b98187aa87d192f97b52425 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 21:32:34 -0800 Subject: [PATCH 081/195] std.dynamic_library: use a global static single threaded Io See #30150 --- lib/std/Build/Watch.zig | 2 +- lib/std/Io/File/Reader.zig | 4 +++- lib/std/Io/Writer.zig | 6 +++--- lib/std/debug.zig | 10 ++-------- lib/std/dynamic_library.zig | 40 +++++++++++++++++++------------------ lib/std/fs/test.zig | 8 +++++--- lib/std/posix.zig | 12 +++++------ 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index f7ac47961e28999ed44fdcfb7034e0f8aa5d2ff1..9042241b5539935956f79d86c1306764f428c8ad 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -684,7 +684,7 @@ const Os = switch (builtin.os.tag) { path.root_dir.handle.handle else posix.openat(path.root_dir.handle.handle, path.sub_path, dir_open_flags, 0) catch |err| { - fatal("failed to open directory {f}: {s}", .{ path, @errorName(err) }); + fatal("failed to open directory {f}: {t}", .{ path, err }); }; // Empirically the dir has to stay open or else no events are triggered. errdefer if (!skip_open_dir) posix.close(dir_fd); diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig index 792b20a7163b769f2981a0261d918cea209078ec..2359fad72223a74af46e9bf02e4e8b910638100c 100644 --- a/lib/std/Io/File/Reader.zig +++ b/lib/std/Io/File/Reader.zig @@ -43,7 +43,9 @@ pub const Error = error{ /// In WASI, this error occurs when the file descriptor does /// not hold the required rights to read from it. AccessDenied, - /// Unable to read file due to lock. + /// Unable to read file due to lock. Depending on the `Io` implementation, + /// reading from a locked file may return this error, or may ignore the + /// lock. LockViolation, } || Io.Cancelable || Io.UnexpectedError; diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index fab40001a5c9c4d72081312fe0acd6c41a15ed89..49ca6056d48a5f26a9066fd5a677b230e2801153 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -2838,7 +2838,7 @@ test "discarding sendFile" { const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); defer file.close(io); var r_buffer: [256]u8 = undefined; - var file_writer: File.Writer = .init(file, &r_buffer); + var file_writer: File.Writer = .init(file, io, &r_buffer); try file_writer.interface.writeByte('h'); try file_writer.interface.flush(); @@ -2860,7 +2860,7 @@ test "allocating sendFile" { const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); defer file.close(io); var r_buffer: [2]u8 = undefined; - var file_writer: File.Writer = .init(file, &r_buffer); + var file_writer: File.Writer = .init(file, io, &r_buffer); try file_writer.interface.writeAll("abcd"); try file_writer.interface.flush(); @@ -2884,7 +2884,7 @@ test sendFileReading { const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true }); defer file.close(io); var r_buffer: [2]u8 = undefined; - var file_writer: File.Writer = .init(file, &r_buffer); + var file_writer: File.Writer = .init(file, io, &r_buffer); try file_writer.interface.writeAll("abcd"); try file_writer.interface.flush(); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index e33a2bd575694508b1d06cfaac839816f7a31b64..3850ec3e8c1ef026b6915f9e6f3e4f2593755726 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1610,14 +1610,8 @@ test "manage resources correctly" { var discarding: Writer.Discarding = .init(&.{}); var di: SelfInfo = .init; defer di.deinit(gpa); - try printSourceAtAddress( - gpa, - io, - &di, - &discarding.writer, - S.showMyTrace(), - .no_color, - ); + const t: Io.Terminal = .{ .writer = &discarding.writer, .mode = .no_color }; + try printSourceAtAddress(gpa, io, &di, t, S.showMyTrace()); } /// This API helps you track where a value originated and where it was mutated, diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index a52d64ac3ffbabb1b7d421b2330dbe22afbc1ca0..18db4ad8c6e4b224547321d5d01ab07cf5534c57 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -55,11 +55,11 @@ pub const DynLib = struct { // An iterator is provided in order to traverse the linked list in a idiomatic // fashion. const LinkMap = extern struct { - l_addr: usize, - l_name: [*:0]const u8, - l_ld: ?*elf.Dyn, - l_next: ?*LinkMap, - l_prev: ?*LinkMap, + addr: usize, + name: [*:0]const u8, + ld: ?*elf.Dyn, + next: ?*LinkMap, + prev: ?*LinkMap, pub const Iterator = struct { current: ?*LinkMap, @@ -70,7 +70,7 @@ const LinkMap = extern struct { pub fn next(self: *Iterator) ?*LinkMap { if (self.current) |it| { - self.current = it.l_next; + self.current = it.next; return it; } return null; @@ -79,10 +79,10 @@ const LinkMap = extern struct { }; const RDebug = extern struct { - r_version: i32, - r_map: ?*LinkMap, - r_brk: usize, - r_ldbase: usize, + version: i32, + map: ?*LinkMap, + brk: usize, + ldbase: usize, }; /// TODO fix comparisons of extern symbol pointers so we don't need this helper function. @@ -107,8 +107,8 @@ pub fn linkmap_iterator() error{InvalidExe}!LinkMap.Iterator { elf.DT_DEBUG => { const ptr = @as(?*RDebug, @ptrFromInt(_DYNAMIC[i].d_val)); if (ptr) |r_debug| { - if (r_debug.r_version != 1) return error.InvalidExe; - break :init r_debug.r_map; + if (r_debug.version != 1) return error.InvalidExe; + break :init r_debug.map; } }, elf.DT_PLTGOT => { @@ -142,6 +142,8 @@ const ElfDynLibError = error{ Streaming, } || posix.OpenError || posix.MMapError; +var static_single_threaded_io: Io.Threaded = .init_single_threaded; + pub const ElfDynLib = struct { strings: [*:0]u8, syms: [*]elf.Sym, @@ -157,7 +159,7 @@ pub const ElfDynLib = struct { dt_gnu_hash: *elf.gnu_hash.Header, }; - fn openPath(path: []const u8, io: Io) !Io.Dir { + fn openPath(io: Io, path: []const u8) !Io.Dir { if (path.len == 0) return error.NotDir; var parts = std.mem.tokenizeScalar(u8, path, '/'); var parent = if (path[0] == '/') try Io.Dir.cwd().openDir(io, "/", .{}) else Io.Dir.cwd(); @@ -172,7 +174,7 @@ pub const ElfDynLib = struct { fn resolveFromSearchPath(io: Io, search_path: []const u8, file_name: []const u8, delim: u8) ?posix.fd_t { var paths = std.mem.tokenizeScalar(u8, search_path, delim); while (paths.next()) |p| { - var dir = openPath(p) catch continue; + var dir = openPath(io, p) catch continue; defer dir.close(io); const fd = posix.openat(dir.handle, file_name, .{ .ACCMODE = .RDONLY, @@ -221,7 +223,9 @@ pub const ElfDynLib = struct { } /// Trusts the file. Malicious file will be able to execute arbitrary code. - pub fn open(io: Io, path: []const u8) Error!ElfDynLib { + pub fn open(path: []const u8) Error!ElfDynLib { + const io = static_single_threaded_io.ioBasic(); + const fd = try resolveFromName(io, path); defer posix.close(fd); @@ -551,11 +555,9 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: elf.Versym, vername: []const u8, str } test "ElfDynLib" { - if (native_os != .linux) { - return error.SkipZigTest; - } - + if (native_os != .linux) return error.SkipZigTest; try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so")); + try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so")); } /// Separated to avoid referencing `WindowsDynLib`, because its field types may not diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 92d3630ac7dcffb31fb90599a6db9cb16dada9e3..8c60ffc343b4b8b71f1a36d6e9e8ad5c7b9b8dd0 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1796,7 +1796,7 @@ test "read from locked file" { const f = try ctx.dir.createFile(io, filename, .{ .read = true }); defer f.close(io); var buffer: [1]u8 = undefined; - _ = try f.read(&buffer); + _ = try f.readPositional(io, &.{&buffer}, 0); } { const f = try ctx.dir.createFile(io, filename, .{ @@ -1806,11 +1806,13 @@ test "read from locked file" { defer f.close(io); const f2 = try ctx.dir.openFile(io, filename, .{}); defer f2.close(io); + // On POSIX locks may be ignored, however on Windows they cause + // LockViolation. var buffer: [1]u8 = undefined; if (builtin.os.tag == .windows) { - try expectError(error.LockViolation, f2.read(&buffer)); + try expectError(error.LockViolation, f2.readPositional(io, &.{&buffer}, 0)); } else { - try expectEqual(0, f2.read(&buffer)); + try expectEqual(0, f2.readPositional(io, &.{&buffer}, 0)); } } } diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 61ef8bb3af2e4a32da6e067daeb6ae4984683337..3997bc70cd5ee3bcc0141be8a3cb38cd8b34d263 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -777,7 +777,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) O .NFILE => return error.SystemFdQuotaExceeded, .NODEV => return error.NoDevice, .NOENT => return error.FileNotFound, - .SRCH => return error.ProcessNotFound, + .SRCH => return error.FileNotFound, .NOMEM => return error.SystemResources, .NOSPC => return error.NoSpaceLeft, .NOTDIR => return error.NotDir, @@ -2759,16 +2759,16 @@ pub fn dl_iterate_phdr( // Last return value from the callback function. while (it.next()) |entry| { - const phdrs: []elf.ElfN.Phdr = if (entry.l_addr != 0) phdrs: { - const ehdr: *elf.ElfN.Ehdr = @ptrFromInt(entry.l_addr); + const phdrs: []elf.ElfN.Phdr = if (entry.addr != 0) phdrs: { + const ehdr: *elf.ElfN.Ehdr = @ptrFromInt(entry.addr); assert(mem.eql(u8, ehdr.ident[0..4], elf.MAGIC)); - const phdrs: [*]elf.ElfN.Phdr = @ptrFromInt(entry.l_addr + ehdr.phoff); + const phdrs: [*]elf.ElfN.Phdr = @ptrFromInt(entry.addr + ehdr.phoff); break :phdrs phdrs[0..ehdr.phnum]; } else getSelfPhdrs(); var info: dl_phdr_info = .{ - .addr = entry.l_addr, - .name = entry.l_name, + .addr = entry.addr, + .name = entry.name, .phdr = phdrs.ptr, .phnum = @intCast(phdrs.len), }; -- 2.54.0 From 7ce5ee2e92bf1bf1f39ccc08df19f9a1044e9f2c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 22:03:10 -0800 Subject: [PATCH 082/195] std: update remaining unit tests for std.Io API changes --- lib/std/Build/Cache.zig | 12 ++++++------ lib/std/Io/File/Reader.zig | 4 ++-- lib/std/Io/File/Writer.zig | 8 ++++++-- lib/std/Io/test.zig | 37 +++++++++++++++++-------------------- lib/std/Thread.zig | 18 ++++++++++-------- lib/std/debug.zig | 9 +++++---- 6 files changed, 46 insertions(+), 42 deletions(-) diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index e2c848b6fdcb7b2862c9c9a7db1b603861b4a331..43f8691000684973dd7d3f9551e3f4ff71adb93e 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -1384,8 +1384,8 @@ test "check that changing a file makes cache fail" { try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = original_temp_file_contents }); // Wait for file timestamps to tick - const initial_time = try testGetCurrentFileTimestamp(tmp.dir); - while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) { + const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); + while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) { try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); } @@ -1502,8 +1502,8 @@ test "Manifest with files added after initial hash work" { try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second!\n" }); // Wait for file timestamps to tick - const initial_time = try testGetCurrentFileTimestamp(tmp.dir); - while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) { + const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); + while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) { try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); } @@ -1553,8 +1553,8 @@ test "Manifest with files added after initial hash work" { try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second, updated\n" }); // Wait for file timestamps to tick - const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir); - while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time2.nanoseconds) { + const initial_time2 = try testGetCurrentFileTimestamp(io, tmp.dir); + while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time2.nanoseconds) { try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); } diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig index 2359fad72223a74af46e9bf02e4e8b910638100c..0c573c9ae1423ef9ebe8d1fee5e26d477cfec008 100644 --- a/lib/std/Io/File/Reader.zig +++ b/lib/std/Io/File/Reader.zig @@ -18,8 +18,8 @@ io: Io, file: File, err: ?Error = null, mode: Mode = .positional, -/// Tracks the true seek position in the file. To obtain the logical -/// position, use `logicalPos`. +/// Tracks the true seek position in the file. To obtain the logical position, +/// use `logicalPos`. pos: u64 = 0, size: ?u64 = null, size_err: ?SizeError = null, diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 56a1c09340649c566db5a172eb765c96021da80c..3487416719a59d1f87fb90a48043ca52804201f9 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -11,8 +11,8 @@ io: Io, file: File, err: ?Error = null, mode: Mode = .positional, -/// Tracks the true seek position in the file. To obtain the logical -/// position, add the buffer size to this value. +/// Tracks the true seek position in the file. To obtain the logical position, +/// use `logicalPos`. pos: u64 = 0, write_file_err: ?WriteFileError = null, seek_err: ?SeekError = null, @@ -221,6 +221,10 @@ pub fn seekTo(w: *Writer, offset: u64) (SeekError || Io.Writer.Error)!void { try seekToUnbuffered(w, offset); } +pub fn logicalPos(w: *const Writer) u64 { + return w.pos + w.interface.end; +} + /// Asserts that no data is currently buffered. pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void { assert(w.interface.buffered().len == 0); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 8f32555b52e96b22c2a8ee630feb4b3db18fb64b..ef0d45d95366b88b227eea8e2fd4e99f62c91411 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -64,33 +64,28 @@ test "write a file, read it, then delete it" { try tmp.dir.deleteFile(io, tmp_file_name); } -test "File seek ops" { +test "File.Writer.seekTo" { var tmp = tmpDir(.{}); defer tmp.cleanup(); const io = testing.io; + var data: [8192]u8 = undefined; + @memset(&data, 0x55); + const tmp_file_name = "temp_test_file.txt"; var file = try tmp.dir.createFile(io, tmp_file_name, .{}); defer file.close(io); - try file.writeAll(&([_]u8{0x55} ** 8192)); + var fw = file.writerStreaming(io, &.{}); - // Seek to the end - try file.seekFromEnd(0); - try expect((try file.getPos()) == try file.length(io)); - // Negative delta - try file.seekBy(-4096); - try expect((try file.getPos()) == 4096); - // Positive delta - try file.seekBy(10); - try expect((try file.getPos()) == 4106); - // Absolute position - try file.seekTo(1234); - try expect((try file.getPos()) == 1234); + try fw.interface.writeAll(&data); + try expect(fw.logicalPos() == try file.length(io)); + try fw.seekTo(1234); + try expect(fw.logicalPos() == 1234); } -test "setLength" { +test "File.setLength" { const io = testing.io; var tmp = tmpDir(.{}); @@ -100,19 +95,21 @@ test "setLength" { var file = try tmp.dir.createFile(io, tmp_file_name, .{}); defer file.close(io); + var fw = file.writerStreaming(io, &.{}); + // Verify that the file size changes and the file offset is not moved try expect((try file.length(io)) == 0); - try expect((try file.getPos()) == 0); + try expect(fw.logicalPos() == 0); try file.setLength(io, 8192); try expect((try file.length(io)) == 8192); - try expect((try file.getPos()) == 0); - try file.seekTo(100); + try expect(fw.logicalPos() == 0); + try fw.seekTo(100); try file.setLength(io, 4096); try expect((try file.length(io)) == 4096); - try expect((try file.getPos()) == 100); + try expect(fw.logicalPos() == 100); try file.setLength(io, 0); try expect((try file.length(io)) == 0); - try expect((try file.getPos()) == 100); + try expect(fw.logicalPos() == 100); } test "legacy setLength" { diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 8453bc4c8122da017e5a54c81869fe9cd0fca414..fbce1cd000381b835642f87916850297170aa4c2 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -211,7 +211,7 @@ pub fn setName(self: Thread, io: Io, name: []const u8) SetNameError!void { const file = try Io.Dir.cwd().openFile(io, path, .{ .mode = .write_only }); defer file.close(io); - try file.writeAll(name); + try file.writeStreamingAll(io, name); return; }, .windows => { @@ -1676,14 +1676,14 @@ const LinuxThreadImpl = struct { } }; -fn testThreadName(thread: *Thread) !void { +fn testThreadName(io: Io, thread: *Thread) !void { const testCases = &[_][]const u8{ "mythread", "b" ** max_name_len, }; inline for (testCases) |tc| { - try thread.setName(tc); + try thread.setName(io, tc); var name_buffer: [max_name_len:0]u8 = undefined; @@ -1698,6 +1698,8 @@ fn testThreadName(thread: *Thread) !void { test "setName, getName" { if (builtin.single_threaded) return error.SkipZigTest; + const io = testing.io; + const Context = struct { start_wait_event: ResetEvent = .unset, test_done_event: ResetEvent = .unset, @@ -1711,11 +1713,11 @@ test "setName, getName" { ctx.start_wait_event.wait(); switch (native_os) { - .windows => testThreadName(&ctx.thread) catch |err| switch (err) { + .windows => testThreadName(io, &ctx.thread) catch |err| switch (err) { error.Unsupported => return error.SkipZigTest, else => return err, }, - else => try testThreadName(&ctx.thread), + else => try testThreadName(io, &ctx.thread), } // Signal our test is done @@ -1735,14 +1737,14 @@ test "setName, getName" { switch (native_os) { .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { - const res = thread.setName("foobar"); + const res = thread.setName(io, "foobar"); try std.testing.expectError(error.Unsupported, res); }, - .windows => testThreadName(&thread) catch |err| switch (err) { + .windows => testThreadName(io, &thread) catch |err| switch (err) { error.Unsupported => return error.SkipZigTest, else => return err, }, - else => try testThreadName(&thread), + else => try testThreadName(io, &thread), } context.thread_done_event.set(); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 3850ec3e8c1ef026b6915f9e6f3e4f2593755726..cef42334957daf668cab93c49553662817937c32 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -384,12 +384,13 @@ pub fn dumpHexFallible(t: Io.Terminal, bytes: []const u8) !void { } test dumpHexFallible { + const gpa = testing.allocator; const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 }; - var aw: Writer.Allocating = .init(testing.allocator); + var aw: Writer.Allocating = .init(gpa); defer aw.deinit(); - try dumpHexFallible(&aw.writer, .no_color, bytes); - const expected = try std.fmt.allocPrint(testing.allocator, + try dumpHexFallible(.{ .writer = &aw.writer, .mode = .no_color }, bytes); + const expected = try std.fmt.allocPrint(gpa, \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ \\{x:0>[2]} 01 12 13 ... \\ @@ -398,7 +399,7 @@ test dumpHexFallible { @intFromPtr(bytes.ptr) + 16, @sizeOf(usize) * 2, }); - defer testing.allocator.free(expected); + defer gpa.free(expected); try testing.expectEqualStrings(expected, aw.written()); } -- 2.54.0 From 651ff9f9ee7d030892bdb06fc1c7e57cdb716d2b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 22:21:35 -0800 Subject: [PATCH 083/195] std.Io.Threaded: implement dirHardLink --- lib/std/Io.zig | 1 + lib/std/Io/Threaded.zig | 112 ++++++++++++++++++++++++++++++++++++++++ lib/std/os/linux.zig | 4 +- 3 files changed, 115 insertions(+), 2 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 0959ce06e7df7b6b28f0949b41eea0e27fb99c67..1d477d330e5d2fec06dca6e7bd8ffa8215610eae 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -683,6 +683,7 @@ pub const VTable = struct { dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void, dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, + 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, fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, fileLength: *const fn (?*anyopaque, File) File.LengthError!u64, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index a018795c21386ae6832098ff55f7c2298670c23a..51412d794959bfb72b32e10ce7d2cee3dd12a0c8 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -728,6 +728,7 @@ pub fn io(t: *Threaded) Io { .dirSetFilePermissions = dirSetFilePermissions, .dirSetTimestamps = dirSetTimestamps, .dirSetTimestampsNow = dirSetTimestampsNow, + .dirHardLink = dirHardLink, .fileStat = fileStat, .fileLength = fileLength, @@ -862,6 +863,7 @@ pub fn ioBasic(t: *Threaded) Io { .dirSetFilePermissions = dirSetFilePermissions, .dirSetTimestamps = dirSetTimestamps, .dirSetTimestampsNow = dirSetTimestampsNow, + .dirHardLink = dirHardLink, .fileStat = fileStat, .fileLength = fileLength, @@ -6264,6 +6266,116 @@ fn dirOpenDirWasi( } } +fn dirHardLink( + userdata: ?*anyopaque, + old_dir: Dir, + old_sub_path: []const u8, + new_dir: Dir, + new_sub_path: []const u8, + options: Dir.HardLinkOptions, +) Dir.HardLinkError!void { + if (is_windows) return error.OperationUnsupported; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (native_os == .wasi and !builtin.link_libc) { + const flags: std.os.wasi.lookupflags_t = .{ + .SYMLINK_FOLLOW = options.follow_symlinks, + }; + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.path_link( + old_dir.handle, + flags, + old_sub_path.ptr, + old_sub_path.len, + new_dir.handle, + new_sub_path.ptr, + new_sub_path.len, + )) { + .SUCCESS => return current_thread.endSyscall(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .FAULT => |err| return errnoBug(err), + .IO => return error.HardwareFailure, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + .XDEV => return error.NotSameFileSystem, + .INVAL => |err| return errnoBug(err), + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + + var old_path_buffer: [posix.PATH_MAX]u8 = undefined; + var new_path_buffer: [posix.PATH_MAX]u8 = undefined; + + const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); + const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); + + const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.linkat( + old_dir.handle, + old_sub_path_posix, + new_dir.handle, + new_sub_path_posix, + flags, + ))) { + .SUCCESS => return current_thread.endSyscall(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .FAULT => |err| return errnoBug(err), + .IO => return error.HardwareFailure, + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + .XDEV => return error.NotSameFileSystem, + .INVAL => |err| return errnoBug(err), + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + fn fileClose(userdata: ?*anyopaque, files: []const File) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index cd12b137735cad643bff1dbfacc2b46733ad071b..1db462ad3e3d3703ad118e804bca5c3125490840 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1561,14 +1561,14 @@ pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) usize { } } -pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: i32) usize { +pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: u32) usize { return syscall5( .linkat, @as(usize, @bitCast(@as(isize, oldfd))), @intFromPtr(oldpath), @as(usize, @bitCast(@as(isize, newfd))), @intFromPtr(newpath), - @as(usize, @bitCast(@as(isize, flags))), + flags, ); } -- 2.54.0 From 47a2e8026fb567b46b9ddf2256f1ace2fba674e3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 22:31:07 -0800 Subject: [PATCH 084/195] std.Io.Threaded: fix dirDeleteDir when not empty --- lib/std/Io/Threaded.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 51412d794959bfb72b32e10ce7d2cee3dd12a0c8..ce49049f021a7406477108c41f6a9ea946a75e72 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -4485,7 +4485,7 @@ fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir. .NOMEM => return error.SystemResources, .ROFS => return error.ReadOnlyFileSystem, .EXIST => |err| return errnoBug(err), - .NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR + .NOTEMPTY => return error.DirNotEmpty, .ILSEQ => return error.BadPathName, .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component .BADF => |err| return errnoBug(err), // File descriptor used after closed. -- 2.54.0 From 6f00157e1e0159a5115a4e9e2ed389e819499282 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 22:32:29 -0800 Subject: [PATCH 085/195] std: fix "open smoke test" by passing allow_directory option --- lib/std/fs/test.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 8c60ffc343b4b8b71f1a36d6e9e8ad5c7b9b8dd0..88d348d0a8dceae9b03fe2e6b413d0a1c60df438 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -2561,7 +2561,7 @@ test "open smoke test" { } // Try opening as file which should fail. - try expectError(error.IsDir, tmp.dir.openFile(io, "some_dir", .{})); + try expectError(error.IsDir, tmp.dir.openFile(io, "some_dir", .{ .allow_directory = false })); } test "hard link with different directories" { -- 2.54.0 From f27bd87ade2e43dce66963f43ce678e361ddbfc2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 23:05:51 -0800 Subject: [PATCH 086/195] std.Io.Threaded: allow length-0 file writes At first I thought about keeping this as an assertion but I can see this being useful if you already know how many bytes to read and you are filling the end of the buffer. This also more closely mirrors POSIX APIs. --- lib/std/Io/File.zig | 4 +--- lib/std/Io/Threaded.zig | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index e06f2ba40ded4cdcce1aaafba0c5395c981d6847..601a88260588b6eb58b4b14f3cbfe86009d7a1e6 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -497,7 +497,7 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { pub const ReadPositionalError = Reader.Error || error{Unseekable}; -/// Returns 0 on end of stream. +/// Returns 0 on stream end or if `buffer` has no space available for data. /// /// See also: /// * `reader` @@ -507,8 +507,6 @@ pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) Rea pub const WritePositionalError = Writer.Error || error{Unseekable}; -/// Returns 0 on end of stream. -/// /// See also: /// * `writer` pub fn writePositional(file: File, io: Io, buffer: []const []const u8, offset: u64) WritePositionalError!usize { diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ce49049f021a7406477108c41f6a9ea946a75e72..ffa292a0409fba2ef742b5608d107177bfd2952e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -6400,6 +6400,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) i += 1; } } + if (i == 0) return 0; const dest = iovecs_buffer[0..i]; assert(dest[0].len > 0); @@ -6482,6 +6483,7 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u const DWORD = windows.DWORD; var index: usize = 0; while (data[index].len == 0) index += 1; + if (index == 0) return 0; const buffer = data[index]; const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); @@ -6519,6 +6521,7 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8 i += 1; } } + if (i == 0) return 0; const dest = iovecs_buffer[0..i]; assert(dest[0].len > 0); @@ -6614,6 +6617,7 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const [] var index: usize = 0; while (data[index].len == 0) index += 1; + if (index == 0) return 0; const buffer = data[index]; const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); -- 2.54.0 From c1b9c46319ff6c76484121c177086b1fbe70d50c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Dec 2025 23:28:10 -0800 Subject: [PATCH 087/195] std.Io: introduce path_only to File.OpenFlags --- lib/std/Io/File.zig | 6 ++++++ lib/std/Io/Threaded.zig | 3 +++ lib/std/fs/test.zig | 9 ++++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 601a88260588b6eb58b4b14f3cbfe86009d7a1e6..75921948ccefdffff8b95149bf73c729f621acd9 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -115,6 +115,12 @@ pub const OpenFlags = struct { /// * On other operating systems, the behavior is implemented with an additional /// `fstat` syscall. allow_directory: bool = true, + /// Indicates intent for only some operations to be performed on this + /// opened file: + /// * `close` + /// * `stat` + /// On Linux and FreeBSD, this corresponds to `std.posix.O.PATH`. + path_only: bool = false, /// Open the file with an advisory lock to coordinate with other processes /// accessing it at the same time. An exclusive lock will prevent other diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ffa292a0409fba2ef742b5608d107177bfd2952e..5f8ce2c101e31f42434daf111369ea432279a63f 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2721,6 +2721,7 @@ fn dirOpenFilePosix( .wasi => .{ .read = flags.mode != .write_only, .write = flags.mode != .read_only, + .NOFOLLOW = !flags.follow_symlinks, }, else => .{ .ACCMODE = switch (flags.mode) { @@ -2728,11 +2729,13 @@ fn dirOpenFilePosix( .write_only => .WRONLY, .read_write => .RDWR, }, + .NOFOLLOW = !flags.follow_symlinks, }, }; if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty; + if (@hasField(posix.O, "PATH") and flags.path_only) os_flags.PATH = true; // Use the O locking flags if the os supports them to acquire the lock // atomically. Note that the NONBLOCK flag is removed after the openat() diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 88d348d0a8dceae9b03fe2e6b413d0a1c60df438..5d03b7c64dd43495f5468c0d6c2c4ab5ef5c7964 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -295,8 +295,8 @@ fn testReadLinkAbsolute(io: Io, target_path: []const u8, symlink_path: []const u test "File.stat on a File that is a symlink returns Kind.sym_link" { const io = testing.io; - // This test requires getting a file descriptor of a symlink which - // is not possible on all targets + // This test requires getting a file descriptor of a symlink which is not + // possible on all targets. switch (builtin.target.os.tag) { .windows, .linux => {}, else => return error.SkipZigTest, @@ -309,7 +309,10 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { try setupSymlink(io, ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); - var symlink: Dir = try ctx.dir.openDir(io, "symlink", .{ .follow_symlinks = false }); + var symlink: File = try ctx.dir.openFile(io, "symlink", .{ + .follow_symlinks = false, + .path_only = true, + }); defer symlink.close(io); const stat = try symlink.stat(io); -- 2.54.0 From 8767a9a6d1f6d92a1e9a98a31cb6dc98eb2c59f3 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 18 Dec 2025 23:55:13 -0800 Subject: [PATCH 088/195] testFilenameLimits: Fix collision with dir and filename --- lib/std/fs/test.zig | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 5d03b7c64dd43495f5468c0d6c2c4ab5ef5c7964..f078e409ac3339be7122a7b9aec190563754a251 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1380,12 +1380,12 @@ test "makepath ignores '.'" { try expectDir(io, tmp.dir, expectedPath); } -fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8) !void { +fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8, maxed_dirname: []const u8) !void { // create a file, a dir, and a nested file all with maxed filenames { try iterable_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" }); - var maxed_dir = try iterable_dir.makeOpenPath(io, maxed_filename, .{}); + var maxed_dir = try iterable_dir.makeOpenPath(io, maxed_dirname, .{}); defer maxed_dir.close(io); try maxed_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" }); @@ -1427,18 +1427,23 @@ test "max file name component lengths" { if (native_os == .windows) { // U+FFFF is the character with the largest code point that is encoded as a single - // UTF-16 code unit, so Windows allows for NAME_MAX of them. - const maxed_windows_filename = ("\u{FFFF}".*) ** windows.NAME_MAX; - try testFilenameLimits(io, tmp.dir, &maxed_windows_filename); + // WTF-16 code unit, so Windows allows for NAME_MAX of them. + const maxed_windows_filename1 = ("\u{FFFF}".*) ** windows.NAME_MAX; + // This is also a code point that is encoded as one WTF-16 code unit, but + // three WTF-8 bytes, so it exercises the limits of both WTF-16 and WTF-8 encodings. + const maxed_windows_filename2 = ("€".*) ** windows.NAME_MAX; + try testFilenameLimits(io, tmp.dir, &maxed_windows_filename1, &maxed_windows_filename2); } else if (native_os == .wasi) { // On WASI, the maxed filename depends on the host OS, so in order for this test to // work on any host, we need to use a length that will work for all platforms // (i.e. the minimum max_name_bytes of all supported platforms). - const maxed_wasi_filename = [_]u8{'1'} ** 255; - try testFilenameLimits(io, tmp.dir, &maxed_wasi_filename); + const maxed_wasi_filename1 = [_]u8{'1'} ** 255; + const maxed_wasi_filename2 = [_]u8{'2'} ** 255; + try testFilenameLimits(io, tmp.dir, &maxed_wasi_filename1, &maxed_wasi_filename2); } else { - const maxed_ascii_filename = [_]u8{'1'} ** std.fs.max_name_bytes; - try testFilenameLimits(io, tmp.dir, &maxed_ascii_filename); + const maxed_ascii_filename1 = [_]u8{'1'} ** std.fs.max_name_bytes; + const maxed_ascii_filename2 = [_]u8{'2'} ** std.fs.max_name_bytes; + try testFilenameLimits(io, tmp.dir, &maxed_ascii_filename1, &maxed_ascii_filename2); } } -- 2.54.0 From 406950f7566f7f220847ab2faa2c26914dcda257 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 19 Dec 2025 03:11:15 -0800 Subject: [PATCH 089/195] std.process: Fix executableDirPath functions --- lib/std/process.zig | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/std/process.zig b/lib/std/process.zig index 23a3d92e9b8e39789548bd7c33ba36e49c955fb6..d6d671e251eb3189ab616c027a2854cac7837308 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -2193,17 +2193,21 @@ pub fn executablePath(io: Io, out_buffer: []u8) ExecutablePathError!usize { /// /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -pub fn executableDirPath(out_buffer: []u8) ExecutablePathError!usize { - const n = try executablePath(out_buffer); +pub fn executableDirPath(io: Io, out_buffer: []u8) ExecutablePathError!usize { + const n = try executablePath(io, out_buffer); // Assert that the OS APIs return absolute paths, and therefore dirname // will not return null. - return std.fs.path.dirname(out_buffer[0..n]).?; + return std.fs.path.dirname(out_buffer[0..n]).?.len; } /// Same as `executableDirPath` except allocates the result. -pub fn executableDirPathAlloc(allocator: Allocator) ![]u8 { +pub fn executableDirPathAlloc(io: Io, allocator: Allocator) ExecutablePathAllocError![]u8 { var buffer: [max_path_bytes]u8 = undefined; - return allocator.dupe(u8, try executableDirPath(&buffer)); + const dir_path_len = executableDirPath(io, &buffer) catch |err| switch (err) { + error.NameTooLong => unreachable, + else => |e| return e, + }; + return allocator.dupe(u8, buffer[0..dir_path_len]); } pub const OpenExecutableError = File.OpenError || ExecutablePathError || File.LockError; -- 2.54.0 From 2f30b0f44d5dbdb80362f181f35504965db733f9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 09:59:05 -0800 Subject: [PATCH 090/195] std.Progress.start: handle cancelation from isTty It's important not to swallow error.Canceled. We don't have recancel() yet but that will be a way to "rearm" cancelation after handling it so that it is not ignored. --- lib/std/Progress.zig | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 54b52d48af11fc06cba1c7a26ca3a2a9198158b4..8bd120dbd1f23899a4707eebc2ac2225543d29cb 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -497,10 +497,17 @@ pub fn start(io: Io, options: Options) Node { global_progress.terminal = stderr; if (stderr.enableAnsiEscapeCodes(io)) |_| { global_progress.terminal_mode = .ansi_escape_codes; - } else |_| if (is_windows and stderr.isTty(io)) { - global_progress.terminal_mode = TerminalMode{ .windows_api = .{ - .code_page = windows.kernel32.GetConsoleOutputCP(), - } }; + } else |_| if (is_windows) { + if (stderr.isTty(io)) { + global_progress.terminal_mode = TerminalMode{ .windows_api = .{ + .code_page = windows.kernel32.GetConsoleOutputCP(), + } }; + } else |err| switch (err) { + error.Canceled => { + // TODO: recancel here, or block cancelation for this function + return Node.none; + }, + } } if (global_progress.terminal_mode == .off) { -- 2.54.0 From f96ef9780a4604316ff9bcb5cc2f0775c0de5c79 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 19 Dec 2025 03:14:12 -0800 Subject: [PATCH 091/195] SelfInfo.Windows: a few fixes for std.Io --- lib/std/debug/SelfInfo/Windows.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index c296b287026758f57f6ee8c53a0e22648e43062f..c34c60f3ec2ede798bb10822b0ece7a59f67098c 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -149,8 +149,9 @@ pub const UnwindContext = struct { return ctx.cur.getRegs().bp; } }; -pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { +pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize { _ = si; + _ = io; _ = gpa; const current_regs = context.cur.getRegs(); @@ -391,7 +392,7 @@ const Module = struct { .section_view = section_view, }; }; - errdefer if (mapped_file) |*mf| mf.deinit(); + errdefer if (mapped_file) |*mf| mf.deinit(io); const coff_image_base = coff_obj.getImageBase(); -- 2.54.0 From b6cde023225de9a164fc5e43ddc04551934cf6e9 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 19 Dec 2025 03:15:37 -0800 Subject: [PATCH 092/195] fs tests: Remove references to readLinkW which no longer exists --- lib/std/fs/test.zig | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index f078e409ac3339be7122a7b9aec190563754a251..e253aeeb54657a571b276299f862c5f8288c8235 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -218,16 +218,10 @@ test "Dir.readLink" { // test 1: symlink to a file try setupSymlink(io, ctx.dir, file_target_path, "symlink1", .{}); try testReadLink(io, ctx.dir, canonical_file_target_path, "symlink1"); - if (builtin.os.tag == .windows) { - try testReadLinkW(testing.allocator, ctx.dir, canonical_file_target_path, "symlink1"); - } // test 2: symlink to a directory (can be different on Windows) try setupSymlink(io, ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true }); try testReadLink(io, ctx.dir, canonical_dir_target_path, "symlink2"); - if (builtin.os.tag == .windows) { - try testReadLinkW(testing.allocator, ctx.dir, canonical_dir_target_path, "symlink2"); - } // test 3: relative path symlink const parent_file = ".." ++ Dir.path.sep_str ++ "target.txt"; @@ -236,9 +230,6 @@ test "Dir.readLink" { defer subdir.close(io); try setupSymlink(io, subdir, canonical_parent_file, "relative-link.txt", .{}); try testReadLink(io, subdir, canonical_parent_file, "relative-link.txt"); - if (builtin.os.tag == .windows) { - try testReadLinkW(testing.allocator, subdir, canonical_parent_file, "relative-link.txt"); - } } }.impl); } @@ -275,17 +266,6 @@ fn testReadLink(io: Io, dir: Dir, target_path: []const u8, symlink_path: []const try expectEqualStrings(target_path, actual); } -fn testReadLinkW(allocator: Allocator, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { - const target_path_w = try std.unicode.wtf8ToWtf16LeAlloc(allocator, target_path); - defer allocator.free(target_path_w); - // Calling the W functions directly requires the path to be NT-prefixed - const symlink_path_w = try std.os.windows.sliceToPrefixedFileW(dir.handle, symlink_path); - const wtf16_buffer = try allocator.alloc(u16, target_path_w.len); - defer allocator.free(wtf16_buffer); - const actual = try dir.readLinkW(symlink_path_w.span(), wtf16_buffer); - try expectEqualSlices(u16, target_path_w, actual); -} - fn testReadLinkAbsolute(io: Io, target_path: []const u8, symlink_path: []const u8) !void { var buffer: [Dir.max_path_bytes]u8 = undefined; const given = buffer[0..try Dir.readLinkAbsolute(io, symlink_path, &buffer)]; @@ -2153,6 +2133,7 @@ test "invalid UTF-8/WTF-8 paths" { try expectError(expected_err, Dir.rename(ctx.dir, invalid_path, ctx.dir, invalid_path, io)); if (native_os != .wasi and ctx.path_type != .relative) { + var buf: [Dir.max_path_bytes]u8 = undefined; try expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, io, .{})); try expectError(expected_err, Dir.makeDirAbsolute(io, invalid_path, .default_dir)); try expectError(expected_err, Dir.deleteDirAbsolute(io, invalid_path)); @@ -2162,10 +2143,10 @@ test "invalid UTF-8/WTF-8 paths" { try expectError(expected_err, Dir.accessAbsolute(io, invalid_path, .{})); try expectError(expected_err, Dir.createFileAbsolute(io, invalid_path, .{})); try expectError(expected_err, Dir.deleteFileAbsolute(io, invalid_path)); - var readlink_buf: [Dir.max_path_bytes]u8 = undefined; - try expectError(expected_err, Dir.readLinkAbsolute(io, invalid_path, &readlink_buf)); + try expectError(expected_err, Dir.readLinkAbsolute(io, invalid_path, &buf)); try expectError(expected_err, Dir.symLinkAbsolute(io, invalid_path, invalid_path, .{})); - try expectError(expected_err, Dir.realPathAbsoluteAlloc(io, invalid_path, testing.allocator)); + try expectError(expected_err, Dir.realPathFileAbsolute(io, invalid_path, &buf)); + try expectError(expected_err, Dir.realPathFileAbsoluteAlloc(io, invalid_path, testing.allocator)); } } }.impl); -- 2.54.0 From edcbd7ed2c75c425a7ea316ecfb41c7d267f83c7 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 19 Dec 2025 03:17:10 -0800 Subject: [PATCH 093/195] os.windows: Add comment explaining why we don't need a separate EX definition for RENAME_INFORMATION --- lib/std/os/windows.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index f2ff2a1c1b7609997cbaf718f0f2e9fdf07bed4a..125b43f736b2b5afabcaa2f0095f9f220969fb49 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -215,6 +215,10 @@ pub const FILE = struct { AccessFlags: ACCESS_MASK, }; + // Note: This is not separated into RENAME_INFORMATION and RENAME_INFORMATION_EX because + // the only difference is the `Flags` type (BOOLEAN before _EX, ULONG in the _EX), + // which doesn't affect the struct layout--the offset of RootDirectory is the same + // regardless. pub const RENAME_INFORMATION = extern struct { Flags: FLAGS, RootDirectory: ?HANDLE, -- 2.54.0 From 07c4ea2de0fa9246bec3c1ed9f7c2c57e4b25893 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 10:02:27 -0800 Subject: [PATCH 094/195] all comments are notes, and use doc comments where possible --- lib/std/os/windows.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 125b43f736b2b5afabcaa2f0095f9f220969fb49..2015759754d1717c16a7161ae0539cc74f65bb6b 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -215,10 +215,10 @@ pub const FILE = struct { AccessFlags: ACCESS_MASK, }; - // Note: This is not separated into RENAME_INFORMATION and RENAME_INFORMATION_EX because - // the only difference is the `Flags` type (BOOLEAN before _EX, ULONG in the _EX), - // which doesn't affect the struct layout--the offset of RootDirectory is the same - // regardless. + /// This is not separated into RENAME_INFORMATION and RENAME_INFORMATION_EX because + /// the only difference is the `Flags` type (BOOLEAN before _EX, ULONG in the _EX), + /// which doesn't affect the struct layout--the offset of RootDirectory is the same + /// regardless. pub const RENAME_INFORMATION = extern struct { Flags: FLAGS, RootDirectory: ?HANDLE, -- 2.54.0 From 1136d36362f87ebad83007ea31d88c8427c0a019 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 19 Dec 2025 03:24:02 -0800 Subject: [PATCH 095/195] windows: Remove BadPathName from error set of GetFinalPathNameByHandle These validations are really only checking for kernel/mount manager bugs, so an assert seems more appropriate. --- lib/std/os/windows.zig | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 2015759754d1717c16a7161ae0539cc74f65bb6b..3ea48585ffd1c53e01d8f276e88f9f2ac8be2c61 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -3599,7 +3599,6 @@ test QueryObjectName { pub const GetFinalPathNameByHandleError = error{ AccessDenied, - BadPathName, FileNotFound, NameTooLong, /// The volume does not contain a recognized file system. File system @@ -3705,6 +3704,7 @@ pub fn GetFinalPathNameByHandle( error.WouldBlock => return error.Unexpected, error.NetworkNotFound => return error.Unexpected, error.AntivirusInterference => return error.Unexpected, + error.BadPathName => return error.Unexpected, else => |e| return e, }; defer CloseHandle(mgmt_handle); @@ -3750,9 +3750,7 @@ pub fn GetFinalPathNameByHandle( const total_len = drive_letter.len + file_name_u16.len; // Validate that DOS does not contain any spurious nul bytes. - if (mem.findScalar(u16, out_buffer[0..total_len], 0)) |_| { - return error.BadPathName; - } + assert(mem.findScalar(u16, out_buffer[0..total_len], 0) == null); return out_buffer[0..total_len]; } else if (mountmgrIsVolumeName(symlink)) { @@ -3802,9 +3800,7 @@ pub fn GetFinalPathNameByHandle( const total_len = volume_path.len + file_name_u16.len; // Validate that DOS does not contain any spurious nul bytes. - if (mem.findScalar(u16, out_buffer[0..total_len], 0)) |_| { - return error.BadPathName; - } + assert(mem.findScalar(u16, out_buffer[0..total_len], 0) == null); return out_buffer[0..total_len]; } -- 2.54.0 From 3431f4503116e06b542eedf9c9bad6f769af3ebc Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 19 Dec 2025 03:25:28 -0800 Subject: [PATCH 096/195] std: Enough fixes to get things compiling on Windows --- lib/std/Io/Dir.zig | 2 + lib/std/Io/File.zig | 4 +- lib/std/Io/Terminal.zig | 2 +- lib/std/Io/Threaded.zig | 220 +++++++++++++++++++++++++--------------- lib/std/posix/test.zig | 2 +- 5 files changed, 144 insertions(+), 86 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index ae817676a71f5ab3962bc65fb736848b1584f35e..2de0fbe564986375f5740e6ea15956938d726249 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -1028,6 +1028,8 @@ pub const SymLinkError = error{ FileNotFound, SystemResources, NoSpaceLeft, + /// On Windows, `\\server` or `\\server\share` was not found. + NetworkNotFound, ReadOnlyFileSystem, NotDir, } || PathNameError || Io.Cancelable || Io.UnexpectedError; diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 75921948ccefdffff8b95149bf73c729f621acd9..dc64a4d2c3b642831012a578d0a623836a5bffa4 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -401,8 +401,8 @@ pub const Permissions = std.options.FilePermissions orelse if (is_windows) enum( const windows = std.os.windows; - pub fn toAttributes(self: @This()) windows.DWORD { - return @intFromEnum(self); + pub fn toAttributes(self: @This()) windows.FILE.ATTRIBUTE { + return @bitCast(@intFromEnum(self)); } pub fn readOnly(self: @This()) bool { diff --git a/lib/std/Io/Terminal.zig b/lib/std/Io/Terminal.zig index d1b257b9975dbd9ba49f2cbd2ff7e1bb8f651688..549adcf20717746f48c61b1d1c9f52178f98c46d 100644 --- a/lib/std/Io/Terminal.zig +++ b/lib/std/Io/Terminal.zig @@ -60,7 +60,7 @@ pub const Mode = union(enum) { const windows = std.os.windows; var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != 0) { - return .{ .terminal_winapi = .{ + return .{ .windows_api = .{ .handle = file.handle, .reset_attributes = info.wAttributes, } }; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 5f8ce2c101e31f42434daf111369ea432279a63f..81c18a8c74c2a81a2688b0a8d8f10877cea7083a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3889,9 +3889,10 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, return realPathWindows(current_thread, h_file, out_buffer); } -fn realPathWindows(current_thread: *Thread, h_file: windows.HANDLE, out_buffer: []u8) Dir.RealPathFileError { +fn realPathWindows(current_thread: *Thread, h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize { _ = current_thread; // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks - const wide_slice = windows.GetFinalPathNameByHandle(h_file, .{}, out_buffer); + var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf); const len = std.unicode.calcWtf8Len(wide_slice); if (len > out_buffer.len) @@ -3981,7 +3982,19 @@ fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.Rea return realPathWindows(current_thread, dir.handle, out_buffer); } -fn fileRealPath(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize { +const fileRealPath = switch (native_os) { + .windows => fileRealPathWindows, + else => fileRealPathPosix, +}; + +fn fileRealPathWindows(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize { + if (native_os == .wasi) return error.OperationUnsupported; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + return realPathWindows(current_thread, file.handle, out_buffer); +} + +fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize { if (native_os == .wasi) return error.OperationUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -4128,7 +4141,10 @@ const dirDeleteFile = switch (native_os) { }; fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { - return dirDeleteWindows(userdata, dir, sub_path, false); + return dirDeleteWindows(userdata, dir, sub_path, false) catch |err| switch (err) { + error.DirNotEmpty => unreachable, + else => |e| return e, + }; } fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { @@ -4261,10 +4277,13 @@ const dirDeleteDir = switch (native_os) { }; fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { - return dirDeleteWindows(userdata, dir, sub_path, true); + return dirDeleteWindows(userdata, dir, sub_path, true) catch |err| switch (err) { + error.IsDir => unreachable, + else => |e| return e, + }; } -fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remove_dir: bool) Dir.DeleteFileError!void { +fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remove_dir: bool) (Dir.DeleteDirError || Dir.DeleteFileError)!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const w = windows; @@ -4352,18 +4371,18 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov try current_thread.checkCancel(); // Deletion with posix semantics if the filesystem supports it. - var info: w.FILE_DISPOSITION_INFORMATION_EX = .{ - .Flags = w.FILE_DISPOSITION_DELETE | - w.FILE_DISPOSITION_POSIX_SEMANTICS | - w.FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE, - }; + const info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{ + .DELETE = true, + .POSIX_SEMANTICS = true, + .IGNORE_READONLY_ATTRIBUTE = true, + } }; rc = w.ntdll.NtSetInformationFile( tmp_handle, &io_status_block, &info, - @sizeOf(w.FILE_DISPOSITION_INFORMATION_EX), - .FileDispositionInformationEx, + @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX), + .DispositionEx, ); switch (rc) { .SUCCESS => return, @@ -4384,7 +4403,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov // Deletion with file pending semantics, which requires waiting or moving // files to get them removed (from here). - var file_dispo: w.FILE_DISPOSITION_INFORMATION = .{ + const file_dispo: w.FILE.DISPOSITION.INFORMATION = .{ .DeleteFile = w.TRUE, }; @@ -4392,8 +4411,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov tmp_handle, &io_status_block, &file_dispo, - @sizeOf(w.FILE_DISPOSITION_INFORMATION), - .FileDispositionInformation, + @sizeOf(w.FILE.DISPOSITION.INFORMATION), + .Disposition, ); } switch (rc) { @@ -4555,29 +4574,23 @@ fn dirRenameWindows( // The strategy here is just to try using FileRenameInformationEx and fall back to // FileRenameInformation if the return value lets us know that some aspect of it is not supported. const need_fallback = need_fallback: { - const struct_buf_len = @sizeOf(w.FILE_RENAME_INFORMATION_EX) + (w.PATH_MAX_WIDE * 2); - var rename_info_buf: [struct_buf_len]u8 align(@alignOf(w.FILE_RENAME_INFORMATION_EX)) = undefined; - const struct_len = @sizeOf(w.FILE_RENAME_INFORMATION_EX) + new_path_w.len * 2; - if (struct_len > struct_buf_len) return error.NameTooLong; - - const rename_info: *w.FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf); - var io_status_block: w.IO_STATUS_BLOCK = undefined; - - var flags: w.ULONG = w.FILE_RENAME_POSIX_SEMANTICS | w.FILE_RENAME_IGNORE_READONLY_ATTRIBUTE; - if (replace_if_exists) flags |= w.FILE_RENAME_REPLACE_IF_EXISTS; - rename_info.* = .{ - .Flags = flags, + const rename_info: w.FILE.RENAME_INFORMATION = .init(.{ + .Flags = .{ + .REPLACE_IF_EXISTS = replace_if_exists, + .POSIX_SEMANTICS = true, + .IGNORE_READONLY_ATTRIBUTE = true, + }, .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, - .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong - .FileName = undefined, - }; - @memcpy((&rename_info.FileName).ptr, new_path_w); + .FileName = new_path_w, + }); + var io_status_block: w.IO_STATUS_BLOCK = undefined; + const rename_info_buf = rename_info.toBuffer(); rc = w.ntdll.NtSetInformationFile( src_fd, &io_status_block, - rename_info, - @intCast(struct_len), // already checked for error.NameTooLong - .FileRenameInformationEx, + rename_info_buf.ptr, + @intCast(rename_info_buf.len), + .RenameEx, ); switch (rc) { .SUCCESS => return, @@ -4594,28 +4607,19 @@ fn dirRenameWindows( }; if (need_fallback) { - const struct_buf_len = @sizeOf(w.FILE_RENAME_INFORMATION) + (w.PATH_MAX_WIDE * 2); - var rename_info_buf: [struct_buf_len]u8 align(@alignOf(w.FILE_RENAME_INFORMATION)) = undefined; - const struct_len = @sizeOf(w.FILE_RENAME_INFORMATION) + new_path_w.len * 2; - if (struct_len > struct_buf_len) return error.NameTooLong; - - const rename_info: *w.FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf); - var io_status_block: w.IO_STATUS_BLOCK = undefined; - - rename_info.* = .{ - .Flags = @intFromBool(replace_if_exists), + const rename_info: w.FILE.RENAME_INFORMATION = .init(.{ + .Flags = .{ .REPLACE_IF_EXISTS = replace_if_exists }, .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, - .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong - .FileName = undefined, - }; - @memcpy((&rename_info.FileName).ptr, new_path_w); - + .FileName = new_path_w, + }); + var io_status_block: w.IO_STATUS_BLOCK = undefined; + const rename_info_buf = rename_info.toBuffer(); rc = w.ntdll.NtSetInformationFile( src_fd, &io_status_block, - rename_info, - @intCast(struct_len), // already checked for error.NameTooLong - .FileRenameInformation, + rename_info_buf.ptr, + @intCast(rename_info_buf.len), + .Rename, ); } @@ -4779,7 +4783,7 @@ fn dirSymLinkWindows( const sym_link_path_w = try w.sliceToPrefixedFileW(dir.handle, sym_link_path); const SYMLINK_DATA = extern struct { - ReparseTag: w.ULONG, + ReparseTag: w.IO_REPARSE_TAG, ReparseDataLength: w.USHORT, Reserved: w.USHORT, SubstituteNameOffset: w.USHORT, @@ -4794,7 +4798,7 @@ fn dirSymLinkWindows( .GENERIC = .{ .READ = true, .WRITE = true }, .STANDARD = .{ .SYNCHRONIZE = true }, }, - .dir = dir, + .dir = dir.handle, .creation = .CREATE, .filter = if (flags.is_directory) .dir_only else .non_directory_only, }) catch |err| switch (err) { @@ -4818,11 +4822,11 @@ fn dirSymLinkWindows( // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw var is_target_absolute = false; const final_target_path = target_path: { - if (w.hasCommonNtPrefix(u16, target_path)) { + if (w.hasCommonNtPrefix(u16, target_path_w.span())) { // Already an NT path, no need to do anything to it - break :target_path target_path; + break :target_path target_path_w.span(); } else { - switch (w.getWin32PathType(u16, target_path)) { + switch (w.getWin32PathType(u16, target_path_w.span())) { // Rooted paths need to avoid getting put through wToPrefixedFileW // (and they are treated as relative in this context) // Note: It seems that rooted paths in symbolic links are relative to @@ -4830,13 +4834,13 @@ fn dirSymLinkWindows( // So, if the symlink is on C:\ and the CWD is on D:\, // it will still resolve the path relative to the root of // the C:\ drive. - .rooted => break :target_path target_path, + .rooted => break :target_path target_path_w.span(), // Keep relative paths relative, but anything else needs to get NT-prefixed. - else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path)) - break :target_path target_path, + else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path_w.span())) + break :target_path target_path_w.span(), } } - var prefixed_target_path = try w.wToPrefixedFileW(dir, target_path); + var prefixed_target_path = try w.wToPrefixedFileW(dir.handle, target_path_w.span()); // We do this after prefixing to ensure that drive-relative paths are treated as absolute is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span()); break :target_path prefixed_target_path.span(); @@ -4848,7 +4852,7 @@ fn dirSymLinkWindows( const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2; const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path); const symlink_data = SYMLINK_DATA{ - .ReparseTag = w.IO_REPARSE_TAG_SYMLINK, + .ReparseTag = .SYMLINK, .ReparseDataLength = @intCast(buf_len - header_len), .Reserved = 0, .SubstituteNameOffset = @intCast(final_target_path.len * 2), @@ -4862,7 +4866,14 @@ fn dirSymLinkWindows( @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); - _ = try w.DeviceIoControl(symlink_handle, w.FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null); + _ = w.DeviceIoControl(symlink_handle, w.FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }) catch |err| switch (err) { + error.PipeClosing => unreachable, + error.PipeAlreadyConnected => unreachable, + error.PipeAlreadyListening => unreachable, + error.Pending => unreachable, + error.UnrecognizedVolume => unreachable, + else => |e| return e, + }; } fn dirSymLinkWasi( @@ -5503,16 +5514,22 @@ fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool { { try current_thread.checkCancel(); var io_status: windows.IO_STATUS_BLOCK = undefined; - var device_info: windows.FILE_FS_DEVICE_INFORMATION = undefined; - const rc = windows.ntdll.NtQueryVolumeInformationFile(handle, &io_status, &device_info, @sizeOf(windows.FILE_FS_DEVICE_INFORMATION), .FileFsDeviceInformation); + var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined; + const rc = windows.ntdll.NtQueryVolumeInformationFile( + handle, + &io_status, + &device_info, + @sizeOf(windows.FILE.FS_DEVICE_INFORMATION), + .Device, + ); switch (rc) { .SUCCESS => {}, else => return false, } - if (device_info.DeviceType != windows.FILE_DEVICE_NAMED_PIPE) return false; + if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false; } - const name_bytes_offset = @offsetOf(windows.FILE_NAME_INFO, "FileName"); + const name_bytes_offset = @offsetOf(windows.FILE.NAME_INFORMATION, "FileName"); // `NAME_MAX` UTF-16 code units (2 bytes each) // This buffer may not be long enough to handle *all* possible paths // (PATH_MAX_WIDE would be necessary for that), but because we only care @@ -5520,11 +5537,17 @@ fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool { // we can use this smaller buffer and just return false on any error from // NtQueryInformationFile. const num_name_bytes = windows.MAX_PATH * 2; - var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); + var name_info_bytes align(@alignOf(windows.FILE.NAME_INFORMATION)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); var io_status_block: windows.IO_STATUS_BLOCK = undefined; try current_thread.checkCancel(); - const rc = windows.ntdll.NtQueryInformationFile(handle, &io_status_block, &name_info_bytes, @intCast(name_info_bytes.len), .FileNameInformation); + const rc = windows.ntdll.NtQueryInformationFile( + handle, + &io_status_block, + &name_info_bytes, + @intCast(name_info_bytes.len), + .Name, + ); switch (rc) { .SUCCESS => {}, .INVALID_PARAMETER => unreachable, @@ -5551,7 +5574,7 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE try current_thread.checkCancel(); var io_status_block: windows.IO_STATUS_BLOCK = undefined; - var eof_info: windows.FILE_END_OF_FILE_INFORMATION = .{ + const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{ .EndOfFile = signed_len, }; @@ -5559,8 +5582,8 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE file.handle, &io_status_block, &eof_info, - @sizeOf(windows.FILE_END_OF_FILE_INFORMATION), - .FileEndOfFileInformation, + @sizeOf(windows.FILE.END_OF_FILE_INFORMATION), + .EndOfFile, ); switch (status) { .SUCCESS => return, @@ -5643,19 +5666,19 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi .windows => { try current_thread.checkCancel(); var io_status_block: windows.IO_STATUS_BLOCK = undefined; - var info: windows.FILE_BASIC_INFORMATION = .{ + const info: windows.FILE.BASIC_INFORMATION = .{ .CreationTime = 0, .LastAccessTime = 0, .LastWriteTime = 0, .ChangeTime = 0, - .FileAttributes = permissions.inner.attributes, + .FileAttributes = permissions.toAttributes(), }; const status = windows.ntdll.NtSetInformationFile( file.handle, &io_status_block, &info, - @sizeOf(windows.FILE_BASIC_INFORMATION), - .FileBasicInformation, + @sizeOf(windows.FILE.BASIC_INFORMATION), + .Basic, ); switch (status) { .SUCCESS => return, @@ -5813,8 +5836,8 @@ fn fileSetTimestamps( if (is_windows) { try current_thread.checkCancel(); - const atime_ft = windows.nanoSecondsToFileTime(last_accessed.toNanoseconds()); - const mtime_ft = windows.nanoSecondsToFileTime(last_modified.toNanoseconds()); + const atime_ft = windows.nanoSecondsToFileTime(last_accessed); + const mtime_ft = windows.nanoSecondsToFileTime(last_modified); // https://github.com/ziglang/zig/issues/1840 const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft); @@ -5958,7 +5981,24 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v if (is_windows) { const exclusive = switch (lock) { - .none => return, + .none => { + // To match the non-Windows behavior, unlock + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + const status = windows.ntdll.NtUnlockFile( + file.handle, + &io_status_block, + &windows_lock_range_off, + &windows_lock_range_len, + 0, + ); + switch (status) { + .SUCCESS => {}, + .RANGE_NOT_LOCKED => {}, + .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer + else => return windows.unexpectedStatus(status), + } + return; + }, .shared => false, .exclusive => true, }; @@ -6020,7 +6060,23 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro if (is_windows) { const exclusive = switch (lock) { - .none => return, + .none => { + // To match the non-Windows behavior, unlock + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + const status = windows.ntdll.NtUnlockFile( + file.handle, + &io_status_block, + &windows_lock_range_off, + &windows_lock_range_len, + 0, + ); + switch (status) { + .SUCCESS => return true, + .RANGE_NOT_LOCKED => return false, + .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer + else => return windows.unexpectedStatus(status), + } + }, .shared => false, .exclusive => true, }; @@ -6093,7 +6149,7 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void { &io_status_block, &windows_lock_range_off, &windows_lock_range_len, - null, + 0, ); if (is_debug) switch (status) { .SUCCESS => {}, diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 648f284131b1e1e35d7dfe761da9ad80e8a0af95..ec8b720bb1787930664143c893bb6e1437926591 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -326,7 +326,7 @@ test "sync" { test "fsync" { switch (native_os) { - .linux, .windows, .illumos => {}, + .linux, .illumos => {}, else => return error.SkipZigTest, } -- 2.54.0 From 018e34271fe58e540dc46e8bca529ce399625bef Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 10:07:56 -0800 Subject: [PATCH 097/195] std.fs.test: fix rebase conflicts --- lib/std/fs/test.zig | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index e253aeeb54657a571b276299f862c5f8288c8235..19df1189fb712b4716e8619b1d0f038943f23ba4 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -237,23 +237,24 @@ test "Dir.readLink" { test "Dir.readLink on non-symlinks" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { + const io = ctx.io; const file_path = try ctx.transformPath("file.txt"); - try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "nonsense" }); + try ctx.dir.writeFile(io, .{ .sub_path = file_path, .data = "nonsense" }); const dir_path = try ctx.transformPath("subdir"); - try ctx.dir.makeDir(dir_path); + try ctx.dir.makeDir(io, dir_path, .default_dir); // file - var buffer: [fs.max_path_bytes]u8 = undefined; - try std.testing.expectError(error.NotLink, ctx.dir.readLink(file_path, &buffer)); + var buffer: [Dir.max_path_bytes]u8 = undefined; + try std.testing.expectError(error.NotLink, ctx.dir.readLink(io, file_path, &buffer)); if (builtin.os.tag == .windows) { - var file_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.fd, file_path); + var file_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.handle, file_path); try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(file_path_w.span(), &file_path_w.data)); } // dir - try std.testing.expectError(error.NotLink, ctx.dir.readLink(dir_path, &buffer)); + try std.testing.expectError(error.NotLink, ctx.dir.readLink(io, dir_path, &buffer)); if (builtin.os.tag == .windows) { - var dir_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.fd, dir_path); + var dir_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.handle, dir_path); try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(dir_path_w.span(), &dir_path_w.data)); } } -- 2.54.0 From 002d444964869a5fd4d588d45c46f3acfa16cf5d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 11:26:06 -0800 Subject: [PATCH 098/195] std: fix Io.Dir.min_buffer_len on Linux --- lib/std/Io/Dir.zig | 7 ++++++- lib/std/Io/Threaded.zig | 17 ++++++++++++++--- lib/std/fs/test.zig | 8 ++++---- lib/std/os/linux.zig | 2 +- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 2de0fbe564986375f5740e6ea15956938d726249..330ac5bf62e19625d610156ccdea007cd3f40d2e 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -102,7 +102,12 @@ pub const Reader = struct { end: usize, /// A length for `buffer` that allows all implementations to function. - pub const min_buffer_len = std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)); + pub const min_buffer_len = switch (native_os) { + .linux => @sizeOf(std.os.linux.dirent64) + + std.mem.alignForward(usize, max_name_bytes, @alignOf(std.os.linux.dirent64)), + .windows => std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)), + else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)), + }; pub const State = enum { /// Indicates the next call to `read` should rewind and start over the diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 81c18a8c74c2a81a2688b0a8d8f10877cea7083a..3472b8fbe2c6b120390fc69c1a3b03f60bd3779d 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3399,7 +3399,10 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir dr.state = .finished; return 0; }, - .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net. + // This can occur when reading /proc/$PID/net, or + // if the provided buffer is too small. Neither + // scenario is intended to be handled by this API. + .INVAL => return error.Unexpected, .ACCES => return error.AccessDenied, // Lacking permission to iterate this directory. else => |err| return posix.unexpectedErrno(err), } @@ -3413,11 +3416,19 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir dr.index = 0; dr.end = n; } - const linux_entry: *align(1) linux.dirent64 = @ptrCast(&dr.buffer[dr.index]); + // Linux aligns the header by padding after the null byte of the name + // to align the next entry. This means we can find the end of the name + // by looking at only the 8 bytes before the next record. However since + // file names are usually short it's better to keep the machine code + // simpler. + const linux_entry: *linux.dirent64 = @ptrCast(@alignCast(&dr.buffer[dr.index])); const next_index = dr.index + linux_entry.reclen; dr.index = next_index; + const name_ptr: [*]u8 = &linux_entry.name; + const padded_name = name_ptr[0 .. linux_entry.reclen - @offsetOf(linux.dirent64, "name")]; + const name_len = std.mem.findScalar(u8, padded_name, 0).?; + const name = name_ptr[0..name_len :0]; - const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0); if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; const entry_kind: File.Kind = switch (linux_entry.type) { diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 19df1189fb712b4716e8619b1d0f038943f23ba4..d66868379d8d1f7d820c178de663e66b95bbd7de 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1418,12 +1418,12 @@ test "max file name component lengths" { // On WASI, the maxed filename depends on the host OS, so in order for this test to // work on any host, we need to use a length that will work for all platforms // (i.e. the minimum max_name_bytes of all supported platforms). - const maxed_wasi_filename1 = [_]u8{'1'} ** 255; - const maxed_wasi_filename2 = [_]u8{'2'} ** 255; + const maxed_wasi_filename1: [255]u8 = @splat('1'); + const maxed_wasi_filename2: [255]u8 = @splat('2'); try testFilenameLimits(io, tmp.dir, &maxed_wasi_filename1, &maxed_wasi_filename2); } else { - const maxed_ascii_filename1 = [_]u8{'1'} ** std.fs.max_name_bytes; - const maxed_ascii_filename2 = [_]u8{'2'} ** std.fs.max_name_bytes; + const maxed_ascii_filename1: [Dir.max_name_bytes]u8 = @splat('1'); + const maxed_ascii_filename2: [Dir.max_name_bytes]u8 = @splat('2'); try testFilenameLimits(io, tmp.dir, &maxed_ascii_filename1, &maxed_ascii_filename2); } } diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 1db462ad3e3d3703ad118e804bca5c3125490840..96b4c8ee6f259d6617c4caee74fb8acbaba77f4f 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -6040,7 +6040,7 @@ pub const dirent64 = extern struct { off: u64, reclen: u16, type: u8, - name: u8, // field address is the address of first byte of name https://github.com/ziglang/zig/issues/173 + name: [0]u8, }; pub const dl_phdr_info = extern struct { -- 2.54.0 From d060cb8cd640c05fbf07ab9452d52d67c0548e92 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 11:36:02 -0800 Subject: [PATCH 099/195] std: fix test "max file name component lengths" --- lib/std/fs/test.zig | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index d66868379d8d1f7d820c178de663e66b95bbd7de..db6a6cccee884ea7748e6cfaa9c5622691d0833b 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1376,24 +1376,46 @@ fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8, max var reader_buf: [Dir.Reader.min_buffer_len]u8 align(@alignOf(usize)) = undefined; var reader: Dir.Reader = .init(iterable_dir, &reader_buf); - var count: usize = 0; + var file_count: usize = 0; + var dir_count: usize = 0; while (try reader.next(io)) |entry| { - try expectEqualStrings(maxed_filename, entry.name); - count += 1; + switch (entry.kind) { + .file => { + try expectEqualStrings(maxed_filename, entry.name); + file_count += 1; + }, + .directory => { + try expectEqualStrings(maxed_dirname, entry.name); + dir_count += 1; + }, + else => return error.TestFailed, + } } - try expectEqual(@as(usize, 2), count); + try expectEqual(@as(usize, 1), file_count); + try expectEqual(@as(usize, 1), dir_count); } // High level walk API { var walker = try iterable_dir.walk(testing.allocator); defer walker.deinit(); - var count: usize = 0; + var file_count: usize = 0; + var dir_count: usize = 0; while (try walker.next(io)) |entry| { - try expectEqualStrings(maxed_filename, entry.basename); - count += 1; + switch (entry.kind) { + .file => { + try expectEqualStrings(maxed_filename, entry.basename); + file_count += 1; + }, + .directory => { + try expectEqualStrings(maxed_dirname, entry.basename); + dir_count += 1; + }, + else => return error.TestFailed, + } } - try expectEqual(@as(usize, 3), count); + try expectEqual(@as(usize, 2), file_count); + try expectEqual(@as(usize, 1), dir_count); } // ensure that we can delete the tree -- 2.54.0 From ac11b8273802a3371ff2b0727d4c76489ee2f8bc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 12:08:27 -0800 Subject: [PATCH 100/195] std.Io.Threaded: fix splat buffer calculations --- lib/std/Io/Threaded.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 3472b8fbe2c6b120390fc69c1a3b03f60bd3779d..424869f3d4c98bf2109da103b4cfbe4ca11568c2 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7112,7 +7112,7 @@ fn fileWritePositional( addBuf(&iovecs, &iovlen, splat_buffer); remaining_splat -= splat_buffer.len; } - addBuf(&iovecs, &iovlen, splat_buffer[0..remaining_splat]); + addBuf(&iovecs, &iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); }, else => for (0..@min(splat, iovecs.len - iovlen)) |_| { addBuf(&iovecs, &iovlen, pattern); @@ -7234,7 +7234,7 @@ fn fileWriteStreaming( addBuf(&iovecs, &iovlen, splat_buffer); remaining_splat -= splat_buffer.len; } - addBuf(&iovecs, &iovlen, splat_buffer[0..remaining_splat]); + addBuf(&iovecs, &iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); }, else => for (0..@min(splat, iovecs.len - iovlen)) |_| { addBuf(&iovecs, &iovlen, pattern); @@ -9860,7 +9860,7 @@ fn netWritePosix( addBuf(&iovecs, &msg.iovlen, splat_buffer); remaining_splat -= splat_buffer.len; } - addBuf(&iovecs, &msg.iovlen, splat_buffer[0..remaining_splat]); + addBuf(&iovecs, &msg.iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); }, else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| { addBuf(&iovecs, &msg.iovlen, pattern); @@ -9945,7 +9945,7 @@ fn netWriteWindows( addWsaBuf(&iovecs, &len, splat_buffer); remaining_splat -= splat_buffer.len; } - addWsaBuf(&iovecs, &len, splat_buffer[0..remaining_splat]); + addWsaBuf(&iovecs, &len, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); }, else => for (0..@min(splat, iovecs.len - len)) |_| { addWsaBuf(&iovecs, &len, pattern); -- 2.54.0 From 1381f9f612a5e03895615ade253411d86bce31a4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 12:10:21 -0800 Subject: [PATCH 101/195] std.debug: fix printLineFromFile by using streamDelimiter and discardDelimiter functions that don't depend on the buffer size being large enough --- lib/std/debug.zig | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index cef42334957daf668cab93c49553662817937c32..26631db5012d9e7b56688768671af46dc891165c 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1203,23 +1203,21 @@ fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) ! const cwd: Io.Dir = .cwd(); var file = try cwd.openFile(io, source_location.file_name, .{}); defer file.close(io); - // TODO fstat and make sure that the file has the correct size var buffer: [4096]u8 = undefined; var file_reader: File.Reader = .init(file, io, &buffer); var line_index: usize = 0; - while (try file_reader.interface.takeDelimiter('\n')) |line| { + const r = &file_reader.interface; + while (true) { line_index += 1; if (line_index == source_location.line) { // TODO delete hard tabs from the language - mem.replaceScalar(u8, line, '\t', ' '); - try writer.writeAll(line); - // Make sure printing last line of file inserts extra newline. + _ = try r.streamDelimiterEnding(writer, '\n'); try writer.writeByte('\n'); return; } + _ = try r.discardDelimiterInclusive('\n'); } - return error.EndOfStream; } test printLineFromFile { -- 2.54.0 From 405db921dc0a053d7bebca6195c5a0031a5b187b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 12:55:00 -0800 Subject: [PATCH 102/195] std: fix compilation targeting WASI --- lib/std/Io/File.zig | 2 +- lib/std/Io/Threaded.zig | 99 ++++++++++++++++++++++++++++++----------- lib/std/Progress.zig | 4 +- lib/std/c.zig | 5 ++- lib/std/process.zig | 4 +- 5 files changed, 81 insertions(+), 33 deletions(-) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index dc64a4d2c3b642831012a578d0a623836a5bffa4..ae6c69285fdda140e3eeb7f74fa08ef3270eb5ac 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -429,7 +429,7 @@ pub const Permissions = std.options.FilePermissions orelse if (is_windows) enum( executable_file = 0o777, _, - pub const has_executable_bit = true; + pub const has_executable_bit = native_os != .wasi; pub fn toMode(self: @This()) std.posix.mode_t { return @intFromEnum(self); diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 424869f3d4c98bf2109da103b4cfbe4ca11568c2..37136879a380453f2b2639bb92a294691a6a9dbb 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -947,6 +947,16 @@ const have_fchmodat2 = native_os == .linux and const have_fchmodat_flags = native_os != .linux or (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); +const have_fchown = switch (native_os) { + .wasi, .windows => false, + else => true, +}; + +const have_fchmod = switch (native_os) { + .wasi, .windows => false, + else => true, +}; + const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat; @@ -1480,6 +1490,7 @@ fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) } fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { + if (builtin.single_threaded) unreachable; // Nothing to wake up. const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; switch (native_os) { @@ -4190,7 +4201,6 @@ fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir. .NOTDIR => return error.NotDir, .NOMEM => return error.SystemResources, .ROFS => return error.ReadOnlyFileSystem, - .NOTEMPTY => return error.DirNotEmpty, .NOTCAPABLE => return error.AccessDenied, .ILSEQ => return error.BadPathName, .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component @@ -4464,7 +4474,6 @@ fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.D .BUSY => return error.FileBusy, .FAULT => |err| return errnoBug(err), .IO => return error.FileSystem, - .ISDIR => return error.IsDir, .LOOP => return error.SymLinkLoop, .NAMETOOLONG => return error.NameTooLong, .NOENT => return error.FileNotFound, @@ -4877,14 +4886,14 @@ fn dirSymLinkWindows( @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); - _ = w.DeviceIoControl(symlink_handle, w.FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }) catch |err| switch (err) { - error.PipeClosing => unreachable, - error.PipeAlreadyConnected => unreachable, - error.PipeAlreadyListening => unreachable, - error.Pending => unreachable, - error.UnrecognizedVolume => unreachable, - else => |e| return e, - }; + const rc = w.DeviceIoControl(symlink_handle, w.FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }); + switch (rc) { + .SUCCESS => {}, + .PRIVILEGE_NOT_HELD => return error.PermissionDenied, + .ACCESS_DENIED => return error.AccessDenied, + .INVALID_DEVICE_REQUEST => return error.FileSystem, + else => return windows.unexpectedStatus(rc), + } } fn dirSymLinkWasi( @@ -4894,7 +4903,7 @@ fn dirSymLinkWasi( sym_link_path: []const u8, flags: Dir.SymLinkFlags, ) Dir.SymLinkError!void { - if (builtin.link_libc) return dirSymLinkPosix(dir, target_path, sym_link_path, flags); + if (builtin.link_libc) return dirSymLinkPosix(userdata, dir, target_path, sym_link_path, flags); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5021,7 +5030,7 @@ fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer switch (std.os.wasi.path_readlink(dir.handle, sub_path.ptr, sub_path.len, buffer.ptr, buffer.len, &n)) { .SUCCESS => { current_thread.endSyscall(); - return buffer[0..n]; + return n; }, .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { @@ -5104,6 +5113,7 @@ fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Pe } fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { + if (@sizeOf(Dir.Permissions) == 0) return; const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); return setPermissionsPosix(current_thread, dir.handle, permissions.toMode()); @@ -5116,7 +5126,7 @@ fn dirSetFilePermissions( permissions: Dir.Permissions, options: Dir.SetFilePermissionsOptions, ) Dir.SetFilePermissionsError!void { - if (!Dir.Permissions.has_executable_bit) return error.Unexpected; + if (@sizeOf(Dir.Permissions) == 0) return; if (is_windows) @panic("TODO"); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5250,14 +5260,16 @@ fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, gro } fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void { + if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function. const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const uid = owner orelse ~@as(posix.uid_t, 0); const gid = group orelse ~@as(posix.gid_t, 0); - return setOwnerPosix(current_thread, dir.handle, uid, gid); + return posixFchown(current_thread, dir.handle, uid, gid); } -fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void { +fn posixFchown(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void { + comptime assert(have_fchown); try current_thread.beginSyscall(); while (true) { switch (posix.errno(posix.system.fchown(fd, uid, gid))) { @@ -5296,13 +5308,10 @@ fn dirSetFileOwner( group: ?File.Gid, options: Dir.SetFileOwnerOptions, ) Dir.SetFileOwnerError!void { + if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function. const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); - if (is_windows) { - @panic("TODO"); - } - var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); @@ -5317,6 +5326,7 @@ fn dirSetFileOwner( const fileSync = switch (native_os) { .windows => fileSyncWindows, + .wasi => fileSyncWasi, else => fileSyncPosix, }; @@ -5366,6 +5376,34 @@ fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void { } } +fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); + while (true) { + switch (std.os.wasi.fd_sync(file.handle)) { + .SUCCESS => return current_thread.endSyscall(), + .CANCELED => return current_thread.endSyscallCanceled(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ROFS => |err| return errnoBug(err), + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .DQUOT => return error.DiskQuota, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5659,18 +5697,16 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE } fn fileSetOwner(userdata: ?*anyopaque, file: File, owner: ?File.Uid, group: ?File.Gid) File.SetOwnerError!void { - switch (native_os) { - .windows, .wasi => return error.Unexpected, - else => {}, - } + if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function. const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const uid = owner orelse ~@as(posix.uid_t, 0); const gid = group orelse ~@as(posix.gid_t, 0); - return setOwnerPosix(current_thread, file.handle, uid, gid); + return posixFchown(current_thread, file.handle, uid, gid); } fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permissions) File.SetPermissionsError!void { + if (@sizeOf(File.Permissions) == 0) return; const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); switch (native_os) { @@ -5704,6 +5740,7 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi } fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode_t) File.SetPermissionsError!void { + comptime assert(have_fchmod); try current_thread.beginSyscall(); while (true) { switch (posix.errno(posix.system.fchmod(fd, mode))) { @@ -5987,6 +6024,7 @@ const windows_lock_range_off: windows.LARGE_INTEGER = 0; const windows_lock_range_len: windows.LARGE_INTEGER = 1; fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void { + if (native_os == .wasi) return error.FileLocksUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -6066,6 +6104,7 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v } fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool { + if (native_os == .wasi) return error.FileLocksUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -6150,6 +6189,7 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro } fn fileUnlock(userdata: ?*anyopaque, file: File) void { + if (native_os == .wasi) return; const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; @@ -6186,6 +6226,7 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void { } fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void { + if (native_os == .wasi) return; const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -7072,7 +7113,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); return end_index; }, - else => @compileError("unsupported OS"), + else => return error.OperationUnsupported, } } @@ -10034,7 +10075,10 @@ fn netWriteUnavailable( /// This is either usize or u32. Since, either is fine, let's use the same /// `addBuf` function for both writing to a file and sending network messages. -const iovlen_t = @FieldType(posix.msghdr_const, "iovlen"); +const iovlen_t = switch (native_os) { + .wasi => u32, + else => @FieldType(posix.msghdr_const, "iovlen"), +}; fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void { // OS checks ptr addr before length so zero length vectors must be omitted. @@ -10775,8 +10819,9 @@ fn statFromPosix(st: *const posix.Stat) File.Stat { fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat { return .{ .inode = st.ino, + .nlink = st.nlink, .size = @bitCast(st.size), - .mode = 0, + .permissions = .default_file, .kind = switch (st.filetype) { .BLOCK_DEVICE => .block_device, .CHARACTER_DEVICE => .character_device, diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 8bd120dbd1f23899a4707eebc2ac2225543d29cb..970cf903829c9aed701e1a7e5f23680adf1c622f 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -498,8 +498,8 @@ pub fn start(io: Io, options: Options) Node { if (stderr.enableAnsiEscapeCodes(io)) |_| { global_progress.terminal_mode = .ansi_escape_codes; } else |_| if (is_windows) { - if (stderr.isTty(io)) { - global_progress.terminal_mode = TerminalMode{ .windows_api = .{ + if (stderr.isTty(io)) |is_tty| { + if (is_tty) global_progress.terminal_mode = TerminalMode{ .windows_api = .{ .code_page = windows.kernel32.GetConsoleOutputCP(), } }; } else |err| switch (err) { diff --git a/lib/std/c.zig b/lib/std/c.zig index 0627882e3c37a5d4d7f7c85de1753931ce315eff..703dda810e81ffc9baaef257c24c3974de7c2a86 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -148,9 +148,10 @@ pub const dev_t = switch (native_os) { pub const mode_t = switch (native_os) { .linux => linux.mode_t, .emscripten => emscripten.mode_t, - .openbsd, .haiku, .netbsd, .illumos, .wasi, .windows => u32, + .openbsd, .haiku, .netbsd, .illumos, .windows => u32, // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L44 .freebsd, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .dragonfly, .serenity => u16, + .wasi => if (builtin.link_libc) u32 else u0, // WASI libc emulates mode. else => u0, }; @@ -10608,7 +10609,7 @@ pub extern "c" fn munmap(addr: *align(page_size) const anyopaque, len: usize) c_ pub extern "c" fn mremap(addr: ?*align(page_size) const anyopaque, old_len: usize, new_len: usize, flags: MREMAP, ...) *anyopaque; pub extern "c" fn mprotect(addr: *align(page_size) anyopaque, len: usize, prot: c_uint) c_int; pub extern "c" fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) c_int; -pub extern "c" fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: c_int) c_int; +pub extern "c" fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: c_uint) c_int; pub extern "c" fn unlink(path: [*:0]const u8) c_int; pub extern "c" fn unlinkat(dirfd: fd_t, path: [*:0]const u8, flags: c_uint) c_int; pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8; diff --git a/lib/std/process.zig b/lib/std/process.zig index d6d671e251eb3189ab616c027a2854cac7837308..07d2b7117fc7f6d075fed1230e0d16b863a52913 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -2121,7 +2121,9 @@ pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn { pub const ExecutablePathBaseError = error{ FileNotFound, AccessDenied, - NotSupported, + /// The operating system does not support an executable learning its own + /// path. + OperationUnsupported, NotDir, SymLinkLoop, InputOutput, -- 2.54.0 From 3e624e17a40fd65b68dbec45030280008c6b4018 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 14:14:21 -0800 Subject: [PATCH 103/195] std: fix compilation errors on FreeBSD --- lib/std/Io/File/Writer.zig | 2 ++ lib/std/Io/Threaded.zig | 45 ++++++++++++++++++++++++++++---------- lib/std/c.zig | 1 + lib/std/process.zig | 5 ++++- 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 3487416719a59d1f87fb90a48043ca52804201f9..226b60993f954b7bce70d41c501d35795daacbcf 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -49,6 +49,8 @@ pub const Error = error{ pub const WriteFileError = Error || error{ /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd. Unimplemented, + /// Can happen on FreeBSD when using copy_file_range. + CorruptedData, EndOfStream, ReadFailed, }; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 37136879a380453f2b2639bb92a294691a6a9dbb..7dd9ef51009f8bfd998cf62afb09edf68d659d76 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -366,12 +366,12 @@ const Thread = struct { fn futexWake(ptr: *const u32, max_waiters: u32) void { @branchHint(.cold); + assert(max_waiters != 0); if (builtin.single_threaded) return; // nothing to wake up if (builtin.cpu.arch.isWasm()) { comptime assert(builtin.cpu.has(.wasm, .atomics)); - assert(max_waiters != 0); const woken_count = asm volatile ( \\local.get %[ptr] \\local.get %[waiters] @@ -416,7 +416,6 @@ const Thread = struct { } }, .windows => { - assert(max_waiters != 0); switch (max_waiters) { 1 => windows.ntdll.RtlWakeAddressSingle(ptr), else => windows.ntdll.RtlWakeAddressAll(ptr), @@ -953,7 +952,8 @@ const have_fchown = switch (native_os) { }; const have_fchmod = switch (native_os) { - .wasi, .windows => false, + .windows => false, + .wasi => builtin.link_libc, else => true, }; @@ -6772,7 +6772,7 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi var result: u64 = undefined; try current_thread.beginSyscall(); while (true) { - switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.CUR))) { + switch (posix.errno(posix.system.llseek(fd, @bitCast(offset), &result, posix.SEEK.CUR))) { .SUCCESS => { current_thread.endSyscall(); return; @@ -7000,7 +7000,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex if (rc != 0) return error.NameTooLong; var real_path_buf: [posix.PATH_MAX]u8 = undefined; - const n = Io.Dir.realPathAbsolute(ioBasic(t), &symlink_path_buf, &real_path_buf) catch |err| switch (err) { + const n = Io.Dir.realPathFileAbsolute(ioBasic(t), &symlink_path_buf, &real_path_buf) catch |err| switch (err) { error.NetworkNotFound => unreachable, // Windows-only else => |e| return e, }; @@ -7020,11 +7020,32 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex }, .freebsd, .dragonfly => { const current_thread = Thread.getCurrent(t); - try current_thread.checkCancel(); var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 }; var out_len: usize = out_buffer.len; - try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); - return out_len; + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { + .SUCCESS => { + current_thread.endSyscall(); + return out_len; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .FAULT => |err| return errnoBug(err), + .PERM => return error.PermissionDenied, + .NOMEM => return error.SystemResources, + .NOENT => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } }, .netbsd => { const current_thread = Thread.getCurrent(t); @@ -7043,7 +7064,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex if (std.mem.indexOf(u8, argv0, "/") != null) { // argv[0] is a path (relative or absolute): use realpath(3) directly var real_path_buf: [posix.PATH_MAX]u8 = undefined; - const real_path = Io.Dir.realPathAbsolute(ioBasic(t), std.os.argv[0], &real_path_buf) catch |err| switch (err) { + const real_path = Io.Dir.realPathFileAbsolute(ioBasic(t), std.os.argv[0], &real_path_buf) catch |err| switch (err) { error.NetworkNotFound => unreachable, // Windows-only else => |e| return e, }; @@ -7063,7 +7084,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex }, 0) catch continue; var real_path_buf: [posix.PATH_MAX]u8 = undefined; - if (Io.Dir.realPathAbsolute(ioBasic(t), resolved_path, &real_path_buf)) |real_path| { + if (Io.Dir.realPathFileAbsolute(ioBasic(t), resolved_path, &real_path_buf)) |real_path| { // found a file, and hope it is the right file if (real_path.len > out_buffer.len) return error.NameTooLong; @@ -7730,8 +7751,8 @@ fn fileWriteFileStreaming( .FBIG => return error.FileTooBig, .IO => return error.InputOutput, .INTEGRITY => return error.CorruptedData, - .ISDIR => return error.IsDir, .NOSPC => return error.NoSpaceLeft, + .ISDIR => |err| errnoBug(err), .BADF => |err| errnoBug(err), else => |err| posix.unexpectedErrno(err), }); @@ -7908,11 +7929,11 @@ fn fileWriteFilePositional( .FBIG => return error.FileTooBig, .IO => return error.InputOutput, .INTEGRITY => return error.CorruptedData, - .ISDIR => return error.IsDir, .NOSPC => return error.NoSpaceLeft, .OVERFLOW => return error.Unseekable, .NXIO => return error.Unseekable, .SPIPE => return error.Unseekable, + .ISDIR => |err| errnoBug(err), .BADF => |err| errnoBug(err), else => |err| posix.unexpectedErrno(err), }); diff --git a/lib/std/c.zig b/lib/std/c.zig index 703dda810e81ffc9baaef257c24c3974de7c2a86..25e52bac0c09e09dbfdd8069a294933742a6b420 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -163,6 +163,7 @@ pub const nlink_t = switch (native_os) { .freebsd, .serenity => u64, .openbsd, .netbsd, .illumos => u32, .haiku => i32, + .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => u16, else => u0, }; diff --git a/lib/std/process.zig b/lib/std/process.zig index 07d2b7117fc7f6d075fed1230e0d16b863a52913..cf751f7a282a763561c3369700bb6a48d877e587 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -2287,7 +2287,10 @@ pub fn exit(status: u8) noreturn { } else switch (native_os) { .windows => windows.ntdll.RtlExitUserProcess(status), .wasi => std.os.wasi.proc_exit(status), - .linux => if (!builtin.single_threaded) std.os.linux.exit_group(status), + .linux => { + if (!builtin.single_threaded) std.os.linux.exit_group(status); + posix.system.exit(status); + }, .uefi => { const uefi = std.os.uefi; // exit() is only available if exitBootServices() has not been called yet. -- 2.54.0 From 6aa31cedb7b5461c06d43b813d70ab6c21375cce Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 14:50:04 -0800 Subject: [PATCH 104/195] std.Io.Dir.Reader: fix min_buffer_len on 32-bit linux dirents64 still uses 8 byte alignment on 32-bit systems --- lib/std/Io/Dir.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 330ac5bf62e19625d610156ccdea007cd3f40d2e..238f25eb6bc8f7974deba9f246e80a2ff252c60a 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -103,8 +103,8 @@ pub const Reader = struct { /// A length for `buffer` that allows all implementations to function. pub const min_buffer_len = switch (native_os) { - .linux => @sizeOf(std.os.linux.dirent64) + - std.mem.alignForward(usize, max_name_bytes, @alignOf(std.os.linux.dirent64)), + .linux => std.mem.alignForward(usize, @sizeOf(std.os.linux.dirent64), 8) + + std.mem.alignForward(usize, max_name_bytes, 8), .windows => std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)), else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)), }; -- 2.54.0 From 4025af9c05f7392acb96b6085c541c6887e8b8fe Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 15:35:26 -0800 Subject: [PATCH 105/195] std.Io.Threaded: fix fchmodat on linux with libc --- lib/std/Io/Threaded.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 7dd9ef51009f8bfd998cf62afb09edf68d659d76..9e9027ee10de1ee1b616db78a29b16507b76c389 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -5153,7 +5153,7 @@ fn posixFchmodat( if (have_fchmodat_flags or flags == 0) { try current_thread.beginSyscall(); while (true) { - const rc = if (have_fchmodat_flags) + const rc = if (have_fchmodat_flags or builtin.link_libc) posix.system.fchmodat(dir_fd, path, mode, flags) else posix.system.fchmodat(dir_fd, path, mode); -- 2.54.0 From 88110139fefb844ff774ba71614139286cf8da99 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 15:57:53 -0800 Subject: [PATCH 106/195] std.Io.Threaded: fix NetBSD compilation --- lib/std/Io/Threaded.zig | 467 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 425 insertions(+), 42 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 9e9027ee10de1ee1b616db78a29b16507b76c389..55c0ad4265bfd86bf050b5b977ffa18ab1347465 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -360,7 +360,11 @@ const Thread = struct { else => unreachable, }; }, - else => @compileError("unimplemented: futexWait"), + else => if (std.Thread.use_pthreads) { + return pthreads_futex.wait(ptr, expect, timeout_ns); + } else { + @compileError("unimplemented: futexWait"); + }, } } @@ -436,7 +440,11 @@ const Thread = struct { else => unreachable, // deadlock due to operating system bug } }, - else => @compileError("unimplemented: futexWake"), + else => if (std.Thread.use_pthreads) { + return pthreads_futex.wake(ptr, max_waiters); + } else { + @compileError("unimplemented: futexWake"); + }, } } }; @@ -4025,9 +4033,7 @@ fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.R fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize { switch (native_os) { - .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { - // On macOS, we can use F.GETPATH fcntl command to query the OS for - // the path to the file descriptor. + .netbsd, .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { var sufficient_buffer: [posix.PATH_MAX]u8 = undefined; @memset(&sufficient_buffer, 0); try current_thread.beginSyscall(); @@ -4045,9 +4051,12 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File else => |e| { current_thread.endSyscall(); switch (e) { + .ACCES => return error.AccessDenied, .BADF => return error.FileNotFound, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, .NOSPC => return error.NameTooLong, - .NOENT => return error.FileNotFound, + .RANGE => return error.NameTooLong, else => |err| return posix.unexpectedErrno(err), } }, @@ -4095,11 +4104,11 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File } }, .freebsd => { - var kfile: std.c.kinfo_file = undefined; - kfile.structsize = std.c.KINFO_FILE_SIZE; + var k_file: std.c.kinfo_file = undefined; + k_file.structsize = std.c.KINFO_FILE_SIZE; try current_thread.beginSyscall(); while (true) { - switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) { + switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&k_file)))) { .SUCCESS => { current_thread.endSyscall(); break; @@ -4118,39 +4127,10 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File }, } } - const len = std.mem.indexOfScalar(u8, &kfile.path, 0) orelse kfile.path.len; + const len = std.mem.indexOfScalar(u8, &k_file.path, 0) orelse k_file.path.len; if (len == 0) return error.NameTooLong; return len; }, - .netbsd, .dragonfly => { - @memset(out_buffer[0..Dir.max_path_bytes], 0); - try current_thread.beginSyscall(); - while (true) { - switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) { - .SUCCESS => { - current_thread.endSyscall(); - break; - }, - .INTR => { - try current_thread.checkCancel(); - continue; - }, - .CANCELED => return current_thread.endSyscallCanceled(), - else => |e| { - current_thread.endSyscall(); - switch (e) { - .ACCES => return error.AccessDenied, - .BADF => return error.FileNotFound, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .RANGE => return error.NameTooLong, - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } - return std.mem.indexOfScalar(u8, &out_buffer, 0) orelse out_buffer.len; - }, else => return error.OperationUnsupported, } comptime unreachable; @@ -7049,11 +7029,32 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex }, .netbsd => { const current_thread = Thread.getCurrent(t); - try current_thread.checkCancel(); var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME }; var out_len: usize = out_buffer.len; - try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); - return out_len; + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { + .SUCCESS => { + current_thread.endSyscall(); + return out_len; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .FAULT => |err| return errnoBug(err), + .PERM => return error.PermissionDenied, + .NOMEM => return error.SystemResources, + .NOENT => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } }, .openbsd, .haiku => { // OpenBSD doesn't support getting the path of a running process, so try to guess it @@ -11320,6 +11321,388 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {} +const pthreads_futex = struct { + const c = std.c; + const atomic = std.atomic; + + const Event = struct { + cond: c.pthread_cond_t, + mutex: c.pthread_mutex_t, + state: enum { empty, waiting, notified }, + + fn init(self: *Event) void { + // Use static init instead of pthread_cond/mutex_init() since this is generally faster. + self.cond = .{}; + self.mutex = .{}; + self.state = .empty; + } + + fn deinit(self: *Event) void { + // Some platforms reportedly give EINVAL for statically initialized pthread types. + const rc = c.pthread_cond_destroy(&self.cond); + assert(rc == .SUCCESS or rc == .INVAL); + + const rm = c.pthread_mutex_destroy(&self.mutex); + assert(rm == .SUCCESS or rm == .INVAL); + + self.* = undefined; + } + + fn wait(self: *Event, timeout: ?u64) error{Timeout}!void { + assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS); + defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS); + + // Early return if the event was already set. + if (self.state == .notified) { + return; + } + + // Compute the absolute timeout if one was specified. + // POSIX requires that REALTIME is used by default for the pthread timedwait functions. + // This can be changed with pthread_condattr_setclock, but it's an extension and may not be available everywhere. + var ts: c.timespec = undefined; + if (timeout) |timeout_ns| { + ts = std.posix.clock_gettime(c.CLOCK.REALTIME) catch unreachable; + ts.sec +|= @as(@TypeOf(ts.sec), @intCast(timeout_ns / std.time.ns_per_s)); + ts.nsec += @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s)); + + if (ts.nsec >= std.time.ns_per_s) { + ts.sec +|= 1; + ts.nsec -= std.time.ns_per_s; + } + } + + // Start waiting on the event - there can be only one thread waiting. + assert(self.state == .empty); + self.state = .waiting; + + while (true) { + // Block using either pthread_cond_wait or pthread_cond_timewait if there's an absolute timeout. + const rc = blk: { + if (timeout == null) break :blk c.pthread_cond_wait(&self.cond, &self.mutex); + break :blk c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts); + }; + + // After waking up, check if the event was set. + if (self.state == .notified) { + return; + } + + assert(self.state == .waiting); + switch (rc) { + .SUCCESS => {}, + .TIMEDOUT => { + // If timed out, reset the event to avoid the set() thread doing an unnecessary signal(). + self.state = .empty; + return error.Timeout; + }, + .INVAL => unreachable, // cond, mutex, and potentially ts should all be valid + .PERM => unreachable, // mutex is locked when cond_*wait() functions are called + else => unreachable, + } + } + } + + fn set(self: *Event) void { + assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS); + defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS); + + // Make sure that multiple calls to set() were not done on the same Event. + const old_state = self.state; + assert(old_state != .notified); + + // Mark the event as set and wake up the waiting thread if there was one. + // This must be done while the mutex as the wait() thread could deallocate + // the condition variable once it observes the new state, potentially causing a UAF if done unlocked. + self.state = .notified; + if (old_state == .waiting) { + assert(c.pthread_cond_signal(&self.cond) == .SUCCESS); + } + } + }; + + const Treap = std.Treap(usize, std.math.order); + const Waiter = struct { + node: Treap.Node, + prev: ?*Waiter, + next: ?*Waiter, + tail: ?*Waiter, + is_queued: bool, + event: Event, + }; + + // An unordered set of Waiters + const WaitList = struct { + top: ?*Waiter = null, + len: usize = 0, + + fn push(self: *WaitList, waiter: *Waiter) void { + waiter.next = self.top; + self.top = waiter; + self.len += 1; + } + + fn pop(self: *WaitList) ?*Waiter { + const waiter = self.top orelse return null; + self.top = waiter.next; + self.len -= 1; + return waiter; + } + }; + + const WaitQueue = struct { + fn insert(treap: *Treap, address: usize, waiter: *Waiter) void { + // prepare the waiter to be inserted. + waiter.next = null; + waiter.is_queued = true; + + // Find the wait queue entry associated with the address. + // If there isn't a wait queue on the address, this waiter creates the queue. + var entry = treap.getEntryFor(address); + const entry_node = entry.node orelse { + waiter.prev = null; + waiter.tail = waiter; + entry.set(&waiter.node); + return; + }; + + // There's a wait queue on the address; get the queue head and tail. + const head: *Waiter = @fieldParentPtr("node", entry_node); + const tail = head.tail orelse unreachable; + + // Push the waiter to the tail by replacing it and linking to the previous tail. + head.tail = waiter; + tail.next = waiter; + waiter.prev = tail; + } + + fn remove(treap: *Treap, address: usize, max_waiters: usize) WaitList { + // Find the wait queue associated with this address and get the head/tail if any. + var entry = treap.getEntryFor(address); + var queue_head: ?*Waiter = if (entry.node) |node| @fieldParentPtr("node", node) else null; + const queue_tail = if (queue_head) |head| head.tail else null; + + // Once we're done updating the head, fix it's tail pointer and update the treap's queue head as well. + defer entry.set(blk: { + const new_head = queue_head orelse break :blk null; + new_head.tail = queue_tail; + break :blk &new_head.node; + }); + + var removed = WaitList{}; + while (removed.len < max_waiters) { + // dequeue and collect waiters from their wait queue. + const waiter = queue_head orelse break; + queue_head = waiter.next; + removed.push(waiter); + + // When dequeueing, we must mark is_queued as false. + // This ensures that a waiter which calls tryRemove() returns false. + assert(waiter.is_queued); + waiter.is_queued = false; + } + + return removed; + } + + fn tryRemove(treap: *Treap, address: usize, waiter: *Waiter) bool { + if (!waiter.is_queued) { + return false; + } + + queue_remove: { + // Find the wait queue associated with the address. + var entry = blk: { + // A waiter without a previous link means it's the queue head that's in the treap so we can avoid lookup. + if (waiter.prev == null) { + assert(waiter.node.key == address); + break :blk treap.getEntryForExisting(&waiter.node); + } + break :blk treap.getEntryFor(address); + }; + + // The queue head and tail must exist if we're removing a queued waiter. + const head: *Waiter = @fieldParentPtr("node", entry.node orelse unreachable); + const tail = head.tail orelse unreachable; + + // A waiter with a previous link is never the head of the queue. + if (waiter.prev) |prev| { + assert(waiter != head); + prev.next = waiter.next; + + // A waiter with both a previous and next link is in the middle. + // We only need to update the surrounding waiter's links to remove it. + if (waiter.next) |next| { + assert(waiter != tail); + next.prev = waiter.prev; + break :queue_remove; + } + + // A waiter with a previous but no next link means it's the tail of the queue. + // In that case, we need to update the head's tail reference. + assert(waiter == tail); + head.tail = waiter.prev; + break :queue_remove; + } + + // A waiter with no previous link means it's the queue head of queue. + // We must replace (or remove) the head waiter reference in the treap. + assert(waiter == head); + entry.set(blk: { + const new_head = waiter.next orelse break :blk null; + new_head.tail = head.tail; + break :blk &new_head.node; + }); + } + + // Mark the waiter as successfully removed. + waiter.is_queued = false; + return true; + } + }; + + const Bucket = struct { + mutex: c.pthread_mutex_t align(atomic.cache_line) = .{}, + pending: atomic.Value(usize) = atomic.Value(usize).init(0), + treap: Treap = .{}, + + // Global array of buckets that addresses map to. + // Bucket array size is pretty much arbitrary here, but it must be a power of two for fibonacci hashing. + var buckets = [_]Bucket{.{}} ** @bitSizeOf(usize); + + // https://github.com/Amanieu/parking_lot/blob/1cf12744d097233316afa6c8b7d37389e4211756/core/src/parking_lot.rs#L343-L353 + fn from(address: usize) *Bucket { + // The upper `@bitSizeOf(usize)` bits of the fibonacci golden ratio. + // Hashing this via (h * k) >> (64 - b) where k=golden-ration and b=bitsize-of-array + // evenly lays out h=hash values over the bit range even when the hash has poor entropy (identity-hash for pointers). + const max_multiplier_bits = @bitSizeOf(usize); + const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - max_multiplier_bits); + + const max_bucket_bits = @ctz(buckets.len); + comptime assert(std.math.isPowerOfTwo(buckets.len)); + + const index = (address *% fibonacci_multiplier) >> (max_multiplier_bits - max_bucket_bits); + return &buckets[index]; + } + }; + + const Address = struct { + fn from(ptr: *const u32) usize { + // Get the alignment of the pointer. + const alignment = @alignOf(atomic.Value(u32)); + comptime assert(std.math.isPowerOfTwo(alignment)); + + // Make sure the pointer is aligned, + // then cut off the zero bits from the alignment to get the unique address. + const addr = @intFromPtr(ptr); + assert(addr & (alignment - 1) == 0); + return addr >> @ctz(@as(usize, alignment)); + } + }; + + fn wait(ptr: *const u32, expect: u32, timeout: ?u64) error{Timeout}!void { + const address = Address.from(ptr); + const bucket = Bucket.from(address); + + // Announce that there's a waiter in the bucket before checking the ptr/expect condition. + // If the announcement is reordered after the ptr check, the waiter could deadlock: + // + // - T1: checks ptr == expect which is true + // - T2: updates ptr to != expect + // - T2: does Futex.wake(), sees no pending waiters, exits + // - T1: bumps pending waiters (was reordered after the ptr == expect check) + // - T1: goes to sleep and misses both the ptr change and T2's wake up + // + // acquire barrier to ensure the announcement happens before the ptr check below. + var pending = bucket.pending.fetchAdd(1, .acquire); + assert(pending < std.math.maxInt(usize)); + + // If the wait gets canceled, remove the pending count we previously added. + // This is done outside the mutex lock to keep the critical section short in case of contention. + var canceled = false; + defer if (canceled) { + pending = bucket.pending.fetchSub(1, .monotonic); + assert(pending > 0); + }; + + var waiter: Waiter = undefined; + { + assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); + defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); + + canceled = @atomicLoad(u32, ptr, .monotonic) != expect; + if (canceled) { + return; + } + + waiter.event.init(); + WaitQueue.insert(&bucket.treap, address, &waiter); + } + + defer { + assert(!waiter.is_queued); + waiter.event.deinit(); + } + + waiter.event.wait(timeout) catch { + // If we fail to cancel after a timeout, it means a wake() thread dequeued us and will wake us up. + // We must wait until the event is set as that's a signal that the wake() thread won't access the waiter memory anymore. + // If we return early without waiting, the waiter on the stack would be invalidated and the wake() thread risks a UAF. + defer if (!canceled) waiter.event.wait(null) catch unreachable; + + assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); + defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); + + canceled = WaitQueue.tryRemove(&bucket.treap, address, &waiter); + if (canceled) { + return error.Timeout; + } + }; + } + + fn wake(ptr: *const u32, max_waiters: u32) void { + const address = Address.from(ptr); + const bucket = Bucket.from(address); + + // Quick check if there's even anything to wake up. + // The change to the ptr's value must happen before we check for pending waiters. + // If not, the wake() thread could miss a sleeping waiter and have it deadlock: + // + // - T2: p = has pending waiters (reordered before the ptr update) + // - T1: bump pending waiters + // - T1: if ptr == expected: sleep() + // - T2: update ptr != expected + // - T2: p is false from earlier so doesn't wake (T1 missed ptr update and T2 missed T1 sleeping) + // + // What we really want here is a Release load, but that doesn't exist under the C11 memory model. + // We could instead do `bucket.pending.fetchAdd(0, Release) == 0` which achieves effectively the same thing, + // LLVM lowers the fetchAdd(0, .release) into an mfence+load which avoids gaining ownership of the cache-line. + if (bucket.pending.fetchAdd(0, .release) == 0) { + return; + } + + // Keep a list of all the waiters notified and wake then up outside the mutex critical section. + var notified = WaitList{}; + defer if (notified.len > 0) { + const pending = bucket.pending.fetchSub(notified.len, .monotonic); + assert(pending >= notified.len); + + while (notified.pop()) |waiter| { + assert(!waiter.is_queued); + waiter.event.set(); + } + }; + + assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); + defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); + + // Another pending check again to avoid the WaitQueue lookup if not necessary. + if (bucket.pending.load(.monotonic) > 0) { + notified = WaitQueue.remove(&bucket.treap, address, max_waiters); + } + } +}; + test { _ = @import("Threaded/test.zig"); } -- 2.54.0 From bf81666beb7ef2678d0d35676318d784385eb5e1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 16:03:02 -0800 Subject: [PATCH 107/195] std.fs.test: don't try to test nonexistent API --- lib/std/fs/test.zig | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index db6a6cccee884ea7748e6cfaa9c5622691d0833b..704805efb979109c5ac0bcf16bca9e845b9d7198 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -246,17 +246,9 @@ test "Dir.readLink on non-symlinks" { // file var buffer: [Dir.max_path_bytes]u8 = undefined; try std.testing.expectError(error.NotLink, ctx.dir.readLink(io, file_path, &buffer)); - if (builtin.os.tag == .windows) { - var file_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.handle, file_path); - try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(file_path_w.span(), &file_path_w.data)); - } // dir try std.testing.expectError(error.NotLink, ctx.dir.readLink(io, dir_path, &buffer)); - if (builtin.os.tag == .windows) { - var dir_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.handle, dir_path); - try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(dir_path_w.span(), &dir_path_w.data)); - } } }.impl); } -- 2.54.0 From 6cfd2df695df04834b935c21b1b40ddac0d39594 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 16:18:45 -0800 Subject: [PATCH 108/195] don't skip test for closed issue --- lib/std/fs/test.zig | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 704805efb979109c5ac0bcf16bca9e845b9d7198..91efb9ae557d084669e790aa8fd88f0d112a42fd 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -2364,11 +2364,6 @@ test "readlinkat" { test "fchmodat smoke test" { if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; - if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) { - // https://github.com/ziglang/zig/issues/23808 - return error.SkipZigTest; - } - const io = testing.io; var tmp = tmpDir(.{}); -- 2.54.0 From 52ba2a4c72cee39292d09506655197811feceede Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 16:19:01 -0800 Subject: [PATCH 109/195] std.Io.Threaded: don't assume dirents are aligned Linux kernel seems to do it but qemu user mode seems not to. --- lib/std/Io/Threaded.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 55c0ad4265bfd86bf050b5b977ffa18ab1347465..f4d60b3934aa5e7d88dd0bd602582ef120ad9263 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3440,7 +3440,10 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir // by looking at only the 8 bytes before the next record. However since // file names are usually short it's better to keep the machine code // simpler. - const linux_entry: *linux.dirent64 = @ptrCast(@alignCast(&dr.buffer[dr.index])); + // + // Furthermore, I observed qemu user mode to not align this struct, so + // this code makes the conservative choice to not assume alignment. + const linux_entry: *align(1) linux.dirent64 = @ptrCast(&dr.buffer[dr.index]); const next_index = dr.index + linux_entry.reclen; dr.index = next_index; const name_ptr: [*]u8 = &linux_entry.name; -- 2.54.0 From 6e0c7ed8659d9d60a59a9dcddd3aed9cd18b659b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 16:21:54 -0800 Subject: [PATCH 110/195] std: rename makeDir to createDir for consistency with createFile --- lib/std/Io/Dir.zig | 12 ++--- lib/std/fs/test.zig | 66 +++++++++++++------------- src/Package/Fetch.zig | 2 +- src/Package/Fetch/git.zig | 4 +- test/standalone/windows_spawn/main.zig | 2 +- test/tests.zig | 2 +- 6 files changed, 44 insertions(+), 44 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 238f25eb6bc8f7974deba9f246e80a2ff252c60a..78637015ba01ff38a0b10bf2579ea07c172e6cd6 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -664,25 +664,25 @@ pub const MakeError = error{ /// /// Related: /// * `makePath` -/// * `makeDirAbsolute` -pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakeError!void { +/// * `createDirAbsolute` +pub fn createDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakeError!void { return io.vtable.dirMake(io.userdata, dir, sub_path, permissions); } /// Create a new directory, based on an absolute path. /// -/// Asserts that the path is absolute. See `makeDir` for a function that +/// Asserts that the path is absolute. See `createDir` for a function that /// operates on both absolute and relative paths. /// /// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `absolute_path` should be encoded as valid UTF-8. /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. -pub fn makeDirAbsolute(io: Io, absolute_path: []const u8, permissions: Permissions) MakeError!void { +pub fn createDirAbsolute(io: Io, absolute_path: []const u8, permissions: Permissions) MakeError!void { assert(path.isAbsolute(absolute_path)); - return makeDir(.cwd(), io, absolute_path, permissions); + return createDir(.cwd(), io, absolute_path, permissions); } -test makeDirAbsolute {} +test createDirAbsolute {} pub const MakePathError = MakeError || StatFileError; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 91efb9ae557d084669e790aa8fd88f0d112a42fd..644a6fc52d45d082377ccadd3a23671ad42f0fa2 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -209,7 +209,7 @@ test "Dir.readLink" { const file_target_path = try ctx.transformPath("file.txt"); try ctx.dir.writeFile(io, .{ .sub_path = file_target_path, .data = "nonsense" }); const dir_target_path = try ctx.transformPath("subdir"); - try ctx.dir.makeDir(io, dir_target_path, .default_dir); + try ctx.dir.createDir(io, dir_target_path, .default_dir); // On Windows, symlink targets always use the canonical path separator const canonical_file_target_path = try ctx.toCanonicalPathSep(file_target_path); @@ -241,7 +241,7 @@ test "Dir.readLink on non-symlinks" { const file_path = try ctx.transformPath("file.txt"); try ctx.dir.writeFile(io, .{ .sub_path = file_path, .data = "nonsense" }); const dir_path = try ctx.transformPath("subdir"); - try ctx.dir.makeDir(io, dir_path, .default_dir); + try ctx.dir.createDir(io, dir_path, .default_dir); // file var buffer: [Dir.max_path_bytes]u8 = undefined; @@ -278,7 +278,7 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { const dir_target_path = try ctx.transformPath("subdir"); - try ctx.dir.makeDir(io, dir_target_path, .default_dir); + try ctx.dir.createDir(io, dir_target_path, .default_dir); try setupSymlink(io, ctx.dir, dir_target_path, "symlink", .{ .is_directory = true }); @@ -301,7 +301,7 @@ test "openDir" { fn impl(ctx: *TestContext) !void { const allocator = ctx.arena.allocator(); const subdir_path = try ctx.transformPath("subdir"); - try ctx.dir.makeDir(io, subdir_path, .default_dir); + try ctx.dir.createDir(io, subdir_path, .default_dir); for ([_][]const u8{ "", ".", ".." }) |sub_path| { const dir_path = try Dir.path.join(allocator, &.{ subdir_path, sub_path }); @@ -340,7 +340,7 @@ test "openDirAbsolute" { const tmp_ino = (try tmp.dir.stat(io)).inode; - try tmp.dir.makeDir(io, "subdir", .default_dir); + try tmp.dir.createDir(io, "subdir", .default_dir); const sub_path = try tmp.dir.realPathFileAlloc(io, "subdir", gpa); defer gpa.free(sub_path); @@ -437,7 +437,7 @@ test "readLinkAbsolute" { // Create some targets try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = "nonsense" }); - try tmp.dir.makeDir(io, "subdir", .default_dir); + try tmp.dir.createDir(io, "subdir", .default_dir); // Get base abs path var arena_allocator = ArenaAllocator.init(testing.allocator); @@ -474,7 +474,7 @@ test "Dir.Iterator" { const file = try tmp_dir.dir.createFile(io, "some_file", .{}); file.close(io); - try tmp_dir.dir.makeDir(io, "some_dir", .default_dir); + try tmp_dir.dir.createDir(io, "some_dir", .default_dir); var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); @@ -543,7 +543,7 @@ test "Dir.Iterator twice" { const file = try tmp_dir.dir.createFile(io, "some_file", .{}); file.close(io); - try tmp_dir.dir.makeDir(io, "some_dir", .default_dir); + try tmp_dir.dir.createDir(io, "some_dir", .default_dir); var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); @@ -578,7 +578,7 @@ test "Dir.Iterator reset" { const file = try tmp_dir.dir.createFile(io, "some_file", .{}); file.close(io); - try tmp_dir.dir.makeDir(io, "some_dir", .default_dir); + try tmp_dir.dir.createDir(io, "some_dir", .default_dir); var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); @@ -662,7 +662,7 @@ test "Dir.realPath smoke test" { // Now create the file and dir try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); - try ctx.dir.makeDir(io, test_dir_path, .default_dir); + try ctx.dir.createDir(io, test_dir_path, .default_dir); const base_path = try ctx.transformPath("."); const base_realpath = try ctx.dir.realPathFileAlloc(io, base_path, arena); @@ -754,7 +754,7 @@ test "Dir.statFile" { try expectError(error.FileNotFound, ctx.dir.statFile(io, test_dir_name, .{})); - try ctx.dir.makeDir(io, test_dir_name, .default_dir); + try ctx.dir.createDir(io, test_dir_name, .default_dir); const stat = try ctx.dir.statFile(io, test_dir_name, .{}); try expectEqual(.directory, stat.kind); @@ -787,12 +787,12 @@ test "directory operations on files" { var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true }); file.close(io); - try expectError(error.PathAlreadyExists, ctx.dir.makeDir(io, test_file_name, .default_dir)); + try expectError(error.PathAlreadyExists, ctx.dir.createDir(io, test_file_name, .default_dir)); try expectError(error.NotDir, ctx.dir.openDir(io, test_file_name, .{})); try expectError(error.NotDir, ctx.dir.deleteDir(io, test_file_name)); if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { - try expectError(error.PathAlreadyExists, Dir.makeDirAbsolute(io, test_file_name, .default_dir)); + try expectError(error.PathAlreadyExists, Dir.createDirAbsolute(io, test_file_name, .default_dir)); try expectError(error.NotDir, Dir.deleteDirAbsolute(io, test_file_name)); } @@ -815,7 +815,7 @@ test "file operations on directories" { fn impl(ctx: *TestContext) !void { const test_dir_name = try ctx.transformPath("test_dir"); - try ctx.dir.makeDir(io, test_dir_name, .default_dir); + try ctx.dir.createDir(io, test_dir_name, .default_dir); try expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{})); try expectError(error.IsDir, ctx.dir.deleteFile(io, test_dir_name)); @@ -887,7 +887,7 @@ test "deleteDir" { try expectError(error.FileNotFound, ctx.dir.deleteDir(io, test_dir_path)); // deleting a non-empty directory - try ctx.dir.makeDir(io, test_dir_path, .default_dir); + try ctx.dir.createDir(io, test_dir_path, .default_dir); try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" }); try expectError(error.DirNotEmpty, ctx.dir.deleteDir(io, test_dir_path)); @@ -956,7 +956,7 @@ test "Dir.rename directories" { const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed"); // Renaming directories - try ctx.dir.makeDir(io, test_dir_path, .default_dir); + try ctx.dir.createDir(io, test_dir_path, .default_dir); try ctx.dir.rename(test_dir_path, ctx.dir, test_dir_renamed_path, io); // Ensure the directory was renamed @@ -992,8 +992,8 @@ test "Dir.rename directory onto empty dir" { const test_dir_path = try ctx.transformPath("test_dir"); const target_dir_path = try ctx.transformPath("target_dir_path"); - try ctx.dir.makeDir(io, test_dir_path, .default_dir); - try ctx.dir.makeDir(io, target_dir_path, .default_dir); + try ctx.dir.createDir(io, test_dir_path, .default_dir); + try ctx.dir.createDir(io, target_dir_path, .default_dir); try ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io); // Ensure the directory was renamed @@ -1014,7 +1014,7 @@ test "Dir.rename directory onto non-empty dir" { const test_dir_path = try ctx.transformPath("test_dir"); const target_dir_path = try ctx.transformPath("target_dir_path"); - try ctx.dir.makeDir(io, test_dir_path, .default_dir); + try ctx.dir.createDir(io, test_dir_path, .default_dir); var target_dir = try ctx.dir.makeOpenPath(io, target_dir_path, .{}); var file = try target_dir.createFile(io, "test_file", .{ .read = true }); @@ -1043,7 +1043,7 @@ test "Dir.rename file <-> dir" { var file = try ctx.dir.createFile(io, test_file_path, .{ .read = true }); file.close(io); - try ctx.dir.makeDir(io, test_dir_path, .default_dir); + try ctx.dir.createDir(io, test_dir_path, .default_dir); try expectError(error.IsDir, ctx.dir.rename(test_file_path, ctx.dir, test_dir_path, io)); try expectError(error.NotDir, ctx.dir.rename(test_dir_path, ctx.dir, test_file_path, io)); } @@ -1115,7 +1115,7 @@ test "renameAbsolute" { // Renaming directories const test_dir_name = "test_dir"; const renamed_test_dir_name = "test_dir_renamed"; - try tmp_dir.dir.makeDir(io, test_dir_name, .default_dir); + try tmp_dir.dir.createDir(io, test_dir_name, .default_dir); try Dir.renameAbsolute( try Dir.path.join(allocator, &.{ base_path, test_dir_name }), try Dir.path.join(allocator, &.{ base_path, renamed_test_dir_name }), @@ -1256,7 +1256,7 @@ test "makePath but sub_path contains pre-existing file" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makeDir(io, "foo", .default_dir); + try tmp.dir.createDir(io, "foo", .default_dir); try tmp.dir.writeFile(io, .{ .sub_path = "foo/bar", .data = "" }); try expectError(error.NotDir, tmp.dir.makePath(io, "foo/bar/baz")); @@ -1273,10 +1273,10 @@ test "makepath existing directories" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makeDir(io, "A", .default_dir); + try tmp.dir.createDir(io, "A", .default_dir); var tmpA = try tmp.dir.openDir(io, "A", .{}); defer tmpA.close(io); - try tmpA.makeDir(io, "B", .default_dir); + try tmpA.createDir(io, "B", .default_dir); const testPath = "A" ++ Dir.path.sep_str ++ "B" ++ Dir.path.sep_str ++ "C"; try tmp.dir.makePath(io, testPath); @@ -1290,7 +1290,7 @@ test "makepath through existing valid symlink" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makeDir(io, "realfolder", .default_dir); + try tmp.dir.createDir(io, "realfolder", .default_dir); try setupSymlink(io, tmp.dir, "." ++ Dir.path.sep_str ++ "realfolder", "working-symlink", .{}); try tmp.dir.makePath(io, "working-symlink" ++ Dir.path.sep_str ++ "in-realfolder"); @@ -1985,7 +1985,7 @@ test "'.' and '..' in Dir functions" { const rename_path = try ctx.transformPath("./subdir/../rename"); const update_path = try ctx.transformPath("./subdir/../update"); - try ctx.dir.makeDir(io, subdir_path, .default_dir); + try ctx.dir.createDir(io, subdir_path, .default_dir); try ctx.dir.access(io, subdir_path, .{}); var created_subdir = try ctx.dir.openDir(io, subdir_path, .{}); created_subdir.close(io); @@ -2026,7 +2026,7 @@ test "'.' and '..' in absolute functions" { const base_path = try tmp.dir.realPathFileAlloc(io, ".", allocator); const subdir_path = try Dir.path.join(allocator, &.{ base_path, "./subdir" }); - try Dir.makeDirAbsolute(io, subdir_path, .default_dir); + try Dir.createDirAbsolute(io, subdir_path, .default_dir); try Dir.accessAbsolute(io, subdir_path, .{}); var created_subdir = try Dir.openDirAbsolute(io, subdir_path, .{}); created_subdir.close(io); @@ -2062,7 +2062,7 @@ test "chmod" { try file.setPermissions(io, .fromMode(0o644)); try expectEqual(0o644, (try file.stat(io)).permissions.toMode() & 0o7777); - try tmp.dir.makeDir(io, "test_dir", .default_dir); + try tmp.dir.createDir(io, "test_dir", .default_dir); var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); defer dir.close(io); @@ -2083,7 +2083,7 @@ test "change ownership" { defer file.close(io); try file.setOwner(io, null, null); - try tmp.dir.makeDir(io, "test_dir", .default_dir); + try tmp.dir.createDir(io, "test_dir", .default_dir); var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true }); defer dir.close(io); @@ -2107,7 +2107,7 @@ test "invalid UTF-8/WTF-8 paths" { try expectError(expected_err, ctx.dir.createFile(io, invalid_path, .{})); - try expectError(expected_err, ctx.dir.makeDir(io, invalid_path, .default_dir)); + try expectError(expected_err, ctx.dir.createDir(io, invalid_path, .default_dir)); try expectError(expected_err, ctx.dir.makePath(io, invalid_path)); try expectError(expected_err, ctx.dir.makeOpenPath(io, invalid_path, .{})); @@ -2150,7 +2150,7 @@ test "invalid UTF-8/WTF-8 paths" { if (native_os != .wasi and ctx.path_type != .relative) { var buf: [Dir.max_path_bytes]u8 = undefined; try expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, io, .{})); - try expectError(expected_err, Dir.makeDirAbsolute(io, invalid_path, .default_dir)); + try expectError(expected_err, Dir.createDirAbsolute(io, invalid_path, .default_dir)); try expectError(expected_err, Dir.deleteDirAbsolute(io, invalid_path)); try expectError(expected_err, Dir.renameAbsolute(invalid_path, invalid_path, io)); try expectError(expected_err, Dir.openDirAbsolute(io, invalid_path, .{})); @@ -2496,7 +2496,7 @@ test "access smoke test" { { // Create some directory - try tmp.dir.makeDir(io, "some_dir", .default_dir); + try tmp.dir.createDir(io, "some_dir", .default_dir); } { @@ -2552,7 +2552,7 @@ test "open smoke test" { } try expectError(error.NotDir, tmp.dir.openDir(io, "some_file", .{})); - try tmp.dir.makeDir(io, "some_dir", .default_dir); + try tmp.dir.createDir(io, "some_dir", .default_dir); { const dir = try tmp.dir.openDir(io, "some_dir", .{}); diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index b17b39cd8389dd84e86d42ea7a25f9b59f800952..45fcd66b8c9e3f6f0aaac2517635cedf5726dfe8 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1458,7 +1458,7 @@ pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u cache_dir.rename(tmp_dir_sub_path, cache_dir, dest_dir_sub_path, io) catch |err| switch (err) { error.FileNotFound => { if (handled_missing_dir) return err; - cache_dir.makeDir(io, dest_dir_sub_path[0..1], .default_dir) catch |mkd_err| switch (mkd_err) { + cache_dir.createDir(io, dest_dir_sub_path[0..1], .default_dir) catch |mkd_err| switch (mkd_err) { error.PathAlreadyExists => handled_missing_dir = true, else => |e| return e, }; diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index fd48b0a9b198d96da6627fd54ea65e0cad4446ea..eb3d2a447e508ccb19316036ced8bbcf17b6edb3 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -253,7 +253,7 @@ pub const Repository = struct { while (try tree_iter.next()) |entry| { switch (entry.type) { .directory => { - try dir.makeDir(io, entry.name, .default_dir); + try dir.createDir(io, entry.name, .default_dir); var subdir = try dir.openDir(io, entry.name, .{}); defer subdir.close(io); const sub_path = try std.fs.path.join(repository.odb.allocator, &.{ current_path, entry.name }); @@ -296,7 +296,7 @@ pub const Repository = struct { .gitlink => { // Consistent with git archive behavior, create the directory but // do nothing else - try dir.makeDir(io, entry.name, .default_dir); + try dir.createDir(io, entry.name, .default_dir); }, } } diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index 0d82ecff975d6d6d0630c47167e7f57ff32349ef..d89dc18bc2db0534d34eacac749f0c7153af5e8b 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -84,7 +84,7 @@ pub fn main() anyerror!void { // without extension should succeed (case insensitive) try testExec(gpa, "heLLo", "hello from exe\n"); - try tmp.dir.makeDir(io, "something", .default_dir); + try tmp.dir.createDir(io, "something", .default_dir); try renameExe(tmp.dir, "hello", "something/hello.exe"); const relative_path_no_ext = try std.fs.path.join(gpa, &.{ tmp_relative_path, "something/hello" }); diff --git a/test/tests.zig b/test/tests.zig index ee324329e284ebcc06496f3f972b32b602205dd5..2a267bba8dbf50b545ebaa1c82cce708dffabc76 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2137,7 +2137,7 @@ pub fn addCliTests(b: *std.Build) *Step { defer dir.close(io); dir.writeFile(io, .{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled"); dir.writeFile(io, .{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled"); - dir.makeDir(io, "subdir", .default_dir) catch @panic("unhandled"); + dir.createDir(io, "subdir", .default_dir) catch @panic("unhandled"); var subdir = dir.openDir(io, "subdir", .{}) catch @panic("unhandled"); defer subdir.close(io); subdir.writeFile(io, .{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled"); -- 2.54.0 From a8088306f6223b07ad9b7ae37486bcc9e0ac08c9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 16:42:28 -0800 Subject: [PATCH 111/195] std: rename other Dir "make" functions to "create" --- lib/compiler/build_runner.zig | 6 +-- lib/compiler/translate-c/main.zig | 2 +- lib/std/Build.zig | 6 +-- lib/std/Build/Cache.zig | 8 +-- lib/std/Build/Cache/Path.zig | 8 +-- lib/std/Build/Step.zig | 6 +-- lib/std/Build/Step/Compile.zig | 4 +- lib/std/Build/Step/ConfigHeader.zig | 2 +- lib/std/Build/Step/ObjCopy.zig | 2 +- lib/std/Build/Step/Options.zig | 4 +- lib/std/Build/Step/Run.zig | 6 +-- lib/std/Build/Step/UpdateSourceFiles.zig | 2 +- lib/std/Build/Step/WriteFile.zig | 8 +-- lib/std/Io.zig | 6 +-- lib/std/Io/Dir.zig | 40 +++++++------- lib/std/Io/Kqueue.zig | 12 ++--- lib/std/Io/Threaded.zig | 65 +++++++++++------------ lib/std/fs/test.zig | 66 ++++++++++++------------ lib/std/posix.zig | 2 +- lib/std/tar.zig | 6 +-- lib/std/testing.zig | 6 +-- lib/std/zip.zig | 4 +- src/Compilation.zig | 34 ++++++------ src/Package/Fetch.zig | 12 ++--- src/Package/Fetch/git.zig | 4 +- src/libs/freebsd.zig | 4 +- src/libs/glibc.zig | 4 +- src/libs/mingw.zig | 4 +- src/libs/netbsd.zig | 4 +- src/link/MachO.zig | 2 +- src/main.zig | 8 +-- 31 files changed, 171 insertions(+), 176 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 1c30ecaadce2a4c863214f42bf5bd74385bf0174..cfe0fee78fae7318cf0b266d426d4a3543224c10 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -66,12 +66,12 @@ pub fn main() !void { const local_cache_directory: std.Build.Cache.Directory = .{ .path = cache_root, - .handle = try cwd.makeOpenPath(io, cache_root, .{}), + .handle = try cwd.createDirPathOpen(io, cache_root, .{}), }; const global_cache_directory: std.Build.Cache.Directory = .{ .path = global_cache_root, - .handle = try cwd.makeOpenPath(io, global_cache_root, .{}), + .handle = try cwd.createDirPathOpen(io, global_cache_root, .{}), }; var graph: std.Build.Graph = .{ @@ -80,7 +80,7 @@ pub fn main() !void { .cache = .{ .io = io, .gpa = arena, - .manifest_dir = try local_cache_directory.handle.makeOpenPath(io, "h", .{}), + .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), }, .zig_exe = zig_exe, .env_map = try process.getEnvMap(arena), diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index d02f21a2a810383c9c4a2a789c62fc10e386c108..80dc67fbfe2608d787b97241b0f4c34ed3cb9122 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -253,7 +253,7 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration if (d.output_name) |path| blk: { if (std.mem.eql(u8, path, "-")) break :blk; if (std.fs.path.dirname(path)) |dirname| { - Io.Dir.cwd().makePath(io, dirname) catch |err| + Io.Dir.cwd().createDirPath(io, dirname) catch |err| return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); } out_file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| { diff --git a/lib/std/Build.zig b/lib/std/Build.zig index cf0b9e5b0deea8b9bf3a4a0b4e8acfd43aea4ed7..4b8909e6892a814c8087f063ba9b1ed34c54ad11 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1701,14 +1701,14 @@ pub fn addCheckFile( return Step.CheckFile.create(b, file_source, options); } -pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.MakeError || Io.Dir.StatFileError)!void { +pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.CreateDirError || Io.Dir.StatFileError)!void { const io = b.graph.io; if (b.verbose) log.info("truncate {s}", .{dest_path}); const cwd = Io.Dir.cwd(); var src_file = cwd.createFile(io, dest_path, .{}) catch |err| switch (err) { error.FileNotFound => blk: { if (fs.path.dirname(dest_path)) |dirname| { - try cwd.makePath(io, dirname); + try cwd.createDirPath(io, dirname); } break :blk try cwd.createFile(io, dest_path, .{}); }, @@ -2654,7 +2654,7 @@ pub fn makeTempPath(b: *Build) []const u8 { const rand_int = std.crypto.random.int(u64); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM"); - b.cache_root.handle.makePath(io, tmp_dir_sub_path) catch |err| { + b.cache_root.handle.createDirPath(io, tmp_dir_sub_path) catch |err| { std.debug.print("unable to make tmp path '{s}': {t}\n", .{ result_path, err }); }; return result_path; diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 43f8691000684973dd7d3f9551e3f4ff71adb93e..396f2043504658672b5ec2fe12d69a0b65e69560 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -1330,7 +1330,7 @@ test "cache file and then recall it" { var cache: Cache = .{ .io = io, .gpa = testing.allocator, - .manifest_dir = try tmp.dir.makeOpenPath(io, temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); @@ -1396,7 +1396,7 @@ test "check that changing a file makes cache fail" { var cache: Cache = .{ .io = io, .gpa = testing.allocator, - .manifest_dir = try tmp.dir.makeOpenPath(io, temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); @@ -1456,7 +1456,7 @@ test "no file inputs" { var cache: Cache = .{ .io = io, .gpa = testing.allocator, - .manifest_dir = try tmp.dir.makeOpenPath(io, temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); @@ -1515,7 +1515,7 @@ test "Manifest with files added after initial hash work" { var cache: Cache = .{ .io = io, .gpa = testing.allocator, - .manifest_dir = try tmp.dir.makeOpenPath(io, temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); diff --git a/lib/std/Build/Cache/Path.zig b/lib/std/Build/Cache/Path.zig index 3ef4dec658a6ff676ff36200cacfe44c45e0ab33..2b7814c544946fc5aaf54833f840be57f6b69f5c 100644 --- a/lib/std/Build/Cache/Path.zig +++ b/lib/std/Build/Cache/Path.zig @@ -84,14 +84,14 @@ pub fn openDir( return p.root_dir.handle.openDir(io, joined_path, args); } -pub fn makeOpenPath(p: Path, io: Io, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io.Dir { +pub fn createDirPathOpen(p: Path, io: Io, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io.Dir { var buf: [fs.max_path_bytes]u8 = undefined; const joined_path = if (p.sub_path.len == 0) sub_path else p: { break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ p.sub_path, sub_path, }) catch return error.NameTooLong; }; - return p.root_dir.handle.makeOpenPath(io, joined_path, opts); + return p.root_dir.handle.createDirPathOpen(io, joined_path, opts); } pub fn statFile(p: Path, io: Io, sub_path: []const u8) !Io.Dir.Stat { @@ -129,14 +129,14 @@ pub fn access(p: Path, io: Io, sub_path: []const u8, flags: Io.Dir.AccessOptions return p.root_dir.handle.access(io, joined_path, flags); } -pub fn makePath(p: Path, io: Io, sub_path: []const u8) !void { +pub fn createDirPath(p: Path, io: Io, sub_path: []const u8) !void { var buf: [fs.max_path_bytes]u8 = undefined; const joined_path = if (p.sub_path.len == 0) sub_path else p: { break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ p.sub_path, sub_path, }) catch return error.NameTooLong; }; - return p.root_dir.handle.makePath(io, joined_path); + return p.root_dir.handle.createDirPath(io, joined_path); } pub fn toString(p: Path, allocator: Allocator) Allocator.Error![]u8 { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 74b41634a73ba36e8130ff9cda652db6aa372f0c..243dee8604da4eca431012923409fdc1b48e2303 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -516,12 +516,12 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err }); } -/// Wrapper around `Io.Dir.makePathStatus` that handles verbose and error output. -pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.MakePathStatus { +/// Wrapper around `Io.Dir.createDirPathStatus` that handles verbose and error output. +pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.CreatePathStatus { const b = s.owner; const io = b.graph.io; try handleVerbose(b, null, &.{ "install", "-d", dest_path }); - return Io.Dir.cwd().makePathStatus(io, dest_path, .default_dir) catch |err| + return Io.Dir.cwd().createDirPathStatus(io, dest_path, .default_dir) catch |err| return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err }); } diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 4752046089a4d6918937ecb6351f4a1a2dcce0d3..0454e5b79dbc2582cac7188b9a2593decb37dfa2 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1669,7 +1669,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { args_length += arg.len + 1; // +1 to account for null terminator } if (args_length >= 30 * 1024) { - try b.cache_root.handle.makePath(io, "args"); + try b.cache_root.handle.createDirPath(io, "args"); const args_to_escape = zig_args.items[2..]; var escaped_args = try std.array_list.Managed([]const u8).initCapacity(arena, args_to_escape.len); @@ -1706,7 +1706,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { // The args file is already present from a previous run. } else |err| switch (err) { error.FileNotFound => { - try b.cache_root.handle.makePath(io, "tmp"); + try b.cache_root.handle.createDirPath(io, "tmp"); const rand_int = std.crypto.random.int(u64); const tmp_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); try b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_path, .data = args }); diff --git a/lib/std/Build/Step/ConfigHeader.zig b/lib/std/Build/Step/ConfigHeader.zig index 250bae500945b8e30ffa38eb60e91840f2d8d946..b55efc0da4f2b97a52fec437f183010b19528764 100644 --- a/lib/std/Build/Step/ConfigHeader.zig +++ b/lib/std/Build/Step/ConfigHeader.zig @@ -258,7 +258,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const sub_path = b.pathJoin(&.{ "o", &digest, config_header.include_path }); const sub_path_dirname = std.fs.path.dirname(sub_path).?; - b.cache_root.handle.makePath(io, sub_path_dirname) catch |err| { + b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| { return step.fail("unable to make path '{f}{s}': {s}", .{ b.cache_root, sub_path_dirname, @errorName(err), }); diff --git a/lib/std/Build/Step/ObjCopy.zig b/lib/std/Build/Step/ObjCopy.zig index b81f59b9a1888d454b2d69c0b37c601ca55d3a84..ea0714adf9b5517dfd206c9001749636d4e9a438 100644 --- a/lib/std/Build/Step/ObjCopy.zig +++ b/lib/std/Build/Step/ObjCopy.zig @@ -177,7 +177,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const cache_path = "o" ++ fs.path.sep_str ++ digest; const full_dest_path = try b.cache_root.join(b.allocator, &.{ cache_path, objcopy.basename }); const full_dest_path_debug = try b.cache_root.join(b.allocator, &.{ cache_path, b.fmt("{s}.debug", .{objcopy.basename}) }); - b.cache_root.handle.makePath(io, cache_path) catch |err| { + b.cache_root.handle.createDirPath(io, cache_path) catch |err| { return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) }); }; diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 676ea4d85189d6b314bd24d9ff8a9a6188f47d84..610d417aeabf53cc1881e0d57563224f6b29cda2 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -477,7 +477,7 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { } else |outer_err| switch (outer_err) { error.FileNotFound => { const sub_dirname = fs.path.dirname(sub_path).?; - b.cache_root.handle.makePath(io, sub_dirname) catch |e| + b.cache_root.handle.createDirPath(io, sub_dirname) catch |e| return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, sub_dirname, e }); const rand_int = std.crypto.random.int(u64); @@ -486,7 +486,7 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { basename; const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?; - b.cache_root.handle.makePath(io, tmp_sub_path_dirname) catch |err| { + b.cache_root.handle.createDirPath(io, tmp_sub_path_dirname) catch |err| { return step.fail("unable to make temporary directory '{f}{s}': {t}", .{ b.cache_root, tmp_sub_path_dirname, err, }); diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 157a0292e70b44d16357c6aa0609d73d03854614..c0ba7f0cee04fd6cfcae4da0923c05e8d78e2e58 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -975,7 +975,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .output_directory => output_sub_path, else => unreachable, }; - b.cache_root.handle.makePath(io, output_sub_dir_path) catch |err| { + b.cache_root.handle.createDirPath(io, output_sub_dir_path) catch |err| { return step.fail("unable to make path '{f}{s}': {s}", .{ b.cache_root, output_sub_dir_path, @errorName(err), }); @@ -1007,7 +1007,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .output_directory => output_sub_path, else => unreachable, }; - b.cache_root.handle.makePath(io, output_sub_dir_path) catch |err| { + b.cache_root.handle.createDirPath(io, output_sub_dir_path) catch |err| { return step.fail("unable to make path '{f}{s}': {s}", .{ b.cache_root, output_sub_dir_path, @errorName(err), }); @@ -1439,7 +1439,7 @@ fn runCommand( const sub_path = b.pathJoin(&output_components); const sub_path_dirname = Dir.path.dirname(sub_path).?; - b.cache_root.handle.makePath(io, sub_path_dirname) catch |err| { + b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| { return step.fail("unable to make path '{f}{s}': {s}", .{ b.cache_root, sub_path_dirname, @errorName(err), }); diff --git a/lib/std/Build/Step/UpdateSourceFiles.zig b/lib/std/Build/Step/UpdateSourceFiles.zig index eb8a6a85ddde43d336af1721a6de4435af3c7e40..1c4c94f9cf65c1167d72a0ac92c8c5c9ec17a805 100644 --- a/lib/std/Build/Step/UpdateSourceFiles.zig +++ b/lib/std/Build/Step/UpdateSourceFiles.zig @@ -78,7 +78,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { var any_miss = false; for (usf.output_source_files.items) |output_source_file| { if (fs.path.dirname(output_source_file.sub_path)) |dirname| { - b.build_root.handle.makePath(io, dirname) catch |err| { + b.build_root.handle.createDirPath(io, dirname) catch |err| { return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err }); }; } diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index 53222289e6fd0723c594f2e1c31f6e8015eb4f45..145c7f9bb30b326ac3dbc4f23bd417c1e918cf1a 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -259,13 +259,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void { write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest }); - var cache_dir = b.cache_root.handle.makeOpenPath(io, cache_path, .{}) catch |err| + var cache_dir = b.cache_root.handle.createDirPathOpen(io, cache_path, .{}) catch |err| return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, cache_path, err }); defer cache_dir.close(io); for (write_file.files.items) |file| { if (fs.path.dirname(file.sub_path)) |dirname| { - cache_dir.makePath(io, dirname) catch |err| { + cache_dir.createDirPath(io, dirname) catch |err| { return step.fail("unable to make path '{f}{s}{c}{s}': {t}", .{ b.cache_root, cache_path, fs.path.sep, dirname, err, }); @@ -300,7 +300,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const dest_dirname = dir.sub_path; if (dest_dirname.len != 0) { - cache_dir.makePath(io, dest_dirname) catch |err| { + cache_dir.createDirPath(io, dest_dirname) catch |err| { return step.fail("unable to make path '{f}{s}{c}{s}': {s}", .{ b.cache_root, cache_path, fs.path.sep, dest_dirname, @errorName(err), }); @@ -315,7 +315,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const src_entry_path = try src_dir_path.join(arena, entry.path); const dest_path = b.pathJoin(&.{ dest_dirname, entry.path }); switch (entry.kind) { - .directory => try cache_dir.makePath(io, dest_path), + .directory => try cache_dir.createDirPath(io, dest_path), .file => { const prev_status = Io.Dir.updateFile( src_entry_path.root_dir.handle, diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 1d477d330e5d2fec06dca6e7bd8ffa8215610eae..ad26893651a66e802c77d6617cf15a76cdcf950c 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -659,9 +659,9 @@ pub const VTable = struct { futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, - dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakeError!void, - dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.MakePathError!Dir.MakePathStatus, - dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, + dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, + dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus, + dirCreateDirPathOpen: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions, Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir, dirOpenDir: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.OpenError!Dir, dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat, dirStatFile: *const fn (?*anyopaque, Dir, []const u8, Dir.StatFileOptions) Dir.StatFileError!File.Stat, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 78637015ba01ff38a0b10bf2579ea07c172e6cd6..54cce082da040992f0b15ca88906adfcda902bc1 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -590,7 +590,7 @@ pub fn updateFile( } if (path.dirname(dest_path)) |dirname| { - try dest_dir.makePath(io, dirname); + try dest_dir.createDirPath(io, dirname); } var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available. @@ -637,7 +637,7 @@ pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileE return buffer[0..n]; } -pub const MakeError = error{ +pub const CreateDirError = error{ /// In WASI, this error may occur when the file descriptor does /// not hold the required rights to create a new directory relative to it. AccessDenied, @@ -663,10 +663,10 @@ pub const MakeError = error{ /// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. /// /// Related: -/// * `makePath` +/// * `createDirPath` /// * `createDirAbsolute` -pub fn createDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakeError!void { - return io.vtable.dirMake(io.userdata, dir, sub_path, permissions); +pub fn createDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) CreateDirError!void { + return io.vtable.dirCreateDir(io.userdata, dir, sub_path, permissions); } /// Create a new directory, based on an absolute path. @@ -677,14 +677,14 @@ pub fn createDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permission /// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `absolute_path` should be encoded as valid UTF-8. /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. -pub fn createDirAbsolute(io: Io, absolute_path: []const u8, permissions: Permissions) MakeError!void { +pub fn createDirAbsolute(io: Io, absolute_path: []const u8, permissions: Permissions) CreateDirError!void { assert(path.isAbsolute(absolute_path)); return createDir(.cwd(), io, absolute_path, permissions); } test createDirAbsolute {} -pub const MakePathError = MakeError || StatFileError; +pub const CreateDirPathError = CreateDirError || StatFileError; /// Creates parent directories with default permissions as necessary to ensure /// `sub_path` exists as a directory. @@ -710,27 +710,27 @@ pub const MakePathError = MakeError || StatFileError; /// and a `./second` directory. /// /// See also: -/// * `makePathStatus` -pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void { - _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, .default_dir); +/// * `createDirPathStatus` +pub fn createDirPath(dir: Dir, io: Io, sub_path: []const u8) CreateDirPathError!void { + _ = try io.vtable.dirCreateDirPath(io.userdata, dir, sub_path, .default_dir); } -pub const MakePathStatus = enum { existed, created }; +pub const CreatePathStatus = enum { existed, created }; -/// Same as `makePath` except returns whether the path already existed or was +/// Same as `createDirPath` except returns whether the path already existed or was /// successfully created. -pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakePathError!MakePathStatus { - return io.vtable.dirMakePath(io.userdata, dir, sub_path, permissions); +pub fn createDirPathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) CreateDirPathError!CreatePathStatus { + return io.vtable.dirCreateDirPath(io.userdata, dir, sub_path, permissions); } -pub const MakeOpenPathError = MakeError || OpenError || StatFileError; +pub const CreateDirPathOpenError = CreateDirError || OpenError || StatFileError; -pub const MakeOpenPathOptions = struct { +pub const CreateDirPathOpenOptions = struct { open_options: OpenOptions = .{}, permissions: Permissions = .default_dir, }; -/// Performs the equivalent of `makePath` followed by `openDir`, atomically if possible. +/// Performs the equivalent of `createDirPath` followed by `openDir`, atomically if possible. /// /// When this operation is canceled, it may leave the file system in a /// partially modified state. @@ -738,8 +738,8 @@ pub const MakeOpenPathOptions = struct { /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: MakeOpenPathOptions) MakeOpenPathError!Dir { - return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options.permissions, options.open_options); +pub fn createDirPathOpen(dir: Dir, io: Io, sub_path: []const u8, options: CreateDirPathOpenOptions) CreateDirPathOpenError!Dir { + return io.vtable.dirCreateDirPathOpen(io.userdata, dir, sub_path, options.permissions, options.open_options); } pub const Stat = File.Stat; @@ -1729,7 +1729,7 @@ pub const AtomicFileOptions = struct { pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFileOptions) !File.Atomic { if (path.dirname(dest_path)) |dirname| { const dir = if (options.make_path) - try parent.makeOpenPath(io, dirname, .{}) + try parent.createDirPathOpen(io, dirname, .{}) else try parent.openDir(io, dirname, .{}); diff --git a/lib/std/Io/Kqueue.zig b/lib/std/Io/Kqueue.zig index 9f9403d5adaed561ce0cb4db850331eb208450d9..26b8298cabb85e6ec34cc93424881541f781c1c8 100644 --- a/lib/std/Io/Kqueue.zig +++ b/lib/std/Io/Kqueue.zig @@ -869,9 +869,9 @@ pub fn io(k: *Kqueue) Io { .conditionWaitUncancelable = conditionWaitUncancelable, .conditionWake = conditionWake, - .dirMake = dirMake, - .dirMakePath = dirMakePath, - .dirMakeOpenPath = dirMakeOpenPath, + .dirCreateDir = dirCreateDir, + .dirCreateDirPath = dirCreateDirPath, + .dirCreateDirPathOpen = dirCreateDirPathOpen, .dirStat = dirStat, .dirStatFile = dirStatFile, @@ -1114,7 +1114,7 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition. k.yield(waiting_fiber, .reschedule); } -fn dirMake(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void { +fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.CreateDirError!void { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = dir; @@ -1122,7 +1122,7 @@ fn dirMake(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode _ = mode; @panic("TODO"); } -fn dirMakePath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void { +fn dirCreateDirPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.CreateDirError!void { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = dir; @@ -1130,7 +1130,7 @@ fn dirMakePath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir. _ = mode; @panic("TODO"); } -fn dirMakeOpenPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.OpenOptions) Dir.MakeOpenPathError!Dir { +fn dirCreateDirPathOpen(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = dir; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f4d60b3934aa5e7d88dd0bd602582ef120ad9263..4afd8d75d15596cf6a5a94672e7315253e45f689 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -711,9 +711,9 @@ pub fn io(t: *Threaded) Io { .futexWaitUncancelable = futexWaitUncancelable, .futexWake = futexWake, - .dirMake = dirMake, - .dirMakePath = dirMakePath, - .dirMakeOpenPath = dirMakeOpenPath, + .dirCreateDir = dirCreateDir, + .dirCreateDirPath = dirCreateDirPath, + .dirCreateDirPathOpen = dirCreateDirPathOpen, .dirStat = dirStat, .dirStatFile = dirStatFile, .dirAccess = dirAccess, @@ -846,9 +846,9 @@ pub fn ioBasic(t: *Threaded) Io { .futexWaitUncancelable = futexWaitUncancelable, .futexWake = futexWake, - .dirMake = dirMake, - .dirMakePath = dirMakePath, - .dirMakeOpenPath = dirMakeOpenPath, + .dirCreateDir = dirCreateDir, + .dirCreateDirPath = dirCreateDirPath, + .dirCreateDirPathOpen = dirCreateDirPathOpen, .dirStat = dirStat, .dirStatFile = dirStatFile, .dirAccess = dirAccess, @@ -1507,13 +1507,13 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { } } -const dirMake = switch (native_os) { - .windows => dirMakeWindows, - .wasi => dirMakeWasi, - else => dirMakePosix, +const dirCreateDir = switch (native_os) { + .windows => dirCreateDirWindows, + .wasi => dirCreateDirWasi, + else => dirCreateDirPosix, }; -fn dirMakePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.MakeError!void { +fn dirCreateDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -1559,8 +1559,8 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissio } } -fn dirMakeWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.MakeError!void { - if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, permissions); +fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { + if (builtin.link_libc) return dirCreateDirPosix(userdata, dir, sub_path, permissions); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); try current_thread.beginSyscall(); @@ -1601,7 +1601,7 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permission } } -fn dirMakeWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.MakeError!void { +fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); try current_thread.checkCancel(); @@ -1627,19 +1627,19 @@ fn dirMakeWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permiss windows.CloseHandle(sub_dir_handle); } -fn dirMakePath( +fn dirCreateDirPath( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions, -) Dir.MakePathError!Dir.MakePathStatus { +) Dir.CreateDirPathError!Dir.CreatePathStatus { const t: *Threaded = @ptrCast(@alignCast(userdata)); var it = std.fs.path.componentIterator(sub_path); - var status: Dir.MakePathStatus = .existed; + var status: Dir.CreatePathStatus = .existed; var component = it.last() orelse return error.BadPathName; while (true) { - if (dirMake(t, dir, component.path, permissions)) |_| { + if (dirCreateDir(t, dir, component.path, permissions)) |_| { status = .created; } else |err| switch (err) { error.PathAlreadyExists => { @@ -1659,37 +1659,37 @@ fn dirMakePath( } } -const dirMakeOpenPath = switch (native_os) { - .windows => dirMakeOpenPathWindows, - .wasi => dirMakeOpenPathWasi, - else => dirMakeOpenPathPosix, +const dirCreateDirPathOpen = switch (native_os) { + .windows => dirCreateDirPathOpenWindows, + .wasi => dirCreateDirPathOpenWasi, + else => dirCreateDirPathOpenPosix, }; -fn dirMakeOpenPathPosix( +fn dirCreateDirPathOpenPosix( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions, options: Dir.OpenOptions, -) Dir.MakeOpenPathError!Dir { +) Dir.CreateDirPathOpenError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); const t_io = ioBasic(t); return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) { error.FileNotFound => { - _ = try dir.makePathStatus(t_io, sub_path, permissions); + _ = try dir.createDirPathStatus(t_io, sub_path, permissions); return dirOpenDirPosix(t, dir, sub_path, options); }, else => |e| return e, }; } -fn dirMakeOpenPathWindows( +fn dirCreateDirPathOpenWindows( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions, options: Dir.OpenOptions, -) Dir.MakeOpenPathError!Dir { +) Dir.CreateDirPathOpenError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const w = windows; @@ -1795,18 +1795,18 @@ fn dirMakeOpenPathWindows( } } -fn dirMakeOpenPathWasi( +fn dirCreateDirPathOpenWasi( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions, options: Dir.OpenOptions, -) Dir.MakeOpenPathError!Dir { +) Dir.CreateDirPathOpenError!Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); const t_io = ioBasic(t); return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) { error.FileNotFound => { - _ = try dir.makePathStatus(t_io, sub_path, permissions); + _ = try dir.createDirPathStatus(t_io, sub_path, permissions); return dirOpenDirWasi(t, dir, sub_path, options); }, else => |e| return e, @@ -3352,11 +3352,6 @@ pub fn dirOpenDirWindows( } } -const MakeOpenDirAccessMaskWOptions = struct { - no_follow: bool, - create_disposition: u32, -}; - fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 644a6fc52d45d082377ccadd3a23671ad42f0fa2..bcb9048e0e05428a7288c25d292d37251b000dee 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -226,7 +226,7 @@ test "Dir.readLink" { // test 3: relative path symlink const parent_file = ".." ++ Dir.path.sep_str ++ "target.txt"; const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file); - var subdir = try ctx.dir.makeOpenPath(io, "subdir", .{}); + var subdir = try ctx.dir.createDirPathOpen(io, "subdir", .{}); defer subdir.close(io); try setupSymlink(io, subdir, canonical_parent_file, "relative-link.txt", .{}); try testReadLink(io, subdir, canonical_parent_file, "relative-link.txt"); @@ -411,7 +411,7 @@ test "openDir non-cwd parent '..'" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - var subdir = try tmp.dir.makeOpenPath(io, "subdir", .{}); + var subdir = try tmp.dir.createDirPathOpen(io, "subdir", .{}); defer subdir.close(io); var dir = try subdir.openDir(io, "..", .{}); @@ -613,13 +613,13 @@ test "Dir.Iterator but dir is deleted during iteration" { defer tmp.cleanup(); // Create directory and setup an iterator for it - var subdir = try tmp.dir.makeOpenPath(io, "subdir", .{ .open_options = .{ .iterate = true } }); + var subdir = try tmp.dir.createDirPathOpen(io, "subdir", .{ .open_options = .{ .iterate = true } }); defer subdir.close(io); var iterator = subdir.iterate(); // Create something to iterate over within the subdir - try tmp.dir.makePath(io, "subdir" ++ Dir.path.sep_str ++ "b"); + try tmp.dir.createDirPath(io, "subdir" ++ Dir.path.sep_str ++ "b"); // Then, before iterating, delete the directory that we're iterating. // This is a contrived reproduction, but this could happen outside of the program, in another thread, etc. @@ -862,13 +862,13 @@ test "file operations on directories" { }.impl); } -test "makeOpenPath parent dirs do not exist" { +test "createDirPathOpen parent dirs do not exist" { const io = testing.io; var tmp_dir = tmpDir(.{}); defer tmp_dir.cleanup(); - var dir = try tmp_dir.dir.makeOpenPath(io, "root_dir/parent_dir/some_dir", .{}); + var dir = try tmp_dir.dir.createDirPathOpen(io, "root_dir/parent_dir/some_dir", .{}); dir.close(io); // double check that the full directory structure was created @@ -1016,7 +1016,7 @@ test "Dir.rename directory onto non-empty dir" { try ctx.dir.createDir(io, test_dir_path, .default_dir); - var target_dir = try ctx.dir.makeOpenPath(io, target_dir_path, .{}); + var target_dir = try ctx.dir.createDirPathOpen(io, target_dir_path, .{}); var file = try target_dir.createFile(io, "test_file", .{ .read = true }); file.close(io); target_dir.close(io); @@ -1155,9 +1155,9 @@ test "deleteTree does not follow symlinks" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makePath(io, "b"); + try tmp.dir.createDirPath(io, "b"); { - var a = try tmp.dir.makeOpenPath(io, "a", .{}); + var a = try tmp.dir.createDirPathOpen(io, "a", .{}); defer a.close(io); try setupSymlink(io, a, "../b", "b", .{ .is_directory = true }); @@ -1184,7 +1184,7 @@ test "deleteTree on a symlink" { try tmp.dir.access(io, "file", .{}); // Symlink to a directory - try tmp.dir.makePath(io, "dir"); + try tmp.dir.createDirPath(io, "dir"); try setupSymlink(io, tmp.dir, "dir", "dirlink", .{ .is_directory = true }); try tmp.dir.deleteTree(io, "dirlink"); @@ -1192,14 +1192,14 @@ test "deleteTree on a symlink" { try tmp.dir.access(io, "dir", .{}); } -test "makePath, put some files in it, deleteTree" { +test "createDirPath, put some files in it, deleteTree" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { const io = ctx.io; const allocator = ctx.arena.allocator(); const dir_path = try ctx.transformPath("os_test_tmp"); - try ctx.dir.makePath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); + try ctx.dir.createDirPath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); try ctx.dir.writeFile(io, .{ .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), .data = "nonsense", @@ -1215,14 +1215,14 @@ test "makePath, put some files in it, deleteTree" { }.impl); } -test "makePath, put some files in it, deleteTreeMinStackSize" { +test "createDirPath, put some files in it, deleteTreeMinStackSize" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { const io = ctx.io; const allocator = ctx.arena.allocator(); const dir_path = try ctx.transformPath("os_test_tmp"); - try ctx.dir.makePath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); + try ctx.dir.createDirPath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" })); try ctx.dir.writeFile(io, .{ .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }), .data = "nonsense", @@ -1238,7 +1238,7 @@ test "makePath, put some files in it, deleteTreeMinStackSize" { }.impl); } -test "makePath in a directory that no longer exists" { +test "createDirPath in a directory that no longer exists" { if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir const io = testing.io; @@ -1247,10 +1247,10 @@ test "makePath in a directory that no longer exists" { defer tmp.cleanup(); try tmp.parent_dir.deleteTree(io, &tmp.sub_path); - try expectError(error.FileNotFound, tmp.dir.makePath(io, "sub-path")); + try expectError(error.FileNotFound, tmp.dir.createDirPath(io, "sub-path")); } -test "makePath but sub_path contains pre-existing file" { +test "createDirPath but sub_path contains pre-existing file" { const io = testing.io; var tmp = tmpDir(.{}); @@ -1259,7 +1259,7 @@ test "makePath but sub_path contains pre-existing file" { try tmp.dir.createDir(io, "foo", .default_dir); try tmp.dir.writeFile(io, .{ .sub_path = "foo/bar", .data = "" }); - try expectError(error.NotDir, tmp.dir.makePath(io, "foo/bar/baz")); + try expectError(error.NotDir, tmp.dir.createDirPath(io, "foo/bar/baz")); } fn expectDir(io: Io, dir: Dir, path: []const u8) !void { @@ -1279,7 +1279,7 @@ test "makepath existing directories" { try tmpA.createDir(io, "B", .default_dir); const testPath = "A" ++ Dir.path.sep_str ++ "B" ++ Dir.path.sep_str ++ "C"; - try tmp.dir.makePath(io, testPath); + try tmp.dir.createDirPath(io, testPath); try expectDir(io, tmp.dir, testPath); } @@ -1293,7 +1293,7 @@ test "makepath through existing valid symlink" { try tmp.dir.createDir(io, "realfolder", .default_dir); try setupSymlink(io, tmp.dir, "." ++ Dir.path.sep_str ++ "realfolder", "working-symlink", .{}); - try tmp.dir.makePath(io, "working-symlink" ++ Dir.path.sep_str ++ "in-realfolder"); + try tmp.dir.createDirPath(io, "working-symlink" ++ Dir.path.sep_str ++ "in-realfolder"); try expectDir(io, tmp.dir, "realfolder" ++ Dir.path.sep_str ++ "in-realfolder"); } @@ -1309,7 +1309,7 @@ test "makepath relative walks" { }); defer testing.allocator.free(relPath); - try tmp.dir.makePath(io, relPath); + try tmp.dir.createDirPath(io, relPath); // How .. is handled is different on Windows than non-Windows switch (native_os) { @@ -1348,7 +1348,7 @@ test "makepath ignores '.'" { }); defer testing.allocator.free(expectedPath); - try tmp.dir.makePath(io, dotPath); + try tmp.dir.createDirPath(io, dotPath); try expectDir(io, tmp.dir, expectedPath); } @@ -1358,7 +1358,7 @@ fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8, max { try iterable_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" }); - var maxed_dir = try iterable_dir.makeOpenPath(io, maxed_dirname, .{}); + var maxed_dir = try iterable_dir.createDirPathOpen(io, maxed_dirname, .{}); defer maxed_dir.close(io); try maxed_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" }); @@ -1511,7 +1511,7 @@ test "access file" { const dir_path = try ctx.transformPath("os_test_tmp"); const file_path = try ctx.transformPath("os_test_tmp" ++ Dir.path.sep_str ++ "file.txt"); - try ctx.dir.makePath(io, dir_path); + try ctx.dir.createDirPath(io, dir_path); try expectError(error.FileNotFound, ctx.dir.access(io, file_path, .{})); try ctx.dir.writeFile(io, .{ .sub_path = file_path, .data = "" }); @@ -1527,7 +1527,7 @@ test "sendfile" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makePath(io, "os_test_tmp"); + try tmp.dir.createDirPath(io, "os_test_tmp"); var dir = try tmp.dir.openDir(io, "os_test_tmp", .{}); defer dir.close(io); @@ -1574,7 +1574,7 @@ test "sendfile with buffered data" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.makePath(io, "os_test_tmp"); + try tmp.dir.createDirPath(io, "os_test_tmp"); var dir = try tmp.dir.openDir(io, "os_test_tmp", .{}); defer dir.close(io); @@ -1851,7 +1851,7 @@ test "walker" { }); for (expected_paths.keys()) |key| { - try tmp.dir.makePath(io, key); + try tmp.dir.createDirPath(io, key); } var walker = try tmp.dir.walk(testing.allocator); @@ -1913,7 +1913,7 @@ test "selective walker, skip entries that start with ." { }); for (paths_to_create) |path| { - try tmp.dir.makePath(io, path); + try tmp.dir.createDirPath(io, path); } var walker = try tmp.dir.walkSelectively(testing.allocator); @@ -1959,8 +1959,8 @@ test "walker without fully iterating" { // Create 2 directories inside the tmp directory, but then only iterate once before breaking. // This ensures that walker doesn't try to close the initial directory when not fully iterating. - try tmp.dir.makePath(io, "a"); - try tmp.dir.makePath(io, "b"); + try tmp.dir.createDirPath(io, "a"); + try tmp.dir.createDirPath(io, "b"); var num_walked: usize = 0; while (try walker.next(io)) |_| { @@ -2109,8 +2109,8 @@ test "invalid UTF-8/WTF-8 paths" { try expectError(expected_err, ctx.dir.createDir(io, invalid_path, .default_dir)); - try expectError(expected_err, ctx.dir.makePath(io, invalid_path)); - try expectError(expected_err, ctx.dir.makeOpenPath(io, invalid_path, .{})); + try expectError(expected_err, ctx.dir.createDirPath(io, invalid_path)); + try expectError(expected_err, ctx.dir.createDirPathOpen(io, invalid_path, .{})); try expectError(expected_err, ctx.dir.openDir(io, invalid_path, .{})); @@ -2574,7 +2574,7 @@ test "hard link with different directories" { const target_name = "link-target"; const link_name = "newlink"; - const subdir = try tmp.dir.makeOpenPath(io, "subdir", .{}); + const subdir = try tmp.dir.createDirPathOpen(io, "subdir", .{}); defer tmp.dir.deleteFile(io, target_name) catch {}; try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" }); diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 3997bc70cd5ee3bcc0141be8a3cb38cd8b34d263..133dfc0293faf42923f3bb41a3d5f87efd88cb41 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1079,7 +1079,7 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDir } } -pub const MakeDirError = std.Io.Dir.MakeError; +pub const MakeDirError = std.Io.Dir.CreateDirError; /// Create a directory. /// `mode` is ignored on Windows and WASI. diff --git a/lib/std/tar.zig b/lib/std/tar.zig index d45aa9944324b8f6441d7e1bed7b4b2bae19a6b3..024a425919af7544339f9ee731f7947bd2d86c07 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -606,7 +606,7 @@ pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOp switch (file.kind) { .directory => { if (file_name.len > 0 and !options.exclude_empty_directories) { - try dir.makePath(io, file_name); + try dir.createDirPath(io, file_name); } }, .file => { @@ -642,7 +642,7 @@ fn createDirAndFile(io: Io, dir: Io.Dir, file_name: []const u8, permissions: Io. const fs_file = dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions }) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { - try dir.makePath(io, dir_name); + try dir.createDirPath(io, dir_name); return try dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions }); } } @@ -656,7 +656,7 @@ fn createDirAndSymlink(io: Io, dir: Io.Dir, link_name: []const u8, file_name: [] dir.symLink(io, link_name, file_name, .{}) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { - try dir.makePath(io, dir_name); + try dir.createDirPath(io, dir_name); return try dir.symLink(io, link_name, file_name, .{}); } } diff --git a/lib/std/testing.zig b/lib/std/testing.zig index f3dd114af3c0c4a47ac7d1c707943b5876e4be91..5b5ec852e9b3c5ac062051b62ac39c51119f7893 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -635,12 +635,12 @@ pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir { _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); const cwd = Io.Dir.cwd(); - var cache_dir = cwd.makeOpenPath(io, ".zig-cache", .{}) catch + var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); defer cache_dir.close(io); - const parent_dir = cache_dir.makeOpenPath(io, "tmp", .{}) catch + const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); - const dir = parent_dir.makeOpenPath(io, &sub_path, .{ .open_options = opts }) catch + const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); return .{ diff --git a/lib/std/zip.zig b/lib/std/zip.zig index 770bfd8ae7ab69e008fc13b37f1298b4429d2598..ff95587e7a83a4e96aad33dc78358599e527a61e 100644 --- a/lib/std/zip.zig +++ b/lib/std/zip.zig @@ -554,13 +554,13 @@ pub const Iterator = struct { if (filename[filename.len - 1] == '/') { if (self.uncompressed_size != 0) return error.ZipBadDirectorySize; - try dest.makePath(io, filename[0 .. filename.len - 1]); + try dest.createDirPath(io, filename[0 .. filename.len - 1]); return; } const out_file = blk: { if (std.fs.path.dirname(filename)) |dirname| { - var parent_dir = try dest.makeOpenPath(io, dirname, .{}); + var parent_dir = try dest.createDirPathOpen(io, dirname, .{}); defer parent_dir.close(io); const basename = std.fs.path.basename(filename); diff --git a/src/Compilation.zig b/src/Compilation.zig index dc254a36c51f46d3d6df6d0e96cc929f8dfb6c2d..8b840b8d452913c6ed7389621841dffe004703e0 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -832,7 +832,7 @@ pub const Directories = struct { const nonempty_path = if (path.len == 0) "." else path; const handle_or_err = switch (thing) { .@"zig lib" => Io.Dir.cwd().openDir(io, nonempty_path, .{}), - .@"global cache", .@"local cache" => Io.Dir.cwd().makeOpenPath(io, nonempty_path, .{}), + .@"global cache", .@"local cache" => Io.Dir.cwd().createDirPathOpen(io, nonempty_path, .{}), }; return .{ .path = if (path.len == 0) null else path, @@ -1879,7 +1879,7 @@ pub const CreateDiagnostic = union(enum) { pub const CreateCachePath = struct { which: enum { local, global }, sub: []const u8, - err: (Io.Dir.MakeError || Io.Dir.OpenError || Io.Dir.StatFileError), + err: (Io.Dir.CreateDirError || Io.Dir.OpenError || Io.Dir.StatFileError), }; pub fn format(diag: CreateDiagnostic, w: *Writer) Writer.Error!void { switch (diag) { @@ -2120,7 +2120,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, cache.* = .{ .gpa = gpa, .io = io, - .manifest_dir = options.dirs.local_cache.handle.makeOpenPath(io, "h", .{}) catch |err| { + .manifest_dir = options.dirs.local_cache.handle.createDirPathOpen(io, "h", .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } }); }, }; @@ -2170,7 +2170,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, // to redundantly happen for each AstGen operation. const zir_sub_dir = "z"; - var local_zir_dir = options.dirs.local_cache.handle.makeOpenPath(io, zir_sub_dir, .{}) catch |err| { + var local_zir_dir = options.dirs.local_cache.handle.createDirPathOpen(io, zir_sub_dir, .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = zir_sub_dir, .err = err } }); }; errdefer local_zir_dir.close(io); @@ -2178,7 +2178,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .handle = local_zir_dir, .path = try options.dirs.local_cache.join(arena, &.{zir_sub_dir}), }; - var global_zir_dir = options.dirs.global_cache.handle.makeOpenPath(io, zir_sub_dir, .{}) catch |err| { + var global_zir_dir = options.dirs.global_cache.handle.createDirPathOpen(io, zir_sub_dir, .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .global, .sub = zir_sub_dir, .err = err } }); }; errdefer global_zir_dir.close(io); @@ -2449,7 +2449,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, const digest = hash.final(); const artifact_sub_dir = "o" ++ fs.path.sep_str ++ digest; - var artifact_dir = options.dirs.local_cache.handle.makeOpenPath(io, artifact_sub_dir, .{}) catch |err| { + var artifact_dir = options.dirs.local_cache.handle.createDirPathOpen(io, artifact_sub_dir, .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = artifact_sub_dir, .err = err } }); }; errdefer artifact_dir.close(io); @@ -2917,7 +2917,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE tmp_dir_rand_int = std.crypto.random.int(u64); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); - const handle = comp.dirs.local_cache.handle.makeOpenPath(io, tmp_dir_sub_path, .{}) catch |err| { + const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| { return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err }); }; break :d .{ .path = path, .handle = handle }; @@ -2998,7 +2998,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE tmp_dir_rand_int = std.crypto.random.int(u64); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); - const handle = comp.dirs.local_cache.handle.makeOpenPath(io, tmp_dir_sub_path, .{}) catch |err| { + const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| { return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err }); }; break :d .{ .path = path, .handle = handle }; @@ -3437,7 +3437,7 @@ fn renameTmpIntoCache( continue; }, error.FileNotFound => { - try cache_directory.handle.makePath(io, "o"); + try cache_directory.handle.createDirPath(io, "o"); continue; }, else => |e| return e, @@ -5276,7 +5276,7 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { const io = comp.io; const docs_path = comp.resolveEmitPath(comp.emit_docs.?); - var out_dir = docs_path.root_dir.handle.makeOpenPath(io, docs_path.sub_path, .{}) catch |err| { + var out_dir = docs_path.root_dir.handle.createDirPathOpen(io, docs_path.sub_path, .{}) catch |err| { return comp.lockAndSetMiscFailure( .docs_copy, "unable to create output directory '{f}': {s}", @@ -5513,7 +5513,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU assert(docs_bin_file.sub_path.len > 0); // emitted binary is not a directory const docs_path = comp.resolveEmitPath(comp.emit_docs.?); - var out_dir = docs_path.root_dir.handle.makeOpenPath(io, docs_path.sub_path, .{}) catch |err| { + var out_dir = docs_path.root_dir.handle.createDirPathOpen(io, docs_path.sub_path, .{}) catch |err| { comp.lockAndSetMiscFailure( .docs_copy, "unable to create output directory '{f}': {t}", @@ -5705,7 +5705,7 @@ pub fn translateC( const tmp_basename = std.fmt.hex(std.crypto.random.int(u64)); const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename; const cache_dir = comp.dirs.local_cache.handle; - var cache_tmp_dir = try cache_dir.makeOpenPath(io, tmp_sub_path, .{}); + var cache_tmp_dir = try cache_dir.createDirPathOpen(io, tmp_sub_path, .{}); defer cache_tmp_dir.close(io); const translated_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, translated_basename }); @@ -6280,7 +6280,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // We can't know the digest until we do the C compiler invocation, // so we need a temporary filename. const out_obj_path = try comp.tmpFilePath(arena, o_basename); - var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath(io, "tmp", .{}); + var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{}); defer zig_cache_tmp_dir.close(io); const out_diag_path = if (comp.clang_passthrough_mode or !ext.clangSupportsDiagnostics()) @@ -6445,7 +6445,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // Rename into place. const digest = man.final(); const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest }); - var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(io, o_sub_path, .{}); + var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{}); defer o_dir.close(io); const tmp_basename = fs.path.basename(out_obj_path); try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename, io); @@ -6534,7 +6534,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 const digest = man.final(); const o_sub_path = try fs.path.join(arena, &.{ "o", &digest }); - var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(io, o_sub_path, .{}); + var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{}); defer o_dir.close(io); const in_rc_path = try comp.dirs.local_cache.join(comp.gpa, &.{ @@ -6622,7 +6622,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 const rc_basename_noext = src_basename[0 .. src_basename.len - fs.path.extension(src_basename).len]; const digest = if (try man.hit()) man.final() else blk: { - var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath(io, "tmp", .{}); + var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{}); defer zig_cache_tmp_dir.close(io); const res_filename = try std.fmt.allocPrint(arena, "{s}.res", .{rc_basename_noext}); @@ -6693,7 +6693,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 // Rename into place. const digest = man.final(); const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest }); - var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(io, o_sub_path, .{}); + var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{}); defer o_dir.close(io); const tmp_basename = fs.path.basename(out_res_path); try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename, io); diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 45fcd66b8c9e3f6f0aaac2517635cedf5726dfe8..8a11736e42150ece0e0f610a9230f8d77af60062 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -500,7 +500,7 @@ fn runResource( var tmp_directory: Cache.Directory = .{ .path = tmp_directory_path, .handle = handle: { - const dir = cache_root.handle.makeOpenPath(io, tmp_dir_sub_path, .{ + const dir = cache_root.handle.createDirPathOpen(io, tmp_dir_sub_path, .{ .open_options = .{ .iterate = true }, }) catch |err| { try eb.addRootErrorMessage(.{ @@ -524,7 +524,7 @@ fn runResource( if (native_os == .linux and f.job_queue.work_around_btrfs_bug) { // https://github.com/ziglang/zig/issues/17095 pkg_path.root_dir.handle.close(io); - pkg_path.root_dir.handle = cache_root.handle.makeOpenPath(io, tmp_dir_sub_path, .{ + pkg_path.root_dir.handle = cache_root.handle.createDirPathOpen(io, tmp_dir_sub_path, .{ .open_options = .{ .iterate = true }, }) catch @panic("btrfs workaround failed"); } @@ -1366,7 +1366,7 @@ fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!U // we do not attempt to replicate the exact structure of a real .git // directory, since that isn't relevant for fetching a package. { - var pack_dir = try out_dir.makeOpenPath(io, ".git", .{}); + var pack_dir = try out_dir.createDirPathOpen(io, ".git", .{}); defer pack_dir.close(io); var pack_file = try pack_dir.createFile(io, "pkg.pack", .{ .read = true }); defer pack_file.close(io); @@ -1427,7 +1427,7 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void .file => { dir.copyFile(entry.path, tmp_dir, entry.path, io, .{}) catch |err| switch (err) { error.FileNotFound => { - if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(io, dirname); + if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.createDirPath(io, dirname); try dir.copyFile(entry.path, tmp_dir, entry.path, io, .{}); }, else => |e| return e, @@ -1440,7 +1440,7 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void // the destination directory, fail with an error instead. tmp_dir.symLink(io, link_name, entry.path, .{}) catch |err| switch (err) { error.FileNotFound => { - if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(io, dirname); + if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.createDirPath(io, dirname); try tmp_dir.symLink(io, link_name, entry.path, .{}); }, else => |e| return e, @@ -2250,7 +2250,7 @@ const TestFetchBuilder = struct { cache_parent_dir: std.Io.Dir, path_or_url: []const u8, ) !*Fetch { - const cache_dir = try cache_parent_dir.makeOpenPath(io, "zig-global-cache", .{}); + const cache_dir = try cache_parent_dir.createDirPathOpen(io, "zig-global-cache", .{}); self.http_client = .{ .allocator = allocator, .io = io }; self.global_cache_directory = .{ .handle = cache_dir, .path = null }; diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index eb3d2a447e508ccb19316036ced8bbcf17b6edb3..f241f768f603f34a85ea5f79647cb9074dec6781 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -1720,10 +1720,10 @@ pub fn main() !void { var pack_file_reader = pack_file.reader(io, &pack_file_buffer); const commit = try Oid.parse(format, args[3]); - var worktree = try Io.Dir.cwd().makeOpenPath(io, args[4], .{}); + var worktree = try Io.Dir.cwd().createDirPathOpen(io, args[4], .{}); defer worktree.close(io); - var git_dir = try worktree.makeOpenPath(io, ".git", .{}); + var git_dir = try worktree.createDirPathOpen(io, ".git", .{}); defer git_dir.close(io); std.debug.print("Starting index...\n", .{}); diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index 44676007e2faee7ee91d79a7cf6ccffb85415da3..ba85f45830c26385f7d9cbc6af73fa735d6f2eb3 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -444,7 +444,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye var cache: Cache = .{ .gpa = gpa, .io = io, - .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath(io, "h", .{}), + .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}), }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); @@ -477,7 +477,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); var o_directory: Cache.Directory = .{ - .handle = try comp.dirs.global_cache.handle.makeOpenPath(io, o_sub_path, .{}), + .handle = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}), .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}), }; defer o_directory.handle.close(io); diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index 8371b3288d2293da2221ae6fd7839b1010d0328b..e9b6ce18823fe6b684f3636a0219e8840c607f54 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -679,7 +679,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye var cache: Cache = .{ .gpa = gpa, .io = io, - .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath(io, "h", .{}), + .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}), }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); @@ -712,7 +712,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); var o_directory: Cache.Directory = .{ - .handle = try comp.dirs.global_cache.handle.makeOpenPath(io, o_sub_path, .{}), + .handle = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}), .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}), }; defer o_directory.handle.close(io); diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index c4b002f983ad66b7db3f8d1679ade064d169cd8b..03ed917c4fe7830d42fe59d5ce82f9b6118b39a6 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -258,7 +258,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { var cache: Cache = .{ .gpa = gpa, .io = io, - .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath(io, "h", .{}), + .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}), }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); @@ -297,7 +297,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { const digest = man.final(); const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); - var o_dir = try comp.dirs.global_cache.handle.makeOpenPath(io, o_sub_path, .{}); + var o_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}); defer o_dir.close(io); const aro = @import("aro"); diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index 9c09c35b0a106dc84ba6f2848e4621ce15340825..9e4213d23726dd67a1b27fa4dcefe5c01ca347c2 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -385,7 +385,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye var cache: Cache = .{ .gpa = gpa, .io = io, - .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath(io, "h", .{}), + .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}), }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); @@ -418,7 +418,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); var o_directory: Cache.Directory = .{ - .handle = try comp.dirs.global_cache.handle.makeOpenPath(io, o_sub_path, .{}), + .handle = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}), .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}), }; defer o_directory.handle.close(io); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 513c857dbf62bcbc051dec536d96c5ebeef10982..97493ada32eb471e23b4cdcf325d11a366d7d2fc 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3319,7 +3319,7 @@ pub fn reopenDebugInfo(self: *MachO) !void { ); defer gpa.free(d_sym_path); - var d_sym_bundle = try self.base.emit.root_dir.handle.makeOpenPath(io, d_sym_path, .{}); + var d_sym_bundle = try self.base.emit.root_dir.handle.createDirPathOpen(io, d_sym_path, .{}); defer d_sym_bundle.close(io); self.d_sym.?.file = try d_sym_bundle.createFile(io, fs.path.basename(self.base.emit.sub_path), .{ diff --git a/src/main.zig b/src/main.zig index fb2a8cfe0fc0a82b885402763734f8c7efeef151..507b01ab0eaf6377da8d807e5ee476ae745800e8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3381,7 +3381,7 @@ fn buildOutputType( const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{ std.crypto.random.int(u64), ext.canonicalName(target), }); - try dirs.local_cache.handle.makePath(io, "tmp"); + try dirs.local_cache.handle.createDirPath(io, "tmp"); // Note that in one of the happy paths, execve() is used to switch to // clang in which case any cleanup logic that exists for this temporary @@ -6955,7 +6955,7 @@ fn cmdFetch( var global_cache_directory: Directory = l: { const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena); break :l .{ - .handle = try Io.Dir.cwd().makeOpenPath(io, p, .{}), + .handle = try Io.Dir.cwd().createDirPathOpen(io, p, .{}), .path = p, }; }; @@ -7201,7 +7201,7 @@ fn createDependenciesModule( const rand_int = std.crypto.random.int(u64); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); { - var tmp_dir = try dirs.local_cache.handle.makeOpenPath(io, tmp_dir_sub_path, .{}); + var tmp_dir = try dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}); defer tmp_dir.close(io); try tmp_dir.writeFile(io, .{ .sub_path = basename, .data = source }); } @@ -7396,7 +7396,7 @@ const Templates = struct { fingerprint: Package.Fingerprint, ) !void { if (fs.path.dirname(template_path)) |dirname| { - out_dir.makePath(io, dirname) catch |err| { + out_dir.createDirPath(io, dirname) catch |err| { fatal("unable to make path '{s}': {t}", .{ dirname, err }); }; } -- 2.54.0 From 64de4a7371e3851dddf39c608d08d480395bb0ab Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 16:48:35 -0800 Subject: [PATCH 112/195] std.Io.Threaded: remove handling of ECANCELED The only known situation in which this occurs is when using musl with the undocumented extension PTHREAD_CANCEL_MASKED, causing the next syscall to return ECANCELED. However zig std lib does not use this mechanism, even when targeting musl libc because it would require each async task to be on a fresh pthread. For the same reason, if third party code were to cause ECANCELED to be returned from any of these syscalls, it would cause subsequent tasks to be incorrectly canceled since they cannot be rearmed. Thus, this Io implementation cannot handle this error code correctly, expecting never to receive it. --- lib/std/Io/Threaded.zig | 113 +--------------------------------------- 1 file changed, 1 insertion(+), 112 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 4afd8d75d15596cf6a5a94672e7315253e45f689..499b58565d724358008678f543b264c3c176335e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -216,13 +216,6 @@ const Thread = struct { ) orelse return; } - fn endSyscallCanceled(thread: *Thread) Io.Cancelable { - if (thread.current_closure) |closure| { - @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); - } - return error.Canceled; - } - fn currentSignalId() SignaleeId { return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); } @@ -1531,7 +1524,6 @@ fn dirCreateDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, perm try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1574,7 +1566,6 @@ fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permi try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1875,7 +1866,6 @@ fn dirStatFileLinux( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1925,7 +1915,6 @@ fn posixStatFile(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]cons try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1986,7 +1975,6 @@ fn dirStatFileWasi( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2026,7 +2014,6 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2077,7 +2064,6 @@ fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2130,7 +2116,6 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2215,7 +2200,6 @@ fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2267,7 +2251,6 @@ fn dirAccessPosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2317,7 +2300,6 @@ fn dirAccessWasi( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2464,7 +2446,6 @@ fn dirCreateFilePosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2519,7 +2500,6 @@ fn dirCreateFilePosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2686,7 +2666,6 @@ fn dirCreateFileWasi( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2783,7 +2762,6 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2849,7 +2827,6 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2878,7 +2855,6 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2899,7 +2875,6 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -3104,7 +3079,6 @@ fn dirOpenFileWasi( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3203,7 +3177,6 @@ fn dirOpenDirPosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3256,7 +3229,6 @@ fn dirOpenDirHaiku( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3398,7 +3370,6 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3508,7 +3479,6 @@ fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Di try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3585,7 +3555,6 @@ fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.R try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3680,7 +3649,6 @@ fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3957,7 +3925,6 @@ fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, o try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4045,7 +4012,6 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4082,7 +4048,6 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4115,7 +4080,6 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4163,7 +4127,6 @@ fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir. try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4208,7 +4171,6 @@ fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), // Some systems return permission errors when trying to delete a // directory, so we need to handle that case specifically and // translate the error. @@ -4225,7 +4187,6 @@ fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir break; }, .INTR => continue, - .CANCELED => return current_thread.endSyscallCanceled(), else => { current_thread.endSyscall(); return error.PermissionDenied; @@ -4443,7 +4404,6 @@ fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.D try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4488,7 +4448,6 @@ fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir. try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4654,7 +4613,6 @@ fn dirRenameWasi( while (true) { 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)) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -4709,7 +4667,6 @@ fn dirRenamePosix( while (true) { switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -4890,7 +4847,6 @@ fn dirSymLinkWasi( while (true) { switch (std.os.wasi.path_symlink(target_path.ptr, target_path.len, dir.handle, sym_link_path.ptr, sym_link_path.len)) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -4943,7 +4899,6 @@ fn dirSymLinkPosix( while (true) { switch (posix.errno(posix.system.symlinkat(target_path_posix, dir.handle, sym_link_path_posix))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5010,7 +4965,6 @@ fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer current_thread.endSyscall(); return n; }, - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5052,7 +5006,6 @@ fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffe const len: usize = @bitCast(rc); return len; }, - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5137,7 +5090,6 @@ fn posixFchmodat( posix.system.fchmodat(dir_fd, path, mode); switch (posix.errno(rc)) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5176,7 +5128,6 @@ fn posixFchmodat( while (true) { switch (std.os.linux.errno(std.os.linux.fchmodat2(dir_fd, path, mode, flags))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5252,7 +5203,6 @@ fn posixFchown(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: p while (true) { switch (posix.errno(posix.system.fchown(fd, uid, gid))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5333,7 +5283,6 @@ fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void { while (true) { switch (posix.errno(posix.system.fsync(file.handle))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5361,7 +5310,6 @@ fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void { while (true) { switch (std.os.wasi.fd_sync(file.handle)) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5405,7 +5353,6 @@ fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool { current_thread.endSyscall(); return true; }, - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5445,7 +5392,6 @@ fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool { current_thread.endSyscall(); return true; }, - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5627,7 +5573,6 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE while (true) { switch (std.os.wasi.fd_filestat_set_size(file.handle, length)) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5653,7 +5598,6 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE while (true) { switch (posix.errno(ftruncate_sym(file.handle, signed_len))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5723,7 +5667,6 @@ fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode while (true) { switch (posix.errno(posix.system.fchmod(fd, mode))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5782,7 +5725,6 @@ fn dirSetTimestamps( while (true) { switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, ×, flags))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5829,7 +5771,6 @@ fn dirSetTimestampsNow( while (true) { switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, null, flags))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5890,7 +5831,6 @@ fn fileSetTimestamps( .MTIM = true, })) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5915,7 +5855,6 @@ fn fileSetTimestamps( while (true) { switch (posix.errno(posix.system.futimens(file.handle, ×))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5952,7 +5891,6 @@ fn fileSetTimestampsNow(userdata: ?*anyopaque, file: File) File.SetTimestampsErr .MTIM_NOW = true, })) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -5977,7 +5915,6 @@ fn fileSetTimestampsNow(userdata: ?*anyopaque, file: File) File.SetTimestampsErr while (true) { switch (posix.errno(posix.system.futimens(file.handle, null))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -6061,7 +5998,6 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v while (true) { switch (posix.errno(posix.system.flock(file.handle, operation))) { .SUCCESS => return current_thread.endSyscall(), - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -6143,7 +6079,6 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro current_thread.endSyscall(); return true; }, - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -6259,7 +6194,6 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError! current_thread.endSyscall(); return; }, - .CANCELED => return current_thread.endSyscallCanceled(), .INTR => { try current_thread.checkCancel(); continue; @@ -6328,7 +6262,6 @@ fn dirOpenDirWasi( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6387,7 +6320,6 @@ fn dirHardLink( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6437,7 +6369,6 @@ fn dirHardLink( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6506,7 +6437,6 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6540,7 +6470,6 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6627,7 +6556,6 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8 try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6665,7 +6593,6 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8 try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6759,7 +6686,6 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6793,7 +6719,6 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6823,7 +6748,6 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6862,7 +6786,6 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6898,7 +6821,6 @@ fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekEr try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -6925,7 +6847,6 @@ fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekEr try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -7011,7 +6932,6 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -7040,7 +6960,6 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -7194,7 +7113,6 @@ fn fileWritePositional( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -7232,7 +7150,6 @@ fn fileWritePositional( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -7316,7 +7233,6 @@ fn fileWriteStreaming( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -7351,7 +7267,6 @@ fn fileWriteStreaming( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -7619,7 +7534,6 @@ fn fileWriteFileStreaming( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); assert(error.Unexpected == switch (e) { @@ -7692,7 +7606,6 @@ fn fileWriteFileStreaming( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), .OPNOTSUPP, .INVAL, .NOSYS => { // Give calling code chance to observe before trying // something else. @@ -7869,7 +7782,6 @@ fn fileWriteFilePositional( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), .OPNOTSUPP, .INVAL, .NOSYS => { // Give calling code chance to observe before trying // something else. @@ -8098,7 +8010,6 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -8176,7 +8087,6 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), // This prong handles success as well as unexpected errors. else => return current_thread.endSyscall(), } @@ -8250,7 +8160,6 @@ fn netListenIpPosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -8414,7 +8323,6 @@ fn netListenUnixPosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -8538,7 +8446,6 @@ fn posixBindUnix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -8583,7 +8490,6 @@ fn posixBind( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -8619,7 +8525,6 @@ fn posixConnect( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -8666,7 +8571,6 @@ fn posixConnectUnix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -8711,7 +8615,6 @@ fn posixGetSockName( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -8779,7 +8682,6 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -9109,7 +9011,6 @@ fn openSocketPosix( switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, .INTR => continue, - .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -9123,7 +9024,6 @@ fn openSocketPosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -9225,7 +9125,6 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -9334,7 +9233,6 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -9367,7 +9265,6 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -9603,7 +9500,6 @@ fn netSendOne( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -9680,7 +9576,6 @@ fn netSendMany( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -9813,7 +9708,6 @@ fn netReceivePosix( continue :recv; }, .INTR => continue, - .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, .FAULT => |err| return .{ errnoBug(err), message_i }, .INVAL => |err| return .{ errnoBug(err), message_i }, @@ -9822,7 +9716,6 @@ fn netReceivePosix( } }, .INTR => continue, - .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, .BADF => |err| return .{ errnoBug(err), message_i }, .NFILE => return .{ error.SystemFdQuotaExceeded, message_i }, @@ -9942,7 +9835,6 @@ fn netWritePosix( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -10040,8 +9932,7 @@ fn netWriteWindows( else => |err| err, }; switch (wsa_error) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return current_thread.endSyscallCanceled(), + .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, .NOTINITIALISED => { try initializeWsa(t); continue; @@ -10160,7 +10051,6 @@ fn netInterfaceNameResolve( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -10464,7 +10354,6 @@ fn netLookupFallible( try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); return posix.unexpectedErrno(e); -- 2.54.0 From e41342af83022202c6e413cd84c12b41f19c681a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 17:29:26 -0800 Subject: [PATCH 113/195] std.Io.Threaded: reinstate fchmodat fallback Stephen Gregoratto (original author of the fallback) says: I read through both libcs again to compare: Musl: - `stat` path. Check error. - If path is a symlink, return `OPNOTSUPP`. - `openat` path as `O_PATH|O_NOFOLLOW|O_CLOEXEC`. Return `OPNOTSUPP` if we got `ELOOP`. - Build procfs filename. - `stat` procfs file. - If procfs file is a symlink, return `OPNOTSUPP`. - close path fd. Glibc: - open path as `O_PATH|O_NOFOLLOW|O_CLOEXEC`. - fstatat path fd. - If path is a symlink, return `OPNOTSUPP`. - Build procfs filename. - chmod procfs filename. Return `OPNOTSUPP` if we got `ENOENT`. - close path fd. I prefer glibc since you open the path first, which avoids a possible TOCTOU race. --- lib/std/Io/Threaded.zig | 125 +++++++++++++++++++++++++++++++++++----- 1 file changed, 111 insertions(+), 14 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 499b58565d724358008678f543b264c3c176335e..53febe07dbeabbb296eebbc6aaf0fec7e0a53d52 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2091,7 +2091,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { try current_thread.beginSyscall(); while (true) { var statx = std.mem.zeroes(linux.Statx); - const rc = sys.statx( + switch (sys.errno(sys.statx( file.handle, "", linux.AT.EMPTY_PATH, @@ -2106,8 +2106,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { .NLINK = true, }, &statx, - ); - switch (sys.errno(rc)) { + ))) { .SUCCESS => { current_thread.endSyscall(); return statFromLinux(&statx); @@ -5120,7 +5119,7 @@ fn posixFchmodat( } if (@atomicLoad(UseFchmodat2, &t.use_fchmodat2, .monotonic) == .disabled) - return fchmodatFallback(current_thread, dir_fd, path, mode, flags); + return fchmodatFallback(current_thread, dir_fd, path, mode); comptime assert(native_os == .linux); @@ -5149,7 +5148,7 @@ fn posixFchmodat( .ROFS => return error.ReadOnlyFileSystem, .NOSYS => { @atomicStore(UseFchmodat2, &t.use_fchmodat2, .disabled, .monotonic); - return fchmodatFallback(current_thread, dir_fd, path, mode, flags); + return fchmodatFallback(current_thread, dir_fd, path, mode); }, else => |err| return posix.unexpectedErrno(err), } @@ -5163,16 +5162,114 @@ fn fchmodatFallback( dir_fd: posix.fd_t, path: [*:0]const u8, mode: posix.mode_t, - flags: u32, ) Dir.SetFilePermissionsError!void { - _ = current_thread; - _ = dir_fd; - _ = path; - _ = mode; - _ = flags; - // I deleted the previous fallback implementation because it looked wrong to me. Please cross-reference - // fhmodat.c in musl libc before blindly restoring the implementation. - @panic("TODO"); + comptime assert(native_os == .linux); + const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) + .{ .major = 30, .minor = 0, .patch = 0 } + else + .{ .major = 2, .minor = 28, .patch = 0 }); + const sys = if (use_c) std.c else std.os.linux; + + // Fallback to changing permissions using procfs: + // + // 1. Open `path` as a `PATH` descriptor. + // 2. Stat the fd and check if it isn't a symbolic link. + // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`. + // 4. Pass the procfs path to `chmod` with the `mode`. + try current_thread.beginSyscall(); + const path_fd: posix.fd_t = while (true) { + const rc = posix.system.openat(dir_fd, path, .{ + .PATH = true, + .NOFOLLOW = true, + .CLOEXEC = true, + }, @as(posix.mode_t, 0)); + switch (posix.errno(rc)) { + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + }; + defer posix.close(path_fd); + + try current_thread.beginSyscall(); + const path_mode = while (true) { + var statx = std.mem.zeroes(std.os.linux.Statx); + switch (sys.errno(sys.statx(path_fd, "", posix.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) { + .SUCCESS => { + current_thread.endSyscall(); + assert(statx.mask.TYPE); + break statx.mode; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .LOOP => return error.SymLinkLoop, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + }; + + // Even though we only wanted TYPE, the kernel can still fill in the additional bits. + if ((path_mode & posix.S.IFMT) == posix.S.IFLNK) + return error.OperationUnsupported; + + var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; + const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, "/proc/self/fd/{d}", .{path_fd}, 0) catch unreachable; + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.chmod(proc_path, mode))) { + .SUCCESS => return current_thread.endSyscall(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .NOENT => return error.OperationUnsupported, // procfs not mounted. + .BADF => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ACCES => return error.AccessDenied, + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.FileNotFound, + .PERM => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } } const dirSetOwner = switch (native_os) { -- 2.54.0 From 7423d6a4044bd2c92ebfe5023bd4f3ad2789c793 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 17:38:19 -0800 Subject: [PATCH 114/195] fix "hello world" 14K -> 57K --- test/standalone/simple/hello_world/hello.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/standalone/simple/hello_world/hello.zig b/test/standalone/simple/hello_world/hello.zig index 37c5c4bb1bf2e8204b3b7b87788b1dbc6af80220..d7083942304c4cc24aa71cbfa45221b95c490ff5 100644 --- a/test/standalone/simple/hello_world/hello.zig +++ b/test/standalone/simple/hello_world/hello.zig @@ -1,5 +1,8 @@ const std = @import("std"); +var static_single_threaded_io: std.Io.Threaded = .init_single_threaded; +const io = static_single_threaded_io.ioBasic(); + pub fn main() !void { - try std.Io.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } -- 2.54.0 From 98f05a0f53d08683e4b8127210448c0de00dc9cf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 18:11:17 -0800 Subject: [PATCH 115/195] compiler: update calls to realpath --- src/link/MachO.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 97493ada32eb471e23b4cdcf325d11a366d7d2fc..b747b3de56700fd4771b01e62995077d67026951 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1221,7 +1221,7 @@ fn parseDependentDylibs(self: *MachO) !void { try checked_paths.append(rel_path); var buffer: [fs.max_path_bytes]u8 = undefined; // TODO don't use realpath - const full_path = buffer[0 .. Io.Dir.realPathAbsolute(io, rel_path, &buffer) catch continue]; + const full_path = buffer[0 .. Io.Dir.realPathFileAbsolute(io, rel_path, &buffer) catch continue]; break :full_path try arena.dupe(u8, full_path); } } else if (eatPrefix(id.name, "@loader_path/")) |_| { @@ -1235,7 +1235,7 @@ fn parseDependentDylibs(self: *MachO) !void { try checked_paths.append(try arena.dupe(u8, id.name)); var buffer: [fs.max_path_bytes]u8 = undefined; // TODO don't use realpath - if (Io.Dir.realPathAbsolute(io, id.name, &buffer)) |full_path_n| { + if (Io.Dir.realPathFileAbsolute(io, id.name, &buffer)) |full_path_n| { break :full_path try arena.dupe(u8, buffer[0..full_path_n]); } else |_| { try self.reportMissingDependencyError( -- 2.54.0 From 99f25bfc23a902d7cf047ae2b95e1d9d7e3b09ee Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 18:16:47 -0800 Subject: [PATCH 116/195] std.Io: implement directory reading for WASI --- lib/std/Io/Dir.zig | 2 + lib/std/Io/Threaded.zig | 111 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 54cce082da040992f0b15ca88906adfcda902bc1..60cdd88453d3f05241207412df41cf6b19688ef0 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -106,6 +106,8 @@ pub const Reader = struct { .linux => std.mem.alignForward(usize, @sizeOf(std.os.linux.dirent64), 8) + std.mem.alignForward(usize, max_name_bytes, 8), .windows => std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)), + .wasi => @sizeOf(std.os.wasi.dirent_t) + + std.mem.alignForward(usize, max_name_bytes, @alignOf(std.os.wasi.dirent_t)), else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)), }; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 53febe07dbeabbb296eebbc6aaf0fec7e0a53d52..db4c5f3fda98fa2d18f237a96b19ef23bd5937d3 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3378,7 +3378,7 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir // To be consistent across platforms, iteration // ends if the directory being iterated is deleted // during iteration. This matches the behavior of - // non-Linux UNIX platforms. + // non-Linux, non-WASI UNIX platforms. .NOENT => { dr.state = .finished; return 0; @@ -3481,7 +3481,7 @@ fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Di else => |e| { current_thread.endSyscall(); switch (e) { - .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability + .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. .FAULT => |err| return errnoBug(err), .NOTDIR => |err| return errnoBug(err), .INVAL => |err| return errnoBug(err), @@ -3839,10 +3839,109 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D } fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { - _ = userdata; - _ = dr; - _ = buffer; - @panic("TODO"); + // We intentinally use fd_readdir even when linked with libc, since its + // implementation is exactly the same as below, and we avoid the code + // complexity here. + const wasi = std.os.wasi; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + const Header = extern struct { + cookie: u64, + }; + const header: *align(@alignOf(usize)) Header = @ptrCast(dr.buffer.ptr); + const header_end: usize = @sizeOf(Header); + if (dr.index < header_end) { + // Initialize header. + dr.index = header_end; + dr.end = header_end; + header.* = .{ .cookie = wasi.DIRCOOKIE_START }; + } + var buffer_index: usize = 0; + while (buffer.len - buffer_index != 0) { + // According to the WASI spec, the last entry might be truncated, so we + // need to check if the remaining buffer contains the whole dirent. + if (dr.end - dr.index < @sizeOf(wasi.dirent_t)) { + // Refill the buffer, unless we've already created references to + // buffered data. + if (buffer_index != 0) break; + if (dr.state == .reset) { + header.* = .{ .cookie = wasi.DIRCOOKIE_START }; + dr.state = .reading; + } + const dents_buffer = dr.buffer[header_end..]; + var n: usize = undefined; + try current_thread.beginSyscall(); + while (true) { + switch (wasi.fd_readdir(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, header.cookie, &n)) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. + .FAULT => |err| return errnoBug(err), + .NOTDIR => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + // To be consistent across platforms, iteration + // ends if the directory being iterated is deleted + // during iteration. This matches the behavior of + // non-Linux, non-WASI UNIX platforms. + .NOENT => { + dr.state = .finished; + return 0; + }, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + if (n == 0) { + dr.state = .finished; + return 0; + } + dr.index = header_end; + dr.end = header_end + n; + } + const entry: *align(1) wasi.dirent_t = @ptrCast(&dr.buffer[dr.index]); + const entry_size = @sizeOf(wasi.dirent_t); + const name_index = dr.index + entry_size; + if (name_index + entry.namlen > dr.end) { + // This case, the name is truncated, so we need to call readdir to store the entire name. + dr.end = dr.index; // Force fd_readdir in the next loop. + continue; + } + const name = dr.buffer[name_index..][0..entry.namlen]; + const next_index = name_index + entry.namlen; + dr.index = next_index; + header.cookie = entry.next; + + if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) + continue; + + const entry_kind: File.Kind = switch (entry.type) { + .BLOCK_DEVICE => .block_device, + .CHARACTER_DEVICE => .character_device, + .DIRECTORY => .directory, + .SYMBOLIC_LINK => .sym_link, + .REGULAR_FILE => .file, + .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, + else => .unknown, + }; + buffer[buffer_index] = .{ + .name = name, + .kind = entry_kind, + .inode = entry.ino, + }; + buffer_index += 1; + } + return buffer_index; } fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { -- 2.54.0 From 2e4a6c88b535f049fd52a84d9b9e510cf70a42fd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 18:58:52 -0800 Subject: [PATCH 117/195] compiler: fix test-unit compilation failures --- src/Package/Fetch.zig | 39 +++++++++++++++++++++------------------ src/Package/Fetch/git.zig | 4 ++-- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 8a11736e42150ece0e0f610a9230f8d77af60062..f8e4b832939f286969961e36bad1f02290f06771 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -2040,7 +2040,7 @@ const UnpackResult = struct { defer errors.deinit(gpa); var aw: Io.Writer.Allocating = .init(gpa); defer aw.deinit(); - try errors.renderToWriter(.{}, &aw.writer, .no_color); + try errors.renderToWriter(.{}, &aw.writer); try std.testing.expectEqualStrings( \\error: unable to unpack \\ note: unable to create symlink from 'dir2/file2' to 'filename': SymlinkError @@ -2210,13 +2210,13 @@ test "set executable bit based on file content" { defer out.close(io); const S = std.posix.S; // expect executable bit not set - try std.testing.expect((try out.statFile(io, "file1", .{})).mode & S.IXUSR == 0); - try std.testing.expect((try out.statFile(io, "script_without_shebang", .{})).mode & S.IXUSR == 0); + try std.testing.expect((try out.statFile(io, "file1", .{})).permissions.toMode() & S.IXUSR == 0); + try std.testing.expect((try out.statFile(io, "script_without_shebang", .{})).permissions.toMode() & S.IXUSR == 0); // expect executable bit set - try std.testing.expect((try out.statFile(io, "hello", .{})).mode & S.IXUSR != 0); - try std.testing.expect((try out.statFile(io, "script", .{})).mode & S.IXUSR != 0); - try std.testing.expect((try out.statFile(io, "script_with_shebang_without_exec_bit", .{})).mode & S.IXUSR != 0); - try std.testing.expect((try out.statFile(io, "hello_ln", .{})).mode & S.IXUSR != 0); + try std.testing.expect((try out.statFile(io, "hello", .{})).permissions.toMode() & S.IXUSR != 0); + try std.testing.expect((try out.statFile(io, "script", .{})).permissions.toMode() & S.IXUSR != 0); + try std.testing.expect((try out.statFile(io, "script_with_shebang_without_exec_bit", .{})).permissions.toMode() & S.IXUSR != 0); + try std.testing.expect((try out.statFile(io, "hello_ln", .{})).permissions.toMode() & S.IXUSR != 0); // // $ ls -al zig-cache/tmp/OCz9ovUcstDjTC_U/zig-global-cache/p/1220fecb4c06a9da8673c87fe8810e15785f1699212f01728eadce094d21effeeef3 @@ -2233,7 +2233,7 @@ fn saveEmbedFile(io: Io, comptime tarball_name: []const u8, dir: Io.Dir) !void { const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name); var tmp_file = try dir.createFile(io, tarball_name, .{}); defer tmp_file.close(io); - try tmp_file.writeAll(tarball_content); + try tmp_file.writeStreamingAll(io, tarball_content); } // Builds Fetch with required dependencies, clears dependencies on deinit(). @@ -2316,21 +2316,22 @@ const TestFetchBuilder = struct { // expected_files must be sorted. fn expectPackageFiles(self: *TestFetchBuilder, expected_files: []const []const u8) !void { const io = self.job_queue.io; + const gpa = std.testing.allocator; var package_dir = try self.packageDir(); defer package_dir.close(io); var actual_files: std.ArrayList([]u8) = .empty; - defer actual_files.deinit(std.testing.allocator); - defer for (actual_files.items) |file| std.testing.allocator.free(file); - var walker = try package_dir.walk(std.testing.allocator); + defer actual_files.deinit(gpa); + defer for (actual_files.items) |file| gpa.free(file); + var walker = try package_dir.walk(gpa); defer walker.deinit(); - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { if (entry.kind != .file) continue; - const path = try std.testing.allocator.dupe(u8, entry.path); - errdefer std.testing.allocator.free(path); + const path = try gpa.dupe(u8, entry.path); + errdefer gpa.free(path); std.mem.replaceScalar(u8, path, std.fs.path.sep, '/'); - try actual_files.append(std.testing.allocator, path); + try actual_files.append(gpa, path); } std.mem.sortUnstable([]u8, actual_files.items, {}, struct { fn lessThan(_: void, a: []u8, b: []u8) bool { @@ -2347,17 +2348,19 @@ const TestFetchBuilder = struct { // Test helper, asserts that fetch has failed with `msg` error message. fn expectFetchErrors(self: *TestFetchBuilder, notes_len: usize, msg: []const u8) !void { + const gpa = std.testing.allocator; + var errors = try self.fetch.error_bundle.toOwnedBundle(""); - defer errors.deinit(std.testing.allocator); + defer errors.deinit(gpa); const em = errors.getErrorMessage(errors.getMessages()[0]); try std.testing.expectEqual(1, em.count); if (notes_len > 0) { try std.testing.expectEqual(notes_len, em.notes_len); } - var aw: Io.Writer.Allocating = .init(std.testing.allocator); + var aw: Io.Writer.Allocating = .init(gpa); defer aw.deinit(); - try errors.renderToWriter(.{}, &aw.writer, .no_color); + try errors.renderToWriter(.{}, &aw.writer); try std.testing.expectEqualStrings(msg, aw.written()); } }; diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index f241f768f603f34a85ea5f79647cb9074dec6781..0cb7e6230080e081c2152dc1a9ec0966c15275e6 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -1586,7 +1586,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u defer git_dir.cleanup(); var pack_file = try git_dir.dir.createFile(io, "testrepo.pack", .{ .read = true }); defer pack_file.close(io); - try pack_file.writeAll(testrepo_pack); + try pack_file.writeStreamingAll(io, testrepo_pack); var pack_file_buffer: [2000]u8 = undefined; var pack_file_reader = pack_file.reader(io, &pack_file_buffer); @@ -1648,7 +1648,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u defer for (actual_files.items) |file| testing.allocator.free(file); var walker = try worktree.dir.walk(testing.allocator); defer walker.deinit(); - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { if (entry.kind != .file) continue; const path = try testing.allocator.dupe(u8, entry.path); errdefer testing.allocator.free(path); -- 2.54.0 From 7d955274bb40bc937275b3d59c6304dd44c85d40 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 19:15:05 -0800 Subject: [PATCH 118/195] update the init templates to new std API --- lib/init/src/main.zig | 26 ++++++++++++++++++++++++-- lib/init/src/root.zig | 19 +++++++------------ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/lib/init/src/main.zig b/lib/init/src/main.zig index 88dd8348e1ff4ff9fdad70126d0f2b075428312a..9df441b56468877f4f8a1a1869827fb6b4c44677 100644 --- a/lib/init/src/main.zig +++ b/lib/init/src/main.zig @@ -1,10 +1,32 @@ const std = @import("std"); +const Io = std.Io; + const _NAME = @import(".NAME"); pub fn main() !void { - // Prints to stderr, ignoring potential errors. + // Prints to stderr, unbuffered, ignoring potential errors. std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); - try _NAME.bufferedPrint(); + + // In order to allocate memory we must construct an `Allocator` instance. + var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + defer _ = debug_allocator.deinit(); // This checks for leaks. + const gpa = debug_allocator.allocator(); + + // In order to do I/O operations we must construct an `Io` instance. + var threaded: std.Io.Threaded = .init(gpa); + defer threaded.deinit(); + const io = threaded.io(); + + // Stdout is for the actual output of your application, for example if you + // are implementing gzip, then only the compressed bytes should be sent to + // stdout, not any debugging messages. + var stdout_buffer: [1024]u8 = undefined; + var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer); + const stdout_writer = &stdout_file_writer.interface; + + try _NAME.printAnotherMessage(stdout_writer); + + try stdout_writer.flush(); // Don't forget to flush! } test "simple test" { diff --git a/lib/init/src/root.zig b/lib/init/src/root.zig index 94c7cd01194b2195b8f5c866fa640fc90a07bb76..5a7125032bf25a4262eca6166cd8d5e2a246a4a1 100644 --- a/lib/init/src/root.zig +++ b/lib/init/src/root.zig @@ -1,17 +1,12 @@ -//! By convention, root.zig is the root source file when making a library. +//! By convention, root.zig is the root source file when making a package. const std = @import("std"); +const Io = std.Io; -pub fn bufferedPrint() !void { - // Stdout is for the actual output of your application, for example if you - // are implementing gzip, then only the compressed bytes should be sent to - // stdout, not any debugging messages. - var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); - const stdout = &stdout_writer.interface; - - try stdout.print("Run `zig build test` to run the tests.\n", .{}); - - try stdout.flush(); // Don't forget to flush! +/// This is a documentation comment to explain the `printAnotherMessage` function below. +/// +/// Accepting an `Io.Writer` instance is a handy way to write reusable code. +pub fn printAnotherMessage(writer: *Io.Writer) Io.Writer.Error!void { + try writer.print("Run `zig build test` to run the tests.\n", .{}); } pub fn add(a: i32, b: i32) i32 { -- 2.54.0 From 50c585227ed2a57a4c1cf3f3b44914881999559d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 19:42:52 -0800 Subject: [PATCH 119/195] fix compilation of incr-check --- tools/docgen.zig | 6 +- tools/doctest.zig | 14 ++--- tools/fetch_them_macos_headers.zig | 42 ++++++------- tools/gen_macos_headers_c.zig | 22 ++++--- tools/generate_linux_syscalls.zig | 10 ++- tools/incr-check.zig | 98 ++++++++++++++++-------------- tools/migrate_langref.zig | 25 ++++---- tools/process_headers.zig | 33 ++++++---- tools/update-linux-headers.zig | 37 ++++++----- tools/update_cpu_features.zig | 30 ++++----- tools/update_crc_catalog.zig | 31 ++++++---- tools/update_freebsd_libc.zig | 17 +++--- tools/update_glibc.zig | 45 ++++++-------- tools/update_mingw.zig | 40 ++++++------ tools/update_netbsd_libc.zig | 25 ++++---- 15 files changed, 262 insertions(+), 213 deletions(-) diff --git a/tools/docgen.zig b/tools/docgen.zig index d23892e06c894bec959b6704b01b20437b34e5fa..11cd40acde4b77bd14a9118a4177c2321066b22c 100644 --- a/tools/docgen.zig +++ b/tools/docgen.zig @@ -73,15 +73,15 @@ pub fn main() !void { const code_dir_path = opt_code_dir orelse fatal("missing --code-dir argument", .{}); var in_file = try fs.cwd().openFile(input_path, .{}); - defer in_file.close(); + defer in_file.close(io); var out_file = try fs.cwd().createFile(output_path, .{}); - defer out_file.close(); + defer out_file.close(io); var out_file_buffer: [4096]u8 = undefined; var out_file_writer = out_file.writer(&out_file_buffer); var code_dir = try fs.cwd().openDir(code_dir_path, .{}); - defer code_dir.close(); + defer code_dir.close(io); var in_file_reader = in_file.reader(io, &.{}); const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .limited(max_doc_file_size)); diff --git a/tools/doctest.zig b/tools/doctest.zig index 63b7e50778f85c51b2299cae0d9e36a475e4c6a9..b7219c53f0110a2329e9a5a32ed0eb928e5960c9 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -85,16 +85,16 @@ pub fn main() !void { const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{ cache_root, std.crypto.random.int(u64), }); - fs.cwd().makePath(tmp_dir_path) catch |err| - fatal("unable to create tmp dir '{s}': {s}", .{ tmp_dir_path, @errorName(err) }); - defer fs.cwd().deleteTree(tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {s}", .{ - tmp_dir_path, @errorName(err), + fs.cwd().createDirPath(io, tmp_dir_path) catch |err| + fatal("unable to create tmp dir '{s}': {t}", .{ tmp_dir_path, err }); + defer fs.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{ + tmp_dir_path, err, }); - var out_file = try fs.cwd().createFile(output_path, .{}); - defer out_file.close(); + var out_file = try fs.cwd().createFile(io, output_path, .{}); + defer out_file.close(io); var out_file_buffer: [4096]u8 = undefined; - var out_file_writer = out_file.writer(&out_file_buffer); + var out_file_writer = out_file.writer(io, &out_file_buffer); const out = &out_file_writer.interface; diff --git a/tools/fetch_them_macos_headers.zig b/tools/fetch_them_macos_headers.zig index 2a2a2452e77e063e56d6738117b7e171cbf085c4..6b254954723fb659fce060446f5ca0c8b9e42410 100644 --- a/tools/fetch_them_macos_headers.zig +++ b/tools/fetch_them_macos_headers.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Io = std.Io; -const fs = std.fs; +const Dir = std.Io.Dir; const mem = std.mem; const process = std.process; const assert = std.debug.assert; @@ -96,9 +96,9 @@ pub fn main() anyerror!void { fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{}); }; - var sdk_dir = try std.fs.cwd().openDir(sysroot_path, .{}); - defer sdk_dir.close(); - const sdk_info = try sdk_dir.readFileAlloc("SDKSettings.json", allocator, .limited(std.math.maxInt(u32))); + var sdk_dir = try Dir.cwd().openDir(io, sysroot_path, .{}); + defer sdk_dir.close(io); + const sdk_info = try sdk_dir.readFileAlloc(io, "SDKSettings.json", allocator, .limited(std.math.maxInt(u32))); const parsed_json = try std.json.parseFromSlice(struct { DefaultProperties: struct { MACOSX_DEPLOYMENT_TARGET: []const u8 }, @@ -135,8 +135,8 @@ fn fetchTarget( const tmp_filename = "macos-headers"; const headers_list_filename = "macos-headers.o.d"; const tmp_path = try tmp.dir.realpathAlloc(arena, "."); - const tmp_file_path = try fs.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename }); - const headers_list_path = try fs.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename }); + const tmp_file_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename }); + const headers_list_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename }); const macos_version = try std.fmt.allocPrint(arena, "-mmacosx-version-min={d}.{d}", .{ ver.major, @@ -176,10 +176,10 @@ fn fetchTarget( } // Read in the contents of `macos-headers.o.d` - const headers_list_file = try tmp.dir.openFile(headers_list_filename, .{}); - defer headers_list_file.close(); + const headers_list_file = try tmp.dir.openFile(io, headers_list_filename, .{}); + defer headers_list_file.close(io); - var headers_dir = fs.cwd().openDir(headers_source_prefix, .{}) catch |err| switch (err) { + var headers_dir = Dir.cwd().openDir(headers_source_prefix, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, => fatal("path '{s}' not found or not a directory. Did you accidentally delete it?", .{ @@ -187,13 +187,13 @@ fn fetchTarget( }), else => return err, }; - defer headers_dir.close(); + defer headers_dir.close(io); const dest_path = try target.fullName(arena); - try headers_dir.deleteTree(dest_path); + try headers_dir.deleteTree(io, dest_path); - var dest_dir = try headers_dir.makeOpenPath(dest_path, .{}); - var dirs = std.StringHashMap(fs.Dir).init(arena); + var dest_dir = try headers_dir.createDirPathOpen(io, dest_path, .{}); + var dirs = std.StringHashMap(Dir).init(arena); try dirs.putNoClobber(".", dest_dir); var headers_list_file_reader = headers_list_file.reader(io, &.{}); @@ -206,25 +206,25 @@ fn fetchTarget( if (mem.lastIndexOf(u8, line, prefix[0..])) |idx| { const out_rel_path = line[idx + prefix.len + 1 ..]; const out_rel_path_stripped = mem.trim(u8, out_rel_path, " \\"); - const dirname = fs.path.dirname(out_rel_path_stripped) orelse "."; + const dirname = Dir.path.dirname(out_rel_path_stripped) orelse "."; const maybe_dir = try dirs.getOrPut(dirname); if (!maybe_dir.found_existing) { - maybe_dir.value_ptr.* = try dest_dir.makeOpenPath(dirname, .{}); + maybe_dir.value_ptr.* = try dest_dir.createDirPathOpen(io, dirname, .{}); } - const basename = fs.path.basename(out_rel_path_stripped); + const basename = Dir.path.basename(out_rel_path_stripped); const line_stripped = mem.trim(u8, line, " \\"); - const abs_dirname = fs.path.dirname(line_stripped).?; - var orig_subdir = try fs.cwd().openDir(abs_dirname, .{}); - defer orig_subdir.close(); + const abs_dirname = Dir.path.dirname(line_stripped).?; + var orig_subdir = try Dir.cwd().openDir(abs_dirname, .{}); + defer orig_subdir.close(io); try orig_subdir.copyFile(basename, maybe_dir.value_ptr.*, basename, .{}); } } var dir_it = dirs.iterator(); - while (dir_it.next()) |entry| { - entry.value_ptr.close(); + while (dir_it.next(io)) |entry| { + entry.value_ptr.close(io); } } diff --git a/tools/gen_macos_headers_c.zig b/tools/gen_macos_headers_c.zig index fe036cf6b7e322b8c2c9d8da6c590002e9546395..62ae815da31c3ae8720011f4788076b3d3084ebb 100644 --- a/tools/gen_macos_headers_c.zig +++ b/tools/gen_macos_headers_c.zig @@ -1,8 +1,9 @@ const std = @import("std"); +const Io = std.Io; +const Dir = std.Io.Dir; const assert = std.debug.assert; const info = std.log.info; const fatal = std.process.fatal; - const Allocator = std.mem.Allocator; var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; @@ -20,6 +21,10 @@ pub fn main() anyerror!void { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); + var threaded: Io.Threaded = .init(gpa); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(arena); if (args.len == 1) fatal("no command or option specified", .{}); @@ -33,10 +38,10 @@ pub fn main() anyerror!void { if (positionals.items.len != 1) fatal("expected one positional argument: [dir]", .{}); - var dir = try std.fs.cwd().openDir(positionals.items[0], .{ .follow_symlinks = false }); - defer dir.close(); + var dir = try std.fs.cwd().openDir(io, positionals.items[0], .{ .follow_symlinks = false }); + defer dir.close(io); var paths = std.array_list.Managed([]const u8).init(arena); - try findHeaders(arena, dir, "", &paths); + try findHeaders(arena, io, dir, "", &paths); const SortFn = struct { pub fn lessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool { @@ -64,7 +69,8 @@ pub fn main() anyerror!void { fn findHeaders( arena: Allocator, - dir: std.fs.Dir, + io: Io, + dir: Dir, prefix: []const u8, paths: *std.array_list.Managed([]const u8), ) anyerror!void { @@ -73,9 +79,9 @@ fn findHeaders( switch (entry.kind) { .directory => { const path = try std.fs.path.join(arena, &.{ prefix, entry.name }); - var subdir = try dir.openDir(entry.name, .{ .follow_symlinks = false }); - defer subdir.close(); - try findHeaders(arena, subdir, path, paths); + var subdir = try dir.openDir(io, entry.name, .{ .follow_symlinks = false }); + defer subdir.close(io); + try findHeaders(arena, io, subdir, path, paths); }, .file, .sym_link => { const ext = std.fs.path.extension(entry.name); diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index e009715e2f6579dd383e800761a3b37311c3f94a..b3e5d623778108651e0c05aa5c0e5c81c4924bb9 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -175,6 +175,10 @@ pub fn main() !void { defer arena.deinit(); const gpa = arena.allocator(); + var threaded: Io.Threaded = .init(gpa); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(gpa); if (args.len < 2 or mem.eql(u8, args[1], "--help")) { const w, _ = std.debug.lockStderrWriter(&.{}); @@ -188,8 +192,8 @@ pub fn main() !void { var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer); const stdout = &stdout_writer.interface; - var linux_dir = try std.fs.cwd().openDir(linux_path, .{}); - defer linux_dir.close(); + var linux_dir = try std.fs.cwd().openDir(io, linux_path, .{}); + defer linux_dir.close(io); // As of 6.11, the largest table is 24195 bytes. // 32k should be enough for now. @@ -198,7 +202,7 @@ pub fn main() !void { // Fetch the kernel version from the Makefile variables. const version = blk: { - const head = try linux_dir.readFile("Makefile", buf[0..128]); + const head = try linux_dir.readFile(io, "Makefile", buf[0..128]); var lines = mem.tokenizeScalar(u8, head, '\n'); _ = lines.next(); // Skip SPDX identifier diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 5023fb85dd7402967b92321fcf3553c59dac7127..b9c9eb0e92757a6319205dd216b497c507fced1e 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -1,5 +1,6 @@ const std = @import("std"); const Io = std.Io; +const Dir = std.Io.Dir; const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; @@ -59,7 +60,7 @@ pub fn main() !void { const zig_exe = opt_zig_exe orelse fatal("missing path to zig\n{s}", .{usage}); const input_file_name = opt_input_file_name orelse fatal("missing input file\n{s}", .{usage}); - const input_file_bytes = try std.fs.cwd().readFileAlloc(input_file_name, arena, .limited(std.math.maxInt(u32))); + const input_file_bytes = try Dir.cwd().readFileAlloc(io, input_file_name, arena, .limited(std.math.maxInt(u32))); const case = try Case.parse(arena, io, input_file_bytes); // Check now: if there are any targets using the `cbe` backend, we need the lib dir. @@ -71,25 +72,25 @@ pub fn main() !void { } } - const prog_node = std.Progress.start(.{}); + const prog_node = std.Progress.start(io, .{}); defer prog_node.end(); const rand_int = std.crypto.random.int(u64); const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int); - var tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{}); + var tmp_dir = try Dir.cwd().createDirPathOpen(io, tmp_dir_path, .{}); defer { - tmp_dir.close(); + tmp_dir.close(io); if (!preserve_tmp) { - std.fs.cwd().deleteTree(tmp_dir_path) catch |err| { - std.log.warn("failed to delete tree '{s}': {s}", .{ tmp_dir_path, @errorName(err) }); + Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| { + std.log.warn("failed to delete tree '{s}': {t}", .{ tmp_dir_path, err }); }; } } // Convert paths to be relative to the cwd of the subprocess. - const resolved_zig_exe = try std.fs.path.relative(arena, tmp_dir_path, zig_exe); + const resolved_zig_exe = try Dir.path.relative(arena, tmp_dir_path, zig_exe); const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir| - try std.fs.path.relative(arena, tmp_dir_path, lib_dir) + try Dir.path.relative(arena, tmp_dir_path, lib_dir) else null; @@ -164,7 +165,7 @@ pub fn main() !void { var cc_child_args: std.ArrayList([]const u8) = .empty; if (target.backend == .cbe) { const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe| - try std.fs.path.relative(arena, tmp_dir_path, cc_zig_exe) + try Dir.path.relative(arena, tmp_dir_path, cc_zig_exe) else resolved_zig_exe; @@ -185,6 +186,7 @@ pub fn main() !void { var eval: Eval = .{ .arena = arena, + .io = io, .case = case, .host = host, .target = target, @@ -196,9 +198,9 @@ pub fn main() !void { .cc_child_args = &cc_child_args, }; - try child.spawn(); + try child.spawn(io); errdefer { - _ = child.kill() catch {}; + _ = child.kill(io) catch {}; } var poller = Io.poll(arena, Eval.StreamEnum, .{ @@ -228,10 +230,11 @@ pub fn main() !void { const Eval = struct { arena: Allocator, + io: Io, host: std.Target, case: Case, target: Case.Target, - tmp_dir: std.fs.Dir, + tmp_dir: Dir, tmp_dir_path: []const u8, child: *std.process.Child, allow_stderr: bool, @@ -245,17 +248,18 @@ const Eval = struct { /// Currently this function assumes the previous updates have already been written. fn write(eval: *Eval, update: Case.Update) void { + const io = eval.io; for (update.changes) |full_contents| { - eval.tmp_dir.writeFile(.{ + eval.tmp_dir.writeFile(io, .{ .sub_path = full_contents.name, .data = full_contents.bytes, }) catch |err| { - eval.fatal("failed to update '{s}': {s}", .{ full_contents.name, @errorName(err) }); + eval.fatal("failed to update '{s}': {t}", .{ full_contents.name, err }); }; } for (update.deletes) |doomed_name| { - eval.tmp_dir.deleteFile(doomed_name) catch |err| { - eval.fatal("failed to delete '{s}': {s}", .{ doomed_name, @errorName(err) }); + eval.tmp_dir.deleteFile(io, doomed_name) catch |err| { + eval.fatal("failed to delete '{s}': {t}", .{ doomed_name, err }); }; } } @@ -307,14 +311,14 @@ const Eval = struct { } const digest = r.takeArray(Cache.bin_digest_len) catch unreachable; - const result_dir = ".local-cache" ++ std.fs.path.sep_str ++ "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*); + const result_dir = ".local-cache" ++ Dir.path.sep_str ++ "o" ++ Dir.path.sep_str ++ Cache.binToHex(digest.*); const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{ .root_name = "root", // corresponds to the module name "root" .target = &eval.target.resolved, .output_mode = .Exe, }); - const bin_path = try std.fs.path.join(arena, &.{ result_dir, bin_name }); + const bin_path = try Dir.path.join(arena, &.{ result_dir, bin_name }); try eval.checkSuccessOutcome(update, bin_path, prog_node); // This message indicates the end of the update. @@ -338,11 +342,12 @@ const Eval = struct { } fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void { + const io = eval.io; const expected = switch (update.outcome) { .unknown => return, .compile_errors => |ce| ce, .stdout, .exit_code => { - error_bundle.renderToStdErr(.{}, .auto); + try error_bundle.renderToStderr(io, .{}, .auto); eval.fatal("update '{s}': unexpected compile errors", .{update.name}); }, }; @@ -351,7 +356,7 @@ const Eval = struct { for (error_bundle.getMessages()) |err_idx| { if (expected_idx == expected.errors.len) { - error_bundle.renderToStdErr(.{}, .auto); + try error_bundle.renderToStderr(io, .{}, .auto); eval.fatal("update '{s}': more errors than expected", .{update.name}); } try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], false, err_idx); @@ -359,7 +364,7 @@ const Eval = struct { for (error_bundle.getNotes(err_idx)) |note_idx| { if (expected_idx == expected.errors.len) { - error_bundle.renderToStdErr(.{}, .auto); + try error_bundle.renderToStderr(io, .{}, .auto); eval.fatal("update '{s}': more error notes than expected", .{update.name}); } try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], true, note_idx); @@ -368,7 +373,7 @@ const Eval = struct { } if (!std.mem.eql(u8, error_bundle.getCompileLogOutput(), expected.compile_log_output)) { - error_bundle.renderToStdErr(.{}, .auto); + try error_bundle.renderToStderr(io, .{}, .auto); eval.fatal("update '{s}': unexpected compile log output", .{update.name}); } } @@ -388,6 +393,8 @@ const Eval = struct { const src = eb.getSourceLocation(err.src_loc); const raw_filename = eb.nullTerminatedString(src.src_path); + const io = eval.io; + // We need to replace backslashes for consistency between platforms. const filename = name: { if (std.mem.indexOfScalar(u8, raw_filename, '\\') == null) break :name raw_filename; @@ -402,7 +409,7 @@ const Eval = struct { expected.column != src.column + 1 or !std.mem.eql(u8, expected.msg, msg)) { - eb.renderToStdErr(.{}, .auto); + eb.renderToStderr(io, .{}, .auto) catch {}; eval.fatal("update '{s}': compile error did not match expected error", .{update.name}); } } @@ -429,8 +436,11 @@ const Eval = struct { }, }; + const io = eval.io; + var argv_buf: [2][]const u8 = undefined; const argv: []const []const u8, const is_foreign: bool = switch (std.zig.system.getExternalExecutor( + io, &eval.host, &eval.target.resolved, .{ .link_libc = eval.target.backend == .cbe }, @@ -459,8 +469,7 @@ const Eval = struct { const run_prog_node = prog_node.start("run generated executable", 0); defer run_prog_node.end(); - const result = std.process.Child.run(.{ - .allocator = eval.arena, + const result = std.process.Child.run(eval.arena, io, .{ .argv = argv, .cwd_dir = eval.tmp_dir, .cwd = eval.tmp_dir_path, @@ -468,17 +477,17 @@ const Eval = struct { if (is_foreign) { // Chances are the foreign executor isn't available. Skip this evaluation. if (eval.allow_stderr) { - std.log.warn("update '{s}': skipping execution of '{s}' via executor for foreign target '{s}': {s}", .{ + std.log.warn("update '{s}': skipping execution of '{s}' via executor for foreign target '{s}': {t}", .{ update.name, binary_path, try eval.target.resolved.zigTriple(eval.arena), - @errorName(err), + err, }); } return; } - eval.fatal("update '{s}': failed to run the generated executable '{s}': {s}", .{ - update.name, binary_path, @errorName(err), + eval.fatal("update '{s}': failed to run the generated executable '{s}': {t}", .{ + update.name, binary_path, err, }); }; @@ -514,11 +523,12 @@ const Eval = struct { } fn requestUpdate(eval: *Eval) !void { + const io = eval.io; const header: std.zig.Client.Message.Header = .{ .tag = .update, .bytes_len = 0, }; - var w = eval.child.stdin.?.writer(&.{}); + var w = eval.child.stdin.?.writer(io, &.{}); w.interface.writeStruct(header, .little) catch |err| switch (err) { error.WriteFailed => return w.err.?, }; @@ -552,16 +562,13 @@ const Eval = struct { try eval.cc_child_args.appendSlice(eval.arena, &.{ out_path, c_path }); defer eval.cc_child_args.items.len -= 2; - const result = std.process.Child.run(.{ - .allocator = eval.arena, + const result = std.process.Child.run(eval.arena, eval.io, .{ .argv = eval.cc_child_args.items, .cwd_dir = eval.tmp_dir, .cwd = eval.tmp_dir_path, .progress_node = child_prog_node, }) catch |err| { - eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{ - update.name, c_path, @errorName(err), - }); + eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {t}", .{ update.name, c_path, err }); }; switch (result.term) { .Exited => |code| if (code != 0) { @@ -588,12 +595,13 @@ const Eval = struct { } fn fatal(eval: *Eval, comptime fmt: []const u8, args: anytype) noreturn { - eval.tmp_dir.close(); + const io = eval.io; + eval.tmp_dir.close(io); if (!eval.preserve_tmp_on_fatal) { // Kill the child since it holds an open handle to its CWD which is the tmp dir path - _ = eval.child.kill() catch {}; - std.fs.cwd().deleteTree(eval.tmp_dir_path) catch |err| { - std.log.warn("failed to delete tree '{s}': {s}", .{ eval.tmp_dir_path, @errorName(err) }); + _ = eval.child.kill(io) catch {}; + Dir.cwd().deleteTree(io, eval.tmp_dir_path) catch |err| { + std.log.warn("failed to delete tree '{s}': {t}", .{ eval.tmp_dir_path, err }); }; } std.process.fatal(fmt, args); @@ -759,7 +767,7 @@ const Case = struct { if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n}); last_update.outcome = .{ .stdout = std.zig.string_literal.parseAlloc(arena, val) catch |err| { - fatal("line {d}: bad string literal: {s}", .{ line_n, @errorName(err) }); + fatal("line {d}: bad string literal: {t}", .{ line_n, err }); }, }; } else if (std.mem.eql(u8, key, "expect_error")) { @@ -833,27 +841,29 @@ const Case = struct { fn requestExit(child: *std.process.Child, eval: *Eval) void { if (child.stdin == null) return; + const io = eval.io; const header: std.zig.Client.Message.Header = .{ .tag = .exit, .bytes_len = 0, }; - var w = eval.child.stdin.?.writer(&.{}); + var w = eval.child.stdin.?.writer(io, &.{}); w.interface.writeStruct(header, .little) catch |err| switch (err) { error.WriteFailed => switch (w.err.?) { error.BrokenPipe => {}, - else => |e| eval.fatal("failed to send exit: {s}", .{@errorName(e)}), + else => |e| eval.fatal("failed to send exit: {t}", .{e}), }, }; // Send EOF to stdin. - child.stdin.?.close(); + child.stdin.?.close(io); child.stdin = null; } fn waitChild(child: *std.process.Child, eval: *Eval) void { + const io = eval.io; requestExit(child, eval); - const term = child.wait() catch |err| eval.fatal("child process failed: {s}", .{@errorName(err)}); + const term = child.wait(io) catch |err| eval.fatal("child process failed: {t}", .{err}); switch (term) { .Exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}), .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}), diff --git a/tools/migrate_langref.zig b/tools/migrate_langref.zig index 3544cee17545667e793e4f5bd0a29e507ec0ed00..e03e266c2e873c60c0319637f3a18b219671fa9c 100644 --- a/tools/migrate_langref.zig +++ b/tools/migrate_langref.zig @@ -1,13 +1,16 @@ -const std = @import("std"); const builtin = @import("builtin"); -const fs = std.fs; + +const std = @import("std"); +const Io = std.Io; +const Dir = std.Io.Dir; const print = std.debug.print; const mem = std.mem; const testing = std.testing; const Allocator = std.mem.Allocator; -const max_doc_file_size = 10 * 1024 * 1024; const fatal = std.process.fatal; +const max_doc_file_size = 10 * 1024 * 1024; + pub fn main() !void { var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena_instance.deinit(); @@ -23,16 +26,16 @@ pub fn main() !void { defer threaded.deinit(); const io = threaded.io(); - var in_file = try fs.cwd().openFile(input_file, .{ .mode = .read_only }); - defer in_file.close(); + var in_file = try Dir.cwd().openFile(input_file, .{ .mode = .read_only }); + defer in_file.close(io); - var out_file = try fs.cwd().createFile(output_file, .{}); - defer out_file.close(); + var out_file = try Dir.cwd().createFile(output_file, .{}); + defer out_file.close(io); var out_file_buffer: [4096]u8 = undefined; var out_file_writer = out_file.writer(&out_file_buffer); - var out_dir = try fs.cwd().openDir(fs.path.dirname(output_file).?, .{}); - defer out_dir.close(); + var out_dir = try Dir.cwd().openDir(Dir.path.dirname(output_file).?, .{}); + defer out_dir.close(io); var in_file_reader = in_file.reader(io, &.{}); const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .unlimited); @@ -266,7 +269,7 @@ const Code = struct { }; }; -fn walk(arena: Allocator, tokenizer: *Tokenizer, out_dir: std.fs.Dir, w: anytype) !void { +fn walk(arena: Allocator, io: Io, tokenizer: *Tokenizer, out_dir: Dir, w: anytype) !void { while (true) { const token = tokenizer.next(); switch (token.id) { @@ -387,7 +390,7 @@ fn walk(arena: Allocator, tokenizer: *Tokenizer, out_dir: std.fs.Dir, w: anytype var file = out_dir.createFile(basename, .{ .exclusive = true }) catch |err| { fatal("unable to create file '{s}': {s}", .{ name, @errorName(err) }); }; - defer file.close(); + defer file.close(io); var file_buffer: [1024]u8 = undefined; var file_writer = file.writer(&file_buffer); const code = &file_writer.interface; diff --git a/tools/process_headers.zig b/tools/process_headers.zig index c8bfa1ac4b4062387e08d03899c58893e5a79562..4229ac5b4b7d3ed3e01b7ecf9fe4c9ec1b9ffd5d 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -12,6 +12,8 @@ //! You'll then have to manually update Zig source repo with these new files. const std = @import("std"); +const Io = std.Io; +const Dir = std.Io.Dir; const Arch = std.Target.Cpu.Arch; const Abi = std.Target.Abi; const OsTag = std.Target.Os.Tag; @@ -128,6 +130,11 @@ const LibCVendor = enum { pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); const allocator = arena.allocator(); + + var threaded: Io.Threaded = .init(allocator); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(allocator); var search_paths = std.array_list.Managed([]const u8).init(allocator); var opt_out_dir: ?[]const u8 = null; @@ -232,28 +239,28 @@ pub fn main() !void { => &[_][]const u8{ search_path, libc_dir, "usr", "include" }, .musl => &[_][]const u8{ search_path, libc_dir, "usr", "local", "musl", "include" }, }; - const target_include_dir = try std.fs.path.join(allocator, sub_path); + const target_include_dir = try Dir.path.join(allocator, sub_path); var dir_stack = std.array_list.Managed([]const u8).init(allocator); try dir_stack.append(target_include_dir); while (dir_stack.pop()) |full_dir_name| { - var dir = std.fs.cwd().openDir(full_dir_name, .{ .iterate = true }) catch |err| switch (err) { + var dir = Dir.cwd().openDir(io, full_dir_name, .{ .iterate = true }) catch |err| switch (err) { error.FileNotFound => continue :search, error.AccessDenied => continue :search, else => return err, }; - defer dir.close(); + defer dir.close(io); var dir_it = dir.iterate(); while (try dir_it.next()) |entry| { - const full_path = try std.fs.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name }); + const full_path = try Dir.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name }); switch (entry.kind) { .directory => try dir_stack.append(full_path), .file, .sym_link => { - const rel_path = try std.fs.path.relative(allocator, target_include_dir, full_path); + const rel_path = try Dir.path.relative(allocator, target_include_dir, full_path); const max_size = 2 * 1024 * 1024 * 1024; - const raw_bytes = try std.fs.cwd().readFileAlloc(full_path, allocator, .limited(max_size)); + const raw_bytes = try Dir.cwd().readFileAlloc(full_path, allocator, .limited(max_size)); const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t"); total_bytes += raw_bytes.len; const hash = try allocator.alloc(u8, 32); @@ -314,7 +321,7 @@ pub fn main() !void { total_bytes, total_bytes - max_bytes_saved, }); - try std.fs.cwd().makePath(out_dir); + try Dir.cwd().createDirPath(io, out_dir); var missed_opportunity_bytes: usize = 0; // iterate path_table. for each path, put all the hashes into a list. sort by hit_count. @@ -334,9 +341,9 @@ pub fn main() !void { const best_contents = contents_list.pop().?; if (best_contents.hit_count > 1) { // worth it to make it generic - const full_path = try std.fs.path.join(allocator, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* }); - try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?); - try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = best_contents.bytes }); + const full_path = try Dir.path.join(allocator, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* }); + try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?); + try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = best_contents.bytes }); best_contents.is_generic = true; while (contents_list.pop()) |contender| { if (contender.hit_count > 1) { @@ -355,9 +362,9 @@ pub fn main() !void { if (contents.is_generic) continue; const dest_target = hash_kv.key_ptr.*; - const full_path = try std.fs.path.join(allocator, &[_][]const u8{ out_dir, dest_target, path_kv.key_ptr.* }); - try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?); - try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = contents.bytes }); + const full_path = try Dir.path.join(allocator, &[_][]const u8{ out_dir, dest_target, path_kv.key_ptr.* }); + try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?); + try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = contents.bytes }); } } } diff --git a/tools/update-linux-headers.zig b/tools/update-linux-headers.zig index bf9edf0753c2204cb7ccf37cadecad10e11ddaf8..4faf9b27488420355acee603a5585dc83880704e 100644 --- a/tools/update-linux-headers.zig +++ b/tools/update-linux-headers.zig @@ -15,6 +15,8 @@ //! You'll then have to manually update Zig source repo with these new files. const std = @import("std"); +const Io = std.Io; +const Dir = std.Io.Dir; const Arch = std.Target.Cpu.Arch; const Abi = std.Target.Abi; const assert = std.debug.assert; @@ -142,6 +144,11 @@ const PathTable = std.StringHashMap(*TargetToHash); pub fn main() !void { var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); const arena = arena_state.allocator(); + + var threaded: Io.Threaded = .init(arena); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(arena); var search_paths = std.array_list.Managed([]const u8).init(arena); var opt_out_dir: ?[]const u8 = null; @@ -183,30 +190,30 @@ pub fn main() !void { .arch = linux_target.arch, }; search: for (search_paths.items) |search_path| { - const target_include_dir = try std.fs.path.join(arena, &.{ + const target_include_dir = try Dir.path.join(arena, &.{ search_path, linux_target.name, "include", }); var dir_stack = std.array_list.Managed([]const u8).init(arena); try dir_stack.append(target_include_dir); while (dir_stack.pop()) |full_dir_name| { - var dir = std.fs.cwd().openDir(full_dir_name, .{ .iterate = true }) catch |err| switch (err) { + var dir = Dir.cwd().openDir(full_dir_name, .{ .iterate = true }) catch |err| switch (err) { error.FileNotFound => continue :search, error.AccessDenied => continue :search, else => return err, }; - defer dir.close(); + defer dir.close(io); var dir_it = dir.iterate(); while (try dir_it.next()) |entry| { - const full_path = try std.fs.path.join(arena, &[_][]const u8{ full_dir_name, entry.name }); + const full_path = try Dir.path.join(arena, &[_][]const u8{ full_dir_name, entry.name }); switch (entry.kind) { .directory => try dir_stack.append(full_path), .file => { - const rel_path = try std.fs.path.relative(arena, target_include_dir, full_path); + const rel_path = try Dir.path.relative(arena, target_include_dir, full_path); const max_size = 2 * 1024 * 1024 * 1024; - const raw_bytes = try std.fs.cwd().readFileAlloc(full_path, arena, .limited(max_size)); + const raw_bytes = try Dir.cwd().readFileAlloc(full_path, arena, .limited(max_size)); const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t"); total_bytes += raw_bytes.len; const hash = try arena.alloc(u8, 32); @@ -253,7 +260,7 @@ pub fn main() !void { total_bytes, total_bytes - max_bytes_saved, }); - try std.fs.cwd().makePath(out_dir); + try Dir.cwd().createDirPath(io, out_dir); var missed_opportunity_bytes: usize = 0; // 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 { const best_contents = contents_list.pop().?; if (best_contents.hit_count > 1) { // worth it to make it generic - const full_path = try std.fs.path.join(arena, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* }); - try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?); - try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = best_contents.bytes }); + const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* }); + try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?); + try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = best_contents.bytes }); best_contents.is_generic = true; while (contents_list.pop()) |contender| { if (contender.hit_count > 1) { @@ -299,9 +306,9 @@ pub fn main() !void { else => @tagName(dest_target.arch), }; const out_subpath = try std.fmt.allocPrint(arena, "{s}-linux-any", .{arch_name}); - const full_path = try std.fs.path.join(arena, &[_][]const u8{ out_dir, out_subpath, path_kv.key_ptr.* }); - try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?); - try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = contents.bytes }); + const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, out_subpath, path_kv.key_ptr.* }); + try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?); + try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = contents.bytes }); } } @@ -316,8 +323,8 @@ pub fn main() !void { "any-linux-any/linux/netfilter_ipv6/ip6t_HL.h", }; for (bad_files) |bad_file| { - const full_path = try std.fs.path.join(arena, &[_][]const u8{ out_dir, bad_file }); - try std.fs.cwd().deleteFile(full_path); + const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, bad_file }); + try Dir.cwd().deleteFile(io, full_path); } } diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 2c722b853153044c6b136a8883b3fb8d251556ae..3741e51d3c4fd539b42cbe2dfa674af93fa1d796 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -1,6 +1,8 @@ -const std = @import("std"); const builtin = @import("builtin"); -const fs = std.fs; + +const std = @import("std"); +const Io = std.Io; +const Dir = std.Io.Dir; const mem = std.mem; const json = std.json; const assert = std.debug.assert; @@ -1927,26 +1929,26 @@ pub fn main() anyerror!void { // there shouldn't be any more argument after the optional filter if (args.skip()) usageAndExit(args0, 1); - var zig_src_dir = try fs.cwd().openDir(zig_src_root, .{}); - defer zig_src_dir.close(); + var zig_src_dir = try Dir.cwd().openDir(io, zig_src_root, .{}); + defer zig_src_dir.close(io); - const root_progress = std.Progress.start(.{ .estimated_total_items = targets.len }); + const root_progress = std.Progress.start(io, .{ .estimated_total_items = targets.len }); defer root_progress.end(); - var group: std.Io.Group = .init; + var group: Io.Group = .init; defer group.cancel(io); for (targets) |target| { if (filter) |zig_name| { if (!std.mem.eql(u8, target.zig_name, zig_name)) continue; } - group.async(io, processOneTarget, .{.{ + group.async(io, processOneTarget, .{ io, .{ .llvm_tblgen_exe = llvm_tblgen_exe, .llvm_src_root = llvm_src_root, .zig_src_dir = zig_src_dir, .root_progress = root_progress, .target = target, - }}); + } }); } group.wait(io); @@ -1955,12 +1957,12 @@ pub fn main() anyerror!void { const Job = struct { llvm_tblgen_exe: []const u8, llvm_src_root: []const u8, - zig_src_dir: std.fs.Dir, + zig_src_dir: Dir, root_progress: std.Progress.Node, target: ArchTarget, }; -fn processOneTarget(job: Job) void { +fn processOneTarget(io: Io, job: Job) void { errdefer |err| std.debug.panic("panic: {s}", .{@errorName(err)}); const target = job.target; @@ -2240,12 +2242,12 @@ fn processOneTarget(job: Job) void { const render_progress = progress_node.start("rendering Zig code", 0); - var target_dir = try job.zig_src_dir.openDir("lib/std/Target", .{}); - defer target_dir.close(); + var target_dir = try job.zig_src_dir.openDir(io, "lib/std/Target", .{}); + defer target_dir.close(io); const zig_code_basename = try std.fmt.allocPrint(arena, "{s}.zig", .{target.zig_name}); - var zig_code_file = try target_dir.createFile(zig_code_basename, .{}); - defer zig_code_file.close(); + var zig_code_file = try target_dir.createFile(io, zig_code_basename, .{}); + defer zig_code_file.close(io); var zig_code_file_buffer: [4096]u8 = undefined; var zig_code_file_writer = zig_code_file.writer(&zig_code_file_buffer); diff --git a/tools/update_crc_catalog.zig b/tools/update_crc_catalog.zig index 4c8614e02fda9fe027ddb69e42239663030393d7..2893f70f674deaacf67e22183ac41765047a5229 100644 --- a/tools/update_crc_catalog.zig +++ b/tools/update_crc_catalog.zig @@ -1,5 +1,6 @@ const std = @import("std"); -const fs = std.fs; +const Io = std.Io; +const Dir = std.Io.Dir; const mem = std.mem; const ascii = std.ascii; @@ -10,25 +11,29 @@ pub fn main() anyerror!void { defer arena_state.deinit(); const arena = arena_state.allocator(); + var threaded: Io.Threaded = .init(arena); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(arena); if (args.len <= 1) printUsageAndExit(args[0]); const zig_src_root = args[1]; if (mem.startsWith(u8, zig_src_root, "-")) printUsageAndExit(args[0]); - var zig_src_dir = try fs.cwd().openDir(zig_src_root, .{}); - defer zig_src_dir.close(); + var zig_src_dir = try Dir.cwd().openDir(io, zig_src_root, .{}); + defer zig_src_dir.close(io); - const hash_sub_path = try fs.path.join(arena, &.{ "lib", "std", "hash" }); - var hash_target_dir = try zig_src_dir.makeOpenPath(hash_sub_path, .{}); - defer hash_target_dir.close(); + const hash_sub_path = try Dir.path.join(arena, &.{ "lib", "std", "hash" }); + var hash_target_dir = try zig_src_dir.createDirPathOpen(io, hash_sub_path, .{}); + defer hash_target_dir.close(io); - const crc_sub_path = try fs.path.join(arena, &.{ "lib", "std", "hash", "crc" }); - var crc_target_dir = try zig_src_dir.makeOpenPath(crc_sub_path, .{}); - defer crc_target_dir.close(); + const crc_sub_path = try Dir.path.join(arena, &.{ "lib", "std", "hash", "crc" }); + var crc_target_dir = try zig_src_dir.createDirPathOpen(io, crc_sub_path, .{}); + defer crc_target_dir.close(io); - var zig_code_file = try hash_target_dir.createFile("crc.zig", .{}); - defer zig_code_file.close(); + var zig_code_file = try hash_target_dir.createFile(io, "crc.zig", .{}); + defer zig_code_file.close(io); var zig_code_file_buffer: [4096]u8 = undefined; var zig_code_file_writer = zig_code_file.writer(&zig_code_file_buffer); const code_writer = &zig_code_file_writer.interface; @@ -51,8 +56,8 @@ pub fn main() anyerror!void { \\ ); - var zig_test_file = try crc_target_dir.createFile("test.zig", .{}); - defer zig_test_file.close(); + var zig_test_file = try crc_target_dir.createFile(io, "test.zig", .{}); + defer zig_test_file.close(io); var zig_test_file_buffer: [4096]u8 = undefined; var zig_test_file_writer = zig_test_file.writer(&zig_test_file_buffer); const test_writer = &zig_test_file_writer.interface; diff --git a/tools/update_freebsd_libc.zig b/tools/update_freebsd_libc.zig index 420147e0dfb41a1bec9af6db3c33922477abe01a..c43b94858db29d5905cee34e1bdf528440008504 100644 --- a/tools/update_freebsd_libc.zig +++ b/tools/update_freebsd_libc.zig @@ -5,6 +5,7 @@ //! `zig run tools/update_freebsd_libc.zig -- ~/Downloads/freebsd-src .` const std = @import("std"); +const Io = std.Io; const exempt_files = [_][]const u8{ // This file is maintained by a separate project and does not come from FreeBSD. @@ -16,22 +17,24 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); + var threaded: Io.Threaded = .init(arena); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(arena); const freebsd_src_path = args[1]; const zig_src_path = args[2]; const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/freebsd", .{zig_src_path}); - var dest_dir = std.fs.cwd().openDir(dest_dir_path, .{ .iterate = true }) catch |err| { - std.log.err("unable to open destination directory '{s}': {s}", .{ - dest_dir_path, @errorName(err), - }); + var dest_dir = std.fs.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| { + std.log.err("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err }); std.process.exit(1); }; - defer dest_dir.close(); + defer dest_dir.close(io); var freebsd_src_dir = try std.fs.cwd().openDir(freebsd_src_path, .{}); - defer freebsd_src_dir.close(); + defer freebsd_src_dir.close(io); // Copy updated files from upstream. { @@ -57,7 +60,7 @@ pub fn main() !void { @errorName(err), }); if (err == error.FileNotFound) { - try dest_dir.deleteFile(entry.path); + try dest_dir.deleteFile(io, entry.path); } }; } diff --git a/tools/update_glibc.zig b/tools/update_glibc.zig index cebd593fe1d8e66ab3ef2846f8cbf8164981aaaf..227400476201db69400ffba676de2d2b7104ee9f 100644 --- a/tools/update_glibc.zig +++ b/tools/update_glibc.zig @@ -7,9 +7,11 @@ //! `zig run ../tools/update_glibc.zig -- ~/Downloads/glibc ..` const std = @import("std"); +const Io = std.Io; +const Dir = std.Io.Dir; const mem = std.mem; const log = std.log; -const fs = std.fs; +const fatal = std.process.fatal; const exempt_files = [_][]const u8{ // This file is maintained by a separate project and does not come from glibc. @@ -41,21 +43,23 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); + var threaded: Io.Threaded = .init(arena); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(arena); const glibc_src_path = args[1]; const zig_src_path = args[2]; const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/glibc", .{zig_src_path}); - var dest_dir = fs.cwd().openDir(dest_dir_path, .{ .iterate = true }) catch |err| { - fatal("unable to open destination directory '{s}': {s}", .{ - dest_dir_path, @errorName(err), - }); + var dest_dir = Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| { + fatal("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err }); }; - defer dest_dir.close(); + defer dest_dir.close(io); - var glibc_src_dir = try fs.cwd().openDir(glibc_src_path, .{}); - defer glibc_src_dir.close(); + var glibc_src_dir = try Dir.cwd().openDir(io, glibc_src_path, .{}); + defer glibc_src_dir.close(io); // Copy updated files from upstream. { @@ -73,13 +77,11 @@ pub fn main() !void { } glibc_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| { - log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{ - glibc_src_path, entry.path, - dest_dir_path, entry.path, - @errorName(err), + log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{ + glibc_src_path, entry.path, dest_dir_path, entry.path, err, }); if (err == error.FileNotFound) { - try dest_dir.deleteFile(entry.path); + try dest_dir.deleteFile(io, entry.path); } }; } @@ -88,20 +90,18 @@ pub fn main() !void { // Warn about duplicated files inside glibc/include/* that can be omitted // because they are already in generic-glibc/*. - var include_dir = dest_dir.openDir("include", .{ .iterate = true }) catch |err| { - fatal("unable to open directory '{s}/include': {s}", .{ - dest_dir_path, @errorName(err), - }); + var include_dir = dest_dir.openDir(io, "include", .{ .iterate = true }) catch |err| { + fatal("unable to open directory '{s}/include': {t}", .{ dest_dir_path, err }); }; - defer include_dir.close(); + defer include_dir.close(io); const generic_glibc_path = try std.fmt.allocPrint( arena, "{s}/lib/libc/include/generic-glibc", .{zig_src_path}, ); - var generic_glibc_dir = try fs.cwd().openDir(generic_glibc_path, .{}); - defer generic_glibc_dir.close(); + var generic_glibc_dir = try Dir.cwd().openDir(io, generic_glibc_path, .{}); + defer generic_glibc_dir.close(io); var walker = try include_dir.walk(arena); defer walker.deinit(); @@ -146,8 +146,3 @@ pub fn main() !void { } } } - -fn fatal(comptime format: []const u8, args: anytype) noreturn { - log.err(format, args); - std.process.exit(1); -} diff --git a/tools/update_mingw.zig b/tools/update_mingw.zig index 6288807cf1fabd290820165189d07df2df42179c..fee5774629b83edffe74e9a6a83d8b3a3f120c9c 100644 --- a/tools/update_mingw.zig +++ b/tools/update_mingw.zig @@ -1,35 +1,41 @@ const std = @import("std"); +const Io = std.Io; +const Dir = std.Io.Dir; pub fn main() !void { var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena_instance.deinit(); const arena = arena_instance.allocator(); + var threaded: Io.Threaded = .init(arena); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(arena); const zig_src_lib_path = args[1]; const mingw_src_path = args[2]; - const dest_mingw_crt_path = try std.fs.path.join(arena, &.{ + const dest_mingw_crt_path = try Dir.path.join(arena, &.{ zig_src_lib_path, "libc", "mingw", }); - const src_mingw_crt_path = try std.fs.path.join(arena, &.{ + const src_mingw_crt_path = try Dir.path.join(arena, &.{ mingw_src_path, "mingw-w64-crt", }); // Update only the set of existing files we have already chosen to include // in zig's installation. - var dest_crt_dir = std.fs.cwd().openDir(dest_mingw_crt_path, .{ .iterate = true }) catch |err| { + var dest_crt_dir = Dir.cwd().openDir(io, dest_mingw_crt_path, .{ .iterate = true }) catch |err| { std.log.err("unable to open directory '{s}': {s}", .{ dest_mingw_crt_path, @errorName(err) }); std.process.exit(1); }; - defer dest_crt_dir.close(); + defer dest_crt_dir.close(io); - var src_crt_dir = std.fs.cwd().openDir(src_mingw_crt_path, .{ .iterate = true }) catch |err| { + var src_crt_dir = Dir.cwd().openDir(io, src_mingw_crt_path, .{ .iterate = true }) catch |err| { std.log.err("unable to open directory '{s}': {s}", .{ src_mingw_crt_path, @errorName(err) }); std.process.exit(1); }; - defer src_crt_dir.close(); + defer src_crt_dir.close(io); { var walker = try dest_crt_dir.walk(arena); @@ -49,11 +55,11 @@ pub fn main() !void { if (!keep) { std.log.warn("deleting {s}", .{entry.path}); - try dest_crt_dir.deleteFile(entry.path); + try dest_crt_dir.deleteFile(io, entry.path); } }, else => { - std.log.err("unable to copy {s}: {s}", .{ entry.path, @errorName(err) }); + std.log.err("unable to copy {s}: {t}", .{ entry.path, err }); fail = true; }, }; @@ -63,24 +69,24 @@ pub fn main() !void { } { - const dest_mingw_winpthreads_path = try std.fs.path.join(arena, &.{ + const dest_mingw_winpthreads_path = try Dir.path.join(arena, &.{ zig_src_lib_path, "libc", "mingw", "winpthreads", }); - const src_mingw_libraries_winpthreads_src_path = try std.fs.path.join(arena, &.{ + const src_mingw_libraries_winpthreads_src_path = try Dir.path.join(arena, &.{ mingw_src_path, "mingw-w64-libraries", "winpthreads", "src", }); - var dest_winpthreads_dir = std.fs.cwd().openDir(dest_mingw_winpthreads_path, .{ .iterate = true }) catch |err| { + var dest_winpthreads_dir = Dir.cwd().openDir(io, dest_mingw_winpthreads_path, .{ .iterate = true }) catch |err| { std.log.err("unable to open directory '{s}': {s}", .{ dest_mingw_winpthreads_path, @errorName(err) }); std.process.exit(1); }; - defer dest_winpthreads_dir.close(); + defer dest_winpthreads_dir.close(io); - var src_winpthreads_dir = std.fs.cwd().openDir(src_mingw_libraries_winpthreads_src_path, .{ .iterate = true }) catch |err| { + var src_winpthreads_dir = Dir.cwd().openDir(io, src_mingw_libraries_winpthreads_src_path, .{ .iterate = true }) catch |err| { std.log.err("unable to open directory '{s}': {s}", .{ src_mingw_libraries_winpthreads_src_path, @errorName(err) }); std.process.exit(1); }; - defer src_winpthreads_dir.close(); + defer src_winpthreads_dir.close(io); { var walker = try dest_winpthreads_dir.walk(arena); @@ -94,10 +100,10 @@ pub fn main() !void { src_winpthreads_dir.copyFile(entry.path, dest_winpthreads_dir, entry.path, .{}) catch |err| switch (err) { error.FileNotFound => { std.log.warn("deleting {s}", .{entry.path}); - try dest_winpthreads_dir.deleteFile(entry.path); + try dest_winpthreads_dir.deleteFile(io, entry.path); }, else => { - std.log.err("unable to copy {s}: {s}", .{ entry.path, @errorName(err) }); + std.log.err("unable to copy {s}: {t}", .{ entry.path, err }); fail = true; }, }; @@ -164,7 +170,7 @@ pub fn main() !void { const kept_crt_files = [_][]const u8{ "COPYING", - "include" ++ std.fs.path.sep_str ++ "config.h", + "include" ++ Dir.path.sep_str ++ "config.h", }; const def_exts = [_][]const u8{ diff --git a/tools/update_netbsd_libc.zig b/tools/update_netbsd_libc.zig index 7bfe99b094941c2daf235f41746a6dc365a7866d..e3b7bd21eed32dc2d7f5e96e17a389f574758a85 100644 --- a/tools/update_netbsd_libc.zig +++ b/tools/update_netbsd_libc.zig @@ -5,6 +5,7 @@ //! `zig run tools/update_netbsd_libc.zig -- ~/Downloads/netbsd-src .` const std = @import("std"); +const Io = std.Io; const exempt_files = [_][]const u8{ // This file is maintained by a separate project and does not come from NetBSD. @@ -16,22 +17,24 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); + var threaded: Io.Threaded = .init(arena); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(arena); const netbsd_src_path = args[1]; const zig_src_path = args[2]; const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/netbsd", .{zig_src_path}); - var dest_dir = std.fs.cwd().openDir(dest_dir_path, .{ .iterate = true }) catch |err| { - std.log.err("unable to open destination directory '{s}': {s}", .{ - dest_dir_path, @errorName(err), - }); + var dest_dir = std.fs.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| { + std.log.err("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err }); std.process.exit(1); }; - defer dest_dir.close(); + defer dest_dir.close(io); - var netbsd_src_dir = try std.fs.cwd().openDir(netbsd_src_path, .{}); - defer netbsd_src_dir.close(); + var netbsd_src_dir = try std.fs.cwd().openDir(io, netbsd_src_path, .{}); + defer netbsd_src_dir.close(io); // Copy updated files from upstream. { @@ -51,13 +54,11 @@ pub fn main() !void { }); netbsd_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| { - std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{ - netbsd_src_path, entry.path, - dest_dir_path, entry.path, - @errorName(err), + std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{ + netbsd_src_path, entry.path, dest_dir_path, entry.path, err, }); if (err == error.FileNotFound) { - try dest_dir.deleteFile(entry.path); + try dest_dir.deleteFile(io, entry.path); } }; } -- 2.54.0 From 77d2ad8c929680ed35fcfe6646f940518a07e7e4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 20:15:50 -0800 Subject: [PATCH 120/195] std: consolidate all instances of std.Io.Threaded into a singleton It's better to avoid references to this global variable, but, in the cases where it's needed, such as in std.debug.print and collecting stack traces, better to share the same instance. --- lib/compiler/test_runner.zig | 10 +++++----- lib/std/Io/Threaded.zig | 13 +++++++++++++ lib/std/Thread.zig | 3 +-- lib/std/debug.zig | 12 ++++++------ lib/std/debug/SelfInfo/Windows.zig | 4 ++-- lib/std/dynamic_library.zig | 4 +--- lib/std/process/Child.zig | 15 +++++++-------- test/incremental/no_change_preserves_tag_names | 4 ++-- test/standalone/cmakedefine/check.zig | 3 +-- test/standalone/dirname/exists_in.zig | 3 +-- test/standalone/dirname/touch.zig | 3 +-- test/standalone/entry_point/check_differ.zig | 3 +-- test/standalone/install_headers/check_exists.zig | 3 +-- test/standalone/posix/relpaths.zig | 3 +-- test/standalone/run_cwd/check_file_exists.zig | 3 +-- test/standalone/run_output_caching/main.zig | 3 +-- test/standalone/run_output_paths/create_file.zig | 3 +-- .../self_exe_symlink/create-symlink.zig | 3 +-- test/standalone/simple/hello_world/hello.zig | 11 ++++++++--- test/standalone/windows_paths/test.zig | 3 +-- 20 files changed, 56 insertions(+), 53 deletions(-) diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index d16fc3ae82f83160aa60fff434b73f50427a4c73..84664feb138d556bafc4a9a90223f79d375f6d92 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -17,7 +17,7 @@ var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer); var fba_buffer: [8192]u8 = undefined; var stdin_buffer: [4096]u8 = undefined; var stdout_buffer: [4096]u8 = undefined; -var runner_threaded_io: Io.Threaded = .init_single_threaded; +const runner_threaded_io: Io = Io.Threaded.global_single_threaded.ioBasic(); /// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether /// the test runner will communicate with the build runner via `std.zig.Server`. @@ -74,8 +74,8 @@ pub fn main() void { fn mainServer() !void { @disableInstrumentation(); - var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io.io(), &stdin_buffer); - var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io.io(), &stdout_buffer); + var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer); var server = try std.zig.Server.init(.{ .in = &stdin_reader.interface, .out = &stdout_writer.interface, @@ -224,11 +224,11 @@ fn mainTerminal() void { var skip_count: usize = 0; var fail_count: usize = 0; var fuzz_count: usize = 0; - const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(runner_threaded_io.io(), .{ + const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(runner_threaded_io, .{ .root_name = "Test", .estimated_total_items = test_fn_list.len, }); - const have_tty = Io.File.stderr().isTty(runner_threaded_io.io()) catch unreachable; + const have_tty = Io.File.stderr().isTty(runner_threaded_io) catch unreachable; var leaks: usize = 0; for (test_fn_list, 0..) |test_fn, i| { diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index db4c5f3fda98fa2d18f237a96b19ef23bd5937d3..182a8f4b6d45ae6561bb8ae6fca88a86d6bbe790 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -626,6 +626,19 @@ pub const init_single_threaded: Threaded = .{ }, }; +var global_single_threaded_instance: Threaded = .init_single_threaded; + +/// In general, the application is responsible for choosing the `Io` +/// implementation and library code should accept an `Io` parameter rather than +/// accessing this declaration. Most code should avoid referencing this +/// declaration entirely. +/// +/// However, in some cases such as debugging, it is desirable to hardcode a +/// reference to this `Io` implementation. +/// +/// This instance does not support concurrency or cancelation. +pub const global_single_threaded: *Threaded = &global_single_threaded_instance; + pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { t.mutex.lock(); defer t.mutex.unlock(); diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index fbce1cd000381b835642f87916850297170aa4c2..f25c664000fbcbb737619c331a08bcb013d7ac71 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -322,8 +322,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co var buf: [32]u8 = undefined; const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); + const io = Io.Threaded.global_single_threaded.ioBasic(); const file = try Io.Dir.cwd().openFile(io, path, .{}); defer file.close(io); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 26631db5012d9e7b56688768671af46dc891165c..0e804e23483fd52230b7862e3357a14e02b46c0e 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -263,7 +263,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { /// This is used for debug information and debug printing. It is intentionally /// separate from the application's `Io` instance. -var static_single_threaded_io: Io.Threaded = .init_single_threaded; +const static_single_threaded_io = Io.Threaded.global_single_threaded.ioBasic(); /// Allows the caller to freely write to stderr until `unlockStderr` is called. /// @@ -284,7 +284,7 @@ var static_single_threaded_io: Io.Threaded = .init_single_threaded; /// Alternatively, use the higher-level `Io.lockStderr` to integrate with the /// application's chosen `Io` implementation. pub fn lockStderr(buffer: []u8) Io.LockedStderr { - return static_single_threaded_io.ioBasic().lockStderr(buffer, null) catch |err| switch (err) { + return static_single_threaded_io.lockStderr(buffer, null) catch |err| switch (err) { // Impossible to cancel because no calls to cancel using // `static_single_threaded_io` exist. error.Canceled => unreachable, @@ -292,7 +292,7 @@ pub fn lockStderr(buffer: []u8) Io.LockedStderr { } pub fn unlockStderr() void { - static_single_threaded_io.ioBasic().unlockStderr(); + static_single_threaded_io.unlockStderr(); } /// Writes to stderr, ignoring errors. @@ -630,7 +630,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: defer it.deinit(); if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace; - const io = static_single_threaded_io.ioBasic(); + const io = static_single_threaded_io; var total_frames: usize = 0; var index: usize = 0; @@ -692,7 +692,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin var total_frames: usize = 0; var wait_for = options.first_address; var printed_any_frame = false; - const io = static_single_threaded_io.ioBasic(); + const io = static_single_threaded_io; while (true) switch (it.next(io)) { .switch_to_fp => |unwind_error| { switch (StackIterator.fp_usability) { @@ -800,7 +800,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void return; }, }; - const io = static_single_threaded_io.ioBasic(); + const io = static_single_threaded_io; const captured_frames = @min(n_frames, st.instruction_addresses.len); for (st.instruction_addresses[0..captured_frames]) |ret_addr| { // `ret_addr` is the return address, which is *after* the function call. diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index c34c60f3ec2ede798bb10822b0ece7a59f67098c..99d3e9f92623696c67b3afdd33f75b459d1cce2d 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -315,8 +315,8 @@ const Module = struct { ); if (len == 0) return error.MissingDebugInfo; const name_w = name_buffer[0 .. len + 4 :0]; - var threaded: Io.Threaded = .init_single_threaded; - const coff_file = threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) { + // TODO eliminate the reference to Io.Threaded.global_single_threaded here + const coff_file = Io.Threaded.global_single_threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) { error.Canceled => |e| return e, error.Unexpected => |e| return e, error.FileNotFound => return error.MissingDebugInfo, diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 18db4ad8c6e4b224547321d5d01ab07cf5534c57..70b236f3e5902cb48bb6b2794ade1baacaa8bca0 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -142,8 +142,6 @@ const ElfDynLibError = error{ Streaming, } || posix.OpenError || posix.MMapError; -var static_single_threaded_io: Io.Threaded = .init_single_threaded; - pub const ElfDynLib = struct { strings: [*:0]u8, syms: [*]elf.Sym, @@ -224,7 +222,7 @@ pub const ElfDynLib = struct { /// Trusts the file. Malicious file will be able to execute arbitrary code. pub fn open(path: []const u8) Error!ElfDynLib { - const io = static_single_threaded_io.ioBasic(); + const io = Io.Threaded.global_single_threaded.ioBasic(); const fd = try resolveFromName(io, path); defer posix.close(fd); diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 05cc4b39449ec6cd80ebdf5b186e77209b2d8211..0db02aa824309ef653e071be0b6c095ff5c58c5d 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -266,7 +266,7 @@ pub fn spawn(self: *Child, io: Io) SpawnError!void { } if (native_os == .windows) { - return self.spawnWindows(); + return self.spawnWindows(io); } else { return self.spawnPosix(io); } @@ -750,7 +750,7 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void { self.progress_node.setIpcFd(prog_pipe[0]); } -fn spawnWindows(self: *Child) SpawnError!void { +fn spawnWindows(self: *Child, io: Io) SpawnError!void { var saAttr = windows.SECURITY_ATTRIBUTES{ .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), .bInheritHandle = windows.TRUE, @@ -953,7 +953,7 @@ fn spawnWindows(self: *Child) SpawnError!void { try dir_buf.appendSlice(self.allocator, app_dir); } - windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| { + windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| { const original_err = switch (no_path_err) { // argv[0] contains unsupported characters that will never resolve to a valid exe. error.InvalidArg0 => return error.FileNotFound, @@ -977,7 +977,7 @@ fn spawnWindows(self: *Child) SpawnError!void { dir_buf.clearRetainingCapacity(); try dir_buf.appendSlice(self.allocator, search_path); - if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) { + if (windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) { break :run; } else |err| switch (err) { // argv[0] contains unsupported characters that will never resolve to a valid exe. @@ -1079,6 +1079,7 @@ const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8); /// Note: If the dir is the cwd, dir_buf should be empty (len = 0). fn windowsCreateProcessPathExt( allocator: Allocator, + io: Io, dir_buf: *ArrayList(u16), app_buf: *ArrayList(u16), pathext: [:0]const u16, @@ -1122,16 +1123,14 @@ fn windowsCreateProcessPathExt( // Under those conditions, here we will have access to lower level directory // opening function knowing which implementation we are in. Here, we imitate // that scenario. - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - var dir = dir: { // needs to be null-terminated try dir_buf.append(allocator, 0); defer dir_buf.shrinkRetainingCapacity(dir_path_len); const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z); - break :dir threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{ + // TODO eliminate this reference + break :dir Io.Threaded.global_single_threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{ .iterate = true, }) catch return error.FileNotFound; }; diff --git a/test/incremental/no_change_preserves_tag_names b/test/incremental/no_change_preserves_tag_names index e399e083e10e198858d1243259e2ed5a0cc9274f..6675d74166016b849c93aaf5663d6d6354770070 100644 --- a/test/incremental/no_change_preserves_tag_names +++ b/test/incremental/no_change_preserves_tag_names @@ -8,7 +8,7 @@ const std = @import("std"); var some_enum: enum { first, second } = .first; pub fn main() !void { - try std.Io.File.stdout().writeAll(@tagName(some_enum)); + try std.Io.File.stdout().writeStreamingAll(std.Io.Threaded.global_single_threaded.ioBasic(), @tagName(some_enum)); } #expect_stdout="first" #update=no change @@ -16,6 +16,6 @@ pub fn main() !void { const std = @import("std"); var some_enum: enum { first, second } = .first; pub fn main() !void { - try std.Io.File.stdout().writeAll(@tagName(some_enum)); + try std.Io.File.stdout().writeStreamingAll(std.Io.Threaded.global_single_threaded.ioBasic(), @tagName(some_enum)); } #expect_stdout="first" diff --git a/test/standalone/cmakedefine/check.zig b/test/standalone/cmakedefine/check.zig index d0751913d755d1fe4d175ed9d6f3c8f6d13fdaef..c2f89ad1127ccba3f663886e84d085cab2f7cb26 100644 --- a/test/standalone/cmakedefine/check.zig +++ b/test/standalone/cmakedefine/check.zig @@ -9,8 +9,7 @@ pub fn main() !void { const actual_path = args[1]; const expected_path = args[2]; - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.io(); + const io = std.Io.Threaded.global_single_threaded.ioBasic(); const actual = try std.Io.Dir.cwd().readFileAlloc(io, actual_path, arena, .limited(1024 * 1024)); const expected = try std.Io.Dir.cwd().readFileAlloc(io, expected_path, arena, .limited(1024 * 1024)); diff --git a/test/standalone/dirname/exists_in.zig b/test/standalone/dirname/exists_in.zig index 7bddc4e61395e1b75accafd37e452f5fc910c167..b321f46c55407cf46d8b12fbe6cc4b8c2323e1cf 100644 --- a/test/standalone/dirname/exists_in.zig +++ b/test/standalone/dirname/exists_in.zig @@ -34,8 +34,7 @@ fn run(allocator: std.mem.Allocator) !void { return error.BadUsage; }; - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.io(); + const io = std.Io.Threaded.global_single_threaded.ioBasic(); var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{}); defer dir.close(io); diff --git a/test/standalone/dirname/touch.zig b/test/standalone/dirname/touch.zig index 9255d27c72ebbb182e564e4781c5d888fe47ed52..d7f9b71553a9e1cab81f40f13e118fb30636b6a8 100644 --- a/test/standalone/dirname/touch.zig +++ b/test/standalone/dirname/touch.zig @@ -29,8 +29,7 @@ fn run(allocator: std.mem.Allocator) !void { const dir_path = std.Io.Dir.path.dirname(path) orelse unreachable; const basename = std.Io.Dir.path.basename(path); - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.io(); + const io = std.Io.Threaded.global_single_threaded.ioBasic(); var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{}); defer dir.close(io); diff --git a/test/standalone/entry_point/check_differ.zig b/test/standalone/entry_point/check_differ.zig index bba45e5f8cf054efd464270e8ad5b405b5f12267..29b333632fcd0f7d823f4b1caf501a2b7e7ca06d 100644 --- a/test/standalone/entry_point/check_differ.zig +++ b/test/standalone/entry_point/check_differ.zig @@ -6,8 +6,7 @@ pub fn main() !void { const args = try std.process.argsAlloc(arena); if (args.len != 3) return error.BadUsage; // usage: 'check_differ ' - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.io(); + const io = std.Io.Threaded.global_single_threaded.ioBasic(); const contents_1 = try std.Io.Dir.cwd().readFileAlloc(io, args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty const contents_2 = try std.Io.Dir.cwd().readFileAlloc(io, args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty diff --git a/test/standalone/install_headers/check_exists.zig b/test/standalone/install_headers/check_exists.zig index 8a7104e4f2072dcf002528a7340c5191630ba37e..22638cf1676080941c3b8b1a451d0c76e50306b8 100644 --- a/test/standalone/install_headers/check_exists.zig +++ b/test/standalone/install_headers/check_exists.zig @@ -11,8 +11,7 @@ pub fn main() !void { var arg_it = try std.process.argsWithAllocator(arena); _ = arg_it.next(); - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.io(); + const io = std.Io.Threaded.global_single_threaded.ioBasic(); const cwd = std.Io.Dir.cwd(); const cwd_realpath = try cwd.realPathAlloc(io, arena, "."); diff --git a/test/standalone/posix/relpaths.zig b/test/standalone/posix/relpaths.zig index ed143ccdc232ac2263ee0fc0fd3f51528654a6bc..447285c444d457a1fe1f2f0a1c1f1b696540136f 100644 --- a/test/standalone/posix/relpaths.zig +++ b/test/standalone/posix/relpaths.zig @@ -14,8 +14,7 @@ pub fn main() !void { const gpa = debug_allocator.allocator(); defer std.debug.assert(debug_allocator.deinit() == .ok); - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.io(); + const io = std.Io.Threaded.global_single_threaded.ioBasic(); // TODO this API isn't supposed to be used outside of unit testing. make it compilation error if used // outside of unit testing. diff --git a/test/standalone/run_cwd/check_file_exists.zig b/test/standalone/run_cwd/check_file_exists.zig index 8830cc115a569475332122f604fa1dd626aa01a7..a885c7dafde7c501ba794ecbe4b377aa61eab705 100644 --- a/test/standalone/run_cwd/check_file_exists.zig +++ b/test/standalone/run_cwd/check_file_exists.zig @@ -8,8 +8,7 @@ pub fn main() !void { if (args.len != 2) return error.BadUsage; const path = args[1]; - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.io(); + const io = std.Io.Threaded.global_single_threaded.ioBasic(); std.Io.Dir.cwd().access(io, path, .{}) catch return error.AccessFailed; } diff --git a/test/standalone/run_output_caching/main.zig b/test/standalone/run_output_caching/main.zig index c21d8d0f6bbd8ea21dd41348e7d6cd4dff020383..66e64beda2eeb7e51811b213551e4ddeb02d41aa 100644 --- a/test/standalone/run_output_caching/main.zig +++ b/test/standalone/run_output_caching/main.zig @@ -1,8 +1,7 @@ const std = @import("std"); pub fn main() !void { - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.io(); + const io = std.Io.Threaded.global_single_threaded.ioBasic(); var args = try std.process.argsWithAllocator(std.heap.page_allocator); _ = args.skip(); const filename = args.next().?; diff --git a/test/standalone/run_output_paths/create_file.zig b/test/standalone/run_output_paths/create_file.zig index 4830790c79e9530a85a7496cf0c40d098cb6ca13..27f741ecaba37aed73f9b7ebe659219b1f034d17 100644 --- a/test/standalone/run_output_paths/create_file.zig +++ b/test/standalone/run_output_paths/create_file.zig @@ -1,8 +1,7 @@ const std = @import("std"); pub fn main() !void { - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.io(); + const io = std.Io.Threaded.global_single_threaded.ioBasic(); var args = try std.process.argsWithAllocator(std.heap.page_allocator); _ = args.skip(); const dir_name = args.next().?; diff --git a/test/standalone/self_exe_symlink/create-symlink.zig b/test/standalone/self_exe_symlink/create-symlink.zig index 6ccf6596aa89398f1f50db56a02efacfc3317aac..d725207320811408d453104f022b4c6409b67681 100644 --- a/test/standalone/self_exe_symlink/create-symlink.zig +++ b/test/standalone/self_exe_symlink/create-symlink.zig @@ -15,8 +15,7 @@ pub fn main() anyerror!void { const exe_rel_path = try std.fs.path.relative(allocator, std.fs.path.dirname(symlink_path) orelse ".", exe_path); defer allocator.free(exe_rel_path); - var threaded: std.Io.Threaded = .init_single_threaded; - const io = threaded.io(); + const io = std.Io.Threaded.global_single_threaded.ioBasic(); try std.Io.Dir.cwd().symLink(io, exe_rel_path, symlink_path, .{}); } diff --git a/test/standalone/simple/hello_world/hello.zig b/test/standalone/simple/hello_world/hello.zig index d7083942304c4cc24aa71cbfa45221b95c490ff5..6bce8413441ed0421687c90deb058bf7b3fa16d2 100644 --- a/test/standalone/simple/hello_world/hello.zig +++ b/test/standalone/simple/hello_world/hello.zig @@ -1,8 +1,13 @@ const std = @import("std"); -var static_single_threaded_io: std.Io.Threaded = .init_single_threaded; -const io = static_single_threaded_io.ioBasic(); - pub fn main() !void { + var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + defer _ = debug_allocator.deinit(); + const gpa = debug_allocator.allocator(); + + var threaded: std.Io.Threaded = .init(gpa); + defer threaded.deinit(); + const io = threaded.io(); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } diff --git a/test/standalone/windows_paths/test.zig b/test/standalone/windows_paths/test.zig index f5fa5947666908f888dbac8dcac55522c93cefab..ed4069dc61e6a5121936b34e9628fd21831da3bc 100644 --- a/test/standalone/windows_paths/test.zig +++ b/test/standalone/windows_paths/test.zig @@ -10,8 +10,7 @@ pub fn main() anyerror!void { if (args.len < 2) return error.MissingArgs; - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.io(); + const io = std.Io.Threaded.global_single_threaded.ioBasic(); const exe_path = args[1]; -- 2.54.0 From debf3075942a0a8aa4078ffefc2f1201dd92d72b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 20:40:24 -0800 Subject: [PATCH 121/195] update all incremental tests to new std API they're still not passing on windows for some reason though --- test/incremental/add_decl | 20 ++++++++++++------- test/incremental/add_decl_namespaced | 20 ++++++++++++------- test/incremental/bad_import | 6 ++++-- test/incremental/change_embed_file | 9 ++++++--- test/incremental/change_enum_tag_type | 9 ++++++--- test/incremental/change_exports | 18 +++++++++++------ test/incremental/change_fn_type | 9 ++++++--- test/incremental/change_generic_line_number | 6 ++++-- test/incremental/change_line_number | 6 ++++-- test/incremental/change_panic_handler | 9 ++++++--- .../incremental/change_panic_handler_explicit | 9 ++++++--- test/incremental/change_shift_op | 6 ++++-- test/incremental/change_struct_same_fields | 9 ++++++--- test/incremental/change_zon_file | 9 ++++++--- .../change_zon_file_no_result_type | 3 ++- test/incremental/compile_log | 9 ++++++--- test/incremental/fix_astgen_failure | 13 +++++++----- test/incremental/function_becomes_inline | 9 ++++++--- test/incremental/hello | 6 ++++-- test/incremental/make_decl_pub | 6 ++++-- test/incremental/modify_inline_fn | 6 ++++-- test/incremental/move_src | 6 ++++-- .../incremental/no_change_preserves_tag_names | 6 ++++-- .../recursive_function_becomes_non_recursive | 8 +++++--- test/incremental/remove_enum_field | 6 ++++-- test/incremental/unreferenced_error | 12 +++++++---- 26 files changed, 155 insertions(+), 80 deletions(-) diff --git a/test/incremental/add_decl b/test/incremental/add_decl index 031085ca584c789842b5d74c56b86a065e4b9b89..99f6b2d34fb9c244caac9d0741f7511b6d279bbc 100644 --- a/test/incremental/add_decl +++ b/test/incremental/add_decl @@ -7,57 +7,63 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(foo); + try std.Io.File.stdout().writeStreamingAll(io, foo); } const foo = "good morning\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="good morning\n" #update=add new declaration #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(foo); + try std.Io.File.stdout().writeStreamingAll(io, foo); } const foo = "good morning\n"; const bar = "good evening\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="good morning\n" #update=reference new declaration #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(bar); + try std.Io.File.stdout().writeStreamingAll(io, bar); } const foo = "good morning\n"; const bar = "good evening\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="good evening\n" #update=reference missing declaration #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(qux); + try std.Io.File.stdout().writeStreamingAll(io, qux); } const foo = "good morning\n"; const bar = "good evening\n"; -#expect_error=main.zig:3:39: error: use of undeclared identifier 'qux' +const io = std.Io.Threaded.global_single_threaded.ioBasic(); +#expect_error=main.zig:3:52: error: use of undeclared identifier 'qux' #update=add missing declaration #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(qux); + try std.Io.File.stdout().writeStreamingAll(io, qux); } const foo = "good morning\n"; const bar = "good evening\n"; const qux = "good night\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="good night\n" #update=remove unused declarations #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(qux); + try std.Io.File.stdout().writeStreamingAll(io, qux); } const qux = "good night\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="good night\n" diff --git a/test/incremental/add_decl_namespaced b/test/incremental/add_decl_namespaced index cbeaf4865ecb7837d2b7584a5565a1930909e6b5..6fc22f10b9411cbeaa988618b0f2512d96bcf652 100644 --- a/test/incremental/add_decl_namespaced +++ b/test/incremental/add_decl_namespaced @@ -7,58 +7,64 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(@This().foo); + try std.Io.File.stdout().writeStreamingAll(io, @This().foo); } const foo = "good morning\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="good morning\n" #update=add new declaration #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(@This().foo); + try std.Io.File.stdout().writeStreamingAll(io, @This().foo); } const foo = "good morning\n"; const bar = "good evening\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="good morning\n" #update=reference new declaration #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(@This().bar); + try std.Io.File.stdout().writeStreamingAll(io, @This().bar); } const foo = "good morning\n"; const bar = "good evening\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="good evening\n" #update=reference missing declaration #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(@This().qux); + try std.Io.File.stdout().writeStreamingAll(io, @This().qux); } const foo = "good morning\n"; const bar = "good evening\n"; -#expect_error=main.zig:3:46: error: root source file struct 'main' has no member named 'qux' +const io = std.Io.Threaded.global_single_threaded.ioBasic(); +#expect_error=main.zig:3:59: error: root source file struct 'main' has no member named 'qux' #expect_error=main.zig:1:1: note: struct declared here #update=add missing declaration #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(@This().qux); + try std.Io.File.stdout().writeStreamingAll(io, @This().qux); } const foo = "good morning\n"; const bar = "good evening\n"; const qux = "good night\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="good night\n" #update=remove unused declarations #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(@This().qux); + try std.Io.File.stdout().writeStreamingAll(io, @This().qux); } const qux = "good night\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="good night\n" diff --git a/test/incremental/bad_import b/test/incremental/bad_import index ad66c17b5b5f6310b4c622e363d09ee4e35600ad..20bdb9ae82fcb1588688d3ae9290bf146b1ee142 100644 --- a/test/incremental/bad_import +++ b/test/incremental/bad_import @@ -8,9 +8,10 @@ #file=main.zig pub fn main() !void { _ = @import("foo.zig"); - try std.Io.File.stdout().writeAll("success\n"); + try std.Io.File.stdout().writeStreamingAll(io, "success\n"); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #file=foo.zig comptime { _ = @import("bad.zig"); @@ -30,7 +31,8 @@ comptime { #file=main.zig pub fn main() !void { //_ = @import("foo.zig"); - try std.Io.File.stdout().writeAll("success\n"); + try std.Io.File.stdout().writeStreamingAll(io, "success\n"); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="success\n" diff --git a/test/incremental/change_embed_file b/test/incremental/change_embed_file index 1eed90dc3250fa0079176d010565f02002b75f6b..84c9c334c72283d2ac5637064cd96a4fca37a0fe 100644 --- a/test/incremental/change_embed_file +++ b/test/incremental/change_embed_file @@ -8,8 +8,9 @@ const std = @import("std"); const string = @embedFile("string.txt"); pub fn main() !void { - try std.Io.File.stdout().writeAll(string); + try std.Io.File.stdout().writeStreamingAll(io, string); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #file=string.txt Hello, World! #expect_stdout="Hello, World!\n" @@ -28,8 +29,9 @@ Hello again, World! const std = @import("std"); const string = @embedFile("string.txt"); pub fn main() !void { - try std.Io.File.stdout().writeAll("a hardcoded string\n"); + try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n"); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="a hardcoded string\n" #update=re-introduce reference to file @@ -37,8 +39,9 @@ pub fn main() !void { const std = @import("std"); const string = @embedFile("string.txt"); pub fn main() !void { - try std.Io.File.stdout().writeAll(string); + try std.Io.File.stdout().writeStreamingAll(io, string); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound #update=recreate file diff --git a/test/incremental/change_enum_tag_type b/test/incremental/change_enum_tag_type index 6e72b3e573fc2e6e18285b8b357144bed1b4a855..06b80ac04deb7f36956dff9d20c8704563602adc 100644 --- a/test/incremental/change_enum_tag_type +++ b/test/incremental/change_enum_tag_type @@ -15,10 +15,11 @@ const Foo = enum(Tag) { pub fn main() !void { var val: Foo = undefined; val = .a; - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="a\n" #update=too many enum fields #file=main.zig @@ -33,7 +34,7 @@ const Foo = enum(Tag) { pub fn main() !void { var val: Foo = undefined; val = .a; - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); } comptime { @@ -42,6 +43,7 @@ comptime { std.debug.assert(@TypeOf(@intFromEnum(Foo.e)) == Tag); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_error=main.zig:7:5: error: enumeration value '4' too large for type 'u2' #update=increase tag size #file=main.zig @@ -56,8 +58,9 @@ const Foo = enum(Tag) { pub fn main() !void { var val: Foo = undefined; val = .a; - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="a\n" diff --git a/test/incremental/change_exports b/test/incremental/change_exports index 0090d45d8ef791e382962925bb15bb74b40e510f..a36afb4ee1aae151871068cae685a93df61111f6 100644 --- a/test/incremental/change_exports +++ b/test/incremental/change_exports @@ -17,10 +17,11 @@ pub fn main() !void { extern const bar: u32; }; S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{}\n", .{S.bar}); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="123\n" #update=add conflict @@ -39,10 +40,11 @@ pub fn main() !void { extern const other: u32; }; S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_error=main.zig:6:5: error: exported symbol collision: foo #expect_error=main.zig:1:1: note: other symbol here @@ -62,10 +64,11 @@ pub fn main() !void { extern const other: u32; }; S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="123 456\n" #update=put exports in decl @@ -87,10 +90,11 @@ pub fn main() !void { extern const other: u32; }; S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="123 456\n" #update=remove reference to exporting decl @@ -133,10 +137,11 @@ pub fn main() !void { extern const other: u32; }; S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="123 456\n" #update=reintroduce reference to exporting decl, introducing conflict @@ -158,10 +163,11 @@ pub fn main() !void { extern const other: u32; }; S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_error=main.zig:5:5: error: exported symbol collision: bar #expect_error=main.zig:2:1: note: other symbol here #expect_error=main.zig:6:5: error: exported symbol collision: other diff --git a/test/incremental/change_fn_type b/test/incremental/change_fn_type index bcf006861ae828724c327edfcc356bfc3e250da0..b4286545e3816227d7d33354885875044c0b72fc 100644 --- a/test/incremental/change_fn_type +++ b/test/incremental/change_fn_type @@ -8,10 +8,11 @@ pub fn main() !void { try foo(123); } fn foo(x: u8) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); return stdout_writer.interface.print("{d}\n", .{x}); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="123\n" #update=change function type @@ -20,10 +21,11 @@ pub fn main() !void { try foo(123); } fn foo(x: i64) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); return stdout_writer.interface.print("{d}\n", .{x}); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="123\n" #update=change function argument @@ -32,8 +34,9 @@ pub fn main() !void { try foo(-42); } fn foo(x: i64) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); return stdout_writer.interface.print("{d}\n", .{x}); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="-42\n" diff --git a/test/incremental/change_generic_line_number b/test/incremental/change_generic_line_number index 252261af6689469ef76161b4eeb7f8e1af910dba..2d731b071c644c35bf921e20187820d81660b3b1 100644 --- a/test/incremental/change_generic_line_number +++ b/test/incremental/change_generic_line_number @@ -4,10 +4,11 @@ #update=initial version #file=main.zig const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); fn Printer(message: []const u8) type { return struct { fn print() !void { - try std.Io.File.stdout().writeAll(message); + try std.Io.File.stdout().writeStreamingAll(io, message); } }; } @@ -19,11 +20,12 @@ pub fn main() !void { #update=change line number #file=main.zig const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); fn Printer(message: []const u8) type { return struct { fn print() !void { - try std.Io.File.stdout().writeAll(message); + try std.Io.File.stdout().writeStreamingAll(io, message); } }; } diff --git a/test/incremental/change_line_number b/test/incremental/change_line_number index a905745e6ad5a9f2076bb825b3d28c91af340778..7bafbbfbdd9d69538c094cbfbbfdaaf2b4f6a555 100644 --- a/test/incremental/change_line_number +++ b/test/incremental/change_line_number @@ -5,14 +5,16 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll("foo\n"); + try std.Io.File.stdout().writeStreamingAll(io, "foo\n"); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="foo\n" #update=change line number #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll("foo\n"); + try std.Io.File.stdout().writeStreamingAll(io, "foo\n"); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="foo\n" diff --git a/test/incremental/change_panic_handler b/test/incremental/change_panic_handler index 2fe9240b9b6d4283f1a034272058c72e59c77ab7..ebce3bc3121be81a04687dce2b77e7f27c39db4a 100644 --- a/test/incremental/change_panic_handler +++ b/test/incremental/change_panic_handler @@ -12,11 +12,12 @@ pub fn main() !u8 { } pub const panic = std.debug.FullPanic(myPanic); fn myPanic(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="panic message: integer overflow\n" #update=change the panic handler body @@ -29,11 +30,12 @@ pub fn main() !u8 { } pub const panic = std.debug.FullPanic(myPanic); fn myPanic(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="new panic message: integer overflow\n" #update=change the panic handler function value @@ -46,9 +48,10 @@ pub fn main() !u8 { } pub const panic = std.debug.FullPanic(myPanicNew); fn myPanicNew(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="third panic message: integer overflow\n" diff --git a/test/incremental/change_panic_handler_explicit b/test/incremental/change_panic_handler_explicit index 7daa851c8f9b186f7b7ac3acdaf2e90544d8fbed..366bffca45baab2478be62a00c90e780ef92597e 100644 --- a/test/incremental/change_panic_handler_explicit +++ b/test/incremental/change_panic_handler_explicit @@ -42,11 +42,12 @@ pub const panic = struct { pub const noreturnReturned = no_panic.noreturnReturned; }; fn myPanic(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="panic message: integer overflow\n" #update=change the panic handler body @@ -89,11 +90,12 @@ pub const panic = struct { pub const noreturnReturned = no_panic.noreturnReturned; }; fn myPanic(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="new panic message: integer overflow\n" #update=change the panic handler function value @@ -136,9 +138,10 @@ pub const panic = struct { pub const noreturnReturned = no_panic.noreturnReturned; }; fn myPanicNew(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {}; std.process.exit(0); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="third panic message: integer overflow\n" diff --git a/test/incremental/change_shift_op b/test/incremental/change_shift_op index 557c17c0b1adffc9a8d606f7af40278ec6e05fd7..af849c0d5b6c28b3ee5a3d974ff9e74004f05ac0 100644 --- a/test/incremental/change_shift_op +++ b/test/incremental/change_shift_op @@ -9,10 +9,11 @@ pub fn main() !void { try foo(0x1300); } fn foo(x: u16) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("0x{x}\n", .{x << 4}); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="0x3000\n" #update=change to right shift #file=main.zig @@ -20,8 +21,9 @@ pub fn main() !void { try foo(0x1300); } fn foo(x: u16) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("0x{x}\n", .{x >> 4}); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="0x130\n" diff --git a/test/incremental/change_struct_same_fields b/test/incremental/change_struct_same_fields index ef8ffab52daa224c7ca48f0a642d3947b21c86cb..3ba715f906d087a34f5ce7f7aa5a81eb5012ce97 100644 --- a/test/incremental/change_struct_same_fields +++ b/test/incremental/change_struct_same_fields @@ -11,13 +11,14 @@ pub fn main() !void { try foo(&val); } fn foo(val: *const S) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print( "{d} {d}\n", .{ val.x, val.y }, ); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="100 200\n" #update=change struct layout @@ -28,13 +29,14 @@ pub fn main() !void { try foo(&val); } fn foo(val: *const S) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print( "{d} {d}\n", .{ val.x, val.y }, ); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="100 200\n" #update=change values @@ -45,11 +47,12 @@ pub fn main() !void { try foo(&val); } fn foo(val: *const S) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print( "{d} {d}\n", .{ val.x, val.y }, ); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="1234 5678\n" diff --git a/test/incremental/change_zon_file b/test/incremental/change_zon_file index 8a4c69cf21327ddb3fa84ca44ed773d2b150aa70..a966df5471c270edd0e67568e930e3a91687f839 100644 --- a/test/incremental/change_zon_file +++ b/test/incremental/change_zon_file @@ -8,8 +8,9 @@ const std = @import("std"); const message: []const u8 = @import("message.zon"); pub fn main() !void { - try std.Io.File.stdout().writeAll(message); + try std.Io.File.stdout().writeStreamingAll(io, message); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #file=message.zon "Hello, World!\n" #expect_stdout="Hello, World!\n" @@ -29,8 +30,9 @@ pub fn main() !void { const std = @import("std"); const message: []const u8 = @import("message.zon"); pub fn main() !void { - try std.Io.File.stdout().writeAll("a hardcoded string\n"); + try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n"); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound #expect_error=main.zig:2:37: note: file imported here @@ -44,6 +46,7 @@ pub fn main() !void { const std = @import("std"); const message: []const u8 = @import("message.zon"); pub fn main() !void { - try std.Io.File.stdout().writeAll(message); + try std.Io.File.stdout().writeStreamingAll(io, message); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="We're back, World!\n" diff --git a/test/incremental/change_zon_file_no_result_type b/test/incremental/change_zon_file_no_result_type index 2dd601765fcb944888942de439864b65e4a0511a..6b3aa73dc6ee87c6e0e64fb2eaeb56508b8c10c7 100644 --- a/test/incremental/change_zon_file_no_result_type +++ b/test/incremental/change_zon_file_no_result_type @@ -6,8 +6,9 @@ #update=initial version #file=main.zig const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); pub fn main() !void { - try std.Io.File.stdout().writeAll(@import("foo.zon").message); + try std.Io.File.stdout().writeStreamingAll(io, @import("foo.zon").message); } #file=foo.zon .{ diff --git a/test/incremental/compile_log b/test/incremental/compile_log index d52259f122c18c03f69ed4402b07462eefd09e7c..19ff7237f275a9cc946665ef1e8e08015d80bb56 100644 --- a/test/incremental/compile_log +++ b/test/incremental/compile_log @@ -8,17 +8,19 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="Hello, World!\n" #update=add compile log #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); @compileLog("this is a log"); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_error=main.zig:4:5: error: found compile log statement #expect_compile_log=@as(*const [13:0]u8, "this is a log") @@ -26,6 +28,7 @@ pub fn main() !void { #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="Hello, World!\n" diff --git a/test/incremental/fix_astgen_failure b/test/incremental/fix_astgen_failure index 4dce2492e7f05244baa776b156b29d1564b62c54..dca371f521800d85e423cf68f008d78b598cfc97 100644 --- a/test/incremental/fix_astgen_failure +++ b/test/incremental/fix_astgen_failure @@ -10,28 +10,31 @@ pub fn main() !void { } #file=foo.zig pub fn hello() !void { - try std.Io.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } #expect_error=foo.zig:2:9: error: use of undeclared identifier 'std' #update=fix the error #file=foo.zig const std = @import("std"); pub fn hello() !void { - try std.Io.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="Hello, World!\n" #update=add new error #file=foo.zig const std = @import("std"); pub fn hello() !void { - try std.Io.File.stdout().writeAll(hello_str); + try std.Io.File.stdout().writeStreamingAll(io, hello_str); } -#expect_error=foo.zig:3:39: error: use of undeclared identifier 'hello_str' +const io = std.Io.Threaded.global_single_threaded.ioBasic(); +#expect_error=foo.zig:3:52: error: use of undeclared identifier 'hello_str' #update=fix the new error #file=foo.zig const std = @import("std"); const hello_str = "Hello, World! Again!\n"; pub fn hello() !void { - try std.Io.File.stdout().writeAll(hello_str); + try std.Io.File.stdout().writeStreamingAll(io, hello_str); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="Hello, World! Again!\n" diff --git a/test/incremental/function_becomes_inline b/test/incremental/function_becomes_inline index 07cd98ef283ca5950985a6b2d0f93df99ac67bab..4021575842527a1c9705b411b634b354d44a6e04 100644 --- a/test/incremental/function_becomes_inline +++ b/test/incremental/function_becomes_inline @@ -8,9 +8,10 @@ pub fn main() !void { try foo(); } fn foo() !void { - try std.Io.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="Hello, World!\n" #update=make function inline @@ -19,9 +20,10 @@ pub fn main() !void { try foo(); } inline fn foo() !void { - try std.Io.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="Hello, World!\n" #update=change string @@ -30,7 +32,8 @@ pub fn main() !void { try foo(); } inline fn foo() !void { - try std.Io.File.stdout().writeAll("Hello, `inline` World!\n"); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, `inline` World!\n"); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="Hello, `inline` World!\n" diff --git a/test/incremental/hello b/test/incremental/hello index 8ff8f61bc0cbb5ee9a040379e8a61be97927a55e..48659e1879ed97bad09ab32130401de4563c23cd 100644 --- a/test/incremental/hello +++ b/test/incremental/hello @@ -6,14 +6,16 @@ #update=initial version #file=main.zig const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); pub fn main() !void { - try std.Io.File.stdout().writeAll("good morning\n"); + try std.Io.File.stdout().writeStreamingAll(io, "good morning\n"); } #expect_stdout="good morning\n" #update=change the string #file=main.zig const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); pub fn main() !void { - try std.Io.File.stdout().writeAll("おはようございます\n"); + try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n"); } #expect_stdout="おはようございます\n" diff --git a/test/incremental/make_decl_pub b/test/incremental/make_decl_pub index 9d90ca51ebc2322671c670fd9896e09087ad694c..b193deb68cf07f5c4709eb42da2a65d2997e4d92 100644 --- a/test/incremental/make_decl_pub +++ b/test/incremental/make_decl_pub @@ -12,8 +12,9 @@ pub fn main() !void { #file=foo.zig const std = @import("std"); fn hello() !void { - try std.Io.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_error=main.zig:3:12: error: 'hello' is not marked 'pub' #expect_error=foo.zig:2:1: note: declared here @@ -21,6 +22,7 @@ fn hello() !void { #file=foo.zig const std = @import("std"); pub fn hello() !void { - try std.Io.File.stdout().writeAll("Hello, World!\n"); + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="Hello, World!\n" diff --git a/test/incremental/modify_inline_fn b/test/incremental/modify_inline_fn index 2c1478682ed7255cb85e478847913b0d3311b482..19e201f1d9e2e994c8ff65c4fe780e96d8c818b0 100644 --- a/test/incremental/modify_inline_fn +++ b/test/incremental/modify_inline_fn @@ -8,20 +8,22 @@ const std = @import("std"); pub fn main() !void { const str = getStr(); - try std.Io.File.stdout().writeAll(str); + try std.Io.File.stdout().writeStreamingAll(io, str); } inline fn getStr() []const u8 { return "foo\n"; } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="foo\n" #update=change the string #file=main.zig const std = @import("std"); pub fn main() !void { const str = getStr(); - try std.Io.File.stdout().writeAll(str); + try std.Io.File.stdout().writeStreamingAll(io, str); } inline fn getStr() []const u8 { return "bar\n"; } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="bar\n" diff --git a/test/incremental/move_src b/test/incremental/move_src index d6a21fc562730a05aec5ecbfb4c8541d6a651257..b79a25df8df11002a6d4e7b1bf78c976ea760372 100644 --- a/test/incremental/move_src +++ b/test/incremental/move_src @@ -7,7 +7,7 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() }); } fn foo() u32 { @@ -16,13 +16,14 @@ fn foo() u32 { fn bar() u32 { return 123; } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="7 123\n" #update=add newline #file=main.zig const std = @import("std"); pub fn main() !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() }); } @@ -32,4 +33,5 @@ fn foo() u32 { fn bar() u32 { return 123; } +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="8 123\n" diff --git a/test/incremental/no_change_preserves_tag_names b/test/incremental/no_change_preserves_tag_names index 6675d74166016b849c93aaf5663d6d6354770070..dc89face50dbf5acbe8be6f50dddeedac323d3d7 100644 --- a/test/incremental/no_change_preserves_tag_names +++ b/test/incremental/no_change_preserves_tag_names @@ -7,15 +7,17 @@ #file=main.zig const std = @import("std"); var some_enum: enum { first, second } = .first; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); pub fn main() !void { - try std.Io.File.stdout().writeStreamingAll(std.Io.Threaded.global_single_threaded.ioBasic(), @tagName(some_enum)); + try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum)); } #expect_stdout="first" #update=no change #file=main.zig const std = @import("std"); var some_enum: enum { first, second } = .first; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); pub fn main() !void { - try std.Io.File.stdout().writeStreamingAll(std.Io.Threaded.global_single_threaded.ioBasic(), @tagName(some_enum)); + try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum)); } #expect_stdout="first" diff --git a/test/incremental/recursive_function_becomes_non_recursive b/test/incremental/recursive_function_becomes_non_recursive index 20e43b64b717d1507c151c446560ce37616b9719..5cee1bfbcff10ab669ee367e80a833d800df1cc6 100644 --- a/test/incremental/recursive_function_becomes_non_recursive +++ b/test/incremental/recursive_function_becomes_non_recursive @@ -11,9 +11,10 @@ pub fn main() !void { fn foo(recurse: bool) !void { const stdout = std.Io.File.stdout(); if (recurse) return foo(true); - try stdout.writeAll("non-recursive path\n"); + try stdout.writeStreamingAll(io, "non-recursive path\n"); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="non-recursive path\n" #update=eliminate recursion and change argument @@ -23,8 +24,9 @@ pub fn main() !void { } fn foo(recurse: bool) !void { const stdout = std.Io.File.stdout(); - if (recurse) return stdout.writeAll("x==1\n"); - try stdout.writeAll("non-recursive path\n"); + if (recurse) return stdout.writeStreamingAll(io, "x==1\n"); + try stdout.writeStreamingAll(io, "non-recursive path\n"); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="x==1\n" diff --git a/test/incremental/remove_enum_field b/test/incremental/remove_enum_field index bbcf7718e9c8a69cf675d163de898af972468db5..c964285707dec13202b9b917e390b6c90bf509ad 100644 --- a/test/incremental/remove_enum_field +++ b/test/incremental/remove_enum_field @@ -10,10 +10,11 @@ const MyEnum = enum(u8) { bar = 2, }; pub fn main() !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)}); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="1\n" #update=remove enum field #file=main.zig @@ -22,9 +23,10 @@ const MyEnum = enum(u8) { bar = 2, }; pub fn main() !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)}); } const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_error=main.zig:7:69: error: enum 'main.MyEnum' has no member named 'foo' #expect_error=main.zig:1:16: note: enum declared here diff --git a/test/incremental/unreferenced_error b/test/incremental/unreferenced_error index 1c1f1f7a8fbd5f8a3d738645cda6b4da1890043c..c9a3277487c103a656a2b72fcb702fab27862856 100644 --- a/test/incremental/unreferenced_error +++ b/test/incremental/unreferenced_error @@ -7,36 +7,40 @@ #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(a); + try std.Io.File.stdout().writeStreamingAll(io, a); } const a = "Hello, World!\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="Hello, World!\n" #update=introduce compile error #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(a); + try std.Io.File.stdout().writeStreamingAll(io, a); } const a = @compileError("bad a"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_error=main.zig:5:11: error: bad a #update=remove error reference #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(b); + try std.Io.File.stdout().writeStreamingAll(io, b); } const a = @compileError("bad a"); const b = "Hi there!\n"; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="Hi there!\n" #update=introduce and remove reference to error #file=main.zig const std = @import("std"); pub fn main() !void { - try std.Io.File.stdout().writeAll(a); + try std.Io.File.stdout().writeStreamingAll(io, a); } const a = "Back to a\n"; const b = @compileError("bad b"); +const io = std.Io.Threaded.global_single_threaded.ioBasic(); #expect_stdout="Back to a\n" -- 2.54.0 From fd0c324cb05d0d30f8ca5ae44f6db5072975b125 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 21 Dec 2025 17:06:02 -0800 Subject: [PATCH 122/195] std.debug: fix simple_panic --- lib/std/debug.zig | 5 +---- lib/std/debug/simple_panic.zig | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 0e804e23483fd52230b7862e3357a14e02b46c0e..5129a70b023db7ec334245f5ea243216c9e3b721 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -489,10 +489,7 @@ const use_trap_panic = switch (builtin.zig_backend) { }; /// Dumps a stack trace to standard error, then aborts. -pub fn defaultPanic( - msg: []const u8, - first_trace_addr: ?usize, -) noreturn { +pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn { @branchHint(.cold); if (use_trap_panic) @trap(); diff --git a/lib/std/debug/simple_panic.zig b/lib/std/debug/simple_panic.zig index 34e8f7b43c8e7dcaaa6a55107757481dc3c6c678..a5a09fa1162e75cf9aad5a69a1bfcd3a3e1845d9 100644 --- a/lib/std/debug/simple_panic.zig +++ b/lib/std/debug/simple_panic.zig @@ -14,9 +14,8 @@ const std = @import("../std.zig"); pub fn call(msg: []const u8, ra: ?usize) noreturn { @branchHint(.cold); _ = ra; - const stderr_writer = std.debug.lockStderr(&.{}, null).terminal().writer; + const stderr_writer = &std.debug.lockStderr(&.{}).file_writer.interface; stderr_writer.writeAll(msg) catch {}; - stderr_writer.flush(msg) catch {}; @trap(); } -- 2.54.0 From ad08117e9daf5d2a34be1ca71a23366f39079990 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 20 Dec 2025 20:15:14 -0800 Subject: [PATCH 123/195] Fix sizing of buffer/reservation size for dirReadWindows --- lib/std/Io/Dir.zig | 7 ++++++- lib/std/Io/Threaded.zig | 8 +++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 60cdd88453d3f05241207412df41cf6b19688ef0..a84a20167dd82c29dde487f75d738ca992743b25 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -105,7 +105,12 @@ pub const Reader = struct { pub const min_buffer_len = switch (native_os) { .linux => std.mem.alignForward(usize, @sizeOf(std.os.linux.dirent64), 8) + std.mem.alignForward(usize, max_name_bytes, 8), - .windows => std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)), + .windows => len: { + const max_info_len = @sizeOf(std.os.windows.FILE_BOTH_DIR_INFORMATION) + std.os.windows.NAME_MAX * 2; + const info_align = @alignOf(std.os.windows.FILE_BOTH_DIR_INFORMATION); + const reserved_len = std.mem.alignForward(usize, max_name_bytes, info_align) - max_info_len; + break :len std.mem.alignForward(usize, reserved_len, info_align) + max_info_len; + }, .wasi => @sizeOf(std.os.wasi.dirent_t) + std.mem.alignForward(usize, max_name_bytes, @alignOf(std.os.wasi.dirent_t)), else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)), diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 182a8f4b6d45ae6561bb8ae6fca88a86d6bbe790..a586ae2b38acafee8df177d8055c382de5cf0010 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3757,11 +3757,13 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D // reserve enough to get us to up to having `3 * NAME_MAX` bytes available when taking into account // that we have the ability to write over top of the reserved memory + the full footprint of that // particular `FILE_BOTH_DIR_INFORMATION`. - const reserve_needed = w.NAME_MAX - @sizeOf(w.FILE_BOTH_DIR_INFORMATION); - const unreserved_start = std.mem.alignForward(usize, reserve_needed, @alignOf(usize)); + const max_info_len = @sizeOf(w.FILE_BOTH_DIR_INFORMATION) + w.NAME_MAX * 2; + const info_align = @alignOf(w.FILE_BOTH_DIR_INFORMATION); + const reserve_needed = std.mem.alignForward(usize, Dir.max_name_bytes, info_align) - max_info_len; + const unreserved_start = std.mem.alignForward(usize, reserve_needed, info_align); const unreserved_buffer = dr.buffer[unreserved_start..]; // This is enforced by `Dir.Reader` - assert(unreserved_buffer.len >= @sizeOf(w.FILE_BOTH_DIR_INFORMATION) + w.NAME_MAX * 2); + assert(unreserved_buffer.len >= max_info_len); var name_index: usize = 0; var buffer_index: usize = 0; -- 2.54.0 From e442a0ecc23d315f99f62684c6240e7c1cb6e9ce Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 20 Dec 2025 20:18:07 -0800 Subject: [PATCH 124/195] Dir.readFile/readFileAlloc: take advantage of `.allow_directory = false` on Windows Since we know the read will fail for directories, we can take advantage of Windows being able to fail with IsDir during open to avoid needing to wait until the read to find out about the directory-ness of the file. --- lib/std/Io/Dir.zig | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index a84a20167dd82c29dde487f75d738ca992743b25..e7b18066160adf95cbace687eb00843eb9175718 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -633,7 +633,11 @@ pub const ReadFileError = File.OpenError || File.Reader.Error; /// * On WASI, `file_path` should be encoded as valid UTF-8. /// * On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileError![]u8 { - var file = try dir.openFile(io, file_path, .{}); + var file = try dir.openFile(io, file_path, .{ + // We can take advantage of this on Windows since it doesn't involve any extra syscalls, + // so we can get error.IsDir during open rather than during the read. + .allow_directory = if (native_os == .windows) false else true, + }); defer file.close(io); var reader = file.reader(io, &.{}); @@ -1217,7 +1221,11 @@ pub fn readFileAllocOptions( comptime alignment: std.mem.Alignment, comptime sentinel: ?u8, ) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) { - var file = try dir.openFile(io, sub_path, .{}); + var file = try dir.openFile(io, sub_path, .{ + // We can take advantage of this on Windows since it doesn't involve any extra syscalls, + // so we can get error.IsDir during open rather than during the read. + .allow_directory = if (native_os == .windows) false else true, + }); defer file.close(io); var file_reader = file.reader(io, &.{}); return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) { -- 2.54.0 From bb788cd3925b7fb21e694d3586fb4c57a26005d9 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 20 Dec 2025 20:20:14 -0800 Subject: [PATCH 125/195] dirOpenFileWtf16: Disallow opening directories when requesting write permissions This matches the behavior of POSIX --- lib/std/Io/Threaded.zig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index a586ae2b38acafee8df177d8055c382de5cf0010..10be5734527fe1e59fbf939031fec35b051644be 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2917,8 +2917,9 @@ pub fn dirOpenFileWtf16( sub_path_w: [:0]const u16, flags: File.OpenFlags, ) File.OpenError!File { - if (std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; - if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; + const allow_directory = flags.allow_directory and !flags.isWrite(); + if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; + if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; const current_thread = Thread.getCurrent(t); const w = windows; @@ -2963,7 +2964,7 @@ pub fn dirOpenFileWtf16( .OPEN, .{ .IO = if (flags.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, - .NON_DIRECTORY_FILE = !flags.allow_directory, + .NON_DIRECTORY_FILE = !allow_directory, .OPEN_REPARSE_POINT = !flags.follow_symlinks, }, null, -- 2.54.0 From 8968d75fb1efa89f5264c5668721c5798267a79a Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 20 Dec 2025 20:50:53 -0800 Subject: [PATCH 126/195] fileStat needs read permissions on Windows --- lib/std/Io/test.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index ef0d45d95366b88b227eea8e2fd4e99f62c91411..796800f000ad78ad915783219a861d2374161c1e 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -74,7 +74,7 @@ test "File.Writer.seekTo" { @memset(&data, 0x55); const tmp_file_name = "temp_test_file.txt"; - var file = try tmp.dir.createFile(io, tmp_file_name, .{}); + var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true }); defer file.close(io); var fw = file.writerStreaming(io, &.{}); @@ -92,7 +92,7 @@ test "File.setLength" { defer tmp.cleanup(); const tmp_file_name = "temp_test_file.txt"; - var file = try tmp.dir.createFile(io, tmp_file_name, .{}); + var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true }); defer file.close(io); var fw = file.writerStreaming(io, &.{}); -- 2.54.0 From 51a6f3a5251096f346eb28b22e1c20ed2ceb7d9e Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 20 Dec 2025 21:14:25 -0800 Subject: [PATCH 127/195] Update a few more callsites for std.Io changes --- lib/compiler/translate-c/main.zig | 2 +- lib/std/Build.zig | 2 +- lib/std/Build/Cache.zig | 2 +- lib/std/zig/LibCInstallation.zig | 2 +- lib/std/zig/WindowsSdk.zig | 24 +++++++++++++----------- src/link/Wasm.zig | 2 +- src/main.zig | 2 +- 7 files changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index 80dc67fbfe2608d787b97241b0f4c34ed3cb9122..76d67afd1819112a9bfc7af6685bf57d14bbade1 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -35,7 +35,7 @@ pub fn main() u8 { } var stderr_buf: [1024]u8 = undefined; - var stderr = Io.File.stderr().writer(&stderr_buf); + var stderr = Io.File.stderr().writer(io, &stderr_buf); var diagnostics: aro.Diagnostics = switch (zig_integration) { false => .{ .output = .{ .to_writer = .{ .color = .detect(stderr.file), diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 4b8909e6892a814c8087f063ba9b1ed34c54ad11..29499d8767d0b69e6a1e294a4107ca2ff2610063 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1776,7 +1776,7 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { while (it.next()) |ext| { if (!supportedWindowsProgramExtension(ext)) continue; - return fs.realpathAlloc(b.allocator, b.fmt("{s}{s}", .{ full_path, ext })) catch |err| switch (err) { + return fs.realPathFileAlloc(b.graph.io, b.fmt("{s}{s}", .{ full_path, ext }), b.allocator) catch |err| switch (err) { error.OutOfMemory => @panic("OOM"), else => continue, }; diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 396f2043504658672b5ec2fe12d69a0b65e69560..b384ab13ff9ae1fc1509b36e60e34bbc91a57177 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -298,7 +298,7 @@ pub const Lock = struct { if (builtin.os.tag == .windows) { // Windows does not guarantee that locks are immediately unlocked when // the file handle is closed. See LockFileEx documentation. - lock.manifest_file.unlock(); + lock.manifest_file.unlock(io); } lock.manifest_file.close(io); diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index 83ed23be042fe19c7473ae1fc3bf5d32a3dac43c..f2489f9ee741ac30a063ca81170e2a4976d314d9 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -201,7 +201,7 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib try self.findNativeMsvcIncludeDir(gpa, io, sdk); try self.findNativeMsvcLibDir(gpa, sdk); try self.findNativeKernel32LibDir(gpa, io, args, sdk); - try self.findNativeIncludeDirWindows(gpa, io, args, sdk); + try self.findNativeIncludeDirWindows(gpa, io, sdk); try self.findNativeCrtDirWindows(gpa, io, args.target, sdk); } else if (is_haiku) { try self.findNativeIncludeDirPosix(gpa, io, args); diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index 55680707936d2f33414b4d1d3423dec4117af693..b0f24c2acaf61ea1f7add8bc975ee400980d2d2f 100644 --- a/lib/std/zig/WindowsSdk.zig +++ b/lib/std/zig/WindowsSdk.zig @@ -24,7 +24,7 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len /// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory. /// Caller owns the result's fields. /// Returns memory allocated by `gpa` -pub fn find(gpa: Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { +pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { if (builtin.os.tag != .windows) return error.NotFound; //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed @@ -33,7 +33,7 @@ pub fn find(gpa: Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFo }; defer roots_key.closeKey(); - const windows10sdk = Installation.find(gpa, roots_key, "KitsRoot10", "", "v10.0") catch |err| switch (err) { + const windows10sdk = Installation.find(gpa, io, roots_key, "KitsRoot10", "", "v10.0") catch |err| switch (err) { error.InstallationNotFound => null, error.PathTooLong => null, error.VersionTooLong => null, @@ -41,7 +41,7 @@ pub fn find(gpa: Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFo }; errdefer if (windows10sdk) |*w| w.free(gpa); - const windows81sdk = Installation.find(gpa, roots_key, "KitsRoot81", "winver", "v8.1") catch |err| switch (err) { + const windows81sdk = Installation.find(gpa, io, roots_key, "KitsRoot81", "winver", "v8.1") catch |err| switch (err) { error.InstallationNotFound => null, error.PathTooLong => null, error.VersionTooLong => null, @@ -49,7 +49,7 @@ pub fn find(gpa: Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFo }; errdefer if (windows81sdk) |*w| w.free(gpa); - const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, arch) catch |err| switch (err) { + const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, arch) catch |err| switch (err) { error.MsvcLibDirNotFound => null, error.OutOfMemory => return error.OutOfMemory, }; @@ -80,6 +80,7 @@ pub fn free(sdk: WindowsSdk, gpa: Allocator) void { fn iterateAndFilterByVersion( iterator: *Dir.Iterator, gpa: Allocator, + io: Io, prefix: []const u8, ) error{OutOfMemory}![][]const u8 { const Version = struct { @@ -104,7 +105,7 @@ fn iterateAndFilterByVersion( dirs.deinit(); } - iterate: while (iterator.next() catch null) |entry| { + iterate: while (iterator.next(io) catch null) |entry| { if (entry.kind != .directory) continue; if (!std.mem.startsWith(u8, entry.name, prefix)) continue; @@ -420,13 +421,14 @@ pub const Installation = struct { /// Caller owns the result's fields. fn find( gpa: Allocator, + io: Io, roots_key: RegistryWtf8, roots_subkey: []const u8, prefix: []const u8, version_key_name: []const u8, ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { roots: { - const installation = findFromRoot(gpa, roots_key, roots_subkey, prefix) catch + const installation = findFromRoot(gpa, io, roots_key, roots_subkey, prefix) catch break :roots; if (installation.isValidVersion()) return installation; installation.free(gpa); @@ -485,7 +487,7 @@ pub const Installation = struct { defer sdk_lib_dir.close(io); var iterator = sdk_lib_dir.iterate(); - const versions = try iterateAndFilterByVersion(&iterator, gpa, prefix); + const versions = try iterateAndFilterByVersion(&iterator, gpa, io, prefix); if (versions.len == 0) return error.InstallationNotFound; defer { for (versions[1..]) |version| gpa.free(version); @@ -673,7 +675,7 @@ const MsvcLibDir = struct { // First, try getting the packages cache path from the registry. // This only seems to exist when the path is different from the default. method1: { - return findInstancesDirViaSetup(gpa) catch |err| switch (err) { + return findInstancesDirViaSetup(gpa, io) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.PathNotFound => break :method1, }; @@ -766,7 +768,7 @@ const MsvcLibDir = struct { var latest_version: u64 = 0; var instances_dir_it = instances_dir.iterateAssumeFirstIteration(); - while (instances_dir_it.next() catch return error.PathNotFound) |entry| { + while (instances_dir_it.next(io) catch return error.PathNotFound) |entry| { if (entry.kind != .directory) continue; var writer: Writer = .fixed(&state_subpath_buf); @@ -828,7 +830,7 @@ const MsvcLibDir = struct { try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); var default_tools_version_buf: [512]u8 = undefined; - const default_tools_version_contents = Dir.cwd().readFile(lib_dir_buf.items, &default_tools_version_buf) catch { + const default_tools_version_contents = Dir.cwd().readFile(io, lib_dir_buf.items, &default_tools_version_buf) catch { return error.PathNotFound; }; var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n"); @@ -871,7 +873,7 @@ const MsvcLibDir = struct { defer visualstudio_folder.close(io); var iterator = visualstudio_folder.iterate(); - break :vs_versions try iterateAndFilterByVersion(&iterator, gpa, ""); + break :vs_versions try iterateAndFilterByVersion(&iterator, gpa, io, ""); }; defer { for (vs_versions) |vs_version| gpa.free(vs_version); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 216a641db3bfdf96f6c41d1b79ecf956fa6dc21a..e9c76ff40a0d8c809197de186c3e8faea577932b 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3008,7 +3008,7 @@ pub fn createEmpty( else .default_file else - 0, + .default_file, }); wasm.name = emit.sub_path; diff --git a/src/main.zig b/src/main.zig index 507b01ab0eaf6377da8d807e5ee476ae745800e8..3a12882468f3138478d43b084a7161a2c65c6195 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4513,7 +4513,7 @@ fn runOrTestHotSwap( // tmp zig-cache and use it to spawn the child process. This way we are free to update // the binary with each requested hot update. .windows => blk: { - try lf.emit.root_dir.handle.copyFile(lf.emit.sub_path, comp.dirs.local_cache.handle, lf.emit.sub_path, .{}); + try lf.emit.root_dir.handle.copyFile(lf.emit.sub_path, comp.dirs.local_cache.handle, lf.emit.sub_path, io, .{}); break :blk try fs.path.join(gpa, &.{ comp.dirs.local_cache.path orelse ".", lf.emit.sub_path }); }, -- 2.54.0 From 70e19ea3533528b90b4548e162e334f5a8392e64 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 21 Dec 2025 17:17:11 -0800 Subject: [PATCH 128/195] update more "realpath" callsites --- lib/std/Build.zig | 11 +++++++++-- test/standalone/windows_bat_args/test.zig | 2 +- test/standalone/windows_spawn/main.zig | 4 ++-- tools/fetch_them_macos_headers.zig | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 29499d8767d0b69e6a1e294a4107ca2ff2610063..317e4600a47d14d9f0fee390495635a93a127206 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1762,7 +1762,10 @@ fn supportedWindowsProgramExtension(ext: []const u8) bool { } fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { - if (fs.realpathAlloc(b.allocator, full_path)) |p| { + const io = b.graph.io; + const arena = b.allocator; + + if (Io.Dir.realPathFileAbsoluteAlloc(io, full_path, arena)) |p| { return p; } else |err| switch (err) { error.OutOfMemory => @panic("OOM"), @@ -1776,7 +1779,11 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { while (it.next()) |ext| { if (!supportedWindowsProgramExtension(ext)) continue; - return fs.realPathFileAlloc(b.graph.io, b.fmt("{s}{s}", .{ full_path, ext }), b.allocator) catch |err| switch (err) { + return Io.Dir.realPathFileAbsoluteAlloc( + io, + b.fmt("{s}{s}", .{ full_path, ext }), + arena, + ) catch |err| switch (err) { error.OutOfMemory => @panic("OOM"), else => continue, }; diff --git a/test/standalone/windows_bat_args/test.zig b/test/standalone/windows_bat_args/test.zig index 04d2fa37f0954c9aa5f54ea89b801d37e5c51467..4630fa33373c309102b1a5d5b13261626bceba8a 100644 --- a/test/standalone/windows_bat_args/test.zig +++ b/test/standalone/windows_bat_args/test.zig @@ -101,7 +101,7 @@ pub fn main() anyerror!void { // operable program or batch file. try std.testing.expectError(error.FileNotFound, testExecBat(gpa, "args1.bat .. ", &.{"abc"}, null)); const absolute_with_trailing = blk: { - const absolute_path = try std.fs.realpathAlloc(gpa, "args1.bat"); + const absolute_path = try Io.Dir.realPathFileAbsoluteAlloc(io, "args1.bat", gpa); defer gpa.free(absolute_path); break :blk try std.mem.concat(gpa, u8, &.{ absolute_path, " .. " }); }; diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index d89dc18bc2db0534d34eacac749f0c7153af5e8b..569eba04070ada74ad1060e8e904201b869f4d73 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -22,11 +22,11 @@ pub fn main() anyerror!void { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const tmp_absolute_path = try tmp.dir.realpathAlloc(gpa, "."); + const tmp_absolute_path = try tmp.dir.realPathFileAlloc(io, ".", gpa); defer gpa.free(tmp_absolute_path); const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeAllocZ(gpa, tmp_absolute_path); defer gpa.free(tmp_absolute_path_w); - const cwd_absolute_path = try Io.Dir.cwd().realpathAlloc(gpa, "."); + const cwd_absolute_path = try Io.Dir.cwd().realPathFileAlloc(io, ".", gpa); defer gpa.free(cwd_absolute_path); const tmp_relative_path = try std.fs.path.relative(gpa, cwd_absolute_path, tmp_absolute_path); defer gpa.free(tmp_relative_path); diff --git a/tools/fetch_them_macos_headers.zig b/tools/fetch_them_macos_headers.zig index 6b254954723fb659fce060446f5ca0c8b9e42410..6575aa2b566ee713b4d675e1bd6437ce5d31102a 100644 --- a/tools/fetch_them_macos_headers.zig +++ b/tools/fetch_them_macos_headers.zig @@ -134,7 +134,7 @@ fn fetchTarget( ) !void { const tmp_filename = "macos-headers"; const headers_list_filename = "macos-headers.o.d"; - const tmp_path = try tmp.dir.realpathAlloc(arena, "."); + const tmp_path = try tmp.dir.realPathFileAlloc(io, ".", arena); const tmp_file_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename }); const headers_list_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename }); -- 2.54.0 From 486ed907f7cf3034e8f9ca1d2c445beec8b2df16 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Fri, 19 Dec 2025 22:09:34 -0500 Subject: [PATCH 129/195] dragonfly: define std.c.nlink_t --- lib/std/c.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 25e52bac0c09e09dbfdd8069a294933742a6b420..fdd544cba944e5f1276286e0a120e38330cfce24 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -161,7 +161,7 @@ pub const nlink_t = switch (native_os) { .wasi => c_ulonglong, // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L45 .freebsd, .serenity => u64, - .openbsd, .netbsd, .illumos => u32, + .openbsd, .netbsd, .dragonfly, .illumos => u32, .haiku => i32, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => u16, else => u0, -- 2.54.0 From 90bc371f564f7992a5137abddd958b012f8855e6 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Fri, 19 Dec 2025 22:43:50 -0500 Subject: [PATCH 130/195] openbsd: use Io.Dir.realPathFileAbsolute --- lib/std/Io/Threaded.zig | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 10be5734527fe1e59fbf939031fec35b051644be..0e67fd1a9d3f762adbebf67d1f1937f5ddfa8b73 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7194,15 +7194,14 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex if (std.mem.indexOf(u8, argv0, "/") != null) { // argv[0] is a path (relative or absolute): use realpath(3) directly var real_path_buf: [posix.PATH_MAX]u8 = undefined; - const real_path = Io.Dir.realPathFileAbsolute(ioBasic(t), std.os.argv[0], &real_path_buf) catch |err| switch (err) { + const real_path_len = Io.Dir.realPathFileAbsolute(ioBasic(t), argv0, &real_path_buf) catch |err| switch (err) { error.NetworkNotFound => unreachable, // Windows-only else => |e| return e, }; - if (real_path.len > out_buffer.len) + if (real_path_len > out_buffer.len) return error.NameTooLong; - const result = out_buffer[0..real_path.len]; - @memcpy(result, real_path); - return result.len; + @memcpy(out_buffer[0..real_path_len], real_path_buf[0..real_path_len]); + return real_path_len; } else if (argv0.len != 0) { // argv[0] is not empty (and not a path): search it inside PATH const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; @@ -7214,13 +7213,12 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex }, 0) catch continue; var real_path_buf: [posix.PATH_MAX]u8 = undefined; - if (Io.Dir.realPathFileAbsolute(ioBasic(t), resolved_path, &real_path_buf)) |real_path| { + if (Io.Dir.realPathFileAbsolute(ioBasic(t), resolved_path, &real_path_buf)) |real_path_len| { // found a file, and hope it is the right file - if (real_path.len > out_buffer.len) + if (real_path_len > out_buffer.len) return error.NameTooLong; - const result = out_buffer[0..real_path.len]; - @memcpy(result, real_path); - return result.len; + @memcpy(out_buffer[0..real_path_len], real_path_buf[0..real_path_len]); + return real_path_len; } else |_| continue; } } -- 2.54.0 From 8fdfeb624dae7d60187c7a2c44c9624cca1bd461 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Sat, 20 Dec 2025 08:30:52 -0500 Subject: [PATCH 131/195] openbsd: fix OS version detection - do not include resulting nullz from sysctl MIB fetch - fixes bug: "7.8" is a std.Target.Query.parseVersion error --- lib/compiler_rt/emutls.zig | 2 +- lib/std/zig/system.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compiler_rt/emutls.zig b/lib/compiler_rt/emutls.zig index 0bb427b651af7c70954e91d0d0c401f20d80f25d..d081aa92943e7f60401b79dda53c8810c963e497 100644 --- a/lib/compiler_rt/emutls.zig +++ b/lib/compiler_rt/emutls.zig @@ -7,7 +7,7 @@ const std = @import("std"); const builtin = @import("builtin"); const common = @import("common.zig"); -const abort = std.posix.abort; +const abort = std.process.abort; const assert = std.debug.assert; const expect = std.testing.expect; diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 92c23a2c18b7878395853f4576bf47d24f748d38..dfa10b84b6deab60f618663cecf950aa9915c314 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -323,7 +323,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { error.Unexpected => return error.OSVersionDetectionFail, }; - if (Target.Query.parseVersion(buf[0..len :0])) |ver| { + if (Target.Query.parseVersion(buf[0 .. len - 1 :0])) |ver| { assert(ver.build == null); assert(ver.pre == null); os.version_range.semver.min = ver; -- 2.54.0 From 7014976d3dc38320257871f0e218ded987843bc1 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Sat, 20 Dec 2025 18:25:04 -0500 Subject: [PATCH 132/195] openbsd: avoid error.OperationUnsupported - realPathPosix does not support OpenBSD - change processExecutablePath OpenBSD section to use std.c.realpath --- lib/std/Io/Threaded.zig | 70 +++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0e67fd1a9d3f762adbebf67d1f1937f5ddfa8b73..c40ce3866c0f66b1dded1cbe18ebb6c3f4507086 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7191,35 +7191,57 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex return error.FileNotFound; const argv0 = std.mem.span(std.os.argv[0]); - if (std.mem.indexOf(u8, argv0, "/") != null) { + if (std.mem.findScalar(u8, argv0, std.fs.path.sep_posix) != null) { // argv[0] is a path (relative or absolute): use realpath(3) directly - var real_path_buf: [posix.PATH_MAX]u8 = undefined; - const real_path_len = Io.Dir.realPathFileAbsolute(ioBasic(t), argv0, &real_path_buf) catch |err| switch (err) { - error.NetworkNotFound => unreachable, // Windows-only - else => |e| return e, - }; - if (real_path_len > out_buffer.len) + const current_thread = Thread.getCurrent(t); + var resolved_buf: [std.c.PATH_MAX]u8 = undefined; + { + try current_thread.beginSyscall(); + defer current_thread.endSyscall(); + _ = std.c.realpath(std.os.argv[0], &resolved_buf) orelse switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { + .SUCCESS => unreachable, + .ACCES => return error.AccessDenied, + .INVAL => unreachable, // the pathname argument is a null pointer + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => unreachable, // sufficient storage space is unavailable for allocation + else => |err| return posix.unexpectedErrno(err), + }; + } + const resolved = std.mem.sliceTo(&resolved_buf, 0); + if (resolved.len > out_buffer.len) return error.NameTooLong; - @memcpy(out_buffer[0..real_path_len], real_path_buf[0..real_path_len]); - return real_path_len; + @memcpy(out_buffer[0..resolved.len], resolved); + return resolved.len; } else if (argv0.len != 0) { - // argv[0] is not empty (and not a path): search it inside PATH + // argv[0] is not empty (and not a path): search PATH + const current_thread = Thread.getCurrent(t); const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; - var path_it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter); - while (path_it.next()) |a_path| { - var resolved_path_buf: [posix.PATH_MAX - 1:0]u8 = undefined; - const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ - a_path, std.os.argv[0], - }, 0) catch continue; + var it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter); + while (it.next()) |dir| { + var prospect_buf: [std.c.PATH_MAX]u8 = undefined; + _ = std.fmt.bufPrintSentinel( + &prospect_buf, + "{s}" ++ std.fs.path.sep_str_posix ++ "{s}", + .{ dir, std.os.argv[0] }, + 0, + ) catch continue; - var real_path_buf: [posix.PATH_MAX]u8 = undefined; - if (Io.Dir.realPathFileAbsolute(ioBasic(t), resolved_path, &real_path_buf)) |real_path_len| { - // found a file, and hope it is the right file - if (real_path_len > out_buffer.len) - return error.NameTooLong; - @memcpy(out_buffer[0..real_path_len], real_path_buf[0..real_path_len]); - return real_path_len; - } else |_| continue; + var resolved_buf: [std.c.PATH_MAX]u8 = undefined; + try current_thread.beginSyscall(); + _ = std.c.realpath(@ptrCast(&prospect_buf), &resolved_buf) orelse { + current_thread.endSyscall(); + continue; + }; + current_thread.endSyscall(); + const resolved = std.mem.sliceTo(&resolved_buf, 0); + if (resolved.len > out_buffer.len) + return error.NameTooLong; + @memcpy(out_buffer[0..resolved.len], resolved); + return resolved.len; } } return error.FileNotFound; -- 2.54.0 From 1132e329d3bf6cbb1959046c4128175038c4bbed Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 21 Dec 2025 18:31:46 -0800 Subject: [PATCH 133/195] std.Io.Threaded: fix up processExecutablePath OpenBSD * policy is to always handle EINTR from all syscalls * assert the result of realpath * don't ignore errors from realpath * hard-code path separators for code simplicity --- lib/std/Io/Threaded.zig | 93 ++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 30 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c40ce3866c0f66b1dded1cbe18ebb6c3f4507086..ee8f0e11320fbd6a693399aef2fa1e8bd02ddd94 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7191,25 +7191,35 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex return error.FileNotFound; const argv0 = std.mem.span(std.os.argv[0]); - if (std.mem.findScalar(u8, argv0, std.fs.path.sep_posix) != null) { + if (std.mem.findScalar(u8, argv0, '/') != null) { // argv[0] is a path (relative or absolute): use realpath(3) directly const current_thread = Thread.getCurrent(t); var resolved_buf: [std.c.PATH_MAX]u8 = undefined; - { - try current_thread.beginSyscall(); - defer current_thread.endSyscall(); - _ = std.c.realpath(std.os.argv[0], &resolved_buf) orelse switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { - .SUCCESS => unreachable, - .ACCES => return error.AccessDenied, - .INVAL => unreachable, // the pathname argument is a null pointer - .IO => return error.InputOutput, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => unreachable, // sufficient storage space is unavailable for allocation - else => |err| return posix.unexpectedErrno(err), - }; + try current_thread.beginSyscall(); + while (true) { + if (std.c.realpath(std.os.argv[0], &resolved_buf)) |p| { + assert(p == &resolved_buf); + break current_thread.endSyscall(); + } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .INVAL => |err| return errnoBug(err), // the pathname argument is a null pointer + .IO => return error.InputOutput, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => |err| return errnoBug(err), // sufficient storage space is unavailable for allocation + else => |err| return posix.unexpectedErrno(err), + } + }, + } } const resolved = std.mem.sliceTo(&resolved_buf, 0); if (resolved.len > out_buffer.len) @@ -7220,23 +7230,46 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex // argv[0] is not empty (and not a path): search PATH const current_thread = Thread.getCurrent(t); const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; - var it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter); - while (it.next()) |dir| { - var prospect_buf: [std.c.PATH_MAX]u8 = undefined; - _ = std.fmt.bufPrintSentinel( - &prospect_buf, - "{s}" ++ std.fs.path.sep_str_posix ++ "{s}", - .{ dir, std.os.argv[0] }, - 0, - ) catch continue; + var it = std.mem.tokenizeScalar(u8, PATH, ':'); + it: while (it.next()) |dir| { + var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined; + const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ + dir, std.os.argv[0], + }, 0) catch continue; var resolved_buf: [std.c.PATH_MAX]u8 = undefined; try current_thread.beginSyscall(); - _ = std.c.realpath(@ptrCast(&prospect_buf), &resolved_buf) orelse { - current_thread.endSyscall(); - continue; - }; - current_thread.endSyscall(); + while (true) { + if (std.c.realpath(resolved_path, &resolved_buf)) |p| { + assert(p == &resolved_buf); + break current_thread.endSyscall(); + } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .NAMETOOLONG => { + current_thread.endSyscall(); + return error.NameTooLong; + }, + .NOMEM => { + current_thread.endSyscall(); + return error.SystemResources; + }, + .IO => { + current_thread.endSyscall(); + return error.InputOutput; + }, + .ACCES, .LOOP, .NOENT, .NOTDIR => { + current_thread.endSyscall(); + continue :it; + }, + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, + } + } const resolved = std.mem.sliceTo(&resolved_buf, 0); if (resolved.len > out_buffer.len) return error.NameTooLong; -- 2.54.0 From 60481b82236fff1198975578c27c02ce6a7fa3fa Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Sat, 20 Dec 2025 19:57:14 -0500 Subject: [PATCH 134/195] std.Io: trim terminating NUL from exe path on BSDs --- lib/std/Io/Threaded.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ee8f0e11320fbd6a693399aef2fa1e8bd02ddd94..35e093ef83f8140d52b701208580d7daef943098 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7138,7 +7138,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { .SUCCESS => { current_thread.endSyscall(); - return out_len; + return out_len - 1; // discard terminating NUL }, .INTR => { try current_thread.checkCancel(); @@ -7166,7 +7166,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { .SUCCESS => { current_thread.endSyscall(); - return out_len; + return out_len - 1; // discard terminating NUL }, .INTR => { try current_thread.checkCancel(); -- 2.54.0 From a29d79313a0c62843694ea723723447661239110 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 21 Dec 2025 19:22:42 -0800 Subject: [PATCH 135/195] std.Io.Threaded: accept argv and environ on init This is needed unfortunately for OpenBSD and Haiku for process executable path. I made it so that you can omit the options usually, but you get a compile error if you omit the options on those targets. --- lib/compiler/build_runner.zig | 2 +- lib/compiler/test_runner.zig | 4 +- lib/std/Io/Threaded.zig | 129 ++++++++++++++++++++++++---------- lib/std/Io/Threaded/test.zig | 12 ++-- src/main.zig | 2 +- 5 files changed, 103 insertions(+), 46 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index cfe0fee78fae7318cf0b266d426d4a3543224c10..e982a2f316176d21f9d5de88e1f7df418dd41efa 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -39,7 +39,7 @@ pub fn main() !void { const args = try process.argsAlloc(arena); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 84664feb138d556bafc4a9a90223f79d375f6d92..32b589cec8415f9bd2ad0985a6b8dcc4272c00b7 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -131,7 +131,7 @@ fn mainServer() !void { .run_test => { testing.allocator_instance = .{}; - testing.io_instance = .init(testing.allocator); + testing.io_instance = .init(testing.allocator, .{}); log_err_count = 0; const index = try server.receiveBody_u32(); const test_fn = builtin.test_functions[index]; @@ -233,7 +233,7 @@ fn mainTerminal() void { var leaks: usize = 0; for (test_fn_list, 0..) |test_fn, i| { testing.allocator_instance = .{}; - testing.io_instance = .init(testing.allocator); + testing.io_instance = .init(testing.allocator, .{}); defer { testing.io_instance.deinit(); if (testing.allocator_instance.deinit() == .leak) leaks += 1; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 35e093ef83f8140d52b701208580d7daef943098..054752fb03a268b964fd188d5df822310656975b 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -29,23 +29,7 @@ join_requested: bool = false, stack_size: usize, /// All threads are spawned detached; this is how we wait until they all exit. wait_group: std.Thread.WaitGroup = .{}, -/// Maximum thread pool size (excluding main thread) when dispatching async -/// tasks. Until this limit, calls to `Io.async` when all threads are busy will -/// cause a new thread to be spawned and permanently added to the pool. After -/// this limit, calls to `Io.async` when all threads are busy run the task -/// immediately. -/// -/// Defaults to a number equal to logical CPU cores. -/// -/// Protected by `mutex` once the I/O instance is already in use. See -/// `setAsyncLimit`. async_limit: Io.Limit, -/// Maximum thread pool size (excluding main thread) for dispatching concurrent -/// tasks. Until this limit, calls to `Io.concurrent` will increase the thread -/// pool size. -/// -/// concurrent tasks. After this number, calls to `Io.concurrent` return -/// `error.ConcurrencyUnavailable`. concurrent_limit: Io.Limit = .unlimited, /// Error from calling `std.Thread.getCpuCount` in `init`. cpu_count_error: ?std.Thread.CpuCountError, @@ -55,17 +39,7 @@ cpu_count_error: ?std.Thread.CpuCountError, busy_count: usize = 0, main_thread: Thread, pid: Pid = .unknown, -/// When a cancel request is made, blocking syscalls can be unblocked by -/// issuing a signal. However, if the signal arrives after the check and before -/// the syscall instruction, it is missed. -/// -/// This option solves the race condition by retrying the signal delivery -/// until it is acknowledged, with an exponential backoff. -/// -/// Unfortunately, trying again until the cancellation request is acknowledged -/// has been observed to be relatively slow, and usually strong cancellation -/// guarantees are not needed, so this defaults to off. -robust_cancel: RobustCancel = .disabled, +robust_cancel: RobustCancel, wsa: if (is_windows) Wsa else struct {} = .{}, @@ -86,6 +60,32 @@ stderr_writer: File.Writer = .{ stderr_mode: Io.Terminal.Mode = .no_color, stderr_writer_initialized: bool = false, +environ: Environ, +args: Args, + +pub const Environ = switch (native_os) { + .openbsd, .haiku => struct { + PATH: ?[]const u8, + + pub const empty: @This() = .{ + .PATH = null, + }; + }, + else => struct { + pub const empty: @This() = .{}; + }, +}; + +pub const Args = switch (native_os) { + .openbsd, .haiku => struct { + list: []const []const u8, + pub const empty: @This() = .{ .list = &.{} }; + }, + else => struct { + pub const empty: @This() = .{}; + }, +}; + pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { enabled, disabled, @@ -557,6 +557,54 @@ const Closure = struct { } }; +pub const InitOptions = struct { + /// Affects how many bytes are memory-mapped for threads. + stack_size: usize = std.Thread.SpawnConfig.default_stack_size, + /// Maximum thread pool size (excluding main thread) when dispatching async + /// tasks. Until this limit, calls to `Io.async` when all threads are busy will + /// cause a new thread to be spawned and permanently added to the pool. After + /// this limit, calls to `Io.async` when all threads are busy run the task + /// immediately. + /// + /// Defaults to a number equal to logical CPU cores. + /// + /// Protected by `Threaded.mutex` once the I/O instance is already in use. See + /// `setAsyncLimit`. + async_limit: ?Io.Limit = null, + /// Maximum thread pool size (excluding main thread) for dispatching concurrent + /// tasks. Until this limit, calls to `Io.concurrent` will increase the thread + /// pool size. + /// + /// concurrent tasks. After this number, calls to `Io.concurrent` return + /// `error.ConcurrencyUnavailable`. + concurrent_limit: Io.Limit = .unlimited, + /// When a cancel request is made, blocking syscalls can be unblocked by + /// issuing a signal. However, if the signal arrives after the check and before + /// the syscall instruction, it is missed. + /// + /// This option solves the race condition by retrying the signal delivery + /// until it is acknowledged, with an exponential backoff. + /// + /// Unfortunately, trying again until the cancellation request is acknowledged + /// has been observed to be relatively slow, and usually strong cancellation + /// guarantees are not needed, so this defaults to off. + robust_cancel: RobustCancel = .disabled, + /// Affects the following operations: + /// * `processExecutablePath` on OpenBSD and Haiku. + /// + /// The default value causes this to be a compile error on systems that need to + /// initialize this field. `Environ.empty` can be used to omit this field on + /// all targets. + environ: Environ = .{}, + /// Affects the following operations: + /// * `processExecutablePath` on OpenBSD and Haiku. + /// + /// The default value causes this to be a compile error on systems that need to + /// initialize this field. `Args.empty` can be used to omit this field on all + /// targets. + args: Args = .{}, +}; + /// Related: /// * `init_single_threaded` pub fn init( @@ -568,6 +616,7 @@ pub fn init( /// If these functions are avoided, then `Allocator.failing` may be passed /// here. gpa: Allocator, + options: InitOptions, ) Threaded { if (builtin.single_threaded) return .init_single_threaded; @@ -575,8 +624,9 @@ pub fn init( var t: Threaded = .{ .allocator = gpa, - .stack_size = std.Thread.SpawnConfig.default_stack_size, - .async_limit = if (cpu_count) |n| .limited(n - 1) else |_| .nothing, + .stack_size = options.stack_size, + .async_limit = options.async_limit orelse if (cpu_count) |n| .limited(n - 1) else |_| .nothing, + .concurrent_limit = options.concurrent_limit, .cpu_count_error = if (cpu_count) |_| null else |e| e, .old_sig_io = undefined, .old_sig_pipe = undefined, @@ -586,6 +636,9 @@ pub fn init( .current_closure = null, .cancel_protection = undefined, }, + .environ = options.environ, + .args = options.args, + .robust_cancel = options.robust_cancel, }; if (posix.Sigaction != void) { @@ -624,6 +677,9 @@ pub const init_single_threaded: Threaded = .{ .current_closure = null, .cancel_protection = undefined, }, + .robust_cancel = .disabled, + .environ = .empty, + .args = .empty, }; var global_single_threaded_instance: Threaded = .init_single_threaded; @@ -7186,18 +7242,17 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex } }, .openbsd, .haiku => { - // OpenBSD doesn't support getting the path of a running process, so try to guess it - if (std.os.argv.len == 0) - return error.FileNotFound; - - const argv0 = std.mem.span(std.os.argv[0]); + // The best we can do on these operating systems is check based on CLI args. + const argv = t.args.list; + if (argv.len == 0) return error.OperationUnsupported; + const argv0 = argv[0]; if (std.mem.findScalar(u8, argv0, '/') != null) { // argv[0] is a path (relative or absolute): use realpath(3) directly const current_thread = Thread.getCurrent(t); var resolved_buf: [std.c.PATH_MAX]u8 = undefined; try current_thread.beginSyscall(); while (true) { - if (std.c.realpath(std.os.argv[0], &resolved_buf)) |p| { + if (std.c.realpath(argv[0], &resolved_buf)) |p| { assert(p == &resolved_buf); break current_thread.endSyscall(); } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { @@ -7229,12 +7284,12 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex } else if (argv0.len != 0) { // argv[0] is not empty (and not a path): search PATH const current_thread = Thread.getCurrent(t); - const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; + const PATH = t.environ.PATH orelse return error.FileNotFound; var it = std.mem.tokenizeScalar(u8, PATH, ':'); it: while (it.next()) |dir| { var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined; const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ - dir, std.os.argv[0], + dir, argv[0], }, 0) catch continue; var resolved_buf: [std.c.PATH_MAX]u8 = undefined; diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index 9c54c3af1f98315722bab9202a032d6352e5118c..8169f6bb3749d28b602fde8fec57795379f849b2 100644 --- a/lib/std/Io/Threaded/test.zig +++ b/lib/std/Io/Threaded/test.zig @@ -1,3 +1,5 @@ +//! Tests belong here if they access internal state of std.Io.Threaded or +//! otherwise assume details of that particular implementation. const builtin = @import("builtin"); const std = @import("std"); @@ -11,7 +13,7 @@ test "concurrent vs main prevents deadlock via oversubscription" { return error.SkipZigTest; } - var threaded: Io.Threaded = .init(std.testing.allocator); + var threaded: Io.Threaded = .init(std.testing.allocator, .{}); defer threaded.deinit(); const io = threaded.io(); @@ -44,7 +46,7 @@ test "concurrent vs concurrent prevents deadlock via oversubscription" { return error.SkipZigTest; } - var threaded: Io.Threaded = .init(std.testing.allocator); + var threaded: Io.Threaded = .init(std.testing.allocator, .{}); defer threaded.deinit(); const io = threaded.io(); @@ -78,7 +80,7 @@ test "async/concurrent context and result alignment" { var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined; var fba: std.heap.FixedBufferAllocator = .init(&buffer); - var threaded: std.Io.Threaded = .init(fba.allocator()); + var threaded: std.Io.Threaded = .init(fba.allocator(), .{}); defer threaded.deinit(); const io = threaded.io(); @@ -111,7 +113,7 @@ test "Group.async context alignment" { var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined; var fba: std.heap.FixedBufferAllocator = .init(&buffer); - var threaded: std.Io.Threaded = .init(fba.allocator()); + var threaded: std.Io.Threaded = .init(fba.allocator(), .{}); defer threaded.deinit(); const io = threaded.io(); @@ -131,7 +133,7 @@ fn returnArray() [32]u8 { } test "async with array return type" { - var threaded: std.Io.Threaded = .init(std.testing.allocator); + var threaded: std.Io.Threaded = .init(std.testing.allocator, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/src/main.zig b/src/main.zig index 3a12882468f3138478d43b084a7161a2c65c6195..f63ead2cc359ecdee020ac678e51873437e937ba 100644 --- a/src/main.zig +++ b/src/main.zig @@ -241,7 +241,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { } } - var threaded: Io.Threaded = .init(gpa); + var threaded: Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); threaded_impl_ptr = &threaded; threaded.stack_size = thread_stack_size; -- 2.54.0 From 86e9e32cf0d5a028d6ebb32f8d0f3d0a23e717b6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 14:30:57 -0800 Subject: [PATCH 136/195] std.Io: fix missing try in waitTimeout --- lib/std/Io.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index ad26893651a66e802c77d6617cf15a76cdcf950c..d01077d803f0207b858d8b97e14897c8b2813e73 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1592,7 +1592,7 @@ pub const Event = enum(u32) { // waiters would wake up when a *new waiter* was added. So it's easiest to just leave // the state at `.waiting`---at worst it causes one redundant call to `futexWake`. } - io.futexWaitTimeout(Event, event, .waiting, timeout); + try io.futexWaitTimeout(Event, event, .waiting, timeout); switch (@atomicLoad(Event, event, .acquire)) { .unset => unreachable, // `reset` called before pending `wait` returned .waiting => return error.Timeout, -- 2.54.0 From 3c2f5adf41f0e75fd5e8f6661891dd7d4fa770a9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 14:37:41 -0800 Subject: [PATCH 137/195] std: integrate Io.Threaded with environment variables * std.option allows overriding the debug Io instance * if the default is used, start code initializes environ and argv0 also fix some places that needed recancel(), thanks mlugg! See #30562 --- lib/compiler/build_runner.zig | 5 +- lib/std/Io/File.zig | 2 +- lib/std/Io/Terminal.zig | 14 +++-- lib/std/Io/Threaded.zig | 99 ++++++++++++++++++++--------------- lib/std/Thread.zig | 2 +- lib/std/debug.zig | 20 +++---- lib/std/dynamic_library.zig | 2 +- lib/std/start.zig | 10 ++++ lib/std/std.zig | 19 ++++++- 9 files changed, 111 insertions(+), 62 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index e982a2f316176d21f9d5de88e1f7df418dd41efa..135cf2dced0a550e8117456a0837fee901f329f4 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -429,8 +429,11 @@ pub fn main() !void { } } + const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(); + const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(); + graph.stderr_mode = switch (color) { - .auto => try .detect(io, .stderr()), + .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE), .on => .escape_codes, .off => .no_color, }; diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index ae6c69285fdda140e3eeb7f74fa08ef3270eb5ac..804b0c91558ab26b8ce8d72ec2f4e42c90640a70 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -391,7 +391,7 @@ pub fn setOwner(file: File, io: Io, owner: ?Uid, group: ?Gid) SetOwnerError!void /// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes". /// /// Overridable via `std.options`. -pub const Permissions = std.options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) { +pub const Permissions = std.io_options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) { default_file = 0, _, diff --git a/lib/std/Io/Terminal.zig b/lib/std/Io/Terminal.zig index 549adcf20717746f48c61b1d1c9f52178f98c46d..beacc4d301c782144c58cccffe52df0ec058ab3d 100644 --- a/lib/std/Io/Terminal.zig +++ b/lib/std/Io/Terminal.zig @@ -48,7 +48,15 @@ pub const Mode = union(enum) { /// stdout/stderr). /// /// Will attempt to enable ANSI escape code support if necessary/possible. - pub fn detect(io: Io, file: File) Io.Cancelable!Mode { + /// + /// * `NO_COLOR` indicates whether "NO_COLOR" environment variable is + /// present and non-empty. + /// * `CLICOLOR_FORCE` indicates whether "CLICOLOR_FORCE" environment + /// variable is present and non-empty. + pub fn detect(io: Io, file: File, NO_COLOR: bool, CLICOLOR_FORCE: bool) Io.Cancelable!Mode { + const force_color: ?bool = if (NO_COLOR) false else if (CLICOLOR_FORCE) true else null; + if (force_color == false) return .no_color; + if (file.enableAnsiEscapeCodes(io)) |_| { return .escape_codes; } else |err| switch (err) { @@ -65,10 +73,8 @@ pub const Mode = union(enum) { .reset_attributes = info.wAttributes, } }; } - return .escape_codes; } - - return .no_color; + return if (force_color == true) .escape_codes else .no_color; } }; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 054752fb03a268b964fd188d5df822310656975b..4f1fe483105a15845fb13d5e843161f6f5ee1679 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -60,30 +60,34 @@ stderr_writer: File.Writer = .{ stderr_mode: Io.Terminal.Mode = .no_color, stderr_writer_initialized: bool = false, +argv0: Argv0, environ: Environ, -args: Args, -pub const Environ = switch (native_os) { +pub const Argv0 = switch (native_os) { .openbsd, .haiku => struct { - PATH: ?[]const u8, - - pub const empty: @This() = .{ - .PATH = null, - }; - }, - else => struct { - pub const empty: @This() = .{}; + value: ?[*:0]const u8 = null, }, + else => struct {}, }; -pub const Args = switch (native_os) { - .openbsd, .haiku => struct { - list: []const []const u8, - pub const empty: @This() = .{ .list = &.{} }; - }, - else => struct { - pub const empty: @This() = .{}; - }, +pub const Environ = struct { + /// Unmodified data directly from the OS. + block: Block = &.{}, + /// Protected by `mutex`. Determines whether the other fields have been + /// memoized based on `block`. + initialized: bool = false, + /// Protected by `mutex`. Memoized based on `block`. Tracks whether the + /// environment variables are present and non-empty. + exist: struct { + NO_COLOR: bool = false, + CLICOLOR_FORCE: bool = false, + } = .{}, + /// Protected by `mutex`. Memoized based on `block`. + string: struct { + PATH: ?[:0]const u8 = null, + } = .{}, + + pub const Block = []const [*:0]const u8; }; pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { @@ -591,18 +595,11 @@ pub const InitOptions = struct { robust_cancel: RobustCancel = .disabled, /// Affects the following operations: /// * `processExecutablePath` on OpenBSD and Haiku. - /// - /// The default value causes this to be a compile error on systems that need to - /// initialize this field. `Environ.empty` can be used to omit this field on - /// all targets. + argv0: Argv0 = .{}, + /// Affects the following operations: + /// * `fileIsTty` + /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH"). environ: Environ = .{}, - /// Affects the following operations: - /// * `processExecutablePath` on OpenBSD and Haiku. - /// - /// The default value causes this to be a compile error on systems that need to - /// initialize this field. `Args.empty` can be used to omit this field on all - /// targets. - args: Args = .{}, }; /// Related: @@ -636,8 +633,8 @@ pub fn init( .current_closure = null, .cancel_protection = undefined, }, + .argv0 = options.argv0, .environ = options.environ, - .args = options.args, .robust_cancel = options.robust_cancel, }; @@ -678,8 +675,8 @@ pub const init_single_threaded: Threaded = .{ .cancel_protection = undefined, }, .robust_cancel = .disabled, - .environ = .empty, - .args = .empty, + .argv0 = .{}, + .environ = .{}, }; var global_single_threaded_instance: Threaded = .init_single_threaded; @@ -7242,17 +7239,16 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex } }, .openbsd, .haiku => { - // The best we can do on these operating systems is check based on CLI args. - const argv = t.args.list; - if (argv.len == 0) return error.OperationUnsupported; - const argv0 = argv[0]; + // The best we can do on these operating systems is check based on + // the first process argument. + const argv0 = t.argv0.value orelse return error.OperationUnsupported; if (std.mem.findScalar(u8, argv0, '/') != null) { // argv[0] is a path (relative or absolute): use realpath(3) directly const current_thread = Thread.getCurrent(t); var resolved_buf: [std.c.PATH_MAX]u8 = undefined; try current_thread.beginSyscall(); while (true) { - if (std.c.realpath(argv[0], &resolved_buf)) |p| { + if (std.c.realpath(argv0, &resolved_buf)) |p| { assert(p == &resolved_buf); break current_thread.endSyscall(); } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { @@ -7283,13 +7279,14 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex return resolved.len; } else if (argv0.len != 0) { // argv[0] is not empty (and not a path): search PATH + t.scanEnviron(); + const PATH = t.environ.string.PATH orelse return error.FileNotFound; const current_thread = Thread.getCurrent(t); - const PATH = t.environ.PATH orelse return error.FileNotFound; var it = std.mem.tokenizeScalar(u8, PATH, ':'); it: while (it.next()) |dir| { var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined; const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ - dir, argv[0], + dir, argv0, }, 0) catch continue; var resolved_buf: [std.c.PATH_MAX]u8 = undefined; @@ -10752,7 +10749,10 @@ fn initLockedStderr( if (is_windows) t.stderr_writer.file = .stderr(); t.stderr_writer.io = io_t; t.stderr_writer_initialized = true; - t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file); + t.scanEnviron(); + const NO_COLOR = t.environ.exist.NO_COLOR; + const CLICOLOR_FORCE = t.environ.exist.CLICOLOR_FORCE; + t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file, NO_COLOR, CLICOLOR_FORCE); } std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch |err| switch (err) { error.WriteFailed => switch (t.stderr_writer.err.?) { @@ -10777,7 +10777,7 @@ fn unlockStderr(userdata: ?*anyopaque) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); t.stderr_writer.interface.flush() catch |err| switch (err) { error.WriteFailed => switch (t.stderr_writer.err.?) { - error.Canceled => @panic("TODO make this uncancelable"), + error.Canceled => recancel(t), else => {}, }, }; @@ -11910,6 +11910,23 @@ const pthreads_futex = struct { } }; +fn scanEnviron(t: *Threaded) void { + t.mutex.lock(); + defer t.mutex.unlock(); + + if (t.environ.initialized) return; + t.environ.initialized = true; + + if (native_os == .wasi) { + @panic("TODO"); + } + + for (t.environ.block) |kv| { + _ = kv; + @panic("TODO"); + } +} + test { _ = @import("Threaded/test.zig"); } diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index f25c664000fbcbb737619c331a08bcb013d7ac71..1900225099fed1d9b9df67b407e6c101e1cbaea1 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -322,7 +322,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co var buf: [32]u8 = undefined; const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); - const io = Io.Threaded.global_single_threaded.ioBasic(); + const io = std.options.debug_io; const file = try Io.Dir.cwd().openFile(io, path, .{}); defer file.close(io); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 5129a70b023db7ec334245f5ea243216c9e3b721..25a0bc120d72db28dfbebbe1f3d5e9914f1be158 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -261,10 +261,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { else => true, }; -/// This is used for debug information and debug printing. It is intentionally -/// separate from the application's `Io` instance. -const static_single_threaded_io = Io.Threaded.global_single_threaded.ioBasic(); - /// Allows the caller to freely write to stderr until `unlockStderr` is called. /// /// During the lock, any `std.Progress` information is cleared from the terminal. @@ -284,15 +280,15 @@ const static_single_threaded_io = Io.Threaded.global_single_threaded.ioBasic(); /// Alternatively, use the higher-level `Io.lockStderr` to integrate with the /// application's chosen `Io` implementation. pub fn lockStderr(buffer: []u8) Io.LockedStderr { - return static_single_threaded_io.lockStderr(buffer, null) catch |err| switch (err) { - // Impossible to cancel because no calls to cancel using - // `static_single_threaded_io` exist. - error.Canceled => unreachable, + const io = std.options.debug_io; + return io.lockStderr(buffer, null) catch |err| switch (err) { + error.Canceled => io.recancel(), }; } pub fn unlockStderr() void { - static_single_threaded_io.unlockStderr(); + const io = std.options.debug_io; + io.unlockStderr(); } /// Writes to stderr, ignoring errors. @@ -627,7 +623,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: defer it.deinit(); if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace; - const io = static_single_threaded_io; + const io = std.options.debug_io; var total_frames: usize = 0; var index: usize = 0; @@ -689,7 +685,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin var total_frames: usize = 0; var wait_for = options.first_address; var printed_any_frame = false; - const io = static_single_threaded_io; + const io = std.options.debug_io; while (true) switch (it.next(io)) { .switch_to_fp => |unwind_error| { switch (StackIterator.fp_usability) { @@ -797,7 +793,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void return; }, }; - const io = static_single_threaded_io; + const io = std.options.debug_io; const captured_frames = @min(n_frames, st.instruction_addresses.len); for (st.instruction_addresses[0..captured_frames]) |ret_addr| { // `ret_addr` is the return address, which is *after* the function call. diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 70b236f3e5902cb48bb6b2794ade1baacaa8bca0..b4acc8ae706efbd824a7f364b13335395247b157 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -222,7 +222,7 @@ pub const ElfDynLib = struct { /// Trusts the file. Malicious file will be able to execute arbitrary code. pub fn open(path: []const u8) Error!ElfDynLib { - const io = Io.Threaded.global_single_threaded.ioBasic(); + const io = std.options.debug_io; const fd = try resolveFromName(io, path); defer posix.close(fd); diff --git a/lib/std/start.zig b/lib/std/start.zig index 64a1c17175dc83fee7d03fb3d47d4cea17592a3b..5a3e39e24ab6970e6f6188b3b02e6aa0fb52e90b 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -669,6 +669,11 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 { std.os.argv = argv[0..argc]; std.os.environ = envp; + if (std.io_options.debug_threaded_io) |t| { + if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; + t.environ = .{ .block = envp }; + } + std.debug.maybeEnableSegfaultHandler(); return callMain(); @@ -691,6 +696,11 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { std.os.argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)]; + + if (@sizeOf(std.Io.Threaded.Argv0) != 0) { + if (std.io_options.debug_threaded_io) |t| t.argv0.value = std.os.argv[0]; + } + return callMain(); } diff --git a/lib/std/std.zig b/lib/std/std.zig index 1690c0575c6c5396758300cb2b23a522cdbd993e..46f6415d09ea95e695747f43fd61d3e92251e9ce 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -108,8 +108,11 @@ pub const start = @import("start.zig"); const root = @import("root"); -/// Stdlib-wide options that can be overridden by the root file. +/// Compile-time known settings overridable by the root source file. pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options else .{}; +/// Minimal set of `options` moved here to avoid dependency loop compilation +/// errors. +pub const io_options: IoOptions = if (@hasDecl(root, "std_io_options")) root.std_io_options else .{}; pub const Options = struct { enable_segfault_handler: bool = debug.default_enable_segfault_handler, @@ -174,8 +177,22 @@ pub const Options = struct { /// stack traces will just print an error to the relevant `Io.Writer` and return. allow_stack_tracing: bool = !@import("builtin").strip_debug_info, + /// The `Io` instance that `std.debug` uses for `std.debug.print`, + /// capturing stack traces, loading debug info, finding the executable's + /// own path, and environment variables that affect terminal mode + /// detection. The default is to use statically initialized singleton that + /// is independent from the application's `Io` instance in order to make + /// debugging more straightforward. For example, while debugging an `Io` + /// implementation based on coroutines, one likely wants `std.debug.print` + /// to directly write to stderr without trying to interact with the code + /// being debugged. + debug_io: Io = io_options.debug_threaded_io.?.ioBasic(), +}; + +pub const IoOptions = struct { /// Overrides `std.Io.File.Permissions`. FilePermissions: ?type = null, + debug_threaded_io: ?*Io.Threaded = Io.Threaded.global_single_threaded, }; // This forces the start.zig file to be imported, and the comptime logic inside that -- 2.54.0 From 78c4fcfcd89e80ef70cf4cbb13f5100c84432496 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 15:13:48 -0800 Subject: [PATCH 138/195] std.debug.lockStderr: cancel protection rather than recancel because we need to return the value --- lib/std/debug.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 25a0bc120d72db28dfbebbe1f3d5e9914f1be158..88af16666ff567eee3f14a0d507ab3bf0d74a3cd 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -281,8 +281,10 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { /// application's chosen `Io` implementation. pub fn lockStderr(buffer: []u8) Io.LockedStderr { const io = std.options.debug_io; + const prev = io.swapCancelProtection(.blocked); + defer _ = io.swapCancelProtection(prev); return io.lockStderr(buffer, null) catch |err| switch (err) { - error.Canceled => io.recancel(), + error.Canceled => unreachable, // Cancel protection enabled above. }; } -- 2.54.0 From 0992f1204e6e1ebf4437d98647f4336aa1c5c95e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 15:14:59 -0800 Subject: [PATCH 139/195] std.debug: delete nosuspend blocks now that the application can choose an Io implementation these might actually suspend. --- lib/std/debug.zig | 66 ++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 88af16666ff567eee3f14a0d507ab3bf0d74a3cd..44a0a489da332e380fe078a22e51b0a68542a8ec 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -305,12 +305,10 @@ pub fn unlockStderr() void { /// Alternatively, use the higher-level `std.log` or `Io.lockStderr` to /// integrate with the application's chosen `Io` implementation. pub fn print(comptime fmt: []const u8, args: anytype) void { - nosuspend { - var buffer: [64]u8 = undefined; - const stderr = lockStderr(&buffer); - defer unlockStderr(); - stderr.file_writer.interface.print(fmt, args) catch return; - } + var buffer: [64]u8 = undefined; + const stderr = lockStderr(&buffer); + defer unlockStderr(); + stderr.file_writer.interface.print(fmt, args) catch return; } /// Marked `inline` to propagate a comptime-known error to callers. @@ -1150,40 +1148,38 @@ fn printLineInfo( symbol_name: []const u8, compile_unit_name: []const u8, ) Writer.Error!void { - nosuspend { - const writer = t.writer; - t.setColor(.bold) catch {}; + const writer = t.writer; + t.setColor(.bold) catch {}; - if (source_location) |*sl| { - try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); - } else { - try writer.writeAll("???:?:?"); - } + if (source_location) |*sl| { + try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); + } else { + try writer.writeAll("???:?:?"); + } - t.setColor(.reset) catch {}; - try writer.writeAll(": "); - t.setColor(.dim) catch {}; - try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); - t.setColor(.reset) catch {}; - try writer.writeAll("\n"); + t.setColor(.reset) catch {}; + try writer.writeAll(": "); + t.setColor(.dim) catch {}; + try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); + t.setColor(.reset) catch {}; + try writer.writeAll("\n"); - // Show the matching source code line if possible - if (source_location) |sl| { - if (printLineFromFile(io, writer, sl)) { - if (sl.column > 0) { - // The caret already takes one char - const space_needed = @as(usize, @intCast(sl.column - 1)); + // Show the matching source code line if possible + if (source_location) |sl| { + if (printLineFromFile(io, writer, sl)) { + if (sl.column > 0) { + // The caret already takes one char + const space_needed = @as(usize, @intCast(sl.column - 1)); - try writer.splatByteAll(' ', space_needed); - t.setColor(.green) catch {}; - try writer.writeAll("^"); - t.setColor(.reset) catch {}; - } - try writer.writeAll("\n"); - } else |_| { - // Ignore all errors; it's a better UX to just print the source location without the - // corresponding line number. The user can always open the source file themselves. + try writer.splatByteAll(' ', space_needed); + t.setColor(.green) catch {}; + try writer.writeAll("^"); + t.setColor(.reset) catch {}; } + try writer.writeAll("\n"); + } else |_| { + // Ignore all errors; it's a better UX to just print the source location without the + // corresponding line number. The user can always open the source file themselves. } } } -- 2.54.0 From c98157a3c9b0a46bd7af6099e4fbef969bb88223 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 15:21:43 -0800 Subject: [PATCH 140/195] std.Io.Threaded: implement environment variable scanning --- lib/std/Io/Threaded.zig | 140 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 126 insertions(+), 14 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 4f1fe483105a15845fb13d5e843161f6f5ee1679..2f6772efd85f3197c4aad3b1ac02ad5819a48dfc 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -77,17 +77,31 @@ pub const Environ = struct { /// memoized based on `block`. initialized: bool = false, /// Protected by `mutex`. Memoized based on `block`. Tracks whether the - /// environment variables are present and non-empty. - exist: struct { - NO_COLOR: bool = false, - CLICOLOR_FORCE: bool = false, - } = .{}, + /// environment variables are present, ignoring their value. + exist: Exist = .{}, /// Protected by `mutex`. Memoized based on `block`. - string: struct { - PATH: ?[:0]const u8 = null, - } = .{}, + string: String = .{}, + /// Protected by `mutex`. Tracks the problem, if any, that occurred when + /// trying to scan environment variables. + /// + /// Errors are only possible on WASI. + err: ?Error = null, + + pub const Error = Allocator.Error || Io.UnexpectedError; pub const Block = []const [*:0]const u8; + + pub const Exist = struct { + NO_COLOR: bool = false, + CLICOLOR_FORCE: bool = false, + }; + + pub const String = switch (native_os) { + .openbsd, .haiku => struct { + PATH: ?[:0]const u8 = null, + }, + else => struct {}, + }; }; pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { @@ -11917,13 +11931,111 @@ fn scanEnviron(t: *Threaded) void { if (t.environ.initialized) return; t.environ.initialized = true; - if (native_os == .wasi) { - @panic("TODO"); - } + if (is_windows) { + const ptr = windows.peb().ProcessParameters.Environment; - for (t.environ.block) |kv| { - _ = kv; - @panic("TODO"); + var i: usize = 0; + while (ptr[i] != 0) { + const key_start = i; + + // There are some special environment variables that start with =, + // so we need a special case to not treat = as a key/value separator + // if it's the first character. + // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 + if (ptr[key_start] == '=') i += 1; + + while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} + const key_w = ptr[key_start..i]; + if (std.mem.eql(u16, key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) { + t.environ.exist.NO_COLOR = true; + } else if (std.mem.eql(u16, key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) { + t.environ.exist.CLICOLOR_FORCE = true; + } + comptime assert(@sizeOf(Environ.String) == 0); + + while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value + i += 1; // skip over null byte + } + } else if (native_os == .wasi and !builtin.link_libc) { + var environ_count: usize = undefined; + var environ_buf_size: usize = undefined; + + switch (std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size)) { + .SUCCESS => {}, + else => |err| { + t.environ.err = posix.unexpectedErrno(err); + return; + }, + } + if (environ_count == 0) return; + + const environ = t.allocator.alloc([*:0]u8, environ_count) catch |err| { + t.environ.err = err; + return; + }; + defer t.allocator.free(environ); + const environ_buf = t.allocator.alloc(u8, environ_buf_size) catch |err| { + t.environ.err = err; + return; + }; + defer t.allocator.free(environ_buf); + + switch (std.os.wasi.environ_get(environ.ptr, environ_buf.ptr)) { + .SUCCESS => {}, + else => |err| { + t.environ.err = posix.unexpectedErrno(err); + return; + }, + } + + for (environ) |env| { + const pair = std.mem.sliceTo(env, 0); + var parts = std.mem.splitScalar(u8, pair, '='); + const key = parts.first(); + if (std.mem.eql(u8, key, "NO_COLOR")) { + t.environ.exist.NO_COLOR = true; + } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { + t.environ.exist.CLICOLOR_FORCE = true; + } + comptime assert(@sizeOf(Environ.String) == 0); + } + } else if (builtin.link_libc) { + var ptr = std.c.environ; + while (ptr[0]) |line| : (ptr += 1) { + var line_i: usize = 0; + while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} + const key = line[0..line_i]; + + var end_i: usize = line_i; + while (line[end_i] != 0) : (end_i += 1) {} + const value = line[line_i + 1 .. end_i]; + + if (std.mem.eql(u8, key, "NO_COLOR")) { + t.environ.exist.NO_COLOR = true; + } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { + t.environ.exist.CLICOLOR_FORCE = true; + } else if (@hasField(Environ.String, "PATH") and std.mem.eql(u8, key, "PATH")) { + t.environ.string.PATH = value; + } + } + } else { + for (t.environ.block) |line| { + var line_i: usize = 0; + while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} + const key = line[0..line_i]; + + var end_i: usize = line_i; + while (line[end_i] != 0) : (end_i += 1) {} + const value = line[line_i + 1 .. end_i]; + + if (std.mem.eql(u8, key, "NO_COLOR")) { + t.environ.exist.NO_COLOR = true; + } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { + t.environ.exist.CLICOLOR_FORCE = true; + } else if (@hasField(Environ.String, "PATH") and std.mem.eql(u8, key, "PATH")) { + t.environ.string.PATH = value; + } + } } } -- 2.54.0 From 9158d3b2819b9fd3d93922f2522162a0ece53f5a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 15:51:04 -0800 Subject: [PATCH 141/195] std: remove ability to override Io.File.Permissions This is causing another compiler dependency loop. I'll tackle these overrides separately. --- lib/std/Io/File.zig | 4 +--- lib/std/std.zig | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 804b0c91558ab26b8ce8d72ec2f4e42c90640a70..30cda7a74cbad11acb3f22952a08ba5e18c249af 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -389,9 +389,7 @@ pub fn setOwner(file: File, io: Io, owner: ?Uid, group: ?Gid) SetOwnerError!void /// Cross-platform representation of permissions on a file. /// /// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes". -/// -/// Overridable via `std.options`. -pub const Permissions = std.io_options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) { +pub const Permissions = if (is_windows) enum(std.os.windows.DWORD) { default_file = 0, _, diff --git a/lib/std/std.zig b/lib/std/std.zig index 46f6415d09ea95e695747f43fd61d3e92251e9ce..dccaffb75fb71e68d70b06f16afc577e51ba3650 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -190,8 +190,6 @@ pub const Options = struct { }; pub const IoOptions = struct { - /// Overrides `std.Io.File.Permissions`. - FilePermissions: ?type = null, debug_threaded_io: ?*Io.Threaded = Io.Threaded.global_single_threaded, }; -- 2.54.0 From 9aee45be97020e65a3eaca67f7a343823a3439f5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 15:52:05 -0800 Subject: [PATCH 142/195] fix "zig std" compilation errors --- lib/compiler/std-docs.zig | 72 +++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index 9ff199d07bc98726923dbfd90932755989f0654a..e2b58b86683f2018cf7f6d3638a231690f515da6 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -7,8 +7,8 @@ const Allocator = std.mem.Allocator; const assert = std.debug.assert; const Cache = std.Build.Cache; -fn usage() noreturn { - std.Io.File.stdout().writeAll( +fn usage(io: Io) noreturn { + Io.File.stdout().writeStreamingAll(io, \\Usage: zig std [options] \\ \\Options: @@ -29,7 +29,7 @@ pub fn main() !void { var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; const gpa = general_purpose_allocator.allocator(); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); @@ -47,11 +47,11 @@ pub fn main() !void { var force_open_browser: ?bool = null; while (argv.next()) |arg| { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - usage(); + usage(io); } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--port")) { - listen_port = std.fmt.parseInt(u16, argv.next() orelse usage(), 10) catch |err| { + listen_port = std.fmt.parseInt(u16, argv.next() orelse usage(io), 10) catch |err| { std.log.err("expected port number: {}", .{err}); - usage(); + usage(io); }; } else if (mem.eql(u8, arg, "--open-browser")) { force_open_browser = true; @@ -59,18 +59,18 @@ pub fn main() !void { force_open_browser = false; } else { std.log.err("unrecognized argument: {s}", .{arg}); - usage(); + usage(io); } } const should_open_browser = force_open_browser orelse (listen_port == 0); - const address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable; - var http_server = try address.listen(.{ + const address = Io.net.IpAddress.parse("127.0.0.1", listen_port) catch unreachable; + var http_server = try address.listen(io, .{ .reuse_address = true, }); - const port = http_server.listen_address.in.getPort(); + const port = http_server.socket.address.getPort(); const url_with_newline = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port}); - std.Io.File.stdout().writeAll(url_with_newline) catch {}; + Io.File.stdout().writeStreamingAll(io, url_with_newline) catch {}; if (should_open_browser) { openBrowserTab(gpa, io, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| { std.log.err("unable to open browser: {t}", .{err}); @@ -86,44 +86,43 @@ pub fn main() !void { .zig_lib_directory = zig_lib_directory, }; + var group: Io.Group = .init; + defer group.cancel(io); + while (true) { - const connection = try http_server.accept(); - _ = std.Thread.spawn(.{}, accept, .{ &context, connection }) catch |err| { - std.log.err("unable to accept connection: {t}", .{err}); - connection.stream.close(io); - continue; - }; + const stream = try http_server.accept(io); + group.async(io, accept, .{ &context, stream }); } } -fn accept(context: *Context, connection: std.net.Server.Connection) void { +fn accept(context: *Context, stream: Io.net.Stream) void { const io = context.io; - defer connection.stream.close(io); + defer stream.close(io); var recv_buffer: [4000]u8 = undefined; var send_buffer: [4000]u8 = undefined; - var conn_reader = connection.stream.reader(&recv_buffer); - var conn_writer = connection.stream.writer(&send_buffer); - var server = std.http.Server.init(conn_reader.interface(), &conn_writer.interface); + var conn_reader = stream.reader(io, &recv_buffer); + var conn_writer = stream.writer(io, &send_buffer); + var server = std.http.Server.init(&conn_reader.interface, &conn_writer.interface); while (server.reader.state == .ready) { var request = server.receiveHead() catch |err| switch (err) { error.HttpConnectionClosing => return, else => { - std.log.err("closing http connection: {s}", .{@errorName(err)}); + std.log.err("closing http connection: {t}", .{err}); return; }, }; serveRequest(&request, context) catch |err| switch (err) { error.WriteFailed => { if (conn_writer.err) |e| { - std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(e) }); + std.log.err("unable to serve {s}: {t}", .{ request.head.target, e }); } else { - std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) }); + std.log.err("unable to serve {s}: {t}", .{ request.head.target, err }); } return; }, else => { - std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) }); + std.log.err("unable to serve {s}: {t}", .{ request.head.target, err }); return; }, }; @@ -216,7 +215,7 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer }; archiver.prefix = "std"; - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { switch (entry.kind) { .file => { if (!std.mem.endsWith(u8, entry.basename, ".zig")) @@ -229,12 +228,13 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { var file = try entry.dir.openFile(io, entry.basename, .{}); defer file.close(io); const stat = try file.stat(io); - var file_reader: std.Io.File.Reader = .{ + var file_reader: Io.File.Reader = .{ + .io = io, .file = file, - .interface = std.Io.File.Reader.initInterface(&.{}), + .interface = Io.File.Reader.initInterface(&.{}), .size = stat.size, }; - try archiver.writeFile(io, entry.path, &file_reader, stat.mtime); + try archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime); } { @@ -267,7 +267,7 @@ fn serveWasm( const wasm_base_path = try buildWasmBinary(arena, context, optimize_mode); const bin_name = try std.zig.binNameAlloc(arena, .{ .root_name = autodoc_root_name, - .target = &(std.zig.system.resolveTargetQuery(std.Build.parseTargetQuery(.{ + .target = &(std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{ .arch_os_abi = autodoc_arch_os_abi, .cpu_features = autodoc_cpu_features, }) catch unreachable) catch unreachable), @@ -330,7 +330,7 @@ fn buildWasmBinary( child.stderr_behavior = .Pipe; try child.spawn(io); - var poller = std.Io.poll(gpa, enum { stdout, stderr }, .{ + var poller = Io.poll(gpa, enum { stdout, stderr }, .{ .stdout = child.stdout.?, .stderr = child.stderr.?, }); @@ -361,7 +361,7 @@ fn buildWasmBinary( result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body); }, .emit_digest => { - var r: std.Io.Reader = .fixed(body); + var r: Io.Reader = .fixed(body); const emit_digest = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable; if (!emit_digest.flags.cache_hit) { std.log.info("source changes detected; rebuilt wasm component", .{}); @@ -387,7 +387,7 @@ fn buildWasmBinary( child.stdin.?.close(io); child.stdin = null; - switch (try child.wait()) { + switch (try child.wait(io)) { .Exited => |code| { if (code != 0) { std.log.err( @@ -407,7 +407,7 @@ fn buildWasmBinary( } if (result_error_bundle.errorMessageCount() > 0) { - result_error_bundle.renderToStderr(io, .{}, true); + try result_error_bundle.renderToStderr(io, .{}, .auto); std.log.err("the following command failed with {d} compilation errors:\n{s}", .{ result_error_bundle.errorMessageCount(), try std.Build.Step.allocPrintCmd(arena, null, argv.items), @@ -423,7 +423,7 @@ fn buildWasmBinary( }; } -fn sendMessage(io: Io, file: std.Io.File, tag: std.zig.Client.Message.Tag) !void { +fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { const header: std.zig.Client.Message.Header = .{ .tag = tag, .bytes_len = 0, -- 2.54.0 From 7c1236e267e536379f8b91148117fb0b8e965334 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 16:10:43 -0800 Subject: [PATCH 143/195] std: different way of doing some options to avoid dependency loops --- lib/std/Io/File.zig | 2 +- lib/std/Thread.zig | 2 +- lib/std/debug.zig | 10 +++++----- lib/std/dynamic_library.zig | 2 +- lib/std/start.zig | 4 ++-- lib/std/std.zig | 14 +++++++------- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 30cda7a74cbad11acb3f22952a08ba5e18c249af..a972ce2fd3e5aadc8dcb7481e0abffe2da3eab81 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -389,7 +389,7 @@ pub fn setOwner(file: File, io: Io, owner: ?Uid, group: ?Gid) SetOwnerError!void /// Cross-platform representation of permissions on a file. /// /// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes". -pub const Permissions = if (is_windows) enum(std.os.windows.DWORD) { +pub const Permissions = std.Options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) { default_file = 0, _, diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 1900225099fed1d9b9df67b407e6c101e1cbaea1..312cc25a9929c91be9fe7419ce44a05875a07ed4 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -322,7 +322,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co var buf: [32]u8 = undefined; const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); - const io = std.options.debug_io; + const io = std.Options.debug_io; const file = try Io.Dir.cwd().openFile(io, path, .{}); defer file.close(io); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 44a0a489da332e380fe078a22e51b0a68542a8ec..d000bda62e99011ec9a5258411bf35aafbd6e0ee 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -280,7 +280,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { /// Alternatively, use the higher-level `Io.lockStderr` to integrate with the /// application's chosen `Io` implementation. pub fn lockStderr(buffer: []u8) Io.LockedStderr { - const io = std.options.debug_io; + const io = std.Options.debug_io; const prev = io.swapCancelProtection(.blocked); defer _ = io.swapCancelProtection(prev); return io.lockStderr(buffer, null) catch |err| switch (err) { @@ -289,7 +289,7 @@ pub fn lockStderr(buffer: []u8) Io.LockedStderr { } pub fn unlockStderr() void { - const io = std.options.debug_io; + const io = std.Options.debug_io; io.unlockStderr(); } @@ -623,7 +623,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: defer it.deinit(); if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace; - const io = std.options.debug_io; + const io = std.Options.debug_io; var total_frames: usize = 0; var index: usize = 0; @@ -685,7 +685,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin var total_frames: usize = 0; var wait_for = options.first_address; var printed_any_frame = false; - const io = std.options.debug_io; + const io = std.Options.debug_io; while (true) switch (it.next(io)) { .switch_to_fp => |unwind_error| { switch (StackIterator.fp_usability) { @@ -793,7 +793,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void return; }, }; - const io = std.options.debug_io; + const io = std.Options.debug_io; const captured_frames = @min(n_frames, st.instruction_addresses.len); for (st.instruction_addresses[0..captured_frames]) |ret_addr| { // `ret_addr` is the return address, which is *after* the function call. diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index b4acc8ae706efbd824a7f364b13335395247b157..ccd60ecd97f12dea8912ecb69dd51e2c1a396975 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -222,7 +222,7 @@ pub const ElfDynLib = struct { /// Trusts the file. Malicious file will be able to execute arbitrary code. pub fn open(path: []const u8) Error!ElfDynLib { - const io = std.options.debug_io; + const io = std.Options.debug_io; const fd = try resolveFromName(io, path); defer posix.close(fd); diff --git a/lib/std/start.zig b/lib/std/start.zig index 5a3e39e24ab6970e6f6188b3b02e6aa0fb52e90b..c6a9f06724e965482cc67a8021aebff67f59d265 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -669,7 +669,7 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 { std.os.argv = argv[0..argc]; std.os.environ = envp; - if (std.io_options.debug_threaded_io) |t| { + if (std.Options.debug_threaded_io) |t| { if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; t.environ = .{ .block = envp }; } @@ -698,7 +698,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { std.os.argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)]; if (@sizeOf(std.Io.Threaded.Argv0) != 0) { - if (std.io_options.debug_threaded_io) |t| t.argv0.value = std.os.argv[0]; + if (std.Options.debug_threaded_io) |t| t.argv0.value = std.os.argv[0]; } return callMain(); diff --git a/lib/std/std.zig b/lib/std/std.zig index dccaffb75fb71e68d70b06f16afc577e51ba3650..93d83b4807b32bb3d7550fc1977cd430932c097a 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -110,9 +110,6 @@ const root = @import("root"); /// Compile-time known settings overridable by the root source file. pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options else .{}; -/// Minimal set of `options` moved here to avoid dependency loop compilation -/// errors. -pub const io_options: IoOptions = if (@hasDecl(root, "std_io_options")) root.std_io_options else .{}; pub const Options = struct { enable_segfault_handler: bool = debug.default_enable_segfault_handler, @@ -177,6 +174,10 @@ pub const Options = struct { /// stack traces will just print an error to the relevant `Io.Writer` and return. allow_stack_tracing: bool = !@import("builtin").strip_debug_info, + pub const debug_threaded_io: ?*Io.Threaded = if (@hasDecl(root, "std_options_debug_threaded_io")) + root.std_options_debug_threaded_io + else + Io.Threaded.global_single_threaded; /// The `Io` instance that `std.debug` uses for `std.debug.print`, /// capturing stack traces, loading debug info, finding the executable's /// own path, and environment variables that affect terminal mode @@ -186,11 +187,10 @@ pub const Options = struct { /// implementation based on coroutines, one likely wants `std.debug.print` /// to directly write to stderr without trying to interact with the code /// being debugged. - debug_io: Io = io_options.debug_threaded_io.?.ioBasic(), -}; + pub const debug_io: Io = if (@hasDecl(root, "std_options_debug_io")) root.std_options_debug_io else debug_threaded_io.?.ioBasic(); -pub const IoOptions = struct { - debug_threaded_io: ?*Io.Threaded = Io.Threaded.global_single_threaded, + /// Overrides `std.Io.File.Permissions`. + pub const FilePermissions: ?type = if (@hasDecl(root, "std_options_FilePermissions")) root.std_options_FilePermissions else null; }; // This forces the start.zig file to be imported, and the comptime logic inside that -- 2.54.0 From 187d0a692de2f00e64a446548873f7a12d9ddb4f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 17:29:28 -0800 Subject: [PATCH 144/195] compiler: handle cancelation from finishPrelinkQueue --- src/Compilation.zig | 4 +++- src/InternPool.zig | 1 - src/link/Queue.zig | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 8b840b8d452913c6ed7389621841dffe004703e0..8e005992ec45f4e0ff32f53e36e3af0c5d1ed090 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -5056,7 +5056,9 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node } prelink_group.wait(io); - comp.link_queue.finishPrelinkQueue(comp); + comp.link_queue.finishPrelinkQueue(comp) catch |err| switch (err) { + error.Canceled => return, + }; } const JobError = Allocator.Error || Io.Cancelable; diff --git a/src/InternPool.zig b/src/InternPool.zig index 3076aa2cf36490aba4f400e581485edbc1e20b98..69a64cbc4597aaa7ab542bde929a6c630703a7e6 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -11,7 +11,6 @@ const assert = std.debug.assert; const BigIntConst = std.math.big.int.Const; const BigIntMutable = std.math.big.int.Mutable; const Cache = std.Build.Cache; -const Io = std.Io; const Limb = std.math.big.Limb; const Hash = std.hash.Wyhash; const Zir = std.zig.Zir; diff --git a/src/link/Queue.zig b/src/link/Queue.zig index caa2cc189816f0a0e4565eec8de66fdffcb059e3..b716800baebe7eaccf8e3eba0b8779dbb744faad 100644 --- a/src/link/Queue.zig +++ b/src/link/Queue.zig @@ -121,7 +121,7 @@ pub fn enqueueZcu( link.doZcuTask(comp, tid, task); } -pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) void { +pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) Io.Cancelable!void { if (q.future != null) { q.prelink_queue.close(comp.io); return; @@ -136,6 +136,7 @@ pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) void { } else |err| switch (err) { error.OutOfMemory => comp.link_diags.setAllocFailure(), error.LinkFailure => {}, + error.Canceled => |e| return e, } } } -- 2.54.0 From 33e302d67a80fe7d213b4c461dc41c91813db869 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 17:35:46 -0800 Subject: [PATCH 145/195] update remaining calls to std.Io.Threaded.init --- lib/compiler/aro/main.zig | 2 +- lib/compiler/libc.zig | 2 +- lib/compiler/objcopy.zig | 2 +- lib/compiler/reduce.zig | 2 +- lib/compiler/resinator/main.zig | 2 +- lib/compiler/translate-c/main.zig | 2 +- lib/init/src/main.zig | 2 +- lib/std/crypto/benchmark.zig | 2 +- src/Package/Fetch/git.zig | 2 +- src/fmt.zig | 2 +- test/src/convert-stack-trace.zig | 2 +- test/standalone/child_process/child.zig | 2 +- test/standalone/child_process/main.zig | 2 +- test/standalone/coff_dwarf/main.zig | 2 +- test/standalone/libfuzzer/main.zig | 2 +- test/standalone/self_exe_symlink/main.zig | 2 +- test/standalone/simple/cat/main.zig | 2 +- test/standalone/simple/hello_world/hello.zig | 2 +- test/standalone/windows_bat_args/fuzz.zig | 2 +- test/standalone/windows_bat_args/test.zig | 2 +- test/standalone/windows_spawn/main.zig | 2 +- tools/docgen.zig | 2 +- tools/doctest.zig | 2 +- tools/dump-cov.zig | 2 +- tools/fetch_them_macos_headers.zig | 2 +- tools/gen_macos_headers_c.zig | 2 +- tools/generate_c_size_and_align_checks.zig | 2 +- tools/generate_linux_syscalls.zig | 2 +- tools/incr-check.zig | 2 +- tools/migrate_langref.zig | 2 +- tools/update-linux-headers.zig | 2 +- tools/update_cpu_features.zig | 2 +- tools/update_crc_catalog.zig | 2 +- tools/update_freebsd_libc.zig | 2 +- tools/update_glibc.zig | 2 +- tools/update_mingw.zig | 2 +- tools/update_netbsd_libc.zig | 2 +- 37 files changed, 37 insertions(+), 37 deletions(-) diff --git a/lib/compiler/aro/main.zig b/lib/compiler/aro/main.zig index 9e0440febfb6bf3c196dec11380c7f726e2a5812..ca079c0e5f7e12dcba64de54a07cb807cb72b4ae 100644 --- a/lib/compiler/aro/main.zig +++ b/lib/compiler/aro/main.zig @@ -31,7 +31,7 @@ pub fn main() u8 { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/lib/compiler/libc.zig b/lib/compiler/libc.zig index 1aa9a587ae32d35405a85dbd627ce6b4ebb18205..eb4614c95db3552f2e36342c4ce1ea7161347f31 100644 --- a/lib/compiler/libc.zig +++ b/lib/compiler/libc.zig @@ -30,7 +30,7 @@ pub fn main() !void { const arena = arena_instance.allocator(); const gpa = arena; - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 2e272b074b83b8fc5c2cacf399303b82d72b325d..600166288c051d30e7f4dd782559514aeb48cfe7 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -148,7 +148,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void const input = opt_input orelse fatal("expected input parameter", .{}); const output = opt_output orelse fatal("expected output parameter", .{}); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/lib/compiler/reduce.zig b/lib/compiler/reduce.zig index a93db3a6d7a20ac1e5d491c8dfd494c151ce8567..922031d00873a8a18cbe2d766c1b5f3b35baa98e 100644 --- a/lib/compiler/reduce.zig +++ b/lib/compiler/reduce.zig @@ -55,7 +55,7 @@ pub fn main() !void { var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; const gpa = general_purpose_allocator.allocator(); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 81a3a8689a45ffcb2029c9a284ccc9c23a35111d..7e2e8403caf33abddc24f7ed0613cae9ed8a14ec 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -24,7 +24,7 @@ pub fn main() !void { defer std.debug.assert(debug_allocator.deinit() == .ok); const gpa = debug_allocator.allocator(); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index 76d67afd1819112a9bfc7af6685bf57d14bbade1..a6737b68fa7b8dfe77494d25355d4f98ff92ac14 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -19,7 +19,7 @@ pub fn main() u8 { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/lib/init/src/main.zig b/lib/init/src/main.zig index 9df441b56468877f4f8a1a1869827fb6b4c44677..865e6c53228c9c69f441eec4427656d671cb9ce3 100644 --- a/lib/init/src/main.zig +++ b/lib/init/src/main.zig @@ -13,7 +13,7 @@ pub fn main() !void { const gpa = debug_allocator.allocator(); // In order to do I/O operations we must construct an `Io` instance. - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 1b71110be52623ba22d58e438a695736cc03e3a8..23154324bc280d8b54d45c032de56c60670ac5ba 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -556,7 +556,7 @@ pub fn main() !void { } } - var io_threaded = std.Io.Threaded.init(arena_allocator); + var io_threaded = std.Io.Threaded.init(arena_allocator, .{}); defer io_threaded.deinit(); const io = io_threaded.io(); diff --git a/src/Package/Fetch/git.zig b/src/Package/Fetch/git.zig index 0cb7e6230080e081c2152dc1a9ec0966c15275e6..18e15f457f0135b77117922119660b4a81a4c3ac 100644 --- a/src/Package/Fetch/git.zig +++ b/src/Package/Fetch/git.zig @@ -1702,7 +1702,7 @@ test "SHA-256 packfile indexing and checkout" { pub fn main() !void { const allocator = std.heap.smp_allocator; - var threaded: Io.Threaded = .init(allocator); + var threaded: Io.Threaded = .init(allocator, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/src/fmt.zig b/src/fmt.zig index ca6885d24e01d6a3215f8b7a8739660f7bdb287a..b1903aad5370ad17f03db0397ea3572621827593 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -370,7 +370,7 @@ pub fn main() !void { var arena_instance = std.heap.ArenaAllocator.init(gpa); const arena = arena_instance.allocator(); const args = try process.argsAlloc(arena); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); return run(gpa, arena, io, args[1..]); diff --git a/test/src/convert-stack-trace.zig b/test/src/convert-stack-trace.zig index 272edad18ed8d063144c34b36f0757011d47df6c..d23623c396826b9488a75eb67dcb2a3fb5137567 100644 --- a/test/src/convert-stack-trace.zig +++ b/test/src/convert-stack-trace.zig @@ -34,7 +34,7 @@ pub fn main() !void { const gpa = arena; - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/test/standalone/child_process/child.zig b/test/standalone/child_process/child.zig index 5dbf95af4c0bbf74018bb3af50075b7a077d85bb..1bd97bace6716d845adfeb9612e9e0c0c941fc15 100644 --- a/test/standalone/child_process/child.zig +++ b/test/standalone/child_process/child.zig @@ -8,7 +8,7 @@ pub fn main() !void { var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); const arena = arena_state.allocator(); - var threaded: std.Io.Threaded = .init(arena); + var threaded: std.Io.Threaded = .init(arena, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index 3aaec0f2b232cd7c7044e368b526dc42a83b4bb0..372ef01ce829c1f3c17e82d71741a1bd3ae812b1 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -21,7 +21,7 @@ pub fn main() !void { }; defer if (needs_free) gpa.free(child_path); - var threaded: Io.Threaded = .init(gpa); + var threaded: Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/test/standalone/coff_dwarf/main.zig b/test/standalone/coff_dwarf/main.zig index e7590f3f0746b53ce90725084158909826b8eb40..7e314d5b287cb781791474a12e599ff5d6b0b175 100644 --- a/test/standalone/coff_dwarf/main.zig +++ b/test/standalone/coff_dwarf/main.zig @@ -11,7 +11,7 @@ pub fn main() void { var di: std.debug.SelfInfo = .init; defer di.deinit(gpa); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/test/standalone/libfuzzer/main.zig b/test/standalone/libfuzzer/main.zig index 077af4ad3eb171f3ea0f2666d968e141071ddd71..0bc093d8705ba63221cd87114931a0fc3e8f0bbc 100644 --- a/test/standalone/libfuzzer/main.zig +++ b/test/standalone/libfuzzer/main.zig @@ -15,7 +15,7 @@ pub fn main() !void { defer args.deinit(); _ = args.skip(); // executable name - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/test/standalone/self_exe_symlink/main.zig b/test/standalone/self_exe_symlink/main.zig index dfe3acb7a0b8f99ee0ce0f2f79aa66ad6fee196f..0f9455e3a43fd452dc55db80b3e8d70022710a1f 100644 --- a/test/standalone/self_exe_symlink/main.zig +++ b/test/standalone/self_exe_symlink/main.zig @@ -5,7 +5,7 @@ pub fn main() !void { defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks"); const gpa = debug_allocator.allocator(); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/test/standalone/simple/cat/main.zig b/test/standalone/simple/cat/main.zig index 699fc8075da282815d3204416a6194ebd23587e7..0135ac4b50943e5fc4c2fe136ea611fc20bc70cf 100644 --- a/test/standalone/simple/cat/main.zig +++ b/test/standalone/simple/cat/main.zig @@ -9,7 +9,7 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var threaded: std.Io.Threaded = .init(arena); + var threaded: std.Io.Threaded = .init(arena, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/test/standalone/simple/hello_world/hello.zig b/test/standalone/simple/hello_world/hello.zig index 6bce8413441ed0421687c90deb058bf7b3fa16d2..7bb4f00e4dd87b876a3c68ab2dd18038b2719bfe 100644 --- a/test/standalone/simple/hello_world/hello.zig +++ b/test/standalone/simple/hello_world/hello.zig @@ -5,7 +5,7 @@ pub fn main() !void { defer _ = debug_allocator.deinit(); const gpa = debug_allocator.allocator(); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig index 6c89c50e0bb87b4821e386fd6d0855be1ecf22ae..db6043d3e9a41cd7d81496b3feb41e36c2e916fa 100644 --- a/test/standalone/windows_bat_args/fuzz.zig +++ b/test/standalone/windows_bat_args/fuzz.zig @@ -9,7 +9,7 @@ pub fn main() anyerror!void { defer std.debug.assert(debug_alloc_inst.deinit() == .ok); const gpa = debug_alloc_inst.allocator(); - var threaded: Io.Threaded = .init(gpa); + var threaded: Io.Threaded = .init(gpa, .{}); const io = threaded.io(); var it = try std.process.argsWithAllocator(gpa); diff --git a/test/standalone/windows_bat_args/test.zig b/test/standalone/windows_bat_args/test.zig index 4630fa33373c309102b1a5d5b13261626bceba8a..48c32826e5cb381c21904fb52cadf923e3b991c8 100644 --- a/test/standalone/windows_bat_args/test.zig +++ b/test/standalone/windows_bat_args/test.zig @@ -7,7 +7,7 @@ pub fn main() anyerror!void { defer std.debug.assert(debug_alloc_inst.deinit() == .ok); const gpa = debug_alloc_inst.allocator(); - var threaded: Io.Threaded = .init(gpa); + var threaded: Io.Threaded = .init(gpa, .{}); const io = threaded.io(); var it = try std.process.argsWithAllocator(gpa); diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index 569eba04070ada74ad1060e8e904201b869f4d73..db771c88eb95617ad5ddabdc6927851d99a9bf5b 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -10,7 +10,7 @@ pub fn main() anyerror!void { defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks"); const gpa = debug_allocator.allocator(); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/docgen.zig b/tools/docgen.zig index 11cd40acde4b77bd14a9118a4177c2321066b22c..a46e34ee7e7a0a05285f8e7ad3c1a13b4cda7118 100644 --- a/tools/docgen.zig +++ b/tools/docgen.zig @@ -38,7 +38,7 @@ pub fn main() !void { const gpa = arena; - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/doctest.zig b/tools/doctest.zig index b7219c53f0110a2329e9a5a32ed0eb928e5960c9..8dffd447a4b7351e2d2dba37bcc9a7076ec3d77f 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -40,7 +40,7 @@ pub fn main() !void { const gpa = arena; - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/dump-cov.zig b/tools/dump-cov.zig index 3dd91de6122a786ad8e15bd9f621d8065ae117fd..d52b61f0583b753c0940c124b625d5c2c3238b65 100644 --- a/tools/dump-cov.zig +++ b/tools/dump-cov.zig @@ -16,7 +16,7 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/fetch_them_macos_headers.zig b/tools/fetch_them_macos_headers.zig index 6575aa2b566ee713b4d675e1bd6437ce5d31102a..815042e3af3b2092fa64ade895af6f03319f2eb8 100644 --- a/tools/fetch_them_macos_headers.zig +++ b/tools/fetch_them_macos_headers.zig @@ -86,7 +86,7 @@ pub fn main() anyerror!void { } else try argv.append(arg); } - var threaded: Io.Threaded = .init(gpa); + var threaded: Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/gen_macos_headers_c.zig b/tools/gen_macos_headers_c.zig index 62ae815da31c3ae8720011f4788076b3d3084ebb..657bda8ab2342850aee37fe348274499b462df9f 100644 --- a/tools/gen_macos_headers_c.zig +++ b/tools/gen_macos_headers_c.zig @@ -21,7 +21,7 @@ pub fn main() anyerror!void { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - var threaded: Io.Threaded = .init(gpa); + var threaded: Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/generate_c_size_and_align_checks.zig b/tools/generate_c_size_and_align_checks.zig index 3663756533ef4bd8595932e6f359267bb2d96b61..6f934564066ecc1fd43d6bfb04105644baacd996 100644 --- a/tools/generate_c_size_and_align_checks.zig +++ b/tools/generate_c_size_and_align_checks.zig @@ -39,7 +39,7 @@ pub fn main() !void { std.process.exit(1); } - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index b3e5d623778108651e0c05aa5c0e5c81c4924bb9..789cb95116781a817e1c27f867ce412e1b0a0ee0 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -175,7 +175,7 @@ pub fn main() !void { defer arena.deinit(); const gpa = arena.allocator(); - var threaded: Io.Threaded = .init(gpa); + var threaded: Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/incr-check.zig b/tools/incr-check.zig index b9c9eb0e92757a6319205dd216b497c507fced1e..15e5d009150242d89cf350071007a20532c39e80 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -15,7 +15,7 @@ pub fn main() !void { const gpa = arena; - var threaded: Io.Threaded = .init(gpa); + var threaded: Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/migrate_langref.zig b/tools/migrate_langref.zig index e03e266c2e873c60c0319637f3a18b219671fa9c..299326d98c7d0ecaaf39d4faa0d508c99761d79a 100644 --- a/tools/migrate_langref.zig +++ b/tools/migrate_langref.zig @@ -22,7 +22,7 @@ pub fn main() !void { const input_file = args[1]; const output_file = args[2]; - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/update-linux-headers.zig b/tools/update-linux-headers.zig index 4faf9b27488420355acee603a5585dc83880704e..2d71d8c71864a55a0199f03300863e393a998f84 100644 --- a/tools/update-linux-headers.zig +++ b/tools/update-linux-headers.zig @@ -145,7 +145,7 @@ pub fn main() !void { var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); const arena = arena_state.allocator(); - var threaded: Io.Threaded = .init(arena); + var threaded: Io.Threaded = .init(arena, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 3741e51d3c4fd539b42cbe2dfa674af93fa1d796..e49ca4d01a795b4d62c67f01f6c2854548ee1df5 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -1892,7 +1892,7 @@ pub fn main() anyerror!void { defer arena_state.deinit(); const arena = arena_state.allocator(); - var threaded: std.Io.Threaded = .init(gpa); + var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/update_crc_catalog.zig b/tools/update_crc_catalog.zig index 2893f70f674deaacf67e22183ac41765047a5229..16ae58a564350b7f0e79d99154b493326311632b 100644 --- a/tools/update_crc_catalog.zig +++ b/tools/update_crc_catalog.zig @@ -11,7 +11,7 @@ pub fn main() anyerror!void { defer arena_state.deinit(); const arena = arena_state.allocator(); - var threaded: Io.Threaded = .init(arena); + var threaded: Io.Threaded = .init(arena, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/update_freebsd_libc.zig b/tools/update_freebsd_libc.zig index c43b94858db29d5905cee34e1bdf528440008504..9a3bd3ee78ef311408a65484b8f29eb650c65c30 100644 --- a/tools/update_freebsd_libc.zig +++ b/tools/update_freebsd_libc.zig @@ -17,7 +17,7 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var threaded: Io.Threaded = .init(arena); + var threaded: Io.Threaded = .init(arena, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/update_glibc.zig b/tools/update_glibc.zig index 227400476201db69400ffba676de2d2b7104ee9f..1a92bda1dbdd5875d94434c048c6fceeb6f09637 100644 --- a/tools/update_glibc.zig +++ b/tools/update_glibc.zig @@ -43,7 +43,7 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var threaded: Io.Threaded = .init(arena); + var threaded: Io.Threaded = .init(arena, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/update_mingw.zig b/tools/update_mingw.zig index fee5774629b83edffe74e9a6a83d8b3a3f120c9c..d427a41e74c16fbad37f3eb84f3cbe95805aad56 100644 --- a/tools/update_mingw.zig +++ b/tools/update_mingw.zig @@ -7,7 +7,7 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var threaded: Io.Threaded = .init(arena); + var threaded: Io.Threaded = .init(arena, .{}); defer threaded.deinit(); const io = threaded.io(); diff --git a/tools/update_netbsd_libc.zig b/tools/update_netbsd_libc.zig index e3b7bd21eed32dc2d7f5e96e17a389f574758a85..31f77daf51ec0f0e3fd4b4f95f3df25243851b88 100644 --- a/tools/update_netbsd_libc.zig +++ b/tools/update_netbsd_libc.zig @@ -17,7 +17,7 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var threaded: Io.Threaded = .init(arena); + var threaded: Io.Threaded = .init(arena, .{}); defer threaded.deinit(); const io = threaded.io(); -- 2.54.0 From 691afee786d095fef2a7abed73efa035feef3f4e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 18:49:10 -0800 Subject: [PATCH 146/195] langref: fix build failure --- doc/langref/bad_default_value.zig | 2 +- doc/langref/hello.zig | 12 ++++++- lib/compiler/aro/aro/Compilation.zig | 3 +- lib/compiler/translate-c/main.zig | 15 ++++---- tools/docgen.zig | 26 +++++++------- tools/doctest.zig | 53 +++++++++++++--------------- 6 files changed, 61 insertions(+), 50 deletions(-) diff --git a/doc/langref/bad_default_value.zig b/doc/langref/bad_default_value.zig index df38209c49f09d7bcfed1b61a7a56b5e667bd1e1..c80374a3334fa5fc1fa823e0989b360f87128dad 100644 --- a/doc/langref/bad_default_value.zig +++ b/doc/langref/bad_default_value.zig @@ -17,7 +17,7 @@ pub fn main() !void { .maximum = 0.20, }; const category = threshold.categorize(0.90); - try std.fs.File.stdout().writeAll(@tagName(category)); + std.log.info("category: {t}", .{category}); } const std = @import("std"); diff --git a/doc/langref/hello.zig b/doc/langref/hello.zig index 27ea1f689a5e26f6f67d89965d9e1383022cb509..3fc2fb98d58b1e65317102941f5a8a06f740e1a7 100644 --- a/doc/langref/hello.zig +++ b/doc/langref/hello.zig @@ -1,7 +1,17 @@ const std = @import("std"); +// See https://github.com/ziglang/zig/issues/24510 +// for the plan to simplify this code. pub fn main() !void { - try std.fs.File.stdout().writeAll("Hello, World!\n"); + var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + defer _ = debug_allocator.deinit(); + const gpa = debug_allocator.allocator(); + + var threaded: std.Io.Threaded = .init(gpa, .{}); + defer threaded.deinit(); + const io = threaded.io(); + + try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } // exe=succeed diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index a78f18dd743575f44d1693d0adf566c7b6eba264..aec780af02abd7f2c9611e7f08b0db14f8f36756 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -1713,7 +1713,8 @@ pub fn initSearchPath(comp: *Compilation, includes: []const Include, verbose: bo } } fn addToSearchPath(comp: *Compilation, include: Include, verbose: bool) !void { - comp.cwd.access(include.path, .{}) catch { + const io = comp.io; + comp.cwd.access(io, include.path, .{}) catch { if (verbose) { std.debug.print("ignoring nonexistent directory \"{s}\"\n", .{include.path}); return; diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index a6737b68fa7b8dfe77494d25355d4f98ff92ac14..ee50df422adbafc194b85930cb21d2b77485a7b6 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -34,11 +34,14 @@ pub fn main() u8 { zig_integration = true; } + const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(); + const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(); + var stderr_buf: [1024]u8 = undefined; var stderr = Io.File.stderr().writer(io, &stderr_buf); var diagnostics: aro.Diagnostics = switch (zig_integration) { false => .{ .output = .{ .to_writer = .{ - .color = .detect(stderr.file), + .mode = Io.Terminal.Mode.detect(io, stderr.file, NO_COLOR, CLICOLOR_FORCE) catch unreachable, .writer = &stderr.interface, } } }, true => .{ .output = .{ .to_list = .{ @@ -69,7 +72,7 @@ pub fn main() u8 { return 1; }, error.FatalError => if (zig_integration) { - serveErrorBundle(arena, &diagnostics) catch |bundle_err| { + serveErrorBundle(arena, io, &diagnostics) catch |bundle_err| { std.debug.print("unable to serve error bundle: {}\n", .{bundle_err}); if (fast_exit) process.exit(1); return 1; @@ -93,14 +96,14 @@ pub fn main() u8 { return @intFromBool(comp.diagnostics.errors != 0); } -fn serveErrorBundle(arena: std.mem.Allocator, diagnostics: *const aro.Diagnostics) !void { +fn serveErrorBundle(arena: std.mem.Allocator, io: Io, diagnostics: *const aro.Diagnostics) !void { const error_bundle = try compiler_util.aroDiagnosticsToErrorBundle( diagnostics, arena, "translation failure", ); var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); var server: std.zig.Server = .{ .out = &stdout_writer.interface, .in = undefined, @@ -130,13 +133,13 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration args[i] = arg; if (mem.eql(u8, arg, "--help")) { var stdout_buf: [512]u8 = undefined; - var stdout = Io.File.stdout().writer(&stdout_buf); + var stdout = Io.File.stdout().writer(io, &stdout_buf); try stdout.interface.print(usage, .{args[0]}); try stdout.interface.flush(); return; } else if (mem.eql(u8, arg, "--version")) { var stdout_buf: [512]u8 = undefined; - var stdout = Io.File.stdout().writer(&stdout_buf); + var stdout = Io.File.stdout().writer(io, &stdout_buf); // TODO add version try stdout.interface.writeAll("0.0.0-dev\n"); try stdout.interface.flush(); diff --git a/tools/docgen.zig b/tools/docgen.zig index a46e34ee7e7a0a05285f8e7ad3c1a13b4cda7118..ac0d26b995c9ed8cfb9a2c2f13dff2082550b093 100644 --- a/tools/docgen.zig +++ b/tools/docgen.zig @@ -1,6 +1,8 @@ -const std = @import("std"); const builtin = @import("builtin"); -const fs = std.fs; + +const std = @import("std"); +const Io = std.Io; +const Dir = std.Io.Dir; const process = std.process; const Progress = std.Progress; const print = std.debug.print; @@ -8,7 +10,6 @@ const mem = std.mem; const testing = std.testing; const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; -const getExternalExecutor = std.zig.system.getExternalExecutor; const fatal = std.process.fatal; const Writer = std.Io.Writer; @@ -49,7 +50,7 @@ pub fn main() !void { while (args_it.next()) |arg| { if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try fs.File.stdout().writeAll(usage); + try Io.File.stdout().writeStreamingAll(io, usage); process.exit(0); } else if (mem.eql(u8, arg, "--code-dir")) { if (args_it.next()) |param| { @@ -72,15 +73,15 @@ pub fn main() !void { const output_path = opt_output orelse fatal("missing output file", .{}); const code_dir_path = opt_code_dir orelse fatal("missing --code-dir argument", .{}); - var in_file = try fs.cwd().openFile(input_path, .{}); + var in_file = try Dir.cwd().openFile(io, input_path, .{}); defer in_file.close(io); - var out_file = try fs.cwd().createFile(output_path, .{}); + var out_file = try Dir.cwd().createFile(io, output_path, .{}); defer out_file.close(io); var out_file_buffer: [4096]u8 = undefined; - var out_file_writer = out_file.writer(&out_file_buffer); + var out_file_writer = out_file.writer(io, &out_file_buffer); - var code_dir = try fs.cwd().openDir(code_dir_path, .{}); + var code_dir = try Dir.cwd().openDir(io, code_dir_path, .{}); defer code_dir.close(io); var in_file_reader = in_file.reader(io, &.{}); @@ -89,7 +90,7 @@ pub fn main() !void { var tokenizer = Tokenizer.init(input_path, input_file_bytes); var toc = try genToc(arena, &tokenizer); - try genHtml(arena, &tokenizer, &toc, code_dir, &out_file_writer.interface); + try genHtml(arena, io, &tokenizer, &toc, code_dir, &out_file_writer.interface); try out_file_writer.end(); } @@ -988,9 +989,10 @@ fn printShell(out: *Writer, shell_content: []const u8, escape: bool) !void { fn genHtml( allocator: Allocator, + io: Io, tokenizer: *Tokenizer, toc: *Toc, - code_dir: std.fs.Dir, + code_dir: Dir, out: *Writer, ) !void { for (toc.nodes) |node| { @@ -1042,11 +1044,11 @@ fn genHtml( }, .Code => |code| { const out_basename = try std.fmt.allocPrint(allocator, "{s}.out", .{ - fs.path.stem(code.name), + Dir.path.stem(code.name), }); defer allocator.free(out_basename); - const contents = code_dir.readFileAlloc(out_basename, allocator, .limited(std.math.maxInt(u32))) catch |err| { + const contents = code_dir.readFileAlloc(io, out_basename, allocator, .limited(std.math.maxInt(u32))) catch |err| { return parseError(tokenizer, code.token, "unable to open '{s}': {t}", .{ out_basename, err }); }; defer allocator.free(contents); diff --git a/tools/doctest.zig b/tools/doctest.zig index 8dffd447a4b7351e2d2dba37bcc9a7076ec3d77f..3a67210a592b0daaf471f1853897d3635602b61e 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -2,10 +2,10 @@ const builtin = @import("builtin"); const std = @import("std"); const Io = std.Io; +const Dir = std.Io.Dir; const Writer = std.Io.Writer; const fatal = std.process.fatal; const mem = std.mem; -const fs = std.fs; const process = std.process; const Allocator = std.mem.Allocator; const testing = std.testing; @@ -53,7 +53,7 @@ pub fn main() !void { while (args_it.next()) |arg| { if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try std.fs.File.stdout().writeAll(usage); + try Io.File.stdout().writeStreamingAll(io, usage); process.exit(0); } else if (mem.eql(u8, arg, "-i")) { opt_input = args_it.next() orelse fatal("expected parameter after -i", .{}); @@ -78,37 +78,37 @@ pub fn main() !void { const zig_path = opt_zig orelse fatal("missing zig compiler path (--zig)", .{}); const cache_root = opt_cache_root orelse fatal("missing cache root path (--cache-root)", .{}); - const source_bytes = try fs.cwd().readFileAlloc(input_path, arena, .limited(std.math.maxInt(u32))); + const source_bytes = try Dir.cwd().readFileAlloc(io, input_path, arena, .limited(std.math.maxInt(u32))); const code = try parseManifest(arena, source_bytes); const source = stripManifest(source_bytes); const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{ cache_root, std.crypto.random.int(u64), }); - fs.cwd().createDirPath(io, tmp_dir_path) catch |err| + Dir.cwd().createDirPath(io, tmp_dir_path) catch |err| fatal("unable to create tmp dir '{s}': {t}", .{ tmp_dir_path, err }); - defer fs.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{ + defer Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{ tmp_dir_path, err, }); - var out_file = try fs.cwd().createFile(io, output_path, .{}); + var out_file = try Dir.cwd().createFile(io, output_path, .{}); defer out_file.close(io); var out_file_buffer: [4096]u8 = undefined; var out_file_writer = out_file.writer(io, &out_file_buffer); const out = &out_file_writer.interface; - try printSourceBlock(arena, out, source, fs.path.basename(input_path)); + try printSourceBlock(arena, out, source, Dir.path.basename(input_path)); try printOutput( arena, io, out, code, tmp_dir_path, - try std.fs.path.relative(arena, tmp_dir_path, zig_path), - try std.fs.path.relative(arena, tmp_dir_path, input_path), + try Dir.path.relative(arena, tmp_dir_path, zig_path), + try Dir.path.relative(arena, tmp_dir_path, input_path), if (opt_zig_lib_dir) |zig_lib_dir| - try std.fs.path.relative(arena, tmp_dir_path, zig_lib_dir) + try Dir.path.relative(arena, tmp_dir_path, zig_lib_dir) else null, ); @@ -141,7 +141,7 @@ fn printOutput( defer shell_buffer.deinit(); const shell_out = &shell_buffer.writer; - const code_name = std.fs.path.stem(input_path); + const code_name = Dir.path.stem(input_path); switch (code.id) { .exe => |expected_outcome| code_block: { @@ -201,8 +201,7 @@ fn printOutput( try shell_out.print("\n", .{}); if (expected_outcome == .build_fail) { - const result = try process.Child.run(.{ - .allocator = arena, + const result = try process.Child.run(arena, io, .{ .argv = build_args.items, .cwd = tmp_dir_path, .env_map = &env_map, @@ -227,7 +226,7 @@ fn printOutput( try shell_out.writeAll(colored_stderr); break :code_block; } - const exec_result = run(arena, &env_map, tmp_dir_path, build_args.items) catch + const exec_result = run(arena, io, &env_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{}); if (code.verbose_cimport) { @@ -258,8 +257,7 @@ fn printOutput( var exited_with_signal = false; const result = if (expected_outcome == .fail) blk: { - const result = try process.Child.run(.{ - .allocator = arena, + const result = try process.Child.run(arena, io, .{ .argv = run_args, .env_map = &env_map, .cwd = tmp_dir_path, @@ -278,7 +276,7 @@ fn printOutput( } break :blk result; } else blk: { - break :blk run(arena, &env_map, tmp_dir_path, run_args) catch + break :blk run(arena, io, &env_map, tmp_dir_path, run_args) catch fatal("example crashed", .{}); }; @@ -327,7 +325,7 @@ fn printOutput( .arch_os_abi = triple, }); const target = try std.zig.system.resolveTargetQuery(io, target_query); - switch (getExternalExecutor(&host, &target, .{ + switch (getExternalExecutor(io, &host, &target, .{ .link_libc = code.link_libc, })) { .native => {}, @@ -347,7 +345,7 @@ fn printOutput( } } - const result = run(arena, &env_map, tmp_dir_path, test_args.items) catch + const result = run(arena, io, &env_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{}); const escaped_stderr = try escapeHtml(arena, result.stderr); const escaped_stdout = try escapeHtml(arena, result.stdout); @@ -378,8 +376,7 @@ fn printOutput( try test_args.append("-lc"); try shell_out.print("-lc ", .{}); } - const result = try process.Child.run(.{ - .allocator = arena, + const result = try process.Child.run(arena, io, .{ .argv = test_args.items, .env_map = &env_map, .cwd = tmp_dir_path, @@ -435,8 +432,7 @@ fn printOutput( }, } - const result = try process.Child.run(.{ - .allocator = arena, + const result = try process.Child.run(arena, io, .{ .argv = test_args.items, .env_map = &env_map, .cwd = tmp_dir_path, @@ -512,8 +508,7 @@ fn printOutput( } if (maybe_error_match) |error_match| { - const result = try process.Child.run(.{ - .allocator = arena, + const result = try process.Child.run(arena, io, .{ .argv = build_args.items, .env_map = &env_map, .cwd = tmp_dir_path, @@ -541,7 +536,7 @@ fn printOutput( const colored_stderr = try termColor(arena, escaped_stderr); try shell_out.print("\n{s} ", .{colored_stderr}); } else { - _ = run(arena, &env_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{}); + _ = run(arena, io, &env_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{}); } try shell_out.writeAll("\n"); }, @@ -600,7 +595,7 @@ fn printOutput( try test_args.append(option); try shell_out.print("{s} ", .{option}); } - const result = run(arena, &env_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{}); + const result = run(arena, io, &env_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{}); const escaped_stderr = try escapeHtml(arena, result.stderr); const escaped_stdout = try escapeHtml(arena, result.stdout); try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout }); @@ -1132,12 +1127,12 @@ fn in(slice: []const u8, number: u8) bool { fn run( allocator: Allocator, + io: Io, env_map: *process.EnvMap, cwd: []const u8, args: []const []const u8, ) !process.Child.RunResult { - const result = try process.Child.run(.{ - .allocator = allocator, + const result = try process.Child.run(allocator, io, .{ .argv = args, .env_map = env_map, .cwd = cwd, -- 2.54.0 From e9da2783ce3c65d8e8e8533b7c70ec5a867f830a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 21:00:39 -0800 Subject: [PATCH 147/195] std.Io.Threaded: add a workaround for self-hosted wasm backend Tracked by #30575 --- lib/std/Io/Threaded.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 2f6772efd85f3197c4aad3b1ac02ad5819a48dfc..1c7d848aaeaec3e5abce81b4c68acda6b20b2daf 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -11085,6 +11085,13 @@ fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp { } fn timestampToPosix(nanoseconds: i96) posix.timespec { + if (builtin.zig_backend == .stage2_wasm) { + // Workaround for https://codeberg.org/ziglang/zig/issues/30575 + return .{ + .sec = @intCast(@divTrunc(nanoseconds, std.time.ns_per_s)), + .nsec = @intCast(@rem(nanoseconds, std.time.ns_per_s)), + }; + } return .{ .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)), .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)), -- 2.54.0 From 6ece10f63d91cfa16d8388f8d696368537c1662e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 20:55:47 -0800 Subject: [PATCH 148/195] update test-link to new std.Io API --- test/link/bss/main.zig | 2 +- test/link/elf.zig | 4 ++-- test/link/macho.zig | 4 ++-- test/link/wasm/extern/main.zig | 2 +- test/standalone/simple/guess_number/main.zig | 12 +++++++++++- test/standalone/simple/hello_world/hello.zig | 2 ++ test/standalone/windows_bat_args/echo-args.zig | 4 +++- test/standalone/windows_paths/relative.zig | 6 +++++- test/standalone/windows_spawn/hello.zig | 3 ++- 9 files changed, 29 insertions(+), 10 deletions(-) diff --git a/test/link/bss/main.zig b/test/link/bss/main.zig index 9c104fec04c1ffdd8559a5f98fb3d1f1328ca12f..0d69f97450336eafa9d53682816ffdc550be7569 100644 --- a/test/link/bss/main.zig +++ b/test/link/bss/main.zig @@ -4,7 +4,7 @@ const std = @import("std"); var buffer: [0x1000000]u64 = [1]u64{0} ** 0x1000000; pub fn main() anyerror!void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{}); buffer[0x10] = 1; diff --git a/test/link/elf.zig b/test/link/elf.zig index 6d9912193baac383129ef4f496d9cea4b4b86c33..d5b0eb75196d4b19beb5691b6c874ffff0a8d228 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -1323,7 +1323,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step { \\extern var live_var2: i32; \\extern fn live_fn2() void; \\pub fn main() void { - \\ var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + \\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{}); \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail"); \\ live_fn2(); \\} @@ -1365,7 +1365,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step { \\extern var live_var2: i32; \\extern fn live_fn2() void; \\pub fn main() void { - \\ var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + \\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{}); \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail"); \\ live_fn2(); \\} diff --git a/test/link/macho.zig b/test/link/macho.zig index 12a74e0825bb19eff463189229e0dd3baa52a0db..ccfecefa4402e235a7bcfb525e93444f6e047861 100644 --- a/test/link/macho.zig +++ b/test/link/macho.zig @@ -716,7 +716,7 @@ fn testHelloZig(b: *Build, opts: Options) *Step { const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes = \\const std = @import("std"); \\pub fn main() void { - \\ std.Io.File.stdout().writeAll("Hello world!\n") catch @panic("fail"); + \\ std.Io.File.stdout().writeStreamingAll(std.Options.debug_io, "Hello world!\n") catch @panic("fail"); \\} }); @@ -2372,7 +2372,7 @@ fn testTlsZig(b: *Build, opts: Options) *Step { \\threadlocal var x: i32 = 0; \\threadlocal var y: i32 = -1; \\pub fn main() void { - \\ var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + \\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{}); \\ stdout_writer.interface.print("{d} {d}\n", .{x, y}) catch unreachable; \\ x -= 1; \\ y += 1; diff --git a/test/link/wasm/extern/main.zig b/test/link/wasm/extern/main.zig index a841cc40a6bd01a1a3f88a38f1207d05620cd7b9..51c5f841815cab8cc23d221396ae8f07ddd2a285 100644 --- a/test/link/wasm/extern/main.zig +++ b/test/link/wasm/extern/main.zig @@ -3,6 +3,6 @@ const std = @import("std"); extern const foo: u32; pub fn main() void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{}); stdout_writer.interface.print("Result: {d}", .{foo}) catch {}; } diff --git a/test/standalone/simple/guess_number/main.zig b/test/standalone/simple/guess_number/main.zig index 25226ec1c8ffd1eb419fc4bfeff781cee96114d3..38082aad9e22986b3c2ffd55336ae9ad1c3b2eab 100644 --- a/test/standalone/simple/guess_number/main.zig +++ b/test/standalone/simple/guess_number/main.zig @@ -1,8 +1,18 @@ const builtin = @import("builtin"); const std = @import("std"); +// See https://github.com/ziglang/zig/issues/24510 +// for the plan to simplify this code. pub fn main() !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + defer _ = debug_allocator.deinit(); + const gpa = debug_allocator.allocator(); + + var threaded: std.Io.Threaded = .init(gpa, .{}); + defer threaded.deinit(); + const io = threaded.io(); + + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); const out = &stdout_writer.interface; const stdin: std.Io.File = .stdin(); diff --git a/test/standalone/simple/hello_world/hello.zig b/test/standalone/simple/hello_world/hello.zig index 7bb4f00e4dd87b876a3c68ab2dd18038b2719bfe..a031d6c6f0482bce684392fda834c0784212801b 100644 --- a/test/standalone/simple/hello_world/hello.zig +++ b/test/standalone/simple/hello_world/hello.zig @@ -1,5 +1,7 @@ const std = @import("std"); +// See https://github.com/ziglang/zig/issues/24510 +// for the plan to simplify this code. pub fn main() !void { var debug_allocator: std.heap.DebugAllocator(.{}) = .init; defer _ = debug_allocator.deinit(); diff --git a/test/standalone/windows_bat_args/echo-args.zig b/test/standalone/windows_bat_args/echo-args.zig index 7e73fa54a00584aa6238a587ba9cd3cf6d577b57..6aeb43d56ca6fb70b5d6d9b8a918c943d66cda9a 100644 --- a/test/standalone/windows_bat_args/echo-args.zig +++ b/test/standalone/windows_bat_args/echo-args.zig @@ -5,7 +5,9 @@ pub fn main() !void { defer arena_state.deinit(); const arena = arena_state.allocator(); - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + const io = std.Options.debug_io; + + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); const stdout = &stdout_writer.interface; var args = try std.process.argsAlloc(arena); for (args[1..], 1..) |arg, i| { diff --git a/test/standalone/windows_paths/relative.zig b/test/standalone/windows_paths/relative.zig index 7f87151ac8084cc5dfb29833bfe9124b2f01a9b4..7b6a51b283a54f6e765900116a9218103727b753 100644 --- a/test/standalone/windows_paths/relative.zig +++ b/test/standalone/windows_paths/relative.zig @@ -10,10 +10,14 @@ pub fn main() !void { if (args.len < 3) return error.MissingArgs; + var threaded: std.Io.Threaded = .init(allocator, .{}); + defer threaded.deinit(); + const io = threaded.io(); + const relative = try std.fs.path.relative(allocator, args[1], args[2]); defer allocator.free(relative); - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); const stdout = &stdout_writer.interface; try stdout.writeAll(relative); } diff --git a/test/standalone/windows_spawn/hello.zig b/test/standalone/windows_spawn/hello.zig index e54bbe038654d5bac034ea101b90e17b1fb32897..51e6aaf8bc60d87e4af08b04e6eba135b51a1f95 100644 --- a/test/standalone/windows_spawn/hello.zig +++ b/test/standalone/windows_spawn/hello.zig @@ -1,7 +1,8 @@ const std = @import("std"); pub fn main() !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(&.{}); + const io = std.Options.debug_io; + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); const stdout = &stdout_writer.interface; try stdout.writeAll("hello from exe\n"); } -- 2.54.0 From bd6acbf7da55a2497fcfb9c589093612a3fe9680 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 21:46:08 -0800 Subject: [PATCH 149/195] std.Io: minor cleanups to futex and event mainly avoid an unnecessary `@ptrCast` --- lib/std/Io.zig | 20 +++++++++++++------- lib/std/Io/Threaded.zig | 19 ++++++------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index d01077d803f0207b858d8b97e14897c8b2813e73..70b6c5bcc3079ff501071fec819b39588685af42 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1314,17 +1314,21 @@ pub fn futexWait(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, e /// wakeups are possible. It remains the caller's responsibility to differentiate between these /// three possible wake-up reasons if necessary. pub fn futexWaitTimeout(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, expected: T, timeout: Timeout) Cancelable!void { - comptime assert(@sizeOf(T) == 4); - const expected_raw: *align(1) const u32 = @ptrCast(&expected); - return io.vtable.futexWait(io.userdata, @ptrCast(ptr), expected_raw.*, timeout); + const expected_int: u32 = switch (@typeInfo(T)) { + .@"enum" => @bitCast(@intFromEnum(expected)), + else => @bitCast(expected), + }; + return io.vtable.futexWait(io.userdata, @ptrCast(ptr), expected_int, timeout); } /// Same as `futexWait`, except does not introduce a cancelation point. /// /// For a description of cancelation and cancelation points, see `Future.cancel`. pub fn futexWaitUncancelable(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, expected: T) void { - comptime assert(@sizeOf(T) == @sizeOf(u32)); - const expected_raw: *align(1) const u32 = @ptrCast(&expected); - io.vtable.futexWaitUncancelable(io.userdata, @ptrCast(ptr), expected_raw.*); + const expected_int: u32 = switch (@typeInfo(T)) { + .@"enum" => @bitCast(@intFromEnum(expected)), + else => @bitCast(expected), + }; + io.vtable.futexWaitUncancelable(io.userdata, @ptrCast(ptr), expected_int); } /// Unblocks pending futex waits on `ptr`, up to a limit of `max_waiters` calls. pub fn futexWake(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, max_waiters: u32) void { @@ -1576,10 +1580,12 @@ pub const Event = enum(u32) { } } + pub const WaitTimeoutError = error{Timeout} || Cancelable; + /// Blocks the calling thread until either the logical boolean is set, the timeout expires, or a /// spurious wakeup occurs. If the timeout expires or a spurious wakeup occurs, `error.Timeout` /// is returned. - pub fn waitTimeout(event: *Event, io: Io, timeout: Timeout) (error{Timeout} || Cancelable)!void { + pub fn waitTimeout(event: *Event, io: Io, timeout: Timeout) WaitTimeoutError!void { if (@cmpxchgStrong(Event, event, .unset, .waiting, .acquire, .acquire)) |prev| switch (prev) { .unset => unreachable, .waiting => assert(!builtin.single_threaded), // invalid state diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 1c7d848aaeaec3e5abce81b4c68acda6b20b2daf..4d26780705d556fc01967f8c91bb2b8d121ba55a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -292,7 +292,7 @@ const Thread = struct { .INTR => {}, // caller's responsibility to retry .AGAIN => {}, // ptr.* != expect .INVAL => {}, // possibly timeout overflow - .TIMEDOUT => {}, // timeout + .TIMEDOUT => {}, .FAULT => recoverableOsBugDetected(), // ptr was invalid else => recoverableOsBugDetected(), } @@ -1548,6 +1548,7 @@ fn cancel( } fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void { + if (builtin.single_threaded) unreachable; // Deadlock. const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); const t_io = ioBasic(t); @@ -1555,29 +1556,21 @@ fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io. const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null; break :ns std.math.lossyCast(u64, d.raw.toNanoseconds()); }; - switch (native_os) { - .illumos, .netbsd, .openbsd => @panic("TODO"), - else => try current_thread.futexWaitTimed(ptr, expected, timeout_ns), - } + return Thread.futexWaitTimed(current_thread, ptr, expected, timeout_ns); } fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void { + if (builtin.single_threaded) unreachable; // Deadlock. const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - switch (native_os) { - .illumos, .netbsd, .openbsd => @panic("TODO"), - else => Thread.futexWaitUncancelable(ptr, expected), - } + Thread.futexWaitUncancelable(ptr, expected); } fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { if (builtin.single_threaded) unreachable; // Nothing to wake up. const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - switch (native_os) { - .illumos, .netbsd, .openbsd => @panic("TODO"), - else => Thread.futexWake(ptr, max_waiters), - } + Thread.futexWake(ptr, max_waiters); } const dirCreateDir = switch (native_os) { -- 2.54.0 From 60e90adbcf0244190e959efde4d5b8670d456029 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 21:59:26 -0800 Subject: [PATCH 150/195] compiler: add missing end() for root std.Progress nodes otherwise the progress task prevents main() from returning --- src/main.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.zig b/src/main.zig index f63ead2cc359ecdee020ac678e51873437e937ba..3501d9fe03638b50f38e6a2a462a87be429bb554 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4153,6 +4153,8 @@ fn serve( var child_pid: ?std.process.Child.Id = null; const main_progress_node = std.Progress.start(io, .{}); + defer main_progress_node.end(); + const file_system_inputs = comp.file_system_inputs.?; const IncrementalDebugServer = if (build_options.enable_debug_extensions and !builtin.single_threaded) @@ -5515,6 +5517,7 @@ fn jitCmd( const root_prog_node = if (options.progress_node) |node| node else std.Progress.start(io, .{ .disable_printing = (color == .off), }); + defer root_prog_node.end(); const target_query: std.Target.Query = .{}; const resolved_target: Package.Module.ResolvedTarget = .{ -- 2.54.0 From 7a09d579b55f942afe563b2c34b8d96c3e62c8c0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 22:30:02 -0800 Subject: [PATCH 151/195] CLI: lock stderr while running jit cmd --- src/main.zig | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main.zig b/src/main.zig index 3501d9fe03638b50f38e6a2a462a87be429bb554..628d91017a6c02d274665f056258f920bec3820c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5687,14 +5687,18 @@ fn jitCmd( child.stdout_behavior = if (options.capture == null) .Inherit else .Pipe; child.stderr_behavior = .Inherit; - try child.spawn(io); + const term = t: { + _ = try io.lockStderr(&.{}, .no_color); + defer io.unlockStderr(); + try child.spawn(io); - if (options.capture) |ptr| { - var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); - ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); - } + if (options.capture) |ptr| { + var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); + ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); + } - const term = try child.wait(io); + break :t try child.wait(io); + }; switch (term) { .Exited => |code| { if (code == 0) { -- 2.54.0 From c05e2720a1acf0dc86e7f8a2661daae095d4d59b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 22:30:49 -0800 Subject: [PATCH 152/195] tools: fix compilation errors --- lib/compiler/objcopy.zig | 22 +++++++++++----------- tools/dump-cov.zig | 4 ++-- tools/gen_spirv_spec.zig | 5 +++-- tools/generate_linux_syscalls.zig | 4 ++-- tools/update-linux-headers.zig | 4 ++-- tools/update_clang_options.zig | 4 ++-- tools/update_cpu_features.zig | 5 +++-- tools/update_crc_catalog.zig | 4 ++-- 8 files changed, 27 insertions(+), 25 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 600166288c051d30e7f4dd782559514aeb48cfe7..77b60c9b372efc1616413ddb713741cbb59c0e65 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -25,11 +25,15 @@ pub fn main() !void { var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; const gpa = general_purpose_allocator.allocator(); + var threaded: std.Io.Threaded = .init(gpa, .{}); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(arena); - return cmdObjCopy(gpa, arena, args[1..]); + return cmdObjCopy(arena, io, args[1..]); } -fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { +fn cmdObjCopy(arena: Allocator, io: Io, args: []const []const u8) !void { var i: usize = 0; var opt_out_fmt: ?std.Target.ObjectFormat = null; var opt_input: ?[]const u8 = null; @@ -57,7 +61,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void fatal("unexpected positional argument: '{s}'", .{arg}); } } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - return Io.File.stdout().writeAll(usage); + return Io.File.stdout().writeStreamingAll(io, usage); } else if (mem.eql(u8, arg, "-O") or mem.eql(u8, arg, "--output-target")) { i += 1; if (i >= args.len) fatal("expected another argument after '{s}'", .{arg}); @@ -148,11 +152,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void const input = opt_input orelse fatal("expected input parameter", .{}); const output = opt_output orelse fatal("expected output parameter", .{}); - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const input_file = Io.Dir.cwd().openFile(input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err }); + const input_file = Io.Dir.cwd().openFile(io, input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err }); defer input_file.close(io); const stat = input_file.stat(io) catch |err| fatal("failed to stat {s}: {t}", .{ input, err }); @@ -178,9 +178,9 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void } }; - const mode = if (out_fmt != .elf or only_keep_debug) Io.File.default_mode else stat.mode; + const permissions: Io.File.Permissions = if (out_fmt != .elf or only_keep_debug) .default_file else stat.permissions; - var output_file = try Io.Dir.cwd().createFile(io, output, .{ .mode = mode }); + var output_file = try Io.Dir.cwd().createFile(io, output, .{ .permissions = permissions }); defer output_file.close(io); var out = output_file.writer(io, &output_buffer); @@ -223,7 +223,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void if (listen) { var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer); - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); var server = try Server.init(.{ .in = &stdin_reader.interface, .out = &stdout_writer.interface, diff --git a/tools/dump-cov.zig b/tools/dump-cov.zig index d52b61f0583b753c0940c124b625d5c2c3238b65..211fe25197b2ef8c91cd27dcc10c00b800bd9159 100644 --- a/tools/dump-cov.zig +++ b/tools/dump-cov.zig @@ -51,8 +51,8 @@ pub fn main() !void { var coverage: std.debug.Coverage = .init; defer coverage.deinit(gpa); - var debug_info = std.debug.Info.load(gpa, exe_path, &coverage, target.ofmt, target.cpu.arch) catch |err| { - fatal("failed to load debug info for {f}: {s}", .{ exe_path, @errorName(err) }); + var debug_info = std.debug.Info.load(gpa, io, exe_path, &coverage, target.ofmt, target.cpu.arch) catch |err| { + fatal("failed to load debug info for {f}: {t}", .{ exe_path, err }); }; defer debug_info.deinit(gpa); diff --git a/tools/gen_spirv_spec.zig b/tools/gen_spirv_spec.zig index c211d1022ea8b69592d69967fe0a58025c88c37d..072de6573ec3c8b8a8b03587e7e6bb42977cff2b 100644 --- a/tools/gen_spirv_spec.zig +++ b/tools/gen_spirv_spec.zig @@ -930,8 +930,9 @@ fn parseHexInt(text: []const u8) !u31 { } fn usageAndExit(arg0: []const u8, code: u8) noreturn { - const stderr, _ = std.debug.lockStderrWriter(&.{}); - stderr.print( + const stderr = std.debug.lockStderr(&.{}); + const w = &stderr.file_writer.interface; + w.print( \\Usage: {s} \\ \\Generates Zig bindings for SPIR-V specifications found in the SPIRV-Headers diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index 789cb95116781a817e1c27f867ce412e1b0a0ee0..ebdc7a6e2fff993a01f863444f906b74979f72d6 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -181,8 +181,8 @@ pub fn main() !void { const args = try std.process.argsAlloc(gpa); if (args.len < 2 or mem.eql(u8, args[1], "--help")) { - const w, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); + const stderr = std.debug.lockStderr(&.{}); + const w = &stderr.file_writer.interface; usage(w, args[0]) catch std.process.exit(2); std.process.exit(1); } diff --git a/tools/update-linux-headers.zig b/tools/update-linux-headers.zig index 2d71d8c71864a55a0199f03300863e393a998f84..c3614e54166bc02e76052979151540c7f2d1a196 100644 --- a/tools/update-linux-headers.zig +++ b/tools/update-linux-headers.zig @@ -197,7 +197,7 @@ pub fn main() !void { try dir_stack.append(target_include_dir); while (dir_stack.pop()) |full_dir_name| { - var dir = Dir.cwd().openDir(full_dir_name, .{ .iterate = true }) catch |err| switch (err) { + var dir = Dir.cwd().openDir(io, full_dir_name, .{ .iterate = true }) catch |err| switch (err) { error.FileNotFound => continue :search, error.AccessDenied => continue :search, else => return err, @@ -213,7 +213,7 @@ pub fn main() !void { .file => { const rel_path = try Dir.path.relative(arena, target_include_dir, full_path); const max_size = 2 * 1024 * 1024 * 1024; - const raw_bytes = try Dir.cwd().readFileAlloc(full_path, arena, .limited(max_size)); + const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, arena, .limited(max_size)); const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t"); total_bytes += raw_bytes.len; const hash = try arena.alloc(u8, 32); diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index c8b941cbfac3f1d3fe5b2fb97dd3d1c2244af87c..5f3c7f40b5949cc40402db8a15468dd038e05653 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -961,8 +961,8 @@ fn objectLessThan(context: void, a: *json.ObjectMap, b: *json.ObjectMap) bool { } fn printUsageAndExit(arg0: []const u8) noreturn { - const w, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); + const stderr = std.debug.lockStderr(&.{}); + const w = &stderr.file_writer.interface; printUsage(w, arg0) catch std.process.exit(2); std.process.exit(1); } diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index e49ca4d01a795b4d62c67f01f6c2854548ee1df5..95864c586a61b7aa4b2c984d342e885e8c10392b 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -2426,8 +2426,9 @@ fn processOneTarget(io: Io, job: Job) void { } fn usageAndExit(arg0: []const u8, code: u8) noreturn { - const stderr, _ = std.debug.lockStderrWriter(&.{}); - stderr.print( + const stderr = std.debug.lockStderr(&.{}); + const w = &stderr.file_writer.interface; + w.print( \\Usage: {s} /path/to/llvm-tblgen /path/git/llvm-project /path/git/zig [zig_name filter] \\ \\Updates lib/std/target/.zig from llvm/lib/Target//.td . diff --git a/tools/update_crc_catalog.zig b/tools/update_crc_catalog.zig index 16ae58a564350b7f0e79d99154b493326311632b..f1448b1278a8563e2b062f33df862ffbbb751021 100644 --- a/tools/update_crc_catalog.zig +++ b/tools/update_crc_catalog.zig @@ -195,8 +195,8 @@ pub fn main() anyerror!void { } fn printUsageAndExit(arg0: []const u8) noreturn { - const w, _ = std.debug.lockStderrWriter(&.{}); - defer std.debug.unlockStderrWriter(); + const stderr = std.debug.lockStderr(&.{}); + const w = &stderr.file_writer.interface; printUsage(w, arg0) catch std.process.exit(2); std.process.exit(1); } -- 2.54.0 From 0870f17501aa7d2eaaf2d774ccbc5d72291664ca Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 23:23:01 -0800 Subject: [PATCH 153/195] fix aro and resinator compilation errors --- lib/compiler/aro/aro/Driver.zig | 3 +- lib/compiler/resinator/cli.zig | 53 +++++++++++----------- lib/compiler/resinator/compile.zig | 6 +-- lib/compiler/resinator/errors.zig | 70 +++++++++++++++--------------- lib/compiler/resinator/main.zig | 53 +++++++++++----------- lib/compiler/resinator/utils.zig | 21 ++++----- 6 files changed, 104 insertions(+), 102 deletions(-) diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig index b344cc8a9d140f1128269b9733894483d3915075..340a35bdde97b4995461bc139866a146246663db 100644 --- a/lib/compiler/aro/aro/Driver.zig +++ b/lib/compiler/aro/aro/Driver.zig @@ -134,8 +134,9 @@ strip: bool = false, unwindlib: ?[]const u8 = null, pub fn deinit(d: *Driver) void { + const io = d.comp.io; for (d.link_objects.items[d.link_objects.items.len - d.temp_file_count ..]) |obj| { - std.fs.deleteFileAbsolute(obj) catch {}; + Io.Dir.deleteFileAbsolute(io, obj) catch {}; d.comp.gpa.free(obj); } d.inputs.deinit(d.comp.gpa); diff --git a/lib/compiler/resinator/cli.zig b/lib/compiler/resinator/cli.zig index ddf3ca18b71dba8ba78a94fd3ad8b8faddd5bf0a..59843585a25b155aa345a2ded88ec16dbf0d7498 100644 --- a/lib/compiler/resinator/cli.zig +++ b/lib/compiler/resinator/cli.zig @@ -125,8 +125,8 @@ pub const Diagnostics = struct { try self.errors.append(self.allocator, error_details); } - pub fn renderToStderr(self: *Diagnostics, io: Io, args: []const []const u8) void { - const stderr = io.lockStderr(&.{}, null); + pub fn renderToStderr(self: *Diagnostics, io: Io, args: []const []const u8) Io.Cancelable!void { + const stderr = try io.lockStderr(&.{}, null); defer io.unlockStderr(); self.renderToWriter(args, stderr.terminal()) catch return; } @@ -419,7 +419,7 @@ pub const Arg = struct { }; } - pub fn looksLikeFilepath(self: Arg) bool { + pub fn looksLikeFilepath(self: Arg, io: Io) bool { const meets_min_requirements = self.prefix == .slash and isSupportedInputExtension(std.fs.path.extension(self.full)); if (!meets_min_requirements) return false; @@ -438,7 +438,7 @@ pub const Arg = struct { // It's still possible for a file path to look like a /fo option but not actually // be one, e.g. `/foo/bar.rc`. As a last ditch effort to reduce false negatives, // check if the file path exists and, if so, then we ignore the 'could be /fo option'-ness - std.fs.accessAbsolute(self.full, .{}) catch return false; + Io.Dir.accessAbsolute(io, self.full, .{}) catch return false; return true; } @@ -490,7 +490,7 @@ pub const ParseError = error{ParseError} || Allocator.Error; /// Note: Does not run `Options.maybeAppendRC` automatically. If that behavior is desired, /// it must be called separately. -pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagnostics) ParseError!Options { +pub fn parse(allocator: Allocator, io: Io, args: []const []const u8, diagnostics: *Diagnostics) ParseError!Options { var options = Options{ .allocator = allocator }; errdefer options.deinit(); @@ -530,7 +530,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn } const args_remaining = args.len - arg_i; - if (args_remaining <= 2 and arg.looksLikeFilepath()) { + if (args_remaining <= 2 and arg.looksLikeFilepath(io)) { var err_details = Diagnostics.ErrorDetails{ .type = .note, .print_args = true, .arg_index = arg_i }; try err_details.msg.appendSlice(allocator, "this argument was inferred to be a filepath, so argument parsing was terminated"); try diagnostics.append(err_details); @@ -1344,41 +1344,42 @@ test parsePercent { try std.testing.expectError(error.InvalidFormat, parsePercent("~1")); } -pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err_details: Diagnostics.ErrorDetails, args: []const []const u8) !void { - try config.setColor(writer, .dim); +pub fn renderErrorMessage(t: Io.Terminal, err_details: Diagnostics.ErrorDetails, args: []const []const u8) !void { + const writer = t.writer; + try t.setColor(.dim); try writer.writeAll(""); - try config.setColor(writer, .reset); - try config.setColor(writer, .bold); + try t.setColor(.reset); + try t.setColor(.bold); try writer.writeAll(": "); switch (err_details.type) { .err => { - try config.setColor(writer, .red); + try t.setColor(.red); try writer.writeAll("error: "); }, .warning => { - try config.setColor(writer, .yellow); + try t.setColor(.yellow); try writer.writeAll("warning: "); }, .note => { - try config.setColor(writer, .cyan); + try t.setColor(.cyan); try writer.writeAll("note: "); }, } - try config.setColor(writer, .reset); - try config.setColor(writer, .bold); + try t.setColor(.reset); + try t.setColor(.bold); try writer.writeAll(err_details.msg.items); try writer.writeByte('\n'); - try config.setColor(writer, .reset); + try t.setColor(.reset); if (!err_details.print_args) { try writer.writeByte('\n'); return; } - try config.setColor(writer, .dim); + try t.setColor(.dim); const prefix = " ... "; try writer.writeAll(prefix); - try config.setColor(writer, .reset); + try t.setColor(.reset); const arg_with_name = args[err_details.arg_index]; const prefix_slice = arg_with_name[0..err_details.arg_span.prefix_len]; @@ -1389,15 +1390,15 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err try writer.writeAll(prefix_slice); if (before_name_slice.len > 0) { - try config.setColor(writer, .dim); + try t.setColor(.dim); try writer.writeAll(before_name_slice); - try config.setColor(writer, .reset); + try t.setColor(.reset); } try writer.writeAll(name_slice); if (after_name_slice.len > 0) { - try config.setColor(writer, .dim); + try t.setColor(.dim); try writer.writeAll(after_name_slice); - try config.setColor(writer, .reset); + try t.setColor(.reset); } var next_arg_len: usize = 0; @@ -1415,13 +1416,13 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err if (err_details.arg_span.value_offset >= arg_with_name.len) { try writer.writeByte(' '); } - try config.setColor(writer, .dim); + try t.setColor(.dim); try writer.writeAll(" ..."); - try config.setColor(writer, .reset); + try t.setColor(.reset); } try writer.writeByte('\n'); - try config.setColor(writer, .green); + try t.setColor(.green); try writer.splatByteAll(' ', prefix.len); // Special case for when the option is *only* a prefix (e.g. invalid option: -) if (err_details.arg_span.prefix_len == arg_with_name.len) { @@ -1447,7 +1448,7 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err } } try writer.writeByte('\n'); - try config.setColor(writer, .reset); + try t.setColor(.reset); } fn testParse(args: []const []const u8) !Options { diff --git a/lib/compiler/resinator/compile.zig b/lib/compiler/resinator/compile.zig index 2d6ea87efbce7d84f966228c79e6e861c870d535..4f75fca18a4287ae96003db378f68f8c83f40c7d 100644 --- a/lib/compiler/resinator/compile.zig +++ b/lib/compiler/resinator/compile.zig @@ -96,7 +96,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io var search_dirs: std.ArrayList(SearchDir) = .empty; defer { for (search_dirs.items) |*search_dir| { - search_dir.deinit(allocator); + search_dir.deinit(allocator, io); } search_dirs.deinit(allocator); } @@ -406,7 +406,7 @@ pub const Compiler = struct { // `/test.bin` relative to include paths and instead only treats it as // an absolute path. if (std.fs.path.isAbsolute(path)) { - const file = try utils.openFileNotDir(Io.Dir.cwd(), path, .{}); + const file = try utils.openFileNotDir(Io.Dir.cwd(), io, path, .{}); errdefer file.close(io); if (self.dependencies) |dependencies| { @@ -418,7 +418,7 @@ pub const Compiler = struct { var first_error: ?(std.Io.File.OpenError || std.Io.File.StatError) = null; for (self.search_dirs) |search_dir| { - if (utils.openFileNotDir(search_dir.dir, path, .{})) |file| { + if (utils.openFileNotDir(search_dir.dir, io, path, .{})) |file| { errdefer file.close(io); if (self.dependencies) |dependencies| { diff --git a/lib/compiler/resinator/errors.zig b/lib/compiler/resinator/errors.zig index 6146f777cd4b9151bbd87dcc66e2ff559fafbcde..4f41a5b56da5e873ae0f9e1a430d484a195890bc 100644 --- a/lib/compiler/resinator/errors.zig +++ b/lib/compiler/resinator/errors.zig @@ -67,9 +67,9 @@ pub const Diagnostics = struct { return @intCast(index); } - pub fn renderToStderr(self: *Diagnostics, cwd: Io.Dir, source: []const u8, source_mappings: ?SourceMappings) void { + pub fn renderToStderr(self: *Diagnostics, cwd: Io.Dir, source: []const u8, source_mappings: ?SourceMappings) Io.Cancelable!void { const io = self.io; - const stderr = io.lockStderr(&.{}, null); + const stderr = try io.lockStderr(&.{}, null); defer io.unlockStderr(); for (self.errors.items) |err_details| { renderErrorMessage(io, stderr.terminal(), cwd, err_details, source, self.strings.items, source_mappings) catch return; @@ -901,8 +901,7 @@ const truncated_str = "<...truncated...>"; pub fn renderErrorMessage( io: Io, - writer: *std.Io.Writer, - tty_config: std.Io.tty.Config, + t: Io.Terminal, cwd: Io.Dir, err_details: ErrorDetails, source: []const u8, @@ -927,36 +926,37 @@ pub fn renderErrorMessage( const err_line = if (corresponding_span) |span| span.start_line else err_details.token.line_number; - try tty_config.setColor(writer, .bold); + const writer = t.writer; + try t.setColor(.bold); if (corresponding_file) |file| { try writer.writeAll(file); } else { - try tty_config.setColor(writer, .dim); + try t.setColor(.dim); try writer.writeAll(""); - try tty_config.setColor(writer, .reset); - try tty_config.setColor(writer, .bold); + try t.setColor(.reset); + try t.setColor(.bold); } try writer.print(":{d}:{d}: ", .{ err_line, column }); switch (err_details.type) { .err => { - try tty_config.setColor(writer, .red); + try t.setColor(.red); try writer.writeAll("error: "); }, .warning => { - try tty_config.setColor(writer, .yellow); + try t.setColor(.yellow); try writer.writeAll("warning: "); }, .note => { - try tty_config.setColor(writer, .cyan); + try t.setColor(.cyan); try writer.writeAll("note: "); }, .hint => unreachable, } - try tty_config.setColor(writer, .reset); - try tty_config.setColor(writer, .bold); + try t.setColor(.reset); + try t.setColor(.bold); try err_details.render(writer, source, strings); try writer.writeByte('\n'); - try tty_config.setColor(writer, .reset); + try t.setColor(.reset); if (!err_details.print_source_line) { try writer.writeByte('\n'); @@ -983,20 +983,20 @@ pub fn renderErrorMessage( try writer.writeAll(source_line_for_display.line); if (source_line_for_display.truncated) { - try tty_config.setColor(writer, .dim); + try t.setColor(.dim); try writer.writeAll(truncated_str); - try tty_config.setColor(writer, .reset); + try t.setColor(.reset); } try writer.writeByte('\n'); - try tty_config.setColor(writer, .green); + try t.setColor(.green); const num_spaces = truncated_visual_info.point_offset - truncated_visual_info.before_len; try writer.splatByteAll(' ', num_spaces); try writer.splatByteAll('~', truncated_visual_info.before_len); try writer.writeByte('^'); try writer.splatByteAll('~', truncated_visual_info.after_len); try writer.writeByte('\n'); - try tty_config.setColor(writer, .reset); + try t.setColor(.reset); if (corresponding_span != null and corresponding_file != null) { var worth_printing_lines: bool = true; @@ -1021,22 +1021,22 @@ pub fn renderErrorMessage( break :blk null; }, }; - defer if (corresponding_lines) |*cl| cl.deinit(); + defer if (corresponding_lines) |*cl| cl.deinit(io); - try tty_config.setColor(writer, .bold); + try t.setColor(.bold); if (corresponding_file) |file| { try writer.writeAll(file); } else { - try tty_config.setColor(writer, .dim); + try t.setColor(.dim); try writer.writeAll(""); - try tty_config.setColor(writer, .reset); - try tty_config.setColor(writer, .bold); + try t.setColor(.reset); + try t.setColor(.bold); } try writer.print(":{d}:{d}: ", .{ err_line, column }); - try tty_config.setColor(writer, .cyan); + try t.setColor(.cyan); try writer.writeAll("note: "); - try tty_config.setColor(writer, .reset); - try tty_config.setColor(writer, .bold); + try t.setColor(.reset); + try t.setColor(.bold); try writer.writeAll("this line originated from line"); if (corresponding_span.?.start_line != corresponding_span.?.end_line) { try writer.print("s {}-{}", .{ corresponding_span.?.start_line, corresponding_span.?.end_line }); @@ -1044,7 +1044,7 @@ pub fn renderErrorMessage( try writer.print(" {}", .{corresponding_span.?.start_line}); } try writer.print(" of file '{s}'\n", .{corresponding_file.?}); - try tty_config.setColor(writer, .reset); + try t.setColor(.reset); if (!worth_printing_lines) return; @@ -1055,21 +1055,21 @@ pub fn renderErrorMessage( }) |display_line| { try writer.writeAll(display_line.line); if (display_line.truncated) { - try tty_config.setColor(writer, .dim); + try t.setColor(.dim); try writer.writeAll(truncated_str); - try tty_config.setColor(writer, .reset); + try t.setColor(.reset); } try writer.writeByte('\n'); } break :write_lines null; }; if (write_lines_err) |err| { - try tty_config.setColor(writer, .red); + try t.setColor(.red); try writer.writeAll(" | "); - try tty_config.setColor(writer, .reset); - try tty_config.setColor(writer, .dim); + try t.setColor(.reset); + try t.setColor(.dim); try writer.print("unable to print line(s) from file: {s}\n", .{@errorName(err)}); - try tty_config.setColor(writer, .reset); + try t.setColor(.reset); } try writer.writeByte('\n'); } @@ -1120,12 +1120,12 @@ const CorrespondingLines = struct { var corresponding_lines = CorrespondingLines{ .span = corresponding_span, - .file = try utils.openFileNotDir(cwd, corresponding_file, .{}), + .file = try utils.openFileNotDir(cwd, io, corresponding_file, .{}), .code_page = err_details.code_page, .file_reader = undefined, }; corresponding_lines.file_reader = corresponding_lines.file.reader(io, file_reader_buf); - errdefer corresponding_lines.deinit(); + errdefer corresponding_lines.deinit(io); try corresponding_lines.writeLineFromStreamVerbatim( &corresponding_lines.file_reader.interface, diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 7e2e8403caf33abddc24f7ed0613cae9ed8a14ec..f311b2a5121b7edc05883d658717a56b8abf5dd8 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -64,18 +64,18 @@ pub fn main() !void { var options = options: { var cli_diagnostics = cli.Diagnostics.init(gpa); defer cli_diagnostics.deinit(); - var options = cli.parse(gpa, cli_args, &cli_diagnostics) catch |err| switch (err) { + var options = cli.parse(gpa, io, cli_args, &cli_diagnostics) catch |err| switch (err) { error.ParseError => { - try error_handler.emitCliDiagnostics(gpa, cli_args, &cli_diagnostics); + try error_handler.emitCliDiagnostics(gpa, io, cli_args, &cli_diagnostics); std.process.exit(1); }, else => |e| return e, }; - try options.maybeAppendRC(Io.Dir.cwd()); + try options.maybeAppendRC(io, Io.Dir.cwd()); if (!zig_integration) { // print any warnings/notes - cli_diagnostics.renderToStderr(io, cli_args); + try cli_diagnostics.renderToStderr(io, cli_args); // If there was something printed, then add an extra newline separator // so that there is a clear separation between the cli diagnostics and whatever // gets printed after @@ -193,7 +193,7 @@ pub fn main() !void { }; }, .filename => |input_filename| { - break :full_input Io.Dir.cwd().readFileAlloc(input_filename, gpa, .unlimited) catch |err| { + break :full_input Io.Dir.cwd().readFileAlloc(io, input_filename, gpa, .unlimited) catch |err| { try error_handler.emitMessage(gpa, io, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) }); std.process.exit(1); }; @@ -206,7 +206,7 @@ pub fn main() !void { if (options.preprocess == .only) { switch (options.output_source) { .stdio => |output_file| { - try output_file.writeAll(full_input); + try output_file.writeStreamingAll(io, full_input); }, .filename => |output_filename| { try Io.Dir.cwd().writeFile(io, .{ .sub_path = output_filename, .data = full_input }); @@ -224,16 +224,16 @@ pub fn main() !void { .source = .{ .memory = .empty }, } else if (options.input_format == .res) - IoStream.fromIoSource(options.input_source, .input) catch |err| { + IoStream.fromIoSource(io, options.input_source, .input) catch |err| { try error_handler.emitMessage(gpa, io, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) }); std.process.exit(1); } else - IoStream.fromIoSource(options.output_source, .output) catch |err| { + IoStream.fromIoSource(io, options.output_source, .output) catch |err| { try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); std.process.exit(1); }; - defer res_stream.deinit(gpa); + defer res_stream.deinit(gpa, io); const res_data = res_data: { if (options.input_format != .res) { @@ -269,7 +269,7 @@ pub fn main() !void { defer diagnostics.deinit(); var output_buffer: [4096]u8 = undefined; - var res_stream_writer = res_stream.source.writer(gpa, &output_buffer); + var res_stream_writer = res_stream.source.writer(gpa, io, &output_buffer); defer res_stream_writer.deinit(&res_stream.source); const output_buffered_stream = res_stream_writer.interface(); @@ -303,7 +303,7 @@ pub fn main() !void { // print any warnings/notes if (!zig_integration) { - diagnostics.renderToStderr(io, Io.Dir.cwd(), final_input, mapping_results.mappings); + try diagnostics.renderToStderr(Io.Dir.cwd(), final_input, mapping_results.mappings); } // write the depfile @@ -356,14 +356,14 @@ pub fn main() !void { }; defer resources.deinit(); - var coff_stream = IoStream.fromIoSource(options.output_source, .output) catch |err| { + var coff_stream = IoStream.fromIoSource(io, options.output_source, .output) catch |err| { try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); std.process.exit(1); }; - defer coff_stream.deinit(gpa); + defer coff_stream.deinit(gpa, io); var coff_output_buffer: [4096]u8 = undefined; - var coff_output_buffered_stream = coff_stream.source.writer(gpa, &coff_output_buffer); + var coff_output_buffered_stream = coff_stream.source.writer(gpa, io, &coff_output_buffer); var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} }; cvtres.writeCoff(gpa, coff_output_buffered_stream.interface(), resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| { @@ -413,22 +413,22 @@ const IoStream = struct { pub const IoDirection = enum { input, output }; - pub fn fromIoSource(source: cli.Options.IoSource, io: IoDirection) !IoStream { + pub fn fromIoSource(io: Io, source: cli.Options.IoSource, io_direction: IoDirection) !IoStream { return .{ .name = switch (source) { .filename => |filename| filename, - .stdio => switch (io) { + .stdio => switch (io_direction) { .input => "", .output => "", }, }, .intermediate = false, - .source = try Source.fromIoSource(source, io), + .source = try Source.fromIoSource(io, source, io_direction), }; } - pub fn deinit(self: *IoStream, allocator: Allocator) void { - self.source.deinit(allocator); + pub fn deinit(self: *IoStream, allocator: Allocator, io: Io) void { + self.source.deinit(allocator, io); } pub fn cleanupAfterError(self: *IoStream, io: Io) void { @@ -450,11 +450,11 @@ const IoStream = struct { /// The source has been closed and any usage of the Source in this state is illegal (except deinit). closed: void, - pub fn fromIoSource(source: cli.Options.IoSource, io: IoDirection) !Source { + pub fn fromIoSource(io: Io, source: cli.Options.IoSource, io_direction: IoDirection) !Source { switch (source) { .filename => |filename| return .{ - .file = switch (io) { - .input => try openFileNotDir(Io.Dir.cwd(), filename, .{}), + .file = switch (io_direction) { + .input => try openFileNotDir(Io.Dir.cwd(), io, filename, .{}), .output => try Io.Dir.cwd().createFile(io, filename, .{}), }, }, @@ -641,7 +641,7 @@ fn getIncludePaths( }; const target = std.zig.resolveTargetQueryOrFatal(io, target_query); const is_native_abi = target_query.isNativeAbi(); - const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch |err| switch (err) { + const detected_libc = std.zig.LibCDirs.detect(arena, io, zig_lib_dir, &target, is_native_abi, true, null) catch |err| switch (err) { error.OutOfMemory => |e| return e, else => return error.MingwIncludesNotFound, }; @@ -672,7 +672,7 @@ const ErrorHandler = union(enum) { try server.serveErrorBundle(error_bundle); }, - .stderr => diagnostics.renderToStderr(io, args), + .stderr => return diagnostics.renderToStderr(io, args), } } @@ -696,7 +696,7 @@ const ErrorHandler = union(enum) { }, .stderr => { // aro errors have already been emitted - const stderr = io.lockStderr(&.{}, null); + const stderr = try io.lockStderr(&.{}, null); defer io.unlockStderr(); try renderErrorMessage(stderr.terminal(), .err, "{s}", .{fail_msg}); }, @@ -706,7 +706,6 @@ const ErrorHandler = union(enum) { pub fn emitDiagnostics( self: *ErrorHandler, allocator: Allocator, - io: Io, cwd: Io.Dir, source: []const u8, diagnostics: *Diagnostics, @@ -719,7 +718,7 @@ const ErrorHandler = union(enum) { try server.serveErrorBundle(error_bundle); }, - .stderr => diagnostics.renderToStderr(io, cwd, source, mappings), + .stderr => return diagnostics.renderToStderr(cwd, source, mappings), } } diff --git a/lib/compiler/resinator/utils.zig b/lib/compiler/resinator/utils.zig index 285342ad74189a9f10abb3035149c1266218fe54..42d3cc5e31b6f5b224c8ca0777fc2535916ac5e5 100644 --- a/lib/compiler/resinator/utils.zig +++ b/lib/compiler/resinator/utils.zig @@ -92,31 +92,32 @@ pub const ErrorMessageType = enum { err, warning, note }; /// Used for generic colored errors/warnings/notes, more context-specific error messages /// are handled elsewhere. -pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, msg_type: ErrorMessageType, comptime format: []const u8, args: anytype) !void { +pub fn renderErrorMessage(t: Io.Terminal, msg_type: ErrorMessageType, comptime format: []const u8, args: anytype) !void { + const writer = t.writer; switch (msg_type) { .err => { - try config.setColor(writer, .bold); - try config.setColor(writer, .red); + try t.setColor(.bold); + try t.setColor(.red); try writer.writeAll("error: "); }, .warning => { - try config.setColor(writer, .bold); - try config.setColor(writer, .yellow); + try t.setColor(.bold); + try t.setColor(.yellow); try writer.writeAll("warning: "); }, .note => { - try config.setColor(writer, .reset); - try config.setColor(writer, .cyan); + try t.setColor(.reset); + try t.setColor(.cyan); try writer.writeAll("note: "); }, } - try config.setColor(writer, .reset); + try t.setColor(.reset); if (msg_type == .err) { - try config.setColor(writer, .bold); + try t.setColor(.bold); } try writer.print(format, args); try writer.writeByte('\n'); - try config.setColor(writer, .reset); + try t.setColor(.reset); } pub fn isLineEndingPair(first: u8, second: u8) bool { -- 2.54.0 From 669dae140c63b1bf4dbae6634145529333cd8371 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 23:24:18 -0800 Subject: [PATCH 154/195] test-standalone: fix most compilation errors --- lib/fuzzer.zig | 84 +++++++++---------- test/standalone/child_process/child.zig | 12 +-- test/standalone/child_process/main.zig | 4 +- test/standalone/dirname/exists_in.zig | 2 +- test/standalone/dirname/touch.zig | 2 +- .../install_headers/check_exists.zig | 6 +- test/standalone/run_output_caching/main.zig | 2 +- .../run_output_paths/create_file.zig | 4 +- test/standalone/self_exe_symlink/main.zig | 5 +- tools/dump-cov.zig | 8 +- tools/fetch_them_macos_headers.zig | 15 ++-- tools/gen_macos_headers_c.zig | 12 +-- tools/gen_outline_atomics.zig | 7 +- tools/gen_spirv_spec.zig | 34 ++++---- tools/gen_stubs.zig | 17 ++-- tools/generate_JSONTestSuite.zig | 13 ++- tools/generate_c_size_and_align_checks.zig | 3 +- tools/generate_linux_syscalls.zig | 6 +- tools/migrate_langref.zig | 14 ++-- tools/process_headers.zig | 10 +-- tools/update-linux-headers.zig | 2 +- tools/update_clang_options.zig | 11 ++- tools/update_cpu_features.zig | 5 +- tools/update_crc_catalog.zig | 4 +- tools/update_freebsd_libc.zig | 17 ++-- tools/update_glibc.zig | 19 ++--- tools/update_mingw.zig | 24 +++--- tools/update_netbsd_libc.zig | 8 +- 28 files changed, 181 insertions(+), 169 deletions(-) diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index 3a48360bf834cc5851d9e72ccaea44e9d7fa59ae..a2414744ca824c6f1a31ffea8e4575b4d6d13a95 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -1,18 +1,22 @@ const builtin = @import("builtin"); +const native_endian = builtin.cpu.arch.endian(); + const std = @import("std"); +const Io = std.Io; const fatal = std.process.fatal; const mem = std.mem; const math = std.math; -const Allocator = mem.Allocator; +const Allocator = std.mem.Allocator; const assert = std.debug.assert; const panic = std.debug.panic; const abi = std.Build.abi.fuzz; -const native_endian = builtin.cpu.arch.endian(); pub const std_options = std.Options{ .logFn = logOverride, }; +const io = std.Io.Threaded.global_single_threaded.ioBasic(); + fn logOverride( comptime level: std.log.Level, comptime scope: @EnumLiteral(), @@ -21,12 +25,12 @@ fn logOverride( ) void { const f = log_f orelse panic("attempt to use log before initialization, message:\n" ++ format, args); - f.lock(.exclusive) catch |e| panic("failed to lock logging file: {t}", .{e}); - defer f.unlock(); + f.lock(io, .exclusive) catch |e| panic("failed to lock logging file: {t}", .{e}); + defer f.unlock(io); var buf: [256]u8 = undefined; - var fw = f.writer(&buf); - const end = f.getEndPos() catch |e| panic("failed to get fuzzer log file end: {t}", .{e}); + var fw = f.writer(io, &buf); + const end = f.length(io) catch |e| panic("failed to get fuzzer log file end: {t}", .{e}); fw.seekTo(end) catch |e| panic("failed to seek to fuzzer log file end: {t}", .{e}); const prefix1 = comptime level.asText(); @@ -45,7 +49,7 @@ const gpa = switch (builtin.mode) { }; /// Part of `exec`, however seperate to allow it to be set before `exec` is. -var log_f: ?std.fs.File = null; +var log_f: ?Io.File = null; var exec: Executable = .preinit; var inst: Instrumentation = .preinit; var fuzzer: Fuzzer = undefined; @@ -59,7 +63,7 @@ const Executable = struct { /// Tracks the hit count for each pc as updated by the process's instrumentation. pc_counters: []u8, - cache_f: std.fs.Dir, + cache_f: Io.Dir, /// Shared copy of all pcs that have been hit stored in a memory-mapped file that can viewed /// while the fuzzer is running. shared_seen_pcs: MemoryMappedList, @@ -76,16 +80,16 @@ const Executable = struct { .pc_digest = undefined, }; - fn getCoverageFile(cache_dir: std.fs.Dir, pcs: []const usize, pc_digest: u64) MemoryMappedList { + fn getCoverageFile(cache_dir: Io.Dir, pcs: []const usize, pc_digest: u64) MemoryMappedList { const pc_bitset_usizes = bitsetUsizes(pcs.len); const coverage_file_name = std.fmt.hex(pc_digest); comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize); comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr); - var v = cache_dir.makeOpenPath("v", .{}) catch |e| + var v = cache_dir.createDirPathOpen(io, "v", .{}) catch |e| panic("failed to create directory 'v': {t}", .{e}); - defer v.close(); - const coverage_file, const populate = if (v.createFile(&coverage_file_name, .{ + defer v.close(io); + const coverage_file, const populate = if (v.createFile(io, &coverage_file_name, .{ .read = true, // If we create the file, we want to block other processes while we populate it .lock = .exclusive, @@ -93,7 +97,7 @@ const Executable = struct { })) |f| .{ f, true } else |e| switch (e) { - error.PathAlreadyExists => .{ v.openFile(&coverage_file_name, .{ + error.PathAlreadyExists => .{ v.openFile(io, &coverage_file_name, .{ .mode = .read_write, .lock = .shared, }) catch |e2| panic( @@ -108,7 +112,7 @@ const Executable = struct { pcs.len * @sizeOf(usize); if (populate) { - defer coverage_file.lock(.shared) catch |e| panic( + defer coverage_file.lock(io, .shared) catch |e| panic( "failed to demote lock for coverage file '{s}': {t}", .{ &coverage_file_name, e }, ); @@ -130,10 +134,8 @@ const Executable = struct { } return map; } else { - const size = coverage_file.getEndPos() catch |e| panic( - "failed to stat coverage file '{s}': {t}", - .{ &coverage_file_name, e }, - ); + const size = coverage_file.length(io) catch |e| + panic("failed to stat coverage file '{s}': {t}", .{ &coverage_file_name, e }); if (size != coverage_file_len) panic( "incompatible existing coverage file '{s}' (differing lengths: {} != {})", .{ &coverage_file_name, size, coverage_file_len }, @@ -165,13 +167,11 @@ const Executable = struct { pub fn init(cache_dir_path: []const u8) Executable { var self: Executable = undefined; - const cache_dir = std.fs.cwd().makeOpenPath(cache_dir_path, .{}) catch |e| panic( - "failed to open directory '{s}': {t}", - .{ cache_dir_path, e }, - ); - log_f = cache_dir.createFile("tmp/libfuzzer.log", .{ .truncate = false }) catch |e| + const cache_dir = Io.Dir.cwd().createDirPathOpen(io, cache_dir_path, .{}) catch |e| + panic("failed to open directory '{s}': {t}", .{ cache_dir_path, e }); + log_f = cache_dir.createFile(io, "tmp/libfuzzer.log", .{ .truncate = false }) catch |e| panic("failed to create file 'tmp/libfuzzer.log': {t}", .{e}); - self.cache_f = cache_dir.makeOpenPath("f", .{}) catch |e| + self.cache_f = cache_dir.createDirPathOpen(io, "f", .{}) catch |e| panic("failed to open directory 'f': {t}", .{e}); // Linkers are expected to automatically add symbols prefixed with these for the start and @@ -391,7 +391,7 @@ const Fuzzer = struct { mutations: std.ArrayList(Mutation) = .empty, /// Filesystem directory containing found inputs for future runs - corpus_dir: std.fs.Dir, + corpus_dir: Io.Dir, corpus_dir_idx: usize = 0, pub fn init(test_one: abi.TestOne, unit_test_name: []const u8) Fuzzer { @@ -405,10 +405,10 @@ const Fuzzer = struct { }; const arena = self.arena_ctx.allocator(); - self.corpus_dir = exec.cache_f.makeOpenPath(unit_test_name, .{}) catch |e| + self.corpus_dir = exec.cache_f.createDirPathOpen(io, unit_test_name, .{}) catch |e| panic("failed to open directory '{s}': {t}", .{ unit_test_name, e }); self.input = in: { - const f = self.corpus_dir.createFile("in", .{ + const f = self.corpus_dir.createFile(io, "in", .{ .read = true, .truncate = false, // In case any other fuzz tests are running under the same test name, @@ -419,7 +419,7 @@ const Fuzzer = struct { error.WouldBlock => @panic("input file 'in' is in use by another fuzzing process"), else => panic("failed to create input file 'in': {t}", .{e}), }; - const size = f.getEndPos() catch |e| panic("failed to stat input file 'in': {t}", .{e}); + const size = f.length(io) catch |e| panic("failed to stat input file 'in': {t}", .{e}); const map = (if (size < std.heap.page_size_max) MemoryMappedList.create(f, 8, std.heap.page_size_max) else @@ -445,6 +445,7 @@ const Fuzzer = struct { while (true) { var name_buf: [@sizeOf(usize) * 2]u8 = undefined; const bytes = self.corpus_dir.readFileAlloc( + io, std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable, arena, .unlimited, @@ -466,7 +467,7 @@ const Fuzzer = struct { self.input.deinit(); self.corpus.deinit(gpa); self.mutations.deinit(gpa); - self.corpus_dir.close(); + self.corpus_dir.close(io); self.arena_ctx.deinit(); self.* = undefined; } @@ -573,17 +574,10 @@ const Fuzzer = struct { // Write new corpus to cache var name_buf: [@sizeOf(usize) * 2]u8 = undefined; - self.corpus_dir.writeFile(.{ - .sub_path = std.fmt.bufPrint( - &name_buf, - "{x}", - .{self.corpus_dir_idx}, - ) catch unreachable, + self.corpus_dir.writeFile(io, .{ + .sub_path = std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable, .data = bytes, - }) catch |e| panic( - "failed to write corpus file '{x}': {t}", - .{ self.corpus_dir_idx, e }, - ); + }) catch |e| panic("failed to write corpus file '{x}': {t}", .{ self.corpus_dir_idx, e }); self.corpus_dir_idx += 1; } } @@ -1320,9 +1314,9 @@ pub const MemoryMappedList = struct { /// How many bytes this list can hold without allocating additional memory. capacity: usize, /// The file is kept open so that it can be resized. - file: std.fs.File, + file: Io.File, - pub fn init(file: std.fs.File, length: usize, capacity: usize) !MemoryMappedList { + pub fn init(file: Io.File, length: usize, capacity: usize) !MemoryMappedList { const ptr = try std.posix.mmap( null, capacity, @@ -1338,13 +1332,13 @@ pub const MemoryMappedList = struct { }; } - pub fn create(file: std.fs.File, length: usize, capacity: usize) !MemoryMappedList { - try file.setEndPos(capacity); + pub fn create(file: Io.File, length: usize, capacity: usize) !MemoryMappedList { + try file.setLength(io, capacity); return init(file, length, capacity); } pub fn deinit(l: *MemoryMappedList) void { - l.file.close(); + l.file.close(io); std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity])); l.* = undefined; } @@ -1369,7 +1363,7 @@ pub const MemoryMappedList = struct { if (l.capacity >= new_capacity) return; std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity])); - try l.file.setEndPos(new_capacity); + try l.file.setLength(io, new_capacity); l.* = try init(l.file, l.items.len, new_capacity); } diff --git a/test/standalone/child_process/child.zig b/test/standalone/child_process/child.zig index 1bd97bace6716d845adfeb9612e9e0c0c941fc15..80e2edaa7f3bed3af38b9961fb364ec5dd0d422c 100644 --- a/test/standalone/child_process/child.zig +++ b/test/standalone/child_process/child.zig @@ -26,14 +26,14 @@ fn run(allocator: std.mem.Allocator, io: Io) !void { const hello_arg = "hello arg"; const a1 = args.next() orelse unreachable; if (!std.mem.eql(u8, a1, hello_arg)) { - testError("first arg: '{s}'; want '{s}'", .{ a1, hello_arg }); + testError(io, "first arg: '{s}'; want '{s}'", .{ a1, hello_arg }); } if (args.next()) |a2| { - testError("expected only one arg; got more: {s}", .{a2}); + testError(io, "expected only one arg; got more: {s}", .{a2}); } // test stdout pipe; parent verifies - try std.Io.File.stdout().writeAll("hello from stdout"); + try std.Io.File.stdout().writeStreamingAll(io, "hello from stdout"); // test stdin pipe from parent const hello_stdin = "hello from stdin"; @@ -42,12 +42,12 @@ fn run(allocator: std.mem.Allocator, io: Io) !void { var reader = stdin.reader(io, &.{}); const n = try reader.interface.readSliceShort(&buf); if (!std.mem.eql(u8, buf[0..n], hello_stdin)) { - testError("stdin: '{s}'; want '{s}'", .{ buf[0..n], hello_stdin }); + testError(io, "stdin: '{s}'; want '{s}'", .{ buf[0..n], hello_stdin }); } } -fn testError(comptime fmt: []const u8, args: anytype) void { - var stderr_writer = std.Io.File.stderr().writer(&.{}); +fn testError(io: Io, comptime fmt: []const u8, args: anytype) void { + var stderr_writer = std.Io.File.stderr().writer(io, &.{}); const stderr = &stderr_writer.interface; stderr.print("CHILD TEST ERROR: ", .{}) catch {}; stderr.print(fmt, args) catch {}; diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index 372ef01ce829c1f3c17e82d71741a1bd3ae812b1..98d38bdee3677184a554fcbe01961b0e930e9865 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -31,7 +31,7 @@ pub fn main() !void { child.stderr_behavior = .Inherit; try child.spawn(io); const child_stdin = child.stdin.?; - try child_stdin.writeAll("hello from stdin"); // verified in child + try child_stdin.writeStreamingAll(io, "hello from stdin"); // verified in child child_stdin.close(io); child.stdin = null; @@ -43,7 +43,7 @@ pub fn main() !void { testError(io, "child stdout: '{s}'; want '{s}'", .{ buf[0..n], hello_stdout }); } - switch (try child.wait()) { + switch (try child.wait(io)) { .Exited => |code| { const child_ok_code = 42; // set by child if no test errors if (code != child_ok_code) { diff --git a/test/standalone/dirname/exists_in.zig b/test/standalone/dirname/exists_in.zig index b321f46c55407cf46d8b12fbe6cc4b8c2323e1cf..ba2de2777fe2c05fb3d6c61e366a127543337915 100644 --- a/test/standalone/dirname/exists_in.zig +++ b/test/standalone/dirname/exists_in.zig @@ -39,5 +39,5 @@ fn run(allocator: std.mem.Allocator) !void { var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{}); defer dir.close(io); - _ = try dir.statFile(io, relpath); + _ = try dir.statFile(io, relpath, .{}); } diff --git a/test/standalone/dirname/touch.zig b/test/standalone/dirname/touch.zig index d7f9b71553a9e1cab81f40f13e118fb30636b6a8..134d53d2fc75ae6919ac091125e7c9c92968a133 100644 --- a/test/standalone/dirname/touch.zig +++ b/test/standalone/dirname/touch.zig @@ -34,7 +34,7 @@ fn run(allocator: std.mem.Allocator) !void { var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{}); defer dir.close(io); - _ = dir.statFile(io, basename) catch { + _ = dir.statFile(io, basename, .{}) catch { var file = try dir.createFile(io, basename, .{}); file.close(io); }; diff --git a/test/standalone/install_headers/check_exists.zig b/test/standalone/install_headers/check_exists.zig index 22638cf1676080941c3b8b1a451d0c76e50306b8..50ad4d08188c5d333411f2c7720b72695c74a40f 100644 --- a/test/standalone/install_headers/check_exists.zig +++ b/test/standalone/install_headers/check_exists.zig @@ -14,7 +14,7 @@ pub fn main() !void { const io = std.Io.Threaded.global_single_threaded.ioBasic(); const cwd = std.Io.Dir.cwd(); - const cwd_realpath = try cwd.realPathAlloc(io, arena, "."); + const cwd_realpath = try cwd.realPathFileAlloc(io, ".", arena); while (arg_it.next()) |file_path| { if (file_path.len > 0 and file_path[0] == '!') { @@ -22,7 +22,7 @@ pub fn main() !void { "exclusive file check '{s}{c}{s}' failed", .{ cwd_realpath, std.fs.path.sep, file_path[1..] }, ); - if (cwd.statFile(io, file_path[1..])) |_| { + if (cwd.statFile(io, file_path[1..], .{})) |_| { return error.FileFound; } else |err| switch (err) { error.FileNotFound => {}, @@ -33,7 +33,7 @@ pub fn main() !void { "inclusive file check '{s}{c}{s}' failed", .{ cwd_realpath, std.fs.path.sep, file_path }, ); - _ = try cwd.statFile(io, file_path); + _ = try cwd.statFile(io, file_path, .{}); } } } diff --git a/test/standalone/run_output_caching/main.zig b/test/standalone/run_output_caching/main.zig index 66e64beda2eeb7e51811b213551e4ddeb02d41aa..9786101d325ebf6098dc4416e47257279febdf05 100644 --- a/test/standalone/run_output_caching/main.zig +++ b/test/standalone/run_output_caching/main.zig @@ -7,5 +7,5 @@ pub fn main() !void { const filename = args.next().?; const file = try std.Io.Dir.cwd().createFile(io, filename, .{}); defer file.close(io); - try file.writeAll(io, filename); + try file.writeStreamingAll(io, filename); } diff --git a/test/standalone/run_output_paths/create_file.zig b/test/standalone/run_output_paths/create_file.zig index 27f741ecaba37aed73f9b7ebe659219b1f034d17..7efa8e051a29d0f0e380fc092827859090fa4b5b 100644 --- a/test/standalone/run_output_paths/create_file.zig +++ b/test/standalone/run_output_paths/create_file.zig @@ -10,8 +10,8 @@ pub fn main() !void { else dir_name, .{}); const file_name = args.next().?; - const file = try dir.createFile(file_name, .{}); - var file_writer = file.writer(&.{}); + const file = try dir.createFile(io, file_name, .{}); + var file_writer = file.writer(io, &.{}); try file_writer.interface.print( \\{s} \\{s} diff --git a/test/standalone/self_exe_symlink/main.zig b/test/standalone/self_exe_symlink/main.zig index 0f9455e3a43fd452dc55db80b3e8d70022710a1f..fa2c3380b580548d01bf00ba9274a0e7db46cdd3 100644 --- a/test/standalone/self_exe_symlink/main.zig +++ b/test/standalone/self_exe_symlink/main.zig @@ -12,10 +12,11 @@ pub fn main() !void { const self_path = try std.process.executablePathAlloc(io, gpa); defer gpa.free(self_path); - var self_exe = try std.fs.openSelfExe(.{}); + var self_exe = try std.process.openExecutable(io, .{}); defer self_exe.close(io); + var buf: [std.fs.max_path_bytes]u8 = undefined; - const self_exe_path = try std.os.getFdPath(self_exe.handle, &buf); + const self_exe_path = buf[0..try self_exe.realPath(io, &buf)]; try std.testing.expectEqualStrings(self_exe_path, self_path); } diff --git a/tools/dump-cov.zig b/tools/dump-cov.zig index 211fe25197b2ef8c91cd27dcc10c00b800bd9159..1a8ebb324e57becbb8a60a42333086e2ce1d529c 100644 --- a/tools/dump-cov.zig +++ b/tools/dump-cov.zig @@ -2,6 +2,7 @@ //! including file:line:column information for each PC. const std = @import("std"); +const Io = std.Io; const fatal = std.process.fatal; const Path = std.Build.Cache.Path; const assert = std.debug.assert; @@ -16,7 +17,7 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var threaded: std.Io.Threaded = .init(gpa, .{}); + var threaded: Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); const io = threaded.io(); @@ -57,6 +58,7 @@ pub fn main() !void { defer debug_info.deinit(gpa); const cov_bytes = cov_path.root_dir.handle.readFileAllocOptions( + io, cov_path.sub_path, arena, .limited(1 << 30), @@ -67,7 +69,7 @@ pub fn main() !void { }; var stdout_buffer: [4000]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout = &stdout_writer.interface; const header: *SeenPcsHeader = @ptrCast(cov_bytes); @@ -83,7 +85,7 @@ pub fn main() !void { std.mem.sortUnstable(usize, sorted_pcs, {}, std.sort.asc(usize)); const source_locations = try arena.alloc(std.debug.Coverage.SourceLocation, sorted_pcs.len); - try debug_info.resolveAddresses(gpa, sorted_pcs, source_locations); + try debug_info.resolveAddresses(gpa, io, sorted_pcs, source_locations); const seen_pcs = header.seenBits(); diff --git a/tools/fetch_them_macos_headers.zig b/tools/fetch_them_macos_headers.zig index 815042e3af3b2092fa64ade895af6f03319f2eb8..ed9b362452fb10ce95ce34a5d465d8a497e6f72b 100644 --- a/tools/fetch_them_macos_headers.zig +++ b/tools/fetch_them_macos_headers.zig @@ -92,7 +92,7 @@ pub fn main() anyerror!void { const sysroot_path = sysroot orelse blk: { const target = try std.zig.system.resolveTargetQuery(io, .{}); - break :blk std.zig.system.darwin.getSdk(allocator, &target) orelse + break :blk std.zig.system.darwin.getSdk(allocator, io, &target) orelse fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{}); }; @@ -166,10 +166,7 @@ fn fetchTarget( }); try cc_argv.appendSlice(args); - const res = try std.process.Child.run(.{ - .allocator = arena, - .argv = cc_argv.items, - }); + const res = try std.process.Child.run(arena, io, .{ .argv = cc_argv.items }); if (res.stderr.len != 0) { std.log.err("{s}", .{res.stderr}); @@ -179,7 +176,7 @@ fn fetchTarget( const headers_list_file = try tmp.dir.openFile(io, headers_list_filename, .{}); defer headers_list_file.close(io); - var headers_dir = Dir.cwd().openDir(headers_source_prefix, .{}) catch |err| switch (err) { + var headers_dir = Dir.cwd().openDir(io, headers_source_prefix, .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, => fatal("path '{s}' not found or not a directory. Did you accidentally delete it?", .{ @@ -215,15 +212,15 @@ fn fetchTarget( const line_stripped = mem.trim(u8, line, " \\"); const abs_dirname = Dir.path.dirname(line_stripped).?; - var orig_subdir = try Dir.cwd().openDir(abs_dirname, .{}); + var orig_subdir = try Dir.cwd().openDir(io, abs_dirname, .{}); defer orig_subdir.close(io); - try orig_subdir.copyFile(basename, maybe_dir.value_ptr.*, basename, .{}); + try orig_subdir.copyFile(basename, maybe_dir.value_ptr.*, basename, io, .{}); } } var dir_it = dirs.iterator(); - while (dir_it.next(io)) |entry| { + while (dir_it.next()) |entry| { entry.value_ptr.close(io); } } diff --git a/tools/gen_macos_headers_c.zig b/tools/gen_macos_headers_c.zig index 657bda8ab2342850aee37fe348274499b462df9f..95880fe3424b1be2949ba05de16a83918c8b2ee1 100644 --- a/tools/gen_macos_headers_c.zig +++ b/tools/gen_macos_headers_c.zig @@ -38,7 +38,7 @@ pub fn main() anyerror!void { if (positionals.items.len != 1) fatal("expected one positional argument: [dir]", .{}); - var dir = try std.fs.cwd().openDir(io, positionals.items[0], .{ .follow_symlinks = false }); + var dir = try Io.Dir.cwd().openDir(io, positionals.items[0], .{ .follow_symlinks = false }); defer dir.close(io); var paths = std.array_list.Managed([]const u8).init(arena); try findHeaders(arena, io, dir, "", &paths); @@ -53,7 +53,7 @@ pub fn main() anyerror!void { std.mem.sort([]const u8, paths.items, {}, SortFn.lessThan); var buffer: [2000]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writerStreaming(&buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &buffer); const w = &stdout_writer.interface; try w.writeAll("#define _XOPEN_SOURCE\n"); for (paths.items) |path| { @@ -75,18 +75,18 @@ fn findHeaders( paths: *std.array_list.Managed([]const u8), ) anyerror!void { var it = dir.iterate(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { switch (entry.kind) { .directory => { - const path = try std.fs.path.join(arena, &.{ prefix, entry.name }); + const path = try Io.Dir.path.join(arena, &.{ prefix, entry.name }); var subdir = try dir.openDir(io, entry.name, .{ .follow_symlinks = false }); defer subdir.close(io); try findHeaders(arena, io, subdir, path, paths); }, .file, .sym_link => { - const ext = std.fs.path.extension(entry.name); + const ext = Io.Dir.path.extension(entry.name); if (!std.mem.eql(u8, ext, ".h")) continue; - const path = try std.fs.path.join(arena, &.{ prefix, entry.name }); + const path = try Io.Dir.path.join(arena, &.{ prefix, entry.name }); try paths.append(path); }, else => {}, diff --git a/tools/gen_outline_atomics.zig b/tools/gen_outline_atomics.zig index 1dade66610198734b0e5d280518852a4ba85bdbd..4d87e531bd823c3cc2c64ba269f15507ea3b2390 100644 --- a/tools/gen_outline_atomics.zig +++ b/tools/gen_outline_atomics.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; const AtomicOp = enum { @@ -15,10 +16,14 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); + var threaded: std.Io.Threaded = .init(arena, .{}); + defer threaded.deinit(); + const io = threaded.io(); + //const args = try std.process.argsAlloc(arena); var stdout_buffer: [2000]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const w = &stdout_writer.interface; try w.writeAll( diff --git a/tools/gen_spirv_spec.zig b/tools/gen_spirv_spec.zig index 072de6573ec3c8b8a8b03587e7e6bb42977cff2b..e96c98b6c01c776476ca37fe624c7bbb029eb3b6 100644 --- a/tools/gen_spirv_spec.zig +++ b/tools/gen_spirv_spec.zig @@ -1,5 +1,7 @@ const std = @import("std"); +const Io = std.Io; const Allocator = std.mem.Allocator; + const g = @import("spirv/grammar.zig"); const CoreRegistry = g.CoreRegistry; const ExtensionRegistry = g.ExtensionRegistry; @@ -63,24 +65,28 @@ pub fn main() !void { usageAndExit(args[0], 1); } - const json_path = try std.fs.path.join(allocator, &.{ args[1], "include/spirv/unified1/" }); - const dir = try std.fs.cwd().openDir(json_path, .{ .iterate = true }); + var threaded: std.Io.Threaded = .init(allocator, .{}); + defer threaded.deinit(); + const io = threaded.io(); - const core_spec = try readRegistry(CoreRegistry, dir, "spirv.core.grammar.json"); + const json_path = try Io.Dir.path.join(allocator, &.{ args[1], "include/spirv/unified1/" }); + const dir = try Io.Dir.cwd().openDir(io, json_path, .{ .iterate = true }); + + const core_spec = try readRegistry(io, CoreRegistry, dir, "spirv.core.grammar.json"); std.mem.sortUnstable(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt); var exts = std.array_list.Managed(Extension).init(allocator); var it = dir.iterate(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { if (entry.kind != .file) { continue; } - try readExtRegistry(&exts, dir, entry.name); + try readExtRegistry(io, &exts, dir, entry.name); } - try readExtRegistry(&exts, std.fs.cwd(), args[2]); + try readExtRegistry(io, &exts, Io.Dir.cwd(), args[2]); var allocating: std.Io.Writer.Allocating = .init(allocator); defer allocating.deinit(); @@ -91,7 +97,7 @@ pub fn main() !void { var tree = try std.zig.Ast.parse(allocator, output, .zig); if (tree.errors.len != 0) { - try std.zig.printAstErrorsToStderr(allocator, tree, "", .auto); + try std.zig.printAstErrorsToStderr(allocator, io, tree, "", .auto); return; } @@ -103,22 +109,22 @@ pub fn main() !void { try wip_errors.addZirErrorMessages(zir, tree, output, ""); var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(allocator); - error_bundle.renderToStdErr(.{}, .auto); + try error_bundle.renderToStderr(io, .{}, .auto); } const formatted_output = try tree.renderAlloc(allocator); - _ = try std.fs.File.stdout().write(formatted_output); + try Io.File.stdout().writeStreamingAll(io, formatted_output); } -fn readExtRegistry(exts: *std.array_list.Managed(Extension), dir: std.fs.Dir, sub_path: []const u8) !void { - const filename = std.fs.path.basename(sub_path); +fn readExtRegistry(io: Io, exts: *std.array_list.Managed(Extension), dir: Io.Dir, sub_path: []const u8) !void { + const filename = Io.Dir.path.basename(sub_path); if (!std.mem.startsWith(u8, filename, "extinst.")) { return; } std.debug.assert(std.mem.endsWith(u8, filename, ".grammar.json")); const name = filename["extinst.".len .. filename.len - ".grammar.json".len]; - const spec = try readRegistry(ExtensionRegistry, dir, sub_path); + const spec = try readRegistry(io, ExtensionRegistry, dir, sub_path); const set_name = set_names.get(name) orelse { 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 }); } -fn readRegistry(comptime RegistryType: type, dir: std.fs.Dir, path: []const u8) !RegistryType { - const spec = try dir.readFileAlloc(path, allocator, .unlimited); +fn readRegistry(io: Io, comptime RegistryType: type, dir: Io.Dir, path: []const u8) !RegistryType { + const spec = try dir.readFileAlloc(io, path, allocator, .unlimited); // Required for json parsing. // TODO: ALI @setEvalBranchQuota(10000); diff --git a/tools/gen_stubs.zig b/tools/gen_stubs.zig index 51f66d3080274a54ea7bf717408b70c7eea9587c..51e83e1a08c08afe91287c27099a398d079540e8 100644 --- a/tools/gen_stubs.zig +++ b/tools/gen_stubs.zig @@ -55,12 +55,14 @@ // - e.g. find a common previous symbol and put it after that one // - they definitely need to go into the correct section +const builtin = @import("builtin"); +const native_endian = builtin.cpu.arch.endian(); + const std = @import("std"); -const builtin = std.builtin; +const Io = std.Io; const mem = std.mem; const log = std.log; const elf = std.elf; -const native_endian = @import("builtin").cpu.arch.endian(); const Arch = enum { aarch64, @@ -284,10 +286,14 @@ pub fn main() !void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); + var threaded: std.Io.Threaded = .init(arena, .{}); + defer threaded.deinit(); + const io = threaded.io(); + const args = try std.process.argsAlloc(arena); const build_all_path = args[1]; - var build_all_dir = try std.fs.cwd().openDir(build_all_path, .{}); + var build_all_dir = try Io.Dir.cwd().openDir(io, build_all_path, .{}); var sym_table = std.StringArrayHashMap(MultiSym).init(arena); var sections = std.StringArrayHashMap(void).init(arena); @@ -299,6 +305,7 @@ pub fn main() !void { // Read the ELF header. const elf_bytes = build_all_dir.readFileAllocOptions( + io, libc_so_path, arena, .limited(100 * 1024 * 1024), @@ -334,7 +341,7 @@ pub fn main() !void { } var stdout_buffer: [2000]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout = &stdout_writer.interface; try stdout.writeAll( \\#ifdef PTR64 @@ -539,7 +546,7 @@ pub fn main() !void { try stdout.flush(); } -fn parseElf(parse: Parse, comptime is_64: bool, comptime endian: builtin.Endian) !void { +fn parseElf(parse: Parse, comptime is_64: bool, comptime endian: std.builtin.Endian) !void { const arena = parse.arena; const elf_bytes = parse.elf_bytes; const header = parse.header; diff --git a/tools/generate_JSONTestSuite.zig b/tools/generate_JSONTestSuite.zig index 2c6fee5bdd5e95f333c1f803656e3b8457587b05..e445a1badf150b56b490861e625becd8233a494d 100644 --- a/tools/generate_JSONTestSuite.zig +++ b/tools/generate_JSONTestSuite.zig @@ -1,13 +1,18 @@ // zig run this file inside the test_parsing/ directory of this repo: https://github.com/nst/JSONTestSuite const std = @import("std"); +const Io = std.Io; pub fn main() !void { var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; var allocator = gpa.allocator(); + var threaded: std.Io.Threaded = .init(allocator, .{}); + defer threaded.deinit(); + const io = threaded.io(); + var stdout_buffer: [2000]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const output = &stdout_writer.interface; try output.writeAll( \\// This file was generated by _generate_JSONTestSuite.zig @@ -20,9 +25,9 @@ pub fn main() !void { ); var names = std.array_list.Managed([]const u8).init(allocator); - var cwd = try std.fs.cwd().openDir(".", .{ .iterate = true }); + var cwd = try Io.Dir.cwd().openDir(io, ".", .{ .iterate = true }); var it = cwd.iterate(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { try names.append(try allocator.dupe(u8, entry.name)); } std.mem.sort([]const u8, names.items, {}, (struct { @@ -32,7 +37,7 @@ pub fn main() !void { }).lessThan); for (names.items) |name| { - const contents = try std.fs.cwd().readFileAlloc(name, allocator, .limited(250001)); + const contents = try Io.Dir.cwd().readFileAlloc(io, name, allocator, .limited(250001)); try output.writeAll("test "); try writeString(output, name); try output.writeAll(" {\n try "); diff --git a/tools/generate_c_size_and_align_checks.zig b/tools/generate_c_size_and_align_checks.zig index 6f934564066ecc1fd43d6bfb04105644baacd996..833fa50f5c21a044283e245166e285e766d012ee 100644 --- a/tools/generate_c_size_and_align_checks.zig +++ b/tools/generate_c_size_and_align_checks.zig @@ -7,6 +7,7 @@ //! target. const std = @import("std"); +const Io = std.Io; fn cName(ty: std.Target.CType) []const u8 { return switch (ty) { @@ -47,7 +48,7 @@ pub fn main() !void { const target = try std.zig.system.resolveTargetQuery(io, query); var buffer: [2000]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writerStreaming(&buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &buffer); const w = &stdout_writer.interface; inline for (@typeInfo(std.Target.CType).@"enum".fields) |field| { const c_type: std.Target.CType = @enumFromInt(field.value); diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index ebdc7a6e2fff993a01f863444f906b74979f72d6..fd632a6c97bd44eb7f9786da2d3047006d37bc92 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -189,10 +189,10 @@ pub fn main() !void { const linux_path = args[1]; var stdout_buffer: [2048]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout = &stdout_writer.interface; - var linux_dir = try std.fs.cwd().openDir(io, linux_path, .{}); + var linux_dir = try Io.Dir.cwd().openDir(io, linux_path, .{}); defer linux_dir.close(io); // As of 6.11, the largest table is 24195 bytes. @@ -225,7 +225,7 @@ pub fn main() !void { , .{version}); for (architectures, 0..) |arch, i| { - const table = try linux_dir.readFile(switch (arch.table) { + const table = try linux_dir.readFile(io, switch (arch.table) { .generic => "scripts/syscall.tbl", .specific => |f| f, }, buf); diff --git a/tools/migrate_langref.zig b/tools/migrate_langref.zig index 299326d98c7d0ecaaf39d4faa0d508c99761d79a..24ddd5941d3956bd42b59b05c8a7505d290a18c6 100644 --- a/tools/migrate_langref.zig +++ b/tools/migrate_langref.zig @@ -26,15 +26,15 @@ pub fn main() !void { defer threaded.deinit(); const io = threaded.io(); - var in_file = try Dir.cwd().openFile(input_file, .{ .mode = .read_only }); + var in_file = try Dir.cwd().openFile(io, input_file, .{ .mode = .read_only }); defer in_file.close(io); - var out_file = try Dir.cwd().createFile(output_file, .{}); + var out_file = try Dir.cwd().createFile(io, output_file, .{}); defer out_file.close(io); var out_file_buffer: [4096]u8 = undefined; - var out_file_writer = out_file.writer(&out_file_buffer); + var out_file_writer = out_file.writer(io, &out_file_buffer); - var out_dir = try Dir.cwd().openDir(Dir.path.dirname(output_file).?, .{}); + var out_dir = try Dir.cwd().openDir(io, Dir.path.dirname(output_file).?, .{}); defer out_dir.close(io); var in_file_reader = in_file.reader(io, &.{}); @@ -42,7 +42,7 @@ pub fn main() !void { var tokenizer = Tokenizer.init(input_file, input_file_bytes); - try walk(arena, &tokenizer, out_dir, &out_file_writer.interface); + try walk(arena, io, &tokenizer, out_dir, &out_file_writer.interface); try out_file_writer.end(); } @@ -387,12 +387,12 @@ fn walk(arena: Allocator, io: Io, tokenizer: *Tokenizer, out_dir: Dir, w: anytyp const basename = try std.fmt.allocPrint(arena, "{s}.zig", .{name}); - var file = out_dir.createFile(basename, .{ .exclusive = true }) catch |err| { + var file = out_dir.createFile(io, basename, .{ .exclusive = true }) catch |err| { fatal("unable to create file '{s}': {s}", .{ name, @errorName(err) }); }; defer file.close(io); var file_buffer: [1024]u8 = undefined; - var file_writer = file.writer(&file_buffer); + var file_writer = file.writer(io, &file_buffer); const code = &file_writer.interface; const source = tokenizer.buffer[source_token.start..source_token.end]; diff --git a/tools/process_headers.zig b/tools/process_headers.zig index 4229ac5b4b7d3ed3e01b7ecf9fe4c9ec1b9ffd5d..cbd94c6292fffecd177572ee6962328bb169b7a2 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -131,7 +131,7 @@ pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); const allocator = arena.allocator(); - var threaded: Io.Threaded = .init(allocator); + var threaded: Io.Threaded = .init(allocator, .{}); defer threaded.deinit(); const io = threaded.io(); @@ -253,14 +253,14 @@ pub fn main() !void { var dir_it = dir.iterate(); - while (try dir_it.next()) |entry| { + while (try dir_it.next(io)) |entry| { const full_path = try Dir.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name }); switch (entry.kind) { .directory => try dir_stack.append(full_path), .file, .sym_link => { const rel_path = try Dir.path.relative(allocator, target_include_dir, full_path); const max_size = 2 * 1024 * 1024 * 1024; - const raw_bytes = try Dir.cwd().readFileAlloc(full_path, allocator, .limited(max_size)); + const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, allocator, .limited(max_size)); const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t"); total_bytes += raw_bytes.len; const hash = try allocator.alloc(u8, 32); @@ -273,9 +273,7 @@ pub fn main() !void { max_bytes_saved += raw_bytes.len; gop.value_ptr.hit_count += 1; std.debug.print("duplicate: {s} {s} ({B})\n", .{ - libc_dir, - rel_path, - raw_bytes.len, + libc_dir, rel_path, raw_bytes.len, }); } else { gop.value_ptr.* = Contents{ diff --git a/tools/update-linux-headers.zig b/tools/update-linux-headers.zig index c3614e54166bc02e76052979151540c7f2d1a196..5091b18dbe74e88eeec1516beb8e67091672b413 100644 --- a/tools/update-linux-headers.zig +++ b/tools/update-linux-headers.zig @@ -206,7 +206,7 @@ pub fn main() !void { var dir_it = dir.iterate(); - while (try dir_it.next()) |entry| { + while (try dir_it.next(io)) |entry| { const full_path = try Dir.path.join(arena, &[_][]const u8{ full_dir_name, entry.name }); switch (entry.kind) { .directory => try dir_stack.append(full_path), diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index 5f3c7f40b5949cc40402db8a15468dd038e05653..3c7762bab6cda7a8d4b4ec9aa60139c414642393 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -10,7 +10,7 @@ //! would mean that the next parameter specifies the target. const std = @import("std"); -const fs = std.fs; +const Io = std.Io; const assert = std.debug.assert; const json = std.json; @@ -634,8 +634,12 @@ pub fn main() anyerror!void { const allocator = arena.allocator(); const args = try std.process.argsAlloc(allocator); + var threaded: std.Io.Threaded = .init(allocator, .{}); + defer threaded.deinit(); + const io = threaded.io(); + var stdout_buffer: [4000]u8 = undefined; - var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); + var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout = &stdout_writer.interface; if (args.len <= 1) printUsageAndExit(args[0]); @@ -676,8 +680,7 @@ pub fn main() anyerror!void { try std.fmt.allocPrint(allocator, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}), }; - const child_result = try std.process.Child.run(.{ - .allocator = allocator, + const child_result = try std.process.Child.run(allocator, io, .{ .argv = &child_args, .max_output_bytes = 100 * 1024 * 1024, }); diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 95864c586a61b7aa4b2c984d342e885e8c10392b..6880c55d05b5c9b55ca73c5c0b509c59efa926da 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -1994,8 +1994,7 @@ fn processOneTarget(io: Io, job: Job) void { }), }; - const child_result = try std.process.Child.run(.{ - .allocator = arena, + const child_result = try std.process.Child.run(arena, io, .{ .argv = &child_args, .max_output_bytes = 500 * 1024 * 1024, }); @@ -2250,7 +2249,7 @@ fn processOneTarget(io: Io, job: Job) void { defer zig_code_file.close(io); var zig_code_file_buffer: [4096]u8 = undefined; - var zig_code_file_writer = zig_code_file.writer(&zig_code_file_buffer); + var zig_code_file_writer = zig_code_file.writer(io, &zig_code_file_buffer); const w = &zig_code_file_writer.interface; try w.writeAll( diff --git a/tools/update_crc_catalog.zig b/tools/update_crc_catalog.zig index f1448b1278a8563e2b062f33df862ffbbb751021..29856aacf8d1ae2b039b20aca40b9eea698e2d42 100644 --- a/tools/update_crc_catalog.zig +++ b/tools/update_crc_catalog.zig @@ -35,7 +35,7 @@ pub fn main() anyerror!void { var zig_code_file = try hash_target_dir.createFile(io, "crc.zig", .{}); defer zig_code_file.close(io); var zig_code_file_buffer: [4096]u8 = undefined; - var zig_code_file_writer = zig_code_file.writer(&zig_code_file_buffer); + var zig_code_file_writer = zig_code_file.writer(io, &zig_code_file_buffer); const code_writer = &zig_code_file_writer.interface; try code_writer.writeAll( @@ -59,7 +59,7 @@ pub fn main() anyerror!void { var zig_test_file = try crc_target_dir.createFile(io, "test.zig", .{}); defer zig_test_file.close(io); var zig_test_file_buffer: [4096]u8 = undefined; - var zig_test_file_writer = zig_test_file.writer(&zig_test_file_buffer); + var zig_test_file_writer = zig_test_file.writer(io, &zig_test_file_buffer); const test_writer = &zig_test_file_writer.interface; try test_writer.writeAll( diff --git a/tools/update_freebsd_libc.zig b/tools/update_freebsd_libc.zig index 9a3bd3ee78ef311408a65484b8f29eb650c65c30..d50364351f599e7501e0b910b6f532abb34fa351 100644 --- a/tools/update_freebsd_libc.zig +++ b/tools/update_freebsd_libc.zig @@ -27,13 +27,13 @@ pub fn main() !void { const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/freebsd", .{zig_src_path}); - var dest_dir = std.fs.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| { + var dest_dir = Io.Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| { std.log.err("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err }); std.process.exit(1); }; defer dest_dir.close(io); - var freebsd_src_dir = try std.fs.cwd().openDir(freebsd_src_path, .{}); + var freebsd_src_dir = try Io.Dir.cwd().openDir(io, freebsd_src_path, .{}); defer freebsd_src_dir.close(io); // Copy updated files from upstream. @@ -41,7 +41,7 @@ pub fn main() !void { var walker = try dest_dir.walk(arena); defer walker.deinit(); - walk: while (try walker.next()) |entry| { + walk: while (try walker.next(io)) |entry| { if (entry.kind != .file) continue; if (std.mem.startsWith(u8, entry.basename, ".")) continue; for (exempt_files) |p| { @@ -49,15 +49,12 @@ pub fn main() !void { } std.log.info("updating '{s}/{s}' from '{s}/{s}'", .{ - dest_dir_path, entry.path, - freebsd_src_path, entry.path, + dest_dir_path, entry.path, freebsd_src_path, entry.path, }); - freebsd_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| { - std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{ - freebsd_src_path, entry.path, - dest_dir_path, entry.path, - @errorName(err), + freebsd_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| { + std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{ + freebsd_src_path, entry.path, dest_dir_path, entry.path, err, }); if (err == error.FileNotFound) { try dest_dir.deleteFile(io, entry.path); diff --git a/tools/update_glibc.zig b/tools/update_glibc.zig index 1a92bda1dbdd5875d94434c048c6fceeb6f09637..296d677d45e0a60bcb485acfa6ba2a52f1575b31 100644 --- a/tools/update_glibc.zig +++ b/tools/update_glibc.zig @@ -66,7 +66,7 @@ pub fn main() !void { var walker = try dest_dir.walk(arena); defer walker.deinit(); - walk: while (try walker.next()) |entry| { + walk: while (try walker.next(io)) |entry| { if (entry.kind != .file) continue; if (mem.startsWith(u8, entry.basename, ".")) continue; for (exempt_files) |p| { @@ -76,7 +76,7 @@ pub fn main() !void { if (mem.endsWith(u8, entry.path, ext)) continue :walk; } - glibc_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| { + glibc_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| { log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{ glibc_src_path, entry.path, dest_dir_path, entry.path, err, }); @@ -106,7 +106,7 @@ pub fn main() !void { var walker = try include_dir.walk(arena); defer walker.deinit(); - walk: while (try walker.next()) |entry| { + walk: while (try walker.next(io)) |entry| { if (entry.kind != .file) continue; if (mem.startsWith(u8, entry.basename, ".")) continue; for (exempt_files) |p| { @@ -116,23 +116,21 @@ pub fn main() !void { const max_file_size = 10 * 1024 * 1024; const generic_glibc_contents = generic_glibc_dir.readFileAlloc( + io, entry.path, arena, .limited(max_file_size), ) catch |err| switch (err) { error.FileNotFound => continue, - else => |e| fatal("unable to load '{s}/include/{s}': {s}", .{ - generic_glibc_path, entry.path, @errorName(e), - }), + else => |e| fatal("unable to load '{s}/include/{s}': {t}", .{ generic_glibc_path, entry.path, e }), }; const glibc_include_contents = include_dir.readFileAlloc( + io, entry.path, arena, .limited(max_file_size), ) catch |err| { - fatal("unable to load '{s}/include/{s}': {s}", .{ - dest_dir_path, entry.path, @errorName(err), - }); + fatal("unable to load '{s}/include/{s}': {t}", .{ dest_dir_path, entry.path, err }); }; const whitespace = " \r\n\t"; @@ -140,8 +138,7 @@ pub fn main() !void { const glibc_include_trimmed = mem.trim(u8, glibc_include_contents, whitespace); if (mem.eql(u8, generic_glibc_trimmed, glibc_include_trimmed)) { log.warn("same contents: '{s}/include/{s}' and '{s}/include/{s}'", .{ - generic_glibc_path, entry.path, - dest_dir_path, entry.path, + generic_glibc_path, entry.path, dest_dir_path, entry.path, }); } } diff --git a/tools/update_mingw.zig b/tools/update_mingw.zig index d427a41e74c16fbad37f3eb84f3cbe95805aad56..678c3dbdca45564d32cfe76c2a4c6153533878cb 100644 --- a/tools/update_mingw.zig +++ b/tools/update_mingw.zig @@ -26,13 +26,13 @@ pub fn main() !void { // in zig's installation. var dest_crt_dir = Dir.cwd().openDir(io, dest_mingw_crt_path, .{ .iterate = true }) catch |err| { - std.log.err("unable to open directory '{s}': {s}", .{ dest_mingw_crt_path, @errorName(err) }); + std.log.err("unable to open directory '{s}': {t}", .{ dest_mingw_crt_path, err }); std.process.exit(1); }; defer dest_crt_dir.close(io); var src_crt_dir = Dir.cwd().openDir(io, src_mingw_crt_path, .{ .iterate = true }) catch |err| { - std.log.err("unable to open directory '{s}': {s}", .{ src_mingw_crt_path, @errorName(err) }); + std.log.err("unable to open directory '{s}': {t}", .{ src_mingw_crt_path, err }); std.process.exit(1); }; defer src_crt_dir.close(io); @@ -43,10 +43,10 @@ pub fn main() !void { var fail = false; - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { if (entry.kind != .file) continue; - src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, .{}) catch |err| switch (err) { + src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, io, .{}) catch |err| switch (err) { error.FileNotFound => { const keep = for (kept_crt_files) |item| { if (std.mem.eql(u8, entry.path, item)) break true; @@ -94,10 +94,10 @@ pub fn main() !void { var fail = false; - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { if (entry.kind != .file) continue; - src_winpthreads_dir.copyFile(entry.path, dest_winpthreads_dir, entry.path, .{}) catch |err| switch (err) { + src_winpthreads_dir.copyFile(entry.path, dest_winpthreads_dir, entry.path, io, .{}) catch |err| switch (err) { error.FileNotFound => { std.log.warn("deleting {s}", .{entry.path}); try dest_winpthreads_dir.deleteFile(io, entry.path); @@ -120,17 +120,17 @@ pub fn main() !void { var fail = false; - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { switch (entry.kind) { .directory => { switch (entry.depth()) { 1 => if (def_dirs.has(entry.basename)) { - try walker.enter(entry); + try walker.enter(io, entry); continue; }, else => { // The top-level directory was already validated - try walker.enter(entry); + try walker.enter(io, entry); continue; }, } @@ -157,15 +157,15 @@ pub fn main() !void { if (std.mem.endsWith(u8, entry.basename, "_onecore.def")) continue; - src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, .{}) catch |err| { - std.log.err("unable to copy {s}: {s}", .{ entry.path, @errorName(err) }); + src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, io, .{}) catch |err| { + std.log.err("unable to copy {s}: {t}", .{ entry.path, err }); fail = true; }; } if (fail) std.process.exit(1); } - return std.process.cleanExit(); + return std.process.cleanExit(io); } const kept_crt_files = [_][]const u8{ diff --git a/tools/update_netbsd_libc.zig b/tools/update_netbsd_libc.zig index 31f77daf51ec0f0e3fd4b4f95f3df25243851b88..a5eeca35c7f2499affd28221931f0a27e137a03b 100644 --- a/tools/update_netbsd_libc.zig +++ b/tools/update_netbsd_libc.zig @@ -27,13 +27,13 @@ pub fn main() !void { const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/netbsd", .{zig_src_path}); - var dest_dir = std.fs.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| { + var dest_dir = Io.Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| { std.log.err("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err }); std.process.exit(1); }; defer dest_dir.close(io); - var netbsd_src_dir = try std.fs.cwd().openDir(io, netbsd_src_path, .{}); + var netbsd_src_dir = try Io.Dir.cwd().openDir(io, netbsd_src_path, .{}); defer netbsd_src_dir.close(io); // Copy updated files from upstream. @@ -41,7 +41,7 @@ pub fn main() !void { var walker = try dest_dir.walk(arena); defer walker.deinit(); - walk: while (try walker.next()) |entry| { + walk: while (try walker.next(io)) |entry| { if (entry.kind != .file) continue; if (std.mem.startsWith(u8, entry.basename, ".")) continue; for (exempt_files) |p| { @@ -53,7 +53,7 @@ pub fn main() !void { netbsd_src_path, entry.path, }); - netbsd_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| { + netbsd_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| { std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{ netbsd_src_path, entry.path, dest_dir_path, entry.path, err, }); -- 2.54.0 From b45984ec809a09d94f1a982c1bbccc7ab9e2d678 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Dec 2025 23:48:24 -0800 Subject: [PATCH 155/195] test-incremental: respect the wine, qemu, etc build flags --- test/tests.zig | 5 ++++ tools/incr-check.zig | 62 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/test/tests.zig b/test/tests.zig index 2a267bba8dbf50b545ebaa1c82cce708dffabc76..fe3843b0f526bf3b618e8ee10e6f1407359425bd 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2706,6 +2706,11 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void { run.addFileArg(b.path("test/incremental/").path(b, entry.path)); run.addArgs(&.{ "--zig-lib-dir", b.fmt("{f}", .{b.graph.zig_lib_directory}) }); + if (b.enable_qemu) run.addArg("-fqemu"); + if (b.enable_wine) run.addArg("-fwine"); + if (b.enable_wasmtime) run.addArg("-fwasmtime"); + if (b.enable_darling) run.addArg("-fdarling"); + run.addCheck(.{ .expect_term = .{ .Exited = 0 } }); test_step.dependOn(&run.step); diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 15e5d009150242d89cf350071007a20532c39e80..43f0ab9e58f45699b7704dda8b051fd554dc4fc9 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -27,6 +27,10 @@ pub fn main() !void { var debug_dwarf = false; var debug_link = false; var preserve_tmp = false; + var enable_qemu: bool = false; + var enable_wine: bool = false; + var enable_wasmtime: bool = false; + var enable_darling: bool = false; var arg_it = try std.process.argsWithAllocator(arena); _ = arg_it.skip(); @@ -42,6 +46,14 @@ pub fn main() !void { debug_link = true; } else if (std.mem.eql(u8, arg, "--preserve-tmp")) { preserve_tmp = true; + } else if (std.mem.eql(u8, arg, "-fqemu")) { + enable_qemu = true; + } else if (std.mem.eql(u8, arg, "-fwine")) { + enable_wine = true; + } else if (std.mem.eql(u8, arg, "-fwasmtime")) { + enable_wasmtime = true; + } else if (std.mem.eql(u8, arg, "-fdarling")) { + enable_darling = true; } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) { opt_cc_zig = arg_it.next() orelse fatal("expect arg after '--zig-cc-binary'\n{s}", .{usage}); } else { @@ -196,6 +208,10 @@ pub fn main() !void { .allow_stderr = debug_log_verbose, .preserve_tmp_on_fatal = preserve_tmp, .cc_child_args = &cc_child_args, + .enable_qemu = enable_qemu, + .enable_wine = enable_wine, + .enable_wasmtime = enable_wasmtime, + .enable_darling = enable_darling, }; try child.spawn(io); @@ -243,6 +259,11 @@ const Eval = struct { /// The arguments `out.c in.c` must be appended before spawning the subprocess. cc_child_args: *std.ArrayList([]const u8), + enable_qemu: bool, + enable_wine: bool, + enable_wasmtime: bool, + enable_darling: bool, + const StreamEnum = enum { stdout, stderr }; const Poller = Io.Poller(StreamEnum); @@ -439,7 +460,7 @@ const Eval = struct { const io = eval.io; var argv_buf: [2][]const u8 = undefined; - const argv: []const []const u8, const is_foreign: bool = switch (std.zig.system.getExternalExecutor( + const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor( io, &eval.host, &eval.target.resolved, @@ -459,10 +480,41 @@ const Eval = struct { argv_buf[0] = binary_path; break :argv .{ argv_buf[0..1], false }; }, - .qemu, .wine, .wasmtime, .darling => |executor_cmd| argv: { - argv_buf[0] = executor_cmd; - argv_buf[1] = binary_path; - break :argv .{ argv_buf[0..2], true }; + .qemu => |executor_cmd| argv: { + if (eval.enable_qemu) { + argv_buf[0] = executor_cmd; + argv_buf[1] = binary_path; + break :argv .{ argv_buf[0..2], true }; + } else { + continue :sw .bad_os_or_cpu; + } + }, + .wine => |executor_cmd| argv: { + if (eval.enable_wine) { + argv_buf[0] = executor_cmd; + argv_buf[1] = binary_path; + break :argv .{ argv_buf[0..2], true }; + } else { + continue :sw .bad_os_or_cpu; + } + }, + .wasmtime => |executor_cmd| argv: { + if (eval.enable_wasmtime) { + argv_buf[0] = executor_cmd; + argv_buf[1] = binary_path; + break :argv .{ argv_buf[0..2], true }; + } else { + continue :sw .bad_os_or_cpu; + } + }, + .darling => |executor_cmd| argv: { + if (eval.enable_darling) { + argv_buf[0] = executor_cmd; + argv_buf[1] = binary_path; + break :argv .{ argv_buf[0..2], true }; + } else { + continue :sw .bad_os_or_cpu; + } }, }; -- 2.54.0 From 7788fd62330bf4c9b4b03700c27e88b16ebd1f46 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 23 Dec 2025 12:31:11 -0800 Subject: [PATCH 156/195] update test API usage --- test/cases/disable_stack_tracing.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cases/disable_stack_tracing.zig b/test/cases/disable_stack_tracing.zig index 5b394beeeb384fc7578c3f7e36741a82ff59ccc9..aa54b30d47cb2b0d29231863b948b351bb45a1f0 100644 --- a/test/cases/disable_stack_tracing.zig +++ b/test/cases/disable_stack_tracing.zig @@ -5,7 +5,7 @@ pub const std_options: std.Options = .{ pub fn main() !void { var st_buf: [8]usize = undefined; var buf: [1024]u8 = undefined; - var stdout = std.Io.File.stdout().writer(&buf); + var stdout = std.Io.File.stdout().writer(std.Options.debug_io, &buf); const captured_st = try foo(&stdout.interface, &st_buf); try std.debug.writeStackTrace(&captured_st, &stdout.interface, .no_color); -- 2.54.0 From 98e9716c082aa7e134e11ed14db8c2f631fedc8a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 23 Dec 2025 12:41:22 -0800 Subject: [PATCH 157/195] std: fix compilation on NetBSD --- lib/std/Io/Threaded.zig | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 4d26780705d556fc01967f8c91bb2b8d121ba55a..ddabccf00da9bfb9a2d5db2ed390804fcc14212c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -372,7 +372,10 @@ const Thread = struct { }; }, else => if (std.Thread.use_pthreads) { - return pthreads_futex.wait(ptr, expect, timeout_ns); + // TODO integrate the following function being called with robust cancelation. + return pthreads_futex.wait(ptr, expect, timeout_ns) catch |err| switch (err) { + error.Timeout => {}, + }; } else { @compileError("unimplemented: futexWait"); }, @@ -11583,7 +11586,7 @@ const pthreads_futex = struct { // This can be changed with pthread_condattr_setclock, but it's an extension and may not be available everywhere. var ts: c.timespec = undefined; if (timeout) |timeout_ns| { - ts = std.posix.clock_gettime(c.CLOCK.REALTIME) catch unreachable; + ts = std.posix.clock_gettime(c.CLOCK.REALTIME) catch return error.Timeout; ts.sec +|= @as(@TypeOf(ts.sec), @intCast(timeout_ns / std.time.ns_per_s)); ts.nsec += @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s)); @@ -11617,9 +11620,9 @@ const pthreads_futex = struct { self.state = .empty; return error.Timeout; }, - .INVAL => unreachable, // cond, mutex, and potentially ts should all be valid - .PERM => unreachable, // mutex is locked when cond_*wait() functions are called - else => unreachable, + .INVAL => recoverableOsBugDetected(), // cond, mutex, and potentially ts should all be valid + .PERM => recoverableOsBugDetected(), // mutex is locked when cond_*wait() functions are called + else => recoverableOsBugDetected(), } } } @@ -11866,9 +11869,12 @@ const pthreads_futex = struct { } waiter.event.wait(timeout) catch { - // If we fail to cancel after a timeout, it means a wake() thread dequeued us and will wake us up. - // We must wait until the event is set as that's a signal that the wake() thread won't access the waiter memory anymore. - // If we return early without waiting, the waiter on the stack would be invalidated and the wake() thread risks a UAF. + // If we fail to cancel after a timeout, it means a wake() thread + // dequeued us and will wake us up. We must wait until the event is + // set as that's a signal that the wake() thread won't access the + // waiter memory anymore. If we return early without waiting, the + // waiter on the stack would be invalidated and the wake() thread + // risks a UAF. defer if (!canceled) waiter.event.wait(null) catch unreachable; assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); -- 2.54.0 From fa79d346744308250c165519f842fedfc26a1c14 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 23 Dec 2025 22:07:31 -0800 Subject: [PATCH 158/195] std: add changing cur dir back There's a good argument to not have this in the std lib but it's more work to remove it than to leave it in, and this branch is already 20,000+ lines changed. --- lib/std/Io.zig | 1 + lib/std/Io/Threaded.zig | 74 +++++++++++++++++- lib/std/os/windows.zig | 34 --------- lib/std/posix.zig | 21 +----- lib/std/process.zig | 25 +++++++ lib/std/testing.zig | 1 + test/standalone/posix/cwd.zig | 72 ++++++++++++++---- test/standalone/posix/relpaths.zig | 91 ++++++++++++++--------- test/standalone/windows_bat_args/fuzz.zig | 4 +- test/standalone/windows_bat_args/test.zig | 4 +- test/standalone/windows_spawn/main.zig | 6 +- tools/fetch_them_macos_headers.zig | 12 ++- 12 files changed, 229 insertions(+), 116 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 70b6c5bcc3079ff501071fec819b39588685af42..162fedca5ff73168a789d4df044c4197c62fbac5 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -718,6 +718,7 @@ pub const VTable = struct { lockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!LockedStderr, tryLockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!?LockedStderr, unlockStderr: *const fn (?*anyopaque) void, + processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void, now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, sleep: *const fn (?*anyopaque, Timeout) SleepError!void, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ddabccf00da9bfb9a2d5db2ed390804fcc14212c..8d383d1c01d6e5ff272becb37205b082baec23f5 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -648,7 +648,7 @@ pub fn init( .main_thread = .{ .signal_id = Thread.currentSignalId(), .current_closure = null, - .cancel_protection = undefined, + .cancel_protection = .unblocked, }, .argv0 = options.argv0, .environ = options.environ, @@ -689,7 +689,7 @@ pub const init_single_threaded: Threaded = .{ .main_thread = .{ .signal_id = undefined, .current_closure = null, - .cancel_protection = undefined, + .cancel_protection = .unblocked, }, .robust_cancel = .disabled, .argv0 = .{}, @@ -742,7 +742,7 @@ fn worker(t: *Threaded) void { var thread: Thread = .{ .signal_id = Thread.currentSignalId(), .current_closure = null, - .cancel_protection = undefined, + .cancel_protection = .unblocked, }; Thread.current = &thread; @@ -844,6 +844,7 @@ pub fn io(t: *Threaded) Io { .lockStderr = lockStderr, .tryLockStderr = tryLockStderr, .unlockStderr = unlockStderr, + .processSetCurrentDir = processSetCurrentDir, .now = now, .sleep = sleep, @@ -979,6 +980,7 @@ pub fn ioBasic(t: *Threaded) Io { .lockStderr = lockStderr, .tryLockStderr = tryLockStderr, .unlockStderr = unlockStderr, + .processSetCurrentDir = processSetCurrentDir, .now = now, .sleep = sleep, @@ -7370,6 +7372,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex }; defer w.CloseHandle(h_file); + // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); const len = std.unicode.calcWtf8Len(wide_slice); @@ -10796,6 +10799,71 @@ fn unlockStderr(userdata: ?*anyopaque) void { std.process.stderr_thread_mutex.unlock(); } +fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentDirError!void { + if (native_os == .wasi) return error.OperationUnsupported; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + if (is_windows) { + try current_thread.checkCancel(); + var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; + // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks + const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer); + const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong; + try current_thread.checkCancel(); + var nt_name: windows.UNICODE_STRING = .{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(dir_path.ptr), + }; + switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) { + .SUCCESS => return, + .OBJECT_NAME_INVALID => return error.BadPathName, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NO_MEDIA_IN_DEVICE => return error.NoDevice, + .INVALID_PARAMETER => |err| return windows.statusBug(err), + .ACCESS_DENIED => return error.AccessDenied, + .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err), + .NOT_A_DIRECTORY => return error.NotDir, + else => |status| return windows.unexpectedStatus(status), + } + } + + if (dir.handle == posix.AT.FDCWD) return; + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.fchdir(dir.handle))) { + .SUCCESS => return current_thread.endSyscall(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .ACCES => { + current_thread.endSyscall(); + return error.AccessDenied; + }, + .BADF => |err| { + current_thread.endSyscall(); + return errnoBug(err); + }, + .NOTDIR => { + current_thread.endSyscall(); + return error.NotDir; + }, + .IO => { + current_thread.endSyscall(); + return error.FileSystem; + }, + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, + } + } +} + pub const PosixAddress = extern union { any: posix.sockaddr, in: posix.sockaddr.in, diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 3ea48585ffd1c53e01d8f276e88f9f2ac8be2c61..12ec0427a9d288a6ab144f56ecae36aa8d9ca9a8 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2939,40 +2939,6 @@ pub fn WriteFile( return bytes_written; } -pub const SetCurrentDirectoryError = error{ - NameTooLong, - FileNotFound, - NotDir, - AccessDenied, - NoDevice, - BadPathName, - Unexpected, -}; - -pub fn SetCurrentDirectory(path_name: []const u16) SetCurrentDirectoryError!void { - const path_len_bytes = math.cast(u16, path_name.len * 2) orelse return error.NameTooLong; - - var nt_name: UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(path_name.ptr), - }; - - const rc = ntdll.RtlSetCurrentDirectory_U(&nt_name); - switch (rc) { - .SUCCESS => {}, - .OBJECT_NAME_INVALID => return error.BadPathName, - .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, - .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .NO_MEDIA_IN_DEVICE => return error.NoDevice, - .INVALID_PARAMETER => unreachable, - .ACCESS_DENIED => return error.AccessDenied, - .OBJECT_PATH_SYNTAX_BAD => unreachable, - .NOT_A_DIRECTORY => return error.NotDir, - else => return unexpectedStatus(rc), - } -} - pub const GetCurrentDirectoryError = error{ NameTooLong, Unexpected, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 133dfc0293faf42923f3bb41a3d5f87efd88cb41..4bd19acb1ace58f7c30c31789b1e89dfcae7a3a5 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1171,11 +1171,9 @@ pub const ChangeCurDirError = error{ /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { if (native_os == .wasi and !builtin.link_libc) { - @compileError("WASI does not support os.chdir"); + @compileError("unsupported OS"); } else if (native_os == .windows) { - var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; - const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path); - return chdirW(wtf16_dir_path[0..len]); + @compileError("unsupported OS"); } else { const dir_path_c = try toPosixPath(dir_path); return chdirZ(&dir_path_c); @@ -1188,12 +1186,9 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { if (native_os == .windows) { - const dir_path_span = mem.span(dir_path); - var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; - const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path_span); - return chdirW(wtf16_dir_path[0..len]); + @compileError("unsupported OS"); } else if (native_os == .wasi and !builtin.link_libc) { - return chdir(mem.span(dir_path)); + @compileError("unsupported OS"); } switch (errno(system.chdir(dir_path))) { .SUCCESS => return, @@ -1210,14 +1205,6 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { } } -/// Windows-only. Same as `chdir` except the parameter is WTF16 LE encoded. -pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void { - windows.SetCurrentDirectory(dir_path) catch |err| switch (err) { - error.NoDevice => return error.FileSystem, - else => |e| return e, - }; -} - pub const FchdirError = error{ AccessDenied, NotDir, diff --git a/lib/std/process.zig b/lib/std/process.zig index cf751f7a282a763561c3369700bb6a48d877e587..865376d907a71a9ff0ce2e1cb0ab1d91c4ce8121 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -2304,3 +2304,28 @@ pub fn exit(status: u8) noreturn { else => posix.system.exit(status), } } + +pub const SetCurrentDirError = error{ + AccessDenied, + BadPathName, + FileNotFound, + FileSystem, + NameTooLong, + NoDevice, + NotDir, + OperationUnsupported, + UnrecognizedVolume, +} || Io.Cancelable || Io.UnexpectedError; + +/// Changes the current working directory to the open directory handle. +/// Corresponds to "fchdir" in libc. +/// +/// This modifies global process state and can have surprising effects in +/// multithreaded applications. Most applications and especially libraries +/// should not call this function as a general rule, however it can have use +/// cases in, for example, implementing a shell, or child process execution. +/// +/// Calling this function makes code less portable and less reusable. +pub fn setCurrentDir(io: Io, dir: Io.Dir) !void { + return io.vtable.processSetCurrentDir(io.userdata, dir); +} diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 5b5ec852e9b3c5ac062051b62ac39c51119f7893..9519eeb4276a24d51eae3973903a16c83bc88eec 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -629,6 +629,7 @@ pub const TmpDir = struct { }; pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir { + comptime assert(builtin.is_test); var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; std.crypto.random.bytes(&random_bytes); var sub_path: [TmpDir.sub_path_len]u8 = undefined; diff --git a/test/standalone/posix/cwd.zig b/test/standalone/posix/cwd.zig index 43dcc63bfeb23a30f4a07ff3ce1663368cb61401..1bafdfebac73ab9381035f2a02973a33ae32a4cf 100644 --- a/test/standalone/posix/cwd.zig +++ b/test/standalone/posix/cwd.zig @@ -1,6 +1,10 @@ -const std = @import("std"); const builtin = @import("builtin"); +const std = @import("std"); +const Io = std.Io; +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + const path_max = std.fs.max_path_bytes; pub fn main() !void { @@ -9,13 +13,17 @@ pub fn main() !void { return; } - var Allocator = std.heap.DebugAllocator(.{}){}; - const a = Allocator.allocator(); - defer std.debug.assert(Allocator.deinit() == .ok); + var debug_allocator: std.heap.DebugAllocator(.{}) = .{}; + defer assert(debug_allocator.deinit() == .ok); + const gpa = debug_allocator.allocator(); + + var threaded: std.Io.Threaded = .init(gpa, .{}); + defer threaded.deinit(); + const io = threaded.io(); try test_chdir_self(); try test_chdir_absolute(); - try test_chdir_relative(a); + try test_chdir_relative(gpa, io); } // get current working directory and expect it to match given path @@ -46,20 +54,20 @@ fn test_chdir_absolute() !void { try expect_cwd(parent); } -fn test_chdir_relative(a: std.mem.Allocator) !void { - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); +fn test_chdir_relative(gpa: Allocator, io: Io) !void { + var tmp = tmpDir(io, .{}); + defer tmp.cleanup(io); // Use the tmpDir parent_dir as the "base" for the test. Then cd into the child - try tmp.parent_dir.setAsCwd(); + try std.process.setCurrentDir(io, tmp.parent_dir); // Capture base working directory path, to build expected full path var base_cwd_buf: [path_max]u8 = undefined; const base_cwd = try std.posix.getcwd(base_cwd_buf[0..]); const relative_dir_name = &tmp.sub_path; - const expected_path = try std.fs.path.resolve(a, &.{ base_cwd, relative_dir_name }); - defer a.free(expected_path); + const expected_path = try std.fs.path.resolve(gpa, &.{ base_cwd, relative_dir_name }); + defer gpa.free(expected_path); // change current working directory to new test directory try std.posix.chdir(relative_dir_name); @@ -68,8 +76,46 @@ fn test_chdir_relative(a: std.mem.Allocator) !void { const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]); // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase - const resolved_cwd = try std.fs.path.resolve(a, &.{new_cwd}); - defer a.free(resolved_cwd); + const resolved_cwd = try std.fs.path.resolve(gpa, &.{new_cwd}); + defer gpa.free(resolved_cwd); try std.testing.expectEqualStrings(expected_path, resolved_cwd); } + +pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir { + var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; + std.crypto.random.bytes(&random_bytes); + var sub_path: [TmpDir.sub_path_len]u8 = undefined; + _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); + + const cwd = Io.Dir.cwd(); + var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch + @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); + defer cache_dir.close(io); + const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch + @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); + const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch + @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); + + return .{ + .dir = dir, + .parent_dir = parent_dir, + .sub_path = sub_path, + }; +} + +pub const TmpDir = struct { + dir: Io.Dir, + parent_dir: Io.Dir, + sub_path: [sub_path_len]u8, + + const random_bytes_count = 12; + const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); + + pub fn cleanup(self: *TmpDir, io: Io) void { + self.dir.close(io); + self.parent_dir.deleteTree(io, &self.sub_path) catch {}; + self.parent_dir.close(io); + self.* = undefined; + } +}; diff --git a/test/standalone/posix/relpaths.zig b/test/standalone/posix/relpaths.zig index 447285c444d457a1fe1f2f0a1c1f1b696540136f..5a8b4382425eccb25cc7736606ce281454b6a025 100644 --- a/test/standalone/posix/relpaths.zig +++ b/test/standalone/posix/relpaths.zig @@ -14,21 +14,21 @@ pub fn main() !void { const gpa = debug_allocator.allocator(); defer std.debug.assert(debug_allocator.deinit() == .ok); - const io = std.Io.Threaded.global_single_threaded.ioBasic(); + var threaded: std.Io.Threaded = .init(gpa, .{}); + defer threaded.deinit(); + const io = threaded.io(); - // TODO this API isn't supposed to be used outside of unit testing. make it compilation error if used - // outside of unit testing. - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); + var tmp = tmpDir(io, .{}); + defer tmp.cleanup(io); // Want to test relative paths, so cd into the tmpdir for these tests - try tmp.dir.setAsCwd(); + try std.process.setCurrentDir(io, tmp.dir); try test_symlink(gpa, io, tmp); try test_link(io, tmp); } -fn test_symlink(gpa: Allocator, io: Io, tmp: std.testing.TmpDir) !void { +fn test_symlink(gpa: Allocator, io: Io, tmp: TmpDir) !void { const target_name = "symlink-target"; const symlink_name = "symlinker"; @@ -47,32 +47,15 @@ fn test_symlink(gpa: Allocator, io: Io, tmp: std.testing.TmpDir) !void { else => return err, }; } else { - try std.posix.symlink(target_name, symlink_name); + try Io.Dir.cwd().symLink(io, target_name, symlink_name, .{}); } var buffer: [std.fs.max_path_bytes]u8 = undefined; - const given = try std.posix.readlink(symlink_name, buffer[0..]); + const given = buffer[0..try Io.Dir.cwd().readLink(io, symlink_name, &buffer)]; try std.testing.expectEqualStrings(target_name, given); } -fn getLinkInfo(fd: std.posix.fd_t) !struct { std.posix.ino_t, std.posix.nlink_t } { - if (builtin.target.os.tag == .linux) { - const stx = try std.os.linux.wrapped.statx( - fd, - "", - std.posix.AT.EMPTY_PATH, - .{ .INO = true, .NLINK = true }, - ); - std.debug.assert(stx.mask.INO); - std.debug.assert(stx.mask.NLINK); - return .{ stx.ino, stx.nlink }; - } - - const st = try std.posix.fstat(fd); - return .{ st.ino, st.nlink }; -} - -fn test_link(io: Io, tmp: std.testing.TmpDir) !void { +fn test_link(io: Io, tmp: TmpDir) !void { switch (builtin.target.os.tag) { .linux, .illumos => {}, else => return, @@ -84,7 +67,7 @@ fn test_link(io: Io, tmp: std.testing.TmpDir) !void { try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" }); // Test 1: create the relative link from inside tmp - try std.posix.link(target_name, link_name); + try Io.Dir.hardLink(.cwd(), target_name, .cwd(), link_name, io, .{}); // Verify const efd = try tmp.dir.openFile(io, target_name, .{}); @@ -94,16 +77,54 @@ fn test_link(io: Io, tmp: std.testing.TmpDir) !void { defer nfd.close(io); { - const eino, _ = try getLinkInfo(efd.handle); - const nino, const nlink = try getLinkInfo(nfd.handle); - try std.testing.expectEqual(eino, nino); - try std.testing.expectEqual(@as(std.posix.nlink_t, 2), nlink); + const e_stat = try efd.stat(io); + const n_stat = try nfd.stat(io); + try std.testing.expectEqual(e_stat.inode, n_stat.inode); + try std.testing.expectEqual(2, n_stat.nlink); } // Test 2: Remove the link and see the stats update - try std.posix.unlink(link_name); + try Io.Dir.cwd().deleteFile(io, link_name); { - _, const elink = try getLinkInfo(efd.handle); - try std.testing.expectEqual(@as(std.posix.nlink_t, 1), elink); + const e_stat = try efd.stat(io); + try std.testing.expectEqual(1, e_stat.nlink); } } + +pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir { + var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; + std.crypto.random.bytes(&random_bytes); + var sub_path: [TmpDir.sub_path_len]u8 = undefined; + _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); + + const cwd = Io.Dir.cwd(); + var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch + @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); + defer cache_dir.close(io); + const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch + @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); + const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch + @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); + + return .{ + .dir = dir, + .parent_dir = parent_dir, + .sub_path = sub_path, + }; +} + +pub const TmpDir = struct { + dir: Io.Dir, + parent_dir: Io.Dir, + sub_path: [sub_path_len]u8, + + const random_bytes_count = 12; + const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); + + pub fn cleanup(self: *TmpDir, io: Io) void { + self.dir.close(io); + self.parent_dir.deleteTree(io, &self.sub_path) catch {}; + self.parent_dir.close(io); + self.* = undefined; + } +}; diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig index db6043d3e9a41cd7d81496b3feb41e36c2e916fa..b68639dda4df7d2d2f9eb5d325fd640de23e5864 100644 --- a/test/standalone/windows_bat_args/fuzz.zig +++ b/test/standalone/windows_bat_args/fuzz.zig @@ -44,8 +44,8 @@ pub fn main() anyerror!void { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.setAsCwd(); - defer tmp.parent_dir.setAsCwd() catch {}; + try std.process.setCurrentDir(io, tmp.dir); + defer std.process.setCurrentDir(io, tmp.parent_dir) catch {}; // `child_exe_path_orig` might be relative; make it relative to our new cwd. const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig }); diff --git a/test/standalone/windows_bat_args/test.zig b/test/standalone/windows_bat_args/test.zig index 48c32826e5cb381c21904fb52cadf923e3b991c8..73f0ee94534ef0e4745fca5dc665414e317c6c30 100644 --- a/test/standalone/windows_bat_args/test.zig +++ b/test/standalone/windows_bat_args/test.zig @@ -18,8 +18,8 @@ pub fn main() anyerror!void { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.setAsCwd(); - defer tmp.parent_dir.setAsCwd() catch {}; + try std.process.setCurrentDir(io, tmp.dir); + defer std.process.setCurrentDir(io, tmp.parent_dir) catch {}; // `child_exe_path_orig` might be relative; make it relative to our new cwd. const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig }); diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index db771c88eb95617ad5ddabdc6927851d99a9bf5b..f5df4dee25bcc803cb7d4a1a018719ce4c7b6589 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -127,8 +127,8 @@ pub fn main() anyerror!void { try testExecError(error.FileNotFound, gpa, "goodbye"); // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir - try tmp.dir.setAsCwd(); - defer tmp.parent_dir.setAsCwd() catch {}; + try std.process.setCurrentDir(io, tmp.dir); + defer std.process.setCurrentDir(io, tmp.parent_dir) catch {}; const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0); defer gpa.free(something_subdir_abs_path); @@ -191,7 +191,7 @@ pub fn main() anyerror!void { defer subdir_cwd.close(io); try renameExe(tmp.dir, "something/goodbye.exe", "hello.exe"); - try subdir_cwd.setAsCwd(); + try std.process.setCurrentDir(io, subdir_cwd); // clear the PATH again std.debug.assert(windows.kernel32.SetEnvironmentVariableW( diff --git a/tools/fetch_them_macos_headers.zig b/tools/fetch_them_macos_headers.zig index ed9b362452fb10ce95ce34a5d465d8a497e6f72b..c55a569e9fdbac6ceb1ee05f42ce7e84a6f7ab32 100644 --- a/tools/fetch_them_macos_headers.zig +++ b/tools/fetch_them_macos_headers.zig @@ -4,7 +4,6 @@ const Dir = std.Io.Dir; const mem = std.mem; const process = std.process; const assert = std.debug.assert; -const tmpDir = std.testing.tmpDir; const fatal = std.process.fatal; const info = std.log.info; @@ -111,15 +110,14 @@ pub fn main() anyerror!void { const os_ver: OsVer = @enumFromInt(version.major); info("found SDK deployment target macOS {f} aka '{t}'", .{ version, os_ver }); - var tmp = tmpDir(.{}); - defer tmp.cleanup(); + const tmp_dir: Io.Dir = .cwd(); for (&[_]Arch{ .aarch64, .x86_64 }) |arch| { const target: Target = .{ .arch = arch, .os_ver = os_ver, }; - try fetchTarget(allocator, io, argv.items, sysroot_path, target, version, tmp); + try fetchTarget(allocator, io, argv.items, sysroot_path, target, version, tmp_dir); } } @@ -130,11 +128,11 @@ fn fetchTarget( sysroot: []const u8, target: Target, ver: Version, - tmp: std.testing.TmpDir, + tmp_dir: Io.Dir, ) !void { const tmp_filename = "macos-headers"; const headers_list_filename = "macos-headers.o.d"; - const tmp_path = try tmp.dir.realPathFileAlloc(io, ".", arena); + const tmp_path = try tmp_dir.realPathFileAlloc(io, ".", arena); const tmp_file_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename }); const headers_list_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename }); @@ -173,7 +171,7 @@ fn fetchTarget( } // Read in the contents of `macos-headers.o.d` - const headers_list_file = try tmp.dir.openFile(io, headers_list_filename, .{}); + const headers_list_file = try tmp_dir.openFile(io, headers_list_filename, .{}); defer headers_list_file.close(io); var headers_dir = Dir.cwd().openDir(io, headers_source_prefix, .{}) catch |err| switch (err) { -- 2.54.0 From 6dbc95fb1f3150eda3966215942f1d545b6bc2ac Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 23 Dec 2025 22:14:09 -0800 Subject: [PATCH 159/195] update test cases --- test/cases/disable_stack_tracing.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cases/disable_stack_tracing.zig b/test/cases/disable_stack_tracing.zig index aa54b30d47cb2b0d29231863b948b351bb45a1f0..36620130c9a6fb6089e222b0bf7d0a5d7f5f179a 100644 --- a/test/cases/disable_stack_tracing.zig +++ b/test/cases/disable_stack_tracing.zig @@ -8,13 +8,13 @@ pub fn main() !void { var stdout = std.Io.File.stdout().writer(std.Options.debug_io, &buf); const captured_st = try foo(&stdout.interface, &st_buf); - try std.debug.writeStackTrace(&captured_st, &stdout.interface, .no_color); + try std.debug.writeStackTrace(&captured_st, .{ .writer = &stdout.interface, .mode = .no_color }); try stdout.interface.print("stack trace index: {d}\n", .{captured_st.index}); try stdout.interface.flush(); } fn foo(w: *std.Io.Writer, st_buf: []usize) !std.builtin.StackTrace { - try std.debug.writeCurrentStackTrace(.{}, w, .no_color); + try std.debug.writeCurrentStackTrace(.{}, .{ .writer = w, .mode = .no_color }); return std.debug.captureCurrentStackTrace(.{}, st_buf); } -- 2.54.0 From c9a609a61a0ebf195780dfdd84bb8ccead67f61f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 23 Dec 2025 22:14:26 -0800 Subject: [PATCH 160/195] test-cases: disable self-hosted aarch64 backend coverage --- test/src/Cases.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/src/Cases.zig b/test/src/Cases.zig index f72748e5ef20d470f2204e6ea9b977c1e09688e4..230b100b68b02d37d7ecb1ab8fb4886a7e5ad040 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -380,6 +380,12 @@ fn addFromDirInner( // Other backends don't support new liveness format continue; } + + if (backend == .selfhosted and target.cpu.arch == .aarch64) { + // https://codeberg.org/ziglang/zig/pulls/30232#issuecomment-9182045 + continue; + } + if (backend == .selfhosted and target.os.tag == .macos and target.cpu.arch == .x86_64 and builtin.cpu.arch == .aarch64) { -- 2.54.0 From 2708a3c1ac9f63cce10b70c780dd7b1530d9c26b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 23 Dec 2025 22:40:17 -0800 Subject: [PATCH 161/195] fix the guess number game example --- test/standalone/simple/guess_number/main.zig | 21 ++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/test/standalone/simple/guess_number/main.zig b/test/standalone/simple/guess_number/main.zig index 38082aad9e22986b3c2ffd55336ae9ad1c3b2eab..b98d109f211f2d8f26761e6a0726cab4efdfb469 100644 --- a/test/standalone/simple/guess_number/main.zig +++ b/test/standalone/simple/guess_number/main.zig @@ -14,7 +14,10 @@ pub fn main() !void { var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); const out = &stdout_writer.interface; - const stdin: std.Io.File = .stdin(); + + var line_buffer: [20]u8 = undefined; + var stdin_reader: std.Io.File.Reader = .init(.stdin(), io, &line_buffer); + const in = &stdin_reader.interface; try out.writeAll("Welcome to the Guess Number Game in Zig.\n"); @@ -22,13 +25,15 @@ pub fn main() !void { while (true) { try out.writeAll("\nGuess a number between 1 and 100: "); - var line_buf: [20]u8 = undefined; - const amt = try stdin.read(&line_buf); - if (amt == line_buf.len) { - try out.writeAll("Input too long.\n"); - continue; - } - const line = std.mem.trimEnd(u8, line_buf[0..amt], "\r\n"); + const untrimmed_line = in.takeSentinel('\n') catch |err| switch (err) { + error.StreamTooLong => { + try out.writeAll("Line too long.\n"); + _ = try in.discardDelimiterInclusive('\n'); + continue; + }, + else => |e| return e, + }; + const line = std.mem.trimEnd(u8, untrimmed_line, "\r\n"); const guess = std.fmt.parseUnsigned(u8, line, 10) catch { try out.writeAll("Invalid number.\n"); -- 2.54.0 From a901e9241396a11f807bf40bbd548e25f120deab Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 23 Dec 2025 23:31:47 -0800 Subject: [PATCH 162/195] std.Io.Threaded: implement process executable path on darwin --- lib/std/Io/Threaded.zig | 43 +++++++++++++++++++++++++++++------------ lib/std/c/darwin.zig | 2 +- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 8d383d1c01d6e5ff272becb37205b082baec23f5..086139445f737be6e55d594570a9f5f4581ffc11 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7152,7 +7152,30 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name); return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags); }, - else => @panic("TODO implement processExecutableOpen"), + .driverkit, + .ios, + .maccatalyst, + .macos, + .tvos, + .visionos, + .watchos, + => { + // _NSGetExecutablePath() returns a path that might be a symlink to + // the executable. Here it does not matter since we open it. + var symlink_path_buf: [posix.PATH_MAX + 1]u8 = undefined; + var n: u32 = symlink_path_buf.len; + const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &n); + if (rc != 0) return error.NameTooLong; + const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0); + return dirOpenFilePosix(t, .cwd(), symlink_path, flags); + }, + else => { + var buffer: [Dir.max_path_bytes]u8 = undefined; + const n = try processExecutablePath(t, &buffer); + buffer[n] = 0; + const executable_path = buffer[0..n :0]; + return dirOpenFilePosix(t, .cwd(), executable_path, flags); + }, } } @@ -7168,21 +7191,17 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex .visionos, .watchos, => { - // Note that _NSGetExecutablePath() will return "a path" to - // the executable not a "real path" to the executable. - var symlink_path_buf: [posix.PATH_MAX:0]u8 = undefined; - var u32_len: u32 = posix.PATH_MAX + 1; // include the sentinel - const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len); + // _NSGetExecutablePath() returns a path that might be a symlink to + // the executable. + var symlink_path_buf: [posix.PATH_MAX + 1]u8 = undefined; + var n: u32 = symlink_path_buf.len; + const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &n); if (rc != 0) return error.NameTooLong; - - var real_path_buf: [posix.PATH_MAX]u8 = undefined; - const n = Io.Dir.realPathFileAbsolute(ioBasic(t), &symlink_path_buf, &real_path_buf) catch |err| switch (err) { + const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0); + return Io.Dir.realPathFileAbsolute(ioBasic(t), symlink_path, out_buffer) catch |err| switch (err) { error.NetworkNotFound => unreachable, // Windows-only else => |e| return e, }; - if (n > out_buffer.len) return error.NameTooLong; - @memcpy(out_buffer[0..n], real_path_buf[0..n]); - return n; }, .linux, .serenity => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/exe", out_buffer) catch |err| switch (err) { error.UnsupportedReparsePointType => unreachable, // Windows-only diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index f0c4f4c278c43b80bd5c6680e034ca3c514cf30a..d4a7dfd5db13aa2e414d3f2d3dc8a3cb203b5a28 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -349,7 +349,7 @@ pub const VM = struct { pub const exception_type_t = c_int; pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32; -pub extern "c" fn _NSGetExecutablePath(buf: [*:0]u8, bufsize: *u32) c_int; +pub extern "c" fn _NSGetExecutablePath(buf: [*]u8, bufsize: *u32) c_int; pub extern "c" fn _dyld_image_count() u32; pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header; pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize; -- 2.54.0 From ace08ba642361d57268b1f102c371b060a1f9a2c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 23 Dec 2025 23:44:38 -0800 Subject: [PATCH 163/195] std.Io.Threaded: prevent 0-byte file writes these cause EINVAL on darwin and are generally a wasted syscall. and remove a bogus error from File.Writer.Error. --- lib/std/Io/File/Writer.zig | 1 - lib/std/Io/Threaded.zig | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 226b60993f954b7bce70d41c501d35795daacbcf..bf8c0bf289d0313926ad241f60188250635a47dc 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -26,7 +26,6 @@ pub const Error = error{ InputOutput, NoSpaceLeft, DeviceBusy, - InvalidArgument, /// File descriptor does not hold the required rights to write to it. AccessDenied, PermissionDenied, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 086139445f737be6e55d594570a9f5f4581ffc11..3662359b31e47c157d570966cec0da355b551e9a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7449,6 +7449,8 @@ fn fileWritePositional( }, }; + if (iovlen == 0) return 0; + if (native_os == .wasi and !builtin.link_libc) { var n_written: usize = undefined; try current_thread.beginSyscall(); @@ -7502,7 +7504,7 @@ fn fileWritePositional( else => |e| { current_thread.endSyscall(); switch (e) { - .INVAL => return error.InvalidArgument, + .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .AGAIN => return error.WouldBlock, .BADF => return error.NotOpenForWriting, // Usually a race condition. @@ -7569,6 +7571,8 @@ fn fileWriteStreaming( }, }; + if (iovlen == 0) return 0; + if (native_os == .wasi and !builtin.link_libc) { var n_written: usize = undefined; try current_thread.beginSyscall(); @@ -7619,7 +7623,7 @@ fn fileWriteStreaming( else => |e| { current_thread.endSyscall(); switch (e) { - .INVAL => return error.InvalidArgument, + .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .AGAIN => return error.WouldBlock, .BADF => return error.NotOpenForWriting, // Can be a race condition. -- 2.54.0 From 6f4e93f0dd8f23835d90a630abecf3d1ce3a23b1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Dec 2025 00:55:27 -0800 Subject: [PATCH 164/195] std.Io.Threaded: wasi does not support processExecutablePath --- lib/std/Io/Threaded.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 3662359b31e47c157d570966cec0da355b551e9a..38b3950683674531a069a2edf9f35425920bfc7e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7142,6 +7142,7 @@ fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekEr fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.process.OpenExecutableError!File { const t: *Threaded = @ptrCast(@alignCast(userdata)); switch (native_os) { + .wasi => return error.OperationUnsupported, .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags), .windows => { // If ImagePathName is a symlink, then it will contain the path of the symlink, -- 2.54.0 From 02e560d9ef7885f39e401d9e30ba2c5119ea1891 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Dec 2025 11:37:51 -0800 Subject: [PATCH 165/195] build.zig: adjust max_rss for unit tests this isn't meant to be a precisely measured thing that is different per operating system. it's meant to have some room for growth but not too much. --- build.zig | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/build.zig b/build.zig index 7ea515de3e8e98221d7f8b8086bd7b66609d55eb..a9c38f33497caacbb5d2ffe30f751c5121c22a1b 100644 --- a/build.zig +++ b/build.zig @@ -651,30 +651,7 @@ pub fn build(b: *std.Build) !void { .use_llvm = use_llvm, .use_lld = use_llvm, .zig_lib_dir = b.path("lib"), - .max_rss = switch (b.graph.host.result.os.tag) { - .freebsd => switch (b.graph.host.result.cpu.arch) { - .x86_64 => 2_188_099_584, - else => 2_200_000_000, - }, - .linux => switch (b.graph.host.result.cpu.arch) { - .aarch64 => 1_991_934_771, - .loongarch64 => 1_844_538_572, - .powerpc64le => 1_793_035_059, - .riscv64 => 2_459_003_289, - .s390x => 1_781_248_409, - .x86_64 => 977_192_550, - else => 2_500_000_000, - }, - .macos => switch (b.graph.host.result.cpu.arch) { - .aarch64 => 2_062_393_344, - else => 2_100_000_000, - }, - .windows => switch (b.graph.host.result.cpu.arch) { - .x86_64 => 1_953_087_488, - else => 2_000_000_000, - }, - else => 2_500_000_000, - }, + .max_rss = 2_500_000_000, }); if (link_libc) { unit_tests.root_module.link_libc = true; -- 2.54.0 From 315bc10a0cbdb6e1cfa5c7786d7f128e19d2cd39 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Dec 2025 12:57:18 -0800 Subject: [PATCH 166/195] std.Io.Threaded: dirRealPathFilePosix use libc realpath sometimes When linking libc, and the directory is the cwd. This avoids bugs on FreeBSD. --- lib/std/Io/Threaded.zig | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 38b3950683674531a069a2edf9f35425920bfc7e..5db5afa77450a21b434d9497955e035f61c398e2 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -4085,6 +4085,37 @@ fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, o var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + if (builtin.link_libc and dir.handle == posix.AT.FDCWD) { + if (out_buffer.len < posix.PATH_MAX) return error.NameTooLong; + try current_thread.beginSyscall(); + while (true) { + if (std.c.realpath(sub_path_posix, out_buffer.ptr)) |redundant_pointer| { + current_thread.endSyscall(); + assert(redundant_pointer == out_buffer.ptr); + return std.mem.indexOfScalar(u8, out_buffer, 0) orelse out_buffer.len; + } + const err: posix.E = @enumFromInt(std.c._errno().*); + if (err == .INTR) { + try current_thread.checkCancel(); + continue; + } + current_thread.endSyscall(); + switch (err) { + .INVAL => return errnoBug(err), + .BADF => return errnoBug(err), + .FAULT => return errnoBug(err), + .ACCES => return error.AccessDenied, + .NOENT => return error.FileNotFound, + .OPNOTSUPP => return error.OperationUnsupported, + .NOTDIR => return error.NotDir, + .NAMETOOLONG => return error.NameTooLong, + .LOOP => return error.SymLinkLoop, + .IO => return error.InputOutput, + else => return posix.unexpectedErrno(err), + } + } + } + var flags: posix.O = .{}; if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true; if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true; -- 2.54.0 From a72c411fdbafc206115c4f9eb19216d504c36a31 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Dec 2025 13:07:11 -0800 Subject: [PATCH 167/195] disable self-hosted aarch64 tests These can be re-enabled when the assertion failures in the backend are fixed, which does not need to block this branch. --- test/tests.zig | 70 ++++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/test/tests.zig b/test/tests.zig index fe3843b0f526bf3b618e8ee10e6f1407359425bd..5b68fc68802ccebc06601564039aaf7e205a80a4 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -187,29 +187,30 @@ const test_targets = blk: { .link_libc = true, }, - .{ - .target = .{ - .cpu_arch = .aarch64, - .os_tag = .linux, - .abi = .none, - }, - .use_llvm = false, - .use_lld = false, - .optimize_mode = .ReleaseFast, - .strip = true, - }, - .{ - .target = .{ - .cpu_arch = .aarch64, - .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.neoverse_n1 }, - .os_tag = .linux, - .abi = .none, - }, - .use_llvm = false, - .use_lld = false, - .optimize_mode = .ReleaseFast, - .strip = true, - }, + // Disabled due to https://codeberg.org/ziglang/zig/pulls/30232#issuecomment-9203351 + //.{ + // .target = .{ + // .cpu_arch = .aarch64, + // .os_tag = .linux, + // .abi = .none, + // }, + // .use_llvm = false, + // .use_lld = false, + // .optimize_mode = .ReleaseFast, + // .strip = true, + //}, + //.{ + // .target = .{ + // .cpu_arch = .aarch64, + // .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.neoverse_n1 }, + // .os_tag = .linux, + // .abi = .none, + // }, + // .use_llvm = false, + // .use_lld = false, + // .optimize_mode = .ReleaseFast, + // .strip = true, + //}, .{ .target = .{ @@ -1204,17 +1205,18 @@ const test_targets = blk: { }, }, - .{ - .target = .{ - .cpu_arch = .aarch64, - .os_tag = .macos, - .abi = .none, - }, - .use_llvm = false, - .use_lld = false, - .optimize_mode = .ReleaseFast, - .strip = true, - }, + // Disabled due to https://codeberg.org/ziglang/zig/pulls/30232#issuecomment-9203351 + //.{ + // .target = .{ + // .cpu_arch = .aarch64, + // .os_tag = .macos, + // .abi = .none, + // }, + // .use_llvm = false, + // .use_lld = false, + // .optimize_mode = .ReleaseFast, + // .strip = true, + //}, .{ .target = .{ -- 2.54.0 From b243e8f8cc7ee27dfa7318d1c3698d01582e84ae Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Dec 2025 13:31:29 -0800 Subject: [PATCH 168/195] std: integrate DebugAllocator with terminal mode by adding a new std.Option for log.terminalMode this is an alternative to the approach that was deleted in aa57793b680b3da05f1d888b4df15807905e57c8 --- lib/std/heap/debug_allocator.zig | 72 +++++++++++++++++++++++++------- lib/std/log.zig | 8 ++++ lib/std/std.zig | 2 + 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/lib/std/heap/debug_allocator.zig b/lib/std/heap/debug_allocator.zig index 66ff59a7116dd6915f5f46c0bb883be2134afdbb..24f1554544c635dac055b465e38f873530afe430 100644 --- a/lib/std/heap/debug_allocator.zig +++ b/lib/std/heap/debug_allocator.zig @@ -84,7 +84,7 @@ const builtin = @import("builtin"); const StackTrace = std.builtin.StackTrace; const std = @import("std"); -const log = std.log.scoped(.gpa); +const log = std.log.scoped(.DebugAllocator); const math = std.math; const assert = std.debug.assert; const mem = std.mem; @@ -441,7 +441,11 @@ pub fn DebugAllocator(comptime config: Config) type { const page_addr = @intFromPtr(bucket) & ~(page_size - 1); const addr = page_addr + slot_index * size_class; log.err("memory address 0x{x} leaked: {f}", .{ - addr, std.debug.FormatStackTrace{ .stack_trace = stack_trace }, + addr, + std.debug.FormatStackTrace{ + .stack_trace = stack_trace, + .terminal_mode = std.log.terminalMode(), + }, }); leaks += 1; } @@ -471,7 +475,10 @@ pub fn DebugAllocator(comptime config: Config) type { const stack_trace = large_alloc.getStackTrace(.alloc); log.err("memory address 0x{x} leaked: {f}", .{ @intFromPtr(large_alloc.bytes.ptr), - std.debug.FormatStackTrace{ .stack_trace = stack_trace }, + std.debug.FormatStackTrace{ + .stack_trace = stack_trace, + .terminal_mode = std.log.terminalMode(), + }, }); leaks += 1; } @@ -528,9 +535,18 @@ pub fn DebugAllocator(comptime config: Config) type { var addr_buf: [stack_n]usize = undefined; const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{ - std.debug.FormatStackTrace{ .stack_trace = alloc_stack_trace }, - std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, - std.debug.FormatStackTrace{ .stack_trace = second_free_stack_trace }, + std.debug.FormatStackTrace{ + .stack_trace = alloc_stack_trace, + .terminal_mode = std.log.terminalMode(), + }, + std.debug.FormatStackTrace{ + .stack_trace = free_stack_trace, + .terminal_mode = std.log.terminalMode(), + }, + std.debug.FormatStackTrace{ + .stack_trace = second_free_stack_trace, + .terminal_mode = std.log.terminalMode(), + }, }); } @@ -575,8 +591,14 @@ pub fn DebugAllocator(comptime config: Config) type { log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ entry.value_ptr.bytes.len, old_mem.len, - std.debug.FormatStackTrace{ .stack_trace = entry.value_ptr.getStackTrace(.alloc) }, - std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, + std.debug.FormatStackTrace{ + .stack_trace = entry.value_ptr.getStackTrace(.alloc), + .terminal_mode = std.log.terminalMode(), + }, + std.debug.FormatStackTrace{ + .stack_trace = free_stack_trace, + .terminal_mode = std.log.terminalMode(), + }, }); } @@ -682,8 +704,14 @@ pub fn DebugAllocator(comptime config: Config) type { log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ entry.value_ptr.bytes.len, old_mem.len, - std.debug.FormatStackTrace{ .stack_trace = entry.value_ptr.getStackTrace(.alloc) }, - std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, + std.debug.FormatStackTrace{ + .stack_trace = entry.value_ptr.getStackTrace(.alloc), + .terminal_mode = std.log.terminalMode(), + }, + std.debug.FormatStackTrace{ + .stack_trace = free_stack_trace, + .terminal_mode = std.log.terminalMode(), + }, }); } @@ -910,8 +938,12 @@ pub fn DebugAllocator(comptime config: Config) type { old_memory.len, std.debug.FormatStackTrace{ .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), + .terminal_mode = std.log.terminalMode(), + }, + std.debug.FormatStackTrace{ + .stack_trace = free_stack_trace, + .terminal_mode = std.log.terminalMode(), }, - std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, }); } if (alignment != slot_alignment) { @@ -921,8 +953,12 @@ pub fn DebugAllocator(comptime config: Config) type { alignment.toByteUnits(), std.debug.FormatStackTrace{ .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), + .terminal_mode = std.log.terminalMode(), + }, + std.debug.FormatStackTrace{ + .stack_trace = free_stack_trace, + .terminal_mode = std.log.terminalMode(), }, - std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, }); } } @@ -1011,8 +1047,12 @@ pub fn DebugAllocator(comptime config: Config) type { memory.len, std.debug.FormatStackTrace{ .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), + .terminal_mode = std.log.terminalMode(), + }, + std.debug.FormatStackTrace{ + .stack_trace = free_stack_trace, + .terminal_mode = std.log.terminalMode(), }, - std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, }); } if (alignment != slot_alignment) { @@ -1022,8 +1062,12 @@ pub fn DebugAllocator(comptime config: Config) type { alignment.toByteUnits(), std.debug.FormatStackTrace{ .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc), + .terminal_mode = std.log.terminalMode(), + }, + std.debug.FormatStackTrace{ + .stack_trace = free_stack_trace, + .terminal_mode = std.log.terminalMode(), }, - std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, }); } } diff --git a/lib/std/log.zig b/lib/std/log.zig index 81bfc4ebb6c94d75f58194e0b16bb5f1a21a2c34..df11fe205b4cefce36bc9d666e4d3718416bccf8 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -80,6 +80,14 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool { return @intFromEnum(level) <= @intFromEnum(std.options.log_level); } +pub const terminalMode = std.options.logTerminalMode; + +pub fn defaultTerminalMode() std.Io.Terminal.Mode { + const stderr = std.debug.lockStderr(&.{}).terminal(); + std.debug.unlockStderr(); + return stderr.mode; +} + /// The default implementation for the log function. Custom log functions may /// forward log messages to this function. /// diff --git a/lib/std/std.zig b/lib/std/std.zig index 93d83b4807b32bb3d7550fc1977cd430932c097a..6ec39306ea949d257a572f90fcc917724979f97d 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -129,6 +129,8 @@ pub const Options = struct { args: anytype, ) void = log.defaultLog, + logTerminalMode: fn () Io.Terminal.Mode = log.defaultTerminalMode, + /// Overrides `std.heap.page_size_min`. page_size_min: ?usize = null, /// Overrides `std.heap.page_size_max`. -- 2.54.0 From 66da6773ebd5b903c41972c0c81372d10571254d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Dec 2025 14:36:58 -0800 Subject: [PATCH 169/195] build: make test-incremental respect -Dtest-filter --- build.zig | 2 +- test/tests.zig | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build.zig b/build.zig index a9c38f33497caacbb5d2ffe30f751c5121c22a1b..290d5e0735cf4f058fab90819a60e825d1249a53 100644 --- a/build.zig +++ b/build.zig @@ -743,7 +743,7 @@ pub fn build(b: *std.Build) !void { } const test_incremental_step = b.step("test-incremental", "Run the incremental compilation test cases"); - try tests.addIncrementalTests(b, test_incremental_step); + try tests.addIncrementalTests(b, test_incremental_step, test_filters); if (!skip_test_incremental) test_step.dependOn(test_incremental_step); if (tests.addLibcTests(b, .{ diff --git a/test/tests.zig b/test/tests.zig index 5b68fc68802ccebc06601564039aaf7e205a80a4..551a26fa83119354a6522ef1c21355cb8a8eb4b4 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2682,7 +2682,7 @@ pub fn addDebuggerTests(b: *std.Build, options: DebuggerContext.Options) ?*Step return step; } -pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void { +pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []const []const u8) !void { const io = b.graph.io; const incr_check = b.addExecutable(.{ @@ -2701,6 +2701,10 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void { while (try it.next(io)) |entry| { if (entry.kind != .file) continue; + for (test_filters) |test_filter| { + if (std.mem.indexOf(u8, entry.path, test_filter)) |_| break; + } else if (test_filters.len > 0) continue; + const run = b.addRunArtifact(incr_check); run.setName(b.fmt("incr-check '{s}'", .{entry.basename})); -- 2.54.0 From a7d2cb704596459b89bb85fb59908d384dd9bba3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Dec 2025 15:11:05 -0800 Subject: [PATCH 170/195] test-incremental: ignore .swp files --- test/tests.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/test/tests.zig b/test/tests.zig index 551a26fa83119354a6522ef1c21355cb8a8eb4b4..aa3c018a627ce2558374a0e787372b4d2cf0b194 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2700,6 +2700,7 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons var it = try dir.walk(b.graph.arena); while (try it.next(io)) |entry| { if (entry.kind != .file) continue; + if (std.mem.endsWith(u8, entry.basename, ".swp")) continue; for (test_filters) |test_filter| { if (std.mem.indexOf(u8, entry.path, test_filter)) |_| break; -- 2.54.0 From 3ad9a6a6045aec32b590387eddbc8ba3d598bcfc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Dec 2025 15:22:14 -0800 Subject: [PATCH 171/195] incr-check: more general --debug-log CLI --- tools/incr-check.zig | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 43f0ab9e58f45699b7704dda8b051fd554dc4fc9..7bc956201a312049848bbc9d18a65dda782ff375 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -4,7 +4,7 @@ const Dir = std.Io.Dir; const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; -const usage = "usage: incr-check [--zig-lib-dir lib] [--debug-zcu] [--debug-dwarf] [--debug-link] [--preserve-tmp] [--zig-cc-binary /path/to/zig]"; +const usage = "usage: incr-check [--zig-lib-dir lib] [--debug-log foo] [--preserve-tmp] [--zig-cc-binary /path/to/zig]"; pub fn main() !void { const fatal = std.process.fatal; @@ -23,27 +23,25 @@ pub fn main() !void { var opt_input_file_name: ?[]const u8 = null; var opt_lib_dir: ?[]const u8 = null; var opt_cc_zig: ?[]const u8 = null; - var debug_zcu = false; - var debug_dwarf = false; - var debug_link = false; var preserve_tmp = false; var enable_qemu: bool = false; var enable_wine: bool = false; var enable_wasmtime: bool = false; var enable_darling: bool = false; + var debug_log_args: std.ArrayList([]const u8) = .empty; + var arg_it = try std.process.argsWithAllocator(arena); _ = arg_it.skip(); while (arg_it.next()) |arg| { if (arg.len > 0 and arg[0] == '-') { if (std.mem.eql(u8, arg, "--zig-lib-dir")) { - opt_lib_dir = arg_it.next() orelse fatal("expected arg after '--zig-lib-dir'\n{s}", .{usage}); - } else if (std.mem.eql(u8, arg, "--debug-zcu")) { - debug_zcu = true; - } else if (std.mem.eql(u8, arg, "--debug-dwarf")) { - debug_dwarf = true; - } else if (std.mem.eql(u8, arg, "--debug-link")) { - debug_link = true; + opt_lib_dir = arg_it.next() orelse fatal("expected arg after --zig-lib-dir\n{s}", .{usage}); + } else if (std.mem.eql(u8, arg, "--debug-log")) { + try debug_log_args.append( + arena, + arg_it.next() orelse fatal("expected arg after --debug-log\n{s}", .{usage}), + ); } else if (std.mem.eql(u8, arg, "--preserve-tmp")) { preserve_tmp = true; } else if (std.mem.eql(u8, arg, "-fqemu")) { @@ -55,7 +53,7 @@ pub fn main() !void { } else if (std.mem.eql(u8, arg, "-fdarling")) { enable_darling = true; } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) { - opt_cc_zig = arg_it.next() orelse fatal("expect arg after '--zig-cc-binary'\n{s}", .{usage}); + opt_cc_zig = arg_it.next() orelse fatal("expected arg after --zig-cc-binary\n{s}", .{usage}); } else { fatal("unknown option '{s}'\n{s}", .{ arg, usage }); } @@ -108,7 +106,7 @@ pub fn main() !void { const host = try std.zig.system.resolveTargetQuery(io, .{}); - const debug_log_verbose = debug_zcu or debug_dwarf or debug_link; + const debug_log_verbose = debug_log_args.items.len != 0; for (case.targets) |target| { const target_prog_node = node: { @@ -146,14 +144,8 @@ pub fn main() !void { .llvm => try child_args.appendSlice(arena, &.{ "-fllvm", "-flld" }), .cbe => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }), } - if (debug_zcu) { - try child_args.appendSlice(arena, &.{ "--debug-log", "zcu" }); - } - if (debug_dwarf) { - try child_args.appendSlice(arena, &.{ "--debug-log", "dwarf" }); - } - if (debug_link) { - try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" }); + for (debug_log_args.items) |arg| { + try child_args.appendSlice(arena, &.{ "--debug-log", arg }); } for (case.modules) |mod| { try child_args.appendSlice(arena, &.{ "--dep", mod.name }); -- 2.54.0 From d7bf0dab8243d23e95c49ad9b27f54d21df116f0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Dec 2025 16:20:39 -0800 Subject: [PATCH 172/195] link.Wasm: fix incremental update crash Description of problem: - wasm linker does GC in flush() - it has the mechanism where it tracks the end index of a bunch of ArrayHashMap before flush() and after flush, shrinkRetainingCapacity() them to restore them to pre-flush() state - this includes `functions`, which contains `__divti3` - flush() notices the call to `__divti3` and calls markFunctionImport(), but that function does nothing on a second update because `alive` is already set to `true` so it incorrectly skips adding the intrinsic back to `functions` I tried to remember why I thought it was OK to use this `alive` flag which is state that's not being restored after flush(). If I remember correctly, I was just leaving the code how it was before, with the plan to change the data layout after encountering this exact problem. However, I found a solution that doesn't require changing data layout, and still takes advantage of the 1-bit-per-symbol data layout. --- src/link/Wasm.zig | 28 ++++++++++++++++++++-------- src/link/Wasm/Flush.zig | 11 +++++++---- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e9c76ff40a0d8c809197de186c3e8faea577932b..af800d77d263b0a99a43833494ad90884a2dd260 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -429,7 +429,11 @@ pub const OutputFunctionIndex = enum(u32) { pub fn fromSymbolName(wasm: *const Wasm, name: String) OutputFunctionIndex { if (wasm.flush_buffer.function_imports.getIndex(name)) |i| return @enumFromInt(i); - return fromFunctionIndex(wasm, FunctionIndex.fromSymbolName(wasm, name).?); + return fromFunctionIndex(wasm, FunctionIndex.fromSymbolName(wasm, name) orelse { + if (std.debug.runtime_safety) { + std.debug.panic("function index for symbol not found: {s}", .{name.slice(wasm)}); + } else unreachable; + }); } }; @@ -3534,7 +3538,10 @@ pub fn markFunctionImport( import: *FunctionImport, func_index: FunctionImport.Index, ) link.File.FlushError!void { - if (import.flags.alive) return; + // import.flags.alive might be already true from a previous update. In such + // case, we must still run the logic in this function, in case the item + // being marked was reverted by the `flush` logic that resets the hash + // table watermarks. import.flags.alive = true; const comp = wasm.base.comp; @@ -3554,8 +3561,9 @@ pub fn markFunctionImport( } else { try wasm.function_imports.put(gpa, name, .fromObject(func_index, wasm)); } - } else { - try markFunction(wasm, import.resolution.unpack(wasm).object_function, import.flags.exported); + } else switch (import.resolution.unpack(wasm)) { + .object_function => try markFunction(wasm, import.resolution.unpack(wasm).object_function, import.flags.exported), + else => return, } } @@ -3594,7 +3602,10 @@ fn markGlobalImport( import: *GlobalImport, global_index: GlobalImport.Index, ) link.File.FlushError!void { - if (import.flags.alive) return; + // import.flags.alive might be already true from a previous update. In such + // case, we must still run the logic in this function, in case the item + // being marked was reverted by the `flush` logic that resets the hash + // table watermarks. import.flags.alive = true; const comp = wasm.base.comp; @@ -3624,8 +3635,9 @@ fn markGlobalImport( } else { try wasm.global_imports.put(gpa, name, .fromObject(global_index, wasm)); } - } else { - try markGlobal(wasm, import.resolution.unpack(wasm).object_global, import.flags.exported); + } else switch (import.resolution.unpack(wasm)) { + .object_global => try markGlobal(wasm, import.resolution.unpack(wasm).object_global, import.flags.exported), + else => return, } } @@ -4043,7 +4055,7 @@ pub fn tagNameSymbolIndex(wasm: *Wasm, ip_index: InternPool.Index) Allocator.Err const comp = wasm.base.comp; assert(comp.config.output_mode == .Obj); const gpa = comp.gpa; - const name = try wasm.internStringFmt("__zig_tag_name_{d}", .{@intFromEnum(ip_index)}); + const name = try wasm.internStringFmt("__zig_tag_name_{d}", .{ip_index}); const gop = try wasm.symbol_table.getOrPut(gpa, name); gop.value_ptr.* = {}; return @enumFromInt(gop.index); diff --git a/src/link/Wasm/Flush.zig b/src/link/Wasm/Flush.zig index b189e2e60079160b55f2e741618b6078726e7581..5bd18a1936fc25288c3c8467e6582e0c9c6a24bf 100644 --- a/src/link/Wasm/Flush.zig +++ b/src/link/Wasm/Flush.zig @@ -128,17 +128,20 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { if (comp.zcu) |zcu| { const ip: *const InternPool = &zcu.intern_pool; // No mutations allowed! + log.debug("total MIR instructions: {d}", .{wasm.mir_instructions.len}); + // Detect any intrinsics that were called; they need to have dependencies on the symbols marked. // Likewise detect `@tagName` calls so those functions can be included in the output and synthesized. for (wasm.mir_instructions.items(.tag), wasm.mir_instructions.items(.data)) |tag, *data| switch (tag) { .call_intrinsic => { const symbol_name = try wasm.internString(@tagName(data.intrinsic)); const i: Wasm.FunctionImport.Index = @enumFromInt(wasm.object_function_imports.getIndex(symbol_name) orelse { - return diags.fail("missing compiler runtime intrinsic '{s}' (undefined linker symbol)", .{ - @tagName(data.intrinsic), + return diags.fail("missing compiler runtime intrinsic '{t}' (undefined linker symbol)", .{ + data.intrinsic, }); }); try wasm.markFunctionImport(symbol_name, i.value(wasm), i); + log.debug("markFunctionImport intrinsic {d}={t}", .{ i, data.intrinsic }); }, .call_tag_name => { assert(ip.indexToKey(data.ip_index) == .enum_type); @@ -147,11 +150,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { wasm.tag_name_table_ref_count += 1; const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu); gop.value_ptr.* = .{ .tag_name = .{ - .symbol_name = try wasm.internStringFmt("__zig_tag_name_{d}", .{@intFromEnum(data.ip_index)}), + .symbol_name = try wasm.internStringFmt("__zig_tag_name_{d}", .{data.ip_index}), .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .slice_const_u8_sentinel_0, target), .table_index = @intCast(wasm.tag_name_offs.items.len), } }; - try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {}); const tag_names = ip.loadEnumType(data.ip_index).names; for (tag_names.get(ip)) |tag_name| { const slice = tag_name.toSlice(ip); @@ -159,6 +161,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { try wasm.tag_name_bytes.appendSlice(gpa, slice[0 .. slice.len + 1]); } } + try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {}); }, else => continue, }; -- 2.54.0 From dc1d27a646b37bbdfae9c8bab46df6384921d13a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Dec 2025 17:14:51 -0800 Subject: [PATCH 173/195] std.Io.Threaded: more verbose TODO panic messages --- lib/std/Io/Threaded.zig | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 5db5afa77450a21b434d9497955e035f61c398e2..28ed8454492bdbf22fd10bdeba4e9a87cfc93c62 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3770,7 +3770,7 @@ fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir _ = userdata; _ = dr; _ = buffer; - @panic("TODO"); + @panic("TODO implement dirReadHaiku"); } fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { @@ -5250,7 +5250,7 @@ fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Pe _ = t; _ = dir; _ = permissions; - @panic("TODO"); + @panic("TODO implement dirSetPermissionsWindows"); } fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { @@ -5268,7 +5268,7 @@ fn dirSetFilePermissions( options: Dir.SetFilePermissionsOptions, ) Dir.SetFilePermissionsError!void { if (@sizeOf(Dir.Permissions) == 0) return; - if (is_windows) @panic("TODO"); + if (is_windows) @panic("TODO implement dirSetFilePermissions windows"); const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -5557,7 +5557,7 @@ fn dirSetFileOwner( _ = owner; _ = group; _ = options; - @panic("TODO"); + @panic("TODO implement dirSetFileOwner"); } const fileSync = switch (native_os) { @@ -6012,11 +6012,11 @@ fn dirSetTimestamps( const current_thread = Thread.getCurrent(t); if (is_windows) { - @panic("TODO"); + @panic("TODO implement dirSetTimestamps windows"); } if (native_os == .wasi and !builtin.link_libc) { - @panic("TODO"); + @panic("TODO implement dirSetTimestamps wasi"); } const times: [2]posix.timespec = .{ @@ -6063,11 +6063,11 @@ fn dirSetTimestampsNow( const current_thread = Thread.getCurrent(t); if (is_windows) { - @panic("TODO"); + @panic("TODO implement dirSetTimestampsNow windows"); } if (native_os == .wasi and !builtin.link_libc) { - @panic("TODO"); + @panic("TODO implement dirSetTimestampsNow wasi"); } const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; @@ -6188,7 +6188,7 @@ fn fileSetTimestampsNow(userdata: ?*anyopaque, file: File) File.SetTimestampsErr const current_thread = Thread.getCurrent(t); if (is_windows) { - @panic("TODO"); + @panic("TODO implement fileSetTimestampsNow windows"); } if (native_os == .wasi and !builtin.link_libc) { @@ -6836,7 +6836,7 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8 const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); - if (!have_preadv) @compileError("TODO"); + if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)"); var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; var i: usize = 0; @@ -7448,7 +7448,7 @@ fn fileWritePositional( const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); - if (is_windows) @panic("TODO"); + if (is_windows) @panic("TODO implement fileWritePositional windows"); var iovecs: [max_iovecs_len]posix.iovec_const = undefined; var iovlen: iovlen_t = 0; @@ -7570,7 +7570,7 @@ fn fileWriteStreaming( const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); - if (is_windows) @panic("TODO"); + if (is_windows) @panic("TODO implement fileWriteStreaming windows"); var iovecs: [max_iovecs_len]posix.iovec_const = undefined; var iovlen: iovlen_t = 0; @@ -8085,7 +8085,7 @@ fn netWriteFile( _ = header; _ = file_reader; _ = limit; - @panic("TODO"); + @panic("TODO implement netWriteFile"); } fn netWriteFileUnavailable( -- 2.54.0 From 8d96d7d2193670f25768287accd0858249992395 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 25 Dec 2025 15:29:35 -0800 Subject: [PATCH 174/195] remove test for nonexistent API --- lib/std/posix/test.zig | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index ec8b720bb1787930664143c893bb6e1437926591..64845d15ee7b3b778a427756fb6ccb533243bfd8 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -20,15 +20,6 @@ const expectEqualStrings = std.testing.expectEqualStrings; const expectError = std.testing.expectError; const tmpDir = std.testing.tmpDir; -// https://github.com/ziglang/zig/issues/20288 -test "WTF-8 to WTF-16 conversion buffer overflows" { - if (native_os != .windows) return error.SkipZigTest; - - const input_wtf8 = "\u{10FFFF}" ** 16385; - try expectError(error.NameTooLong, posix.chdir(input_wtf8)); - try expectError(error.NameTooLong, posix.chdirZ(input_wtf8)); -} - test "check WASI CWD" { if (native_os == .wasi) { if (std.options.wasiCwd() != 3) { -- 2.54.0 From 0126e71903fbb71ce0d968034920e2869f60548f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 25 Dec 2025 15:29:55 -0800 Subject: [PATCH 175/195] build.zig: adjust max_rss for zig libc this isn't meant to be a precisely measured thing that is different per operating system. it's meant to have some room for growth but not too much. --- build.zig | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/build.zig b/build.zig index 290d5e0735cf4f058fab90819a60e825d1249a53..f168eef662caa8c76e05af27a9633892e3954c47 100644 --- a/build.zig +++ b/build.zig @@ -565,30 +565,7 @@ pub fn build(b: *std.Build) !void { .skip_llvm = skip_llvm, .skip_libc = true, .no_builtin = true, - .max_rss = switch (b.graph.host.result.os.tag) { - .freebsd => switch (b.graph.host.result.cpu.arch) { - .x86_64 => 557_892_403, - else => 600_000_000, - }, - .linux => switch (b.graph.host.result.cpu.arch) { - .aarch64 => 615_302_758, - .loongarch64 => 598_974_464, - .powerpc64le => 587_845_632, - .riscv64 => 382_786_764, - .s390x => 395_555_635, - .x86_64 => 871_883_161, - else => 900_000_000, - }, - .macos => switch (b.graph.host.result.cpu.arch) { - .aarch64 => 451_389_030, - else => 500_000_000, - }, - .windows => switch (b.graph.host.result.cpu.arch) { - .x86_64 => 367_747_072, - else => 400_000_000, - }, - else => 900_000_000, - }, + .max_rss = 900_000_000, })); test_modules_step.dependOn(tests.addModuleTests(b, .{ -- 2.54.0 From b0691df5ba898c8f6ac2c03e19e312502a2a0cc5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 25 Dec 2025 15:36:21 -0800 Subject: [PATCH 176/195] test_runner: fix simple runner compilation --- lib/compiler/test_runner.zig | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 32b589cec8415f9bd2ad0985a6b8dcc4272c00b7..da3da684d920bd9758406453a19eda086782b676 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -329,6 +329,8 @@ pub fn mainSimple() anyerror!void { else => false, }; + testing.io_instance = .init(testing.allocator, .{}); + var passed: u64 = 0; var skipped: u64 = 0; var failed: u64 = 0; @@ -338,26 +340,26 @@ pub fn mainSimple() anyerror!void { for (builtin.test_functions) |test_fn| { if (enable_write) { - stdout.writeAll(test_fn.name) catch {}; - stdout.writeAll("... ") catch {}; + stdout.writeStreamingAll(runner_threaded_io, test_fn.name) catch {}; + stdout.writeStreamingAll(runner_threaded_io, "... ") catch {}; } if (test_fn.func()) |_| { - if (enable_write) stdout.writeAll("PASS\n") catch {}; + if (enable_write) stdout.writeStreamingAll(runner_threaded_io, "PASS\n") catch {}; } else |err| { if (err != error.SkipZigTest) { - if (enable_write) stdout.writeAll("FAIL\n") catch {}; + if (enable_write) stdout.writeStreamingAll(runner_threaded_io, "FAIL\n") catch {}; failed += 1; if (!enable_write) return err; continue; } - if (enable_write) stdout.writeAll("SKIP\n") catch {}; + if (enable_write) stdout.writeStreamingAll(runner_threaded_io, "SKIP\n") catch {}; skipped += 1; continue; } passed += 1; } if (enable_print) { - var stdout_writer = stdout.writer(&.{}); + var stdout_writer = stdout.writer(runner_threaded_io, &.{}); stdout_writer.interface.print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {}; } if (failed != 0) std.process.exit(1); -- 2.54.0 From 28639bd6d741ca96f384d85bbf40331664197068 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 25 Dec 2025 15:36:35 -0800 Subject: [PATCH 177/195] std: prevent testing.io from use outside tests --- lib/std/testing.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 9519eeb4276a24d51eae3973903a16c83bc88eec..733b54ec19e5abdb5a1e8dc0c4322f7b44fc2955 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -31,7 +31,7 @@ pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{ }; pub var io_instance: Io.Threaded = undefined; -pub const io = io_instance.io(); +pub const io = if (builtin.is_test) io_instance.io() else @compileError("not testing"); /// TODO https://github.com/ziglang/zig/issues/5738 pub var log_level = std.log.Level.warn; -- 2.54.0 From eb0d5b1377089ff6c51ffc6de6963b9897c906e4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 13:26:14 -0800 Subject: [PATCH 178/195] std.testing: use debug Io instance in expectEqualSlices --- lib/std/testing.zig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 733b54ec19e5abdb5a1e8dc0c4322f7b44fc2955..9bb2622c3da3246d7ecaa0841112315af05e6e35 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -368,11 +368,11 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const break :diff_index if (expected.len == actual.len) return else shortest; }; if (!backend_can_print) return error.TestExpectedEqual; - if (io.lockStderr(&.{}, null)) |stderr| { - defer io.unlockStderr(); - const w = &stderr.file_writer.interface; - failEqualSlices(T, expected, actual, diff_index, w, stderr.terminal_mode) catch {}; - } else |_| {} + // Intentionally using the debug Io instance rather than the testing Io instance. + const stderr = std.debug.lockStderr(&.{}); + defer std.debug.unlockStderr(); + const w = &stderr.file_writer.interface; + failEqualSlices(T, expected, actual, diff_index, w, stderr.terminal_mode) catch {}; return error.TestExpectedEqual; } -- 2.54.0 From 0dbcf97551765d3abbaea5fb0b9e94e5e0be1b14 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 14:08:45 -0800 Subject: [PATCH 179/195] std.Io.Threaded: fix resource leak in dirRealPathFilePosix it should unconditionally close the opened file descriptor, not only on error. --- lib/std/Io/Threaded.zig | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 28ed8454492bdbf22fd10bdeba4e9a87cfc93c62..bf1bfc00ef9e8376baf81a92467fd81dbd55a4e8 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -4165,7 +4165,7 @@ fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, o }, } }; - errdefer posix.close(fd); + defer posix.close(fd); return realPathPosix(current_thread, fd, out_buffer); } @@ -4290,16 +4290,17 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File try current_thread.checkCancel(); continue; }, - else => |e| { + .BADF => { current_thread.endSyscall(); - switch (e) { - .BADF => return error.FileNotFound, - else => |err| return posix.unexpectedErrno(err), - } + return error.FileNotFound; + }, + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); }, } } - const len = std.mem.indexOfScalar(u8, &k_file.path, 0) orelse k_file.path.len; + const len = std.mem.findScalar(u8, &k_file.path, 0) orelse k_file.path.len; if (len == 0) return error.NameTooLong; return len; }, -- 2.54.0 From 2e9415d8a0645adbfcce6e9dc7f6bbf6be233b6f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 14:11:30 -0800 Subject: [PATCH 180/195] std.Io.Threaded: fix missing `@memcpy` in realPathPosix for FreeBSD --- lib/std/Io/Threaded.zig | 1 + lib/std/c/freebsd.zig | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index bf1bfc00ef9e8376baf81a92467fd81dbd55a4e8..18ee2c920611fbc000331e41e382b32112fb1127 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -4302,6 +4302,7 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File } const len = std.mem.findScalar(u8, &k_file.path, 0) orelse k_file.path.len; if (len == 0) return error.NameTooLong; + @memcpy(out_buffer[0..len], k_file.path[0..len]); return len; }, else => return error.OperationUnsupported, diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig index 45ad812ed14e6ca43a5d4156e1e055c896546bae..ad67b23a14685bd6fc7fd4ffefa25c3edb7f41e9 100644 --- a/lib/std/c/freebsd.zig +++ b/lib/std/c/freebsd.zig @@ -250,7 +250,7 @@ pub const kinfo_file = extern struct { /// Reserved for future cap_rights _cap_spare: u64, /// Path to file, if any. - path: [PATH_MAX - 1:0]u8, + path: [PATH_MAX]u8, comptime { assert(@sizeOf(@This()) == KINFO_FILE_SIZE); -- 2.54.0 From 6dcf95139118ec7e91222f7e15f5ea07a51c67f3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 14:45:41 -0800 Subject: [PATCH 181/195] std.Io.Threaded: error instead of assert for statx when statx fails to return some of the requested fields --- lib/std/Io/Threaded.zig | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 18ee2c920611fbc000331e41e382b32112fb1127..c5e0415ad16c37e5a9836cc933c101ac759ff55d 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2086,6 +2086,7 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 { switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .{ .SIZE = true }, &statx))) { .SUCCESS => { current_thread.endSyscall(); + if (!statx.mask.SIZE) return error.Unexpected; return statx.size; }, .INTR => { @@ -5431,7 +5432,7 @@ fn fchmodatFallback( switch (sys.errno(sys.statx(path_fd, "", posix.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) { .SUCCESS => { current_thread.endSyscall(); - assert(statx.mask.TYPE); + if (!statx.mask.TYPE) return error.Unexpected; break statx.mode; }, .INTR => { @@ -11112,15 +11113,15 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t { }; } -fn statFromLinux(stx: *const std.os.linux.Statx) File.Stat { - assert(stx.mask.TYPE); - assert(stx.mask.MODE); - assert(stx.mask.ATIME); - assert(stx.mask.MTIME); - assert(stx.mask.CTIME); - assert(stx.mask.INO); - assert(stx.mask.SIZE); - assert(stx.mask.NLINK); +fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat { + if (!stx.mask.TYPE) return error.Unexpected; + if (!stx.mask.MODE) return error.Unexpected; + if (!stx.mask.ATIME) return error.Unexpected; + if (!stx.mask.MTIME) return error.Unexpected; + if (!stx.mask.CTIME) return error.Unexpected; + if (!stx.mask.INO) return error.Unexpected; + if (!stx.mask.SIZE) return error.Unexpected; + if (!stx.mask.NLINK) return error.Unexpected; const atime = stx.atime; const mtime = stx.mtime; const ctime = stx.ctime; -- 2.54.0 From eb29737d5d77b195b1e030e6bcb99ed0e30ede33 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 14:52:14 -0800 Subject: [PATCH 182/195] std.Io.Threaded: more efficient statx mask check --- lib/std/Io/Threaded.zig | 58 ++++++++++++----------------------------- 1 file changed, 17 insertions(+), 41 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c5e0415ad16c37e5a9836cc933c101ac759ff55d..d9bc6e0d1ad75657f47d5748b5a6b9649c7ab58a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1919,23 +1919,7 @@ fn dirStatFileLinux( try current_thread.beginSyscall(); while (true) { var statx = std.mem.zeroes(linux.Statx); - const rc = sys.statx( - dir.handle, - sub_path_posix, - flags, - .{ - .TYPE = true, - .MODE = true, - .ATIME = true, - .MTIME = true, - .CTIME = true, - .INO = true, - .SIZE = true, - .NLINK = true, - }, - &statx, - ); - switch (sys.errno(rc)) { + switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_mask, &statx))) { .SUCCESS => { current_thread.endSyscall(); return statFromLinux(&statx); @@ -2170,22 +2154,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { try current_thread.beginSyscall(); while (true) { var statx = std.mem.zeroes(linux.Statx); - switch (sys.errno(sys.statx( - file.handle, - "", - linux.AT.EMPTY_PATH, - .{ - .TYPE = true, - .MODE = true, - .ATIME = true, - .MTIME = true, - .CTIME = true, - .INO = true, - .SIZE = true, - .NLINK = true, - }, - &statx, - ))) { + switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_mask, &statx))) { .SUCCESS => { current_thread.endSyscall(); return statFromLinux(&statx); @@ -11113,15 +11082,22 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t { }; } +const linux_statx_mask: std.os.linux.STATX = .{ + .TYPE = true, + .MODE = true, + .ATIME = true, + .MTIME = true, + .CTIME = true, + .INO = true, + .SIZE = true, + .NLINK = true, +}; + fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat { - if (!stx.mask.TYPE) return error.Unexpected; - if (!stx.mask.MODE) return error.Unexpected; - if (!stx.mask.ATIME) return error.Unexpected; - if (!stx.mask.MTIME) return error.Unexpected; - if (!stx.mask.CTIME) return error.Unexpected; - if (!stx.mask.INO) return error.Unexpected; - if (!stx.mask.SIZE) return error.Unexpected; - if (!stx.mask.NLINK) return error.Unexpected; + const actual_mask_int: u32 = @bitCast(stx.mask); + const wanted_mask_int: u32 = @bitCast(linux_statx_mask); + if ((actual_mask_int | wanted_mask_int) != actual_mask_int) return error.Unexpected; + const atime = stx.atime; const mtime = stx.mtime; const ctime = stx.ctime; -- 2.54.0 From 58f0c8cfed1c8dd2fb29cf4a1807e6e0d239c006 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 20 Dec 2025 02:01:50 -0800 Subject: [PATCH 183/195] Fix logic for detecting zero buffer capacity in fileReadStreamingWindows/fileReadPositionalWindows --- lib/std/Io/Threaded.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index d9bc6e0d1ad75657f47d5748b5a6b9649c7ab58a..3241c45d5ca804ed59301c4e86a09cd2ead11f5e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -6780,8 +6780,8 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u const DWORD = windows.DWORD; var index: usize = 0; - while (data[index].len == 0) index += 1; - if (index == 0) return 0; + while (index < data.len and data[index].len == 0) index += 1; + if (index == data.len) return 0; const buffer = data[index]; const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); @@ -6912,8 +6912,8 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const [] const DWORD = windows.DWORD; var index: usize = 0; - while (data[index].len == 0) index += 1; - if (index == 0) return 0; + while (index < data.len and data[index].len == 0) index += 1; + if (index == data.len) return 0; const buffer = data[index]; const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); -- 2.54.0 From feeed922e1a7d89b99572d5577c814f9e6293b9b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 16:10:26 -0800 Subject: [PATCH 184/195] std.Io.net: disable flaky test on mipsel --- lib/std/Io/net/test.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig index 23edab5a5d8454298b014cfb3b9ca965c56bc021..6ef8c15f4f3f5c8e144f8943d54a8607671c6697 100644 --- a/lib/std/Io/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -271,6 +271,7 @@ test "listen on a unix socket, send bytes, receive bytes" { if (builtin.single_threaded) return error.SkipZigTest; if (!net.has_unix_sockets) return error.SkipZigTest; if (builtin.os.tag == .windows) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25983 + if (builtin.cpu.arch == .mipsel) return error.SkipZigTest; // TODO const io = testing.io; -- 2.54.0 From f160d5f979c9f2bcc98ef0169a879a76d0234ed2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 16:12:09 -0800 Subject: [PATCH 185/195] std.Io.Threaded: implement file writing for Windows --- lib/std/Io/Threaded.zig | 90 ++++++++++++++++++++++++++++++++- lib/std/os/windows.zig | 109 ---------------------------------------- lib/std/posix.zig | 60 ++-------------------- 3 files changed, 92 insertions(+), 167 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 3241c45d5ca804ed59301c4e86a09cd2ead11f5e..1e37f5636506ae1f2eccaabf698c7bdfefe5eec6 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7420,7 +7420,18 @@ fn fileWritePositional( const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); - if (is_windows) @panic("TODO implement fileWritePositional windows"); + if (is_windows) { + if (header.len != 0) { + return writeFilePositionalWindows(current_thread, file.handle, header, offset); + } + for (data[0 .. data.len - 1]) |buf| { + if (buf.len == 0) continue; + return writeFilePositionalWindows(current_thread, file.handle, buf, offset); + } + const pattern = data[data.len - 1]; + if (pattern.len == 0 or splat == 0) return 0; + return writeFilePositionalWindows(current_thread, file.handle, pattern, offset); + } var iovecs: [max_iovecs_len]posix.iovec_const = undefined; var iovlen: iovlen_t = 0; @@ -7532,6 +7543,44 @@ fn fileWritePositional( } } +fn writeFilePositionalWindows( + current_thread: *Thread, + handle: windows.HANDLE, + bytes: []const u8, + offset: u64, +) File.WritePositionalError!usize { + try current_thread.checkCancel(); + + var bytes_written: windows.DWORD = undefined; + var overlapped: windows.OVERLAPPED = .{ + .Internal = 0, + .InternalHigh = 0, + .DUMMYUNIONNAME = .{ + .DUMMYSTRUCTNAME = .{ + .Offset = @truncate(offset), + .OffsetHigh = @truncate(offset >> 32), + }, + }, + .hEvent = null, + }; + const adjusted_len = std.math.lossyCast(u32, bytes.len); + if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) == 0) { + switch (windows.GetLastError()) { + .INVALID_USER_BUFFER => return error.SystemResources, + .NOT_ENOUGH_MEMORY => return error.SystemResources, + .OPERATION_ABORTED => return error.Canceled, + .NOT_ENOUGH_QUOTA => return error.SystemResources, + .NO_DATA => return error.BrokenPipe, + .INVALID_HANDLE => return error.NotOpenForWriting, + .LOCK_VIOLATION => return error.LockViolation, + .ACCESS_DENIED => return error.AccessDenied, + .WORKING_SET_QUOTA => return error.SystemResources, + else => |err| return windows.unexpectedError(err), + } + } + return bytes_written; +} + fn fileWriteStreaming( userdata: ?*anyopaque, file: File, @@ -7542,7 +7591,18 @@ fn fileWriteStreaming( const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); - if (is_windows) @panic("TODO implement fileWriteStreaming windows"); + if (is_windows) { + if (header.len != 0) { + return writeFileStreamingWindows(current_thread, file.handle, header); + } + for (data[0 .. data.len - 1]) |buf| { + if (buf.len == 0) continue; + return writeFileStreamingWindows(current_thread, file.handle, buf); + } + const pattern = data[data.len - 1]; + if (pattern.len == 0 or splat == 0) return 0; + return writeFileStreamingWindows(current_thread, file.handle, pattern); + } var iovecs: [max_iovecs_len]posix.iovec_const = undefined; var iovlen: iovlen_t = 0; @@ -7647,6 +7707,32 @@ fn fileWriteStreaming( } } +fn writeFileStreamingWindows( + current_thread: *Thread, + handle: windows.HANDLE, + bytes: []const u8, +) File.Writer.Error!usize { + try current_thread.checkCancel(); + + var bytes_written: windows.DWORD = undefined; + const adjusted_len = std.math.lossyCast(u32, bytes.len); + if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) == 0) { + switch (windows.GetLastError()) { + .INVALID_USER_BUFFER => return error.SystemResources, + .NOT_ENOUGH_MEMORY => return error.SystemResources, + .OPERATION_ABORTED => return error.Canceled, + .NOT_ENOUGH_QUOTA => return error.SystemResources, + .NO_DATA => return error.BrokenPipe, + .INVALID_HANDLE => return error.NotOpenForWriting, + .LOCK_VIOLATION => return error.LockViolation, + .ACCESS_DENIED => return error.AccessDenied, + .WORKING_SET_QUOTA => return error.SystemResources, + else => |err| return windows.unexpectedError(err), + } + } + return bytes_written; +} + fn fileWriteFileStreaming( userdata: ?*anyopaque, file: File, diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 12ec0427a9d288a6ab144f56ecae36aa8d9ca9a8..371883e9a4d9bf0705186e23c5cd89cb9f0949eb 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2830,115 +2830,6 @@ pub fn CloseHandle(hObject: HANDLE) void { assert(ntdll.NtClose(hObject) == .SUCCESS); } -pub const ReadFileError = error{ - BrokenPipe, - /// The specified network name is no longer available. - ConnectionResetByPeer, - Canceled, - /// Unable to read file due to lock. - LockViolation, - /// Known to be possible when: - /// - Unable to read from disconnected virtual com port (Windows) - AccessDenied, - NotOpenForReading, - Unexpected, -}; - -/// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into -/// multiple non-atomic reads. -pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize { - while (true) { - const want_read_count: DWORD = @min(@as(DWORD, maxInt(DWORD)), buffer.len); - var amt_read: DWORD = undefined; - var overlapped_data: OVERLAPPED = undefined; - const overlapped: ?*OVERLAPPED = if (offset) |off| blk: { - overlapped_data = .{ - .Internal = 0, - .InternalHigh = 0, - .DUMMYUNIONNAME = .{ - .DUMMYSTRUCTNAME = .{ - .Offset = @as(u32, @truncate(off)), - .OffsetHigh = @as(u32, @truncate(off >> 32)), - }, - }, - .hEvent = null, - }; - break :blk &overlapped_data; - } else null; - if (kernel32.ReadFile(in_hFile, buffer.ptr, want_read_count, &amt_read, overlapped) == 0) { - switch (GetLastError()) { - .IO_PENDING => unreachable, - .OPERATION_ABORTED => continue, - .BROKEN_PIPE => return 0, - .HANDLE_EOF => return 0, - .NETNAME_DELETED => return error.ConnectionResetByPeer, - .LOCK_VIOLATION => return error.LockViolation, - .ACCESS_DENIED => return error.AccessDenied, - .INVALID_HANDLE => return error.NotOpenForReading, - else => |err| return unexpectedError(err), - } - } - return amt_read; - } -} - -pub const WriteFileError = error{ - SystemResources, - Canceled, - BrokenPipe, - NotOpenForWriting, - /// The process cannot access the file because another process has locked - /// a portion of the file. - LockViolation, - /// The specified network name is no longer available. - ConnectionResetByPeer, - /// Known to be possible when: - /// - Unable to write to disconnected virtual com port (Windows) - AccessDenied, - Unexpected, -}; - -pub fn WriteFile( - handle: HANDLE, - bytes: []const u8, - offset: ?u64, -) WriteFileError!usize { - var bytes_written: DWORD = undefined; - var overlapped_data: OVERLAPPED = undefined; - const overlapped: ?*OVERLAPPED = if (offset) |off| blk: { - overlapped_data = .{ - .Internal = 0, - .InternalHigh = 0, - .DUMMYUNIONNAME = .{ - .DUMMYSTRUCTNAME = .{ - .Offset = @truncate(off), - .OffsetHigh = @truncate(off >> 32), - }, - }, - .hEvent = null, - }; - break :blk &overlapped_data; - } else null; - const adjusted_len = math.cast(u32, bytes.len) orelse maxInt(u32); - if (kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, overlapped) == 0) { - switch (GetLastError()) { - .INVALID_USER_BUFFER => return error.SystemResources, - .NOT_ENOUGH_MEMORY => return error.SystemResources, - .OPERATION_ABORTED => return error.Canceled, - .NOT_ENOUGH_QUOTA => return error.SystemResources, - .IO_PENDING => unreachable, - .NO_DATA => return error.BrokenPipe, - .INVALID_HANDLE => return error.NotOpenForWriting, - .LOCK_VIOLATION => return error.LockViolation, - .NETNAME_DELETED => return error.ConnectionResetByPeer, - .ACCESS_DENIED => return error.AccessDenied, - .WORKING_SET_QUOTA => return error.SystemResources, - else => |err| return unexpectedError(err), - } - } - return bytes_written; -} - pub const GetCurrentDirectoryError = error{ NameTooLong, Unexpected, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 4bd19acb1ace58f7c30c31789b1e89dfcae7a3a5..52bc5f83e8b0a37e9b3346d1d6fb70005ef695f4 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -486,34 +486,8 @@ pub const ReadError = std.Io.File.Reader.Error; /// The corresponding POSIX limit is `maxInt(isize)`. pub fn read(fd: fd_t, buf: []u8) ReadError!usize { if (buf.len == 0) return 0; - if (native_os == .windows) { - return windows.ReadFile(fd, buf, null); - } - if (native_os == .wasi and !builtin.link_libc) { - const iovs = [1]iovec{iovec{ - .base = buf.ptr, - .len = buf.len, - }}; - - var nread: usize = undefined; - switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) { - .SUCCESS => return nread, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .BADF => return error.NotOpenForReading, // Can be a race condition. - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } + if (native_os == .windows) @compileError("unsupported OS"); + if (native_os == .wasi) @compileError("unsupported OS"); // Prevents EINVAL. const max_count = switch (native_os) { @@ -606,34 +580,8 @@ pub const WriteError = error{ /// The corresponding POSIX limit is `maxInt(isize)`. pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { if (bytes.len == 0) return 0; - if (native_os == .windows) { - return windows.WriteFile(fd, bytes, null); - } - - if (native_os == .wasi and !builtin.link_libc) { - const ciovs = [_]iovec_const{iovec_const{ - .base = bytes.ptr, - .len = bytes.len, - }}; - var nwritten: usize = undefined; - switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) { - .SUCCESS => return nwritten, - .INTR => unreachable, - .INVAL => unreachable, - .FAULT => unreachable, - .AGAIN => unreachable, - .BADF => return error.NotOpenForWriting, // can be a race condition. - .DESTADDRREQ => unreachable, // `connect` was never called. - .DQUOT => return error.DiskQuota, - .FBIG => return error.FileTooBig, - .IO => return error.InputOutput, - .NOSPC => return error.NoSpaceLeft, - .PERM => return error.PermissionDenied, - .PIPE => return error.BrokenPipe, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } - } + if (native_os == .windows) @compileError("unsupported OS"); + if (native_os == .wasi) @compileError("unsupported OS"); const max_count = switch (native_os) { .linux => 0x7ffff000, -- 2.54.0 From 28810f5e3d41597116773b2eb6686f3d8e272b36 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 16:40:41 -0800 Subject: [PATCH 186/195] std.Io.File: add readStreaming I resisted adding this because it's generally better to create a File.Reader instead, but we do need a simple wrapper around the vtable function, and there are use cases for it such as std.Progress. --- lib/std/Io/File.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index a972ce2fd3e5aadc8dcb7481e0abffe2da3eab81..ee30103af0bee7e04d048da8da6cf66f9aa0f9a8 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -499,6 +499,14 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { return io.vtable.fileSetTimestampsNow(io.userdata, file); } +/// Returns 0 on stream end or if `buffer` has no space available for data. +/// +/// See also: +/// * `reader` +pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize { + return io.vtable.fileReadStreaming(io.userdata, file, buffer); +} + pub const ReadPositionalError = Reader.Error || error{Unseekable}; /// Returns 0 on stream end or if `buffer` has no space available for data. -- 2.54.0 From 252c4e57c68d8ed0c2eecc17dfa5f6c63e672df2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 16:41:16 -0800 Subject: [PATCH 187/195] std.Progress: fix compilation on Windows by using std.Io.File.readStreaming rather than posix.read --- lib/std/Progress.zig | 45 ++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 970cf903829c9aed701e1a7e5f23680adf1c622f..8d21698dac6a6ddc4ce249f7c8d0e51990bbeb92 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -127,20 +127,20 @@ pub const Node = struct { name: [max_name_len]u8 align(@alignOf(usize)), /// Not thread-safe. - fn getIpcFd(s: Storage) ?posix.fd_t { - return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(posix.fd_t)) { + fn getIpcFd(s: Storage) ?Io.File.Handle { + return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) { .int => @bitCast(s.completed_count), .pointer => @ptrFromInt(s.completed_count), - else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)), + else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), } else null; } /// Thread-safe. - fn setIpcFd(s: *Storage, fd: posix.fd_t) void { - const integer: u32 = switch (@typeInfo(posix.fd_t)) { + fn setIpcFd(s: *Storage, fd: Io.File.Handle) void { + const integer: u32 = switch (@typeInfo(Io.File.Handle)) { .int => @bitCast(fd), .pointer => @intFromPtr(fd), - else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)), + else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), }; // `estimated_total_count` max int indicates the special state that // causes `completed_count` to be treated as a file descriptor, so @@ -340,7 +340,7 @@ pub const Node = struct { } /// Posix-only. Used by `std.process.Child`. Thread-safe. - pub fn setIpcFd(node: Node, fd: posix.fd_t) void { + pub fn setIpcFd(node: Node, fd: Io.File.Handle) void { const index = node.index.unwrap() orelse return; assert(fd >= 0); assert(fd != posix.STDOUT_FILENO); @@ -351,14 +351,14 @@ pub const Node = struct { /// Posix-only. Thread-safe. Assumes the node is storing an IPC file /// descriptor. - pub fn getIpcFd(node: Node) ?posix.fd_t { + pub fn getIpcFd(node: Node) ?Io.File.Handle { const index = node.index.unwrap() orelse return null; const storage = storageByIndex(index); const int = @atomicLoad(u32, &storage.completed_count, .monotonic); - return switch (@typeInfo(posix.fd_t)) { + return switch (@typeInfo(Io.File.Handle)) { .int => @bitCast(int), .pointer => @ptrFromInt(int), - else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)), + else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), }; } @@ -479,10 +479,10 @@ pub fn start(io: Io, options: Options) Node { if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| { global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, - @as(Io.File, .{ .handle = switch (@typeInfo(posix.fd_t)) { + @as(Io.File, .{ .handle = switch (@typeInfo(Io.File.Handle)) { .int => ipc_fd, .pointer => @ptrFromInt(ipc_fd), - else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)), + else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), } }), }) catch |err| { global_progress.start_failure = .{ .spawn_ipc_worker = err }; @@ -934,11 +934,11 @@ const SavedMetadata = struct { const Fd = enum(i32) { _, - fn init(fd: posix.fd_t) Fd { + fn init(fd: Io.File.Handle) Fd { return @enumFromInt(if (is_windows) @as(isize, @bitCast(@intFromPtr(fd))) else fd); } - fn get(fd: Fd) posix.fd_t { + fn get(fd: Fd) Io.File.Handle { return if (is_windows) @ptrFromInt(@as(usize, @bitCast(@as(isize, @intFromEnum(fd))))) else @@ -949,6 +949,7 @@ const Fd = enum(i32) { var ipc_metadata_len: u8 = 0; fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize { + const io = global_progress.io; const ipc_metadata_fds_copy = &serialized_buffer.ipc_metadata_fds_copy; const ipc_metadata_copy = &serialized_buffer.ipc_metadata_copy; const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds; @@ -967,11 +968,11 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff 0.., ) |main_parent, *main_storage, main_index| { if (main_parent == .unused) continue; - const fd = main_storage.getIpcFd() orelse continue; - const opt_saved_metadata = findOld(fd, old_ipc_metadata_fds, old_ipc_metadata); + const file: Io.File = .{ .handle = main_storage.getIpcFd() orelse continue }; + const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); var bytes_read: usize = 0; while (true) { - const n = posix.read(fd, pipe_buf[bytes_read..]) catch |err| switch (err) { + const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) { error.WouldBlock => break, else => |e| { std.log.debug("failed to read child progress data: {t}", .{e}); @@ -1000,7 +1001,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff // Ignore all but the last message on the pipe. var input: []u8 = pipe_buf[0..bytes_read]; if (input.len == 0) { - serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, fd); + serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, file.handle); continue; } @@ -1010,7 +1011,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff if (input.len < expected_bytes) { // Ignore short reads. We'll handle the next full message when it comes instead. const remaining_read_trash_bytes: u16 = @intCast(expected_bytes - input.len); - serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, fd); + serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, file.handle); continue :main_loop; } if (input.len > expected_bytes) { @@ -1028,7 +1029,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff const nodes_len: u8 = @intCast(@min(parents.len - 1, serialized_buffer.storage.len - serialized_len)); // Remember in case the pipe is empty on next update. - ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd); + ipc_metadata_fds[ipc_metadata_len] = Fd.init(file.handle); ipc_metadata[ipc_metadata_len] = .{ .remaining_read_trash_bytes = 0, .start_index = @intCast(serialized_len), @@ -1086,7 +1087,7 @@ fn copyRoot(dest: *Node.Storage, src: *align(1) Node.Storage) void { } fn findOld( - ipc_fd: posix.fd_t, + ipc_fd: Io.File.Handle, old_metadata_fds: []Fd, old_metadata: []SavedMetadata, ) ?*SavedMetadata { @@ -1104,7 +1105,7 @@ fn useSavedIpcData( main_index: usize, opt_saved_metadata: ?*SavedMetadata, remaining_read_trash_bytes: u16, - fd: posix.fd_t, + fd: Io.File.Handle, ) usize { const parents_copy = &serialized_buffer.parents_copy; const storage_copy = &serialized_buffer.storage_copy; -- 2.54.0 From 447f80c0cc66f9f214e54f8808372d9fe3e105d1 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 26 Dec 2025 17:12:50 -0800 Subject: [PATCH 188/195] Delete test_symlink in standalone/posix, it's fully redundant See readlink tests in std/fs/test.zig --- test/standalone/posix/relpaths.zig | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/test/standalone/posix/relpaths.zig b/test/standalone/posix/relpaths.zig index 5a8b4382425eccb25cc7736606ce281454b6a025..b5b2bb5f61876a75213f8b734379977e1259d21a 100644 --- a/test/standalone/posix/relpaths.zig +++ b/test/standalone/posix/relpaths.zig @@ -5,7 +5,6 @@ const builtin = @import("builtin"); const std = @import("std"); const Io = std.Io; -const Allocator = std.mem.Allocator; pub fn main() !void { if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir @@ -24,37 +23,9 @@ pub fn main() !void { // Want to test relative paths, so cd into the tmpdir for these tests try std.process.setCurrentDir(io, tmp.dir); - try test_symlink(gpa, io, tmp); try test_link(io, tmp); } -fn test_symlink(gpa: Allocator, io: Io, tmp: TmpDir) !void { - const target_name = "symlink-target"; - const symlink_name = "symlinker"; - - // Create the target file - try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "nonsense" }); - - if (builtin.target.os.tag == .windows) { - const wtarget_name = try std.unicode.wtf8ToWtf16LeAllocZ(gpa, target_name); - const wsymlink_name = try std.unicode.wtf8ToWtf16LeAllocZ(gpa, symlink_name); - defer gpa.free(wtarget_name); - defer gpa.free(wsymlink_name); - - std.os.windows.CreateSymbolicLink(tmp.dir.fd, wsymlink_name, wtarget_name, false) catch |err| switch (err) { - // Symlink requires admin privileges on windows, so this test can legitimately fail. - error.AccessDenied => return, - else => return err, - }; - } else { - try Io.Dir.cwd().symLink(io, target_name, symlink_name, .{}); - } - - var buffer: [std.fs.max_path_bytes]u8 = undefined; - const given = buffer[0..try Io.Dir.cwd().readLink(io, symlink_name, &buffer)]; - try std.testing.expectEqualStrings(target_name, given); -} - fn test_link(io: Io, tmp: TmpDir) !void { switch (builtin.target.os.tag) { .linux, .illumos => {}, -- 2.54.0 From ec5c2d4ce36394b1d675519a824c101ca9a82fa7 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 26 Dec 2025 17:18:12 -0800 Subject: [PATCH 189/195] Skip posix-specific standalone test on Windows --- test/standalone/posix/cwd.zig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/standalone/posix/cwd.zig b/test/standalone/posix/cwd.zig index 1bafdfebac73ab9381035f2a02973a33ae32a4cf..3bd1ac066ae48bf8592227ee05fee20dea78cb36 100644 --- a/test/standalone/posix/cwd.zig +++ b/test/standalone/posix/cwd.zig @@ -8,9 +8,10 @@ const assert = std.debug.assert; const path_max = std.fs.max_path_bytes; pub fn main() !void { - if (builtin.target.os.tag == .wasi) { - // WASI doesn't support changing the working directory at all. - return; + switch (builtin.target.os.tag) { + .wasi => return, // WASI doesn't support changing the working directory at all. + .windows => return, // POSIX is not implemented by Windows + else => {}, } var debug_allocator: std.heap.DebugAllocator(.{}) = .{}; -- 2.54.0 From 4f8aa8213d6314cba7ea21c5b13a5b9acbac3ad7 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 26 Dec 2025 17:23:00 -0800 Subject: [PATCH 190/195] Fix for compile error in resinator --- lib/compiler/resinator/main.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index f311b2a5121b7edc05883d658717a56b8abf5dd8..e286a5f4b9d8047d5a77935567b24d71b9aee69c 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -615,7 +615,7 @@ fn getIncludePaths( }; const target = std.zig.resolveTargetQueryOrFatal(io, target_query); const is_native_abi = target_query.isNativeAbi(); - const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch { + const detected_libc = std.zig.LibCDirs.detect(arena, io, zig_lib_dir, &target, is_native_abi, true, null) catch { if (includes == .any) { // fall back to mingw includes = .gnu; -- 2.54.0 From e3f36d0d81b85c213d03ffcd090ce19bfcbb3fbc Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 26 Dec 2025 17:23:14 -0800 Subject: [PATCH 191/195] Fix a few standalone tests on Windows --- test/standalone/env_vars/main.zig | 2 + test/standalone/windows_argv/build.zig | 2 +- test/standalone/windows_bat_args/fuzz.zig | 43 ++++++- test/standalone/windows_bat_args/test.zig | 134 ++++++++++++++-------- test/standalone/windows_spawn/main.zig | 118 ++++++++++++------- 5 files changed, 208 insertions(+), 91 deletions(-) diff --git a/test/standalone/env_vars/main.zig b/test/standalone/env_vars/main.zig index 12b911404a7641a7bef304fed888104ddec699db..b85105642e1da6a5530246195658f04189e027a2 100644 --- a/test/standalone/env_vars/main.zig +++ b/test/standalone/env_vars/main.zig @@ -3,6 +3,8 @@ const builtin = @import("builtin"); // Note: the environment variables under test are set by the build.zig pub fn main() !void { + @setEvalBranchQuota(10000); + var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; defer _ = gpa.deinit(); const allocator = gpa.allocator(); diff --git a/test/standalone/windows_argv/build.zig b/test/standalone/windows_argv/build.zig index df988d2371e0a3e4dd0bca7f5b93090c64f3840e..afe6dd80e5da2a950a317aa13b0b290c88a14781 100644 --- a/test/standalone/windows_argv/build.zig +++ b/test/standalone/windows_argv/build.zig @@ -67,7 +67,7 @@ pub fn build(b: *std.Build) !void { // Only target the MSVC ABI if MSVC/Windows SDK is available const has_msvc = has_msvc: { - const sdk = std.zig.WindowsSdk.find(b.allocator, builtin.cpu.arch) catch |err| switch (err) { + const sdk = std.zig.WindowsSdk.find(b.allocator, b.graph.io, builtin.cpu.arch) catch |err| switch (err) { error.OutOfMemory => @panic("oom"), else => break :has_msvc false, }; diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig index b68639dda4df7d2d2f9eb5d325fd640de23e5864..28749259f7d832ed5de43ed2ed552adb7ac1a8ad 100644 --- a/test/standalone/windows_bat_args/fuzz.zig +++ b/test/standalone/windows_bat_args/fuzz.zig @@ -10,6 +10,7 @@ pub fn main() anyerror!void { const gpa = debug_alloc_inst.allocator(); var threaded: Io.Threaded = .init(gpa, .{}); + defer threaded.deinit(); const io = threaded.io(); var it = try std.process.argsWithAllocator(gpa); @@ -41,8 +42,8 @@ pub fn main() anyerror!void { std.debug.print("rand seed: {}\n", .{seed}); } - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); + var tmp = tmpDir(io, .{}); + defer tmp.cleanup(io); try std.process.setCurrentDir(io, tmp.dir); defer std.process.setCurrentDir(io, tmp.parent_dir) catch {}; @@ -167,3 +168,41 @@ fn randomArg(gpa: Allocator, rand: std.Random) ![]const u8 { return buf.toOwnedSlice(gpa); } + +pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir { + var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; + std.crypto.random.bytes(&random_bytes); + var sub_path: [TmpDir.sub_path_len]u8 = undefined; + _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); + + const cwd = Io.Dir.cwd(); + var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch + @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); + defer cache_dir.close(io); + const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch + @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); + const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch + @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); + + return .{ + .dir = dir, + .parent_dir = parent_dir, + .sub_path = sub_path, + }; +} + +pub const TmpDir = struct { + dir: Io.Dir, + parent_dir: Io.Dir, + sub_path: [sub_path_len]u8, + + const random_bytes_count = 12; + const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); + + pub fn cleanup(self: *TmpDir, io: Io) void { + self.dir.close(io); + self.parent_dir.deleteTree(io, &self.sub_path) catch {}; + self.parent_dir.close(io); + self.* = undefined; + } +}; diff --git a/test/standalone/windows_bat_args/test.zig b/test/standalone/windows_bat_args/test.zig index 73f0ee94534ef0e4745fca5dc665414e317c6c30..e0d1abe80640bcab15592c416ca973efc5584231 100644 --- a/test/standalone/windows_bat_args/test.zig +++ b/test/standalone/windows_bat_args/test.zig @@ -15,8 +15,8 @@ pub fn main() anyerror!void { _ = it.next() orelse unreachable; // skip binary name const child_exe_path_orig = it.next() orelse unreachable; - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); + var tmp = tmpDir(io, .{}); + defer tmp.cleanup(io); try std.process.setCurrentDir(io, tmp.dir); defer std.process.setCurrentDir(io, tmp.parent_dir) catch {}; @@ -47,41 +47,41 @@ pub fn main() anyerror!void { buf.shrinkRetainingCapacity(preamble_len); // Test cases are from https://github.com/rust-lang/rust/blob/master/tests/ui/std/windows-bat-args.rs - try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\x00"}); - try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\n"}); - try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\r"}); - try testExec(gpa, &.{ "a", "b" }, null); - try testExec(gpa, &.{ "c is for cat", "d is for dog" }, null); - try testExec(gpa, &.{ "\"", " \"" }, null); - try testExec(gpa, &.{ "\\", "\\" }, null); - try testExec(gpa, &.{">file.txt"}, null); - try testExec(gpa, &.{"whoami.exe"}, null); - try testExec(gpa, &.{"&a.exe"}, null); - try testExec(gpa, &.{"&echo hello "}, null); - try testExec(gpa, &.{ "&echo hello", "&whoami", ">file.txt" }, null); - try testExec(gpa, &.{"!TMP!"}, null); - try testExec(gpa, &.{"key=value"}, null); - try testExec(gpa, &.{"\"key=value\""}, null); - try testExec(gpa, &.{"key = value"}, null); - try testExec(gpa, &.{"key=[\"value\"]"}, null); - try testExec(gpa, &.{ "", "a=b" }, null); - try testExec(gpa, &.{"key=\"foo bar\""}, null); - try testExec(gpa, &.{"key=[\"my_value]"}, null); - try testExec(gpa, &.{"key=[\"my_value\",\"other-value\"]"}, null); - try testExec(gpa, &.{"key\\=value"}, null); - try testExec(gpa, &.{"key=\"&whoami\""}, null); - try testExec(gpa, &.{"key=\"value\"=5"}, null); - try testExec(gpa, &.{"key=[\">file.txt\"]"}, null); - try testExec(gpa, &.{"%hello"}, null); - try testExec(gpa, &.{"%PATH%"}, null); - try testExec(gpa, &.{"%%cd:~,%"}, null); - try testExec(gpa, &.{"%PATH%PATH%"}, null); - try testExec(gpa, &.{"\">file.txt"}, null); - try testExec(gpa, &.{"abc\"&echo hello"}, null); - try testExec(gpa, &.{"123\">file.txt"}, null); - try testExec(gpa, &.{"\"&echo hello&whoami.exe"}, null); - try testExec(gpa, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null); - try testExec(gpa, &.{"&whoami.exe"}, null); + try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\x00"}); + try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\n"}); + try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\r"}); + try testExec(gpa, io, &.{ "a", "b" }, null); + try testExec(gpa, io, &.{ "c is for cat", "d is for dog" }, null); + try testExec(gpa, io, &.{ "\"", " \"" }, null); + try testExec(gpa, io, &.{ "\\", "\\" }, null); + try testExec(gpa, io, &.{">file.txt"}, null); + try testExec(gpa, io, &.{"whoami.exe"}, null); + try testExec(gpa, io, &.{"&a.exe"}, null); + try testExec(gpa, io, &.{"&echo hello "}, null); + try testExec(gpa, io, &.{ "&echo hello", "&whoami", ">file.txt" }, null); + try testExec(gpa, io, &.{"!TMP!"}, null); + try testExec(gpa, io, &.{"key=value"}, null); + try testExec(gpa, io, &.{"\"key=value\""}, null); + try testExec(gpa, io, &.{"key = value"}, null); + try testExec(gpa, io, &.{"key=[\"value\"]"}, null); + try testExec(gpa, io, &.{ "", "a=b" }, null); + try testExec(gpa, io, &.{"key=\"foo bar\""}, null); + try testExec(gpa, io, &.{"key=[\"my_value]"}, null); + try testExec(gpa, io, &.{"key=[\"my_value\",\"other-value\"]"}, null); + try testExec(gpa, io, &.{"key\\=value"}, null); + try testExec(gpa, io, &.{"key=\"&whoami\""}, null); + try testExec(gpa, io, &.{"key=\"value\"=5"}, null); + try testExec(gpa, io, &.{"key=[\">file.txt\"]"}, null); + try testExec(gpa, io, &.{"%hello"}, null); + try testExec(gpa, io, &.{"%PATH%"}, null); + try testExec(gpa, io, &.{"%%cd:~,%"}, null); + try testExec(gpa, io, &.{"%PATH%PATH%"}, null); + try testExec(gpa, io, &.{"\">file.txt"}, null); + try testExec(gpa, io, &.{"abc\"&echo hello"}, null); + try testExec(gpa, io, &.{"123\">file.txt"}, null); + try testExec(gpa, io, &.{"\"&echo hello&whoami.exe"}, null); + try testExec(gpa, io, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null); + try testExec(gpa, io, &.{"&whoami.exe"}, null); // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution. // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped @@ -99,14 +99,14 @@ pub fn main() anyerror!void { // > "args1.bat .. " // '"args1.bat .. "' is not recognized as an internal or external command, // operable program or batch file. - try std.testing.expectError(error.FileNotFound, testExecBat(gpa, "args1.bat .. ", &.{"abc"}, null)); + try std.testing.expectError(error.FileNotFound, testExecBat(gpa, io, "args1.bat .. ", &.{"abc"}, null)); const absolute_with_trailing = blk: { - const absolute_path = try Io.Dir.realPathFileAbsoluteAlloc(io, "args1.bat", gpa); + const absolute_path = try Io.Dir.cwd().realPathFileAlloc(io, "args1.bat", gpa); defer gpa.free(absolute_path); break :blk try std.mem.concat(gpa, u8, &.{ absolute_path, " .. " }); }; defer gpa.free(absolute_with_trailing); - try std.testing.expectError(error.FileNotFound, testExecBat(gpa, absolute_with_trailing, &.{"abc"}, null)); + try std.testing.expectError(error.FileNotFound, testExecBat(gpa, io, absolute_with_trailing, &.{"abc"}, null)); var env = env: { var env = try std.process.getEnvMap(gpa); @@ -120,20 +120,20 @@ pub fn main() anyerror!void { break :env env; }; defer env.deinit(); - try testExec(gpa, &.{"%FOO%"}, &env); + try testExec(gpa, io, &.{"%FOO%"}, &env); // Ensure that none of the `>file.txt`s have caused file.txt to be created - try std.testing.expectError(error.FileNotFound, tmp.dir.access("file.txt", .{})); + try std.testing.expectError(error.FileNotFound, tmp.dir.access(io, "file.txt", .{})); } -fn testExecError(err: anyerror, gpa: Allocator, args: []const []const u8) !void { - return std.testing.expectError(err, testExec(gpa, args, null)); +fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8) !void { + return std.testing.expectError(err, testExec(gpa, io, args, null)); } -fn testExec(gpa: Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void { - try testExecBat(gpa, "args1.bat", args, env); - try testExecBat(gpa, "args2.bat", args, env); - try testExecBat(gpa, "args3.bat", args, env); +fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void { + try testExecBat(gpa, io, "args1.bat", args, env); + try testExecBat(gpa, io, "args2.bat", args, env); + try testExecBat(gpa, io, "args3.bat", args, env); } fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { @@ -164,3 +164,41 @@ fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8 i += 1; } } + +pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir { + var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; + std.crypto.random.bytes(&random_bytes); + var sub_path: [TmpDir.sub_path_len]u8 = undefined; + _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); + + const cwd = Io.Dir.cwd(); + var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch + @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); + defer cache_dir.close(io); + const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch + @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); + const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch + @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); + + return .{ + .dir = dir, + .parent_dir = parent_dir, + .sub_path = sub_path, + }; +} + +pub const TmpDir = struct { + dir: Io.Dir, + parent_dir: Io.Dir, + sub_path: [sub_path_len]u8, + + const random_bytes_count = 12; + const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); + + pub fn cleanup(self: *TmpDir, io: Io) void { + self.dir.close(io); + self.parent_dir.deleteTree(io, &self.sub_path) catch {}; + self.parent_dir.close(io); + self.* = undefined; + } +}; diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index f5df4dee25bcc803cb7d4a1a018719ce4c7b6589..c9522bf4de301022d4b8e39d0f5e71fb97040ba2 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -19,8 +19,8 @@ pub fn main() anyerror!void { _ = it.next() orelse unreachable; // skip binary name const hello_exe_cache_path = it.next() orelse unreachable; - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); + var tmp = tmpDir(io, .{}); + defer tmp.cleanup(io); const tmp_absolute_path = try tmp.dir.realPathFileAlloc(io, ".", gpa); defer gpa.free(tmp_absolute_path); @@ -44,10 +44,10 @@ pub fn main() anyerror!void { ) == windows.TRUE); // No PATH, so it should fail to find anything not in the cwd - try testExecError(error.FileNotFound, gpa, "something_missing"); + try testExecError(error.FileNotFound, gpa, io, "something_missing"); // make sure we don't get error.BadPath traversing out of cwd with a relative path - try testExecError(error.FileNotFound, gpa, "..\\.\\.\\.\\\\..\\more_missing"); + try testExecError(error.FileNotFound, gpa, io, "..\\.\\.\\.\\\\..\\more_missing"); std.debug.assert(windows.kernel32.SetEnvironmentVariableW( utf16Literal("PATH"), @@ -55,12 +55,12 @@ pub fn main() anyerror!void { ) == windows.TRUE); // Move hello.exe into the tmp dir which is now added to the path - try Io.Dir.cwd().copyFile(hello_exe_cache_path, tmp.dir, "hello.exe", .{}); + try Io.Dir.cwd().copyFile(hello_exe_cache_path, tmp.dir, "hello.exe", io, .{}); // with extension should find the .exe (case insensitive) - try testExec(gpa, "HeLLo.exe", "hello from exe\n"); + try testExec(gpa, io, "HeLLo.exe", "hello from exe\n"); // without extension should find the .exe (case insensitive) - try testExec(gpa, "heLLo", "hello from exe\n"); + try testExec(gpa, io, "heLLo", "hello from exe\n"); // with invalid cwd try std.testing.expectError(error.FileNotFound, testExecWithCwd(gpa, io, "hello.exe", "missing_dir", "")); @@ -70,33 +70,33 @@ pub fn main() anyerror!void { try tmp.dir.writeFile(io, .{ .sub_path = "hello.cmd", .data = "@echo hello from cmd" }); // with extension should find the .bat (case insensitive) - try testExec(gpa, "heLLo.bat", "hello from bat\r\n"); + try testExec(gpa, io, "heLLo.bat", "hello from bat\r\n"); // with extension should find the .cmd (case insensitive) - try testExec(gpa, "heLLo.cmd", "hello from cmd\r\n"); + try testExec(gpa, io, "heLLo.cmd", "hello from cmd\r\n"); // without extension should find the .exe (since its first in PATHEXT) - try testExec(gpa, "heLLo", "hello from exe\n"); + try testExec(gpa, io, "heLLo", "hello from exe\n"); // now rename the exe to not have an extension - try renameExe(tmp.dir, "hello.exe", "hello"); + try renameExe(tmp.dir, io, "hello.exe", "hello"); // with extension should now fail - try testExecError(error.FileNotFound, gpa, "hello.exe"); + try testExecError(error.FileNotFound, gpa, io, "hello.exe"); // without extension should succeed (case insensitive) - try testExec(gpa, "heLLo", "hello from exe\n"); + try testExec(gpa, io, "heLLo", "hello from exe\n"); try tmp.dir.createDir(io, "something", .default_dir); - try renameExe(tmp.dir, "hello", "something/hello.exe"); + try renameExe(tmp.dir, io, "hello", "something/hello.exe"); const relative_path_no_ext = try std.fs.path.join(gpa, &.{ tmp_relative_path, "something/hello" }); defer gpa.free(relative_path_no_ext); // Giving a full relative path to something/hello should work - try testExec(gpa, relative_path_no_ext, "hello from exe\n"); + try testExec(gpa, io, relative_path_no_ext, "hello from exe\n"); // But commands with path separators get excluded from PATH searching, so this will fail - try testExecError(error.FileNotFound, gpa, "something/hello"); + try testExecError(error.FileNotFound, gpa, io, "something/hello"); // Now that .BAT is the first PATHEXT that should be found, this should succeed - try testExec(gpa, "heLLo", "hello from bat\r\n"); + try testExec(gpa, io, "heLLo", "hello from bat\r\n"); // Add a hello.exe that is not a valid executable try tmp.dir.writeFile(io, .{ .sub_path = "hello.exe", .data = "invalid" }); @@ -105,26 +105,26 @@ pub fn main() anyerror!void { // case for .EXE extensions, where if they ever try to get executed but they are // invalid, that gets treated as a fatal error wherever they are found and InvalidExe // is returned immediately. - try testExecError(error.InvalidExe, gpa, "hello.exe"); + try testExecError(error.InvalidExe, gpa, io, "hello.exe"); // Same thing applies to the command with no extension--even though there is a // hello.bat that could be executed, it should stop after it tries executing // hello.exe and getting InvalidExe. - try testExecError(error.InvalidExe, gpa, "hello"); + try testExecError(error.InvalidExe, gpa, io, "hello"); // If we now rename hello.exe to have no extension, it will behave differently - try renameExe(tmp.dir, "hello.exe", "hello"); + try renameExe(tmp.dir, io, "hello.exe", "hello"); // Now, trying to execute it without an extension should treat InvalidExe as recoverable // and skip over it and find hello.bat and execute that - try testExec(gpa, "hello", "hello from bat\r\n"); + try testExec(gpa, io, "hello", "hello from bat\r\n"); // If we rename the invalid exe to something else - try renameExe(tmp.dir, "hello", "goodbye"); + try renameExe(tmp.dir, io, "hello", "goodbye"); // Then we should now get FileNotFound when trying to execute 'goodbye', // since that is what the original error will be after searching for 'goodbye' // in the cwd. It will try to execute 'goodbye' from the PATH but the InvalidExe error // should be ignored in this case. - try testExecError(error.FileNotFound, gpa, "goodbye"); + try testExecError(error.FileNotFound, gpa, io, "goodbye"); // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir try std.process.setCurrentDir(io, tmp.dir); @@ -139,26 +139,26 @@ pub fn main() anyerror!void { // Now trying to execute goodbye should give error.InvalidExe since it's the original // error that we got when trying within the cwd - try testExecError(error.InvalidExe, gpa, "goodbye"); + try testExecError(error.InvalidExe, gpa, io, "goodbye"); // hello should still find the .bat - try testExec(gpa, "hello", "hello from bat\r\n"); + try testExec(gpa, io, "hello", "hello from bat\r\n"); // If we rename something/hello.exe to something/goodbye.exe - try renameExe(tmp.dir, "something/hello.exe", "something/goodbye.exe"); + try renameExe(tmp.dir, io, "something/hello.exe", "something/goodbye.exe"); // And try to execute goodbye, then the one in something should be found // since the one in cwd is an invalid executable - try testExec(gpa, "goodbye", "hello from exe\n"); + try testExec(gpa, io, "goodbye", "hello from exe\n"); // If we use an absolute path to execute the invalid goodbye const goodbye_abs_path = try std.mem.join(gpa, "\\", &.{ tmp_absolute_path, "goodbye" }); defer gpa.free(goodbye_abs_path); // then the PATH should not be searched and we should get InvalidExe - try testExecError(error.InvalidExe, gpa, goodbye_abs_path); + try testExecError(error.InvalidExe, gpa, io, goodbye_abs_path); // If we try to exec but provide a cwd that is an absolute path, the PATH // should still be searched and the goodbye.exe in something should be found. - try testExecWithCwd(gpa, "goodbye", tmp_absolute_path, "hello from exe\n"); + try testExecWithCwd(gpa, io, "goodbye", tmp_absolute_path, "hello from exe\n"); // introduce some extra path separators into the path which is dealt with inside the spawn call. const denormed_something_subdir_size = std.mem.replacementSize(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\")); @@ -177,20 +177,20 @@ pub fn main() anyerror!void { null, ) == windows.TRUE); - try testExecWithCwd(gpa, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n"); + try testExecWithCwd(gpa, io, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n"); // normalization should also work if the non-normalized path is found in the PATH var. std.debug.assert(windows.kernel32.SetEnvironmentVariableW( utf16Literal("PATH"), denormed_something_subdir_abs_path, ) == windows.TRUE); - try testExec(gpa, "goodbye", "hello from exe\n"); + try testExec(gpa, io, "goodbye", "hello from exe\n"); // now make sure we can launch executables "outside" of the cwd - var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{}); + var subdir_cwd = try tmp.dir.openDir(io, denormed_something_subdir_wtf8, .{}); defer subdir_cwd.close(io); - try renameExe(tmp.dir, "something/goodbye.exe", "hello.exe"); + try renameExe(tmp.dir, io, "something/goodbye.exe", "hello.exe"); try std.process.setCurrentDir(io, subdir_cwd); // clear the PATH again @@ -200,15 +200,15 @@ pub fn main() anyerror!void { ) == windows.TRUE); // while we're at it make sure non-windows separators work fine - try testExec(gpa, "../hello", "hello from exe\n"); + try testExec(gpa, io, "../hello", "hello from exe\n"); } -fn testExecError(err: anyerror, gpa: Allocator, command: []const u8) !void { - return std.testing.expectError(err, testExec(gpa, command, "")); +fn testExecError(err: anyerror, gpa: Allocator, io: Io, command: []const u8) !void { + return std.testing.expectError(err, testExec(gpa, io, command, "")); } -fn testExec(gpa: Allocator, command: []const u8, expected_stdout: []const u8) !void { - return testExecWithCwd(gpa, command, null, expected_stdout); +fn testExec(gpa: Allocator, io: Io, command: []const u8, expected_stdout: []const u8) !void { + return testExecWithCwd(gpa, io, command, null, expected_stdout); } fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void { @@ -223,9 +223,9 @@ fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8 try std.testing.expectEqualStrings(expected_stdout, result.stdout); } -fn renameExe(dir: Io.Dir, old_sub_path: []const u8, new_sub_path: []const u8) !void { +fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void { var attempt: u5 = 0; - while (true) break dir.rename(old_sub_path, new_sub_path) catch |err| switch (err) { + while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) { error.AccessDenied => { if (attempt == 13) return error.AccessDenied; // give the kernel a chance to finish closing the executable handle @@ -236,3 +236,41 @@ fn renameExe(dir: Io.Dir, old_sub_path: []const u8, new_sub_path: []const u8) !v else => |e| return e, }; } + +pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir { + var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; + std.crypto.random.bytes(&random_bytes); + var sub_path: [TmpDir.sub_path_len]u8 = undefined; + _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); + + const cwd = Io.Dir.cwd(); + var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch + @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); + defer cache_dir.close(io); + const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch + @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); + const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch + @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); + + return .{ + .dir = dir, + .parent_dir = parent_dir, + .sub_path = sub_path, + }; +} + +pub const TmpDir = struct { + dir: Io.Dir, + parent_dir: Io.Dir, + sub_path: [sub_path_len]u8, + + const random_bytes_count = 12; + const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); + + pub fn cleanup(self: *TmpDir, io: Io) void { + self.dir.close(io); + self.parent_dir.deleteTree(io, &self.sub_path) catch {}; + self.parent_dir.close(io); + self.* = undefined; + } +}; -- 2.54.0 From e08837b0fe2cf4ef470d8416abcc6db7573d473e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 18:10:27 -0800 Subject: [PATCH 192/195] build.zig: simplify max_rss compiler_rt on windows I observed this take more memory than the previous declaration --- build.zig | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.zig b/build.zig index f168eef662caa8c76e05af27a9633892e3954c47..338709c7c4193ad3c3c4874104fdcf15205eda45 100644 --- a/build.zig +++ b/build.zig @@ -535,10 +535,6 @@ pub fn build(b: *std.Build) !void { .aarch64 => 701_413_785, else => 800_000_000, }, - .windows => switch (b.graph.host.result.cpu.arch) { - .x86_64 => 536_414_208, - else => 600_000_000, - }, else => 900_000_000, }, })); -- 2.54.0 From 2fb46b0ed959fcdf5a47f08bd48c99a8e57931de Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 19:28:51 -0800 Subject: [PATCH 193/195] std.Progress: recancel in start() one must never swallow error.Canceled; if you can't handle it, either temporarily disable it for the function, or recancel if you catch it. --- lib/std/Progress.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 8d21698dac6a6ddc4ce249f7c8d0e51990bbeb92..6baa24d246f72f550f5f667109abb920a048a14c 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -504,7 +504,7 @@ pub fn start(io: Io, options: Options) Node { } }; } else |err| switch (err) { error.Canceled => { - // TODO: recancel here, or block cancelation for this function + io.recancel(); return Node.none; }, } -- 2.54.0 From d8460910b47d175fc77ef6fe5e9f8e58e291e2fd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Dec 2025 19:36:30 -0800 Subject: [PATCH 194/195] std.Io.Threaded: init stderr_writer in streaming mode --- lib/std/Io/Threaded.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 1e37f5636506ae1f2eccaabf698c7bdfefe5eec6..d6d44aa775289a9dbee95f7edaac8c5e2181d30a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -56,6 +56,7 @@ stderr_writer: File.Writer = .{ .io = undefined, .interface = Io.File.Writer.initInterface(&.{}), .file = if (is_windows) undefined else .stderr(), + .mode = .streaming, }, stderr_mode: Io.Terminal.Mode = .no_color, stderr_writer_initialized: bool = false, -- 2.54.0 From 60a1ba0a8f3517356fa2941462f002a7f580545b Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Sat, 27 Dec 2025 10:23:46 +0000 Subject: [PATCH 195/195] stage1: add more wasi.c stubs Because `std.Io` forces analysis of unused functions in the vtable, there are now references to 4 more WASI functions, even though the compiler does not actually call them at runtime. Therefore, these new stubs fix the bootstrap process after a zig1.wasm update (though this branch does *not* update zig1.wasm). --- stage1/wasi.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/stage1/wasi.c b/stage1/wasi.c index 83240d39b41dc08a3617d0dd8ab42078ce3d5554..e4772735d9ada66cc977d7c681a4930faebc203e 100644 --- a/stage1/wasi.c +++ b/stage1/wasi.c @@ -939,6 +939,58 @@ uint32_t wasi_snapshot_preview1_path_remove_directory(uint32_t fd, uint32_t path return wasi_errno_success; } +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) { + uint8_t *const m = *wasm_memory; + const char *old_path_ptr = (const char *)&m[old_path]; + const char *new_path_ptr = (const char *)&m[new_path]; +#if LOG_TRACE + 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); +#endif + (void)old_path_ptr; + (void)old_path_len; + (void)fd; + (void)new_path_ptr; + (void)new_path_len; + panic("unimplemented: path_symlink"); + return wasi_errno_success; +} + +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) { + uint8_t *const m = *wasm_memory; + const char *path_ptr = (const char *)&m[path]; + char *buf_ptr = (char *)&m[buf]; + uint32_t *out_len_ptr = (uint32_t *)&m[out_len]; +#if LOG_TRACE + 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); +#endif + (void)fd; + (void)path_ptr; + (void)path_len; + (void)buf_ptr; + (void)buf_len; + (void)out_len_ptr; + panic("unimplemented: path_readlink"); + return wasi_errno_success; +} + +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) { + uint8_t *const m = *wasm_memory; + const char *old_path_ptr = (const char *)&m[old_path]; + const char *new_path_ptr = (const char *)&m[new_path]; +#if LOG_TRACE + 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); +#endif + (void)old_fd; + (void)old_flags; + (void)old_path_ptr; + (void)old_path_len; + (void)new_fd; + (void)new_path_ptr; + (void)new_path_len; + panic("unimplemented: path_link"); + return wasi_errno_success; +} + uint32_t wasi_snapshot_preview1_path_unlink_file(uint32_t fd, uint32_t path, uint32_t path_len) { uint8_t *const m = *wasm_memory; 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_ return wasi_errno_success; } +uint32_t wasi_snapshot_preview1_fd_sync(uint32_t fd) { +#if LOG_TRACE + fprintf(stderr, "wasi_snapshot_preview1_fd_sync(%u)\n", fd); +#endif + (void)fd; + panic("unimplemented: fd_sync"); + return wasi_errno_success; +} + uint32_t wasi_snapshot_preview1_poll_oneoff(uint32_t in, uint32_t out, uint32_t nsubscriptions, uint32_t res_nevents) { (void)in; (void)out; -- 2.54.0