Thursday, March 28, 2013

All About inodes, Hard Links and Soft Links

Open your terminal and fire "ls -i" and you will see that each file is associated with a number.
$ ls -i
2889973 users.sh 2889972 fedoraRepo.sh 2889970 sfs.sh
2889969 bigFile.sh 2889971 dbBackup.sh 2889714 tree-clone.py


Ever wondered what this number is?
Ever thought what happens when a file is deleted?
How does the system knows the owner of the file or it's last modification time?
What are hard links?
What is the difference between hard links and soft links?

I'll try to answer these questions and probably more but I want to stress on a point; every thing in Linux is a file including the directories and devices attached.
Also install a package called sleuthkit using yum or apt to obtain a tool called istat.

When a filesystem (considering ext3/ext4 for now) is created on a disk, a special data structure is created. We'll call this inode table (technically it is an array of structure). It is indexed from 1 to n where n is the maximum numbers of inodes in the filesystem. Details like maximum number of inodes are decided while creating the filesystem usually. We can run "df -i" to check out number of inodes used and available.
Now whenever we create a file or a directory, an unallocated inode number is assigned and this is where several details about the file or the directory is stored. POSIX standard requires inode to contain the following information (borrowed from Wikipedia):
  • The size of the file in bytes.
  • Device ID (this identifies the device containing the file).
  • The User ID of the file's owner.
  • The Group ID of the file.
  • The file mode which determines the file type and how the file's owner, its group, and others can access the file.
  • Additional system and user flags to further protect the file (limit its use and modification).
  • Timestamps telling when the inode itself was last modified (ctime, inode change time), the file content last modified (mtime, modification time), and last accessed (atime, access time).
  • A link count telling how many hard links point to the inode.
  • Pointers to the disk blocks that store the file's contents (see inode pointer structure).
Notice that this does not include the filename. Surprised? In fact inodes never hold that information. So an obvious question arises, when we open file, how does the system know what inode it is associated with? To understand this, we need to understand what exactly is a directory. As I have mentioned, everything in Linux is a file which implies that even directory is a file. Every directory consist of a data structure, first part of which holds an inode number and the second part holds the file name. So when we try to perform any operation on a file, a recursive traversal is performed to lookup for inode number against that file name and that is how inode is obtained.

Among the information contained in inode, a link count and size is maintained. When we delete a file the link count is decreased until it reaches zero where the size of the file is also set to zero. See the example:
# ls -i abc
2891791 abc

# istat /dev/sda5 2891791
inode: 2891791
Allocated
Group: 353
Generation Id: 3534721592
uid / gid: 1000 / 1000
mode: rrw-rw-r--
Flags:
size: 6
num of links: 1

Inode Times:
Accessed: 2013-03-29 01:16:46 (IST)
File Modified: 2013-03-29 01:16:46 (IST)
Inode Modified: 2013-03-29 01:16:46 (IST)

Direct Blocks:
127754

# ln abc def
# istat /dev/sda5 2891791
inode: 2891791
Allocated
Group: 353
Generation Id: 3534721592
uid / gid: 1000 / 1000
mode: rrw-rw-r--
Flags:
size: 6
num of links: 2

Inode Times:
Accessed: 2013-03-29 01:18:41 (IST)
File Modified: 2013-03-29 01:16:46 (IST)
Inode Modified: 2013-03-29 01:18:34 (IST)

Direct Blocks:
127754

# rm abc
# istat /dev/sda5 2891791
inode: 2891791
Allocated
Group: 353
Generation Id: 3534721592
uid / gid: 1000 / 1000
mode: rrw-rw-r--
Flags:
size: 6
num of links: 1

Inode Times:
Accessed: 2013-03-29 01:18:41 (IST)
File Modified: 2013-03-29 01:16:46 (IST)
Inode Modified: 2013-03-29 01:18:57 (IST)

Direct Blocks:
127754 


So the "num of links" increased when we created a hardlink using ln command by one. When we deleted the file using rm command, the "num of links" decreased by one. If we delete the def file then the count and size will be set to zero.

# istat /dev/sda5 2891791
inode: 2891791
Not Allocated
Group: 353
Generation Id: 3534721592
uid / gid: 1000 / 1000
mode: rrw-rw-r--
Flags:
size: 0
num of links: 0


Inode Times:
Accessed: 2013-03-29 01:18:41 (IST)
File Modified: 2013-03-29 01:30:10 (IST)
Inode Modified: 2013-03-29 01:30:10 (IST)
Deleted: 2013-03-29 01:30:10 (IST)

Direct Blocks:


This brings us to our next topic of discussion, what are hard links and what are soft links? Simply putting, hard link of a file holds the inode of that file where as the soft link of the file is just a reference to another file. If we delete the original file but have a hard link to it, then we can still access the contents using the hard link. If we delete the original file, the soft link is pretty much useless since all it did was to point to the original name which contained the inode. A crude way to depict what I am saying is below. See how both "Original Name" and "Hard Link" is pointing to the inode but "Soft Link" is not.
Original Name ---------> inode <--------- Hard Link
Soft Link ----------> Original Name -----------> inode

Now, soft links have their own importance. We cannot use hard links to point to files across different filesystems but with soft links we can. This comes really handy when you want to maintain one name regardless of version differences. See how /usr/bin/python actually points to another binary.

# ls -l /usr/bin/python
lrwxrwxrwx. 1 root root 7 Jan 9 22:45 /usr/bin/python -> python2



Honestly, there are many more creative uses of links. If you are interested then I recommend that you check out how BusyBox implements a lot of commands using a single binary.
(Hint: $0 is the name of the script which is passed as a variable to the binary)

Update: As mentioned by reirob on HackerNews, there is a particular case where deleting the file does not set the "num of links" to zero. This usually happens when there is a process which is writing to the file. I have encountered this a few times myself when I delete a log file but the server writing it hasn't been restarted.