Linux File System Internals: Complete Guide for Developers

 

Linux File System Internals: Complete Guide for Developers

Linux File System Internals: Complete Guide for Developers

🗄️
Inode Structure
🔗
Hard Links
📂
Directories

What is this about?

In Linux, every file has two parts: the data (actual content) and the metadata (who owns it, how big it is, permissions, etc.). The metadata lives in a special structure called an inode. A directory is just a table that maps filenames to inode numbers. This page explains how all of that works.

Key Terms

inode hard link link count directory entry i-node table unlink() ln command st_nlink

What is an Inode?

Think of an inode as a ID card for a file. It stores everything about the file except its name. The name is stored separately in a directory.

An inode contains:

File type (regular/directory/symlink) Owner (UID, GID) Permissions (rwxrwxrwx) File size Timestamps (atime, mtime, ctime) Link count Data block pointers

What the inode does NOT contain: the filename. The filename is stored only in the directory.

Diagram: How a Directory Maps Names to Inodes

Below is a simplified view of how /etc/passwd is stored on disk.

/ directory
Name Inode #
. 2
.. 2
etc 7
tmp 5

/etc directory (inode 7)
Name Inode #
passwd 6422
group 282

Inode 6422
Field Value
type regular file
perm rw-r–r–
UID root
links 1
data ptr → block 0x3A
📌 Key insight: The directory entry says “passwd → inode 6422”. The inode knows nothing about the name “passwd”. This separation is the foundation of hard links.

Hard Links — Two Names, One File

A hard link means two different filenames pointing to the same inode. There is no original or copy — both names are equal owners of the same data.

📄
abc
directory entry
🗄️
Inode 122232
link count: 2
data blocks: …
📄
xyz
directory entry

When you run rm abc, the link count drops from 2 to 1. The file data is only deleted when the link count reaches 0.

Code Examples

Example 1: Read inode info using stat()
#include <stdio.h>
#include <sys/stat.h>

int main() {
    struct stat sb;

    if (stat("/etc/passwd", &sb) == -1) {
        perror("stat");
        return 1;
    }

    printf("Inode number  : %lu\n", (unsigned long)sb.st_ino);
    printf("Hard link count: %lu\n", (unsigned long)sb.st_nlink);
    printf("File size     : %ld bytes\n", (long)sb.st_size);
    printf("Owner UID     : %d\n", sb.st_uid);

    return 0;
}

Compile and run:

gcc -o stat_demo stat_demo.c
./stat_demo

Sample output:

Inode number  : 6422
Hard link count: 1
File size     : 2341 bytes
Owner UID     : 0
Example 2: Check if two paths point to the same inode (hard link detection)
#include <stdio.h>
#include <sys/stat.h>

int same_file(const char *path1, const char *path2) {
    struct stat s1, s2;

    if (stat(path1, &s1) == -1 || stat(path2, &s2) == -1)
        return -1;

    /* Same inode AND same device = same file */
    return (s1.st_ino == s2.st_ino && s1.st_dev == s2.st_dev);
}

int main() {
    /* Create hard link first: ln /tmp/a /tmp/b */
    const char *f1 = "/tmp/a";
    const char *f2 = "/tmp/b";

    int result = same_file(f1, f2);
    if (result == -1)
        perror("stat failed");
    else if (result)
        printf("%s and %s point to the SAME inode\n", f1, f2);
    else
        printf("%s and %s are DIFFERENT files\n", f1, f2);

    return 0;
}
Example 3: Print inode numbers for all files in current directory
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>

int main() {
    DIR *dp = opendir(".");
    struct dirent *entry;
    struct stat sb;
    char path[512];

    if (!dp) { perror("opendir"); return 1; }

    printf("%-30s %s\n", "Filename", "Inode#");
    printf("%-30s %s\n", "--------", "------");

    while ((entry = readdir(dp)) != NULL) {
        snprintf(path, sizeof(path), "./%s", entry->d_name);
        if (stat(path, &sb) == 0)
            printf("%-30s %lu\n", entry->d_name, (unsigned long)sb.st_ino);
    }
    closedir(dp);
    return 0;
}
Example 4: Show link count before and after creating a hard link
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

