Linux Directories and Links: Complete Guide to File System Navigation
Linux Directories and Links: Complete Guide to File System Navigation
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
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.
real.txt, mylink becomes a dangling link — it still exists but points to nothing.| 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 |
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
Symlinks can point to other symlinks, forming a chain. The kernel follows the chain automatically, but there is a limit to prevent infinite loops.
ELOOP.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
#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
#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
#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;
}
#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
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.
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).
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.
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).
ELOOP. Linux allows up to 8 dereferences per component and 40 total for an entire pathname.
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.
Use unlink(). It removes only the symlink file itself. The target file is unaffected.
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.
