ファイルをトランザクションでコピーする方法は?


9

ファイルをAからBにコピーしたいのですが、ファイルシステムが異なる場合があります。

追加の要件がいくつかあります。

  1. コピーは全部かゼロかであり、クラッシュ時にファイルBの一部または破損は残されません。
  2. 既存のファイルBを上書きしないでください。
  3. 同じコマンドの同時実行と競合しないでください。成功するのは1つだけです。

私はこれが近づくと思います:

cp A B.part && \
ln B B.part && \
rm B.part

しかし3.は、B.partが存在する場合でも(-nフラグを使用しても)失敗しないcpに違反しています。その後、他のプロセスがcpに「勝ち取り」、リンクされたファイルが不完全な場合、1。が失敗する可能性があります。B.partも無関係のファイルである可能性がありますが、その場合は他の隠しファイル名を試さなくても失敗します。

私はbash noclobberが役立つと思いますが、これは完全に機能しますか?bashバージョンの要件なしで取得する方法はありますか?

#!/usr/bin/env bash
set -o noclobber
cat A > B.part && \
ln B.part B && \
rm B.part

フォローアップ、とにかくこれでいくつかのファイルシステムが失敗することを知っています(NFS)。そのようなファイルシステムを検出する方法はありますか?

他のいくつかの関連するがまったく同じではない質問:

ファイルシステム全体のアトミック移動を概算していますか?

私のfsはmvアトミックですか?

eMMCでファイルとディレクトリをtempfsからext4パーティションにアトミックに移動する方法はありますか

https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html


2
同じコマンドの同時実行(つまり、ツール内でのロックで十分か)だけを懸念しているのか、それともファイルに対する他の外部干渉も懸念しているのか?
Michael Homer

3
「トランザクション」の方がいいかもしれません
muru

1
ツール内の@MichaelHomerで十分です。外では非常に困難になると思います。しかし、ファイルロックとその可能ならば...
エヴァン・ベン

1
@marcelm mvは既存のファイルを上書きしますB. mv -nは失敗したことを通知しません。ln(1)rename(2))は、Bがすでに存在する場合は失敗します。
エヴァン・ベン

1
@EvanBenn良い点!私はあなたの要求をもっとよく読んでいるべきだった。(私は既存のターゲットのアトミック更新が必要になる傾向があり、それを念頭に置いて返信していました)
marcelm

回答:


11

rsyncこの仕事をします。一時ファイルはO_EXCLデフォルトで作成され(を使用する場合のみ無効--inplacerenamed、ターゲットファイルの上に作成されます。--ignore-existingBが存在する場合、それを上書きしないように使用します。

実際には、ext4、zfs、さらにはNFSマウントでこの問題が発生することはありませんでした。


rsyncはおそらくこれをうまく実行しますが、非常に複雑なmanページは私を怖がらせます。オプションは他のオプションを意味し、相互に互換性がないなど
Evan Benn

私の知る限り、Rsyncは要件#3には役立ちません。それでも、それは素晴らしいツールであり、少しマンページを読むことをためらわないでください。github.com/tldr-pages/tldr/blob/master/pages/common/rsync.mdまたはcheat.sh/rsyncを試すこともできます。。(tldrとチートはあなたが述べた問題、すなわちのヘルプを目指す二つの異なるプロジェクトは、「; DRのmanページはTLがある」、ある共通のコマンドの多くがサポートされている、とあなたは、最も一般的な使用法が示され表示されます。
シタラーム

@EvanBenn rsyncは素晴らしいツールであり、学ぶ価値があります!それは非常に用途が広いので、マニュアルページは複雑です。脅迫しないでください:)
Josh

@ sitaram、#3はpidファイルで解決できます。ここ答えのような小さなスクリプト。
Robert Riedl

2
これが最良の答えです。Rsyncはアトミックファイル転送の業界標準であり、さまざまな構成ですべての要件を満たすことができます。
wKavey

4

おかげで、この簡潔な答えを受け入れたくなりました。NFSのような危険なファイルシステムに関するコメントはありますか?
エヴァンベン

@EvanBenn、私はNFSが何らかの方法でここであなたを台無しにするつもりかどうかわからないことを付け加えるつもりでしたが、忘れました。
ilkkachu

4

あなたはNFSについて尋ねました。この種類のコードは、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が存在しません。

コピー後とmvor ln操作の前にもう一度チェックすることで、レースのサイズを縮小できますが、まだ小さな競合状態があります。ただし、競合状態に関係なく、両方のプロセスがAから(または有効なファイルからのコピーを起点として)作成しようとしていると仮定して、Bの内容は一貫している必要があります。

の最初の状況でmvは、競合が存在する場合、rename(2)が既存のファイルをアトミックに置き換えるため、最後のプロセスが勝者となります。

newpathがすでに存在する場合は、アトミックに置き換えられるため、newpathにアクセスしようとする別のプロセスがそれを見つけられなくなることはありません。[...]

場合newpathがが存在しますが、操作が何らかの理由で失敗し、rename()インスタンス残すことを保証newpathをする場所に。

したがって、その時点でBを消費しているプロセスでは、このプロセス中に異なるバージョンのiノード(異なるiノード)が表示される可能性があります。ライターがすべて同じコンテンツをコピーしようとしていて、リーダーが単にファイルのコンテンツを消費しているだけの場合、問題はないかもしれません。同じコンテンツのファイルに対して異なるiノードを取得しても、同じように満足します。