void print_link_count(const char *path) {
    struct stat sb;
    stat(path, &sb);
    printf("Link count of '%s': %lu\n", path, (unsigned long)sb.st_nlink);
}

int main() {
    /* Create a file */
    int fd = open("/tmp/demo_file", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    write(fd, "hello", 5);
    close(fd);

    printf("--- After creating /tmp/demo_file ---\n");
    print_link_count("/tmp/demo_file");

    /* Create a hard link */
    link("/tmp/demo_file", "/tmp/demo_link");

    printf("--- After: link(demo_file, demo_link) ---\n");
    print_link_count("/tmp/demo_file");
    print_link_count("/tmp/demo_link");

    /* Remove one link */
    unlink("/tmp/demo_link");
    printf("--- After unlink(demo_link) ---\n");
    print_link_count("/tmp/demo_file");

    /* Cleanup */
    unlink("/tmp/demo_file");
    return 0;
}

Output:

--- After creating /tmp/demo_file ---
Link count of '/tmp/demo_file': 1
--- After: link(demo_file, demo_link) ---
Link count of '/tmp/demo_file': 2
Link count of '/tmp/demo_link': 2
--- After unlink(demo_link) ---
Link count of '/tmp/demo_file': 1

Hard Link Rules to Remember
⚠️ Hard links cannot cross file system boundaries. Both filenames must be on the same filesystem (same device).
⚠️ Hard links to directories are NOT allowed (only root can, and only on old UNIX). This prevents circular references.
✅ All hard links are equal — there is no “original” file. Any of the names can be deleted without affecting the others as long as link count > 0.
✅ The inode starts at 1, not 0. 0 in a directory entry means the slot is empty. Root directory (/) is always inode 2.

Interview Questions

Q1: What is an inode and what information does it store?
An inode is a data structure in the filesystem that stores all metadata about a file except its name. It stores file type, permissions, owner UID/GID, size, timestamps, link count, and pointers to data blocks.
Q2: Why doesn’t the inode store the filename?
Because a file can have multiple names (hard links). The filename lives in the directory entry. The inode is the file itself; directories just map names to inode numbers.
Q3: What happens when you delete a file with rm?
The directory entry is removed, the inode’s link count is decremented by 1. The actual data blocks are freed only when the link count reaches 0 AND there are no open file descriptors for that file.
Q4: Can a hard link be created across file systems? Why or why not?
No. Inode numbers are unique only within a single filesystem. A hard link in filesystem A cannot refer to an inode in filesystem B because the numbers would overlap.
Q5: Why does the root directory (/) always have inode number 2?
Inode 0 means “unused entry” in a directory table. Inode 1 is reserved for recording bad blocks. So the kernel reserves inode 2 for the root directory as the well-known starting point for pathname resolution.
Q6: Two processes open the same file. One process calls unlink() to remove the file. What happens?
The directory entry is removed and the link count drops to 0, but the file is NOT immediately deleted. The kernel keeps the inode and data blocks alive because there are still open file descriptors. The file is deleted only when both processes close their file descriptors.
Q7: What is st_nlink in struct stat? When is it greater than 1?
st_nlink is the hard link count. It is greater than 1 when more than one directory entry (filename) refers to the same inode. For directories it is at least 2 (the directory itself and its “.” entry).
Q8: How can you programmatically check if two filenames refer to the same file?
Call stat() on both paths and compare st_ino (inode number) and st_dev (device number). If both fields are equal, both paths point to the same inode on the same device.

Continue Learning

Next: Symbolic Links — the flexible alternative to hard links

Next: Symbolic Links → Back to Index

Leave a Reply

Your email address will not be published. Required fields are marked *