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 NtQueryInformationProcess or GetProcessMemoryInfo heuristics
  • PID from the PROCESSENTRY32 structure

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 /proc access with systemd fallback—fastest and most detailed, no external process spawning
  • macOS relies on ps parsing and launchctl—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.ResolveAncestry to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →