Linux Directories and Links: Complete Guide to File System Navigation

 

Linux Directories and Links: Complete Guide to File System Navigation

Linux Directories and Links: Complete Guide to File System Navigation

🔀
Soft Links
🚨
Dangling Links
🌐
Cross Filesystem

Symbolic Link — The Shortcut

A symbolic link (symlink or soft link) is a special file whose content is simply a path string pointing to another file or directory. Think of it like a desktop shortcut in Windows. It is its own file with its own inode, but when you access it, the kernel follows the path inside it to reach the real file.

Key Terms

symlink soft link dangling link dereference ln -s lstat() readlink() SYMLOOP_MAX

How a Symlink Works — Visual

A symlink is a file whose data block contains a path string like /home/ravi/myfile.txt. When the kernel opens it, it reads that string and then resolves the path.

🔀
mylink
type: symlink
data: “/home/ravi/real.txt”
──→

📄
real.txt
type: regular file
inode: 9001
link count: 1
📌 The symlink is NOT counted in the target file’s link count. If you delete real.txt, mylink becomes a dangling link — it still exists but points to nothing.

Hard Link vs Symbolic Link — Side by Side
Feature Hard Link Symbolic Link
Own inode? No — shares inode with original Yes — has its own inode
Cross filesystem? ❌ No ✅ Yes
Link to directory? ❌ Not allowed ✅ Yes
Counted in link count? ✅ Yes ❌ No
Dangling link possible? ❌ No ✅ Yes
Delete target → link breaks? No — link keeps the file alive Yes — becomes dangling
Shell command to create ln src dest ln -s src dest

Dangling Symlink — What it looks like
🔀
badlink
points to: “gone.txt”
──✖
gone.txt
DELETED

Accessing badlink returns ENOENT. But badlink itself still exists as a file.

$ ls -la badlink
lrwxrwxrwx 1 ravi ravi 8 Jan 1 10:00 badlink -> gone.txt

$ cat badlink
cat: badlink: No such file or directory

Symlink Chains and the Loop Limit

Symlinks can point to other symlinks, forming a chain. The kernel follows the chain automatically, but there is a limit to prevent infinite loops.

🔀 a → b
🔀 b → c
📄 c (real file)
Linux allows up to 8 dereferences per component and a total of 40 dereferences for an entire path. If exceeded, the kernel returns ELOOP.

Which System Calls Follow Symlinks?

Some calls automatically follow (dereference) symlinks; some operate on the link itself.

System Call Follows Symlink? Notes
open() ✅ Yes Unless O_NOFOLLOW is set
stat() ✅ Yes Gives info about target file
lstat() ❌ No Gives info about the symlink itself
readlink() ❌ No Reads the path string inside the symlink
unlink() ❌ No Removes the symlink, not the target
rename() ❌ No (either arg) Renames the link itself
chdir() ✅ Yes Follows to the directory

Code Examples

Example 1: Create a symlink with symlink() and read it with readlink()
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>

int main() {
    /* Create a real file first */
    FILE *f = fopen("/tmp/target.txt", "w");
    fputs("I am the real file\n", f);
    fclose(f);

    /* Create a symlink pointing to it */
    if (symlink("/tmp/target.txt", "/tmp/mylink") == -1) {
        perror("symlink");
        return 1;
    }

    /* Read the symlink content (the path string) */
    char buf[PATH_MAX];
    ssize_t len = readlink("/tmp/mylink", buf, sizeof(buf) - 1);
    if (len == -1) {
        perror("readlink");
        return 1;
    }
    buf[len] = '\0';  /* readlink does NOT null-terminate */
    printf("Symlink /tmp/mylink points to: %s\n", buf);

    /* Cleanup */
    unlink("/tmp/mylink");
    unlink("/tmp/target.txt");
    return 0;
}

Output:

Symlink /tmp/mylink points to: /tmp/target.txt
Example 2: stat() vs lstat() — the difference
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>

