link() and unlink() System Calls
Chapter 18 Part 3 โ Creating and Removing Hard Links | EmbeddedPathashala
๐
link()
๐๏ธ
unlink()
๐๏ธ
Open File Trick
What do link() and unlink() do?
link() creates a new hard link to an existing file โ a new name pointing to the same inode. unlink() removes a directory entry (a name). If that was the last name for the file AND no process has it open, the file data is freed.
link() unlink() EEXIST ENOENT link count open file descriptor tmpfile trick EISDIR
Function Signatures
#include <unistd.h>
/* Create a new hard link to an existing file */
int link(const char *oldpath, const char *newpath);
/* Returns: 0 on success, -1 on error */
/* Remove a hard link (directory entry) */
int unlink(const char *pathname);
/* Returns: 0 on success, -1 on error */
For link(): oldpath is the existing file, newpath is the new link to create.
For unlink(): pathname is the directory entry to remove.
link() โ What Happens Step by Step
1 Kernel finds the inode for
oldpath2 Kernel adds a new entry to the parent directory of
newpath: newname โ same inode#3 Inode’s link count (st_nlink) is incremented by 1
โ ๏ธ If
newpath already exists, link() fails with EEXIST โ it does NOT overwrite.โ ๏ธ On Linux,
link() does NOT dereference oldpath if it is a symlink. The new link points to the symlink file, not the symlink’s target. This is non-standard behavior.unlink() โ When Does the File Actually Disappear?
This is one of the most important concepts in Linux file handling.
๐
File exists
link count = 1
open fds = 1
open fds = 1
โ unlink() โ
๐ป
Name gone
link count = 0
but still open
data still exists!
but still open
data still exists!
โ close() โ
๐
Truly gone
inode freed
blocks freed
blocks freed
โ
A file is deleted from disk only when: (1) link count = 0 AND (2) no open file descriptors exist for it.
โ
This enables the “anonymous temp file trick”: open a file, immediately unlink it, use it in your program, and when you close it (or the program exits), the data is cleaned up automatically.
Code Examples
Example 1: Basic link() and unlink()
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
void show_links(const char *path) {
struct stat sb;
if (stat(path, &sb) == 0)
printf("'%s' -> inode %lu, links=%lu\n",
path, (unsigned long)sb.st_ino,
(unsigned long)sb.st_nlink);
else
printf("'%s' -> does not exist\n", path);
}
int main() {
/* Create a file */
int fd = open("/tmp/file_a", O_WRONLY | O_CREAT | O_TRUNC, 0644);
write(fd, "Hello, world!\n", 14);
close(fd);
printf("=== Initial state ===\n");
show_links("/tmp/file_a");
/* Create a hard link */
if (link("/tmp/file_a", "/tmp/file_b") == -1) {
perror("link");
return 1;
}
printf("\n=== After link(file_a, file_b) ===\n");
show_links("/tmp/file_a");
show_links("/tmp/file_b");
/* Remove the original name */
unlink("/tmp/file_a");
printf("\n=== After unlink(file_a) ===\n");
show_links("/tmp/file_a");
show_links("/tmp/file_b"); /* Still exists! */
/* Cleanup */
unlink("/tmp/file_b");
return 0;
}
Output:
=== Initial state ===
'file_a' -> inode 12345, links=1
=== After link(file_a, file_b) ===
'file_a' -> inode 12345, links=2
'file_b' -> inode 12345, links=2
=== After unlink(file_a) ===
'file_a' -> does not exist
'file_b' -> inode 12345, links=1
Example 2: The Anonymous Temp File Trick
Create a file, unlink the name immediately, then use the file via fd. It gets cleaned up automatically on close.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main() {
/* Create a temp file */
int fd = open("/tmp/secret_data", O_RDWR | O_CREAT | O_TRUNC, 0600);
if (fd == -1) { perror("open"); return 1; }
/* IMMEDIATELY unlink the name โ file is now "invisible" */
unlink("/tmp/secret_data");
/* The name is gone but we can still use the fd */
/* Write sensitive data */
const char *msg = "This data is never on disk with a visible name!\n";
write(fd, msg, strlen(msg));
/* Read back from start */
lseek(fd, 0, SEEK_SET);
char buf[256];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
buf[n] = '\0';
printf("Data via fd: %s", buf);
/* When we close fd, the file is physically deleted */
close(fd);
printf("File cleaned up automatically on close\n");
return 0;
}
Example 3: Trying to unlink a directory (should fail)
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
int main() {
mkdir("/tmp/testdir", 0755);
if (unlink("/tmp/testdir") == -1) {
/* Linux returns EISDIR for this, SUSv3 says EPERM */
printf("unlink on directory failed: %s (errno=%d)\n",
strerror(errno), errno);
}
/* Correct way to remove a directory */
if (rmdir("/tmp/testdir") == 0)
printf("rmdir succeeded\n");
return 0;
}
Output:
unlink on directory failed: Is a directory (errno=21)
rmdir succeeded
Example 4: linkat() โ newer variant with dirfd support
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
/* Open the source directory */
int srcdir = open("/tmp", O_RDONLY | O_DIRECTORY);
int dstdir = open("/tmp", O_RDONLY | O_DIRECTORY);
/* Create a test file */
int fd = open("/tmp/orig.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
write(fd, "data", 4);
close(fd);
/* linkat with AT_FDCWD is same as link() when using absolute paths */
if (linkat(srcdir, "orig.txt", dstdir, "copy.txt", 0) == -1)
perror("linkat");
else
printf("linkat() succeeded: /tmp/copy.txt created\n");
close(srcdir);
close(dstdir);
unlink("/tmp/orig.txt");
unlink("/tmp/copy.txt");
return 0;
}
linkat() with AT_SYMLINK_FOLLOW flag can dereference symlinks in oldpath โ fixing the non-standard behavior of link().
Example 5: Simulate a safe file update (write new, rename, old vanishes)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
/* Safe way to update /tmp/config.txt atomically */
int safe_write(const char *target, const char *data) {
char tmpname[256];
snprintf(tmpname, sizeof(tmpname), "%s.tmp", target);
int fd = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) { perror("open"); return -1; }
write(fd, data, strlen(data));
close(fd);
/* Atomic rename: if rename succeeds, update is complete */
if (rename(tmpname, target) == -1) {
perror("rename");
unlink(tmpname); /* Clean up temp file */
return -1;
}
return 0;
}
int main() {
if (safe_write("/tmp/config.txt", "key=value\nmode=debug\n") == 0)
printf("Config updated atomically\n");
return 0;
}
Interview Questions
Q1: What is the difference between link() and symlink()?
link() creates a hard link โ a new directory entry pointing to the same inode. symlink() creates a symbolic link โ a new file whose data contains a path string pointing to another file.
link() creates a hard link โ a new directory entry pointing to the same inode. symlink() creates a symbolic link โ a new file whose data contains a path string pointing to another file.
Q2: When does unlink() actually delete the file’s data?
Only when the link count drops to 0 AND all open file descriptors for that file are closed. Removing the last filename sets count to 0, but if a process has the file open, data stays until that fd is closed.
Only when the link count drops to 0 AND all open file descriptors for that file are closed. Removing the last filename sets count to 0, but if a process has the file open, data stays until that fd is closed.
Q3: Can you use unlink() on a directory?
No. On Linux it returns EISDIR. Use rmdir() for empty directories. SUSv3 specifies EPERM but Linux returns EISDIR.
No. On Linux it returns EISDIR. Use rmdir() for empty directories. SUSv3 specifies EPERM but Linux returns EISDIR.
Q4: What is the “anonymous temp file” trick?
Open a temp file, immediately unlink the name, and keep using the fd. The file has no visible name but the data exists because there’s an open fd. When the fd is closed (or process exits), the data is automatically freed.
Open a temp file, immediately unlink the name, and keep using the fd. The file has no visible name but the data exists because there’s an open fd. When the fd is closed (or process exits), the data is automatically freed.
Q5: Why does link() on Linux not follow symlinks in oldpath?
This is a non-standard Linux behavior. Instead of creating a hard link to the symlink’s target, Linux creates a hard link to the symlink file itself. Portable code should avoid passing a symlink as oldpath. Use linkat() with AT_SYMLINK_FOLLOW for standard behavior.
This is a non-standard Linux behavior. Instead of creating a hard link to the symlink’s target, Linux creates a hard link to the symlink file itself. Portable code should avoid passing a symlink as oldpath. Use linkat() with AT_SYMLINK_FOLLOW for standard behavior.
Q6: What error does link() return if newpath already exists?
EEXIST. Unlike rename(), link() does not overwrite an existing path.
EEXIST. Unlike rename(), link() does not overwrite an existing path.
Q7: A process opens a file and another process unlinks it. Can the first process still read the file?
Yes. The first process’s file descriptor remains valid. The data is preserved until all fds are closed. This is guaranteed by the kernel maintaining an open file description count separate from the link count.
Yes. The first process’s file descriptor remains valid. The data is preserved until all fds are closed. This is guaranteed by the kernel maintaining an open file description count separate from the link count.
