Why the filename is not the file
You delete a 40GB log and the disk does not get one byte back. A config symlink points at nothing after a deploy. mv to another disk takes ten minutes instead of an instant. All three make sense once you see that a name and a file are two separate things.
What you need to know already: 4.5 (ls -l, stat), 3.16 (file descriptors), 3.12 (lsof).
Inodes
A file is really an inode: a numbered record holding the metadata (owner, mode, timestamps, size) plus pointers to its data blocks - the chunks of disk (usually 4 KiB each) that hold the contents. A directory entry is just a name pointing at an inode number. The name is not stored in the inode at all.
$ echo data > a; ln a b; ln -s a c
$ ls -li a b c
3226 -rw-r--r-- 2 learner learner 5 Sep 22 20:00 a
3226 -rw-r--r-- 2 learner learner 5 Sep 22 20:00 b
3227 lrwxrwxrwx 1 learner learner 1 Sep 22 20:00 c -> a
└┬─┘ └┬┘
│ link count: TWO names point at inode 3226
inode number: a and b are the same file; c is a different inode
ln TARGET NAME makes a hard link, ln -s TARGET NAME a symlink (both below). ls -i adds the inode number as the first column. The number after the mode is the link count: how many names point at this inode.
Hard link
ln a b creates a second name for the same inode - a hard link. Not a copy: change one and the other changes, because there is only one file. Delete a and b still works - the link count drops to 1 and the data stays.
There is no "original" and "link" after the fact. Both names are equally real; ls -li is the only way to tell they are the same file.
Limits, with the real errors:
$ ln b /data/x
ln: failed to create hard link '/data/x' => 'b': Invalid cross-device link
$ ln /etc /tmp/etclink
ln: /etc: hard link not allowed for directory
Inode numbers only mean something inside one filesystem (one formatted disk or partition; /data here is a separate disk - 4.18). So a hard link cannot cross into another one. Directories cannot be hard-linked because that would allow loops the kernel could never walk out of. (mv across filesystems works only because it silently becomes copy + delete - which is why it is slow.)
A directory's own link count is 2 + its number of subdirectories: its name in the parent, its own ., and each child's ...
Symbolic link
ln -s target name creates a symlink (symbolic link, soft link): a tiny file of its own that contains a path. It can cross filesystems, can point at directories, and can point at something that does not exist:
$ stat c
File: c -> a
Size: 1 Blocks: 8 IO Block: 4096 symbolic link
$ stat -L c | head -3
File: c
Size: 5 Blocks: 8 IO Block: 4096 regular file
The symlink's size is the length of the path it holds (1 byte: a). stat -L follows the link and describes the target instead.
Remove the target and the symlink dangles (points at nothing):
$ rm a
$ cat b
data
$ cat c
cat: c: No such file or directory
$ ls -l c
lrwxrwxrwx 1 learner learner 1 Sep 22 20:00 c -> a
b (the hard link) still has the data; c still exists but points at a name that is gone. ls is happy, cat is not. In colour, ls shows dangling links in red; find -xtype l lists them; readlink -f LINK prints the full path the link resolves to.
Relative targets are resolved from the link's directory, not from where you are standing: ln -s ../lib/app.jar /opt/app/bin/app.jar means /opt/app/lib/app.jar. This is why symlinks created with a relative path from the wrong directory dangle immediately.
The symlink's own permissions (lrwxrwxrwx) are meaningless - what counts is the target's, and x on the directories on the target's path (4.7). A symlink into a directory you cannot traverse is a symlink you cannot follow, however open the link looks.
The part that matters for disk space
The data blocks are freed when both of these reach zero:
- the number of names (the link count), and
- the number of open file descriptors on the inode.
So rm bigfile.log on a file that a running process still has open removes the name and frees nothing. We call it a deleted-but-open file: the inode lives on, invisible, until that process closes the fd or exits.
# after the rm of the 40G log in this chapter's disk mission (empty until then)
sudo lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
logwriter 1302 appuser 3w REG 253,17 42949672960 0 1157 /data/app/debug.log (deleted)
lsof +L1 means "open files with a link count less than 1". Columns: the program and its PID, the user, FD (3w = descriptor 3, open for writing), TYPE REG a regular file, SIZE/OFF its size in bytes, NLINK 0 no names left, NODE the inode number, and the old name marked (deleted). That is the whole diagnosis, and you will use it in a few steps.
Links in the wild
- Backup tools like rsnapshot (and
cp -al) use hard links, so a file unchanged since yesterday's snapshot costs no extra space. - Release directories often use a symlink:
/opt/app/current -> releases/v42. Deploying is repointing one link. /proc/<pid>/fd/Nlook like symlinks but are special: opening/proc/1302/fd/3opens the deleted inode itself.
What you can now do
- Tell hard links from symlinks with
ls -li. - Explain why a hard link survives
rmof the original and a symlink dangles. - Explain why deleting an open file frees no space.