rsync
この仕事をします。一時ファイルは O_EXCL
です デフォルトで作成されます (--inplace
を使用する場合のみ無効になります) ) そして renamed
ターゲットファイルの上。 --ignore-existing
を使用 存在する場合、B を上書きしないようにします。
実際には、ext4、zfs、または NFS マウントでさえ、これで問題を経験したことはありません.
心配しないで、noclobber
は標準機能です。
あなたは NFS について尋ねました。 noclobber
のチェックが 2 つの別個の NFS 操作 (ファイルが存在するかどうかの確認、新しいファイルの作成) が含まれ、2 つの別個の NFS クライアントからの 2 つのプロセスが競合状態になり、両方が成功する可能性があります (両方とも B.part
まだ存在しない場合は、両方が正常に作成され、結果としてお互いに上書きされます。)
書き込んでいるファイルシステムが noclobber
のようなものをサポートするかどうかについて一般的なチェックを行う必要はありません。 アトミックかどうか。 NFSであるかどうかにかかわらず、ファイルシステムのタイプを確認できますが、それはヒューリスティックであり、必ずしも保証されるわけではありません. SMB/CIFS (Samba) などのファイルシステムも同じ問題に悩まされる可能性があります。 FUSE を介して公開されるファイルシステムは、正しく動作する場合と正しく動作しない場合がありますが、それは主に実装に依存します。
おそらくより良いアプローチは、 B.part
での衝突を避けることです noclobber
に依存する必要がないように、(他のエージェントと協力して) 一意のファイル名を使用することにより、手順を実行します。 .たとえば、ファイル名の一部として、ホスト名、PID、およびタイムスタンプ (+乱数の可能性あり) を含めることができます。任意の時点でホストの特定の PID の下で単一のプロセスが実行されている必要があるため、これは一意性を保証します。
したがって、次のいずれか:
test -f B && continue # skip already existing
unique=$(hostname).$$.$(date +%s).$RANDOM
cp A B.part."$unique"
# Maybe check for existance of B again, remove
# the temporary file and bail out in that case.
mv B.part."$unique" B
# mv (rename) should always succeed, overwrite a
# previously copied B if one exists.
または:
test -f B && continue # skip already existing
unique=$(hostname).$$.$(date +%s).$RANDOM
cp A B.part."$unique"
if ln B.part."$unique" B ; then
echo "Success creating B"
else
echo "Failed creating B, already existed"
fi
# Both cases require cleanup.
rm B.part."$unique"
したがって、2 つのエージェント間で競合状態が発生した場合、両者は操作を続行しますが、最後の操作はアトミックであるため、B が A の完全なコピーと共に存在するか、B が存在しません。
コピーの後、mv
の前にもう一度チェックすることで、レースのサイズを小さくすることができます。 または ln
動作しますが、まだ小さな競合状態があります。ただし、競合状態に関係なく、両方のプロセスが A (またはオリジンとして有効なファイルからのコピー) から B を作成しようとしていると仮定すると、B の内容は一貫している必要があります。
mv
の最初の状況で注意してください 、競合が存在する場合、rename(2) は既存のファイルをアトミックに置き換えるため、最後のプロセスが勝つプロセスです:
If 新しいパス newpath にアクセスしようとする別のプロセスが存在しないように、アトミックに置き換えられます。 欠けていることがわかります。 [...]
If 新しいパス 存在しますが、何らかの理由で操作が失敗します rename()
newpath のインスタンスを残すことを保証します
そのため、その時点で B を消費するプロセスが、このプロセス中に異なるバージョン (異なる i ノード) を参照する可能性は十分にあります。ライターがすべて同じ内容をコピーしようとしていて、リーダーが単にファイルの内容を消費している場合、それは問題ないかもしれません。同じ内容のファイルに対して異なる i ノードを取得しても、同じように満足します。
ハード リンクを使用する 2 番目のアプローチ 見た目 より良いですが、多くの同時クライアントからNFSのタイトループでハードリンクを使って実験を行い、成功を数えたことを思い出します.2つのクライアントが同時にハードリンク操作を発行した場合、競合状態がまだあるように見えました.同じ目的地で、両方とも成功したようです。 (この動作は、特定の NFS サーバー実装である YMMV に関連している可能性があります。) いずれにせよ、それはおそらく同じ種類の競合状態であり、同じファイルに対して 2 つの別々の inode を取得することになる可能性があります。これらの競合状態をトリガーするライター間の同時実行。ライターが一貫しており (両方とも A から B にコピー)、リーダーがコンテンツを消費するだけであれば、それで十分かもしれません。
最後に、ロックについて言及しました。残念なことに、少なくとも NFSv3 ではロックが大幅に不足しています (NFSv4 についてはわかりませんが、それも良くないと思います)。ロックを検討している場合は、分散ロック用のさまざまなプロトコルを調べてください。実際のファイル コピーを作成する必要がありますが、これは混乱を招き、複雑で、デッドロックなどの問題が発生しやすいため、回避することをお勧めします。
NFS の原子性に関する背景については、ロックを回避し、NFS でも確実に動作するように作成された Maildir メールボックス形式を参照してください。これは、一意のファイル名をどこにでも保持することで実現します (そのため、最後に最後の B を取得することさえありません)。
おそらく、あなたの特定のケースにはもう少し興味深いかもしれませんが、Maildir++ 形式は Maildir を拡張してメールボックス クォータのサポートを追加し、メールボックス内の固定名のファイルをアトミックに更新することによってこれを行います (そのため、B に近い可能性があります)。これは NFS ではあまり安全ではありませんが、これと同様の手順を使用する再計算アプローチがあり、アトミックな置換として有効です。
願わくば、これらすべての指針がお役に立てば幸いです!