なぜ Directory が開かれたままになるのか? ―― ひとつの解

ついでにもう一点.
『ファイルの使用者を調べる PowerShell Cmdlet』に対し,こういった質問をいただきました.

bear.mini 『ソースコード、ダウンロードさせていただきました。(中略) ところで、ファイルだけでなく、ディレクトリを Lock しているプロセスを識別できるようにはなりませんでしょうか?ディレクトリ内のファイルを再帰的にチェックさせようとすると、どうもディレクトリ自体のチェックの際にエラーが発生しているようですので・・・。』

あとからよく考えてみると,確かに Explorer から「特定ディレクトリだけ操作できなくなること」というのは少なからず経験があります.中のファイルは削除できたのに,なぜか大元のディレクトリを削除しようとすると,「別のプログラムがこのフォルダを開いているので、操作を完了できません」というダイアログが表示されてしまうという経験,皆さんもありませんか?.

ディレクトリのファイルハンドル

さて,何らかのプロセスが何らかの形でフォルダを開いているのは確からしいとして,普段 Win32 プログラミングを行っている身からすると,「そもそもディレクトリのファイルハンドルって開けたっけ?」という疑問もわいてきます.
実際には MSDN Library の CreateFile の頁にあるように,「開ける」が正解なのですが,どうも FILE_FLAG_BACKUP_SEMANTICS を指定して特殊な開き方をする必要があるようです.

HANDLE handle = CreateFile(
    _T("c:\\test"),
    READ_CONTROL,
    0,
    NULL,
    OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS,
    NULL);

しかし,バックアップソフトが裏で動いているならまだしも,こんなへんてこな動作をするプロセスがそんなにいるものでしょうか?

ディレクトリのファイルハンドル (再考)

一方,id:NyaRuRu:20070808:p1 の実験から,どうも全てのプロセスは最低ひとつのディレクトリを開いているらしいことも分かります.そして多くのプロセスは,自身の exe ファイルが置かれているディレクトリのハンドルを開いています.どうやらこれは FILE_FLAG_BACKUP_SEMANTICS とは関係ない理由で,プロセスがディレクトリのハンドルを所有する仕組みがありそうです.
ちょっとしたクイズですが,これらの状況証拠から,どうもプロセスのカレントディレクトリが怪しそうに見えてきます.実験してみるとこの直感は正しく,SetCurrentDirectory API によってカレントディレクトリを変更すると,プロセスがそのディレクトリのファイルハンドルをオープンすることが,Process Explorer や handle.exe によって確かめられました.実験環境は Windows Vista Ultimate Edition 日本語版です.

// c:\test のファイルハンドルが開かれる
BOOL ret = SetCurrentDirectory( L"c:\\test" );

悪魔のシナリオ

ここまで来れば,いったいどういったシナリオで「フォルダが削除できなくなる」か,勘のいい人ならいくつか想像が付いているのではないでしょうか.
たとえば Windows のバッドノウハウに,「ファイル選択ダイアログでカレントディレクトリが変わる」というものがあります.ならばシナリオはこんな感じ.

  1. c:\test\hoge.txt をあるプロセスのファイル選択ダイアログで開く (hoge.txt と c:\test の 2 つのファイルハンドルが開かれる)
  2. hoge.txt を閉じる (hoge.txt のファイルハンドルが閉じられる)
  3. hoge.txt を Explorer から削除 (これは可能)
  4. c:\test を Explorer から削除しようとしてエラー (あるプロセスのカレントディレクトリになったままなので削除できない)

他にも,c:\test\hoge.pptx をダブルクリックで開いたところ,PowerPoint 2007 のカレントディレクトリが c:\test になってしまったとか,conime.exe のような常駐プロセスのカレントディレクトリが何かの拍子でデスクトップ上の特定フォルダになってしまったとか,色々とバリエーションはありそうです.
以上,夏の夜のちょっと怖い話でした.