2009-11-28 11 views
15

Imagine que tiene una biblioteca para trabajar con algún tipo de archivo XML o archivo de configuración. La biblioteca lee el archivo completo en la memoria y proporciona métodos para editar el contenido. Cuando termine de manipular el contenido, puede llamar al write para guardar el contenido nuevamente en el archivo. La pregunta es cómo hacer esto de una manera segura.¿Cómo escribir de forma segura en un archivo?

Sobrescribir el archivo existente (empezando a escribir en el archivo original) obviamente no es seguro. Si el método write falla antes de finalizar, terminas con un medio medio escrito y has perdido datos.

Una mejor opción sería la de escribir en un archivo temporal alguna parte, y cuando el método write ha terminado, copiar el archivo temporal en el archivo original.

Ahora, si la copia falla de algún modo, aún tiene datos guardados correctamente en el archivo temporal. Y si la copia tiene éxito, puede eliminar el archivo temporal.

En sistemas POSIX supongo que puede utilizar la llamada al sistema rename, que es una operación atómica. Pero, ¿cómo harías esto mejor en un sistema Windows? En particular, ¿cómo maneja esto mejor usando Python?

Además, ¿hay algún otro plan para escribir de forma segura en los archivos?

+0

¿Por qué copiar? ¿Por qué no renombrar? –

Respuesta

14

Si ve la documentación de Python, se menciona claramente que os.rename() es una operación atómica. Entonces, en su caso, escribir datos en un archivo temporal y luego cambiarle el nombre al archivo original sería bastante seguro.

Otra manera podría funcionar así:

  • dejó archivo original sea abc.xml
  • crear abc.xml.tmp y escribir nuevos datos en él
  • abc.xml de cambio de nombre a abc.xml bak
  • renombrar abc.xml.tmp a abc.xml
  • después de nuevo abc.xml se pone correctamente en su lugar, retire abc.xml.bak

Como puede ver, tiene el archivo abc.xml.bak con usted, que puede usar para restaurar si hay algún problema relacionado con el archivo tmp y copiarlo de nuevo.

+0

Esto es similar a la respuesta de S.Lott con la adición para eliminar el archivo de copia de seguridad. Parece que es la mejor manera de hacerlo. Gracias. –

+0

De hecho, vi esta implementación en la forma en que ZODB (base de datos de objetos Zope) empaqueta su archivo de base de datos (Data.fs), es decir, elimina el espacio no utilizado de transacciones anteriores del archivo de base de datos. El código es código python regular, el embalaje se realiza en un archivo temporal y luego se llevan a cabo los pasos similares a los anteriores. ZODB ha existido durante muchos años y funciona bien tanto en plataformas Windows como POSIX, así que creo que este enfoque debería funcionar. –

+3

Python no puede hacer cumplir la garantía de que el cambio de nombre sea atómico. Por lo que sé, solo está llamando a la llamada al sistema del sistema operativo. El procedimiento que proporcionas funciona bien, sin embargo. –

4

En Win API encontré bastante buena función ReplaceFile que hace lo que sugiere el nombre incluso con una copia de seguridad opcional. Siempre hay forma de combinar DeleteFile, MoveFile.

En general, lo que quiere hacer es realmente bueno. Y no puedo pensar en ningún mejor esquema de escritura.

+3

Sería incluso mejor si lo ilustrara con el código Python adecuado que llama a la API de la biblioteca MS. – RedGlyph

+1

No me di cuenta de que ReplaceFile existía. Al leer los documentos, parece hacer mucho más que cambiar el nombre. Mantiene muchos de los atributos del archivo reemplazado, por lo que parece diseñado específicamente para este propósito. –

3

Una solución simplista. Use tempfile para crear un archivo temporal y si la escritura tiene éxito, simplemente cambie el nombre del archivo a su archivo de configuración original.

Para bloquear un archivo, consulte portalocker.

+1

Si el archivo temporal se crea en otro sistema de archivos distinto al de destino, el cambio de nombre final no funcionará, o no será atómico. – tzot

+0

Pero a menos que el cambio de nombre sea atómico, corro el riesgo de perder datos, ¿verdad? –

4

La solución estándar es esta.

  1. Escribir un nuevo archivo con un nombre similar. X.ext # por ejemplo.

  2. Cuando ese archivo se ha cerrado (y tal vez incluso leído y suma de comprobación), los dos dos cambian de nombre.

    • X.ext (el original) a X.ext ~

    • X.ext # (la nueva) a X.ext

  3. (Sólo para el loco paranoicos) llaman a la función de sincronización del sistema operativo para forzar las grabaciones sucias del búfer.

En ningún momento se pierde o se corrompe nada. El único error puede ocurrir durante los cambios de nombre. Pero no has perdido nada ni corrompido nada. El original es recuperable hasta el último cambio de nombre.

+0