void print_type(const char *label, struct stat *sb) {
    if (S_ISREG(sb->st_mode))  printf("%s: Regular file\n", label);
    if (S_ISLNK(sb->st_mode))  printf("%s: Symbolic link\n", label);
    if (S_ISDIR(sb->st_mode))  printf("%s: Directory\n", label);
}

int main() {
    /* Create a file and a symlink to it */
    FILE *f = fopen("/tmp/real.txt", "w"); fputs("data", f); fclose(f);
    symlink("/tmp/real.txt", "/tmp/soft.txt");

    struct stat sb1, sb2;

    /* stat() follows the symlink */
    stat("/tmp/soft.txt", &sb1);
    print_type("stat()  on soft.txt", &sb1);

    /* lstat() does NOT follow the symlink */
    lstat("/tmp/soft.txt", &sb2);
    print_type("lstat() on soft.txt", &sb2);

    unlink("/tmp/soft.txt");
    unlink("/tmp/real.txt");
    return 0;
}

Output:

stat()  on soft.txt: Regular file
lstat() on soft.txt: Symbolic link
Example 3: Detect and report dangling symlinks in /tmp
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>

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

    while ((entry = readdir(dp)) != NULL) {
        snprintf(path, sizeof(path), "/tmp/%s", entry->d_name);

        /* lstat tells us if it IS a symlink */
        if (lstat(path, &lsb) == -1) continue;
        if (!S_ISLNK(lsb.st_mode)) continue;

        /* stat follows the link; if it fails, link is dangling */
        if (stat(path, &sb) == -1)
            printf("DANGLING symlink: %s\n", path);
        else
            printf("OK symlink      : %s\n", path);
    }
    closedir(dp);
    return 0;
}
Example 4: Safe open() that refuses to follow symlinks
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main() {
    /* O_NOFOLLOW makes open() fail if path is a symlink */
    int fd = open("/tmp/mylink", O_RDONLY | O_NOFOLLOW);
    if (fd == -1) {
        if (errno == ELOOP)
            printf("Refused: path is a symbolic link\n");
        else
            perror("open");
    } else {
        printf("Opened successfully (not a symlink)\n");
        close(fd);
    }
    return 0;
}

This is important in security-sensitive code where following a symlink could lead to a TOCTOU attack.

Interview Questions

Q1: What is a symbolic link? How is it different from a hard link?
A symlink is a special file whose data contains a path to another file. Unlike a hard link, a symlink has its own inode, is counted separately, can cross filesystems, can point to directories, and becomes dangling if the target is deleted.
Q2: What is a dangling symlink? How do you detect it programmatically?
A dangling symlink points to a target that no longer exists. Detect it by calling lstat() (succeeds — the link exists) and then stat() (fails with ENOENT — the target is gone).
Q3: Is a symlink counted in the target file’s link count? Why not?
No. The symlink count is not added to st_nlink of the target. If the symlink were counted, deleting all hard links wouldn’t bring the count to 0, and the file could never be automatically deleted.
Q4: What is the difference between stat() and lstat() for a symlink?
stat() follows the symlink and gives information about the target file. lstat() does NOT follow and gives information about the symlink itself (type = S_ISLNK).
Q5: What error does the kernel return when a symlink chain is too long?
ELOOP. Linux allows up to 8 dereferences per component and 40 total for an entire pathname.
Q6: Can a symlink be created before its target exists?
Yes. symlink() does not check if the target path exists. The link is created successfully and will appear as a dangling link until the target is created.
Q7: How do you remove a symlink? Does it affect the target?
Use unlink(). It removes only the symlink file itself. The target file is unaffected.
Q8: Why do symlink permissions always show rwxrwxrwx? Do they matter?
Symlink permissions are ignored by the kernel for almost all operations. The kernel uses the permissions of the target file instead. Symlink ownership matters only when the symlink is being deleted or renamed in a sticky-bit directory.

Leave a Reply

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