Una volta, durante un colloquio, mi è stato chiesto cosa faresti se scoprissi un servizio non funzionante perché lo spazio su disco è esaurito.
Naturalmente ho risposto che avrei controllato cosa occupa quello spazio e, se possibile, libererei spazio.
Allora l'intervistatore ha chiesto: e se non ci fosse spazio libero sulla partizione, ma non vedi nemmeno file che occupano tutto lo spazio?
Ho risposto che si possono sempre controllare i file descriptor aperti, ad esempio con il comando lsof, e capire quale applicazione ha occupato tutto lo spazio disponibile; dopodiché si può agire in base alle circostanze, a seconda se i dati siano necessari.
L'intervistatore mi ha interrotto sull'ultima parola, completando la sua domanda: «Supponiamo che i dati non ci servano, è solo un log di debug, ma l'applicazione non funziona perché non riesce a 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 clienti importanti sono collegati al servizio, ai quali non possiamo far chiedere di riconnettersi di nuovo».
«Va bene», dissi, «se non possiamo riavviare l'applicazione e i dati non sono importanti, possiamo semplicemente cancellare questo file aperto tramite il file descriptor, anche se non lo vediamo nel comando ls nel filesystem».
L'intervistatore era soddisfatto, ma io no.
Allora mi sono chiesto perché la persona che controlla le mie conoscenze non scava più a fondo? E se i dati fossero importanti? E se non potessimo riavviare il processo, e questo processo scrive sul filesystem in una partizione che non ha spazio libero? E se non potessimo perdere solo i dati già scritti, ma anche quelli che questo processo sta scrivendo o cerca di scrivere?
Tuzik
All'inizio della mia carriera, ho cercato di creare una piccola applicazione in cui fosse necessario conservare informazioni sugli utenti. E allora mi chiedevo come potessi abbinare un utente ai suoi dati. Per esempio, ho Ivanov Ivan Ivanovich, e lui ha alcuni dati, ma come posso legarli insieme? Posso indicare direttamente che un cane di nome "Tuzik" appartiene a questo Ivan. Ma cosa succede se lui cambia nome e diventa, per esempio, Olya? Allora avremo 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ù. Risolvere questo problema è stata la base di dati, che assegnava a ogni utente un identificatore unico (ID), e il mio Tuzik era collegato a questo ID, che, in sostanza, era solo un numero di sequenza. Così, il padrone di Tuzik aveva ID numero 2, e per un certo periodo di tempo questo ID apparteneva a Ivan, e poi divenne Olya. Il problema dell'umanità e dell'allevamento era praticamente risolto.
File descriptor
Il problema di un file e di un programma che lavora con questo file è simile a quello tra un nostro cane e un essere umano. Supponiamo di aver aperto un file chiamato ivan.txt e di aver iniziato a scrivere la parola tuzik, ma di aver scritto solo la prima lettera «t» nel file, e questo file è stato rinominato da qualcuno, ad esempio in olya.txt. Ma il file rimane lo stesso, e io voglio ancora scrivere del mio tuzik. Ogni volta che apro il file con una chiamata di sistema in qualsiasi linguaggio di programmazione ricevo un ID unico che indica il file, questo ID è il descrittore del file. E non importa cosa e chi faccia con questo file dopo, può essere eliminato, può essere rinominato, può cambiare proprietario o perderne i diritti di lettura e scrittura; avrò comunque accesso, perché al momento dell'apertura del file avevo i diritti per leggerlo e/o scriverlo e ho cominciato a lavorarci sopra, quindi devo continuare a farlo.
In Linux, la libreria libc apre 3 descrittori di file per ogni applicazione (processo) in esecuzione, numerati 0, 1, 2. Maggiori informazioni possono essere trovate ai seguenti link e
- Il file descriptor 0 si chiama STDIN ed è associato all'input dati dell'applicazione.
- Il file descriptor 1 si chiama STDOUT e viene utilizzato dalle applicazioni per l'output dei dati, ad esempio dai comandi print.
- Il file descriptor 2 si chiama STDERR e viene utilizzato dalle applicazioni per l'output dei dati relativi agli errori.
Se nella tua programma apri un file per la lettura o la scrittura, è probabile che tu riceva il primo ID disponibile e questo sarà il numero 3.
Puoi visualizzare l'elenco dei file descriptor di qualsiasi processo, se conosci il suo PID.
Ad esempio, apriamo una console con bash e vediamo il PID del nostro processo.
[user@localhost ]$ echo $$
15771
Nella seconda console eseguiamo
[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
Il file descriptor con numero 255 può essere tranquillamente ignorato in questo articolo, è stato aperto per le sue necessità da bash stesso, non dalla libreria collegata.
Attualmente, tutti e 3 i file descriptor sono collegati al 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 descrittori di file 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 ti verrò a parlare brevemente.
Il nostro processo padre bash con PID 15771 analizza il nostro comando e comprende quante esattamente comandi vogliamo avviare; nel nostro caso sono due: cat e sleep. Bash sa di dover creare due processi figli e collegarli tramite 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 descrittori di file su un buffer temporaneo della pipe, ma questo buffer non collega ancora i nostri due processi figli.
Per il processo padre sembra che la pipe sia già presente, mentre i processi figli non ci sono ancora:
PID comando
15771 bash
lrwx------ 1 user user 64 Ott 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Ott 7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Ott 7 15:42 255 -> /dev/pts/21
Poi, attraverso una chiamata di sistema bash crea due processi figli, e i nostri tre processi appariranno così:
PID comando
15771 bash
lrwx------ 1 user user 64 Ott 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Ott 7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Ott 7 15:42 255 -> /dev/pts/21
PID comando
9004 bash
lrwx------ 1 user user 64 Ott 7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Ott 7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Ott 7 15:57 255 -> /dev/pts/21
PID comando
9005 bash
lrwx------ 1 user user 64 Ott 7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Ott 7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Ott 7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Ott 7 15:57 255 -> /dev/pts/21
Ricordiamo che clone clona il processo insieme a tutte le descrizioni di file, quindi nel processo genitore e in quelli figli saranno identiche. Il compito del processo genitore con PID 15771 è monitorare i processi figli, quindi attende semplicemente una risposta da questi ultimi.
Pertanto, il pipe non è necessario, e chiude le descrizioni dei file ai numeri 3 e 4.
Nel primo processo figlio bash con PID 9004, attraverso la chiamata di sistema , cambia la nostra descrizione di file STDOUT con il numero 1 su una descrizione di file che punta al pipe, che nel nostro caso è il numero 3. Così, tutto ciò che il primo processo figlio con PID 9004 scriverà in STDOUT finirà automaticamente nel buffer del pipe.
Nel secondo processo figlio con PID 9005, bash cambia con dup2 la descrizione di file STDIN con il numero 0. Ora, tutto ciò che il nostro secondo bash con PID 9005 leggerà, verrà letto dal pipe.
Dopo ciò, anche nei processi figli vengono chiuse le descrizioni di file ai numeri 3 e 4, poiché non sono più necessarie.
La descrizione di file 255 la ignoro intenzionalmente, viene usata per le esigenze interne di bash stesso e sarà chiusa anche nei processi figli.
Successivamente, nel primo processo figlio con PID 9004, bash avvia tramite una chiamata di sistema il file eseguibile che abbiamo specificato nella riga di comando, nel nostro caso è /usr/bin/cat.
Nel secondo processo figlio con PID 9005, bash avvia il secondo file eseguibile che abbiamo specificato, nel nostro 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 l'esecuzione della chiamata open. Nel nostro caso, dopo l'avvio dei file eseguibili, tutti i descrittori di file attuali rimarranno aperti.
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 genitore comune.
Per chi non è familiare con le chiamate di sistema utilizzate da bash, consiglio vivamente di eseguire i comandi tramite strace per vedere cosa succede internamente, ad esempio in questo modo:
strace -s 1024 -f bash -c "ls | grep hello"
Torniamo al nostro problema di mancanza di spazio su disco e del tentativo di salvare dati senza riavviare il processo. Scriveremo un piccolo programma che scrive su disco circa 1 megabyte al secondo. Se per qualsiasi motivo non riusciamo a scrivere i dati su disco, ignoreremo semplicemente questo e cercheremo di scrivere nuovamente i dati dopo un secondo. Nell'esempio userò Python, ma puoi 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 file descrittori
[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
Come possiamo vedere, abbiamo i nostri 3 descrittori di file standard e uno in più che abbiamo aperto. Controlliamo la dimensione del file:
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 117M Oct 7 16:30 123.txt
I dati vengono scritti, proviamo a cambiare i permessi del file:
[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
Possiamo notare che i dati continuano a essere scritti, anche se il nostro utente non ha i permessi per scrivere nel file. Proviamo a eliminarlo:
[user@localhost ]$ sudo rm 123.txt
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory
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 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ì, il nostro file descriptor esiste ancora e possiamo lavorare con questo file descriptor 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 aumenta e il nostro truncate non ha funzionato. Rivoltiamoci alla documentazione della chiamata di sistema. Se usiamo il flag O_APPEND quando apriamo un file, ogni scrittura verifica la dimensione del file e scrive i dati alla fine del file, facendo questo 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 un'altra dimensione del file in lsof dopo il truncate solo se apriamo il file per l'aggiunta, quindi nel nostro codice invece di
with open("123.txt", "w") as f:
dobbiamo impostare
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à in esecuzione
Spesso i programmatori utilizzano debugger (come GDB) o diversi livelli di log nella loro applicazione durante la creazione e il test del programma. Linux offre la possibilità di scrivere e modificare effettivamente un programma già in esecuzione, ad esempio cambiando i valori delle variabili, impostando breakpoint, ecc.
Tornando alla domanda originale sulla mancanza di spazio su disco per scrivere un file, proviamo a simulare il problema.
Creeremo 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 ~]$
Creeremo un file system:
[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 file system:
[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
Apriremo il file solo in modalità scrittura nel nostro programma:
with open("/mnt/logs/123.txt", "w") as f:
Avviamo
[user@localhost ]$ python openforwrite.py
Aspettiamo qualche secondo
[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 problema, stiamo cercando di scrivere dati molto importanti che non possiamo perdere. E nel contempo dobbiamo riparare il servizio senza riavviare il processo.
Supponiamo di avere comunque spazio su disco, ma in un'altra partizione, ad esempio in /home.
Proviamo a "riprogrammare al volo" il nostro codice.
Guardiamo il PID del nostro processo che ha consumato 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)
Guardiamo i descrittori di file 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
Visualizziamo le informazioni sul descrittore di file numero 3, che ci interessa.
(gdb) shell cat /proc/10078/fdinfo/3
pos: 8189952
flags: 0100001
mnt_id: 482
Tenendo a mente quale chiamata di sistema effettua Python (vedi sopra, dove abbiamo eseguito strace e trovato la chiamata open), gestendo il nostro codice per aprire un file, facciamo la stessa cosa autonomamente a nome del nostro processo, ma dobbiamo sostituire i bit O_WRONLY|O_CREAT|O_TRUNC con un valore numerico. Per questo apriamo il codice sorgente del kernel, ad esempio e vediamo quali flag corrispondono a cosa.
#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000
Combiniamo tutti i valori in uno, otteniamo 00001101.
Eseguiamo 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 altro volume, controlliamo:
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 8 Ott 11:06 .
dr-xr-xr-x 9 user user 0 8 Ott 11:06 ..
lrwx------ 1 user user 64 8 Ott 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 8 Ott 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 8 Ott 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 8 Ott 11:09 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 8 Ott 11:15 4 -> /home/user/123.txt
Ricordiamo l'esempio con pipe — come bash modifica i descrittori di file e abbiamo già appreso la syscall dup2.
Proviamo a sostituire un descrittore di file con un altro
(gdb) call dup2(4,3)
$2 = 3
Controllando:
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 8 Ott 11:06 .
dr-xr-xr-x 9 user user 0 8 Ott 11:06 ..
lrwx------ 1 user user 64 8 Ott 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 8 Ott 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 8 Ott 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 8 Ott 11:09 3 -> /home/user/123.txt
l-wx------ 1 user user 64 8 Ott 11:15 4 -> /home/user/123.txt
Chiudiamo il descrittore di file 4 poiché non ci serve:
(gdb) call close(4)
$1 = 0
E usciamo da gdb
(gdb) quit
A debugging session is active.
Inferior 1 [processo 10078] verrà scollegato.
Esci comunque? (y o n) y
Scollegamento dal programma: /usr/bin/python2.7, processo 10078
Controlliamo il nuovo file:
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 5.1M 8 Ott 11:18 /home/user/123.txt
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 7.1M 8 Ott 11:18 /home/user/123.txt
Come vediamo, i dati vengono scritti nel nuovo file, controlliamo il vecchio:
[user@localhost ~]$ ls -lah /mnt/logs/123.txt
-rw-rw-r-- 1 user user 7.9M 8 Ott 11:08 /mnt/logs/123.txt
I dati non sono stati persi, l'applicazione è in funzione e i log vengono scritti in un nuovo luogo.
Rendiamo le cose un po' più complicate.
Immaginiamo che i dati siano importanti, ma non abbiamo spazio sul disco in nessuna delle partizioni e non possiamo collegare un disco.
Ciò che possiamo fare è ridirigere i nostri dati da qualche parte, ad esempio in un pipe, e i dati dal pipe possono essere a loro volta ridiretti nella rete tramite un programma come netcat.
Possiamo creare un pipe nominato con il comando mkfifo. Questo creerà un pseudo-file nel file system, anche se non c'è spazio libero su di esso.
Riavviamo l'applicazione e verifichiamo:
[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
Non c'è spazio sul disco, ma riusciamo a creare un pipe nominato:
[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
Ora dobbiamo in qualche modo incapsulare tutti i dati che entrano in questo pipe su un altro server attraverso la rete, per questo possiamo usare 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 entreranno nel pipe passeranno automaticamente allo stdin in netcat, che li invierà in rete sulla porta 7777.
Ciò che rimane da fare è iniziare a scrivere i nostri dati in questo pipe denominato.
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 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
Tra tutti i flag abbiamo bisogno solo di O_WRONLY poiché il file esiste già e non dobbiamo pulirlo.
[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
Controlliamo il server remoto remote-server.example.com
[user@localhost ~]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 38M Oct 8 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 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
I dati sono stati salvati, il problema è risolto.
Colgo l'occasione per salutare i colleghi della Degiro.
Ascolta i podcast di Radio-T.
A tutti buona giornata.
Come compito a casa, vi propongo di pensare a cosa ci sarà nei file dei descrittori del processo cat e sleep se si esegue il seguente comando:
[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000
Fonte: habr.com
