Una volta, durante un colloquio, mi hanno chiesto cosa faresti se scoprissi un servizio non funzionante a causa della mancanza di spazio su disco.
Naturalmente ho risposto che avrei controllato cosa occupa quel spazio e, se possibile, avrei liberato un po' di spazio.
Allora l'intervistatore ha chiesto: e se la partizione non ha spazio libero, ma non vedi nemmeno file che occupano tutto lo spazio?
A questo ho risposto che si possono sempre controllare i file descriptor aperti, ad esempio con il comando lsof, per capire quale applicazione ha occupato tutto lo spazio disponibile e poi agire di conseguenza, a seconda che i dati siano necessari.
L'intervistatore mi ha interrotto sull'ultima parola, aggiungendo la sua domanda: «Supponiamo che i dati non ci servano, è solo un log di debug, ma l'applicazione non funziona perché non può scrivere il debug»?
«Va bene», ho risposto, «possiamo disattivare il debug nella configurazione dell'applicazione e riavviarla».
L'intervistatore ha obiettato: «No, non possiamo riavviare l'applicazione, abbiamo ancora dati importanti in memoria e i clienti importanti sono connessi al servizio, non possiamo costringerli a riconnettersi».
«D'accordo», ho detto, «se non possiamo riavviare l'applicazione e i dati non sono importanti, possiamo semplicemente ripulire quel file aperto tramite il file descriptor, anche se non lo vediamo nel comando ls nel filesystem».
L'intervistatore era soddisfatto, ma io non lo ero.
Allora ho pensato, perché la persona che sta valutando le mie conoscenze non scava più a fondo? E se i dati fossero comunque importanti? E se non potessimo riavviare il processo, e nel frattempo questo processo scrive sul filesystem in una partizione che non ha spazio libero? Cosa succede se non possiamo perdere solo i dati già registrati, ma anche i dati che questo processo sta scrivendo o cercando di scrivere?
Tuzik
All'inizio della mia carriera ho cercato di creare una piccola applicazione in cui dovevo memorizzare informazioni sugli utenti. E allora pensavo, come posso associare un utente ai suoi dati? Ad esempio, ho Ivanov Ivan Ivanovich, e ha alcuni dati, ma come posso metterli insieme? Posso indicare direttamente che il cane di nome «Tuzik» appartiene a questo Ivan. Ma cosa succede se cambia nome e diventa, ad esempio, Olya? Allora risulterebbe che la nostra Olya Ivanovna Ivanova non avrà più un cane, mentre il nostro Tuzik continuerà ad appartenere a un Ivan che non esiste più. Per risolvere questo problema, mi ha aiutato un database, che attribuiva a ogni utente un identificatore unico (ID), e il mio Tuzik veniva legato a questo ID, che, in sostanza, era solo un numero progressivo. Così, il proprietario del Tuzik aveva un ID numero 2, e per un certo periodo di tempo sotto questo ID c'era Ivan, e poi sotto lo stesso ID c'era Olya. Il problema dell'umanità e dell'allevamento degli animali era praticamente risolto.
Descrittore di file
Il problema del file e del programma che utilizza questo file è simile a quello tra il nostro cane e la persona. Supponiamo che io abbia aperto il file con il nome ivan.txt e abbia iniziato a scrivere la parola tuzik, ma sono riuscito a scrivere solo la prima lettera «t» nel file e questo file è stato rinominato da qualcuno, ad esempio in olya.txt. Ma il file è rimasto lo stesso, e io voglio ancora scrivere il mio Tuzik in esso. Ogni volta che apro il file tramite una chiamata di sistema in qualsiasi linguaggio di programmazione ricevo un ID unico che mi indica il file, questo ID è il descrittore di file. E non importa cosa e chi fa con questo file in seguito; può essere eliminato, può essere rinominato, può essere cambiato il proprietario o può essere revocati i diritti di lettura e scrittura, io avrò sempre accesso, perché al momento dell'apertura del file avevo i diritti per leggerlo e/o scriverci, e ho iniziato a lavorarci, quindi devo continuare a farlo.
In Linux la libreria libc apre per ogni applicazione (processo) in esecuzione 3 descrittori di file, con i numeri 0, 1, 2. Maggiori informazioni possono essere trovate ai link e
- Il descrittore di file 0 si chiama STDIN ed è associato all'input dei dati dell'applicazione
- Il file descrittore 1 si chiama STDOUT e viene utilizzato dalle applicazioni per l'output dei dati, ad esempio dai comandi print.
- Il file descrittore 2 si chiama STDERR e viene utilizzato dalle applicazioni per l'output dei dati che segnalano errori.
Se nella tua programmazione apri un file per lettura o scrittura, probabilmente riceverai il primo ID libero e sarà il numero 3.
Puoi visualizzare l'elenco dei file descrittori di qualsiasi processo, se conosci il suo PID.
Ad esempio, apriamo una console con bash e visualizziamo il PID del nostro processo.
[user@localhost ]$ echo $$
15771
Nella seconda console avviamo
[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
Puoi ignorare il file descrittore con il numero 255 nel contesto di questo articolo, è stato aperto per i propri scopi da bash, non da una libreria collegata.
Attualmente, tutti e 3 i file descrittori sono collegati a un dispositivo pseudoterminale. , ma possiamo comunque manipolarli, ad esempio avviando nella seconda console
[user@localhost ]$ echo "hello world" > /proc/15771/fd/0
E nella prima console vedremo
[user@localhost ]$ hello world
Redirect e Pipe
Puoi facilmente sovrascrivere questi 3 file descrittori in qualsiasi processo, incluso bash, ad esempio tramite una pipe, che collega due processi, vediamo
[user@localhost ]$ cat /dev/zero | sleep 10000
Puoi eseguire tu stesso questo comando con strace -f e vedere cosa succede all'interno, ma te lo spiegherò brevemente.
Il nostro processo padre bash con PID 15771 analizza il nostro comando e comprende quante esatte comandi vogliamo eseguire, nel nostro caso sono due: cat e sleep. Bash sa che deve creare due processi figlio e collegarli con una pipe. In totale bash avrà bisogno di 2 processi figli e una pipe.
Prima di creare i processi figli, bash esegue una chiamata di sistema e ottiene nuovi file descrittori per il buffer temporaneo della pipe, ma questo buffer non collega ancora i nostri due processi figli.
Per il processo padre sembra che la pipe esista già, mentre i processi figli non ci sono ancora:
PID command
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
Poi, tramite una chiamata di sistema, bash crea due processi figli, e i nostri tre processi appariranno come segue:
PID comando
15771 bash
lrwx------ 1 user user 64 7 Ott 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 7 Ott 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 7 Ott 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 7 Ott 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7 Ott 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7 Ott 15:42 255 -> /dev/pts/21
PID comando
9004 bash
lrwx------ 1 user user 64 7 Ott 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 7 Ott 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 7 Ott 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 7 Ott 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7 Ott 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7 Ott 15:57 255 -> /dev/pts/21
PID comando
9005 bash
lrwx------ 1 user user 64 7 Ott 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 7 Ott 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 7 Ott 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 7 Ott 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7 Ott 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7 Ott 15:57 255 -> /dev/pts/21
Non dimentichiamo che clone clona il processo insieme a tutti i descrittori di file, quindi nel processo padre e in quelli figli saranno identici. Il compito del processo padre con PID 15771 è monitorare i processi figli, quindi attende semplicemente una risposta da questi ultimi.
Pertanto, non ha bisogno di pipe, e chiude i descrittori di file con i numeri 3 e 4.
Nel primo processo figlio bash con PID 9004, tramite una chiamata di sistema , cambia il nostro descrittore di file STDOUT con il numero 1 in un descrittore di file che punta a pipe, in questo caso è il numero 3. Così, tutto ciò che scriverà il primo processo figlio con PID 9004 in STDOUT andrà automaticamente nel buffer della pipe.
Nel secondo processo figlio con PID 9005, bash modifica tramite dup2 il descrittore di file STDIN con il numero 0. Ora, tutto ciò che leggerà il nostro secondo bash con PID 9005, verrà letto dalla pipe.
Dopo di ciò, anche nei processi figli vengono chiusi i descrittori di file con i numeri 3 e 4, poiché non sono più utilizzati.
Il descrittore di file 255 lo ignoro intenzionalmente, viene utilizzato per esigenze interne di bash e sarà chiuso anche nei processi figli.
Successivamente, nel primo processo figlio con PID 9004, bash avvia tramite una chiamata di sistema il file eseguibile che abbiamo indicato nella riga di comando, in questo caso è /usr/bin/cat.
Nel secondo processo figlio con PID 9005, bash avvia il secondo file eseguibile che abbiamo indicato, in questo caso è /usr/bin/sleep.
La chiamata di sistema exec non chiude i descrittori di file se non sono stati aperti con il flag O_CLOEXEC durante la chiamata open. Nel nostro caso, dopo l'avvio dei file eseguibili, tutti i descrittori di file attuali verranno mantenuti.
Controlliamo nella console:
[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
Come potete vedere, il numero unico del nostro pipe coincide in entrambi i processi. Pertanto, abbiamo una connessione tra due processi diversi con un solo genitore.
Per coloro che non sono familiari con le chiamate di sistema utilizzate da bash, consiglio vivamente di eseguire i comandi tramite strace e di vedere cosa succede all'interno, ad esempio in questo modo:
strace -s 1024 -f bash -c "ls | grep hello"
Torniamo al nostro problema di spazio insufficiente sul disco e al tentativo di salvare i dati senza riavviare il processo. Scriverò un piccolo programma che scriverà su disco circa 1 megabyte al secondo. Se per qualche motivo non riusciamo a scrivere i dati su disco, ignoreremo semplicemente questo e tenteremo di scrivere nuovamente i dati dopo un secondo. Nell'esempio utilizzo Python, potete usare qualsiasi altro linguaggio di programmazione.
[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
Avviamo il programma e guardiamo i descrittori di file
[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 7 ott 16:29 .
dr-xr-xr-x 9 user user 0 7 ott 16:29 ..
lrwx------ 1 user user 64 7 ott 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 7 ott 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 7 ott 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 7 ott 16:29 3 -> /home/user/123.txt
Come possiamo vedere, abbiamo i nostri 3 descrittori di file standard e un ulteriore uno che abbiamo aperto. Controlliamo la dimensione del file:
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 117M 7 ott 16:30 123.txt
i dati stanno venendo scritti, proviamo a cambiare i diritti sul file:
[user@localhost ]$ sudo chown root: 123.txt
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 root root 168M 7 ott 16:31 123.txt
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 root root 172M 7 ott 16:31 123.txt
Possiamo vedere che i dati stanno ancora venendo scritti, anche se il nostro utente non ha il permesso di scrivere nel file. Proviamo a eliminarlo:
[user@localhost ]$ sudo rm 123.txt
[user@localhost ]$ ls 123.txt
ls: impossibile accedere a 123.txt: File o directory non esistente
Dove vengono scritti i dati? E vengono scritti davvero? Controlliamo:
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user 0 7 ott 16:29 .
dr-xr-xr-x 9 user user 0 7 ott 16:29 ..
lrwx------ 1 user user 64 7 ott 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 7 ott 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 7 ott 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 7 ott 16:29 3 -> /home/user/123.txt (eliminato)
Sì, il nostro descrittore di file esiste ancora, e possiamo interagire con questo descrittore di file come se fosse il nostro vecchio file, possiamo leggerlo, svuotarlo e copiarlo.
Controlliamo la dimensione del file:
[user@localhost ]$ lsof | grep 123.txt
python 31083 user 3w REG 8,5 19923457 2621522 /home/user/123.txt
La dimensione del file è 19923457. Proviamo a svuotare il file:
[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
Come vediamo, la dimensione del file continua ad aumentare e il nostro truncate non ha avuto effetto. Rivoltiamoci alla documentazione sulla syscall . Se durante l'apertura del file utilizziamo il flag O_APPEND, ad ogni scrittura, il sistema operativo controlla la dimensione del file e scrive i dati alla fine del file, e lo fa in modo atomico. Ciò consente a più thread o processi di scrivere nello stesso file. Ma nel nostro codice non usiamo questo flag. Possiamo vedere una dimensione diversa del file in lsof dopo il truncate solo se apriamo il file per l'annessione, e quindi nel nostro codice invece di
with open("123.txt", "w") as f:
dobbiamo mettere
with open("123.txt", "a") as f:
Controlliamo con il 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
e con il flag «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
Programmiamo un processo già avviato
Spesso gli sviluppatori utilizzano debugger (come GDB) o diversi livelli di logging nell'applicazione durante la creazione e il testing del programma. Linux offre la possibilità di scrivere e modificare effettivamente un programma già in esecuzione, ad esempio cambiando i valori delle variabili, impostando un breakpoint, ecc.
Tornando alla questione originale della mancanza di spazio su disco per scrivere il file, cerchiamo di simulare il problema.
Creiamo un file per la nostra partizione, che monteremo come un disco separato:
[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 ~]$
Creiamo un filesystem:
[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd is not a block special device.
Proceed anyway? (y,n) y
...
Writing superblocks and filesystem accounting information: done
[user@localhost ~]$
Montiamo il filesystem:
[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] password for user:
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 172K 7.9M 3% /mnt
Creiamo una directory con il nostro proprietario:
[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs
Apriamo il file solo per scrittura nel nostro programma:
with open("/mnt/logs/123.txt", "w") as f:
Avviamo
[user@localhost ]$ python openforwrite.py
Aspettiamo alcuni secondi
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 8.0M 0 100% /mnt
Quindi, abbiamo ottenuto il problema descritto all'inizio di questo articolo. Spazio libero 0, occupato 100%.
Ricordiamo che, secondo le condizioni del compito, stiamo cercando di scrivere dati molto importanti, che non possono essere persi. E nel frattempo dobbiamo riparare il servizio senza riavviare il processo.
Supponiamo che abbiamo comunque spazio su disco, ma in un'altra partizione, ad esempio in /home.
Cerchiamo di "riprogammare al volo" il nostro codice.
Controlliamo il PID del nostro processo, che ha occupato tutto lo spazio su 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
Colleghiamoci al processo tramite gdb
[user@localhost ~]$ gdb -p 10078
...
(gdb)
Controlliamo i file descriptor aperti:
(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
Controlliamo le informazioni sul file descriptor numero 3, che ci interessa
(gdb) shell cat /proc/10078/fdinfo/3
pos: 8189952
flags: 0100001
mnt_id: 482
Ricordando quale chiamata di sistema esegue Python (vedi sopra, dove abbiamo eseguito strace e trovato la chiamata open), mentre elaboriamo il nostro codice per aprire un file, facciamo la stessa cosa noi stessi a nome del nostro processo, ma dobbiamo sostituire i bit O_WRONLY|O_CREAT|O_TRUNC con un valore numerico. Per questo apriamo i sorgenti del kernel, ad esempio e vediamo quali flag cosa rappresentano
#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000
Uniamo tutti i valori in uno, otteniamo 00001101
Avviamo la nostra chiamata da gdb
(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4
Quindi abbiamo ottenuto un nuovo descrittore di file con numero 4 e un nuovo file aperto su un'altra partizione, verifichiamo:
(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
Ricordiamo l'esempio con pipe — come bash modifica i descrittori di file, e abbiamo già imparato la chiamata di sistema dup2.
Proviamo a sostituire un descrittore di file con un altro
(gdb) call dup2(4,3)
$2 = 3
Controlliamo:
(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
Chiudiamo il descrittore di file 4, poiché non ci serve più:
(gdb) call close (4)
$1 = 0
E usciamo da 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
Controlliamo il nuovo file:
[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
Come vediamo, i dati vengono scritti nel nuovo file, verifichiamo il vecchio:
[user@localhost ~]$ ls -lah /mnt/logs/123.txt
-rw-rw-r-- 1 user user 7.9M Oct 8 11:08 /mnt/logs/123.txt
I dati non sono stati persi, l'applicazione è in esecuzione, i log vengono scritti in un nuovo luogo.
Complicheremo un po' il compito
Immaginiamo che i dati siano importanti per noi, ma non abbiamo spazio su disco in nessuna partizione e non possiamo collegare un'unità.
Quello che possiamo fare è reindirizzare i nostri dati da qualche parte, ad esempio in una pipe, e i dati dalla pipe a loro volta reindirizzare in rete tramite un programma, come netcat.
Possiamo creare una pipe nominata con il comando mkfifo. Questo creerà un file pseudo sulla filesystem, anche se non c'è spazio disponibile su di essa.
Riavviamo l'applicazione e controlliamo:
[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 8 ott 11:27 .
dr-xr-xr-x 9 user user 0 8 ott 11:27 ..
lrwx------ 1 user user 64 8 ott 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 8 ott 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 8 ott 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 8 ott 11:28 3 -> /mnt/logs/123.txt
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 8.0M 0 100% /mnt
Non c'è spazio su disco, ma creiamo con successo un pipe nominato lì:
[user@localhost ~]$ mkfifo /mnt/logs/megapipe
[user@localhost ~]$ ls -lah /mnt/logs/megapipe
prw-rw-r-- 1 user user 0 8 ott 11:28 /mnt/logs/megapipe
Ora dobbiamo in qualche modo incapsulare tutti i dati che arrivano in questo pipe su un altro server attraverso la rete, per questo ci serve sempre netcat.
Sul server remote-server.example.com avviamo
[user@localhost ~]$ nc -l 7777 > 123.txt
Sul nostro server problematico avviamo in un terminale separato
[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe
Ora tutti i dati che arriveranno nel pipe verranno automaticamente inviati allo stdin di netcat, che li manderà in rete sulla porta 7777.
Tutto ciò che ci resta da fare è iniziare a scrivere i nostri dati in questo pipe nominato.
Abbiamo già un'applicazione in esecuzione:
[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 8 ott 11:27 .
dr-xr-xr-x 9 user user 0 8 ott 11:27 ..
lrwx------ 1 user user 64 8 ott 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 8 ott 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 8 ott 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 8 ott 11:28 3 -> /mnt/logs/123.txt
Di tutti i flag ci serve solo O_WRONLY poiché il file esiste già e non è necessario cancellarlo
[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 8 ott 11:27 .
dr-xr-xr-x 9 user user 0 8 ott 11:27 ..
lrwx------ 1 user user 64 8 ott 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 8 ott 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 8 ott 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 8 ott 11:28 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 8 ott 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 8 ott 11:27 .
dr-xr-xr-x 9 user user 0 8 ott 11:27 ..
lrwx------ 1 user user 64 8 ott 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 8 ott 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 8 ott 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 8 ott 11:28 3 -> /mnt/logs/megapipe
l-wx------ 1 user user 64 8 ott 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 8 ott 11:27 .
dr-xr-xr-x 9 user user 0 8 ott 11:27 ..
lrwx------ 1 user user 64 8 ott 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 8 ott 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 8 ott 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 8 ott 11:28 3 -> /mnt/logs/megapipe
(gdb) quit
Una sessione di debug è attiva.
Inferiore 1 [processo 5946] sarà scollegato.
Vuoi uscire comunque? (y o n) y
Scollegamento dal programma: /usr/bin/python2.7, processo 5946
Controlliamo il server remoto remote-server.example.com
[user@localhost ~]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 38M 8 ott 14:21 123.txt
I dati stanno arrivando, controlliamo il server problematico
[user@localhost ~]$ ls -lah /mnt/logs/
total 7.9M
drwxr-xr-x 2 user user 1.0K 8 ott 11:28 .
drwxr-xr-x 4 root root 1.0K 8 ott 10:55 ..
-rw-rw-r-- 1 user user 7.9M 8 ott 14:17 123.txt
prw-rw-r-- 1 user user 0 8 ott 14:22 megapipe
I dati sono stati salvati, il problema è risolto.
Colgo l'occasione per salutare i colleghi dell'azienda Degiro.
Ascoltate i podcast di Radio-T.
A tutti, buona giornata.
Come compito a casa, vi propongo di riflettere su cosa conterranno i file dei descrittori del processo cat e sleep se eseguiamo questo comando:
[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000
Fonte: habr.com
