Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
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.
#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.
oldpathnewpath: newname → same inode#newpath already exists, link() fails with EEXIST — it does NOT overwrite.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.This is one of the most important concepts in Linux file handling.
open fds = 1
but still open
data still exists!
blocks freed
Code Examples
#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
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;
}
#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
#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;
}
#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
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.
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.
No. On Linux it returns EISDIR. Use rmdir() for empty directories. SUSv3 specifies EPERM but Linux returns EISDIR.
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.
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.
EEXIST. Unlike rename(), link() does not overwrite an existing path.
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.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
