How Hard Links and Inodes Work on Linux

Share
In this article, we'll learn a bit about how hard links and inodes work in Linux's filesystems.
A diagram showing the relationship between three components: hard links, inode, and disk. The hard links /foo and /bar are associated with the number 42 (this is the data in a directory block). The inode has properties such as being the number 42, having the owner 2, the group 2, having 2 hard links, the permission rwxrw-r--, and multiple pointers to blocks, namely, 1, 2, 12, 25, and 15, following by 5 pointers set to 0. The disk component has blocks (e.g. 4096 bytes each) that are pointed to by arrows according to the numbers in the inode component. The blocks 1, 2, 12, and 24 are coloed orange. The last block pointed, 15, is colored partially orange.
A diagram showing the relationship between hard links, inodes, and disk blocks on Linux.

File Data Separation

On Linux, we can separate a file into three distinct sets of data:

First, its filename or filepath. This is the hard link.

Second, its metadata, such as when was the file created, last modified and accessed, which user owns the file, which user group is associated with the file, and what file permissions are set on the file—if it's an executable file, if it's read-only, etc. This is the inode.

Third, the actual data contained inside the file. For a plain text file encoded in ASCII, this data is the sequence of bytes that matches the characters in an ASCII table. For a PNG image, this data always starts with the bytes 89 50 4E 47 0D 0A 1A 0A. So this data is always exactly what the program that saves the file outputs, and what a program that loads files expects. This data resists in one or more "blocks" in a disk.

The same separation applies for folders (directories) and symlinks as well.

File Data Relationships

For a directory such as / there will be an inode associated with this directory, and blocks associated with the inode. In these blocks we find the data of what files are contained inside the directory. More specifically, this data lists hard links contained inside the directory.

Every directory is a list of hard links. A hard link associates a filepath with an inode through the inode's number (also called inumber).

There is a table of inodes where the operating system can find the inode's data from the inode number that identifies that inode. This data is only the metadata of the file, such as file permissions.

In this metadata, we also find a set of multiple pointers to blocks in the disk where the actual data of the file resides. How many pointers depends on the size of the file and the block size of the disk. For example, if a disk has a block size of 4096 bytes, then each pointer in the inode points to a block of 4096 bytes in the disk.

Note that this means if a file contains only 1 byte of data, it still occupies 4096 bytes in the disk because the whole block must be reserved for the file. Thus the size of a file is how many blocks it occupies, plus how much data is needed to store its inode and its hard links.

The inode data structure has a fixed size in bytes, which means it doesn't have infinite pointers to blocks. It seems that on some filesystems there will be around 10 direct pointers to blocks (e.g. around 40 kilobytes), and after that there will be indirect pointers.

The indirect pointer points to a block on the disk that contains pointers to other blocks. Assuming the size of a pointer is 4 bytes and the block size 4096, that's enough data to point to 4096 / 4 * 4096 bytes = 4 mebibytes of data[how to read math]. The inode also has a doubly indirect pointer (a pointer to a block of pointers to blocks of pointers to data blocks), which is enough to reach the gigabyte range.

Additionally, for very small files (e.g. under 100 bytes), some file systems can also store the data "inline," meaning that all data is stored within the inode itself and dereferencing pointers to blocks isn't necessary.

Consequences of the Design

Sharing Inode Numbers

When you create a hard link to a file, what you're doing, actually, is creating a SECOND hard link to the same inode of the hard link represented by the filepath you typed. For example, if you do this:

ln old-link new-link

Then there is a hard link called old-link in the current working directory and you're simply creating a new hard link called new-link that points to the same inode.

Sharing File Metadata

The hard links don't hold any metadata of the file themselves, besides the filename.

As noted previously, inodes hold data such as file permissions, which means that if you make a "file" read-only, what you're actually doing is making the inode read-only, and that will be reflected in all hard links that point to it. (below, $ represents the shell prompt)

