Something went wrong. Try again.
My version of https://github.com/vercel-labs/fx
Something went wrong. Try again.
Zig
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832const std = @import("std");const builtin = @import("builtin");const io_mod = @import("../shared/io.zig");
const Allocator = std.mem.Allocator;
const Identity = union(enum) { linux_start_ticks: u64, macos_unique_id: u64,
fn eql(self: Identity, other: Identity) bool { return switch (self) { .linux_start_ticks => |ticks| switch (other) { .linux_start_ticks => |other_ticks| ticks == other_ticks, else => false, }, .macos_unique_id => |unique_id| switch (other) { .macos_unique_id => |other_unique_id| unique_id == other_unique_id, else => false, }, }; }};
const TrackedProcess = struct { pid: std.posix.pid_t, identity: Identity,};
const ProcessSnapshot = struct { identity: Identity, parent_pid: std.posix.pid_t, parent_unique_id: ?u64 = null,};
pub const DeliverySummary = struct { delivered: usize = 0, incomplete: bool = false,};
const ProcessGroupState = union(enum) { found: std.posix.pid_t, vanished, unavailable,};
const SystemSignalEffects = struct { fn capture(alloc: Allocator, pid: std.posix.pid_t) !ProcessSnapshot { return captureSnapshot(alloc, pid); }
fn processGroup(pid: std.posix.pid_t) ProcessGroupState { return inspectProcessGroup(pid); }
fn send(pid: std.posix.pid_t, signal: std.posix.SIG) std.posix.KillError!void { return std.posix.kill(pid, signal); }};
/// Tracks a command root and descendants across new sessions or process/// groups. Identity checks guard both traversal and signaling against PID/// reuse.pub const Tracker = struct { alloc: Allocator, root: ?TrackedProcess = null, processes: std.ArrayList(TrackedProcess) = .empty, macos_child_buffer: []std.posix.pid_t = &.{}, macos_pid_buffer: []std.posix.pid_t = &.{},
pub fn init(alloc: Allocator) !Tracker { var tracker = Tracker{ .alloc = alloc }; errdefer tracker.deinit(); if (comptime builtin.os.tag == .macos) { const reported = Darwin.proc_listchildpids(0, null, 0); const capacity: usize = if (reported > 0) @max(@as(usize, @intCast(reported)) + 256, 1024) else 1024; tracker.macos_child_buffer = try alloc.alloc( std.posix.pid_t, capacity, ); const process_count = Darwin.proc_listallpids(null, 0); const process_capacity: usize = if (process_count > 0) @max(@as(usize, @intCast(process_count)) + 256, 1024) else 1024; tracker.macos_pid_buffer = try alloc.alloc( std.posix.pid_t, process_capacity, ); } return tracker; }
pub fn deinit(self: *Tracker) void { self.processes.deinit(self.alloc); if (self.macos_child_buffer.len > 0) { self.alloc.free(self.macos_child_buffer); } if (self.macos_pid_buffer.len > 0) { self.alloc.free(self.macos_pid_buffer); } self.* = undefined; }
pub fn refresh(self: *Tracker, root_pid: std.posix.pid_t) !void { const root_snapshot: ?ProcessSnapshot = captureSnapshot(self.alloc, root_pid) catch |err| switch (err) { error.ProcessNotFound => null, else => return err, }; var traverse_root = false; if (root_snapshot) |snapshot| { if (self.root) |root| { traverse_root = root.pid == root_pid and root.identity.eql(snapshot.identity); } else { self.root = .{ .pid = root_pid, .identity = snapshot.identity, }; traverse_root = true; } } if (traverse_root) try self.appendDirectChildren(self.root.?); if (comptime builtin.os.tag == .macos) { if (root_snapshot == null) try self.refreshLineageProcesses(); }
var parent_index: usize = 0; while (parent_index < self.processes.items.len) : (parent_index += 1) { const parent = self.processes.items[parent_index]; const actual = captureSnapshot(self.alloc, parent.pid) catch continue; if (!shouldTraverseParent(parent.identity, actual.identity)) continue; try self.appendDirectChildren(parent); } }
pub fn refreshAdditionalRoot( self: *Tracker, root_pid: std.posix.pid_t, ) !void { const snapshot = captureSnapshot(self.alloc, root_pid) catch |err| switch (err) { error.ProcessNotFound => return, else => return err, }; try self.appendDirectChildren(.{ .pid = root_pid, .identity = snapshot.identity, }); }
pub fn refreshLineageProcesses(self: *Tracker) !void { if (comptime builtin.os.tag != .macos) return; const reported = Darwin.proc_listallpids(null, 0); if (reported <= 0) return; const required = @as(usize, @intCast(reported)) + 256; if (required > self.macos_pid_buffer.len) { self.macos_pid_buffer = try self.alloc.realloc( self.macos_pid_buffer, required, ); } const count = Darwin.proc_listallpids( self.macos_pid_buffer.ptr, @intCast(self.macos_pid_buffer.len * @sizeOf(std.posix.pid_t)), ); if (count <= 0) return; const process_count = @min( @as(usize, @intCast(count)), self.macos_pid_buffer.len, ); var changed = true; while (changed) { changed = false; for (self.macos_pid_buffer[0..process_count]) |pid| { if (pid <= 0 or pid == std.c.getpid()) continue; if (try self.trackLineageProcess(pid)) changed = true; } } }
pub fn signalAll(self: *Tracker, signal: std.posix.SIG) usize { return self.signalProcessesChecked(signal, null).delivered; }
pub fn signalOutsideProcessGroup( self: *Tracker, signal: std.posix.SIG, preserved_group: std.posix.pid_t, ) usize { return self.signalProcessesChecked(signal, preserved_group).delivered; }
pub fn signalOutsideProcessGroupChecked( self: *Tracker, signal: std.posix.SIG, preserved_group: std.posix.pid_t, ) DeliverySummary { return self.signalProcessesChecked(signal, preserved_group); }
fn signalProcessesChecked( self: *Tracker, signal: std.posix.SIG, preserved_group: ?std.posix.pid_t, ) DeliverySummary { return self.signalProcessesWith( signal, preserved_group, SystemSignalEffects, ); }
fn signalProcessesWith( self: *Tracker, signal: std.posix.SIG, preserved_group: ?std.posix.pid_t, comptime Effects: type, ) DeliverySummary { var summary: DeliverySummary = .{}; var index = self.processes.items.len; while (index > 0) { index -= 1; self.signalTrackedProcessWith( self.processes.items[index], signal, preserved_group, &summary, Effects, ); } if (self.root) |root| { self.signalTrackedProcessWith( root, signal, preserved_group, &summary, Effects, ); } return summary; }
fn signalTrackedProcessWith( self: *Tracker, process: TrackedProcess, signal: std.posix.SIG, preserved_group: ?std.posix.pid_t, summary: *DeliverySummary, comptime Effects: type, ) void { const actual = Effects.capture(self.alloc, process.pid) catch |err| { if (err != error.ProcessNotFound) summary.incomplete = true; return; }; if (!process.identity.eql(actual.identity)) return; const process_group = switch (Effects.processGroup(process.pid)) { .found => |value| value, .vanished => return, .unavailable => { summary.incomplete = true; return; }, }; if (!shouldSignalProcess(process_group, preserved_group)) return; Effects.send(process.pid, signal) catch |err| switch (err) { error.ProcessNotFound => return, else => { summary.incomplete = true; return; }, }; summary.delivered += 1; }
pub fn anyAlive(self: *Tracker) bool { if (self.root) |root| { const actual: ?ProcessSnapshot = captureSnapshot(self.alloc, root.pid) catch null; if (actual) |snapshot| { if (root.identity.eql(snapshot.identity)) return true; } } for (self.processes.items) |process| { const actual = captureSnapshot(self.alloc, process.pid) catch continue; if (process.identity.eql(actual.identity)) return true; } return false; }
fn appendDirectChildren( self: *Tracker, parent: TrackedProcess, ) !void { switch (builtin.os.tag) { .linux => try self.appendLinuxChildren(parent), .macos => try self.appendMacOSChildren(parent), else => return error.ProcessTreeUnsupported, } }
fn appendLinuxChildren( self: *Tracker, parent: TrackedProcess, ) !void { if (comptime builtin.os.tag != .linux) return error.ProcessTreeUnsupported; if (!try self.parentIdentityMatches(parent)) return; const task_path = try std.fmt.allocPrint( self.alloc, "/proc/{d}/task", .{parent.pid}, ); defer self.alloc.free(task_path); var task_dir = (try openLinuxProcDir(task_path)) orelse return; defer task_dir.close(io_mod.getIo()); var tasks = task_dir.iterate(); while (try tasks.next(io_mod.getIo())) |entry| { const tid = std.fmt.parseInt( std.posix.pid_t, entry.name, 10, ) catch continue; if (tid <= 0) continue; try self.appendLinuxTaskChildren(parent, tid); } }
fn appendLinuxTaskChildren( self: *Tracker, parent: TrackedProcess, tid: std.posix.pid_t, ) !void { const path = try std.fmt.allocPrint( self.alloc, "/proc/{d}/task/{d}/children", .{ parent.pid, tid }, ); defer self.alloc.free(path); var file = (try openLinuxProcFile(path)) orelse return; defer file.close(io_mod.getIo()); var buffer: [64 * 1024]u8 = undefined; const read_len = readLinuxChildrenFile(file, &buffer) catch |err| switch (err) { error.ProcessNotFound => return, else => return err, }; if (!try self.parentIdentityMatches(parent)) return; var children = std.mem.tokenizeAny(u8, buffer[0..read_len], " \t\r\n"); while (children.next()) |pid_text| { const pid = std.fmt.parseInt(std.posix.pid_t, pid_text, 10) catch continue; try self.trackChild(pid, parent.pid); } }
fn appendMacOSChildren( self: *Tracker, parent: TrackedProcess, ) !void { if (comptime builtin.os.tag != .macos) return error.ProcessTreeUnsupported; if (!try self.parentIdentityMatches(parent)) return; const count = Darwin.proc_listchildpids( parent.pid, self.macos_child_buffer.ptr, @intCast(self.macos_child_buffer.len * @sizeOf(std.posix.pid_t)), ); if (count <= 0) return; const child_count = @min( @as(usize, @intCast(count)), self.macos_child_buffer.len, ); if (!try self.parentIdentityMatches(parent)) return; for (self.macos_child_buffer[0..child_count]) |pid| { if (pid > 0) try self.trackChild(pid, parent.pid); } }
fn parentIdentityMatches(self: *Tracker, parent: TrackedProcess) !bool { const snapshot = captureSnapshot(self.alloc, parent.pid) catch |err| switch (err) { error.ProcessNotFound => return false, else => return err, }; return parent.identity.eql(snapshot.identity); }
fn trackChild( self: *Tracker, pid: std.posix.pid_t, expected_parent_pid: std.posix.pid_t, ) !void { const snapshot = captureSnapshot(self.alloc, pid) catch |err| switch (err) { error.ProcessNotFound => return, else => return err, }; if (!snapshotBelongsToParent(snapshot, expected_parent_pid)) return; if (self.root) |root| { if (root.pid == pid and root.identity.eql(snapshot.identity)) return; } for (self.processes.items) |*process| { if (process.pid != pid) continue; process.identity = snapshot.identity; return; } try self.processes.append(self.alloc, .{ .pid = pid, .identity = snapshot.identity, }); }
fn trackLineageProcess(self: *Tracker, pid: std.posix.pid_t) !bool { const snapshot = captureSnapshot(self.alloc, pid) catch return false; const parent_unique_id = snapshot.parent_unique_id orelse return false; if (!self.containsMacOSUniqueId(parent_unique_id)) return false; if (self.root) |root| { if (root.pid == pid and root.identity.eql(snapshot.identity)) return false; } for (self.processes.items) |*process| { if (process.pid != pid) continue; if (process.identity.eql(snapshot.identity)) return false; process.identity = snapshot.identity; return true; } try self.processes.append(self.alloc, .{ .pid = pid, .identity = snapshot.identity, }); return true; }
fn containsMacOSUniqueId(self: *Tracker, unique_id: u64) bool { if (self.root) |root| { if (identityHasMacOSUniqueId(root.identity, unique_id)) return true; } for (self.processes.items) |process| { if (identityHasMacOSUniqueId(process.identity, unique_id)) return true; } return false; }};
fn identityHasMacOSUniqueId(identity: Identity, unique_id: u64) bool { return switch (identity) { .macos_unique_id => |actual| actual == unique_id, else => false, };}
fn shouldTraverseParent(expected: Identity, actual: Identity) bool { return expected.eql(actual);}
fn snapshotBelongsToParent( snapshot: ProcessSnapshot, expected_parent_pid: std.posix.pid_t,) bool { return snapshot.parent_pid == expected_parent_pid;}
fn shouldSignalProcess( process_group: ?std.posix.pid_t, preserved_group: ?std.posix.pid_t,) bool { const preserved = preserved_group orelse return true; const actual = process_group orelse return false; return actual != preserved;}
fn inspectProcessGroup(pid: std.posix.pid_t) ProcessGroupState { if (comptime builtin.os.tag == .windows or builtin.os.tag == .wasi) { return .unavailable; } const process_group = getpgid(pid); if (process_group >= 0) return .{ .found = process_group }; return switch (std.c.errno(process_group)) { .SRCH => .vanished, else => .unavailable, };}
extern "c" fn getpgid(pid: std.posix.pid_t) std.posix.pid_t;
fn readLinuxChildrenFile(file: std.Io.File, buffer: []u8) !usize { if (comptime builtin.os.tag != .linux) return error.ProcessTreeUnsupported; while (true) { const result = std.posix.system.read(file.handle, buffer.ptr, buffer.len); switch (std.posix.errno(result)) { .SUCCESS => return @intCast(result), .INTR => continue, .SRCH, .NOENT => return error.ProcessNotFound, else => return error.ProcessTreeInspectionFailed, } }}
fn openLinuxProcDir(path: []const u8) !?std.Io.Dir { if (comptime builtin.os.tag != .linux) return error.ProcessTreeUnsupported; // A process can disappear between identity validation and opening its // procfs entry. The POSIX wrapper maps Linux's ESRCH to FileNotFound; // std.Io currently treats ESRCH from directory opens as unexpected. const fd = std.posix.openat(std.posix.AT.FDCWD, path, .{ .ACCMODE = .RDONLY, .NOFOLLOW = true, .DIRECTORY = true, .CLOEXEC = true, }, 0) catch |err| switch (err) { error.FileNotFound => return null, else => return err, }; return .{ .handle = fd };}
fn openLinuxProcFile(path: []const u8) !?std.Io.File { if (comptime builtin.os.tag != .linux) return error.ProcessTreeUnsupported; const fd = std.posix.openat(std.posix.AT.FDCWD, path, .{ .ACCMODE = .RDONLY, .NOFOLLOW = true, .CLOEXEC = true, }, 0) catch |err| switch (err) { error.FileNotFound => return null, else => return err, }; return .{ .handle = fd, .flags = .{ .nonblocking = false }, };}
test "Linux proc helpers treat missing process data as vanished" { if (builtin.os.tag != .linux) return error.SkipZigTest; try std.testing.expect( (try openLinuxProcDir("/proc/self/fx-process-tree-missing")) == null, ); try std.testing.expect( (try openLinuxProcFile("/proc/self/fx-process-tree-missing")) == null, );}
test "process-group exclusion preserves the captured command grace" { try std.testing.expect(shouldSignalProcess(41, null)); try std.testing.expect(!shouldSignalProcess(41, 41)); try std.testing.expect(shouldSignalProcess(42, 41)); try std.testing.expect(!shouldSignalProcess(null, 41));}
test "stale process identities cannot become traversal roots" { try std.testing.expect(shouldTraverseParent( .{ .linux_start_ticks = 41 }, .{ .linux_start_ticks = 41 }, )); try std.testing.expect(!shouldTraverseParent( .{ .linux_start_ticks = 41 }, .{ .linux_start_ticks = 42 }, )); try std.testing.expect(!shouldTraverseParent( .{ .linux_start_ticks = 41 }, .{ .macos_unique_id = 41 }, ));}
test "child admission binds the observed process to its expected parent" { const snapshot = ProcessSnapshot{ .identity = .{ .linux_start_ticks = 42 }, .parent_pid = 17, }; try std.testing.expect(snapshotBelongsToParent(snapshot, 17)); try std.testing.expect(!snapshotBelongsToParent(snapshot, 18));}
test "macOS lineage identity matches only the same unique process" { try std.testing.expect(identityHasMacOSUniqueId( .{ .macos_unique_id = 42 }, 42, )); try std.testing.expect(!identityHasMacOSUniqueId( .{ .macos_unique_id = 42 }, 43, )); try std.testing.expect(!identityHasMacOSUniqueId( .{ .linux_start_ticks = 42 }, 42, ));}
test "checked signal delivery distinguishes vanished stale and failed targets" { const FakeEffects = struct { fn capture(_: Allocator, pid: std.posix.pid_t) !ProcessSnapshot { return switch (pid) { 11 => error.ProcessNotFound, 12 => error.ProcessIdentityUnavailable, 13 => .{ .identity = .{ .linux_start_ticks = 113 }, .parent_pid = 1, }, else => .{ .identity = .{ .linux_start_ticks = @intCast(pid) }, .parent_pid = 1, }, }; }
fn processGroup(pid: std.posix.pid_t) ProcessGroupState { return switch (pid) { 14 => .vanished, 15 => .unavailable, 16 => .{ .found = 41 }, else => .{ .found = pid + 100 }, }; }
fn send(pid: std.posix.pid_t, _: std.posix.SIG) std.posix.KillError!void { return switch (pid) { 17 => error.PermissionDenied, 18 => error.ProcessNotFound, else => {}, }; } };
var tracker = Tracker{ .alloc = std.testing.allocator }; defer tracker.deinit(); for (10..19) |pid| { try tracker.processes.append(std.testing.allocator, .{ .pid = @intCast(pid), .identity = .{ .linux_start_ticks = pid }, }); }
const summary = tracker.signalProcessesWith( std.posix.SIG.TERM, 41, FakeEffects, ); try std.testing.expectEqual(@as(usize, 1), summary.delivered); try std.testing.expect(summary.incomplete);}
test "checked signal delivery keeps vanished stale and excluded targets complete" { const FakeEffects = struct { fn capture(_: Allocator, pid: std.posix.pid_t) !ProcessSnapshot { if (pid == 21) return error.ProcessNotFound; return .{ .identity = .{ .linux_start_ticks = if (pid == 22) 122 else @as(u64, @intCast(pid)) }, .parent_pid = 1, }; }
fn processGroup(pid: std.posix.pid_t) ProcessGroupState { return switch (pid) { 23 => .vanished, else => .{ .found = 41 }, }; }
fn send(_: std.posix.pid_t, _: std.posix.SIG) std.posix.KillError!void { return; } };
var tracker = Tracker{ .alloc = std.testing.allocator }; defer tracker.deinit(); for (21..25) |pid| { try tracker.processes.append(std.testing.allocator, .{ .pid = @intCast(pid), .identity = .{ .linux_start_ticks = pid }, }); }
const summary = tracker.signalProcessesWith( std.posix.SIG.TERM, 41, FakeEffects, ); try std.testing.expectEqual(@as(usize, 0), summary.delivered); try std.testing.expect(!summary.incomplete);}
fn captureSnapshot(alloc: Allocator, pid: std.posix.pid_t) !ProcessSnapshot { return switch (builtin.os.tag) { .linux => try captureLinuxSnapshot(alloc, pid), .macos => try captureMacOSSnapshot(pid), else => error.ProcessTreeUnsupported, };}
fn captureLinuxSnapshot(alloc: Allocator, pid: std.posix.pid_t) !ProcessSnapshot { if (comptime builtin.os.tag != .linux) return error.ProcessTreeUnsupported; const path = try std.fmt.allocPrint(alloc, "/proc/{d}/stat", .{pid}); defer alloc.free(path); var file = (try openLinuxProcFile(path)) orelse return error.ProcessNotFound; defer file.close(io_mod.getIo()); var buffer: [4096]u8 = undefined; const read_len = try readLinuxProcFile(file, &buffer); const stat = buffer[0..read_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 parent_pid: ?std.posix.pid_t = null; while (fields.next()) |field| : (field_number += 1) { if (field_number == 4) { parent_pid = std.fmt.parseInt(std.posix.pid_t, field, 10) catch return error.ProcessIdentityUnavailable; } if (field_number == 22) { const start_ticks = std.fmt.parseUnsigned(u64, field, 10) catch return error.ProcessIdentityUnavailable; return .{ .identity = .{ .linux_start_ticks = start_ticks }, .parent_pid = parent_pid orelse return error.ProcessIdentityUnavailable, }; } } return error.ProcessIdentityUnavailable;}
fn readLinuxProcFile(file: std.Io.File, buffer: []u8) !usize { if (comptime builtin.os.tag != .linux) return error.ProcessTreeUnsupported; while (true) { const result = std.posix.system.read(file.handle, buffer.ptr, buffer.len); switch (std.posix.errno(result)) { .SUCCESS => { const read_len: usize = @intCast(result); if (read_len == 0) return error.ProcessNotFound; return read_len; }, .INTR => continue, .SRCH => return error.ProcessNotFound, else => return error.ProcessIdentityUnavailable, } }}
fn captureMacOSSnapshot(pid: std.posix.pid_t) !ProcessSnapshot { if (comptime builtin.os.tag != .macos) return error.ProcessTreeUnsupported; var unique: Darwin.ProcUniqueIdentifierInfo = undefined; const unique_len = Darwin.proc_pidinfo( pid, Darwin.proc_pid_unique_identifier_info, 0, &unique, @sizeOf(Darwin.ProcUniqueIdentifierInfo), ); if (unique_len == 0) return error.ProcessNotFound; if (unique_len != @sizeOf(Darwin.ProcUniqueIdentifierInfo)) { return error.ProcessIdentityUnavailable; } var info: Darwin.ProcBsdInfo = undefined; const read_len = Darwin.proc_pidinfo( pid, 3, 0, &info, @sizeOf(Darwin.ProcBsdInfo), ); if (read_len == 0) return error.ProcessNotFound; if (read_len != @sizeOf(Darwin.ProcBsdInfo)) { return error.ProcessIdentityUnavailable; } return .{ .identity = .{ .macos_unique_id = unique.p_uniqueid }, .parent_pid = @intCast(info.pbi_ppid), .parent_unique_id = unique.p_puniqueid, };}
const Darwin = struct { // Stable libproc process-identity flavor; the SDK omits this constant from // its public header, but XNU defines the record as API with a fixed size. const proc_pid_unique_identifier_info: c_int = 17;
const ProcUniqueIdentifierInfo = extern struct { p_uuid: [16]u8, p_uniqueid: u64, p_puniqueid: u64, p_idversion: i32, p_orig_ppidversion: i32, p_reserve2: u64, p_reserve3: u64, };
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, };
extern "c" fn proc_listchildpids( ppid: c_int, buffer: ?*anyopaque, buffersize: c_int, ) c_int;
extern "c" fn proc_listallpids( buffer: ?*anyopaque, buffersize: c_int, ) c_int;
extern "c" fn proc_pidinfo( pid: c_int, flavor: c_int, arg: u64, buffer: *anyopaque, buffersize: c_int, ) c_int;};
test "tracked identity distinguishes process instances" { const linux = Identity{ .linux_start_ticks = 42 }; try std.testing.expect(linux.eql(.{ .linux_start_ticks = 42 })); try std.testing.expect(!linux.eql(.{ .linux_start_ticks = 43 })); try std.testing.expect(!linux.eql(.{ .macos_unique_id = 42 }));}