4 of 8

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.

  • stat
  • ln
  • ls -i
  • readlink
  • find

30 min5 exercises

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
FieldMeaning
SizeBytes of content
Blocks512-byte units actually allocated
LinksHow many names point at this inode
Access (mode)Permissions (lesson 7)
Access / Modify / ChangeLast 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.

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

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

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 run ln. 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.

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.

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 linkSymbolic link
Points toAn inodeA path
Survives deleting the originalYesNo, becomes dangling
Crosses filesystemsNoYes
Can point at a directoryNoYes
Visible in ls -lOnly as a link countAs 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.

  1. Warm-up

    Make both kinds of link

    In ~/lab, create report.txt containing hello. Make a hard link to it called copy.txt and a symbolic link called shortcut.txt. List all three with their inode numbers.

    Check your result
    ls -li report.txt copy.txt shortcut.txt

    report.txt and copy.txt show the same inode number and a link count of 2. shortcut.txt has its own inode, starts with l, and ends with -> report.txt.

    Hint

    ln makes hard links. ln -s makes 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
  2. Warm-up

    Predict, then delete

    Before you run anything, write down whether cat copy.txt and cat shortcut.txt will still work after rm report.txt. Then run it and check your prediction.

    Check your result

    cat copy.txt still prints hello: the data lives on until the last name is removed. cat shortcut.txt fails with No such file or directory: the link only stores the text report.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
  3. 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 from deep/.

    mkdir -p ~/lab/links/{a,b/deep}
    echo "port=8080" > ~/lab/links/a/settings.conf
    Check your result

    After the move, cat ~/lab/links/b/deep/cfg fails. A relative target is resolved from the directory that holds the link, so from deep/ it points at b/a/settings.conf, which does not exist. The fixed link prints port=8080.

    Hint

    Use find ~/lab/links -xtype l to 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
  4. 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/count
    5

    The 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
  5. Challenge

    Find every name of one file

    Create ~/lab/inodes/a/data containing a line of text. Add two more hard links to it in ~/lab/inodes/b and ~/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 of 1.

    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/data3

    After removing two names, stat -c %h on the survivor prints 1 and cat still shows the text.

    Hint

    find -samefile NAME or find -inum NUMBER. The number comes from ls -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