2010-07-11 3 views
6

Если я хочу, чтобы переименовать A в B, но только если B не существует, наивная вещь будет проверять, если B существует (с access("B", F_OK) или что-то в этом роде), и если он не идет с rename. К сожалению, это открывает окно, в течение которого какой-то другой процесс может решить создать B, а затем он будет перезаписан - и даже хуже, нет никаких признаков того, что что-то подобное произошло.Как переименовать() без условий гонки?

Другие функции доступа к файловой системе, не страдают от этого - open имеет O_EXCL (так копирования файлов безопасно), а в последнее время Linux получил целое семейство *at системных вызовов, которые защищают от большинства других условий гонки - но это не особенно один (renameat существует, но защищает от совершенно другой проблемы).

У этого есть решение?

+0

Возможно, вам стоит рассмотреть возможность использования явного механизма блокировки вместо того, чтобы полагаться на неявную блокировку в (оболочке) функции переименования. Если то, что создает 'B', является программой под вашим контролем, вы можете использовать примитивы синхронизации между процессами. –

ответ

14

У вас должно быть link (2) к новому имени файла. Если ссылка не удалась, вы сдаетесь, потому что файл уже существует. Если ссылка удалась, ваш файл теперь существует как при старом, так и в новом имени. Тогда вы unlink (2) старое имя. Никакого возможного состояния гонки.

+0

Какие файлы находятся под разными точками подключения? – 2010-07-11 17:47:11

+2

@Moron: rename() не работает в разных файловых системах. – Dummy00001

+0

@ Dummy00001: Я вижу. Я пропустил скобки в названии. +1 then :-) – 2010-07-11 21:11:39

1

На странице переименования людей:

Если NewPath уже существует, то он будет атомарно заменен (с учетом несколько условий, см ОШИБКИ ниже), так что нет точки, в которой еще процесс, пытающийся получить доступ к newpath , не обнаружит его.

Таким образом, невозможно избежать переименования, когда файл B уже существует. Я думаю, что, возможно, у вас просто нет выбора, кроме как проверить наличие (используйте для этого stat() не access()), прежде чем пытаться переименовать, если вы не хотите, чтобы переименование произошло, если файл уже существует. Игнорирование состояния гонки.

В противном случае решение, представленное ниже со ссылкой(), соответствует вашим требованиям.

+0

Если файл B не существует, по-прежнему существует условие гонки, если вы проверяете существование - оба процесса могут обнаружить, что их нет, а затем оба делают переименование с неопределенными результатами в отношении того, какой процесс переименован последним. Единственное решение для rename() - быть атомарным и терпеть неудачу, если новое имя файла уже существует, что оно и делает в Windows, и то, что я думал (неправильно, похоже), указано в C-стандарте. – 2010-07-11 08:58:33

6

Вы могли бы 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() (см дискуссию об изменении файла ПАРОЛЬ):

3

К сожалению для добавления что-то в старую нить. И за такой длинный пост.

Я знаю только один способ сделать полное состояние гонки rename() в отсутствие блокировки, которая должна практически работать на любой файловой системе, даже на NFS с перезагрузкой сервера intermittend и перекосами клиентского времени на месте.

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

Это не состояние гонки бесплатно в том смысле, что если что-то серьезно нарушается, все остается в чистом состоянии. Он также имеет короткий период времени, когда ни источник, ни место назначения не присутствуют в их местоположении, однако источник все еще находится под другим именем. И это не ожесточается от случаев, когда злоумышленник пытается спровоцировать вред (rename() является виновником, иди рисунок).

S является источником, D является местом, Р (х) dirname(x), С (х, у) x/y путь конкатенации

  1. проверка, что адресат не существует. Просто чтобы убедиться, что мы не делаем следующие шаги напрасно.
  2. создать вероятно, уникальное имя T: = C (P (D), случайным образом)
  3. MkDir (Т), если это не удается петлю на предыдущем шаге
  4. открыт (С (Т, "замок"), O_EXCL), если это не удается RmDir (T), игнорируя ошибки и петли к предыдущему шагу
  5. переименования (S, C (T, "TMP"))
  6. линии связи (с (Т, "TMP"), D)
  7. Unlink (С (Т, "TMP"))
  8. Unlink (С (Т, "замок"))
  9. RmDir (Т)

Алгоритм safe_rename(S,D) объяснил:

Проблема заключается в том, что мы хотим, чтобы убедиться, что нет никаких условий расы, ни от источника, ни от места назначения. Предполагается, что (почти) все может происходить между каждым шагом, но все остальные процессы следуют точному алгоритму при выполнении бесплатных условий перерыва. Это включает в себя, что временные каталоги T никогда не затрагиваются, кроме как после того, как вы убедитесь (это ручной процесс), что процесс с использованием каталога умер и не может быть воскрешен (например, продолжить спящий режим VM после восстановления).

Чтобы правильно сделать rename(), нам нужно место, чтобы спрятаться. Таким образом, мы создаем каталог таким образом, чтобы его никто не использовал (кто следит за тем же алгоритмом).

Однако mkdir() не гарантированно является атомарным на NFS. Следовательно, мы должны убедиться, что у нас есть определенная гарантия, что мы одни в каталоге. Это O_EXCL в файле lockfile. Это - строго говоря - не блокировка, это семафор.

За исключением таких редких случаев, mkdir() обычно является атомарным. Также мы можем создать использование криптографически безопасного случайного имени для каталога, добавить GUID, имя хоста и PID, чтобы убедиться, что маловероятно, что кто-то другой выбирает одно и то же имя случайно. Однако, чтобы доказать правильность алгоритма, нам нужен этот файл с именем lock.

Теперь, когда у нас есть, в основном, пустая директория, мы можем безопасно rename() источник там. Это гарантирует, что никто другой не изменит источник до тех пор, пока не будет unlink(). (Ну, содержимое может измениться, это не проблема.)

Теперь можно использовать трюк link(), чтобы мы не перезаписывали пункт назначения.

Впоследствии unlink() может быть выполнено условие гонки бесплатно на оставшемся источнике. Остальное - очистка.

Существует осталось только одна проблема:

В случае link() терпит неудачу, мы переместили источник уже. Для правильной очистки мы должны вернуть его обратно. Это можно сделать, позвонив по телефону safe_rename(C(T,"tmp"),S). Если это не удается, все, что мы можем сделать, это попытаться очистить как можно больше (unlink(C(T,"lock")), rmdir(T)) и оставить обломки для ручной очистки администратором.

Конечные ноты:

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

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

Однако я согласен с тем, что этот алгоритм является излишним и что-то вроде O_EXCL на rename() отсутствует.

Смежные вопросы