Una vez, en una entrevista, me preguntaron qué haría si descubriera un servicio que no funciona porque el disco se ha llenado.
Por supuesto, respondí que miraría qué ocupa ese espacio y, si es posible, lo limpiaría.
Entonces, el entrevistador preguntó: ¿y si en la partición no hay espacio libre, pero tampoco ves archivos que ocupen todo el espacio?
A esto respondí que siempre se pueden revisar los descriptores de archivos abiertos, por ejemplo, con el comando lsof, y entender qué aplicación ha ocupado todo el espacio disponible, y luego actuar según las circunstancias, dependiendo de si los datos son necesarios.
El entrevistador me interrumpió en la última palabra, añadiendo su pregunta: «Supongamos que los datos no nos importan, es solo un log de depuración, pero la aplicación no funciona porque no puede escribir el log de depuración».
«Está bien», respondí, «podemos desactivar la depuración en la configuración de la aplicación y reiniciarla».
El entrevistador replicó: «No, no podemos reiniciar la aplicación, todavía tenemos datos importantes en memoria, y a la aplicación están conectados clientes importantes, a los que no podemos obligar a reconectarse».
«Bueno», dije, «si no podemos reiniciar la aplicación y los datos no son importantes, simplemente podemos limpiar ese archivo abierto a través del descriptor de archivo, incluso si no lo vemos en el comando ls del sistema de archivos».
El entrevistador quedó satisfecho, pero yo no.
Entonces pensé, ¿por qué la persona que evalúa mis conocimientos no profundiza más? ¿Y si los datos son realmente importantes? ¿Qué sucede si no podemos reiniciar el proceso, y este proceso está escribiendo en el sistema de archivos en una partición sin espacio libre? ¿Qué pasa si no podemos perder no solo los datos ya escritos, sino también los datos que este proceso está escribiendo o intentando escribir?
Tuzik
Al principio de mi carrera intenté crear una pequeña aplicación para almacenar información sobre usuarios. Y entonces pensé, ¿cómo puedo vincular al usuario con sus datos? Por ejemplo, tengo a Ivanov Ivan Ivanovich, y tiene algunos datos, pero ¿cómo los relaciono? Puedo especificar directamente que un perro llamado "Tuzik" pertenece a este mismo Ivan. Pero, ¿qué pasa si cambia de nombre y en lugar de Ivan se convierte en Olya? Entonces, resultaría que nuestra Olya Ivanovna Ivanova ya no tendría un perro, mientras que nuestro Tuzik seguiría perteneciendo a un Ivan que ya no existe. Para resolver este problema, me ayudó una base de datos que asignaba a cada usuario un identificador único (ID), y mi Tuzik se vinculaba a este ID, que, en esencia, era simplemente un número secuencial. Así, el dueño de Tuzik tenía el ID número 2, y en un momento dado, este ID pertenecía a Ivan, y luego a Olya. El problema de la humanidad y la cría de animales estaba prácticamente resuelto.
Descriptor de archivo
El problema del archivo y el programa que trabaja con este archivo es similar al de nuestro perro y su dueño. Supongamos que abrí el archivo llamado ivan.txt y comencé a escribir la palabra tuzik, pero solo logré escribir la primera letra "t" en el archivo, y alguien lo renombró, por ejemplo, a olya.txt. Pero el archivo sigue siendo el mismo, y aún quiero escribir mi Tuzik en él. Cada vez que abro el archivo mediante una llamada al sistema en cualquier lenguaje de programación, recibo un ID único que me apunta al archivo; este ID es el descriptor de archivo. Y no importa lo que y quién haga con este archivo después, puede ser eliminado, renombrado, puede cambiar de propietario o perder derechos de lectura y escritura; aun así, tendré acceso a él, porque en el momento de abrir el archivo tenía derechos para leerlo y/o escribir en él y logré comenzar a trabajar con él, lo que significa que debo seguir haciéndolo.
En Linux, la biblioteca libc abre 3 descriptores de archivo para cada aplicación (proceso) en ejecución, numerados como 0, 1, 2. Puede encontrar más información en los enlaces y
- El descriptor de archivo 0 se llama STDIN y se asocia con la entrada de datos de la aplicación
- El descriptor de archivo 1 se llama STDOUT y es utilizado por las aplicaciones para salida de datos, como los comandos print.
- El descriptor de archivo 2 se llama STDERR y es utilizado por las aplicaciones para salida de datos que informan un error.
Si en su programa abre algún archivo para lectura o escritura, probablemente obtendrá el primer ID libre y será el número 3.
Puede ver la lista de descriptores de archivo de cualquier proceso si conoce su PID.
Por ejemplo, abramos una consola con bash y veamos el PID de nuestro proceso.
[user@localhost ]$ echo $$
15771
En la segunda consola ejecutaremos
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user 0 Oct 7 15:42 .
dr-xr-xr-x 9 user user 0 Oct 7 15:42 ..
lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21
El descriptor de archivo con el número 255 puede ser ignorado en este artículo, fue abierto para sus propias necesidades por bash, y no por la biblioteca enlazada.
Actualmente, los 3 descriptores de archivo están relacionados con el dispositivo de pseudo-terminal. , pero aún podemos manipularlos, por ejemplo, ejecutaremos en la segunda consola
[user@localhost ]$ echo "hello world" > /proc/15771/fd/0
Y en la primera consola veremos
[user@localhost ]$ hello world
Redirect y Pipe
Puede fácilmente redefinir estos 3 descriptores de archivo en cualquier proceso, incluso en bash, como a través de un pipe que conecta dos procesos, veamos.
[user@localhost ]$ cat /dev/zero | sleep 10000
Puede ejecutar este comando usted mismo con strace -f y ver lo que sucede dentro, pero yo lo explicaré brevemente.
Nuestro proceso padre bash con PID 15771 analiza nuestro comando y entiende cuántos comandos queremos ejecutar, en nuestro caso son dos: cat y sleep. Bash sabe que necesita crear dos procesos hijos y unirlos con un pipe. En total, bash requiere 2 procesos hijos y un pipe.
Antes de crear los procesos hijos, bash realiza una llamada al sistema y obtiene nuevos descriptores de archivo en un buffer temporal del pipe, pero este buffer aún no conecta nuestros dos procesos hijos.
Para el proceso padre, parece que el pipe ya existe, pero los procesos hijos aún no:
PID comando
15771 bash
lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21
Luego, mediante una llamada al sistema bash crea dos procesos hijos, y nuestros tres procesos se verán así:
PID comando
15771 bash
lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21
PID comando
9004 bash
lrwx------ 1 user user 64 Oct 7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 255 -> /dev/pts/21
PID comando
9005 bash
lrwx------ 1 user user 64 Oct 7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 255 -> /dev/pts/21
No olvidemos que clone clona el proceso junto con todos los descriptores de archivo, por lo que en el proceso padre y en los hijos serán iguales. La tarea del proceso padre con PID 15771 es supervisar a los procesos hijos, por lo que simplemente espera una respuesta de los hijos.
Por lo tanto, no necesita el pipe, y cierra los descriptores de archivo con los números 3 y 4.
En el primer proceso hijo, bash con PID 9004, mediante la llamada al sistema , cambia nuestro descriptor de archivo STDOUT con el número 1 al descriptor de archivo que apunta al pipe, en nuestro caso es el número 3. Así, todo lo que el primer proceso hijo con PID 9004 escriba en STDOUT se enviará automáticamente al buffer del pipe.
En el segundo proceso hijo con PID 9005, bash cambia con dup2 el descriptor de archivo STDIN con el número 0. Ahora, todo lo que lea nuestro segundo bash con PID 9005, será leído desde el pipe.
Después de esto, en los procesos hijos también se cierran los descriptores de archivo con los números 3 y 4, ya que ya no se utilizan.
El descriptor de archivo 255 lo ignoro intencionadamente, lo utiliza para las necesidades internas de bash y también se cerrará en los procesos hijos.
Luego, en el primer proceso hijo con PID 9004, bash inicia mediante la llamada al sistema el archivo ejecutable que indicamos en la línea de comandos, en nuestro caso es /usr/bin/cat.
En el segundo proceso hijo con PID 9005, bash inicia el segundo archivo ejecutable que hemos indicado, en nuestro caso es /usr/bin/sleep.
La llamada del sistema exec no cierra los descriptores de archivos si no fueron abiertos con el flag O_CLOEXEC durante la ejecución de la llamada open. En nuestro caso, después de iniciar los archivos ejecutables, todos los descriptores de archivos actuales se mantendrán.
Revisamos en la consola:
[user@localhost ]$ pgrep -P 15771
9004
9005
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user 0 Oct 7 15:42 .
dr-xr-xr-x 9 user user 0 Oct 7 15:42 ..
lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21
[user@localhost ]$ ls -lah /proc/9004/fd
total 0
dr-x------ 2 user user 0 Oct 7 15:57 .
dr-xr-xr-x 9 user user 0 Oct 7 15:57 ..
lrwx------ 1 user user 64 Oct 7 15:57 0 -> /dev/pts/21
l-wx------ 1 user user 64 Oct 7 15:57 1 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21
lr-x------ 1 user user 64 Oct 7 15:57 3 -> /dev/zero
[user@localhost ]$ ls -lah /proc/9005/fd
total 0
dr-x------ 2 user user 0 Oct 7 15:57 .
dr-xr-xr-x 9 user user 0 Oct 7 15:57 ..
lr-x------ 1 user user 64 Oct 7 15:57 0 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21
[user@localhost ]$ ps -up 9004
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
user 9004 0.0 0.0 107972 620 pts/21 S+ 15:57 0:00 cat /dev/zero
[user@localhost ]$ ps -up 9005
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
user 9005 0.0 0.0 107952 360 pts/21 S+ 15:57 0:00 sleep 10000
Como pueden ver, el número único de nuestro pipe coincide en ambos procesos. Así, tenemos una conexión entre dos procesos diferentes con un mismo padre.
Para aquellos que no están familiarizados con las llamadas del sistema utilizadas por bash, se recomienda encarecidamente ejecutar los comandos a través de strace y observar lo que está sucediendo internamente, por ejemplo, así:
strace -s 1024 -f bash -c "ls | grep hello"
Regresando a nuestro problema de falta de espacio en disco y el intento de guardar datos sin reiniciar el proceso. Escribamos un pequeño programa que escriba en el disco aproximadamente 1 megabyte por segundo. Si por alguna razón no podemos escribir datos en el disco, simplemente lo ignoraremos e intentaremos escribir datos de nuevo después de un segundo. En el ejemplo estoy usando Python, pero pueden usar cualquier otro lenguaje de programación.
[user@localhost ]$ cat openforwrite.py
import datetime
import time
mystr="a"*1024*1024+"n"
with open("123.txt", "w") as f:
while True:
try:
f.write(str(datetime.datetime.now()))
f.write(mystr)
f.flush()
time.sleep(1)
except:
pass
Iniciaremos el programa y observaremos los descriptores de archivos
[user@localhost ]$ python openforwrite.py &
[1] 3762
[user@localhost ]$ ps axuf | grep [o]penforwrite
user 3762 0.0 0.0 128600 5744 pts/22 S+ 16:28 0:00 | _ python openforwrite.py
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user 0 Oct 7 16:29 .
dr-xr-xr-x 9 user user 0 Oct 7 16:29 ..
lrwx------ 1 user user 64 Oct 7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 7 16:29 3 -> /home/user/123.txt
Como podemos ver, tenemos nuestros 3 descriptores de archivo estándar y uno más que hemos abierto. Verificamos el tamaño del archivo:
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 117M Oct 7 16:30 123.txt
Los datos se están escribiendo, intentamos cambiar los permisos del archivo:
[user@localhost ]$ sudo chown root: 123.txt
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 root root 168M Oct 7 16:31 123.txt
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 root root 172M Oct 7 16:31 123.txt
Vemos que los datos aún se están escribiendo, aunque nuestro usuario no tiene permiso para escribir en el archivo. Intentemos eliminarlo:
[user@localhost ]$ sudo rm 123.txt
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory
¿A dónde se escriben los datos? ¿Y se están escribiendo? Verifiquemos:
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user 0 Oct 7 16:29 .
dr-xr-xr-x 9 user user 0 Oct 7 16:29 ..
lrwx------ 1 user user 64 Oct 7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 7 16:29 3 -> /home/user/123.txt (deleted)
Sí, nuestro descriptor de archivo aún existe, y podemos trabajar con este descriptor de archivo como si fuera nuestro archivo antiguo, podemos leerlo, limpiarlo y copiarlo.
Veamos el tamaño del archivo:
[user@localhost ]$ lsof | grep 123.txt
python 31083 user 3w REG 8,5 19923457 2621522 /home/user/123.txt
El tamaño del archivo es 19923457. Intentamos limpiar el archivo:
[user@localhost ]$ truncate -s 0 /proc/31083/fd/3
[user@localhost ]$ lsof | grep 123.txt
python 31083 user 3w REG 8,5 136318390 2621522 /home/user/123.txt
Como vemos, el tamaño del archivo solo aumenta y nuestro truncamiento no funcionó. Consultemos la documentación sobre la llamada al sistema. . Si al abrir un archivo usamos el flag O_APPEND, entonces en cada escritura el sistema operativo verifica el tamaño del archivo y escribe los datos al final del archivo, de forma atómica. Esto permite que varios hilos o procesos escriban en el mismo archivo. Pero en nuestro código no usamos este flag. Solo podemos ver otro tamaño del archivo en lsof después del truncamiento si abrimos el archivo para escritura adicional, así que en nuestro código en lugar de
with open("123.txt", "w") as f:
debemos poner
with open("123.txt", "a") as f:
Verificamos con el flag «w»
[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
y con la bandera «a»
[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
Programando un proceso ya en marcha
A menudo, los programadores utilizan depuradores (como GDB) o diferentes niveles de registro en la aplicación al crear y probar un programa. Linux permite modificar y escribir sobre un programa ya en ejecución, como cambiar el valor de las variables, establecer puntos de interrupción, etc.
Regresando a la pregunta original sobre la falta de espacio en el disco para escribir un archivo, intentemos simular el problema.
Creamos un archivo para nuestra sección, que montaremos como un disco separado:
[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$
Creamos un sistema de archivos:
[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd no es un dispositivo especial de bloque.
¿Proceder de todos modos? (y,n) y
...
Escribiendo superbloques e información de contabilidad del sistema de archivos: hecho
[user@localhost ~]$
Montamos el sistema de archivos:
[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] contraseña para user:
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 172K 7.9M 3% /mnt
Creamos un directorio con nuestro propietario:
[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs
Abrimos el archivo solo para escribir en nuestro programa:
with open("/mnt/logs/123.txt", "w") as f:
Iniciamos
[user@localhost ]$ python openforwrite.py
Esperamos unos segundos
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 8.0M 0 100% /mnt
Así que tenemos el problema descrito al principio de este artículo. Espacio libre 0, utilizado 100%.
Recordamos que, según las condiciones del problema, intentamos grabar datos muy importantes que no se pueden perder. Y debemos reparar el servicio sin reiniciar el proceso.
Supongamos que aún tenemos espacio en el disco, pero en otra partición, por ejemplo en /home.
Intentemos 'reprogramar sobre la marcha' nuestro código.
Observamos el PID de nuestro proceso que ha consumido todo el espacio en disco:
[user@localhost ~]$ ps axuf | grep [o]penfor
user 10078 27.2 0.0 128600 5744 pts/22 R+ 11:06 0:02 | _ python openforwrite.py
Conectamos al proceso a través de gdb
[user@localhost ~]$ gdb -p 10078
...
(gdb)
Observamos los descriptores de archivo abiertos:
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 Oct 8 11:06 .
dr-xr-xr-x 9 user user 0 Oct 8 11:06 ..
lrwx------ 1 user user 64 Oct 8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:09 3 -> /mnt/logs/123.txt
Observamos la información del descriptor de archivo número 3, que nos interesa
(gdb) shell cat /proc/10078/fdinfo/3
pos: 8189952
flags: 0100001
mnt_id: 482
Recordando qué llamada al sistema hace Python (mire arriba, donde ejecutamos strace y encontramos la llamada open), al procesar nuestro código para abrir un archivo, hacemos lo mismo nosotros mismos en nombre de nuestro proceso, pero los bits O_WRONLY|O_CREAT|O_TRUNC los tenemos que reemplazar por un valor numérico. Para ello, abrimos los fuentes del núcleo, por ejemplo y vemos qué banderas corresponden a qué
#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000
Uniendo todos los valores en uno, obtenemos 00001101
Ejecutamos nuestra llamada desde gdb
(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4
Así que obtuvimos un nuevo descriptor de archivo con el número 4 y un nuevo archivo abierto en otra partición, verificamos:
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 Oct 8 11:06 .
dr-xr-xr-x 9 user user 0 Oct 8 11:06 ..
lrwx------ 1 user user 64 Oct 8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:09 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct 8 11:15 4 -> /home/user/123.txt
Recordamos el ejemplo con pipe — cómo bash cambia los descriptores de archivo, y ya hemos aprendido la llamada al sistema dup2.
Intentamos sustituir un descriptor de archivo por otro
(gdb) call dup2(4,3)
$2 = 3
Verificando:
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 Oct 8 11:06 .
dr-xr-xr-x 9 user user 0 Oct 8 11:06 ..
lrwx------ 1 user user 64 Oct 8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:09 3 -> /home/user/123.txt
l-wx------ 1 user user 64 Oct 8 11:15 4 -> /home/user/123.txt
Cerramos el descriptor de archivo 4, ya que no lo necesitamos:
(gdb) call close (4)
$1 = 0
Y salimos de gdb
(gdb) quit
A debugging session is active.
Inferior 1 [process 10078] will be detached.
Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 10078
Verificamos el nuevo archivo:
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 5.1M Oct 8 11:18 /home/user/123.txt
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 7.1M Oct 8 11:18 /home/user/123.txt
Como vemos, los datos se escriben en el nuevo archivo, verificamos el antiguo:
[user@localhost ~]$ ls -lah /mnt/logs/123.txt
-rw-rw-r-- 1 user user 7.9M Oct 8 11:08 /mnt/logs/123.txt
Los datos no se han perdido, la aplicación está funcionando, los registros se están escribiendo en un nuevo lugar.
Complicaremos un poco la tarea
Imaginemos que los datos son importantes para nosotros, pero no tenemos espacio en disco en ninguna de las particiones y no podemos conectar un disco.
Lo que podemos hacer es redirigir nuestros datos a alguna parte, por ejemplo a un pipe, y los datos del pipe a su vez redirigirlos a la red a través de algún programa, como netcat.
Podemos crear un pipe nombrado con el comando mkfifo. Esto creará un pseudoarchivo en el sistema de archivos, incluso si no hay espacio libre en él.
Reiniciamos la aplicación y verificamos:
[user@localhost ]$ python openforwrite.py
[user@localhost ~]$ ps axuf | grep [o]pen
user 5946 72.9 0.0 128600 5744 pts/22 R+ 11:27 0:20 | _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user 0 Oct 8 11:27 .
dr-xr-xr-x 9 user user 0 Oct 8 11:27 ..
lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/123.txt
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 8.0M 0 100% /mnt
No hay espacio en disco, pero logramos crear un pipe con nombre ahí:
[user@localhost ~]$ mkfifo /mnt/logs/megapipe
[user@localhost ~]$ ls -lah /mnt/logs/megapipe
prw-rw-r-- 1 user user 0 Oct 8 11:28 /mnt/logs/megapipe
Ahora necesitamos enviar los datos que lleguen a este pipe a otro servidor a través de la red, para esto usaremos nuevamente netcat.
En el servidor remote-server.example.com ejecutamos
[user@localhost ~]$ nc -l 7777 > 123.txt
En nuestro servidor problemático ejecutamos en una terminal separada
[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe
Ahora todos los datos que lleguen al pipe automáticamente se enviarán a stdin en netcat, que los enviará a la red en el puerto 7777.
Lo único que nos queda por hacer es comenzar a escribir nuestros datos en este pipe con nombre.
Ya tenemos una aplicación en funcionamiento:
[user@localhost ~]$ ps axuf | grep [o]pen
user 5946 99.8 0.0 128600 5744 pts/22 R+ 11:27 169:27 | _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user 0 Oct 8 11:27 .
dr-xr-xr-x 9 user user 0 Oct 8 11:27 ..
lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/123.txt
De todas las banderas, solo necesitamos O_WRONLY ya que el archivo ya existe y no necesitamos limpiarlo.
[user@localhost ~]$ gdb -p 5946
...
(gdb) call open("/mnt/logs/megapipe", 00000001,0666)
$1 = 4
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user 0 Oct 8 11:27 .
dr-xr-xr-x 9 user user 0 Oct 8 11:27 ..
lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct 8 14:20 4 -> /mnt/logs/megapipe
(gdb) call dup2(4,3)
$2 = 3
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user 0 Oct 8 11:27 .
dr-xr-xr-x 9 user user 0 Oct 8 11:27 ..
lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/megapipe
l-wx------ 1 user user 64 Oct 8 14:20 4 -> /mnt/logs/megapipe
(gdb) call close(4)
$3 = 0
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user 0 Oct 8 11:27 .
dr-xr-xr-x 9 user user 0 Oct 8 11:27 ..
lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/megapipe
(gdb) quit
A debugging session is active.
Inferior 1 [process 5946] will be detached.
Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 5946
Estamos verificando el servidor remoto remote-server.example.com
[user@localhost ~]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 38M Oct 8 14:21 123.txt
Los datos están fluyendo, estamos verificando el servidor problemático
[user@localhost ~]$ ls -lah /mnt/logs/
total 7.9M
drwxr-xr-x 2 user user 1.0K Oct 8 11:28 .
drwxr-xr-x 4 root root 1.0K Oct 8 10:55 ..
-rw-rw-r-- 1 user user 7.9M Oct 8 14:17 123.txt
prw-rw-r-- 1 user user 0 Oct 8 14:22 megapipe
Los datos se han guardado, el problema ha sido resuelto.
Aprovecho la ocasión para enviar saludos a mis compañeros de la empresa Degiro.
Escuchen los podcasts de Radio-T.
Les deseo a todos lo mejor.
Como tarea para casa, les propongo pensar en qué habrá en los descriptores de archivos de los procesos cat y sleep si se ejecuta este comando:
[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000
Fuente: habr.com
