Cancellare un file da decine di gigabyte e scoprire che Linux continua a segnalare il disco quasi pieno sembra una grave anomalia. Il comando rm non restituisce errori, il file non compare più nella directory e neppure il comando du riesce a trovarlo; eppure df sostiene che lo spazio sia ancora occupato. Una delle cause più comuni di questo comportamento non ha a che vedere con un problema sul file system: la causa è molto più interessante. Un processo mantiene ancora aperto il file cancellato.
Su Linux rimuovere un file e liberare i blocchi che occupa non coincidono sempre con la stessa operazione.
La system call unlink() rimuove il nome con cui il file è raggiungibile nel file system; se però un processo lo ha già aperto e continua a utilizzarlo, i dati restano ancora presenti sul disco. Lo spazio viene liberato solo quando tutti i processi che stavano usando quel file lo chiudono.
Cancellare un file significa prima di tutto eliminare il suo nome
Per comprendere il meccanismo legato alla cancellazione file su Linux, bisogna innanzi tutto allontanarci dal concetto di file come elemento descritto da un percorso assoluto. Quando digitiamo un comando come rm /var/log/applicazione.log, chiediamo al sistema di rimuovere la voce che collega il nome specificato all’oggetto conservato nel file system. Suggeriamo di dare anche un’occhiata al nostro articolo sulla struttura dei file system Linux.
Su file system Linux come ext4, un file non coincide semplicemente con il suo nome. Il nome che vediamo nella directory, ad esempio /var/log/applicazione.log, serve a raggiungere un inode, cioè la struttura che contiene i metadati del file e i riferimenti ai blocchi nei quali sono memorizzati i dati.
L’inode mantiene anche un contatore, chiamato i_links_count, che indica quanti hard link, cioè quanti nomi presenti nel file system, puntano a quello stesso file. Se esiste un solo nome e impartiamo il comando rm /var/log/applicazione.log, quel collegamento viene rimosso e il file non è più visibile nella directory.
Se un programma aveva già aperto il file prima della cancellazione, Linux gli permette di continuare a usarlo attraverso il riferimento ottenuto al momento dell’apertura. In altre parole, il nome è scomparso, ma il processo possiede ancora un accesso diretto a quel file.
Finché almeno un processo mantiene aperto quel riferimento, l’inode e i relativi blocchi di dati non possono essere liberati. Soltanto quando tutti i programmi che stavano utilizzando il file lo chiudono, e non esistono più hard link che puntano allo stesso inode, il file system può recuperare definitivamente lo spazio occupato. È proprio questo meccanismo a spiegare perché un file cancellato con rm possa continuare, apparentemente, a occupare gigabyte sul disco.
Perché df e du possono raccontare due storie diverse
Il comportamento, piuttosto comune, descritto al paragrafo precedente produce spesso un altro sintomo rivelatore.
Un comando come df -h mostra molto spazio occupato, mentre du -sh / o la somma delle directory restituiscono un dato sensibilmente inferiore.
Il principale motivo è che i due strumenti osservano il problema da prospettive differenti: df interroga il file system e considera i blocchi effettivamente allocati. Se un inode senza nome resta aperto da un processo, quei blocchi risultano ancora occupati.
du, invece, attraversa la gerarchia delle directory e somma lo spazio attribuibile ai file che riesce a raggiungere attraverso i relativi nomi. Un file ormai scollegato dalla directory non è più normalmente raggiungibile mediante quel percorso.
Una forte discrepanza tra du e df è quindi un ottimo indizio, anche se non costituisce da sola una prova perché i valori possono differire anche per altri motivi.
Gli snapshot, per esempio, sono copie dello stato del file system in un determinato momento e possono continuare a conservare dati che l’utente ha successivamente cancellato. I file sparse sono invece file che possono risultare molto grandi come dimensione dichiarata, pur occupando fisicamente meno spazio perché alcune aree non contengono dati reali. Alcuni file system, come ext4, tengono inoltre una quota di spazio riservata all’amministratore, utile per consentire al sistema di continuare a funzionare anche quando il disco è quasi pieno.
Come trovare i file cancellati su Linux che sono ancora aperti
Lo strumento più immediato è lsof, acronimo di list open files: nello specifico, il comando sudo lsof +L1 risulta particolarmente utile.
L’opzione +L1 seleziona i file aperti il cui numero di link è inferiore a 1, quindi i file che sono stati rimossi dalla gerarchia ma risultano ancora aperti. L’output può somigliare a questo:
| COMMAND | PID | USER | FD | TYPE | DEVICE | SIZE/OFF | NLINK | NODE | NAME |
|---|---|---|---|---|---|---|---|---|---|
| java | 1842 | app | 7w | REG | 253,0 | 32212254720 | 0 | 12345 | /var/log/server.log (deleted) |
NLINK a zero indica che non esistono più nomi collegati al file; (deleted) conferma che la voce è stata eliminata, mentre FD 7w mostra che il processo riportato continua ad averlo aperto in scrittura. Il file non è quindi “fantasma”: esiste ancora, ma non dispone più di un normale pathname attraverso cui raggiungerlo.
Dopo aver annotato il PID, si può usare il comando seguente (sostituendo evidentemente il PID corretto) ps -fp 1842 per identificare meglio il processo di riferimento. Se si tratta di un servizio systemd, systemctl status nome-servizio aiuta a comprenderne lo stato.
Come recuperare realmente lo spazio
La soluzione più sicura per liberare spazio su Linux consiste nel fare in modo che il processo, individuato seguendo la procedura al paragrafo precedente, chiuda il file che sta ancora utilizzando.
Quando un programma apre un file, Linux gli assegna un file descriptor, cioè un identificatore numerico che il processo usa per continuare a leggere o scrivere quel file senza doverne cercare ogni volta il nome nel file system. Finché quel riferimento rimane aperto, Linux considera il file ancora in uso e non può liberarne completamente lo spazio, anche se il nome è già stato cancellato con rm.
Se il processo può essere riavviato senza conseguenze, il comando sudo systemctl restart nome-servizio fa sì che alla sua chiusura il kernel rilasci i descriptor ancora aperti. Se quello era l’ultimo riferimento al file cancellato, lo spazio diventa finalmente riutilizzabile.
È possibile verificare lo stato dell’unità di memorizzazione con il comando df -h da eseguire prima e dopo il riavvio.
Per i servizi critici è naturalmente necessario capire se sia possibile utilizzare una procedura di reload o rotazione che faccia riaprire correttamente i file necessari senza interrompere completamente il servizio. Il riavvio del sistema, ovviamente, è risolutivo ma in molti casi – soprattutto su sistemi server – appare un intervento sproporzionato.
Un test pratico che mostra il fenomeno in pochi secondi
È possibile riprodurre il comportamento descritto nel nostro articolo senza correre alcun rischio.
Con l’istruzione seguente si può creare un file di test da 500 MB, riempito con altrettanti byte tutti impostati a 0.
dd if=/dev/zero of=/tmp/prova-spazio.dat bs=1M count=500
Il comando seguente apre il file mantenendo il descriptor attivo:
tail -f /tmp/prova-spazio.dat
Provando a eliminare il file da una seconda finestra del terminale con rm, quindi usando ls si può verificare che il file non esiste più. Eppure, sudo lsof +L1 mostrerà ancora qualcosa di simile a ciò che segue:
| COMMAND | PID | USER | FD | TYPE | DEVICE | SIZE/OFF | NLINK | NODE | NAME |
|---|---|---|---|---|---|---|---|---|---|
| tail | 12345 | utente | 3r | REG | … | … | 0 | … | /tmp/prova-spazio.dat (deleted) |
Terminando tail con CTRL+C, l’ultimo riferimento al file scompare e lo spazio può essere restituito al file system.
Conclusioni
La mancata liberazione immediata dello spazio dopo rm non è quindi un’anomalia, ma una conseguenza diretta del modo in cui Linux gestisce file, inode e processi.
Quando df e du mostrano valori molto diversi, vale la pena controllare subito se qualche applicazione stia ancora mantenendo aperti file già cancellati: sudo lsof +L1 consente spesso di individuare il responsabile in pochi secondi.
Prima di riavviare l’intero sistema o eliminare altri dati, è preferibile identificare il processo coinvolto e far sì che chiuda correttamente il file ancora in uso.