File descriptor in Linux con esempi

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 open 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 man stdio e man stdout

  • 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. /dev/pts, 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 pipe 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, clone 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 dup2, 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 exec 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 open. 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 qui 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

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS - ProHoster