消えないファイルの話
こんにちは。Windows SDK のサポートをしています masanish です。 今回は、ファイルの削除についてしばしばお問い合わせいただく話題をご紹介します。
ファイルが消せない
いらなくなったファイルを ”完全に" 削除するには、コマンドプロンプトの Del コマンドや エクスプローラー で [Shift] + [Del] を利用しますね。ところが、ファイルを削除しようとしても、消せない、消えてくれないという経験はないでしょうか。この理由として2つのパターンがあります。
1つは、そもそもそのファイルを削除する権限を持っていない場合です。同じコンピュータを利用している別のユーザが作成したファイルとか、もともと Windows を動かすために必要なファイルだったりすると、消されると都合が悪いですね。Windowsでは、それぞれのファイルについて、誰がファイルを消す権限をもっているかどうか決められているので、許可されていない場合にはファイルを消すことができません。
もう1つは、そのファイルが使用中の場合です。使用中というのは、プログラマ的な表現にすると、別のプログラム(プロセス)そのファイルを開いている場合です。だれかが、ほかで使っているいるのに、そのファイルが突然消されてしまうとやっぱり具合が悪いですね。この場合もファイルを消そうとしても失敗します。いずれも、ファイルを消せない理由があるのです。
(消せない...使っているのはだれかぁ)
「ファイルは消せた」 はずだが、まだ残っている...
さて、今回紹介するのは、もうちょっと変わった現象です。というのは、
- ファイルの削除は成功します(ですから、エラーは発生しません)
- でも、ファイルはまだ残っています。たとえば、コマンドプロンプトで Dir や Explorer で [F5] をしてフォルダ中身をみるとちゃんとファイルはあるのです。
- ところが、しばらくするとそのファイルがなくなっている。
これは、ファイルの削除を行った際に、そのファイルの削除が直ちに行われず保留されている状態です。で、どのようなときにファイルの削除が「保留」されるかというと、別のプログラム(プロセス)でまだオープンされている場合です。
「えっと、ちょっと待った。別のファイルでオープンしていると、そのファイルって消せないのじゃないの?ちょっと前にそう書いてありますよね。」 はい、そうです。別のプログラムが使用中の場合に、ファイルを削除できるかどうかは、そのファイルの開き方によるのです。
ちょっと実験
では、このようなファイルの削除が遅延される現象をみてみましょう。
1.ここでは、C++ でのプログラムを用意します。以下のプログラムを コンパイルしてopenfile.exe という名前で作成します。
----
// openfile.c
// cl /Zi openfile.c
#include <windows.h>
#include <stdio.h>
void main( int argc, char** argv )
{
HANDLE hFile ;
if( argc != 2 ){
printf( "%s <file>\n", argv[ 0 ] ) ;
return ;
}
hFile = CreateFile( argv[ 1 ], GENERIC_READ , FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL ,NULL ) ;
if( hFile == (HANDLE)-1 ){
printf( "CreateFile Error %d\n", GetLastError()) ;
return ;
}
printf( "... Hit [Enter] Key to exit ...\n" ) ;
getchar() ;
CloseHandle( hFile ) ;
}
----
2. 消すファイルを 1 つ用意しておきます。a.txt という名前をつけました。
3.このファイルを先程作成したプログラム(openfile.exe )でオープンします。これで準備完了です。
4.次にこのファイルを、別のコマンドプロンプトから削除してみましょう。
エラーメッセージも表示されず削除成功。ほんとに無くなったから確かめてみるとまだ存在しています。
5.この状態がまさに、削除が保留されている状態です。
6. では、openfile.exe を実行したコマンドプロンプトで [Enter] を押してプログラムを終了しましょう。その後で、もう一度 dir を行うと、たしかに消えていますね。ファイルを開いていたプログラムが、そのファイルを閉じる(CloseHandle)することで、保留されいたファイルが削除されることになります。
FILE_SHARE_DELETE フラグ
プログラマの方には常識かもしれないですが、ファイルを開く場合には共有モードというフラグがあります。この共有モードのなかに、実は、削除共有というのがあります。これを指定しておくと、自分がファイルを開いて使用中のときに、別のプログラムがそのファイルを削除することを許可します。あるプログラムがそのファイルを削除しても、ほんとに削除されるのは、ファイルを閉じてからです。
- プログラム A -削除共有でファイル X を開く
- プログラム B - ファイル X を削除 -> 成功
- プログラム A - ファイル X を閉じる
- ファイル X が削除される
この 2 ~3 がファイル削除が保留されている状態です。
FILE_SHARE_DELETEなんてどのような時に利用するか、ちょっとイメージできない方もいらっしゃるかもしれません。自分のアプリで作成する重要なデータファイルを、FILE_SHARE_DELETEを 開くことはしませんよね。
ファイルのインデックスを作成するインデックスサービスや、ウィルススキャンソフトが FILE_SHARE_DELETE を利用することがあるようです。すなわち、対象のファイルにはアクセスしたいのですが、利用者がそのファイルを削除することを邪魔しない。逆にこのようなソフトウェアが、削除共有を指定しないとファイルを消そうとしもたまたまこれらのソフトウェアがファイルのチェックしていたため、消せないということが発生しますね。
Delete Pending という状態
ファイルの削除が保留されている状態と書いてきましたが、Delete Pending とちょっと短く表現します。Delete Pending の状態の特徴を書いてみます。
- そのファイルをオープンしようとすると Access 拒否が発生する。
- FindFirstFile()ではファイルは見つかる。
- そのファイルを削除するとフォルダの削除ができない。
一旦 Delete Pendingになると、あとはファイルが閉じれらてクローズされるのみです。この時点で削除されることが確定しているので、そのファイルを新たに開く(オープン)ことはできません。オープンしようとすると、アクセス拒否というエラーで失敗します。
実は、Delete Pending になっている状態のファイルを再度 Del コマンドで削除しようとすると今度は アクセス拒否 で失敗します。このときの厳密なエラーコードを参照すると STATUS_DELETE_PENDINGになります。process explorerで見てみるとその内容がわかりますね。
一方で、Dir コマンドなどそのフォルダのディレクトリエントリの情報を参照するとまだ存在していることになります。(保留状態なので、まだ見えるということですね)さらに、Delete Pending のファイルが存在しているフォルダを削除しようとしても、まだ保留の状態なので、ファイルが存在するということになります。このため、ファイルの削除が成功したけれども、そのフォルダが削除できないとう場合も発生します。
まとめ
ファイルを削除した場合に、そのファイルの削除が直ちに行われず保留される場合があります。これは他のプログラムが、削除共有という指定ですでにそのファイルをオープンしている場合があります。この状態を Delete Pending といいます。通常別のプログラムがファイルを閉じた場合に、そのファイルが削除されます。
Delete Pending 状態のファイルをオープンすると、アクセス拒否のエラーとなります。そのファイルが完全に削除されるタイミングは、オープンしている別のプログラムに依存します。このため、ファイルの削除後、ファイルの作成に失敗するということが発生した場合には、たとえば一定期間再試行をするという考慮が必要です。