2010-07-11 9 views
6

Si desea cambiar el nombre A a B, pero sólo si B no existe, lo ingenuo estaría comprobando si existe B (con access("B", F_OK) o algo por el estilo), y si no proceder con rename. Desafortunadamente, esto abre una ventana durante la cual algún otro proceso puede decidir crear B, y luego se sobrescribe, y lo que es peor no hay indicios de que algo así haya pasado.¿Cómo cambiar el nombre() sin condiciones de carrera?

Otras funciones de acceso del sistema de archivos no sufren de esto - open tiene O_EXCL (archivos de modo de copia es seguro), y recientemente Linux tiene una familia completa de *at llamadas al sistema que protegen contra la mayoría de las demás condiciones de carrera - pero no este en particular (renameat existe, pero protege contra un problema completamente diferente).

Entonces, ¿tiene una solución?

+0

tal vez debería considerar el uso de un mecanismo de bloqueo explícita en lugar de depender de un bloqueo implícito en (un envoltorio de) la función de cambiar el nombre. Si lo que crea 'B' es un programa bajo su control, posiblemente podría usar primitivas de sincronización entre procesos. –

Respuesta

14

Debería poder link (2) con el nuevo nombre de archivo. Si el enlace falla, entonces se da por vencido porque el archivo ya existe. Si el enlace tiene éxito, su archivo ahora existe bajo el nombre anterior y el nuevo. Luego, unlink (2) el nombre anterior. No hay una condición de carrera posible.

+0

¿Qué son los archivos bajo diferentes puntos de montaje? –

+2

@Moron: rename() no funciona en diferentes sistemas de archivos. – Dummy00001

+0

@ Dummy00001: Ya veo. Me perdí los corchetes en el título. +1 luego :-) –

1

Desde la página del manual de cambio de nombre:

Si newpath ya existe, será automáticamente reemplazado (sujeto a unos condiciones; ver ERRORES más adelante), de manera que no hay ningún punto en el que otra el proceso que intenta acceder a newpath lo encontrará perdido.

Por lo tanto, no es posible evitar el cambio de nombre cuando el archivo B ya existe. Creo que tal vez simplemente no tiene más remedio que verificar la existencia (use stat() no access() para eso) antes de intentar cambiar el nombre, si no desea que el cambio de nombre ocurra si el archivo ya existe. Ignorando una condición de carrera.

De lo contrario, la solución que se presenta a continuación con el enlace() parece ajustarse a sus requisitos.

+0

Si el archivo B no existe, todavía hay una condición de carrera si prueba la existencia; ambos procesos pueden detectar que no está allí, y luego ambos cambian de nombre, con resultados indeterminados en cuanto a qué proceso se renombró por última vez. La única solución es que rename() sea atómico y falle si el nuevo nombre de archivo ya existe, que es lo que hace en Windows, y lo que pensé (incorrectamente parece) el estándar C especificado. –

6

Puede link() al archivo existente con el nuevo nombre de archivo que desee, luego elimine el nombre de archivo existente.

link() debe tener éxito al crear un nuevo enlace solo si el nuevo nombre de ruta no existe.

Algo así como:

int result = link("A", "B"); 

if (result != 0) { 
    // the link wasn't created for some reason (maybe because "B" already existed) 
    // handle the failure however appropriate... 
    return -1; 
} 

// at this point there are 2 filenames hardlinked to the contents of "A", 
// filename "A" and filename "B" 

// remove filename "A" 
unlink("A"); 

Esta técnica se discute en los documentos de link() (véase la discusión acerca de cómo modificar el archivo passwd):

3

Lo siento por la adición algo a un viejo hilo. Y por hacer una publicación tan larga.

Sólo conozco una sola manera de hacer una condición completa carrera sin rename() en ausencia de bloqueo que deben trabajar en prácticamente cualquier sistema de archivos, incluso en NFS con reinicios del servidor y el tiempo intermittend cliente deformaciones en su lugar.

La siguiente receta es libre de condiciones de carrera en el sentido de que en ningún caso se pueden perder datos. Tampoco necesita bloqueos y puede ser realizado por clientes que no desean cooperar, excepto que todos usan el mismo algoritmo.

