// LSOFRS — LIST SYSTEM OPEN FILES IN RUST

lsofrs v4.9.1 · Zero-copy FFI · Rayon-parallel gathering · macOS + Linux + FreeBSD · 7-tab TUI · 31 cyberpunk themes · 5–21× faster than lsof / lsofng

23,562 Rust src · 8,813 test src · 1,672 #[test] functions · 13 direct dependencies

Report GitHub Issues
// Color scheme

>_LSOFRS — THE FASTEST LSOF EVER WRITTEN

Maps the invisible topology between processes and the files they hold open. Regular files, directories, sockets, pipes, devices, kqueues — anything the kernel touches. Rust rewrite of lsofng with zero-copy repr(C) FFI to Darwin / Linux / FreeBSD kernel headers, rayon work-stealing across every CPU for per-PID FD enumeration, and a single streaming pass from gather → filter → print. 14.2 ms wall-clock vs lsof's 169.8 ms on a ~470-process / ~5000-FD macOS box (hyperfine, 10 runs, 3 warmup).

Quickstart

Install from crates.io or source. The same binary is published twice — lsofrs (long) and lsf (short, quicker to type):

# install
cargo install lsofrs

# from source
git clone https://github.com/MenkeTechnologies/lsofrs
cd lsofrs && cargo build --release
sudo cp target/release/lsf /usr/local/sbin/

# everyday queries
lsf                           # list all open files
lsf -p 1234                   # files for PID 1234
lsf -c Chrome                 # files for Chrome processes
lsf -u root                   # files for root user
lsf -i                        # network connections only
lsf -i :8080                  # who's listening on port 8080
lsf /path/to/file             # who has this file open
lsf -t -c nginx               # just PIDs (for scripting)

# pipelines
lsf --json | jq '.[] | select(.fd == "cwd")'
lsf --csv  > snapshot.csv
lsf -F pcfn                   # field output (p=pid, c=cmd, f=fd, n=name)

Full install + usage live in the README. Man page: man lsofrs (310-line roff under man/man1/lsofrs.1). Zsh completion ships under completions/_lsofrs.

Why lsofrs — Wall-Clock Comparison

Benchmarked on macOS with hyperfine (10 runs, 3 warmup, ~470 processes / ~5000 open files). Numbers are reproducible from README §PERFORMANCE.

Workload lsofrs (Rust) lsof 4.91 (C) lsofng (C) Speedup
All open files (default)14.2 ms169.8 ms173.0 ms12×
Network only (-i TCP)7.2 ms91.7 ms88.1 ms12–13×
Terse PIDs (-t)6.9 ms101.4 ms142.9 ms15–21×
Structured (-J JSON / -F field)29.3 ms156.1 ms136.7 ms

Feature parity vs incumbents

Feature lsofrs lsof lsofng fuser ss
Built-in JSON (--json / -J)
Built-in CSV (--csv, RFC 4180)
Field output (-F)
Full-screen tabbed TUI (--tui)7 tabs
Top-N FD dashboard (--top)
Stale-FD finder (--stale)
Listening-ports view (--ports)
Process tree (--tree)
Pipe / unix-socket IPC map (--pipe-chain)
Net-by-remote-host map (--net-map)
File-open/close watch (--watch)
FD-leak detector (--leak-detect)
Single-process follow (--follow)
Delta highlighting (--delta)
Aggregate summary + bar charts (--summary)
Themeable output (31 palettes)
Parallel per-PID gather (rayon)
Cross-platform (Darwin + Linux + FreeBSD)Linux/BSDLinux

