¿Es seguro mover un archivo al que se está agregando?

28

Tengo un proceso node.js que utiliza fs.appendFilepara agregar líneas file.log. Solo se añaden líneas completas de aproximadamente 40 caracteres por línea, por ejemplo, las llamadas son similares fs.appendFile("start-end"), no 2 llamadas como fs.appendFile("start-")y fs.appendFile("end"). Si muevo este archivo a file2.log¿puedo estar seguro de que no se pierden ni copian líneas parcialmente?

Mullido
fuente

Respuestas:

36

Mientras no mueva el archivo a través de los bordes del sistema de archivos, la operación debe ser segura. Esto se debe al mecanismo, cómo se realiza realmente el "movimiento".

Si tiene mvun archivo en el mismo sistema de archivos, el archivo no se toca realmente, sino que solo se modifica la entrada del sistema de archivos.

$ mv foo bar

en realidad hace algo como

$ ln foo bar
$ rm foo

Esto crearía un enlace rígido (una segunda entrada de directorio) para el archivo (en realidad el inodo señalado por la entrada del sistema de archivos) foonombrado bary eliminaría la fooentrada. Como ahora al eliminar foo, hay una segunda entrada del sistema de archivos que apunta al fooinodo de '', eliminar la entrada anterior foono elimina realmente ningún bloque que pertenezca al inodo.

De todos modos, su programa se agregaría al archivo, ya que su identificador de archivo abierto apunta al inodo del archivo, no a la entrada del sistema de archivos.

Nota: Si su programa cierra y vuelve a abrir el archivo entre escrituras, ¡terminaría teniendo un nuevo archivo creado con la entrada anterior del sistema de archivos!

Movimientos cruzados del sistema de archivos:

Si mueve el archivo a través de los bordes del sistema de archivos, las cosas se ponen feas. En este caso, no puede garantizar que su archivo se mantenga constante, ya mvque en realidad

  • crear un nuevo archivo en el sistema de archivos de destino
  • copie el contenido del archivo antiguo al archivo nuevo
  • eliminar el archivo antiguo

o

$ cp /path/to/foo /path/to/bar
$ rm /path/to/foo

resp.

$ touch /path/to/bar
$ cat < /path/to/foo > /path/to/bar
$ rm /path/to/foo

Dependiendo de si la copia llega al final del archivo durante la escritura de su aplicación, podría suceder que solo tenga la mitad de una línea en el nuevo archivo.

Además, si su aplicación no cierra y vuelve a abrir el archivo anterior, continuaría escribiendo en el archivo anterior, incluso si parece haber sido eliminado: el núcleo sabe qué archivos están abiertos y, aunque eliminaría la entrada del sistema de archivos, no eliminará el inodo del archivo antiguo y los bloques asociados hasta que su aplicación cierre su identificador de archivo abierto.

Andreas Wiese
fuente
3
Para su información, las primeras versiones de Unix no tenían una rename()llamada al sistema. Entonces, la versión original de mvrealmente llamó link()para crear el enlace duro, seguido de unlink()eliminar el nombre original. rename()fue agregado en FreeBSD, para implementar esto atómicamente en el kernel.
Barmar
Lo siento, pero ¿qué es file-system borders?
laike9m
1
@ laike9m: los bordes del sistema de archivos se refieren al hecho de que un sistema de archivos simple debe residir en una partición en un dispositivo de memoria como una unidad de disco. Si cambia el nombre de un archivo dentro del sistema de archivos, todo lo que cambia es su nombre en una entrada de directorio. Todavía tiene el mismo inodo, si estaba en un sistema de archivos basado en inodos para empezar, como la mayoría de los sistemas de archivos de Linux. Pero, si mueve el archivo a otro sistema de archivos, los datos reales deben moverse y el archivo obtendrá un nuevo inodo del nuevo sistema de archivos. Esto interrumpiría cualquier operación en el archivo que estuviera en progreso cuando esto ocurriera.
Joe
9

Como dice que está usando node.js, supongo que estaría usando fs.rename()(o fs.renameSync()) para cambiar el nombre de los archivos. Este método de node.js está documentado para usar la llamada al sistema rename (2) , que no toca el archivo en sí mismo de ninguna manera, sino que simplemente cambia el nombre con el que aparece en el sistema de archivos:

" rename () cambia el nombre de un archivo, moviéndolo entre directorios si es necesario. Cualquier otro enlace duro al archivo (tal como se creó usando el enlace (2) ) no se ve afectado. Los descriptores de archivo abiertos para oldpath tampoco se ven afectados".

En particular, tenga en cuenta la última oración citada anteriormente, que dice que cualquier descriptor de archivo abierto (como el que usaría su programa para escribir en el archivo) continuará apuntándolo incluso después de haber cambiado el nombre. Por lo tanto, no habrá pérdida de datos ni corrupción, incluso si se cambia el nombre del archivo mientras se escribe simultáneamente.


Como Andreas Weise señala en su respuesta , la llamada al sistema rename (2) (y, por lo tanto, fs.rename()en node.js) no funcionará a través de los límites del sistema de archivos. Por lo tanto, intentar mover un archivo a un sistema de archivos diferente de esta manera simplemente fallará.

El mvcomando Unix intenta ocultar esta limitación detectando el error y, en cambio, moviendo el archivo copiando su contenido a un nuevo archivo y eliminando el original. Desafortunadamente, mover archivos como este conlleva el riesgo de pérdida de datos si el archivo se mueve mientras se está escribiendo. Por lo tanto, si desea cambiar el nombre de forma segura archivos que se pueden escribir simultáneamente a, usted debe no usar mv(o, al menos, debe estar absolutamente seguro de que el camino nuevo y viejo están en el mismo sistema de archivos).

Ilmari Karonen
fuente