1298 lines · 46.2 KB · Zig
RawDownload
const std = @import("std");const builtin = @import("builtin");const darwin_process_spawn = @import("darwin_process_spawn.zig"); pub const RawEnviron = [*:null]const ?[*:0]const u8; // Process globals are installed before threads start and remain read-only.var real_io: ?std.Io = null; // Fallback used by non-test code only when setIo was not called. The real// application installs `real_io` from main before spawning threads.var fallback_threaded: std.Io.Threaded = .init_single_threaded; var global_environ: ?*const std.process.Environ.Map = null;var global_environ_block: ?std.process.Environ.Block = null;var global_raw_environ: ?RawEnviron = null; pub fn setIo(zio: std.Io) void {    real_io = process_io_for(builtin.os.tag, zio);} fn process_io_for(comptime os_tag: std.Target.Os.Tag, zio: std.Io) std.Io {    return if (os_tag == .macos) darwin_process_spawn.wrap(zio) else zio;} pub fn getIo() std.Io {    if (real_io) |zio| return zio;    if (comptime builtin.is_test) return process_io_for(builtin.os.tag, std.testing.io);    return process_io_for(builtin.os.tag, fallback_threaded.io());} /// Opens an absolute directory path without following any path component./// The caller owns the returned directory handle.pub fn openDirAbsoluteNoFollow(path: []const u8, options: std.Io.Dir.OpenOptions) !std.Io.Dir {    if (!std.fs.path.isAbsolute(path)) return error.InvalidPath;    var components = std.fs.path.componentIterator(path);    const root = components.root() orelse return error.InvalidPath;    var component = components.next() orelse {        var root_options = options;        root_options.follow_symlinks = false;        return std.Io.Dir.openDirAbsolute(getIo(), root, root_options);    };     var dir = try std.Io.Dir.openDirAbsolute(getIo(), root, .{ .follow_symlinks = false });    errdefer dir.close(getIo());    while (components.next()) |next_component| {        if (std.mem.eql(u8, component.name, ".") or std.mem.eql(u8, component.name, "..")) {            return error.InvalidPath;        }        const next_dir = try dir.openDir(getIo(), component.name, .{ .follow_symlinks = false });        dir.close(getIo());        dir = next_dir;        component = next_component;    }    if (std.mem.eql(u8, component.name, ".") or std.mem.eql(u8, component.name, "..")) {        return error.InvalidPath;    }    var final_options = options;    final_options.follow_symlinks = false;    const result = try dir.openDir(getIo(), component.name, final_options);    dir.close(getIo());    return result;} test "Darwin process I/O replaces only processSpawn with stable storage" {    const original = std.testing.io;    const selected = process_io_for(.macos, original);    const selected_again = process_io_for(.macos, original);     try std.testing.expect(selected.userdata == original.userdata);    try std.testing.expect(selected.vtable == selected_again.vtable);    inline for (@typeInfo(std.Io.VTable).@"struct".fields) |field| {        if (comptime std.mem.eql(u8, field.name, "processSpawn")) {            try std.testing.expect(@field(selected.vtable, field.name) != @field(original.vtable, field.name));        } else {            try std.testing.expectEqual(@field(original.vtable, field.name), @field(selected.vtable, field.name));        }    }} test "getIo applies Darwin process selection to the test fallback" {    if (builtin.os.tag != .macos) return error.SkipZigTest;     const previous = real_io;    real_io = null;    defer real_io = previous;     const selected = getIo();    try std.testing.expect(selected.userdata == std.testing.io.userdata);    try std.testing.expect(selected.vtable.processSpawn != std.testing.io.vtable.processSpawn);} test "getIo Darwin test fallback runs a child through the selected backend" {    if (builtin.os.tag != .macos) return error.SkipZigTest;     const previous = real_io;    real_io = null;    defer real_io = previous;     const alloc = std.testing.allocator;    const io = getIo();    const result = try std.process.run(alloc, io, .{        .argv = &.{ "/usr/bin/printf", "get-io-spawn-ok" },    });    defer alloc.free(result.stdout);    defer alloc.free(result.stderr);     switch (result.term) {        .exited => |code| try std.testing.expectEqual(@as(u8, 0), code),        else => return error.UnexpectedTermination,    }    try std.testing.expectEqualStrings("get-io-spawn-ok", result.stdout);    try std.testing.expectEqualStrings("", result.stderr);} test "non-Darwin process I/O keeps the original vtable" {    const original = std.testing.io;    const selected = process_io_for(.linux, original);     try std.testing.expect(selected.userdata == original.userdata);    try std.testing.expect(selected.vtable == original.vtable);} test "openDirAbsoluteNoFollow rejects unsafe path components" {    const alloc = std.testing.allocator;    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();     try tmp.dir.createDirPath(getIo(), "real/child");    try writeTempFile(tmp.dir, "plain-file", "not a directory");    tmp.dir.symLink(std.testing.io, "real", "linked", .{ .is_directory = true }) catch |err| {        if (err == error.AccessDenied or err == error.FileSystem) return error.SkipZigTest;        return err;    };     const root = try dirRealpathAlloc(alloc, tmp.dir, ".");    defer alloc.free(root);    const linked_child = try std.fs.path.join(alloc, &.{ root, "linked/child" });    defer alloc.free(linked_child);    const missing = try std.fs.path.join(alloc, &.{ root, "missing" });    defer alloc.free(missing);    const wrong_kind = try std.fs.path.join(alloc, &.{ root, "plain-file" });    defer alloc.free(wrong_kind);     if (openDirAbsoluteNoFollow(linked_child, .{})) |dir| {        dir.close(getIo());        return error.TestExpectedError;    } else |err| switch (err) {        error.NotDir, error.SymLinkLoop => {},        else => return err,    }    try std.testing.expectError(error.FileNotFound, openDirAbsoluteNoFollow(missing, .{}));    try std.testing.expectError(error.NotDir, openDirAbsoluteNoFollow(wrong_kind, .{}));} /// Opens an existing regular file without following the final symlink and/// without waiting on a special file that races the initial metadata check./// The caller owns the returned file.pub fn openExistingRegularFile(    dir: std.Io.Dir,    sub_path: []const u8,    mode: std.Io.Dir.OpenFileOptions.Mode,) !std.Io.File {    const initial = try dir.statFile(getIo(), sub_path, .{        .follow_symlinks = false,    });    if (initial.kind != .file or initial.nlink != 1) {        return error.DurablePathUnsafe;    }     if (comptime builtin.os.tag == .windows or builtin.os.tag == .wasi) {        var file = try dir.openFile(getIo(), sub_path, .{            .mode = mode,            .allow_directory = false,            .follow_symlinks = false,        });        errdefer file.close(getIo());        const stat = try file.stat(getIo());        try verifyOpenedRegularFile(stat, mode);        return file;    }     var flags: std.posix.O = .{        .ACCMODE = switch (mode) {            .read_only => .RDONLY,            .write_only => .WRONLY,            .read_write => .RDWR,        },        .NOFOLLOW = true,        .NONBLOCK = true,    };    if (@hasField(std.posix.O, "CLOEXEC")) flags.CLOEXEC = true;    if (@hasField(std.posix.O, "LARGEFILE")) flags.LARGEFILE = true;    if (@hasField(std.posix.O, "NOCTTY")) flags.NOCTTY = true;     const fd = std.posix.openat(dir.handle, sub_path, flags, 0) catch |err| switch (err) {        error.NoDevice, error.SymLinkLoop, error.NotDir => return error.DurablePathUnsafe,        else => return err,    };    var file = std.Io.File{        .handle = fd,        .flags = .{ .nonblocking = true },    };    errdefer file.close(getIo());    const stat = try file.stat(getIo());    try verifyOpenedRegularFile(stat, mode);    try makeFileBlocking(&file);    return file;} pub fn verifyOpenedRegularFile(    stat: std.Io.File.Stat,    mode: std.Io.Dir.OpenFileOptions.Mode,) !void {    if (stat.kind != .file or stat.nlink > 1) {        return error.DurablePathUnsafe;    }    if (mode != .read_only and stat.nlink != 1) {        return error.DurablePathUnsafe;    }} fn makeFileBlocking(file: *std.Io.File) !void {    const current = while (true) {        const rc = std.posix.system.fcntl(            file.handle,            std.posix.F.GETFL,            @as(usize, 0),        );        switch (std.posix.errno(rc)) {            .SUCCESS => break @as(usize, @intCast(rc)),            .INTR => continue,            else => return error.FileControlFailed,        }    };    const nonblock = @as(usize, 1) << @bitOffsetOf(std.posix.O, "NONBLOCK");    while (true) {        const rc = std.posix.system.fcntl(            file.handle,            std.posix.F.SETFL,            current & ~nonblock,        );        switch (std.posix.errno(rc)) {            .SUCCESS => break,            .INTR => continue,            else => return error.FileControlFailed,        }    }    file.flags.nonblocking = false;} test "read-only regular files remain valid when atomic replacement unlinks the descriptor" {    if (comptime builtin.os.tag == .windows or builtin.os.tag == .wasi) {        return error.SkipZigTest;    }    const alloc = std.testing.allocator;    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    try tmp.dir.writeFile(getIo(), .{ .sub_path = "target", .data = "old" });     var file = try openExistingRegularFile(tmp.dir, "target", .read_only);    defer file.close(getIo());    try tmp.dir.writeFile(getIo(), .{ .sub_path = "replacement", .data = "new" });    try tmp.dir.rename("replacement", tmp.dir, "target", getIo());     const stat = try file.stat(getIo());    try std.testing.expectEqual(@as(u64, 0), stat.nlink);    try verifyOpenedRegularFile(stat, .read_only);    try std.testing.expectError(        error.DurablePathUnsafe,        verifyOpenedRegularFile(stat, .read_write),    );    const bytes = try readFileToEnd(alloc, &file, 16);    defer alloc.free(bytes);    try std.testing.expectEqualStrings("old", bytes);} pub fn setEnvironMap(m: *const std.process.Environ.Map) void {    global_environ = m;    global_environ_block = null;    global_raw_environ = null;} pub fn setEnvironBlock(block: std.process.Environ.Block) void {    global_environ = null;    global_environ_block = block;    global_raw_environ = null;} pub fn setRawEnviron(raw: RawEnviron) void {    global_environ = null;    global_environ_block = null;    global_raw_environ = raw;} pub fn getenv(key: []const u8) ?[]const u8 {    if (global_environ) |m| return m.get(key);    if (global_environ_block) |block| return getenvFromBlock(block, key);    if (global_raw_environ) |raw| return getenvFromLibc(key) orelse getenvFromRaw(raw, key);    return null;} pub fn e2eFailIfDurableMutationAttempted() void {    const enabled = getenv("FX_E2E_FAIL_ON_DURABLE_MUTATION") orelse return;    if (!std.mem.eql(u8, enabled, "1")) return;    std.process.exit(86);} pub fn environMap() ?*const std.process.Environ.Map {    return global_environ;} fn getenvFromBlock(block: std.process.Environ.Block, key: []const u8) ?[]const u8 {    if (comptime builtin.os.tag == .windows or builtin.os.tag == .freestanding or builtin.os.tag == .other) {        return null;    }    if (comptime (builtin.os.tag == .wasi or builtin.os.tag == .emscripten) and !builtin.link_libc) {        return null;    }     const view = block.view();    for (view.slice) |entry_z| {        const entry = std.mem.sliceTo(entry_z, 0);        if (entry.len <= key.len or entry[key.len] != '=') continue;        if (std.mem.eql(u8, entry[0..key.len], key)) return entry[key.len + 1 ..];    }    return null;} fn getenvFromRaw(raw: RawEnviron, key: []const u8) ?[]const u8 {    @setRuntimeSafety(false);    var i: usize = 0;    while (raw[i]) |entry_z| : (i += 1) {        const entry = std.mem.sliceTo(entry_z, 0);        if (entry.len <= key.len or entry[key.len] != '=') continue;        if (std.mem.eql(u8, entry[0..key.len], key)) return entry[key.len + 1 ..];    }    return null;} fn getenvFromLibc(key: []const u8) ?[]const u8 {    if (comptime !builtin.link_libc) return null;    if (key.len >= 128) return null;     var key_buf: [128]u8 = undefined;    @memcpy(key_buf[0..key.len], key);    key_buf[key.len] = 0;    const value_z = std.c.getenv(key_buf[0..key.len :0].ptr) orelse return null;    return std.mem.sliceTo(value_z, 0);} pub fn readFileToEnd(alloc: std.mem.Allocator, file: *std.Io.File, max_bytes: usize) ![]u8 {    const zio = getIo();    var read_buf: [8192]u8 = undefined;    var r = file.reader(zio, &read_buf);    return r.interface.allocRemaining(alloc, std.Io.Limit.limited(max_bytes));} pub fn readFileToEndZ(alloc: std.mem.Allocator, file: *std.Io.File, max_bytes: usize) ![:0]u8 {    const data = try readFileToEnd(alloc, file, max_bytes);    errdefer alloc.free(data);    const result = try alloc.realloc(data, data.len + 1);    result[data.len] = 0;    return result[0..data.len :0];} pub fn milliTimestamp() i64 {    const ts = std.Io.Timestamp.now(getIo(), .real);    return @intCast(@divFloor(ts.nanoseconds, 1_000_000));} pub fn nanoTimestamp() i128 {    const ts = std.Io.Timestamp.now(getIo(), .real);    return @intCast(ts.nanoseconds);} pub fn writeFileAtomic(alloc: std.mem.Allocator, path: []const u8, text: []const u8) !void {    e2eFailIfDurableMutationAttempted();    const maybe_existing_permissions = existingFilePermissions(path);    if (maybe_existing_permissions) |existing_permissions| {        if (existing_permissions.toMode() & 0o222 == 0) return error.AccessDenied;    }    const permissions = maybe_existing_permissions orelse .default_file;    const temp_path = try std.fmt.allocPrint(alloc, "{s}.tmp.{d}", .{ path, nanoTimestamp() });    defer alloc.free(temp_path);     var cleanup_temp = true;    defer if (cleanup_temp) std.Io.Dir.deleteFileAbsolute(getIo(), temp_path) catch {};     {        var file = try std.Io.Dir.createFileAbsolute(getIo(), temp_path, .{ .truncate = true, .permissions = permissions });        defer file.close(getIo());        try file.writeStreamingAll(getIo(), text);        try file.sync(getIo());    }     try std.Io.Dir.renameAbsolute(temp_path, path, getIo());    cleanup_temp = false;} const private_dir_permissions = std.Io.File.Permissions.fromMode(0o700);const private_file_permissions = std.Io.File.Permissions.fromMode(0o600); pub const VerifiedDir = struct {    dir: std.Io.Dir,     pub fn close(self: *VerifiedDir) void {        self.dir.close(getIo());        self.* = undefined;    }}; fn defaultSyncFile(_: ?*anyopaque, file: std.Io.File) anyerror!void {    try file.sync(getIo());} fn defaultSyncDir(_: ?*anyopaque, dir: std.Io.Dir) anyerror!void {    try syncVerifiedDir(dir);} pub const DurableOps = struct {    ctx: ?*anyopaque = null,    sync_file: *const fn (?*anyopaque, std.Io.File) anyerror!void = defaultSyncFile,    sync_dir: *const fn (?*anyopaque, std.Io.Dir) anyerror!void = defaultSyncDir,}; fn defaultTryLock(_: ?*anyopaque, file: std.Io.File) anyerror!bool {    return file.tryLock(getIo(), .exclusive);} fn defaultLockNow(_: ?*anyopaque) i64 {    return milliTimestamp();} fn defaultLockSleep(_: ?*anyopaque, millis: u64) void {    sleep(millis * std.time.ns_per_ms);} pub const LockOps = struct {    ctx: ?*anyopaque = null,    try_lock: *const fn (?*anyopaque, std.Io.File) anyerror!bool = defaultTryLock,    now_ms: *const fn (?*anyopaque) i64 = defaultLockNow,    sleep_ms: *const fn (?*anyopaque, u64) void = defaultLockSleep,}; pub const TimedAdvisoryLock = struct {    file: std.Io.File,     pub fn release(self: *TimedAdvisoryLock) void {        self.file.unlock(getIo());        self.file.close(getIo());        self.* = undefined;    }}; fn validateRelativeLeaf(name: []const u8) !void {    if (name.len == 0 or std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) {        return error.DurablePathUnsafe;    }    if (std.mem.indexOfAny(u8, name, "/\\") != null) return error.DurablePathUnsafe;} fn verifyPrivateRegularFile(file: std.Io.File) !void {    const stat = try file.stat(getIo());    if (stat.kind != .file or stat.nlink != 1) return error.DurablePathUnsafe;    if (stat.permissions.toMode() & 0o777 != 0o600) return error.PrivateStatePermissionsUnsupported;} fn verifyPrivateDirectory(dir: std.Io.Dir) !void {    const stat = try dir.stat(getIo());    if (stat.kind != .directory) return error.DurablePathUnsafe;    if (stat.permissions.toMode() & 0o777 != 0o700) return error.PrivateStatePermissionsUnsupported;} /// The handle must come from an `openDir` that requested iteration. Linux returns an/// `O_PATH` descriptor otherwise, and `fsync` rejects those with `EBADF`.pub fn syncVerifiedDir(dir: std.Io.Dir) !void {    if (comptime builtin.os.tag == .windows) return error.OperationUnsupported;    while (true) {        const rc = std.c.fsync(dir.handle);        if (rc == 0) return;        switch (std.c.errno(rc)) {            .INTR => continue,            .INVAL, .OPNOTSUPP => return error.OperationUnsupported,            .NOSPC => return error.NoSpaceLeft,            .DQUOT => return error.DiskQuota,            .ROFS => return error.ReadOnlyFileSystem,            else => return error.DirectorySyncFailed,        }    }} fn openOrCreateVerifiedPrivateChild(parent: std.Io.Dir, name: []const u8) !VerifiedDir {    e2eFailIfDurableMutationAttempted();    try validateRelativeLeaf(name);    const zio = getIo();     var created = false;    var dir = parent.openDir(zio, name, .{        .iterate = true,

Lines 1–500 of 1298.