2009-11-28 6 views
15

Представьте, что у вас есть библиотека для работы с каким-либо файлом XML или конфигурационным файлом. Библиотека читает весь файл в памяти и предоставляет методы для редактирования содержимого. Когда вы закончите манипулирование контентом, вы можете позвонить write, чтобы сохранить содержимое обратно в файл. Вопрос в том, как сделать это безопасным способом.Как безопасно писать в файл?

Перезапись существующего файла (начиная с записи в исходный файл), очевидно, небезопасна. Если метод write завершился с ошибкой до его завершения, вы получите половину записанного файла, и вы потеряли данные.

Лучшим вариантом было бы записать в файл временного где-то, и когда метод write закончил, вы скопировать временный файл в исходный файл.

Теперь, если копия как-то не удалась, вы все равно правильно сохранили данные во временном файле. И если копия завершается успешно, вы можете удалить временный файл.

В системах POSIX, я думаю, вы можете использовать системный вызов rename, который является атомной операцией. Но как бы вы сделали это лучше всего в системе Windows? В частности, как вы справляетесь с этим, используя Python?

Кроме того, существует ли другая схема безопасного хранения файлов?

+0

Зачем копировать? Почему бы не переименовать? –

ответ

14

Если вы видите документацию Python, в нем четко указано, что os.rename() является атомной операцией. Поэтому в вашем случае запись данных во временный файл, а затем переименование его в исходный файл будет совершенно безопасным.

Другой способ мог бы работать так:

  • пусть исходный файл будет abc.xml
  • создавать abc.xml.tmp и записывать новые данные в нем
  • переименования abc.xml в abc.xml .bak
  • переименования abc.xml.tmp в abc.xml
  • после того, как новый abc.xml правильно поставить на место, удалить abc.xml.bak

Как вы можете видеть, у вас есть abc.xml.bak with you, который вы можете использовать для восстановления, если есть какие-либо проблемы, связанные с файлом tmp и его копированием.

+0

Это похоже на ответ С.Лотта с добавлением для удаления файла резервной копии. Кажется, что это лучший способ сделать это. Спасибо. –

+0

Я действительно видел эту реализацию так, как ZODB (база данных Zope Object Database) делает упаковку своего файла базы данных (Data.fs), т.е. удаление неиспользуемого пространства более старых транзакций из файла базы данных. Код является регулярным кодом python, упаковка выполняется во временном файле, а затем выполняются шаги, аналогичные приведенным выше. ZODB существует уже много лет и хорошо работает как на платформах Windows, так и на POSIX, поэтому я считаю, что этот подход должен работать. –

+3

Python не может обеспечить гарантию, что переименование будет атомарным. Насколько я знаю, это просто вызов системного вызова ОС. Процедура, которую вы даете, работает хорошо. –

4

В Win API я нашел довольно приятную функцию ReplaceFile, которая делает то, что предлагает название, даже с дополнительным резервным копированием. Всегда есть способ с DeleteFile, MoveFile комбо.

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

+3

Было бы даже лучше, если бы вы проиллюстрировали это с помощью правильного кода Python, который вызывает API-интерфейс библиотеки MS. – RedGlyph

+1

Я не знал, что ReplaceFile существует. Читая документы, он, кажется, делает намного больше, чем просто переименовывает. Он поддерживает многие атрибуты замененного файла, поэтому он кажется специально разработанным для этой цели. –

3

Простейшее решение. Используйте tempfile для создания временного файла, и если запись удастся, просто переименуйте файл в исходный файл конфигурации.

Для блокировки файла см. portalocker.

+1

Если tempfile создан в другой файловой системе, чем целевой, то окончательное переименование либо не будет работать, либо не будет атомарным. – tzot

+0

Но если переименование не является атомарным, я рискую потерять данные, верно? –

4

Стандартное решение это.

  1. Написать новый файл с аналогичным именем. X.ext # например.

  2. Когда этот файл был закрыт (и, возможно, даже прочитан и проверен), то вы два двух переименования.

    • X.ext (оригинал) в X.ext ~

    • X.ext # (новый) для X.ext

  3. (Только для сумасшедших параноиды) вызывают функцию синхронизации ОС для принудительной записи грязных буферов.

Ни в коем случае не потеряно и не может быть повреждено. Единственный сбой может произойти во время переименований. Но вы ничего не потеряли или ничего не испортили. Оригинал восстанавливается вплоть до окончательного переименования.

+0

Но если вы не переименовываете дважды (создаете резервную копию, как и вы), вы рискуете потерять данные, если переименование не является атомарным, не так ли? –

+1

