Reemplazar archivos en general
Primero, hay varias estrategias para reemplazar un archivo:
Abra el archivo existente para escribir, trúnquelo a 0 de longitud y escriba el nuevo contenido. (Una variante menos común es abrir el archivo existente, sobrescribir el contenido antiguo con el nuevo contenido, truncar el archivo a la nueva longitud si es más corto). En términos de shell:
echo 'new content' >somefile
Elimine el archivo antiguo y cree un nuevo archivo con el mismo nombre. En términos de shell:
rm somefile
echo 'new content' >somefile
Escriba en un nuevo archivo con un nombre temporal, luego mueva el nuevo archivo al nombre existente. El movimiento elimina el archivo antiguo. En términos de shell:
echo 'new content' >somefile.new
mv somefile.new somefile
No enumeraré todas las diferencias entre las estrategias, solo mencionaré algunas que son importantes aquí. Con la estrategia 1, si algún proceso está usando el archivo actualmente, el proceso ve el nuevo contenido a medida que se actualiza. Esto puede causar cierta confusión si el proceso espera que el contenido del archivo permanezca igual. Tenga en cuenta que esto solo se trata de procesos que tienen el archivo abierto (como se ve en lsof
o dentro ; las aplicaciones interactivas que tienen un documento abierto (por ejemplo, abrir un archivo en un editor) generalmente no mantienen el archivo abierto, cargan el contenido del archivo durante el Operación "abrir documento" y reemplazan el archivo (usando una de las estrategias anteriores) durante la operación "guardar documento"./proc/PID/fd/
Con las estrategias 2 y 3, si algún proceso tiene el archivo somefile
abierto, el archivo antiguo permanece abierto durante la actualización de contenido. Con la estrategia 2, el paso de eliminar el archivo de hecho solo elimina la entrada del archivo en el directorio. El archivo en sí solo se elimina cuando no tiene una entrada de directorio que lo lleve (en los sistemas de archivos Unix típicos, puede haber más de una entrada de directorio para el mismo archivo ) y ningún proceso lo tiene abierto. Aquí hay una manera de observar esto: el archivo solo se elimina cuando se cierra el sleep
proceso ( rm
solo elimina su entrada de directorio).
echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .
Con la estrategia 3, el paso de mover el nuevo archivo al nombre existente elimina la entrada del directorio que conduce al contenido anterior y crea una entrada de directorio que conduce al nuevo contenido. Esto se realiza en una operación atómica, por lo que esta estrategia tiene una gran ventaja: si un proceso abre el archivo en cualquier momento, verá el contenido antiguo o el nuevo contenido; no hay riesgo de obtener contenido mixto o el archivo no existente.
Sustitución de ejecutables
Si prueba la estrategia 1 con un ejecutable en ejecución en Linux, obtendrá un error.
cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy
Un "archivo de texto" significa un archivo que contiene código ejecutable por razones históricas oscuras . Linux, como muchas otras variantes de Unix, se niega a sobrescribir el código de un programa en ejecución; Algunas variantes de Unix permiten esto, lo que lleva a bloqueos a menos que el nuevo código fuera una muy buena modificación del antiguo código.
En Linux, puede sobrescribir el código de una biblioteca cargada dinámicamente. Es probable que conduzca a un bloqueo del programa que lo está usando. (Es posible que no pueda observar esto sleep
porque carga todo el código de biblioteca que necesita cuando se inicia. Pruebe un programa más complejo que haga algo útil después de dormir, como perl -e 'sleep 9; print lc $ARGV[0]'
).
Si un intérprete ejecuta un script, el intérprete abre el archivo de script de manera normal, por lo que no hay protección contra la sobrescritura del script. Algunos intérpretes leen y analizan el guión completo antes de comenzar a ejecutar la primera línea, otros leen el guión según sea necesario. Consulte ¿Qué sucede si edita un script durante la ejecución? y ¿Cómo trata Linux los scripts de shell? para más detalles.
Las estrategias 2 y 3 también son seguras para los ejecutables: aunque ejecutar ejecutables (y bibliotecas cargadas dinámicamente) no son archivos abiertos en el sentido de tener un descriptor de archivo, se comportan de una manera muy similar. Mientras algún programa ejecute el código, el archivo permanece en el disco incluso sin una entrada de directorio.
Actualizar una aplicación
La mayoría de los administradores de paquetes usan la estrategia 3 para reemplazar archivos, debido a la gran ventaja mencionada anteriormente: en cualquier momento, abrir el archivo conduce a una versión válida del mismo.
Donde las actualizaciones de la aplicación pueden romperse es que si bien la actualización de un archivo es atómica, la actualización de la aplicación en su conjunto no lo es si la aplicación consta de múltiples archivos (programa, bibliotecas, datos, ...). Considere la siguiente secuencia de eventos:
- Se inicia una instancia de la aplicación.
- La aplicación está actualizada.
- La aplicación de instancia en ejecución abre uno de sus archivos de datos.
En el paso 3, la instancia en ejecución de la versión anterior de la aplicación está abriendo un archivo de datos de la nueva versión. Si esto funciona o no depende de la aplicación, de qué archivo es y cuánto se ha modificado el archivo.
Después de una actualización, notará que el antiguo programa aún se está ejecutando. Si desea ejecutar la nueva versión, deberá salir del programa anterior y ejecutar la nueva versión. Los administradores de paquetes generalmente matan y reinician los demonios en una actualización, pero dejan en paz las aplicaciones del usuario final.
Algunos demonios tienen procedimientos especiales para manejar las actualizaciones sin tener que matar al demonio y esperar a que la nueva instancia se reinicie (lo que causa una interrupción del servicio). Esto es necesario en el caso de init , que no se puede matar; Los sistemas init proporcionan una manera de solicitar que la instancia en ejecución llame execve
para reemplazarse con la nueva versión.