Β§39.11–39.14Directories, and the Mystery of unlink(): Hard Links

Part III OSTEP pp. 480–483 Β· ~6 min read

  • hard link
  • link count

We can now solve the puzzle from the last page β€” why is deleting a file called unlink? The answer runs through directories and a second way to name a file.

39.11 Making Directories

A set of calls create, read, and delete directories. You can never write a directory directly: its format is file-system metadata, and the file system keeps sole responsibility for its integrity β€” you change it only indirectly, by creating files or directories inside it. To make one, call mkdir().

A freshly made directory is β€œempty” but not truly bare β€” it always has two entries: . (dot), referring to itself, and .. (dot-dot), referring to its parent:

foo/ (new). β†’ foo.. β†’ parent. = itself.. = parent

Tip: Be Wary Of Powerful Commands

rm * deletes every file in a directory; rm -rf * recursively deletes directories and all their contents. Run that by accident from the wrong place (say, /) and you can erase an entire file system. Great power, one keystroke away from great harm β€” tread carefully.

39.12 Reading Directories

To read a directory (what ls does) you don’t open() it like a file; you use opendir(), readdir(), and closedir(), looping one entry at a time:

DIR *dp = opendir(".");
struct dirent *d;
while ((d = readdir(dp)) != NULL) {
    printf("%lu %s\n", (unsigned long) d->d_ino, d->d_name);
}
closedir(dp);

Each struct dirent is light β€” mostly a name and an inode number:

struct dirent {
    char           d_name[256]; // filename
    ino_t          d_ino;       // inode number
    off_t          d_off;       // offset to the next dirent
    unsigned short d_reclen;    // length of this record
    unsigned char  d_type;      // type of file
};

Because a directory holds so little per entry, a tool that wants more (size, owner…) calls stat() on each file β€” which is exactly what ls -l does.

39.13 Deleting Directories

You delete an empty directory with rmdir(). It has a safety requirement: the directory must be empty (contain only . and ..) first. Trying to rmdir() a non-empty directory simply fails β€” a guardrail against wiping out a lot of data with one call.

Now the payoff. The link(old, new) system call β€” via the ln program β€” creates a hard link : a new directory entry that points at the same inode number as an existing file. Nothing is copied; you just get a second human-readable name for one underlying file. ls -i (which prints inode numbers) proves it:

prompt> ln file file2
prompt> ls -i file file2
67158084 file
67158084 file2

So creating a file is really two steps: make an inode (holding the data and metadata), and link a name to it in a directory. A hard link just adds another name β†’ same inode. To track this, the inode holds a link count (a reference count of how many names point at it). unlink() removes one name and decrements the count; the inode and its data blocks are freed only when the count reaches zero. Watch it happen:

A hard link is another directory entry pointing at the SAME inode. The inode's link count tracks how many names refer to it; the file dies only when it hits zero. (Blue = names, brown = inode, gold = count.)
directory entriesfileinode 67158084link count: 1one name β†’ one inode, count 1

1echo hello > file

Creating a file does two things: it makes an inode (holding the file's real data + metadata) and links one human-readable name to it in the directory. Link count: 1.

step 1 / 6

And there’s the answer to the mystery: the call is unlink(), not delete(), because removing a name usually doesn’t delete the file β€” it just unlinks one of possibly-many names, and only the last unlink (count β†’ 0) truly frees it.

Check yourself: directories and hard links

1.Why can't a program write to a directory directly with write()?

2.A freshly created directory is 'empty', yet it already contains two entries. Which?

3.What restriction does rmdir() impose?

4.What exactly does a hard link (via link()) create?

5.You create a file, then two hard links to it (3 names), then rm two of them. Is the file's data still there?

6.Why is the file-removal system call named unlink() rather than delete()?

6 questions