Overview

  • CLI front-end — clap derive parser in src/cli.rs (1,343 lines, 38 #[arg(...)] attributes). main.rs dispatches to the active mode based on which flag is set.
  • Platform gathersrc/darwin.rs (1,253 lines, libproc FFI), src/linux.rs (820 lines, /proc reader), src/freebsd.rs (835 lines, sysctl + procfs). All three return the same Vec<Process> from types.rs; each per-PID FD scan is parallelized with rayon's work-stealing pool.
  • Selectionsrc/filter.rs (2,812 lines) implements OR-mode (default) and AND-mode (-a) filter composition over PID / PGID / user / command / FD / network-spec / path. Supports comma-lists, ranges (0-10), exclusion (^root), and regex (/nginx|apache/).
  • Outputsrc/output.rs (columnar + field), src/json.rs (serde), src/csv_out.rs (RFC 4180). TTY detection auto-switches between cyberpunk headers (H4XOR / CL4SS / T4RGET) and plain headers for pipelines.
  • Live modes--tui (7-tab dashboard, 5,308 lines), --top (1,009 lines), --summary -r (1,026 lines), --monitor (398 lines), --watch FILE, --follow PID, --leak-detect. All share src/tui_app.rs's TuiMode trait for common keybindings, alternate-screen entry/exit, and atomic frame rendering.
  • Themessrc/theme.rs (1,005 lines) defines 31 named palettes via the ThemeName enum. Custom 6-color themes persist to ~/.lsofrs.conf through src/config.rs (TOML).

Modes — one card per entry point

--tui

Unified 7-tab dashboard: TOP / SUMMARY / PORTS / TREE / NET-MAP / PIPES / STALE. Click tabs, mouse-hover for tooltips, right-click for verbose detail (PID, FD breakdown, kill / copy hints). Theme chooser (c) + editor (C).

--top [N]

Live auto-refreshing top-N processes sorted by FD count. FD-type distribution bar, delta tracking, per-process breakdown. s cycles sort column, +/- grows/shrinks N.

--summary [-r N]

Aggregate FD breakdown with bar charts, top processes, per-user totals. With -r N becomes a live TUI; --json emits a structured report; -i restricts to network FDs.

--ports

Quick "what's listening where" summary — like ss -tlnp but cross-platform (macOS + Linux + FreeBSD). --json and per-user filtering supported.

--tree

Hierarchical process tree with FD counts, per-PID type breakdown ([REG:12 IPv4:3 PIPE:2]), network connection counts, and notable open files inline.

--net-map

Network connections grouped by remote host — fastest way to see which servers your box talks to and how many sockets each peer holds.

--pipe-chain

Trace pipe and unix-socket pairs between processes — visualize the IPC topology that ps and top hide.

--stale

Find FDs pointing to deleted files. Common source of disk-space leaks, zombie handles, and security issues (process held an unlinked secret file).

--watch FILE

Print timestamped +OPEN / -CLOSE events for a single path. Lightweight inotifywait / fs_usage equivalent that runs on macOS, Linux, and FreeBSD.

--follow PID

Watch one process's FDs in real time. New opens highlight +NEW (green), closes -DEL (red).

--leak-detect[=I,N]

Sample per-process FD counts every I seconds; flag any PID whose count rises N consecutive intervals in a row. Circular buffer per PID in src/leak.rs.

--delta -r N

Color-code changes between repeat iterations. New FDs in green, removed in red — useful inside watch-style loops without the noise of full re-prints.

--monitor / -W

Classic full-screen alternate-buffer monitor like top(1). s sort / r reverse / f filter / p pause / ? help.

--csv

Pure RFC-4180 CSV dump for pipelines and spreadsheets. Composes with every selector — lsf --csv -i TCP -u root > root_tcp.csv.

--json / -J

Serde-backed JSON array. Composes with every selector. Each element carries the full OpenFile shape from src/types.rs (PID, command, FD, type, device, size/offset, node, name, plus parsed socket / protocol fields when present).

-F <chars>

Per-record field output for scripting (compat with the original lsof). p=pid, c=command, f=fd, n=name, t=type, etc.

Selection grammar

Every selector is independent and composable. By default selectors OR together; -a switches the entire predicate to AND mode. Negation via ^, ranges via N-M, regex via /…/.

lsf -p 1234,5678               # PIDs 1234 OR 5678
lsf -u root,wizard             # users root OR wizard
lsf -p ^1234                   # everything except PID 1234
lsf -u ^root                   # everything except root
lsf -d 0-10                    # FDs 0 through 10
lsf -c '/nginx|apache/'        # command name matches regex

lsf -a -p 1234 -i              # AND: PID 1234 AND has network FD
lsf -a -u nginx -i TCP:443     # AND: nginx user AND TCP/443

lsf -i                         # any network FD
lsf -i 4                       # IPv4 only
lsf -i 6                       # IPv6 only
lsf -i TCP                     # TCP only
lsf -i :443                    # any protocol on port 443
lsf -i TCP:443                 # TCP on port 443

Full flag reference

Every flag below is a real #[arg(…)] in src/cli.rs (38 attributes total). Short flags follow the original lsof letters; long flags name the lsofrs-specific modes. +/- toggles (+r/-r, +w/-w, +d/+D) mirror lsof's prefix grammar.

Selection

FlagArgumentMeaning
-pPIDSelect by PID (comma-list, ^ excludes)
-uUSERSelect by login name or UID (comma-list, ^ excludes)
-g[PGID]Select (or ^-exclude) process-group IDs
-cCOMMANDSelect by command name (prefix, ^exclude, /regex/)
-dFDSelect by file-descriptor set (names or ranges like 0-10)
-aAND the selections together (default is OR)
FILE...path(s)List processes holding these files open
--dirDIRList open files in DIR, one level (lsof +d)
--dir-recurseDIRRecursively list open files under DIR (lsof +D)

Network

FlagArgumentMeaning
-i[ADDR]Select internet connections: [4|6][proto][@host|addr][:svc|port]
-NSelect NFS files
-USelect UNIX-domain socket files
-nInhibit hostname resolution
-PInhibit port-number-to-name conversion

Display & output

FlagArgumentMeaning
-F[FIELDS]Field output; -F ? lists field letters
-J / --jsonJSON output
--csvRFC 4180 CSV output
-tTerse output (PID only)
-RList parent PID
--pgid-showShow process-group IDs
-0NUL field terminator instead of newline
+w / -wEnable (+) or suppress (-) warnings (default +)
--colorMODEColor output: auto (default), always, never
--themeNAMEColor theme (default neon-sprawl); one of 31 palettes

Modes & live system

FlagArgumentMeaning
+r / -r[SECONDS]Repeat mode (default interval 1)
--deltaHighlight new / gone FDs in repeat mode
-W / --monitorLive full-screen refresh mode (top(1)-style)
--tuiUnified tabbed TUI for all modes
--top[N]Live top-N processes by FD count (default 20)
--summary / --statsAggregate FD summary: type breakdown, top processes, per-user
--treeProcess tree with FD counts (pstree + lsof)
--portsListening-ports summary (like ss -tlnp)
--net-mapNetwork connections grouped by remote host
--pipe-chainTrace pipe / socket IPC between processes
--staleFind FDs pointing to deleted files
--watchFILEWatch who opens / closes a file over time
--followPIDWatch one process's FDs, highlight opens / closes
--leak-detect[=I[,N]]Detect FD leaks: poll every I s, flag after N rises (default 5,3)
-h / --helpDisplay help
-V / --versionDisplay version

Cyberpunk theming

When stdout is a TTY, column headers swap from utilitarian (COMMAND / PID / USER) to cyberpunk (PROCESS / PRC / H4XOR) and ANSI coloring activates. Piped output is always plain — safe for scripts. 31 named palettes ship in src/theme.rs:

Palette familyThemes
Neon / synthNeonSprawl (default), NeonNoir, Synthwave, GlitchPop, HoloShift
Matrix / phosphorMatrix, AcidRain, BioHazard, ToxicWaste
Blade Runner / noirBladeRunner, NightCity, Megacorp, Zaibatsu, VoidWalker, Darkwave, DarkSignal
Frost / iceIceBreaker, CyberFrost, GhostWire, ChromeHeart, SteelNerve, QuantumFlux
Solar / emberSolarFlare, PlasmaCore, LaserGrid, RedSector, SakuraDen
Data / minimalClassic, DeepNet, DataStream, Overlock

Inside any TUI mode press c for the theme chooser (live preview), C for the 6-color editor. Selections persist to ~/.lsofrs.conf via src/config.rs.

Configuration

Settings persist to ~/.lsofrs.conf, a TOML file read and written by src/config.rs (Prefs struct). The TUI auto-saves every change made via keybinds; you can also edit the file by hand. A documented template ships as lsofrs.default.conf — copy it to ~/.lsofrs.conf or let lsofrs create one on first run.

# ~/.lsofrs.conf
theme = "NeonSprawl"      # one of the 31 named palettes
refresh_rate = 1          # seconds between refreshes in live modes
hover_tooltips = true     # auto-tooltips after 1s hover (T toggles; right-click always works)
KeyTypeMeaning
themestringActive ThemeName (e.g. NeonSprawl, Matrix, BladeRunner). Set live via c.
refresh_rateintegerRefresh interval (seconds) for --top / --summary -r / --tui. Set via 19 or </>.
hover_tooltipsbooleanEnable auto-tooltips on hover. Toggled with T in the TUI.
active_tabintegerLast-used --tui tab (0–6); restored on next launch.
custom_themestableNamed custom 6-color palettes built in the in-app editor (C).
active_custom_themestringThe currently selected custom palette, if one is active instead of a built-in.

Interactive controls (shared across TUI modes)

KeyAction
19Set refresh interval (seconds)
< / >Fine-adjust refresh interval (±1s)
pPause / resume data collection
? / hToggle help overlay
c / CTheme chooser / editor
TToggle hover tooltips (right-click still works)
xToggle border
tToggle compact / expanded view
oFreeze / unfreeze sort order
/ / 0Filter popup (regex) / clear filter
j / k / / Navigate rows
FPin / unpin selected row
yCopy selected row to clipboard
eExport current tab to file
q / Esc / Ctrl-CQuit

--tui adds tab navigation (Tab / BackTab / 17 / click). --top adds s sort, r reverse, +/- grow/shrink, b bar toggle, d delta toggle.

Architecture in one screen

src/
├── main.rs        # 283  CLI entry, mode dispatch, repeat/leak loops
├── cli.rs         # 1,343 clap derive args (38 #[arg], 20 short flags)
├── types.rs       # 972  Process, OpenFile, SocketInfo, NetworkFilter, FileType, …
├── darwin.rs      # 1,253 macOS libproc FFI (zero-copy repr(C))
├── linux.rs       # 820  /proc reader (parallel per-PID via rayon)
├── freebsd.rs     # 835  sysctl + procfs reader
├── filter.rs      # 2,812 selection & filter composition (AND/OR, regex)
├── strutil.rs     # 76   safe UTF-8 truncation for fixed-width display
├── output.rs      # 793  columnar & field formatting, ANSI theming
├── json.rs        # serde JSON
├── csv_out.rs     # RFC 4180 CSV
├── monitor.rs     # 398  classic full-screen alternate-buffer monitor
├── follow.rs      # 192  single-PID FD tracker (status transitions)
├── leak.rs        # 340  circular-buffer FD-count leak detector
├── delta.rs       # iteration-diff engine for change highlighting
├── summary.rs     # 1,026 aggregate stats + bars (+ live TUI mode)
├── tree.rs        # 556  process tree w/ FD inheritance
├── tui_app.rs     # 572  shared TuiMode trait (ratatui)
├── tui_tabs.rs    # 5,308 unified 7-tab TUI (mouse, tooltips)
├── theme.rs       # 1,005 31 themes + custom palette support
├── config.rs      # TOML config persistence (~/.lsofrs.conf)
├── top.rs         # 1,009 live top-N FD dashboard
├── watch.rs       # 369  file-open/close watch
├── stale.rs       # 313  deleted-file FD finder
├── ports.rs       # 450  listening-ports summary
├── pipe_chain.rs  # 358  pipe / unix-socket IPC topology
└── net_map.rs     # 608  connections grouped by remote host
man/man1/lsofrs.1  # 310-line roff man page
completions/_lsofrs # 101-line zsh completion (#compdef lsofrs)

Platform support matrix

OSModuleMechanismStatus
macOS / Darwinsrc/darwin.rs (1,253 lines)libproc FFI via zero-copy #[repr(C)] structs matching kernel headers; proc_listpids + proc_pidinfo + proc_pidfdinfo.Primary platform
Linuxsrc/linux.rs (820 lines)/proc/<pid>/{stat,status,fd,fdinfo} reader; parallelized per-PID with rayon.Supported
FreeBSDsrc/freebsd.rs (835 lines)sysctl(KERN_PROC_FILEDESC) + /proc when mounted; FreeBSD-specific TCP / UDP / unix-socket decoding from /proc/net.Supported

Each platform module is gated by #[cfg(target_os = "…")] in src/lib.rs. The shared Process / OpenFile / SocketInfo shapes live in src/types.rs (972 lines, 11 public structs / enums) — every gather backend returns the same data, so the entire stack above filter.rs is platform-agnostic.

Why it's fast

  • Zero-copy FFI. Darwin gather uses raw #[repr(C)] structs sized to kernel headers — no intermediate parse step, no allocator pressure per-FD.
  • Rayon work-stealing per-PID. Each PID's FD scan runs on its own worker; CPU count = parallel degree.
  • Username caching. v4.8.0 added a OnceCell-backed getpwuid cache — previously the hot loop called the syscall once per record.
  • Precomputed filter paths. The filter predicate canonicalizes its lookups once before the gather pass, not per-FD.
  • Streaming output. No intermediate Vec<Record> for the common path — gather → filter → format → write goes through the same iterator chain.
  • serde + clap derive. No hand-rolled JSON escaping, no hand-rolled getopt loop.

Net result: 14.2 ms wall-clock for the default lsf invocation vs 169.8 ms for lsof 4.91 on the same machine (hyperfine, ~470 procs, ~5000 FDs).

JSON shape

--json / -J emits an array of process records; each process carries a nested files array. The shape is produced by JsonProcess / JsonFile in src/json.rs (verbatim sample from lsf -i TCP --json). Optional fields are omitted when empty (#[serde(skip_serializing_if = "Option::is_none")]):

[
  {
    "command": "rapportd",
    "pid": 667,
    "uid": 501,
    "pgid": 667,
    "ppid": 1,
    "files": [
      {
        "fd": "10u",
        "type": "IPv4",
        "size_off": 0,
        "access": "u",
        "name": "*:60865 (LISTEN)",
        "protocol": "TCP",
        "tcp_state": "LISTEN"
      }
    ]
  }
]

Per-file keys: fd, type (from the FileType enum), device ("maj,min"), size_off, node (inode), access (r/w/u), lock, name, and — for sockets — protocol + tcp_state. Hard-coded JSON contract tests live in tests/json_shape.rs, tests/json_wrappers.rs, and tests/json_and_csv_contracts.rs (233 #[test] functions in the latter alone) — refactors can't silently break downstream consumers.

Where to go next

  • Engineering report — per-subsystem LOC, top-20 files, dependency table, test surface.
  • README — full install, every flag, full benchmark table.
  • docs.rs/lsofrs — rustdoc for the library crate (pub mod cli; pub mod types; pub mod filter; …).
  • man/man1/lsofrs.1 — 310-line roff man page (also man lsofrs after install).