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. Load more
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;