9

A名前をに変更したいが、存在しないB場合に限り、単純なことは、存在するかどうか(withまたはそのようなもの)をBチェックし、それが続行しないかどうかを確認することです。残念ながら、これによりウィンドウが開き、その間に他のプロセスが作成を決定する可能性があり、その後上書きされます。さらに悪いことに、そのようなことが起こったという兆候はありません。Baccess("B", F_OK)renameB

他のファイルシステムアクセス機能はこれに悩まされていません-open持っています(ファイルのコピーは安全です)、そして最近Linuxは他のほとんどの競合状態から保護するsyscallO_EXCLのファミリー全体を手に入れました-しかしこの特定のものではありません(存在しますが、まったく異なる問題)。*atrenameat

それで、それは解決策を持っていますか?

4

5 に答える 5

15

(2)を新しいファイル名にリンクできるはずです。リンクが失敗した場合は、ファイルがすでに存在するため、あきらめます。リンクが成功すると、ファイルは古い名前と新しい名前の両方で存在するようになります。次に、(2)古い名前のリンクを解除します。競合状態の可能性はありません。

于 2010-07-11T09:00:48.897 に答える
7

link()必要な新しいファイル名で既存のファイルに移動してから、既存のファイル名を削除することができます。

link()新しいパス名がまだ存在しない場合にのみ、新しいリンクの作成に成功するはずです。

何かのようなもの:

int result = link( "A", "B");

if (result != 0) {
    // the link wasn't created for some reason (maybe because "B" already existed)
    // handle the failure however appropriate...
    return -1;
}

// at this point there are 2 filenames hardlinked to the contents of "A", 
//   filename "A" and filename "B"

// remove filename "A"
unlink( "A");

この手法については、次のドキュメントで説明されていlink()ます(passwdファイルの変更に関する説明を参照してください)。

于 2010-07-11T09:04:00.640 に答える
3

古いスレッドに何かを追加してすみません。そして、そのような長いポストをするために。

私は、ロックがなくても完全な競合状態を解放する唯一の方法を知っていますrename()。これは、サーバーの再起動が断続的に行われ、クライアントのタイムワープが発生しているNFSでも、実質的にどのファイルシステムでも機能するはずです。

次のレシピは、いかなる状況でもデータが失われることがないという意味で、競合状態がありません。また、ロックを必要とせず、すべて同じアルゴリズムを使用することを除いて、協力したくないクライアントが実行できます。

何かが深刻に壊れた場合、すべてがきれいに整頓された状態に保たれるという意味で、競合状態がないわけではありません。また、ソースも宛先もその場所に存在しない短い期間がありますが、ソースはまだ別の名前で存在しています。そして、攻撃者が危害を加えようとする場合に対しては強化されません(rename()犯人です、図を見てください)。

Sはソース、Dは宛先、P(x)はdirname(x)、C(x、y)はx/yパス連結です。

  1. 宛先が存在しないことを確認してください。次のステップを無駄にしないようにするためです。
  2. おそらく一意の名前を作成するT:= C(P(D)、random)
  3. mkdir(T)、これが失敗した場合は前のステップへのループ
  4. open(C(T、 "lock")、O_EXCL)、これが失敗した場合rmdir(T)はエラーを無視し、前のステップにループします
  5. rename(S、C(T、 "tmp"))
  6. link(C(T、 "tmp")、D)
  7. unlink(C(T、 "tmp"))
  8. unlink(C(T、 "lock"))
  9. 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_EXCLonのようなものrename()が欠落していることに同意します。

于 2011-03-19T01:21:05.273 に答える
2

Linuxカーネル3.15(2014年6月にリリース)以降、これはsyscall(__ NR_renameat2、AT_FDCWD、 "source-file"、AT_FDCWD、 "dest-file"、RENAME_NOREPLACE)(、、<syscall.h>および<fcntl.h>)を使用して実行できます<linux/fs.h>

これは、両方のファイル名が同時に存在するポイントがないため、link()よりも優れています(特に、link()を使用すると、正確なタイミングで停電が発生すると、両方の名前が永久に残る可能性があります)。

glibc 2.28(2018年8月にリリース)はrenameat2()ラッパーを追加するため、syscall.hおよびlinux / fs.hの代わりにそれを使用できます(ただし、代わりに必要<stdio.h>になる可能性があり#define __GNU_SOURCEます)。

詳細については、http://man7.org/linux/man-pages/man2/rename.2.htmlを参照してください(ただし、執筆時点では、glibcにrenameat2ラッパーがあることはわかりません)。

于 2018-08-01T22:47:58.707 に答える
1

名前の変更のマニュアルページから:

newpathがすでに存在する場合は、アトミックに置き換えられます(いくつかの条件があります。以下のエラーを参照してください)。そのため、newpathにアクセスしようとする別のプロセスが、newpathが欠落していることを検出することはありません。

したがって、Bファイルがすでに存在する場合、名前の変更を回避することはできません。ファイルがすでに存在する場合に名前の変更を行わせたくない場合は、名前の変更を試みる前に、存在を確認する(そのために使用しstat()ない)以外に選択肢がないのではないかと思います。access()競合状態を無視します。

それ以外の場合は、link()を使用して以下に示すソリューションが要件に適合しているようです。

于 2010-07-11T08:21:28.920 に答える