Cree un archivo de forma segura si y solo si no existe con python

Deseo escribir en un archivo según si ese archivo ya existe o no, solo escribir si no existe (en la práctica, deseo seguir probando archivos hasta que encuentre uno que no existe).

El siguiente código muestra la forma en que un atacante potencial podría insertar un enlace simbólico, como se sugiere en esta publicación entre una prueba para el archivo y el archivo que se está escribiendo. Si el código se ejecuta con permisos suficientemente altos, esto podría sobrescribir un archivo arbitrario.

¿Hay alguna manera de resolver este problema?

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

Edición : Consulte también la respuesta de Dave Jones : desde Python 3.3, puede usar la x para open() para proporcionar esta función.

Respuesta original a continuación

Sí, pero sin usar la llamada estándar open() Python. Deberá usar os.open() lugar, lo que le permite especificar marcas al código C subyacente.

En particular, desea utilizar O_CREAT | O_EXCL O_CREAT | O_EXCL . Desde la página de manual de open(2) bajo O_EXCL en mi sistema Unix:

Asegúrese de que esta llamada cree el archivo: si este indicador se especifica junto con O_CREAT , y ya existe una ruta, open() fallará. El comportamiento de O_EXCL no está definido si no se especifica O_CREAT .

Cuando se especifican estas dos banderas, no se siguen los enlaces simbólicos: si pathname es un enlace simbólico, entonces open() falla, independientemente de a dónde apunta el enlace simbólico.

O_EXCL solo se admite en NFS cuando se usa NFSv3 o posterior en el kernel 2.6 o posterior. En los entornos en los que no se proporciona compatibilidad con NFS O_EXCL , los progtwigs que dependen de él para realizar tareas de locking contendrán una condición de carrera.

Así que no es perfecto, pero AFAIK es lo más cerca que puedes estar de evitar esta condición de carrera.

Editar: las otras reglas de uso de os.open() lugar de open() todavía se aplican. En particular, si desea utilizar el descriptor de archivo devuelto para leer o escribir, también necesitará uno de los O_RDONLY , O_WRONLY o O_RDWR .

Todas las banderas O_* están en el módulo os de Python, por lo que deberá import os y usar os.O_CREAT etc.

Ejemplo:

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

Para referencia, Python 3.3 implementa un nuevo modo 'x' en la función open() para cubrir este caso de uso (crear solo, fallar si el archivo existe). Tenga en cuenta que el modo 'x' se especifica por sí solo. Usar 'wx' da como resultado un ValueError ya que la 'w' es redundante (lo único que puede hacer si la llamada es exitosa es escribir en el archivo de todos modos; no puede haber existido si la llamada es exitosa):

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

Para Python 3.2 y versiones anteriores (incluido Python 2.x), consulte la respuesta aceptada .

Este código creará fácilmente un archivo si no existe uno.

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