Linuxでは、ディレクトリ内の1000個のファイルが別の場所に移動され、元の1000個のファイルが移動されている間に別の300個のファイルがソースディレクトリに追加された場合にどうなりますか。宛先は最終的に1300ファイルになりますか?または、ソースフォルダーに300個のファイルが残っています。
これは、使用するツールによって異なります。いくつかのケースを確認してみましょう。
シェルのmv /path/to/source/* /path/to/dest/
に沿って何かを実行すると、元の1000個のファイルが移動され、新しい300個は変更されません。これは、シェルが移動操作を開始する前に*
を展開するため、移動が進行中の場合、リストはすでに固定されているためです。
Nautilus(および他のGUIフレンド)を使用する場合、同じ方法で終了します。選択したファイルに基づいて移動操作が実行されます。これは、新しいファイルが表示されても変更されません。
glob
のループの行に沿ってsyscallsを使用して独自のプログラムを使用すると、mv
が空のままになるまでoneglob
だけになると、最終的には新しいディレクトリ内のすべての1300ファイル。これは、すべての新しいglob
が、その間に表示された新しいファイルを取得するためです。
カーネル自体を「1000ファイルの移動」操作の「途中」にすることはできません。提案する操作について、より具体的にする必要があります。
rename(*oldpath, const char *newpath)
またはrenameat
システムコール を使用すると、1つのスレッドは一度に1つのファイルしか移動できません(同じファイルシステム内のみ)1)。または、2つのパス名をアトミックに交換するrenameat2
などのフラグを持つLinux RENAME_EXCHANGE
、またはRENAME_NOREPLACE
をnotに置き換えて、宛先が存在する場合はそれを置き換えます。 (たとえば、stat
とrename
の競合状態を回避するmv -i
実装を許可すると、stat
の後に作成されたファイルが引き続き上書きされます。 link
+ unlink
でも解決できます。新しい名前が存在する場合、link
は失敗するためです。)
しかし、これらの各システムコールは、システムコールごとに1つのディレクトリエントリの名前のみを変更します。 POSIX renameat
をolddirfd
およびnewdirfd
とともに使用すると(open(O_DIRECTORY)
で開いて)、ソースまたは宛先であってもディレクトリ内のファイルをループし続けることができます。ディレクトリitselfは名前が変更されました。 (相対パスを使用すると、通常のrename()
でもそれが可能になります。)
とにかく、他の答えが言うように、renameシステムコールを使用するほとんどのプログラムは、最初のrename
を実行する前にファイル名のリストを理解します。 (通常、 readdir(3)
POSIXライブラリ関数をLinuxのようなプラットフォーム固有のシステムコールのラッパーとして使用します getdents
)。
しかし、ファイルごとに1つのコマンドを実行するfind -exec ... {} \;
や、1つのコマンドラインに収まらないほど多くのファイルを含むより効率的な-exec {} +
について話している場合は、スキャン中に名前が変更される可能性があります。例えば.
find . -name '*.txt' -exec mv -t ../txtfiles {} \; # Intentionally inefficient
これの実行中にいくつかの新しい.txt
ファイルを作成した場合、mightいくつかの../txtfiles
でそれらを参照してください。ただし、内部的にはfind(1)
は.
でopen(O_DIRECTORY)
およびgetdents
を使用します。
1つのシステムコールでall.
のディレクトリエントリを返すのに十分な場合(findは一度に1つずつループし、 -type
または再帰、または一致時のfork + execが必要な場合にのみ、さらにシステムコールを実行する場合、リストは、ある時点でのディレクトリエントリのスナップショットです。ディレクトリをさらに変更しても、find
の動作に影響を与えることはできません。ループする対象をリストしたディレクトリのコピーがすでにあるためです。 (おそらく、内部でreaddir(3)
を使用して、一度に1つのエントリを返しますが、glibc内では、strace find .
を使用して、getdents64
エントリのバッファサイズでcount=32768
システムコールを行うことがわかっています。
しかし、ディレクトリが巨大であるか、カーネルがfind
のバッファを満たさない場合、最初にループした後に2番目のgetdentsシステムコールを実行する必要があります。したがって、名前を変更した後に新しいエントリが表示される可能性があります。
しかし、他の回答の下のコメントでの議論を参照してください:(私が思うに)getdentsが同じファイル名を2回返すことが許可されていないため、カーネルが私たちのためにスナップショットを撮ったかもしれません。巨大なディレクトリのエントリへのアクセスを線形検索よりも効率的にするために、ファイルシステムごとに異なるソート/インデックス作成メカニズムを使用しています。したがって、ディレクトリを追加または削除すると、残りのエントリの順序に他の影響が及ぶ可能性があります。うーん、おそらくファイルシステムが安定した順序を保ち、実際のインデックス(EXT4 dir_index
機能など)を更新するだけの可能性が高いので、ディレクトリFDの位置は、再開するディレクトリエントリにすぎませんか? telldir(3)
ライブラリインターフェイスがlseek
にどのようにマップされるか、またはユーザースペースによって取得されたバッファーをループするための純粋にユーザースペースのものであるかどうか、私は本当に知りません。ただし、巨大なディレクトリからすべてのエントリを取得するには、複数のgetdents
が必要になる場合があるため、シークがサポートされていない場合でも、カーネルは現在の位置を記録できる必要があります。
脚注1:
ファイルシステム間で「移動」するには、コピーしてリンクを解除するのはユーザースペース次第です。 (たとえば、open
とread+write
、mmap+write
または sendfile(2)
または copy_file_range(2)
のいずれかを使用すると、後者の2つは完全に回避されますユーザースペースを介してファイルデータをバウンスします。)
ディレクトリからすべてのファイルを移動するようにシステムに指示すると、すべてのファイルが一覧表示され、それらの移動が開始されます。新しいファイルがディレクトリに表示される場合、それらは移動するファイルのリストに追加されないため、元の場所に残ります。
もちろん、mv
とは異なるファイルを移動する方法をプログラムして、ソースディレクトリの新しいファイルを定期的にチェックすることもできます。