Skip to content

The Neighbours

Scope: the tools that overlap lsof — fuser, which answers "who" more bluntly and can act on the answer; /proc/<pid>/fd, the raw evidence lsof itself reads; ss, revisited from chapter 5; and netstat, the retired ancestor all of them replaced.

fuser: who, with a trigger

fuser answers a narrower question — which processes are using this file or this socket — and prints just pids. The -v form names them properly. The lab's tail, asked both ways:

$ fuser -v /home/hatch/workspace/lsof-lab/tree/notes.log
                     USER        PID ACCESS COMMAND
/home/hatch/workspace/lsof-lab/tree/notes.log:
                     root      22552 f.... tail
[exit 0]
$ fuser -v 8734/tcp
                     USER        PID ACCESS COMMAND
8734/tcp:            root      22553 F.... python3
[exit 0]

Pid 22552 is the planted tail, pid 22553 the planted listener — both check out. The ACCESS column is fuser's own dialect: f is an open file (the tail's read of the log), and a socket is reported F — psmisc counts it on the write side. Other letters cover cwd (c), a running executable (e), a root directory (r), a mapped library (m): a compressed cousin of lsof's FD column, with no sizes, no inode numbers, no full tables. For a quick "who has this file" on a machine without lsof, fuser is a fine answer; for anything diagnostic, you end up wanting the table.

And fuser has the option lsof deliberately lacks: -k, kill the processes using this. The lab ran it against the planted tail:

$ fuser -k /home/hatch/workspace/lsof-lab/tree/notes.log
/home/hatch/workspace/lsof-lab/tree/notes.log: 22552
[exit 0]
$ lsof /home/hatch/workspace/lsof-lab/tree/notes.log
[exit 1]
$ pgrep -a tail
[exit 1]

Dead, verified two ways (lsof silent, no tail process anywhere). Treat -k the way you'd treat any pre-aimed kill: it sends SIGKILL by default — no cleanup, no shutdown hooks — to every process the file matches, which for a popular file can be a crowd. Identify with lsof first, kill by pid with intent, and let -k stay the lab trick it is here. (The 22552 Killed shell notice in the raw transcript is the lab shell reporting its own child's death, not fuser talking.)

/proc//fd: the evidence itself

Everything lsof prints about a process comes out of /proc, and the descriptor table is sitting there as symlinks for anyone (with permission — chapter 8) to read. The same tail, raw:

$ ls -l /proc/22552/fd
total 0
lr-x------ 1 root root 64 Oct 21 01:15 0 -> /dev/null
l-wx------ 1 root root 64 Oct 21 01:15 1 -> /home/hatch/workspace/lsof-lab/transcripts/s10-neighbours.txt
l-wx------ 1 root root 64 Oct 21 01:15 2 -> /home/hatch/workspace/lsof-lab/transcripts/s10-neighbours.txt
lr-x------ 1 root root 64 Oct 21 01:15 3 -> /home/hatch/workspace/lsof-lab/tree/notes.log
lr-x------ 1 root root 64 Oct 21 01:15 4 -> anon_inode:inotify
[exit 0]

Line it up against chapter 2's table: fd 3 is the log, fd 4 is the inotify watch (anon_inode:inotify here, a_inode … inotify in lsof's rendering), 0/1/2 are the standard streams, and the r/w in the symlink permissions are where lsof's access letters come from. readlink /proc/PID/cwd and …/exe gave chapter 2 its cross-checks. What raw /proc doesn't do: search. There is no ls that answers "who holds this file" — that requires walking every pid's fd directory and matching inodes, which is precisely the work lsof exists to do. Read /proc to verify; query with lsof.

ss, one more time

Chapter 5 did the head-to-head; the verdict in one line each way. For socket-shaped questions — states, queues, timers, every connection on the box — ss is the native tool and the faster design (a direct kernel query versus a per-process scan). For "the process as a whole" questions — its files, its cwd, its deleted-but-open log — only lsof has the table. Working fluency is both, chosen per question, and the two agreeing (as they did on pid 22428, fd 3) is what a verified answer looks like.

netstat: the ancestor

The build machine does have it:

$ command -v netstat
/usr/bin/netstat
[exit 0]

netstat is the tool ss replaced: same socket questions, answered from a slower /proc scrape, output format frozen a generation ago. It lingers in old runbooks and older administrators' fingers. Nothing in this guide needs it, and the mapping is one-to-one enough to state plainly: netstat -ltnp is ss -ltnp. It is named here so a reader who meets it in the wild knows it's the same territory, documented rather than taught — the series teaches the current tools.