Переименование * является * атомарным во многих ОС. Тем не менее, резервное копирование важнее, чем ручное нажатие на атомарность операции. Помните: вероятность сбоя (за исключением Windows) очень мала. Вероятность аварии в середине переименования (это всего лишь несколько инструкций плюс синхронизация очень мала. –

11

Если вы хотите быть POSIXly правильно и сохранить вы должны:

  1. Запись на временный файл
  2. Flush и fsync файл (или fdatasync)
  3. Rename поверх оригинального файла

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

Отметьте, что rename is не атомная операция в POSIX - по крайней мере, не по отношению к данным файла, как вы ожидаете. Однако большинство операционных систем и файловых систем будут работать таким образом. Но, похоже, вы пропустили очень большую дискуссию по Linux о проблемах с Ext4 и файловой системой об атомарности. Я не знаю, где именно ссылку, но вот начало: ext4 and data loss.

Обратите внимание, что во многих системах переименование будет таким же безопасным на практике, как вы ожидаете. Однако в обоих случаях невозможно получить как производительность, так и надежность во всех возможных консолях Linux!

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

Проблема в том, что большинство, если не все файловые системы, разделяют метаданные и данные. Переименование - это только метаданные. Это может показаться вам ужасным, но файловые системы оценивают метаданные по данным (например, для ведения журнала в HFS + или Ext3,4)! Причина в том, что метаданные легче, и если метаданные повреждены, вся файловая система повреждена - файловая система должна, конечно, сохранить ее самостоятельно, а затем сохранить данные пользователя в этом порядке.

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

Итак, POSIX не дает такой гарантии. Прочтите связанную статью Ext4 для получения дополнительной информации!

+0

Возможно, я неправильно понял. Но если вы переименуете систему POSIX, разве вы не гарантируете, что назначение немодифицировано, если переименование не выполняется? »Если функция rename() не работает по какой-либо причине, отличной от [EIO], любой файл с именем new не будет затронут.« Я предполагаю, что он все равно может оставить поврежденные данные. –

+0

проблема успешное переименование, и что просто переименование не гарантирует атомарности всей операции. – u0b34a0f6ae

+1

Что вы имеете в виду, что rename() не является контрольной точкой. Это, безусловно, атомарно, см .: http://www.opengroup.org/onlinepubs /009695399/functions/rename.html – geocar

1

По предложению RedGlyph, я добавил реализацию ReplaceFile, которая использует ctypes для доступа к API Windows. Я сначала добавил это в jaraco.windows.api.filesystem.

ReplaceFile = windll.kernel32.ReplaceFileW 
ReplaceFile.restype = BOOL 
ReplaceFile.argtypes = [ 
    LPWSTR, 
    LPWSTR, 
    LPWSTR, 
    DWORD, 
    LPVOID, 
    LPVOID, 
    ] 

REPLACEFILE_WRITE_THROUGH = 0x1 
REPLACEFILE_IGNORE_MERGE_ERRORS = 0x2 
REPLACEFILE_IGNORE_ACL_ERRORS = 0x4 

Затем я проверил поведение, используя этот скрипт.

from jaraco.windows.api.filesystem import ReplaceFile 
import os 

open('orig-file', 'w').write('some content') 
open('replacing-file', 'w').write('new content') 
ReplaceFile('orig-file', 'replacing-file', 'orig-backup', 0, 0, 0) 
assert open('orig-file').read() == 'new content' 
assert open('orig-backup').read() == 'some content' 
assert not os.path.exists('replacing-file') 

Хотя это работает только в Windows, это, кажется, есть много хороших функций, которые другие замены подпрограммы будут лишены. Подробности см. На странице API docs.

0

Вы можете использовать модуль FileInput, чтобы обрабатывать резервное копирование и на месте писать для вас:

import fileinput 
for line in fileinput.input(filename,inplace=True, backup='.bak'): 
    # inplace=True causes the original file to be moved to a backup 
    # standard output is redirected to the original file. 
    # backup='.bak' specifies the extension for the backup file. 

    # manipulate line 
    newline=process(line) 
    print(newline) 

Если вам нужно прочитать всего содержимого, прежде чем вы можете написать новой строки х, то может сделать это сначала, а затем распечатать все новое содержимое с помощью

newcontents=process(contents) 
for line in fileinput.input(filename,inplace=True, backup='.bak'): 
    print(newcontents) 
    break 

Если скрипт заканчивается внезапно, у вас все еще будет резервная копия.

3

В настоящее время существует кодифицированный чистый Python, и я смею сказать, что это решение Pythonic в boltons utility library: boltons.fileutils.atomic_save.

Просто pip install boltons, то:

from boltons.fileutils import atomic_save 

with atomic_save('/path/to/file.txt') as f: 
    f.write('this will only overwrite if it succeeds!\n') 

Есть много практических вариантов, all well-documented. Полное раскрытие, я являюсь автором болтонов, но эта конкретная часть была построена с большой помощью сообщества. Не стесняйтесь drop a note, если что-то неясно!

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