Как с помощью оператора try избежать состояния гонки?


При определении того, существует ли файл, как с помощью оператора try избежать "состояния гонки"?

Я спрашиваю, потому что высокооплачиваемый ответ (обновление: он был удален), кажется, подразумевает, что использование os.path.exists() создает возможность, которая иначе не существовала бы.

Приведенный пример:

try:
   with open(filename): pass
except IOError:
   print 'Oh dear.'

Но я не понимаю, как это позволяет избежать состояния гонки по сравнению с:

if not os.path.exists(filename):
    print 'Oh dear.'

Как вызов os.path.exists(filename) позволяет атакующему сделать что-то с файл, который они уже не могли сделать?

3 24

3 ответа:

Состояние гонки, конечно, находится между вашей программой и некоторым другим кодом, который работает с файлом (состояние гонки всегда требует по крайней мере двух параллельных процессов или потоков, см. this для получения подробной информации). Это означает, что использование open() вместо exists() может реально помочь только в двух ситуациях:

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

exists() просто выполняет одну проверку. Если файл существует, он может быть удален через микросекунду после exists() возврата True. Если файл отсутствует, он может быть создан немедленно.

Однако, open() не только проверяет наличие файла, но и открывает файл (и делает эти два действия атомарно, поэтому ничего не может произойти между проверкой и открытием). Обычно файлы не могут быть удалены, пока они открыты кем-то. Это означает, что внутри with Вы можете быть полностью уверены: файл действительно существует сейчас, так как он открыт. Хотя это верно только внутри with, и файл все еще может быть удален сразу после выхода из блока with, размещение кода, который должен существовать внутри файла with, гарантирует, что этот код не подведет.

Вот пример использования:

try:
    with open('filename') as f:
        do_stuff_that_depends_on_the_existence_of_the_file(f)
except IOError as e:
    print 'Trouble opening file'

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

try - это просто способ обнаружить ошибку или успех акта открытия файла, так как API ввода-вывода файлов в Python обычно не имеют кодов возврата (используются исключения вместо). Поэтому, чтобы действительно ответить на ваш вопрос, это не try, который избегает условия гонки, это open. Это в основном то же самое в C (на котором основан Python), но без исключений. Прочитайте это для получения дополнительной информации.

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

Вызов os.path.exists просто дает моментальный снимок в тот момент времени, когда файл может существовать, а может и не существовать, и вы не знаете о существовании файла, как только os.path.exists возвращается. Злонамеренный код или неожиданная логика могут удалить или изменить файл, Когда вы этого не ожидаете. Это сродни повороту головы, чтобы убедиться, что дорога свободна, прежде чем въехать на нее. Как только вы поворачиваете голову назад, у вас нет ничего, кроме догадки о том, что происходит там, куда вы больше не смотрите. Удержание файла открытым гарантирует длительное согласованное состояние, что-то невозможное (хорошо или плохо). болен) при вождении. :)

Ваше предложение проверить, что файл не существует, а не использовать try/open все еще недостаточно из-за природы моментального снимка os.path.exists. К сожалению, я не знаю способа предотвратить создание файлов в каталоге во всех случаях, поэтому я думаю, что лучше всего проверить наличие файла, а не его отсутствие.

Я думаю, что вы спрашиваете о конкретном состоянии расы, где:

  1. файл открыт
  2. контекст переключается и файл удаляется
  3. контекст переключается обратно, и файловые операции предпринимаются над "открытым" файлом

Способ, которым вы "защищены" в этом случае, заключается в том, чтобы поместить весь код обработки файлов в блок try, если в какой-то момент файл становится недоступным/поврежденным, ваши файловые операции смогут "изящно" завершиться с помощью блока catch.

Примечание, конечно, в современных ОС это не может произойти в любом случае, когда файл "удален", удаление не будет происходить до тех пор, пока все открытые дескрипторы на файле не будут разрешены (освобождены)