PHP

PHPは上書きしないrenameができない

PHPのrename関数はファイルが存在すると上書きする仕様になっている。上書きrenameをさせたくなくてもだ。renameの直前でfile_existsでrename先のファイルが存在しないことをチェックしたとしても、上書きrenameの仕様を完全に解消することはできない。もちろん意図しない上書きが起こる可能性を下げることはできる。

file_put_contents('a', 'a');

echo "check if file does not exist\n";
file_exists('b') && exit(1); // ファイルが存在しないことチェック

echo "other program create file b\n";
file_put_contents('b', 'b'); // 他のプログラムが同時期にbを作ってしまう

echo "before rename\n";
rename('a', 'b'); // 他のプログラムが作ったbをaで上書きしてしまう

echo 'now file b content is ' . file_get_contents('b') . "\n";

unlink('b');


renameが内部でどんなシステムコールを使っているのか?

touch('a');

echo 'before rename';
rename('a', 'b');
echo 'after rename';
unlink('b');

write(1, "before rename", 13before rename)           = 13

rename("a", "b") = 0
write(1, "after rename", 12after rename) = 12


Linuxのrenameインターフェイス

int rename(const char *oldpath, const char *newpath);


newpath が既に存在する場合、それは不可分操作で (atomically) 置き換えられる (ただし、いくつかの条件がある; 以下の「エラー」のセクションを参照)。 そのため、 newpath にアクセスしようとしている他のプロセスがファイルを見失うことはない (訳註: 常にアクセス可能である)。

── Man page of RENAME


PHPのrename関数はLinuxのrenameシステムコールをそのまま使っているようで、PHPのrenameの「newname が存在する場合、上書きされます」という仕様はLinuxの仕様を踏襲していると考えられる。

PHP__rename_-_Manual.png

Linuxのシステムコールにはrenameat2があり、これは上書きしないオプションが存在する。が、PHPではこれを使っていない。


考えられる対策


  • ファイル名がかぶらないものにする(UUIDとか)

  • linkで一時ファイルのハードリンクを作ってから一時ファイルを消す

続く