lsfd: The util-linux Replacement for lsof That Actually Understands Modern Linux

2026-08-24

lsof turns 40 next year, and its parser is 40 years old too — it walks /proc, guesses at socket state, calls anything it hasn't been taught "unknown," and its filter grammar is nine mutually incompatible flags glued together with -a. lsfd shipped in util-linux 2.38 (July 2022) as the ground-up rewrite. If you've upgraded a distro in the last two years, it's already on your system.

Why bother switching? lsfd asks the kernel through modern interfaces — fdinfo, sock_diag, nsfs — so it sees what lsof invents or ignores: io_uring rings, eventpoll wakeup sources, bpf-map fds, pidfds, memfds, sockets scoped to specific namespaces. It also has a real filter language instead of "hope -a means AND this time."

The equivalent of lsof -i :443, but expressed as a query the kernel can answer in one pass:

lsfd -Q '(FD >= 0) and SOCK.LISTENING and (SOCK.PROTONAME == "TCP")' \
     -o COMMAND,PID,FD,TCP.LADDR

Who has your log file open (the "what do I HUP after logrotate?" question)?

lsfd -Q 'NAME == "/var/log/nginx/access.log"'

The killer feature nobody knows: pipe endpoints. lsfd walks the kernel's pipe inode table and prints both ends resolved:

lsfd -Q 'TYPE == "FIFO"' -o PID,COMMAND,FD,ENDPOINTS

You get rows like 1234 firefox 3 [5678 ffmpeg 0w] — the actual process on the other side of the pipe. lsof hands you an inode and wishes you luck grepping for it.

io_uring and other things lsof can't parse. Every high-throughput daemon written since 2020 uses io_uring. lsof labels those fds "unknown." lsfd:

lsfd -Q 'FD.TYPE == "io_uring"' -o PID,COMMAND,FD

Same for eventpoll — see what an epoll set is actually watching, not just that it exists:

lsfd -Q 'FD.TYPE == "eventpoll"' -o PID,COMMAND,FD,EVENTPOLL.TFDS

Machine-readable output. -J emits real JSON with typed fields, not lsof's pseudo-columns. Pipe straight to jq:

lsfd -J -Q 'COMMAND == "postgres"' \
  | jq '.lsfd[] | select(.type=="REG") | .name'

Hunting fd leaks. Add --summary=only for a per-type histogram:

lsfd -p 4321 --summary=only

You'll get a table of REG / DIR / FIFO / SOCK / io_uring counts. Run it on a healthy pid and a sick one; the diff tells you what's leaking without reading a single line of C. If anon_inode:[eventfd] keeps climbing, you have your suspect.

Discovering fields. The filter language only helps if you know the columns. Run lsfd --list-columns — it prints every filterable field, its type, and a one-line description. You'll find gems like NS.NAME (which namespace inode?), MISCDEV, BPF-PROG.TYPE, and PIDFD.PID.

Counterexamples. lsfd -i :443 doesn't exist — you write the query. If a script depends on lsof's exact output format, keep lsof. Otherwise, the two-decade tradition of piping lsof through awk is now obsolete: one -Q and one -o does the filter and the projection in a single pass, from the kernel's actual view of the fd table rather than a 1988-vintage guess about it.

Key Takeaway: lsfd is what lsof would look like if you rewrote it in 2022 knowing about namespaces, io_uring, structured output, and how to write a real filter expression.

All newsletters