$ touch foo
$ ln foo bar
$ ls -l
total 0
-rw-rw-r-- 2 me me 0 Mar 17 12:06 bar
-rw-rw-r-- 2 me me 0 Mar 17 12:06 foo
$ chmod 000 foo
$ ls -l
total 0
---------- 2 me me 0 Mar 17 12:06 bar
---------- 2 me me 0 Mar 17 12:06 foo

Sharing File Data

Since we need to go through the inode to get the pointers to the data of the file in the disk, all hard links indirectly point to the exact same data in the disk. Saving a file to one hard link affects all hard links since they all point to the same data.

Notably, they also share the the same metadata for modification time and access time, so accessing or modifying a file from ANY hard link will change those times on EVERY hard link whenever you use the terminal commands ls -l or stat.

A good use of hard links would be to create "copies" of large files that you never modify. If you copy a large file, that requires as much disk space as the original file, but a hard link is just a pointer so it barely takes any space. Keep in mind, however, that if you modify the file, it affects all "copies," so it's important to make it read-only to avoid accidentally losing data.

Some filesystem formats, like Btrfs, support a concept called deduplication that achieves similar effect to save space of identical data without the use of hard links, e.g. by making copies of a file refer to the same data until it's modified, i.e. they're links until you modify them, then they become copies, also called copy-on-write, or COW.

Reference Counting for Garbage Collection

When you delete a file on Linux, you aren't directly "deleting a file." You're deleting a hard link.

The inode has a counter for how many hard links currently exist that point to the inode. When you create a hard link, this counter is increased by 1, and when you delete a hard link, it's decreased by 1.

Deleting a hard link decrements the counter. If the counter reaches 0, that means the inode has no hard links left. If there are no hard links left, that means there are no filepaths that are associated with that particular file's data, which should mean that it's no longer possible to access the file at all.

In this case, the system can safely delete the file itself, since it's no longer possible to access it.

In other words, when you delete a file on Linux, you're actually deleting a hard link, and ONLY if there are no hard link the file data is actually deleted. If you create multiple hard links to the same inode, deleting a single hard link won't free the space the file occupies in memory.

Hard Coded Globals

Some inode numbers have special meaning. For example, on an ext4 filesystem, the inode number 2 always refers to the root directory (/)1.

Portability

Since hard links refer to inodes by their number, in order for a hard link to work we must know where the table that lists inodes by their number is stored, or rather, which table is it.

As such information isn't stored anywhere, the only way it can work is if hard links point to inodes in the same filesystem as the hard links reside. That is, it's not possible to hard link an inode from one filesystem to another filesystem because the hard link has no way to specify what filesystem it's referring to.

This means we can't hard link a file that exists in an HDD to an SSD, or even from one partition to another in the same disk.

Symlinks on the other hand do not have this limitation as they operate on filepaths instead of inode numbers.

Observations

Dumping Inode Data of Directoriess

Although we can use cat to view the data of files, it's normally impossible to view the data contained in inodes of directories. However, there is a method we can use to inspect it.

To do this, use stat to find the inode number of a file, then use debugfs's special cat command to print the data from the inode's pointers directly. debugfs's argument is a device (e.g. /dev/sda2). You can use the Disks utility in Linux Mint to figure out what is the appropriate value. Be careful not to click on any buttons in this utility, however, since some buttons will delete all your data.

For example, we can view an inode's data like this:

$ inode=2
$ dev=/dev/sda2
$ sudo debugfs -R "cat <$inode>" $dev

sudo is required because accessing inodes directly bypasses directory permissions. For example, without read and execute permission on a directory, you can't access anything inside the directory, but by the inode number you would be able to bypass this.

The -R flag executes a single command. Without this debugfs starts in interactive mode. Press Ctrl+D to exit the interactive mode.

The output of the above should be some filepaths and some random characters. The random characters are bytes that hold non-text data such as the number of the inode the hard link points to.

Note: this method doesn't seem to work with symlinks.

References

Written by Noel Santos.

About the Author

I'm a self-taught Brazilian programmer graduated in IT from a FATEC. In a world of increasingly complex and essential computers, I decided to use my technical expertise in hardware, desktop applications, and web technologies to create an informative resource to make PC's easier to understand.

View Comments