Linux FilesystemLesson 4 of 8
Inodes, hard links and symbolic links
See what a file really is on disk, then use links to give one file several names, or a name that points elsewhere.
statlnls -ireadlinkfind
After this lesson you can
- Explain what an inode holds and what a directory entry holds.
- Create hard links and symbolic links, and predict what happens when you delete either end.
- Find broken links and every name of a file.
What a file is on disk
A file has two parts, stored separately.
- An inode holds everything about the file except its name: size, owner, permissions, timestamps, and where its data blocks are. Each inode has a number, unique within its filesystem.
- A directory entry is a pair in a directory: a name and an inode number.
So a name is just a label pointing at an inode. ls -i shows the number:
cd ~/lab
echo hello > report.txt
ls -i report.txt
843796 report.txt
stat shows everything the inode holds:
stat report.txt
File: report.txt
Size: 6 Blocks: 8 IO Block: 4096 regular file
Device: 254,0 Inode: 843796 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ alex) Gid: ( 1000/ alex)
Access: 2026-11-03 10:12:44.811137368 +0000
Modify: 2026-11-03 10:12:44.815196506 +0000
Change: 2026-11-03 10:12:44.815196506 +0000
Birth: 2026-11-03 10:12:44.811137368 +0000
| Field | Meaning |
|---|---|
Size | Bytes of content |
Blocks | 512-byte units actually allocated |
Links | How many names point at this inode |
Access (mode) | Permissions (lesson 7) |
Access / Modify / Change | Last read, last content change, last metadata change |
Modify changes when the content changes. Change also moves when permissions, owner or link count change. That is why ls -l and ls -lc can disagree.
For a single field, use a format string:
stat -c 'inode=%i links=%h size=%s blocks=%b type=%F' report.txt
inode=843796 links=1 size=6 blocks=8 type=regular file
Notice that 6 bytes of text use 8 blocks of 512 bytes: files are stored in whole blocks, usually 4 KiB. Lesson 5 returns to this.
Hard links
A hard link is a second directory entry for the same inode:
ln report.txt copy.txt
ls -li report.txt copy.txt
843796 -rw-rw-r-- 2 alex alex 6 Nov 3 10:12 copy.txt
843796 -rw-rw-r-- 2 alex alex 6 Nov 3 10:12 report.txt
Same inode, link count 2. There is no “original” and no “copy”: both names are equal. Change the content through either and both show it:
echo more >> copy.txt
cat report.txt
hello
more
The data is freed only when the link count reaches 0 and no process has the file open. Removing one name just lowers the count:
rm report.txt
cat copy.txt
hello
more
Two limits follow from names being inode numbers:
- A hard link cannot cross filesystems, because inode numbers are only unique within one.
- A hard link to a directory is not allowed.
ln: failed to create hard link '/mnt/x/hard' => 'report.txt': Invalid cross-device link
ln: d1: hard link not allowed for directory
Why directories have link counts too
A directory is a file whose content is a list of entries. Every directory has a name in its parent, a . entry for itself, and a .. entry in each subdirectory pointing back up. So a directory’s link count is 2 plus the number of its subdirectories:
mkdir d1 d2 d2/sub
stat -c '%h %n' d1 d2
2 d1
3 d2
Symbolic links
A symbolic link (symlink) is a small file whose content is a path. When something opens it, the kernel follows that path:
mv copy.txt report.txt
ln -s report.txt shortcut.txt
ls -l shortcut.txt
lrwxrwxrwx 1 alex alex 10 Nov 3 10:15 shortcut.txt -> report.txt
The size, 10, is the length of the text report.txt. A symlink has its own inode, and it can point at directories, other filesystems, or nothing at all.
Relative or absolute
The target is stored exactly as you typed it.
- Absolute (
/etc/hostname) works from anywhere and breaks if the target moves. - Relative (
../report.txt) is resolved from the directory containing the link, not from where you runln. It survives moving the whole tree, but breaks if you move only the link.
ln -s ../report.txt d1/rel-link
mv d1/rel-link d2/sub/
cat d2/sub/rel-link
cat: d2/sub/rel-link: No such file or directory
From d1/ the target ../report.txt is report.txt. From d2/sub/ it is d2/report.txt, which does not exist. The link is dangling.
Inspect and find links
readlink shortcut.txt
readlink -f d2/sub/rel-link
report.txt
/home/alex/lab/d2/report.txt
readlink prints the stored target. -f resolves it fully to an absolute path. To list every dangling link under a directory:
find . -xtype l
./d2/sub/rel-link
-type l lists all symlinks. -xtype l lists only those whose target is missing.
Replace a link
ln -s refuses to overwrite. -f replaces. When the link points at a directory add -n, or the new link is created inside the old target:
ln -sfn release-2 current
This is the standard way to switch a deployed version: current -> release-2 changes in one step, with no moment when current is missing.
Which to use
| Hard link | Symbolic link | |
|---|---|---|
| Points to | An inode | A path |
| Survives deleting the original | Yes | No, becomes dangling |
| Crosses filesystems | No | Yes |
| Can point at a directory | No | Yes |
Visible in ls -l | Only as a link count | As l and -> |
Use a symlink by default. It shows what it is, and it can point anywhere. Use a hard link when you want two names for data that must not vanish if one name does, which is how some backup tools keep many snapshots cheaply.
Practice
0 of 5 done
Do these on your own system. They get harder. Check each result, then tick it off.
- Warm-up
Make both kinds of link
In
~/lab, createreport.txtcontaininghello. Make a hard link to it calledcopy.txtand a symbolic link calledshortcut.txt. List all three with their inode numbers.Check your result
ls -li report.txt copy.txt shortcut.txtreport.txtandcopy.txtshow the same inode number and a link count of2.shortcut.txthas its own inode, starts withl, and ends with-> report.txt.Hint
lnmakes hard links.ln -smakes symbolic links.Solution
cd ~/lab echo hello > report.txt ln report.txt copy.txt ln -s report.txt shortcut.txt ls -li report.txt copy.txt shortcut.txt - Warm-up
Predict, then delete
Before you run anything, write down whether
cat copy.txtandcat shortcut.txtwill still work afterrm report.txt. Then run it and check your prediction.Check your result
cat copy.txtstill printshello: the data lives on until the last name is removed.cat shortcut.txtfails withNo such file or directory: the link only stores the textreport.txt, and that name no longer exists.Hint
A hard link is another name for the same inode. A symbolic link is a file that contains a path.
Solution
rm report.txt cat copy.txt cat shortcut.txt - Practice
Break and fix a relative link
Set up the tree below. Create
~/lab/links/b/cfg, a symbolic link to../a/settings.conf, and check it reads the file. Move the link into~/lab/links/b/deep/and show that it is now broken, then explain why in one sentence and recreate it so it works fromdeep/.mkdir -p ~/lab/links/{a,b/deep} echo "port=8080" > ~/lab/links/a/settings.confCheck your result
After the move,
cat ~/lab/links/b/deep/cfgfails. A relative target is resolved from the directory that holds the link, so fromdeep/it points atb/a/settings.conf, which does not exist. The fixed link printsport=8080.Hint
Use
find ~/lab/links -xtype lto list broken links. Either add another../or use the absolute path.Solution
cd ~/lab/links/b ln -s ../a/settings.conf cfg cat cfg mv cfg deep/ cat deep/cfg find ~/lab/links -xtype l rm deep/cfg ln -s ../../a/settings.conf deep/cfg cat deep/cfg - Practice
Predict a directory's link count
Create a directory with exactly three subdirectories and no files. Predict its link count from the rule in the lesson, then read it from
stat.Check your result
mkdir -p ~/lab/count/{x,y,z} stat -c '%h' ~/lab/count5The directory has its own entry in the parent, its own
., and one..from each of the three children.Hint
Two plus the number of subdirectories.
Solution
mkdir -p ~/lab/count/{x,y,z} stat -c '%h' ~/lab/count - Challenge
Find every name of one file
Create
~/lab/inodes/a/datacontaining a line of text. Add two more hard links to it in~/lab/inodes/band~/lab/inodes/c, with different names. Using the inode, not the names, list all three paths. Then remove two of the names and prove the content is still there with a link count of1.mkdir -p ~/lab/inodes/{a,b,c}Check your result
find ~/lab/inodes -samefile ~/lab/inodes/a/data | sort/home/alex/lab/inodes/a/data /home/alex/lab/inodes/b/data2 /home/alex/lab/inodes/c/data3After removing two names,
stat -c %hon the survivor prints1andcatstill shows the text.Hint
find -samefile NAMEorfind -inum NUMBER. The number comes fromls -i.Solution
cd ~/lab/inodes echo "keep me" > a/data ln a/data b/data2 ln a/data c/data3 find . -samefile a/data | sort rm a/data b/data2 stat -c %h c/data3 cat c/data3