//go:build linux package main import ( "fmt" "os" "strconv" "strings" "syscall" ) // spec:26174-planctl-context-tokens/R1.2+R6.3+D§3.3 // ppidOf returns the parent PID of pid by reading /proc//status. // Returns 0 on error. func ppidOf(pid int) int { data, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid)) if err != nil { return 0 } for _, line := range strings.Split(string(data), "\n") { if strings.HasPrefix(line, "PPid:") { s := strings.TrimSpace(strings.TrimPrefix(line, "PPid:")) n, err := strconv.Atoi(s) if err != nil { return 0 } return n } } return 0 } // spec:26174-planctl-context-tokens/R6.3+D§3.3 // processAlive reports whether a process with pid exists. func processAlive(pid int) bool { if pid <= 0 { return false } return syscall.Kill(pid, 0) == nil } // spec:26174-planctl-context-tokens/R6.3+D§3.3+D§4.1 // processStartTimeSec returns the process start time as raw clock ticks since boot, // read from /proc//stat field 22 (starttime). The hook writes the same units // (clock ticks) on Linux per §5.5/§4.1, so comparison units match. // // The name retains "Sec" for API symmetry with the macOS implementation (which // returns epoch seconds). Within a platform both sides match; cross-platform // comparison never happens. // // /proc//stat is a single line: "pid (comm) state ppid ... starttime ...". // `comm` can contain spaces and parens. We find the LAST ')' and tokenize the // remainder so comm noise can't shift field indices. func processStartTimeSec(pid int) (int64, error) { data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid)) if err != nil { return 0, err } return parseStatStartTime(string(data)) }