名古屋出身ソフトウェアエンジニアのブログ

fopen したファイルを unlink しても fread できるという話

公開:
更新:

UNIX 系 OS では、fopen() で開いたファイルを、その後すぐに unlink() しても、開いたファイルポインタからは引き続き fread() などで読み取りが可能です。

これは、UNIX 系 OS のファイル管理の仕組みに由来します。ファイルを unlink() すると、そのファイルへのディレクトリエントリ(パス)が削除され、以降、ファイルシステム上からはそのファイルを見つけることができなくなります。しかし、開かれているファイルディスクリプタやファイルポインタがある限り、ファイルの内容自体は即座に削除されません。

unlink() はファイルの名前(パス)を削除する操作ですが、既に開かれているファイルディスクリプタが存在する場合、そのファイルのデータは物理的に削除されず、最後のファイルディスクリプタが閉じられるまでアクセス可能です。

つまり、次のようなコードが動作します。

# 適当な内容で test.txt を生成しておく
tr -dc 'a-zA-Z0-9!?' < /dev/urandom | fold -w 64 | head -n 100 > test.txt
#include <stdio.h>
#include <unistd.h>

int main() {
    FILE *fp = fopen("test.txt", "r");
    unlink("test.txt");  // ファイルを即座に unlink

    // unlink 後でも問題なく読み出せる
    char buffer[128];
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("%s", buffer);
    }
    fclose(fp);
    return 0;
}

応用例

複数のプロセスが同時に動作するシステムで、異なるプロセスが同時に使用しうるファイルを削除する動作が存在する場合、あるプロセスがファイルを触っている途中で別のプロセスがそのファイルを削除してしまう心配があります。

しかし、一旦プロセスが fopen() でファイルを開くことに成功してしまえば、そのファイルポインタを使ってデータを読み出すことができます。一度ファイルが開かれてしまえば、たとえ後からファイル名が削除されても、既に取得したファイルポインタがあれば問題はなくなります。

以下は PHP での例です。

<?php
$fp = @fopen("test.txt", "r");
if ($fp !== false) {
  // すでにオープンされているため、自分や他プロセスが unlink しても OK
  @unlink("test.txt");
  // ファイルポインタ経由でファイルサイズを得る
  $filesize = fstat($fp)["size"];
  // データも読み込み可能
  $data = fread($fp, $filesize);
  fclose($fp);
  echo $data;
}

ファイルサイズの確認やデータの読み取りには、ファイルパスではなくファイルポインタを利用します。これにより、他のプロセスがファイルを削除した場合でも、既に開いているファイルポインタから失敗することなく情報を取得できます。

これは、ファイルを原子的に作成するために rename() を利用する方法の逆のアプローチと考えることができるかもしれません。すなわち、並行してファイルが削除される可能性のある環境で、オープン済みのファイルポインタを利用して安全に操作を続ける設計方法です。