Если я хочу переименовать A
кому: B
, но только если B
не существует, наивная вещь проверила бы если B
существует (с access("B", F_OK)
или что-то как этот), и если это не делает продолжения rename
. К сожалению, это открывает окно, во время которого некоторый другой процесс мог бы решить создать B
, и затем это перезаписывается - и еще хуже нет никакого признака, что что-то как этот когда-либо происходило.
Другие функции доступа к файловой системе не страдают от этого - open
имеет O_EXCL
(настолько копирующие файлы безопасны), и недавно Linux получил все семейство *at
syscalls, которые защищают от большинства других условий состязания - но не этот конкретный один (renameat
существует, но защищает от совершенно другой проблемы).
Это имеет решение?
Вы можете 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()
(см. обсуждение изменения файла passwd):
У вас должна получиться ссылка(2) на новое имя файла. Если ссылка не работает, то вы сдаетесь, потому что файл уже существует. Если ссылка удалась, ваш файл теперь существует и под старым, и под новым именем. Затем вы отсоединяете(2) старое имя. Нет возможного состояния гонки.
Из man-страницы rename:
Если newpath уже существует, он будет атомарно заменен (при соблюдении нескольких условий; см. ОШИБКИ ниже), так что не существует точки, в которой другой процесс, пытающийся получить доступ к newpath обнаружит его отсутствие.
Поэтому невозможно избежать переименования, когда файл B
уже существует. Я думаю, что, возможно, у вас просто нет выбора, кроме как проверить существование (используйте stat()
, а не access()
для этого) перед попыткой переименования, если вы не хотите, чтобы переименование произошло, если файл уже существует. Игнорирование состояния гонки.
В противном случае представленное ниже решение с использованием link(), похоже, соответствует вашим требованиям.