Descrittore di file in Linux con esempi

Una volta, durante un'intervista, mi è stato chiesto, cosa farai se trovi un servizio che non funziona perché il disco ha esaurito lo spazio?

Naturalmente ho risposto che avrei visto cosa era occupato da questo posto e, se possibile, avrei pulito il posto.
Quindi l'intervistatore ha chiesto, cosa succede se non c'è spazio libero sulla partizione, ma non vedi nemmeno alcun file che occuperebbe tutto lo spazio?

A questo ho detto che puoi sempre guardare i descrittori di file aperti, ad esempio con il comando lsof, e capire quale applicazione ha occupato tutto lo spazio disponibile, e poi puoi agire a seconda delle circostanze, a seconda che i dati siano necessari o meno. .

L’intervistatore mi ha interrotto sull’ultima parola, aggiungendo alla sua domanda: “Supponiamo che non abbiamo bisogno dei dati, è solo un registro di debug, ma l’applicazione non funziona perché non può scrivere un debug”?

"Va bene", ho risposto, "possiamo disattivare il debug nella configurazione dell'applicazione e riavviarlo."
L'intervistatore ha obiettato: "No, non possiamo riavviare l'applicazione, abbiamo ancora dati importanti archiviati in memoria e client importanti sono collegati al servizio stesso, che non possiamo forzare a riconnettersi di nuovo".

"ok", dissi, "se non possiamo riavviare l'applicazione e i dati non sono importanti per noi, allora possiamo semplicemente cancellare questo file aperto tramite il descrittore di file, anche se non lo vediamo nel comando ls sul file system."

L'intervistatore era contento, ma io no.

Poi ho pensato, perché la persona che mette alla prova le mie conoscenze non approfondisce? Ma cosa succede se dopo tutto i dati sono importanti? Cosa succede se non riusciamo a riavviare un processo e il processo scrive nel file system su una partizione che non ha spazio libero? E se non potessimo perdere non solo i dati che sono già stati scritti, ma anche i dati che questo processo scrive o tenta di scrivere?

dinghy

All'inizio della mia carriera, ho provato a creare una piccola applicazione che doveva memorizzare le informazioni dell'utente. E poi ho pensato, come posso abbinare l'utente ai suoi dati. Ad esempio, ho Ivanov Ivan Ivanovich e lui ha alcune informazioni, ma come posso fare amicizia con loro? Posso sottolineare direttamente che il cane chiamato "Tuzik" appartiene proprio a questo Ivan. E se cambiasse nome e invece di Ivan diventasse, ad esempio, Olya? Allora si scoprirà che la nostra Olya Ivanovna Ivanova non avrà più un cane e il nostro Tuzik apparterrà ancora all'inesistente Ivan. Un database che forniva a ciascun utente un identificatore univoco (ID) ha aiutato a risolvere questo problema e il mio Tuzik era legato a questo ID, che, in realtà, era solo un numero di serie. Pertanto, il proprietario dell'asso aveva l'ID numero 2, e ad un certo punto Ivan era sotto questo ID, e poi Olya divenne sotto lo stesso ID. Il problema dell'umanità e dell'allevamento degli animali è stato praticamente risolto.

Descrittore di file

Il problema del file e del programma che funziona con questo file è più o meno lo stesso di quello del nostro cane e del nostro uomo. Supponiamo che io abbia aperto un file chiamato ivan.txt e abbia iniziato a scrivere la parola tuzik al suo interno, ma sia riuscito a scrivere solo la prima lettera "t" nel file e questo file sia stato rinominato da qualcuno, ad esempio, in olya.txt. Ma il file rimane lo stesso e voglio ancora registrarci il mio asso. Ogni volta che un file viene aperto da una chiamata di sistema aprire in qualsiasi linguaggio di programmazione ricevo un ID univoco che mi indirizza a un file, questo ID è il descrittore del file. E non importa cosa e chi farà dopo questo file, può essere cancellato, può essere rinominato, il proprietario può essere cambiato o i diritti di lettura e scrittura possono essere tolti, avrò comunque accesso ad esso, perché al momento dell'apertura del file avevo i diritti di lettura e/o scrittura e sono riuscito a iniziare a lavorarci, il che significa che devo continuare a farlo.

In Linux, la libreria libc apre 3 file descrittori per ciascuna applicazione (processo) in esecuzione, numerati 0,1,2. Maggiori informazioni possono essere trovate sui link uomo, studio и uomo stdout

  • Il descrittore di file 0 si chiama STDIN ed è associato all'input dell'applicazione
  • Il descrittore di file 1 si chiama STDOUT e viene utilizzato dalle applicazioni per inviare dati in output, come i comandi di stampa
  • Il descrittore di file 2 si chiama STDERR e viene utilizzato dalle applicazioni per visualizzare messaggi di errore.

Se nel tuo programma apri un file in lettura o scrittura, molto probabilmente otterrai il primo ID libero e sarà il numero 3.

L'elenco dei descrittori di file può essere visualizzato per qualsiasi processo se ne conosci il PID.

