ディレクトリの正体:特殊なファイルとしての構造
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 番号。
可変長の文字列。ファイルシステムによって最大長が異なる(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 .ディレクトリを「中身をリストアップするための帳簿」と考えると理解しやすい。帳簿自体のページ数と、帳簿に記録された商品の合計価格は別物だ。