ディレクトリの正体:特殊なファイルとしての構造

Unix ではディレクトリも「ファイル」の一種として扱われる。ただし通常のファイルとは異なり、特殊な構造を持っている。

ディレクトリは特殊なファイル

ディレクトリの実体は「ファイル名と inode 番号のペアを格納したテーブル」だ。

通常のファイル

任意のバイト列を格納する。テキスト、画像、バイナリなど何でも入る

ディレクトリ

「ファイル名 → inode 番号」のマッピングを格納する。中身はカーネルが管理する特殊なフォーマット

# ディレクトリも inode を持つ
ls -lid /home/user
# 1048577 drwxr-xr-x 5 user user 4096 Jan 15 10:00 /home/user

# 'd' はディレクトリを示す
stat /home/user | grep "File:"
# File: /home/user
# Size: 4096       Blocks: 8          IO Block: 4096   directory

ディレクトリエントリの構造

ディレクトリ内の各エントリは「dirent(directory entry)」と呼ばれる構造を持つ。

inode 番号

エントリが指すファイルの inode 番号。

ファイル名

可変長の文字列。ファイルシステムによって最大長が異なる(ext4 では 255 バイト)。

ファイルタイプ

通常ファイル、ディレクトリ、シンボリックリンクなどを示すフラグ(一部のファイルシステムのみ)。

Python から見ると、os.scandir() がこのディレクトリエントリに直接アクセスする。

import os

# scandir はディレクトリエントリを直接読む
with os.scandir("/home/user") as entries:
    for entry in entries:
        print(f"{entry.name}: inode={entry.inode()}, is_dir={entry.is_dir()}")

os.listdir() よりも os.scandir() のほうが効率的なのは、ディレクトリエントリから直接情報を取得でき、追加の stat() 呼び出しが不要な場合があるためだ。

「.」と「…」エントリ

すべてのディレクトリには 2 つの特殊エントリが存在する。

. (ドット)

自分自身を指す。現在のディレクトリの inode への参照

.. (ドットドット)

親ディレクトリを指す。一つ上の階層の inode への参照

# . と .. の inode を確認
ls -lia /home/user
# 1048577 drwxr-xr-x 5 user user 4096 Jan 15 10:00 .
# 1048576 drwxr-xr-x 3 root root 4096 Jan 10 09:00 ..

# . は /home/user の inode
# .. は /home の inode

これが「ディレクトリのリンクカウントが 2 以上になる」理由だ。空のディレクトリでもリンクカウントは 2(自身へのエントリ「.」と親からのエントリ)。サブディレクトリを作ると、子の「…」から参照されるためカウントが増える。

なぜディレクトリを直接読めないのか

ディレクトリは特殊なファイルなので、通常の read() では中身を読めない。

# これはエラーになる
try:
    with open("/home/user", "r") as f:
        content = f.read()
except IsADirectoryError as e:
    print(e)  # [Errno 21] Is a directory: '/home/user'

カーネルがディレクトリの読み取りを禁止しているのは、フォーマットがファイルシステムごとに異なるためだ。代わりに readdir() システムコール(Python では os.scandir()os.listdir())を使う。

// C 言語では opendir/readdir を使う
#include <dirent.h>

DIR *dir = opendir("/home/user");
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
    printf("%s: inode=%lu\n", entry->d_name, entry->d_ino);
}
closedir(dir);

ディレクトリのハードリンクは作れない

ファイルにはハードリンクを作れるが、ディレクトリには(通常)作れない。

# ディレクトリへのハードリンクは禁止
ln /home/user /tmp/userlink
# ln: /home/user: hard link not allowed for directory

ディレクトリのハードリンクを許可すると、ファイルシステムが循環参照を持つ可能性があり、多くのツール(find、du など)が無限ループに陥る危険がある。「.」と「…」は例外的に許可された特殊なハードリンクだ。

ディレクトリのサイズ

ディレクトリのサイズは「中身のファイルの合計サイズ」ではなく、「ディレクトリエントリを格納する領域のサイズ」だ。

# 空のディレクトリでも 4096 バイト(1 ブロック)
mkdir empty_dir
ls -ld empty_dir
# drwxr-xr-x 2 user user 4096 Jan 15 10:00 empty_dir

# ファイルを大量に作るとディレクトリ自体のサイズが増える
cd empty_dir
touch file{1..10000}
ls -ld .
# drwxr-xr-x 2 user user 155648 Jan 15 10:01 .

ディレクトリを「中身をリストアップするための帳簿」と考えると理解しやすい。帳簿自体のページ数と、帳簿に記録された商品の合計価格は別物だ。