No es una condición de carrera libre en el sentido de que, si algo se rompe seriamente, todo queda en un estado limpio y ordenado. También tiene un corto período de tiempo, donde ni la fuente ni el destino están presentes en su ubicación, sin embargo, la fuente aún se encuentra bajo otro nombre. Y no está reforzado contra los casos en que un atacante intenta provocar daño (el rename() es el culpable, vaya figura).

S es la Fuente, D es el destino, P (x) es dirname(x), C (x, y) es x/y camino concatenación

  1. verificación de que el destino no existe. Solo para asegurarnos de que no hagamos los próximos pasos en vano.
  2. crear un nombre probablemente única T: = C (P (D), al azar)
  3. mkdir (T), si esto falla bucle al paso anterior
  4. abierto (C (T, "bloquear"), O_EXCL), si esto falla rmdir (T) haciendo caso omiso de errores y bucle al paso anterior
  5. de cambio de nombre (S, C (T, "tmp"))
  6. enlace (C (T, "tmp"), D)
  7. unlink (C (T, "tmp"))
  8. unlink (C (T, "bloquear"))
  9. rmdir (T)

Algoritmo safe_rename(S,D) explicó:

El problema es que queremos para asegurarse de que no hay una condición de carrera, ni en el origen ni el destino. Se supone que (casi) puede pasar cualquier cosa entre cada paso, pero todos los demás procesos siguen exactamente el mismo algoritmo al hacer cambios de condición de condición de carrera. Esto incluye que los directorios temporales T nunca se tocan, excepto después de asegurarse (este es un proceso manual) de que el proceso que utiliza el directorio ha muerto y no puede resucitar (como continuar una máquina hibernando después de una restauración).

Para hacer correctamente el rename(), necesitamos un lugar donde escondernos. Así que construimos un directorio de una manera que asegura que nadie más (que esté siguiendo el mismo algoritmo) lo use accidentalmente. Sin embargo, no se garantiza que mkdir() sea atómico en NFS. Por lo tanto, debemos asegurarnos de tener alguna garantía de que estamos solos en el directorio. Esto es O_EXCL en el archivo de bloqueo. Esto es, estrictamente hablando, sin bloqueo, es un semáforo.

Excepto en casos tan raros, mkdir() suele ser atómico. También podemos crear el uso de algún nombre aleatorio criptográficamente seguro para el directorio, agregar algunos GUID, nombre de host y PID para asegurarnos de que sea muy poco probable que otra persona elija el mismo nombre por casualidad. Sin embargo, para probar que el algoritmo es correcto, necesitamos este archivo llamado lock.

Ahora que tenemos un directorio prácticamente vacío, podemos rename() con seguridad la fuente allí. Esto asegura que nadie más altera la fuente hasta que lo hagamos unlink(). (Bueno, el contenido puede cambiar, esto no es un problema.)

Ahora se puede aplicar el truco link() para asegurarnos de no sobrescribir el destino.

condición de carrera Después, el unlink() se puede hacer libre de la fuente restante. El resto es limpieza.

Sólo hay un problema de la izquierda:

En caso de que la falla link() hemos pasado ya la fuente. Para la limpieza adecuada, tenemos que moverlo hacia atrás. Esto se puede hacer llamando al safe_rename(C(T,"tmp"),S). Si esto no funciona, también, todo lo que podemos hacer es tratar de limpieza tanto como podemos (unlink(C(T,"lock")), rmdir(T)) y dejar los escombros detrás de limpieza manual por el administrador.

notas finales:

para ayudar a limpiar los escombros en el caso, se puede utilizar posiblemente algunos mejores que el nombre del archivo tmp. Elegir nombres hábilmente puede endurecer algo el algoritmo contra ataques también.

Y si está trenes cargados de mover archivos en algún lugar se puede volver a utilizar el directorio, por supuesto.

Sin embargo, estoy de acuerdo, que este algoritmo es demasiado simple y algo así como O_EXCL en rename() falta.

Cuestiones relacionadas