ハードリンクを使用した2番目のアプローチより良いように見えますが、多くの同時クライアントからNFSのタイトループでハードリンクを使って実験を行い、成功を数えたことを覚えています。同じ宛先で同じ操作を同時に実行すると、どちらも成功したようです。(この動作は特定のNFSサーバー実装であるYMMVに関連していた可能性があります。)いずれにせよ、それはおそらく同じ種類の競合状態であり、重いファイルがある場合、同じファイルに対して2つの別々のiノードを取得することになります。これらの競合状態をトリガーするライター間の同時実行性。ライターが一貫していて(どちらもAからBにコピーする)、リーダーがコンテンツのみを消費している場合は、それで十分かもしれません。

最後に、ロックについて説明しました。残念ながら、少なくともNFSv3ではロックが大幅に不足しています(NFSv4については不明ですが、それも良くないでしょう)。ロックを検討している場合は、おそらく、実際のファイルのコピーですが、それは混乱を招き、複雑で、デッドロックなどの問題が発生しやすいので、回避することをお勧めします。


NFSのアトミック性の主題の背景については、ロックを回避し、NFSでも確実に動作するように作成されたMaildirメールボックス形式をお読みください。それはどこでもユニークなファイル名を維持することによってそうします(それで、最後に最後のBを得ることすらありません。)

Maildir ++形式は、Maildir ++形式を拡張してメールボックスクォータのサポートを追加し、メールボックス内の固定名でファイルをアトミックに更新することで(Bに近い可能性があるため)、Maildir ++が試行すると思います。追加することは、NFSでは本当に安全ではありませんが、これと同様の手順を使用する再計算アプローチがあり、アトミックな置き換えとして有効です。

うまくいけば、これらすべてのポインタが役立つでしょう!


2

このためのプログラムを書くことができます。

を使用open(O_CREAT|O_RDWD)してターゲットファイルを開き、すべてのバイトとメタデータを読み取って、ターゲットファイルが完全かどうかを確認します。そうでない場合は、2つの可能性があります。

  1. 不完全な書き込み

  2. 他のプロセスが同じプログラムを実行しています。

ターゲットファイルの開いているファイル説明ロックを取得してください。

失敗は、並行プロセスが存在することを意味し、現在のプロセスが存在する必要があります。

成功とは、最後の書き込みがクラッシュしたことを意味します。最初からやり直すか、ファイルに書き込んで修正してください。

またfsync()、ターゲットファイルに書き込んだ後、ファイルを閉じてロックを解除する前に実行することをお勧めします。そうしないと、他のプロセスがまだディスク上にないデータを読み取る場合があります。

https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html

これは、同時に実行されているプログラムと最後にクラッシュした操作を区別するために重要です。


情報をありがとう、私はこれを自分で実装することに興味があり、試してみるつもりです。一部のcoreutils /類似パッケージの一部としてまだ存在していないことに驚いています!
エヴァン・ベン

このアプローチは、クラッシュ要件で残された部分的または破損したファイルBに対応できません。ファイルを一時的な名前にコピーし、それを所定の場所に移動するという標準的なアプローチを使用するのが本当に最善です。
reinierpost

@reinierpostクラッシュしてもデータが完全にコピーされない場合、部分的にコピーされたデータは何があっても残ります。しかし、私のアプローチはこれを検出して修正します。ファイルの移動はアトミックにすることはできません。物理セクターをまたがってディスクに書き込まれるデータはアトミックではありませんが、ソフトウェア(例:OSファイルシステムドライバー、このアプローチ)はそれを修正したり(rwの場合)、一貫した状態を報告したり(if ro) 、質問のコメントセクションで述べたように。また、問題は移動ではなくコピーについてです。
爆鱼芋条德里克

私はO_TMPFILEも見ました。これはおそらく役立つでしょう。(そしてFSで利用できない場合、エラーが発生するはずです)
Evan Benn

@Evanは、ドキュメントを読んだり、O_TMPFILEがファイルシステムのサポートに依存する理由を考えたりしたことがありますか?
爆鱼芋条德里克

0

cp一緒にを実行すると、正しい結果が得られますmv。これにより、「B」が「A」の新しいコピーに置き換えられるか、「B」が以前と同じままになります。

cp A B.tmp && mv B.tmp B

既存のものを収容するための更新B

cp A B.tmp && if [ ! -e B ]; then mv B.tmp B; else rm B.tmp; fi

これは100%アトミックではありませんが、近づきます。これらの2つが実行されている競合状態があり、両方が同時にifテストに入り、どちらもB存在しないことを確認してから、両方がを実行しmvます。


mv B.tmp Bは、既存のBを上書きします。cpA B.tmpは、既存のB.tmpを上書きします。どちらのエラーも発生します。
エヴァン・ベン

mv B.tmp Bcp A B.tmp最初に実行して成功結果コードを返さない限り、実行されません。それは失敗ですか?また、あなたがやりたいことでcp A B.tmpある既存のB.tmpものを上書きすることに同意します。&&2番目のコマンドは、最初のものは正常に完了した場合にのみ実行されることを保証します。
kaan

質問では、成功とは既存のファイルBを上書きしないことと定義されています。B.tmpの使用は1つのメカニズムですが、既存のファイルを上書きしてはなりません。
エヴァン・ベン

回答を更新しました。最終的に、ファイルが存在する場合と存在しない場合の完全な100%のアトミック性、および複数のスレッドが必要な場合、どこかに単一の排他ロック(特別なファイルを作成するか、データベースを使用するなど)が必要です。コピー/移動プロセス。
kaan

この更新は依然としてB.tmpを上書きし、テストとmvの間で競合状態が発生します。はい、ポイントは、うまく行けば大体うまくいかないかもしれないことを正しく行うことです。他の回答は、ロックとデータベースが不要な理由を示しています。
エヴァン・ベン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.