1330 lines · 39.9 KB · Zig
RawDownload
const std = @import("std");const io_mod = @import("../../core/shared/io.zig");const builtin = @import("builtin");const background_process_provider = @import(    "../../core/execution/background_process_provider.zig",);const background_launch_output = @import(    "../../core/background/background_launch_output.zig",);const debug_trace = @import("../../core/shared/debug_trace.zig");const host = @import("../../core/hosts/host.zig");const process_supervisor = @import(    "../../core/background/process_supervisor.zig",); const Allocator = std.mem.Allocator; const background_exit_marker = background_process_provider.exit_marker;const background_release_byte: u8 = 0x06;const background_ready_byte: u8 = 'R';const blocked_background_wrapper_command = std.fmt.comptimePrint(    "printf '{c}' >&2\n" ++        "release=\n" ++        "IFS= read -r release || exit 125\n" ++        "expected=$(printf '\\006')\n" ++        "[ \"$release\" = \"$expected\" ] || exit 125\n" ++        "script=$(command cat; command printf .)\n" ++        "script=${{script%.}}\n" ++        "exec 0</dev/null\n" ++        "exec 2>&1\n" ++        "trap '' HUP\n" ++        "eval \"$script\"\n" ++        "status=$?\n" ++        "printf '\\n{s}%s\\n' \"$status\"\n" ++        "exit \"$status\"",    .{ background_ready_byte, background_exit_marker },); pub const provider = background_process_provider.Provider{    .spawn_prepared_fn = spawnPrepared,    .capture_token_fn = captureToken,    .match_token_fn = matchToken,    .signal_process_fn = signalProcess,}; const PreparedState = struct {    alloc: Allocator,    handshake: SpawnedBackgroundHandshake,}; const OwnedState = struct {    alloc: Allocator,    child: std.process.Child,}; fn spawnPrepared(    _: ?*anyopaque,    alloc: Allocator,    request: background_process_provider.SpawnRequest,) background_process_provider.ProviderError!background_process_provider.PreparedProcess {    if (!host.current().background_processes) return error.Unsupported;     const direct_argv = [_][]const u8{        "sh",        "-lc",        blocked_background_wrapper_command,        "fx-background",    };    var sandbox_argv: [7][]const u8 = undefined;    const argv: []const []const u8 = switch (request.isolation) {        .none => &direct_argv,        .macos_profile => |profile_path| blk: {            if (builtin.os.tag != .macos) return error.Unsupported;            sandbox_argv = .{                "/usr/bin/sandbox-exec",                "-f",                profile_path,                "sh",                "-lc",                blocked_background_wrapper_command,                "fx-background",            };            break :blk &sandbox_argv;        },    };    var child = try std.process.spawn(io_mod.getIo(), .{        .argv = argv,        .cwd = .{ .path = request.cwd },        .stdin = .pipe,        .stdout = .{ .file = background_launch_output.Output            .childStdioFileForProvider(request.output) },        .stderr = .pipe,    });    var child_owned = true;    errdefer if (child_owned) {        if (child.stdin) |stdin| stdin.close(io_mod.getIo());        if (child.stderr) |stderr| stderr.close(io_mod.getIo());        child.stdin = null;        child.stderr = null;        _ = child.wait(io_mod.getIo()) catch {};    };     const child_id = child.id orelse return error.SpawnFailed;    const pid = try std.fmt.allocPrint(alloc, "{d}", .{child_id});    var pid_owned = true;    errdefer if (pid_owned) alloc.free(pid);     var release_write = child.stdin orelse return error.SpawnFailed;    child.stdin = null;    var release_owned = true;    errdefer if (release_owned) release_write.close(io_mod.getIo());    var ready_read = child.stderr orelse return error.SpawnFailed;    child.stderr = null;    var ready_owned = true;    errdefer if (ready_owned) ready_read.close(io_mod.getIo());     var handshake = SpawnedBackgroundHandshake{        .child = child,        .ready_read = ready_read,        .release_write = release_write,        .pid = pid,    };    child_owned = false;    pid_owned = false;    release_owned = false;    ready_owned = false;     var ready: [1]u8 = undefined;    const count = handshake.ready_read.readStreaming(        io_mod.getIo(),        &.{&ready},    ) catch |err| return cleanupFailedHandshake(alloc, &handshake, err);    if (count != 1 or ready[0] != background_ready_byte) {        return cleanupFailedHandshake(            alloc,            &handshake,            error.BackgroundWrapperNotReady,        );    }     const state = alloc.create(PreparedState) catch {        return cleanupFailedHandshake(            alloc,            &handshake,            error.OutOfMemory,        );    };    state.* = .{ .alloc = alloc, .handshake = handshake };    return .{        .context = state,        .pid = state.handshake.pid,        .close_and_wait_fn = closeAndWaitPrepared,        .wait_for_exit_fn = waitForPreparedExit,        .detach_reaper_fn = detachPreparedReaper,        .release_fn = releasePrepared,    };} fn cleanupFailedHandshake(    alloc: Allocator,    handshake: *SpawnedBackgroundHandshake,    cause: background_process_provider.ProviderError,) background_process_provider.ProviderError {    if (handshake.closeAndWaitUnreleased(        alloc,        null,        2000,    ) == .confirmed) return cause;    _ = handshake.detachUnreleasedReaper(alloc);    return error.BackgroundProcessIdentityIndeterminate;} fn closeAndWaitPrepared(    raw: *anyopaque,    token: ?process_supervisor.ProcessInstanceToken,    timeout_ms: i64,) background_process_provider.CleanupStatus {    const state: *PreparedState = @ptrCast(@alignCast(raw));    const status = state.handshake.closeAndWaitUnreleased(        state.alloc,        token,        timeout_ms,    );    if (status == .confirmed) state.alloc.destroy(state);    return switch (status) {        .confirmed => .confirmed,        .timed_out => .timed_out,    };} fn waitForPreparedExit(    raw: *anyopaque,    token: ?process_supervisor.ProcessInstanceToken,    timeout_ms: i64,) bool {    const state: *PreparedState = @ptrCast(@alignCast(raw));    const exited = state.handshake.waitForUnreleasedExit(        state.alloc,        token,        timeout_ms,    );    if (exited) state.alloc.destroy(state);    return exited;} fn detachPreparedReaper(raw: *anyopaque) bool {    const state: *PreparedState = @ptrCast(@alignCast(raw));    if (!state.handshake.detachUnreleasedReaper(state.alloc)) return false;    state.alloc.destroy(state);    return true;} fn releasePrepared(    raw: *anyopaque,    original_command: []const u8,) background_process_provider.ProviderError!background_process_provider.OwnedProcess {    const state: *PreparedState = @ptrCast(@alignCast(raw));    const owned = try state.alloc.create(OwnedState);    errdefer state.alloc.destroy(owned);     try state.handshake.release_write.writeStreamingAll(        io_mod.getIo(),        &.{ background_release_byte, '\n' },    );    try state.handshake.release_write.writeStreamingAll(        io_mod.getIo(),        original_command,    );    state.handshake.release_write.close(io_mod.getIo());    state.handshake.ready_read.close(io_mod.getIo());    state.handshake.child.stdin = null;    state.handshake.child.stderr = null;     owned.* = .{ .alloc = state.alloc, .child = state.handshake.child };    state.alloc.free(state.handshake.pid);    state.alloc.destroy(state);    return .{        .context = owned,        .wait_fn = waitOwned,        .forget_fn = forgetOwned,    };} fn waitOwned(raw: *anyopaque) void {    const state: *OwnedState = @ptrCast(@alignCast(raw));    _ = state.child.wait(io_mod.getIo()) catch {};    state.alloc.destroy(state);} fn forgetOwned(raw: *anyopaque) void {    const state: *OwnedState = @ptrCast(@alignCast(raw));    state.alloc.destroy(state);} fn isValidPidText(pid: []const u8) bool {    return background_process_provider.isValidPidText(pid);} fn captureToken(    _: ?*anyopaque,    alloc: Allocator,    pid_text: []const u8,) background_process_provider.ProviderError!process_supervisor.ProcessInstanceToken {    const pid = std.fmt.parseInt(std.posix.pid_t, pid_text, 10) catch        return error.InvalidPid;    return switch (builtin.os.tag) {        .linux => captureLinuxToken(alloc, pid) catch |err| switch (err) {            error.OutOfMemory => error.OutOfMemory,            error.ProcessNotFound => error.ProcessNotFound,            else => error.ProcessIdentityUnavailable,        },        .macos => captureMacOSToken(pid) catch |err| switch (err) {            error.ProcessNotFound => error.ProcessNotFound,            else => error.ProcessIdentityUnavailable,        },        else => error.ProcessIdentityUnsupported,    };} fn matchToken(    context: ?*anyopaque,    alloc: Allocator,    pid: []const u8,    expected: process_supervisor.ProcessInstanceToken,) process_supervisor.TokenMatch {    const actual = captureToken(context, alloc, pid) catch |err| {        return if (err == error.ProcessNotFound) .missing else .unavailable;    };    return if (actual.eql(expected)) .matched else .mismatched;} fn readLinuxProcStat(file: std.Io.File, buffer: []u8) !usize {    if (builtin.os.tag != .linux) return error.ProcessIdentityUnsupported;    while (true) {        // A process can disappear after open, and procfs reports that read as        // ESRCH. Read directly so the expected race does not reach Zig's        // unexpected-errno reporter before classification.        const rc = std.posix.system.read(file.handle, buffer.ptr, buffer.len);        switch (std.posix.errno(rc)) {            .SUCCESS => {                const read_len: usize = @intCast(rc);                if (read_len == 0) return error.ProcessNotFound;                return read_len;            },            .INTR => continue,            .SRCH => return error.ProcessNotFound,            else => return error.ProcessIdentityUnavailable,        }    }} fn captureLinuxToken(    alloc: Allocator,    pid: std.posix.pid_t,) !process_supervisor.ProcessInstanceToken {    const zio = io_mod.getIo();    var boot_id_file = std.Io.Dir.openFileAbsolute(        zio,        "/proc/sys/kernel/random/boot_id",        .{},    ) catch |err| switch (err) {        error.FileNotFound => return error.ProcessIdentityUnavailable,        else => return err,    };    defer boot_id_file.close(zio);    var boot_id_text: [128]u8 = undefined;    var boot_id_reader = boot_id_file.readerStreaming(zio, &.{});    const boot_id_text_len = boot_id_reader.interface.readSliceShort(        &boot_id_text,    ) catch return boot_id_reader.err.?;     var boot_id: [32]u8 = undefined;    var boot_len: usize = 0;    for (std.mem.trim(u8, boot_id_text[0..boot_id_text_len], " \r\n\t")) |byte| {        if (byte == '-') continue;        if (!std.ascii.isHex(byte) or std.ascii.isUpper(byte) or            boot_len == boot_id.len)        {            return error.ProcessIdentityUnavailable;        }        boot_id[boot_len] = byte;        boot_len += 1;    }    if (boot_len != boot_id.len) return error.ProcessIdentityUnavailable;     const stat_path = try std.fmt.allocPrint(alloc, "/proc/{d}/stat", .{pid});    defer alloc.free(stat_path);    var stat_file = std.Io.Dir.openFileAbsolute(        zio,        stat_path,        .{},    ) catch |err| switch (err) {        error.FileNotFound => return error.ProcessNotFound,        else => return err,    };    defer stat_file.close(zio);    var stat_text: [4096]u8 = undefined;    const stat_text_len = try readLinuxProcStat(stat_file, &stat_text);    const stat = stat_text[0..stat_text_len];     const close_paren = std.mem.lastIndexOfScalar(u8, stat, ')') orelse        return error.ProcessIdentityUnavailable;    var fields = std.mem.tokenizeScalar(        u8,        stat[close_paren + 1 ..],        ' ',    );    var field_number: usize = 3;    var start_ticks: ?[]const u8 = null;    while (fields.next()) |field| : (field_number += 1) {        if (field_number == 22) {            start_ticks = field;            break;        }    }    const ticks = start_ticks orelse return error.ProcessIdentityUnavailable;    _ = std.fmt.parseUnsigned(u64, ticks, 10) catch        return error.ProcessIdentityUnavailable;     var token_buf: [128]u8 = undefined;    const text = try std.fmt.bufPrint(        &token_buf,        "linux:{s}:{s}",        .{ boot_id[0..], ticks },    );    return process_supervisor.ProcessInstanceToken.parse(text);} fn captureMacOSToken(    pid: std.posix.pid_t,) !process_supervisor.ProcessInstanceToken {    if (builtin.os.tag != .macos) return error.ProcessIdentityUnsupported;    const ProcBsdInfo = extern struct {        pbi_flags: u32,        pbi_status: u32,        pbi_xstatus: u32,        pbi_pid: u32,        pbi_ppid: u32,        pbi_uid: u32,        pbi_gid: u32,        pbi_ruid: u32,        pbi_rgid: u32,        pbi_svuid: u32,        pbi_svgid: u32,        rfu_1: u32,        pbi_comm: [16]u8,        pbi_name: [32]u8,        pbi_nfiles: u32,        pbi_pgid: u32,        pbi_pjobc: u32,        e_tdev: u32,        e_tpgid: u32,        pbi_nice: i32,        pbi_start_tvsec: u64,        pbi_start_tvusec: u64,    };    const Darwin = struct {        extern "c" fn proc_pidinfo(            pid_value: c_int,            flavor: c_int,            arg: u64,            buffer: *anyopaque,            buffersize: c_int,        ) c_int;        extern "c" fn sysctlbyname(            name: [*:0]const u8,            oldp: ?*anyopaque,            oldlenp: *usize,            newp: ?*const anyopaque,            newlen: usize,        ) c_int;    };     var info: ProcBsdInfo = undefined;    const read_len = Darwin.proc_pidinfo(        pid,        3,        0,        &info,        @sizeOf(ProcBsdInfo),    );    if (read_len == 0) return error.ProcessNotFound;    if (read_len != @sizeOf(ProcBsdInfo)) {        return error.ProcessIdentityUnavailable;    }     var uuid_buf: [64]u8 = undefined;    var uuid_len: usize = uuid_buf.len;    if (Darwin.sysctlbyname(        "kern.bootsessionuuid",        &uuid_buf,        &uuid_len,        null,        0,    ) != 0) return error.ProcessIdentityUnavailable;     var uuid: [32]u8 = undefined;    var normalized_len: usize = 0;    for (uuid_buf[0..uuid_len]) |byte| {        if (byte == 0) break;        if (byte == '-') continue;        const lower = std.ascii.toLower(byte);        if (!std.ascii.isHex(lower) or normalized_len == uuid.len) {            return error.ProcessIdentityUnavailable;        }        uuid[normalized_len] = lower;        normalized_len += 1;    }    if (normalized_len != uuid.len) return error.ProcessIdentityUnavailable;     var token_buf: [128]u8 = undefined;    const text = try std.fmt.bufPrint(        &token_buf,        "macos:{s}:{d}:{d}",        .{ uuid[0..], info.pbi_start_tvsec, info.pbi_start_tvusec },    );    return process_supervisor.ProcessInstanceToken.parse(text);} const PidPair = struct {    pid: std.posix.pid_t,    ppid: std.posix.pid_t,}; fn signalProcess(    context: ?*anyopaque,    alloc: Allocator,    pid_text: []const u8,    expected: process_supervisor.ProcessInstanceToken,) background_process_provider.ProviderError!void {    switch (matchToken(context, alloc, pid_text, expected)) {        .matched => {},        .missing, .mismatched => {            return error.BackgroundProcessIdentityMismatch;        },        .unavailable => {            return error.BackgroundProcessIdentityIndeterminate;        },    }    if (!host.current().background_processes) return error.Unsupported;

Lines 1–500 of 1330.