Ad esempio, apriamo la console bash e osserviamo il PID del nostro processo

[user@localhost ]$ echo $$
15771

Nella seconda console corriamo

[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 tranquillamente ignorare il descrittore di file numero 255 per gli scopi di questo articolo; è stato aperto per le sue esigenze da bash stesso e non dalla libreria collegata.

Ora tutti e 3 i file descrittori sono associati al dispositivo pseudo terminale /dev/pt, ma possiamo ancora manipolarli, ad esempio eseguirli su una seconda console

[user@localhost ]$ echo "hello world" > /proc/15771/fd/0

E nella prima console vedremo

[user@localhost ]$ hello world

Reindirizzamento e pipe

Puoi facilmente sovrascrivere questi 3 file descrittori in qualsiasi processo, incluso in bash, ad esempio attraverso una pipe che collega due processi, vedi

[user@localhost ]$ cat /dev/zero | sleep 10000

Puoi eseguire tu stesso questo comando con strace -f e vedere cosa succede dentro, ma te lo dirò brevemente.

Il nostro processo bash genitore con PID 15771 analizza il nostro comando e capisce esattamente quanti comandi vogliamo eseguire, nel nostro caso ce ne sono due: cat e sleep. Bash sa che deve creare due processi figli e unirli in un'unica pipe. In totale, bash avrà bisogno di 2 processi figli e di una pipe.

Bash esegue una chiamata di sistema prima di creare processi figli tubo e riceve nuovi descrittori di file sul buffer temporaneo della pipe, ma questo buffer non connette ancora i nostri due processi figli.

Per il processo genitore, sembra che ci sia già una pipe, ma non ci sono ancora processi figli:

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

Quindi utilizzando la chiamata di sistema clonare bash crea due processi figli e i nostri tre processi saranno simili a questi:

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
PID    command
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    command
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

Non dimenticare che clone clona il processo insieme a tutti i descrittori di file, quindi saranno gli stessi nel processo genitore e in quelli figli. Il compito del processo genitore con PID 15771 è monitorare i processi figli, quindi attende semplicemente una risposta dai figli.

Pertanto non necessita di pipe e chiude i descrittori di file numerati 3 e 4.

Nel primo processo bash figlio con PID 9004, la chiamata di sistema dup2, cambia il nostro descrittore di file STDOUT numero 1 in un descrittore di file che punta alla pipe, nel nostro caso è il numero 3. Pertanto, tutto ciò che il primo processo figlio con PID 9004 scrive su STDOUT finirà automaticamente nel buffer della pipe.

Nel secondo processo figlio con PID 9005, bash utilizza dup2 per modificare il descrittore di file STDIN numero 0. Ora tutto ciò che leggerà la nostra seconda bash con PID 9005 verrà letto dalla pipe.

Successivamente, anche i descrittori di file numerati 3 e 4 vengono chiusi nei processi figli, poiché non vengono più utilizzati.

Ignoro deliberatamente il descrittore di file 255; viene utilizzato per scopi interni dalla stessa bash e verrà chiuso anche nei processi figli.

Successivamente, nel primo processo figlio con PID 9004, bash inizia a utilizzare una chiamata di sistema exec il file eseguibile che abbiamo specificato sulla riga di comando, nel nostro caso è /usr/bin/cat.

Nel secondo processo figlio con PID 9005, bash esegue il secondo eseguibile specificato, nel nostro caso /usr/bin/sleep.

La chiamata di sistema exec non chiude gli handle di file a meno che non siano stati aperti con il flag O_CLOEXEC nel momento in cui è stata effettuata la chiamata di apertura. Nel nostro caso, dopo aver avviato i file eseguibili, verranno salvati tutti i descrittori di file correnti.

Controlla 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 puoi vedere, il numero univoco della nostra pipa è lo stesso in entrambi i processi. Quindi abbiamo una connessione tra due diversi processi con lo stesso genitore.

Per coloro che non hanno familiarità con le chiamate di sistema utilizzate da bash, consiglio vivamente di eseguire i comandi tramite strace e vedere cosa sta succedendo internamente, ad esempio in questo modo:

strace -s 1024 -f bash -c "ls | grep hello"

Torniamo al nostro problema di esaurire lo spazio su disco e provare a salvare i dati senza riavviare il processo. Scriviamo un piccolo programma che scriverà circa 1 megabyte al secondo su disco. Inoltre, se per qualche motivo non fossimo in grado di scrivere i dati sul disco, lo ignoreremo e proveremo a scrivere nuovamente i dati tra un secondo. Nell'esempio in cui sto usando Python, 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

Eseguiamo 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 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 puoi vedere, abbiamo i nostri 3 descrittori di file standard e un altro 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 sono in scrittura, proviamo a cambiare i permessi sul 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

Vediamo che i dati sono ancora in fase di scrittura, sebbene il nostro utente non abbia il permesso di scrivere nel file. Proviamo a rimuoverlo:

[user@localhost ]$ sudo rm 123.txt 
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory

Dove sono scritti i dati? E sono scritti? 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 descrittore di file esiste ancora e possiamo trattarlo come il nostro vecchio file, possiamo leggerlo, cancellarlo e copiarlo.

Diamo un'occhiata alla 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 cancellare 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 puoi vedere, la dimensione del file non fa che aumentare e il nostro trunk non ha funzionato. Diamo un'occhiata alla documentazione delle chiamate di sistema aprire. Se utilizziamo il flag O_APPEND quando apriamo un file, ad ogni scrittura il sistema operativo controlla la dimensione del file e scrive i dati fino alla fine del file, e lo fa in modo atomico. Ciò consente a più thread o processi di scrivere sullo stesso file. Ma nel nostro codice non usiamo questo flag. Possiamo vedere una dimensione del file diversa in lsof dopo trunk solo se apriamo il file per ulteriore scrittura, ovvero nel nostro codice

with open("123.txt", "w") as f:

dobbiamo mettere

with open("123.txt", "a") as f:

Controllo 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 la bandiera "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

Programmazione di un processo già in esecuzione

Spesso i programmatori, durante la creazione e il test dei programmi, utilizzano debugger (ad esempio GDB) o vari livelli di registrazione nell'applicazione. Linux offre la possibilità di scrivere e modificare effettivamente un programma già in esecuzione, ad esempio modificare i valori delle variabili, impostare un punto di interruzione, ecc., Ecc.

Tornando alla domanda iniziale relativa allo spazio su disco insufficiente per scrivere un file, proviamo a simulare il problema.

Creiamo un file per la nostra partizione, che monteremo come 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 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 ~]$

