34

В Linux, что происходит, если 1000 файлов в каталоге перемещаются в другое место и еще 300 файлов добавляются в исходный каталог во время перемещения оригинальных 1000 файлов. Будет ли в конечном итоге 1300 файлов? или в исходной папке останется 300 файлов.

3 ответа3

86

Это зависит от того, какие инструменты вы используете: Давайте проверим несколько случаев:

Если вы запустите что-то вроде оболочки mv /path/to/source/* /path/to/dest/ int в оболочке, вы получите исходные 1000 перемещаемых файлов, а новые 300 не тронуты. Это связано с тем, что оболочка расширит * перед началом операции перемещения, поэтому, когда перемещение выполняется, список уже зафиксирован.

Если вы используете Nautilus (и других друзей с графическим интерфейсом), вы закончите тем же путем: он будет запускать операцию перемещения в зависимости от того, какие файлы были выбраны - это не изменится, когда появятся новые файлы.

Если вы используете свою собственную программу, используя syscalls вдоль линии loop over glob и только одну mv пока glob останется пустым, вы получите все 1300 файлов в новом каталоге. Это потому, что каждый новый glob будет забирать новые файлы, которые появились за это время.

8

Само ядро не может быть "в середине" операции "переместить 1000 файлов". Вы должны быть более конкретны в отношении того, какую операцию вы предлагаете.

Один поток может перемещать только один файл за раз с помощью системных вызовов rename(*oldpath, const char *newpath) или renameat (и только внутри одной файловой системы 1). Или Linux renameat2 который имеет флаги, такие как RENAME_EXCHANGE чтобы атомарно обмениваться двумя путями , или RENAME_NOREPLACE чтобы не заменить место назначения, если оно существует. (например, разрешение реализации mv -i которая позволяет избежать состояния гонки stat и затем rename , что все равно перезапишет файл, созданный после stat . link + unlink также может решить эту проблему, потому что link не работает, если существует новое имя.)

Но каждый из этих системных вызовов переименовывает только одну запись каталога на системный вызов. Использование POSIX renameat с olddirfd и newdirfd (открыт с open(O_DIRECTORY) позволит вам сохранить цикл над файлами в каталоге , даже если сам исходный или конечный каталог был переименован. (Использование относительных путей также может позволить это с обычным rename() .)

В любом случае, как говорят другие ответы, большинство программ, использующих системный вызов переименования, составят список имен файлов перед первым rename . (Обычно используют библиотечную функцию POSIX readdir(3) в качестве оболочки для системных вызовов, специфичных для платформы, таких как Linux getdents).

Но если вы говорите о find -exec ... {} \; чтобы выполнить одну команду для каждого файла или более эффективный -exec {} + с таким количеством файлов, что они не помещаются в одной командной строке, тогда вы, безусловно, можете иметь переименования, которые происходят во время сканирования. например

find . -name '*.txt' -exec mv -t ../txtfiles {} \;   # Intentionally inefficient

Если вы создали несколько новых файлов .txt во время его работы, вы можете увидеть некоторые из них в ../txtfiles . Но внутренне find(1) будет использовать open(O_DIRECTORY) и getdents on . ,

Если одного системного вызова достаточно, чтобы вернуть все записи каталога в . (который find будет зацикливаться по одному за раз, только делая дополнительные системные вызовы, если это необходимо для -type или recurse, или fork+exec для совпадения), тогда список представляет собой снимок записей каталога в один момент времени. Дальнейшие изменения в каталоге не могут повлиять на то, что делает find , потому что у него уже есть копия каталога, в которой он будет зацикливаться. (Возможно, он внутренне использует readdir(3) , который возвращает одну запись за раз, но внутри glibc мы знаем об использовании strace find . Что он выполняет системный вызов getdents64 с размером буфера count=32768 записей.)

Но если каталог огромен и / или ядро не заполняет буфер find , ему придется выполнить 2-й системный вызов getdents после зацикливания того, что он получил в первый раз. Так что он может увидеть новые записи после некоторых переименований.

Но посмотрите обсуждение в комментариях под другими ответами: ядро могло бы сделать снимок для нас, потому что (я думаю) getdents не могут возвращать одно и то же имя файла дважды. Разные файловые системы используют разные механизмы сортировки / индексирования, чтобы сделать доступ к записи в огромном каталоге более эффективным, чем линейный поиск. Поэтому добавление или удаление каталога может повлиять на порядок оставшихся записей. Хм, вероятно, более вероятно, что файловые системы сохраняют стабильный порядок и просто обновляют фактический индекс (например, функцию EXT4 dir_index ), так что позиция FD каталога может быть просто записью каталога, с которой можно продолжить? Я действительно не знаю, как интерфейс библиотеки telldir(3) отображается на lseek , или это чисто пользовательское пространство для зацикливания буфера, полученного из пользовательского пространства. Но для получения всех записей из огромного каталога может потребоваться несколько getdents , поэтому даже если поиск не поддерживается, ядро должно иметь возможность записывать текущую позицию.


Сноска 1:

Чтобы "перемещаться" между файловыми системами, нужно копировать и отсоединять пользовательское пространство. (Например, с open и read+write , mmap+write или sendfile(2) или copy_file_range(2) , последние два полностью избегают пересылки данных файла через пространство пользователя.)

8

Когда вы говорите системе переместить все файлы из каталога, она перечисляет все файлы и начинает их перемещение. Если в каталоге появляются новые файлы, они не добавляются в список файлов для перемещения, поэтому они остаются в исходном местоположении.

Конечно, вы можете запрограммировать способ перемещения файлов, отличный от mv который будет периодически проверять наличие новых файлов в исходном каталоге.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .