Безопасное создание файла тогда и только тогда, когда он не существует с помощью python

Я хочу записать файл в зависимости от того, существует ли этот файл или нет, только запись, если он еще не существует (на практике я хочу продолжать поиск файлов до тех пор, пока не найду ту, которая не существует).

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

Есть ли способ решить эту проблему?

import os
import errno

file_to_be_attacked = 'important_file'

with open(file_to_be_attacked, 'w') as f:
    f.write('Some important content!\n')

test_file = 'testfile'

try:
    with open(test_file) as f: pass
except IOError, e:

    # symlink created here
    os.symlink(file_to_be_attacked, test_file)

    if e.errno != errno.ENOENT:
        raise
    else:
        with open(test_file, 'w') as f:
            f.write('Hello, kthxbye!\n')

Ответ 1

Изменить: см. также ответ Дэйва Джонса: из Python 3.3 вы можете использовать флаг x для open() для выполните эту функцию.

Оригинальный ответ ниже

Да, но не используя стандартный вызов open() Python. Вам нужно будет использовать os.open(), что позволяет указывать флаги в базовом C-коде.

В частности, вы хотите использовать O_CREAT | O_EXCL. На странице man для open(2) в разделе O_EXCL в моей системе Unix:

Убедитесь, что этот вызов создает файл: если этот флаг указан вместе с O_CREAT, а путь уже существует, то open() завершится сбой. Поведение O_EXCL undefined, если O_CREAT не указано.

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

O_EXCL поддерживается только в NFS при использовании NFSv3 или более поздней версии ядра 2.6 или новее. В средах, где поддержка NFS O_EXCL не предоставляется, программы, которые полагаются на нее для выполнения задач блокировки, будут содержать условие гонки.

Так что это не идеально, но AFAIK это самое близкое, что вы можете избежать, чтобы избежать этого состояния гонки.

Изменить: применяются другие правила использования os.open() вместо open(). В частности, если вы хотите использовать возвращаемый дескриптор файла для чтения или записи, вам понадобится один из флагов O_RDONLY, O_WRONLY или O_RDWR.

Все флаги O_* находятся в модуле Python os, поэтому вам нужно import os и использовать os.O_CREAT и т.д.

Пример:

import os
import errno

flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY

try:
    file_handle = os.open('filename', flags)
except OSError as e:
    if e.errno == errno.EEXIST:  # Failed as the file already exists.
        pass
    else:  # Something unexpected went wrong so reraise the exception.
        raise
else:  # No exception, so the file must have been created successfully.
    with os.fdopen(file_handle, 'w') as file_obj:
        # Using `os.fdopen` converts the handle to an object that acts like a
        # regular Python file object, and the `with` context manager means the
        # file will be automatically closed when we're done with it.
        file_obj.write("Look, ma, I'm writing to a new file!")

Ответ 2

Для справки, Python 3.3 реализует новый 'x' режим open() для покрытия этого прецедента (создайте только, если файл существует). Обратите внимание, что режим 'x' указывается сам по себе. Использование 'wx' приводит к тому, что ValueError, поскольку 'w' является избыточным (единственное, что вы можете сделать, если успешный вызов - это запись в файл в любом случае, он не может существовать, если вызов завершается успешно):

>>> f1 = open('new_binary_file', 'xb')
>>> f2 = open('new_text_file', 'x')

Для Python 3.2 и ниже (включая Python 2.x) см. принятый ответ.

Ответ 3

Этот код будет легко создавать FILE, если он не существует.

import os
if not os.path.exists('file'):
    open('file', 'w').close()