Montare 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

Apriamo il file in scrittura solo nel nostro programma:

with open("/mnt/logs/123.txt", "w") as f:

Lanciamo

[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 il problema descritto all'inizio di questo articolo. Spazio libero 0, occupato al 100%.

Ricordiamo che, in base alle condizioni dell'attività, stiamo cercando di registrare dati molto importanti che non possono essere persi. E allo stesso tempo dobbiamo riparare il servizio senza riavviare il processo.

Diciamo che abbiamo ancora spazio su disco, ma in una partizione diversa, ad esempio in /home.

Proviamo a “riprogrammare al volo” il nostro codice.

Diamo un'occhiata al 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

Connettersi al processo tramite gdb

[user@localhost ~]$ gdb -p 10078
...
(gdb) 

Diamo un'occhiata ai 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

Esaminiamo 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 presente quale chiamata di sistema fa Python (vedi sopra dove abbiamo eseguito strace e trovato la chiamata aperta), quando elaboriamo il nostro codice per aprire un file, facciamo lo stesso noi stessi per conto del nostro processo, ma abbiamo bisogno di O_WRONLY|O_CREAT| I bit O_TRUNC vengono sostituiti con un valore numerico. Per fare ciò, aprire ad esempio i sorgenti del kernel qui e guarda quali bandiere sono responsabili di cosa

#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000

Combiniamo tutti i valori in uno solo, otteniamo 00001101

Eseguiamo la nostra chiamata da gdb

(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4

Quindi abbiamo un nuovo descrittore di file con il numero 4 e un nuovo file aperto su un'altra partizione, 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 -> /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:

(gdb) call close (4)
$1 = 0

Ed esci 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

Controllo del 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 puoi vedere, i dati vengono scritti in un nuovo file, controlliamo quello 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

Nessun dato viene perso, l'applicazione funziona, i registri vengono scritti in una nuova posizione.

Complichiamo un po' il compito

Immaginiamo che i dati siano importanti per noi, ma non abbiamo spazio su disco in nessuna delle partizioni e non possiamo connettere il disco.

Quello che possiamo fare è reindirizzare i nostri dati da qualche parte, ad esempio su pipe, e a sua volta reindirizzare i dati dalla pipe alla rete attraverso qualche programma, ad esempio netcat.
Possiamo creare una pipe denominata con il comando mkfifo. Creerà uno pseudo file sul file system anche se non c'è spazio libero su di esso.

Riavviare l'applicazione e verificare:

[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 su disco, ma creiamo con successo una pipe denominata lì:

[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 avvolgere tutti i dati che entrano in questa pipe su un altro server tramite la rete, lo stesso netcat è adatto a questo.

Sul server remote-server.example.com eseguiamo

[user@localhost ~]$ nc -l 7777 > 123.txt 

Sul nostro server problematico lanciamo in un terminale separato

[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe 

Ora tutti i dati che finiscono nella pipe andranno automaticamente allo stdin di netcat, che li invierà alla rete sulla porta 7777.

Tutto quello che dobbiamo fare è iniziare a scrivere i nostri dati in questa pipe denominata.

Abbiamo già l'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

Di tutti i flag, abbiamo bisogno solo di 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 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

Controllo del 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 vengono salvati, il problema è risolto.

Colgo l'occasione per salutare i miei colleghi di Degiro.
Ascolta i podcast di Radio-T.

Tutto bene.

Come compito a casa, ti suggerisco di pensare a cosa ci sarà nel processo dei descrittori di file cat e sleep se esegui il seguente comando:

[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000

Fonte: habr.com

Aggiungi un commento