>_EXECUTIVE SUMMARY
lsofrs is a single-binary Rust implementation of lsof. Three platform backends (src/darwin.rs via libproc FFI, src/linux.rs via /proc, src/freebsd.rs via sysctl + procfs) feed a shared Vec<Process> through a composable filter pipeline (src/filter.rs) into one of fourteen output / live-monitor modes. Per-PID FD enumeration runs on rayon's work-stealing pool; every TTY mode shares a TuiMode trait (src/tui_app.rs) for common keybindings, alternate-screen entry/exit, and atomic frame rendering. 23,562 production Rust lines + 8,813 test lines + 1,672 #[test] functions. hyperfine wall-clock: 14.2 ms for the default invocation vs 169.8 ms for lsof 4.91 (~12× speedup), 21× on terse PID output.
Source Distribution — 32,375 lines
Production: 28 files in src/. Tests: 15 integration modules in tests/ + inline #[cfg(test)] blocks in 25 of the production files. Total #[test] count is taken from grep -rh '#[test]' tests/*.rs src/*.rs | wc -l.
~BENCHMARK POSITION
hyperfine wall-clock (10 runs, 3 warmup, ~470 processes / ~5000 open files on macOS). Reproducible from README § PERFORMANCE. The rayon-parallel per-PID enumeration + zero-copy FFI + OnceCell-cached getpwuid together push the steady-state cost below 15 ms.
| Workload | lsofrs (Rust) | lsof 4.91 (C) | lsofng (C) | Speedup vs lsof |
|---|---|---|---|---|
| All open files (default) | 14.2 ms | 169.8 ms | 173.0 ms | 12× |
Network only (-i TCP) |
7.2 ms | 91.7 ms | 88.1 ms | 13× |
Terse PIDs (-t) |
6.9 ms | 101.4 ms | 142.9 ms | 15× |
Structured JSON (-J) |
29.3 ms | 156.1 ms (-F pcfn) |
136.7 ms | 5× |
Where the speedup comes from
Per-PID FD enumeration runs on rayon's work-stealing pool — CPU count = parallel degree. Darwin uses raw #[repr(C)] structs sized to libproc's kernel headers, so the gather pass never allocates per-FD. v4.8.0 added a OnceCell-cached getpwuid path, precomputed canonical filter paths, and removed every per-record allocation from the hot loop (see commit 1464becf31).
Why feature set still beats both incumbents
Modes that ship in lsofrs but neither lsof nor lsofng: unified 7-tab TUI, top-N FD dashboard, summary bar charts, stale-FD finder, listening-ports view, process tree with FD-type breakdown, pipe / unix-socket IPC map, net-by-remote-host view, file-open/close watch, single-PID follow, FD-leak detector, delta highlighting, CSV export, 31 themes with editor.
Single-binary footprint
One static lsofrs / lsf binary — clap derive parser, serde JSON, RFC 4180 CSV, ratatui TUI, crossterm terminal control, nix POSIX bindings, libc FFI, rayon work-stealing, regex selector, chrono timestamps, TOML config — all linked in. 13 direct dependencies, all foundational crates likely to still build cleanly in 2035 (clap, serde, libc, nix, regex, rayon, ratatui, crossterm, chrono, toml, dirs, users, serde_json).
Cross-platform parity
All three platform backends return the same Process + OpenFile + SocketInfo shapes from src/types.rs. Everything above filter.rs is platform-agnostic, so adding a new TUI tab or output format doesn't fan out across three implementations — it ships once.
#SUBSYSTEM BREAKDOWN
Source partitioned by role. TUI & theming dominate at 31.0% of production Rust — tui_tabs.rs alone is 5,308 lines for the unified 7-tab dashboard (mouse, tooltips, theme chooser + editor). Selection / filter is the second-biggest subsystem (2,812 lines) because every selector composes with every other under OR / AND mode with regex, range, and negation grammar.
| Subsystem | Key Files | Lines | % of prod | Share | Description |
|---|---|---|---|---|---|
| TUI & Theming | tui_tabs, tui_app, theme, config | 7,303 | 31.0% | Unified 7-tab dashboard (--tui) with mouse + tooltips + theme chooser/editor (tui_tabs.rs, 5,308); shared TuiMode trait + ratatui framework (tui_app.rs); 31 named palettes via ThemeName enum (theme.rs); TOML config persistence at ~/.lsofrs.conf (config.rs) | |
| Selection / Filter | filter | 2,812 | 11.9% | PID / PGID / user / command / FD / network-spec / path selectors, OR-mode (default) and AND-mode (-a) composition, comma-lists, ranges (0-10), exclusion (^root), regex (/…/), precomputed canonical paths | |
| Platform Gather | darwin, linux, freebsd | 2,908 | 12.3% | macOS libproc FFI with zero-copy #[repr(C)] structs (darwin.rs, 1,253); /proc reader with parallel per-PID rayon (linux.rs, 820); sysctl + procfs socket decoder (freebsd.rs, 835) | |
| Live Modes | top, summary, monitor, follow, watch, leak, delta | 3,914 | 16.6% | Live top-N FD dashboard (top.rs, 1,009); aggregate summary + bars in static and live form (summary.rs, 1,026); classic full-screen monitor (monitor.rs, 398); single-PID follow (follow.rs, 192); file-open/close watch (watch.rs, 369); circular-buffer FD-leak detector (leak.rs, 340); iteration-diff engine for change highlighting (delta.rs, 580) | |
| Single-Shot Views | tree, ports, stale, pipe_chain, net_map | 2,285 | 9.7% | Process tree with FD inheritance + per-PID type breakdown (tree.rs); listening-ports summary like ss -tlnp (ports.rs); deleted-file FD finder (stale.rs); pipe / unix-socket IPC map (pipe_chain.rs); connections grouped by remote host (net_map.rs) | |
| CLI & Dispatch | cli, main, lib, strutil | 1,737 | 7.4% | clap derive parser with 38 #[arg] attributes and custom help display (cli.rs, 1,343); mode-dispatch entry point including repeat / leak-detect loops (main.rs, 283); library facade gated by #[cfg(target_os)] (lib.rs); safe UTF-8 truncation for fixed-width column display, no mid-codepoint slices (strutil.rs) | |
| Output / Serialization | output, json, csv_out | 1,631 | 6.9% | Columnar + field (-F) formatting with TTY-detected ANSI theming (output.rs, 793); serde-backed JSON array (json.rs, 441); RFC 4180-compliant CSV with proper quoting (csv_out.rs, 397) | |
| Type System | types | 972 | 4.1% | Shared cross-platform data shapes: Process, OpenFile, SocketInfo, FileType, NetworkFilter, FdName, FdFilter, DeltaStatus — 11 public struct / enum definitions, all #[derive(Serialize)] for the JSON contract | |
| Tests | tests/*.rs + #[cfg(test)] inline | 8,813 | 27.2% | 1,672 #[test] functions across 15 integration modules (tests/) + 25 inline #[cfg(test)] blocks. Covers JSON / CSV contracts (233 tests in json_and_csv_contracts.rs), CLI dispatch (177 in integration.rs, 86 in cli_combinations.rs, 63 in dispatch_contracts.rs), library smoke / filter semantics / color output / binary aliasing | |
| TOTAL (incl. tests) | 32,375 | 100% | |||
$TOP 20 FILES BY SIZE
The 20 largest files account for 82.0% of the codebase (Rust under src/ + tests/). tui_tabs.rs is by far the largest single file — the entire 7-tab dashboard, mouse handling, tooltips, and theme picker live there. The four largest test modules (json_and_csv_contracts, integration, cli_combinations, dispatch_contracts) cover 6,602 lines — every flag combination, every JSON shape, every CSV escape rule.
| File | Lines | Role |
|---|---|---|
| src/tui_tabs.rs | 5,308 | Unified 7-tab TUI dashboard (TOP / SUMMARY / PORTS / TREE / NET-MAP / PIPES / STALE), mouse handling, hover & right-click tooltips, theme chooser + 6-color editor, bottom-bar segments with verbose tooltips, atomic frame rendering |
| src/filter.rs | 2,812 | Selection & filter composition: PID / PGID / user / command / FD / network-spec / path selectors with comma-lists, ranges, exclusion, regex; OR (default) and AND (-a) modes; precomputed canonical filter paths (v4.8.0 perf path) |
| tests/json_and_csv_contracts.rs | 2,581 | 233 #[test] functions pinning every JSON field, every CSV escape rule, every per-flag output shape — refactors can’t silently break downstream consumers |
| tests/integration.rs | 2,090 | 177 end-to-end tests: full binary invocation, every flag, every output mode |
| src/cli.rs | 1,343 | clap derive args (38 #[arg] attributes, 20 short flags), custom help display, mode-flag groupings, version printing |
| src/darwin.rs | 1,253 | macOS libproc FFI: proc_listpids, proc_pidinfo, proc_pidfdinfo, proc_pidpath; zero-copy #[repr(C)] structs sized to kernel headers; per-PID FD scan parallelized with rayon |
| tests/cli_combinations.rs | 1,142 | 86 tests covering every selector / mode combinator (-a -p -u -i AND mode, regex command match, range FDs, exclusion) |
| src/top.rs | 1,009 | Live top-N FD dashboard: sort cycling (FDs → PID → USER → REG → SOCK → PIPE → OTHER → DELTA → CMD), distribution bar column, delta tracking, +/- to grow / shrink N |
| src/theme.rs | 1,005 | 31 named cyberpunk palettes (NeonSprawl, BladeRunner, Matrix, SolarFlare, …) via the ThemeName enum with display name + 6-color resolver; custom-palette support persisted through config.rs |
| src/summary.rs | 1,026 | Aggregate FD breakdown with bar charts, top-N processes, per-user totals; doubles as a live TUI mode under --summary -r N via the shared TuiMode trait |
| src/types.rs | 972 | Cross-platform shared shapes: Process, OpenFile, SocketInfo, NetworkFilter, FileType, etc. — 11 public structs / enums, all #[derive(Serialize)] for the JSON contract |
| src/freebsd.rs | 835 | FreeBSD gather: sysctl(KERN_PROC_FILEDESC) + /proc when mounted, FreeBSD-specific TCP / UDP / unix-socket decoding from /proc/net |
| src/linux.rs | 820 | Linux gather: /proc/<pid>/{stat,status,fd,fdinfo} reader, /proc/net/{tcp,tcp6,udp,udp6,unix} parser for socket decoding; parallelized per-PID with rayon |
| tests/dispatch_contracts.rs | 789 | 63 tests pinning the mode-dispatch matrix in main.rs: which mode wins when multiple flags are passed, exit-code contracts, alternate-screen lifecycle |
| src/output.rs | 793 | Columnar + field (-F) formatting, TTY-detected ANSI theming, cyberpunk header swap (PROCESS/PRC/H4XOR on TTY, COMMAND/PID/USER when piped), Unicode-safe column widths via strutil.rs |
| src/net_map.rs | 608 | Network connections grouped by remote host: aggregates each peer’s open-socket count, supports JSON output and per-user filtering |
| src/delta.rs | 580 | Iteration-diff engine for change highlighting: stable record IDs, additions in green, removals in red, used by --delta -r N and all live modes |
| src/tui_app.rs | 572 | Shared TuiMode trait + ratatui scaffold: alternate-screen entry/exit, common keybindings (1-9, p pause, ? help, c/C theme, x border, / filter, y copy, e export, q quit), atomic frame rendering |
| src/tree.rs | 556 | Hierarchical process tree with FD inheritance, per-PID type breakdown ([REG:12 IPv4:3 PIPE:2]), notable-file listing inline; JSON tree with nested children |
| src/ports.rs | 450 | Listening-ports summary like ss -tlnp but cross-platform (macOS + Linux + FreeBSD); supports JSON output and per-user filtering |
| TOP 20 SUBTOTAL | 26,544 | 82.0% of 32,375-line Rust slice |
@EXECUTION PIPELINE
One invocation flows through three stages: gather (platform-specific FFI, parallel per-PID), filter (composable selectors, AND/OR mode), output (one of: columnar TTY, columnar plain, JSON, CSV, field, or live TUI). The shared Process / OpenFile shape from src/types.rs means every backend feeds every output mode without intermediate marshaling.
argv ──▶ cli.rs (clap derive, 38 args)
│
▼
┌────────────────┐
│ main.rs │ mode dispatch:
│ (283 lines) │ tui / top / summary -r / monitor /
│ │ watch / follow / leak-detect /
│ │ tree / ports / stale / pipe-chain /
│ │ net-map / csv / json / field / list
└────────┬───────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
#[cfg(target_os = …)] filter.rs (2,812 lines)
┌───────────┐ - PID / PGID / USER / CMD
│ darwin.rs │ - FD range / exclusion
│ (1,253) │ - network spec (4|6|TCP|UDP|:port)
│ libproc │ - path canonicalization
│ FFI │ - AND mode (-a) / OR mode (default)
│ repr(C) │ - regex via /…/
└─────┬─────┘
│
┌───────────┐
│ linux.rs │
│ (820) │
│ /proc fs │ ┌──────────────────────┐
│ rayon │ ──▶ │ Vec<Process> │
│ per-PID │ │ (types.rs, 972 ln) │
└─────┬─────┘ │ Process │
│ │ ├─ OpenFile │
┌───────────┐ │ │ ├─ FileType │
│freebsd.rs │ │ │ └─ SocketInfo │
│ (835) │ │ └─ command/uid/… │
│sysctl+proc│ └──────────┬───────────┘
└───────────┘ │
▼
┌─────────────────────────────┐
│ output dispatch │
├─────────────────────────────┤
│ output.rs - columnar/TTY │
│ json.rs - serde JSON │
│ csv_out.rs - RFC 4180 │
│ tui_app.rs - TuiMode trait │
│ └─ tui_tabs, top, │
│ summary, monitor, │
│ follow, watch, leak │
└─────────────────────────────┘
&TYPE SYSTEM
Eleven public struct / enum definitions in src/types.rs (972 lines) carry every fact between the gather, filter, and output layers: FileType, Access, InetAddr, TcpState, SocketInfo, FdName, OpenFile, Process, NetworkFilter, FdFilter, DeltaStatus. The serialized shapes #[derive(Serialize)], so the JSON contract is one derive macro — no hand-rolled escaping, no schema drift. The resolved theme types (ThemeName, LsofTheme) live in src/theme.rs, not types.rs.
Process
Per-PID record: pid, ppid, pgid, uid, command, files: Vec<OpenFile>, plus selection-state fields (sel_flags, sel_state) and a private OnceCell<String> username cache. Populated by each platform backend, consumed by every filter and output mode.
OpenFile
One row of lsof output: fd (FdName), access (Access), lock, file_type (FileType), device: Option<(u32,u32)>, size, offset, inode, nlink, name, optional socket_info (SocketInfo) when the FD is a socket.
FileType
Enum of the file-descriptor classes lsofrs decodes, rendered by as_str(): REG, DIR, CHR, BLK, FIFO, sock, LINK, PIPE, KQUE, unix, IPv4, IPv6, systm, PSEM, PSHM, ATALK, FSEV, plus Unknown(String) for anything else.
SocketInfo
Decoded socket facts: local + foreign (InetAddr), protocol (TCP/UDP/…), tcp_state (Option<TcpState>), and optional queue / buffer / option counters (recv_queue, send_queue, recv_buf_size, send_buf_size, socket_options, socket_state). Drives --ports, --net-map, and -i filtering.
InetAddr
The pair used for socket endpoints: addr: Option<IpAddr> + port: u16. Each SocketInfo holds a local and a foreign InetAddr.
TcpState
The TCP state machine as an enum: Closed, Listen, SynSent, SynRecv, Established, CloseWait, FinWait1, Closing, LastAck, FinWait2, TimeWait, plus Unknown(i32). Surfaces as the tcp_state field in JSON and the (LISTEN) / (ESTABLISHED) annotation in columnar output.
FdName
The descriptor identity: Cwd, Rtd, Txt, Mem, Err, Number(i32) for ordinary numeric FDs, or Other(String). with_access() renders it with the access suffix (10u, 0r, 1w).
Access
Read / write access mode: Read (r), Write (w), ReadWrite (u), None (space). Drives the access suffix on the FD column and the JSON access field.
NetworkFilter
Parsed form of the -i argument: protocol, addr_family, addr, host, a precomputed parsed_host: Option<IpAddr> for O(1) comparison, and a port_start / port_end range. One parsed predicate filters against every SocketInfo.
FdFilter
The -d descriptor selector: Name(String) for named FDs (cwd, txt, …) or Range(i32, i32) for numeric ranges like 0-10.
DeltaStatus
Iteration-to-iteration diff bookkeeping consumed by delta.rs: Unchanged, New (green), or Gone (red) so live modes can highlight churn between refreshes.
%MODES MATRIX
Fifteen distinct entry points dispatched from main.rs based on which flag is set. Single-shot modes print and exit; live modes share tui_app.rs's TuiMode trait for common keybindings and atomic frame rendering.
| Flag | Module | Lines | Mode | Description |
|---|---|---|---|---|
(default) | output.rs | 793 | single-shot | Columnar output (cyberpunk on TTY, plain when piped); the original lsof default |
--tui | tui_tabs.rs | 5,308 | live (TuiMode) | Unified 7-tab dashboard with mouse + tooltips + theme picker + theme editor |
--top [N] | top.rs | 1,009 | live (TuiMode) | Live top-N processes sorted by FD count with distribution bars + delta |
--summary [-r N] | summary.rs | 1,026 | both | Aggregate stats + bar charts; -r N turns it into a live TuiMode |
--monitor / -W | monitor.rs | 398 | live (TuiMode) | Classic full-screen alternate-buffer monitor (top(1)-style) |
--watch FILE | watch.rs | 369 | live (stream) | Timestamped +OPEN / -CLOSE events for a single path |
--follow PID | follow.rs | 192 | live | Single-PID FD tracker; new opens +NEW green, closes -DEL red |
--leak-detect[=I,N] | leak.rs | 340 | live | Per-PID FD-count sampler with circular buffer; flags monotonically increasing counts |
--tree | tree.rs | 556 | single-shot | Hierarchical process tree with per-PID FD-type breakdown |
--ports | ports.rs | 450 | single-shot | Listening-ports summary like ss -tlnp, cross-platform |
--stale | stale.rs | 313 | single-shot | Deleted-file FD finder (disk-space leak / zombie-handle detector) |
--pipe-chain | pipe_chain.rs | 358 | single-shot | Pipe + unix-socket IPC topology between processes |
--net-map | net_map.rs | 608 | single-shot | Network connections grouped by remote host |
--csv | csv_out.rs | 397 | single-shot | RFC 4180-compliant CSV export |
--json / -J | json.rs | 441 | single-shot | Serde-backed JSON array (full OpenFile shape) |
--delta -r N | delta.rs | 580 | live (repeat) | Color-coded diff between iterations (green=new, red=gone) |
-F <chars> | output.rs | — | single-shot | Per-record field output (p=pid, c=cmd, f=fd, n=name, …) — lsof-compatible |
-t | output.rs | — | single-shot | Terse PIDs-only output (scripting path) |
!DEPENDENCY AUDIT
Thirteen direct dependencies, all foundational crates from the durable-by-design tier (libc, nix, serde, clap, regex, rayon, chrono) with one TUI layer (ratatui + crossterm). Picked for "will this still build cleanly in 2035" — no curl-pipe-bash installers, no flaky build scripts, no nightly-only features.
| Crate | Version | Purpose |
|---|---|---|
clap | 4.x (derive) | CLI parsing with derive macros — backs cli.rs's 38 #[arg] attributes |
serde | 1.x (derive) | Trait-driven serialization for JSON + TOML config; one derive macro pins the JSON contract |
serde_json | 1.x | JSON serializer used by --json / -J |
crossterm | 0.29 | Terminal control (alternate screen, raw mode, key/mouse events) under every live mode |
libc | 0.2 | Raw FFI types for Darwin libproc structs and POSIX getpwuid |
nix | 0.31 | Safe POSIX bindings — signal, term, ioctl, user, fs, net, hostname feature set |
users | 0.11 | Username lookup wrapped in a OnceCell cache (v4.8.0 hot-path optimization) |
regex | 1.x | Regex matcher for command-name selectors via /…/ grammar |
chrono | 0.4 | Timestamps for --watch open/close events and live-mode footers |
rayon | 1.x | Work-stealing thread pool for per-PID FD enumeration — CPU count = parallel degree |
ratatui | 0.30 | TUI rendering primitives (widgets, layouts, frame) under tui_app.rs's TuiMode trait |
toml | 1.1 | Config-file parser for ~/.lsofrs.conf (theme persistence + custom palettes) |
dirs | 6.x | OS-correct home-directory lookup for ~/.lsofrs.conf |
| TOTAL | 13 direct | All from the foundational / durable tier — no nightly, no proc-macro-heavy frameworks, no opinionated DI containers |
?TEST SURFACE
1,672 #[test] functions across 15 integration modules and 25 inline #[cfg(test)] blocks. The integration suite is contract-heavy: every JSON field, every CSV escape, every CLI flag combination is pinned so refactors can't silently shift output shape.
| Test Module | Lines | #[test] | Focus |
|---|---|---|---|
| tests/json_and_csv_contracts.rs | 2,581 | 233 | Every JSON field, every CSV escape, every per-flag output shape |
| tests/integration.rs | 2,090 | 177 | End-to-end binary invocation, every flag, every output mode |
| tests/cli_combinations.rs | 1,142 | 86 | Selector / mode combinator matrix (-a -p -u -i, regex, ranges) |
| tests/dispatch_contracts.rs | 789 | 63 | Mode-dispatch precedence in main.rs, exit-code contracts, alternate-screen lifecycle |
| tests/library_smoke.rs | 393 | 28 | Library-crate facade smoke tests (pub mod cli; pub mod types; pub mod filter; …) |
| tests/json_wrappers.rs | 391 | 43 | JSON envelope shapes for the structured modes (summary, ports, tree, …) |
| tests/filters_and_paths.rs | 268 | 26 | Path canonicalization, FD-range parsing, exclusion grammar |
| tests/json_shape.rs | 147 | 13 | Top-level JSON shape pins for the default --json output |
| tests/color_output.rs | 146 | 6 | TTY-vs-piped ANSI behavior — cyberpunk on TTY, plain when piped |
| tests/lsf_binary.rs | 52 | 5 | lsf short-form alias contract — same binary, same behavior, both bin targets in Cargo.toml |
| tests/contract_csv_fd_port_range.rs | 241 | 8 | CSV FD / port-range contract pins (descending ranges, selector composition) |
| tests/contract_filter_combinators.rs | 153 | 9 | Filter combinator contract: 4/6 selector + :port semantics |
| tests/contract_field_output_round4.rs | 167 | 6 | -F field-output bijection, -P/-n independence, end-to-end markers |
| tests/inet_filter_selector_port_and_reversed_range.rs | 175 | 3 | -i selector :port + reversed/descending port-range parsing |
| tests/inet_parsed_host_precomputation.rs | 78 | 4 | parse_inet_filter precomputed parsed_host contract |
| tests/ subtotal | 8,813 | 710 | 15 integration modules |
| + inline #[cfg(test)] in src/ | — | 962 | 25 modules with inline test blocks (filter.rs alone has 220 inline tests) |
| TOTAL | 8,813+ | 1,672 | Pinned contracts across CLI, JSON, CSV, filters, dispatch, color, library facade |
*SHIPPING ARTIFACTS
Two binaries from one source
Cargo.toml declares both [[bin]] name = "lsofrs" and [[bin]] name = "lsf" at the same src/main.rs path. The short form is a quicker-to-type alias — same code, same behavior, both end up on PATH after cargo install lsofrs.
Man page
310-line roff page at man/man1/lsofrs.1 — every flag documented, copy with sudo cp man/man1/lsofrs.1 /usr/local/share/man/man1/ then man lsofrs.
Zsh completion
101-line #compdef lsofrs script at completions/_lsofrs; drop into any fpath entry (e.g. /usr/local/share/zsh/site-functions/) and reload via compinit.
Library crate
The lsofrs crate also exposes a library facade (pub mod cli; pub mod types; pub mod filter; pub mod output; pub mod json; pub mod csv_out; …) gated by #[cfg(target_os)] so downstream tools can reuse the gather + filter + serialize stack without shelling out. Documented on docs.rs/lsofrs.
Config persistence
~/.lsofrs.conf is a TOML file written by src/config.rs. Stores the last-used --tui tab, the active ThemeName, and any custom 6-color palettes built in the in-app editor (C).
CI
GitHub Actions workflow at .github/workflows/ci.yml — rustfmt, clippy, test on stable. Rust toolchain pinned to stable via rust-toolchain.toml with rustfmt + clippy components required.
=INVARIANTS
Constraints the codebase enforces or relies on. Listed here so future changes know which lines are load-bearing.
Single gather shape
All three platform backends must return Vec<Process> using the same Process / OpenFile / SocketInfo types from src/types.rs. Every output mode + filter consumes the same shape — no per-platform output branches.
TTY-detected coloring only
Output coloring + cyberpunk headers (H4XOR, CL4SS, T4RGET) are gated by std::io::IsTerminal. Pipelines always get plain ASCII headers and no ANSI escapes — safe for downstream tools like jq, awk, spreadsheets.
JSON contract is pinned
233 tests in tests/json_and_csv_contracts.rs + 43 in tests/json_wrappers.rs + 13 in tests/json_shape.rs = 289 JSON-shape tests. Renaming a field in OpenFile breaks the build until tests are updated — downstream consumers can't be silently broken.
No mid-codepoint slices
src/strutil.rs provides safe UTF-8 truncation for fixed-width column display. Output code never slices bytes by index — international filenames + emoji process names display cleanly without panics.
One shared TuiMode trait
Every live mode (--tui, --top, --summary -r, --monitor, …) implements the TuiMode trait in src/tui_app.rs. Adding a new keybinding once propagates everywhere; no per-mode reimplementations of q / p / ? / c.
OR mode is the default
Selectors compose with OR by default; -a switches the whole predicate to AND. This matches lsof's grammar so existing scripts port directly — pinned by tests in cli_combinations.rs.
Zero-allocation hot loop
v4.8.0 removed every per-record allocation from the gather loop: OnceCell-cached getpwuid, precomputed filter paths, no per-FD intermediate Vec. This is what bought the 12× default-mode speedup over lsof 4.91 — future PRs touching darwin.rs / linux.rs / filter.rs hot paths should re-bench with hyperfine.
Both binaries always ship together
Cargo.toml has [[bin]] name = "lsofrs" and [[bin]] name = "lsf" at the same src/main.rs path. tests/lsf_binary.rs pins this aliasing — lsf must remain a name-only alias forever; behavior divergence is a regression.
>>WHERE NEXT
- Docs index — quickstart, mode catalog, selection grammar, JSON shape, interactive controls.
- README — install, every flag with examples, full benchmark table, screenshots.
- docs.rs/lsofrs — rustdoc for the library crate.
- man/man1/lsofrs.1 — full man page (also
man lsofrsafter install). - Issues — bug reports, feature requests, perf regressions.
- crates.io/crates/lsofrs — releases, downloads, version history.