Pero si no cambia el nombre dos veces (la creación de una copia de seguridad como lo hizo) corre el riesgo de que pierden datos si el cambio de nombre no es atómica, ¿verdad? –

+1

Cambiar el nombre * es * atómico en muchos sistemas operativos. Sin embargo, la copia de seguridad es más importante que exprimir manualmente la atomicidad de la operación. Recuerde: las probabilidades de un bloqueo (excepto en Windows) son muy pequeñas. Las probabilidades de un bloqueo en el medio de un cambio de nombre (que son solo unas pocas instrucciones más una sincronización son muy, muy pequeñas. –

11

Si quieres ser POSIXly correcta y guardar usted tiene que:

  1. Escribir al archivo temporal
  2. Enjuague y fsync el archivo (o fdatasync)
  3. Cambiar nombre en el archivo original

tenga en cuenta que llamar a fsync tiene efectos impredecibles sobre el rendimiento - Linux en ext3 puede detener para el disco E/S números enteros de segundos, como resultado, dependiendo de otros o E/S excepcional.

Observe que rename es no una operación atómica en POSIX - al menos no en relación con los datos de archivo como usted espera. Sin embargo, la mayoría de los sistemas operativos y sistemas de archivos funcionarán de esta manera. Pero parece que te perdiste la gran discusión de Linux sobre Ext4 y las garantías del sistema de archivos sobre la atomicidad. No sé exactamente dónde vincular, pero aquí hay un comienzo: ext4 and data loss.

Aviso sin embargo, que en muchos sistemas, cambiar el nombre será tan seguro en la práctica como se espera. Sin embargo, de alguna manera no es posible obtener ambos: rendimiento y confiabilidad en todas las confugraciones posibles de Linux.

Con una escritura en un archivo temporal, entonces un cambio de nombre del archivo temporal, se esperaría que las operaciones son dependientes y se ejecutan en orden.

El problema sin embargo es que la mayoría, si no todos los sistemas de archivos separados metadatos y datos. Un cambio de nombre es solo metadata. Puede sonarle horrible, pero los sistemas de archivos valoran los metadatos sobre los datos (¡tome el Diario en HFS + o Ext3,4 por ejemplo)! La razón es que los metadatos son más livianos, y si los metadatos están corruptos, todo el sistema de archivos está dañado: el sistema de archivos, por supuesto, debe preservarse y luego preservar los datos del usuario, en ese orden.

Ext4 hizo romper la expectativa rename cuando salió por primera vez, sin embargo se añadieron heurística para resolverlo. El problema es no un cambio de nombre fallido, pero un cambio de nombre exitoso. Ext4 podría registrar con éxito el cambio de nombre, pero no puede escribir los datos del archivo si se produce un bloqueo poco después. El resultado es entonces un archivo de 0 longitudes y ninguno de los datos originales ni nuevos.

Así que en resumen, POSIX no ofrece ninguna garantía de tal. ¡Lea el artículo Ext4 vinculado para obtener más información!

+0

Quizás entendí mal. Pero si cambias el nombre en un sistema POSIX, ¿no estás seguro de que El destino no se modifica si el cambio de nombre falla? "Si la función rename() falla por cualquier motivo que no sea [EIO], cualquier archivo nombrado por nuevo no se verá afectado." Supongo que aún puede dejar datos corruptos. –

+0

el problema es una . renombrar éxito, y que sólo un cambio de nombre no garantiza la atomicidad de toda la operación – u0b34a0f6ae

+1

lo que quiere decir es que renombrar() no puesto de control lo más seguro es atómica, véase:. http://www.opengroup.org/onlinepubs /009695399/functions/rename.html – geocar

1

Según la sugerencia de RedGlyph, se agregó una implementación de ReplaceFile que usa ctypes para acceder a las API de Windows. Primero agregué esto a 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 

Luego probé el comportamiento con este script.

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') 

Mientras que esto sólo funciona en Windows, parece tener una gran cantidad de características interesantes que otras rutinas reemplazan carecerían. Vea el API docs para más detalles.

0

Usted podría utilizar el módulo fileinput para manejar la copias de seguridad y en el lugar de escribir para usted:

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) 

Si es necesario leer en todo el contenido antes de poder escribir la nueva línea de, entonces puede hacer eso primero, y luego imprimir todo con nuevos contenidos

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

Si el script termina abruptamente, de todas maneras tendrá la copia de seguridad.

3

Ahora hay una solución Pythonic codificada, pura-Python, y me atrevería a decir esto en el boltons utility library: boltons.fileutils.atomic_save.

Sólo pip install boltons, entonces:

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') 

Hay una gran cantidad de opciones prácticas, all well-documented. Divulgación completa, soy el autor de boltons, pero esta parte en particular fue construida con mucha ayuda de la comunidad. ¡No vacile en drop a note si algo no está claro!

Cuestiones relacionadas