Безопасно создать файл, если и только если он не существует в 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')
3 75

3 ответа:

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

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

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

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

убедитесь, что этот вызов создает файла: если этот флаг указан в сочетании с O_CREAT, и путь уже существует, то open() не удастся. Поведение O_EXCL неопределено, если 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 etc.

пример:

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!")

для справки, 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.х) обращайтесь к принято отвечать.

этот код легко создаст файл, если он не существует.

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