古いスレッドに何かを追加してすみません。そして、そのような長いポストをするために。
私は、ロックがなくても完全な競合状態を解放する唯一の方法を知っていますrename()
。これは、サーバーの再起動が断続的に行われ、クライアントのタイムワープが発生しているNFSでも、実質的にどのファイルシステムでも機能するはずです。
次のレシピは、いかなる状況でもデータが失われることがないという意味で、競合状態がありません。また、ロックを必要とせず、すべて同じアルゴリズムを使用することを除いて、協力したくないクライアントが実行できます。
何かが深刻に壊れた場合、すべてがきれいに整頓された状態に保たれるという意味で、競合状態がないわけではありません。また、ソースも宛先もその場所に存在しない短い期間がありますが、ソースはまだ別の名前で存在しています。そして、攻撃者が危害を加えようとする場合に対しては強化されません(rename()
犯人です、図を見てください)。
Sはソース、Dは宛先、P(x)はdirname(x)
、C(x、y)はx/y
パス連結です。
- 宛先が存在しないことを確認してください。次のステップを無駄にしないようにするためです。
- おそらく一意の名前を作成するT:= C(P(D)、random)
- mkdir(T)、これが失敗した場合は前のステップへのループ
- open(C(T、 "lock")、O_EXCL)、これが失敗した場合rmdir(T)はエラーを無視し、前のステップにループします
- rename(S、C(T、 "tmp"))
- link(C(T、 "tmp")、D)
- unlink(C(T、 "tmp"))
- unlink(C(T、 "lock"))
- rmdir(T)
アルゴリズムのsafe_rename(S,D)
説明:
問題は、ソースにもデスティネーションにも競合状態がないことを確認したいということです。(ほぼ)各ステップ間で何かが発生する可能性があると想定されますが、競合状態のない名前変更を行う場合、他のすべてのプロセスはまったく同じアルゴリズムに従います。これには、一時ディレクトリTが変更されないことも含まれます。ただし、ディレクトリを使用するプロセスが停止し、復活できないことを確認した後(これは手動プロセスです)(復元後にVMの休止状態を継続する場合など)を除きます。
適切に行うにはrename()
、隠れるための場所が必要です。そのため、他の人(同じアルゴリズムを使用している人)が誤って使用しないようにディレクトリを作成します。
ただしmkdir()
、NFSでアトミックであることが保証されているわけではありません。したがって、ディレクトリ内で1人であることが保証されていることを確認する必要があります。これはO_EXCL
ロックファイルにあります。これは、厳密に言えば、ロックではなく、セマフォです。
このようなまれなケースを除いて、mkdir()
通常はアトミックです。また、ディレクトリに暗号的に安全なランダム名を使用して作成し、GUID、ホスト名、およびPIDを追加して、他の誰かが偶然同じ名前を選択する可能性が非常に低いことを確認できます。ただし、アルゴリズムが正しいことを証明するには、という名前のこのファイルが必要ですlock
。
ほとんど空のディレクトリができrename()
たので、そこにソースを安全に置くことができます。これにより、私たちが変更するまで、他の誰もソースを変更しないことが保証さunlink()
れます。(まあ、内容は変わる可能性がありますが、これは問題ありません。)
これで、このlink()
トリックを適用して、宛先を上書きしないようにすることができます。
その後、unlink()
残りのソースで競合状態を発生させずに実行できます。残りはクリーンアップです。
残っている問題は1つだけです。
失敗した場合はlink()
、すでにソースを移動しています。適切にクリーンアップするには、元に戻す必要があります。これは、を呼び出すことで実行できますsafe_rename(C(T,"tmp"),S)
。これも失敗した場合、私たちにできることは、できる限りクリーンアップを試み(unlink(C(T,"lock"))
、rmdir(T)
)、管理者による手動クリーンアップのために破片を残しておくことです。
最後の注意:
デブリケースのクリーンアップを支援するために、よりも適切なファイル名を使用できる可能性がありますtmp
。名前を巧妙に選択すると、攻撃に対するアルゴリズムも多少強化される可能性があります。
また、大量のファイルをどこかに移動する場合は、もちろんディレクトリを再利用できます。
ただし、このアルゴリズムは明らかにやり過ぎであり、O_EXCL
onのようなものrename()
が欠落していることに同意します。