Platform-Specific Process Resolution in witr: Linux, macOS, and Windows Differences Explained
witr resolves target processes using OS-specific strategies in internal/target, with Linux scanning /proc, macOS parsing ps output, and Windows calling the Toolhelp API—each falling back to native service managers when needed.
The witr command-line tool provides unified process inspection across operating systems, but its implementation adapts to each platform's native APIs. This article breaks down how pranshuparmar/witr handles platform-specific process resolution on Linux, macOS, and Windows, examining the source code that makes cross-platform targeting possible.
Linux: /proc Filesystem and systemd Fallback
Process Enumeration via /proc
On Linux, witr walks the /proc filesystem directly. In internal/target/name_linux.go, the ResolveName function reads numeric directories representing PIDs and extracts:
<pid>/comm— the executable name (15 bytes max from the kernel)<pid>/cmdline— the full null-separated command line
Both values are lower-cased and matched against the target name using exact or fuzzy logic.
// Simplified excerpt from name_linux.go
func ResolveName(name string, exact bool) ([]Target, error) {
entries, _ := os.ReadDir("/proc")
for _, entry := range entries {
pid, err := strconv.Atoi(entry.Name())
if err != nil { continue } // skip non-numeric entries
comm, _ := os.ReadFile(fmt.Sprintf("/proc/%d/comm", pid))
cmdline, _ := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
// ... matching logic
}
}
Ancestor Filtering
To prevent witr from targeting itself, the implementation calls procpkg.ResolveAncestry(os.Getpid()) to build a set of PIDs to exclude—including the current process and all its parent shells.
systemd Service Resolution
When process scanning yields no matches, witr falls back to systemd. The helper resolveSystemdServiceMainPID in internal/target/port_linux.go executes:
systemctl show -p MainPID <service-name>
If the service is active, MainPID provides the target process ID directly.
macOS: ps Parsing and launchctl Integration
Process Discovery with ps
macOS lacks a /proc equivalent, so internal/target/name_darwin.go spawns ps -axo pid=,comm=,args= and parses its tabular output. The columns map directly to:
- PID (integer)
- Command name (
comm) - Full arguments (
args)
Matching applies the same exact/fuzzy rules as Linux, ensuring consistent behavior across platforms.
Ancestor Handling
Darwin uses the same procpkg.ResolveAncestry approach, building an exclusion set from the current PID upward through the process tree.
launchd Service Lookup
For macOS services, witr queries launchd. Before invocation, labels are validated against a strict regex in internal/target/port_darwin.go:
var launchdLabelRegex = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
This prevents command injection when constructing the launchctl call:
launchctl print system/<label>
The output is parsed to extract the service's running PID.
Windows: WinAPI Toolhelp and Service Control Manager
Native Process Enumeration
Windows implementation diverges significantly. Process enumeration lives in internal/proc/process_windows.go, using the Toolhelp32 API:
// Windows API calls (wrapped via syscall)
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
Process32First(hSnapshot, &procEntry)
Process32Next(hSnapshot, &procEntry)
For each process, witr retrieves:
- Executable path via
QueryFullProcessImageName - Command line via
NtQueryInformationProcessorGetProcessMemoryInfoheuristics - PID from the
PROCESSENTRY32structure
SCM Service Resolution
When a service name is specified, witr communicates with the Service Control Manager through advapi32.dll:
// Excerpt from port_windows.go
mgr, _ := winio.OpenSCManager("", "", windows.SC_MANAGER_CONNECT)
svc, _ := winio.OpenService(mgr, serviceName, windows.SERVICE_QUERY_STATUS)
svc.QueryServiceStatusEx(windows.SC_STATUS_PROCESS_INFO)
The SERVICE_STATUS_PROCESS structure returns the service's current PID directly.
Cross-Platform Comparison Table
| Aspect | Linux | macOS | Windows |
|---|---|---|---|
| Primary API | /proc filesystem |
ps command |
Toolhelp32 API |
| Process info source | comm, cmdline files |
ps output columns |
PROCESSENTRY32, QueryFullProcessImageName |
| Service fallback | systemctl |
launchctl |
Service Control Manager |
| Service validation | systemd internal | Regex: ^[a-zA-Z0-9._-]+$ |
SCM handle validation |
| Ancestor resolution | procpkg.ResolveAncestry |
procpkg.ResolveAncestry |
procpkg.ResolveAncestry |
| Build-selected file | name_linux.go |
name_darwin.go |
Windows equivalent |
Practical Usage
The CLI abstracts all platform differences behind a single -p flag:
# fuzzy match any process containing "node"
witr -p node
# Linux: resolve nginx systemd service
witr -p nginx.service
# macOS: resolve WindowServer launchd service
witr -p com.apple.windowserver
# Windows: resolve Windows Update service
witr -p wuauserv
All invocations route through target.ResolveName(), with Go build tags selecting the appropriate implementation file at compile time.
Summary
- Linux uses direct
/procaccess withsystemdfallback—fastest and most detailed, no external process spawning - macOS relies on
psparsing andlaunchctl—portable across Darwin versions, requires subprocess execution - Windows calls native WinAPI and SCM—avoids command-line parsing entirely, most robust against formatting changes
- All platforms share ancestor exclusion logic via
procpkg.ResolveAncestryto prevent self-targeting - Service integration is platform-native:
systemctl(Linux),launchctl(macOS), SCM (Windows)
Frequently Asked Questions
How does witr prevent targeting its own process?
witr calls procpkg.ResolveAncestry(os.Getpid()) on every platform to build a set of PIDs representing itself and all parent processes. These PIDs are excluded before any matching logic runs. The ancestry resolution uses /proc on Linux, kinfo_proc syscalls on macOS, and NtQueryInformationProcess on Windows.
Why does macOS use ps instead of native APIs?
Apple's Darwin kernel lacks a stable, documented procfs equivalent. While libproc exists, it is less stable across macOS versions than the standard ps output format. Spawning ps provides reliable PID, command name, and argument access without private API dependencies.
Can witr resolve services that are not currently running?
No. All three service resolution paths—systemctl show, launchctl print, and QueryServiceStatusEx—return the MainPID only when a service is active. For inactive services, witr reports no match and proceeds with process name matching if applicable.
What happens if multiple processes match the target name?
witr returns all matching PIDs. The caller (CLI or programmatic API) decides how to handle multiple results—typically by displaying information for each process or allowing further filtering by additional criteria like port numbers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →