File duplicati: trovarli e toglierli senza perdere dati
Un disco con qualche anno di lavoro sopra contiene quasi sempre lo stesso file in tre, cinque, dieci posti: la versione scaricata dalla posta, quella salvata nella cartella della pratica, quella allegata a una bozza, quella nel backup del backup. Trovarle è facile. Toglierle senza perdere niente — e poter dimostrare dopo che cosa si è tolto e dove è finito — è il lavoro vero.
Lo stesso nome non è lo stesso file
Il primo istinto è cercare i duplicati per nome. È il criterio sbagliato, e sbaglia in tutte e due le direzioni. Due relazione.txt in due cartelle di pratiche diverse hanno lo stesso nome e contenuti che non c'entrano niente l'uno con l'altro. Al contrario, IMG_2041.jpg, IMG_2041 (1).jpg e foto cliente.jpg possono essere tre copie bit per bit della stessa immagine.
Nemmeno la data aiuta. Copiare un file ne rinnova spesso la data di creazione, quindi due copie identiche possono portare date lontane anni, e due file diversi la stessa data al secondo. Nome e data sono etichette che il filesystem attacca al contenuto; il contenuto è un'altra cosa.
Il criterio che non sbaglia è l'impronta del contenuto. Probatio considera identici due file quando hanno lo stesso SHA-256 (per un ripasso su che cosa garantisce un'impronta c'è la guida sugli hash crittografici). Va detto con precisione: è un'uguaglianza di impronte, non un confronto byte per byte. Per avere due file diversi con lo stesso SHA-256 servirebbe una collisione che nessuno sa produrre, quindi in pratica le due cose coincidono; ma la certezza su cui ci si appoggia è quella crittografica, ed è giusto saperlo.
Perché non si calcola l'impronta di tutto
Il metodo ingenuo è calcolare il SHA-256 di ogni file e raggruppare le impronte uguali. Funziona, ma costa quanto leggere il disco per intero: su un disco da due terabyte sono ore, e quasi tutto quel tempo serve a scoprire che file evidentemente diversi sono, appunto, diversi.
Il modulo Deduplica procede invece a imbuto. Ogni passo scarta ciò che il precedente ha già dimostrato diverso, e solo i superstiti arrivano al passo successivo, più costoso.
| Passo | Che cosa legge | Che cosa scarta |
|---|---|---|
| 1. Percorso e raggruppamento per dimensione | Solo i metadati del filesystem | I file di dimensione unica: non possono avere un gemello |
| 2. Impronta dei primi 64 KiB | 64 KiB per file | I file che differiscono all'inizio |
| 3. Impronta degli ultimi 64 KiB | Un salto a fine file e 64 KiB, solo sui file più grandi di 64 KiB | I file uguali in testa ma diversi in coda |
| 4. SHA-256 completo | Il file intero | Niente: forma i gruppi |
L'ordine dei passi non è un'ottimizzazione facoltativa: è ciò che rende la ricerca usabile su un disco intero invece che su una cartella. Il primo passo non apre nemmeno un file, perché la dimensione si legge dai metadati, e di solito elimina la grande maggioranza dei candidati. Il secondo e il terzo leggono al massimo 128 KiB per file, qualunque sia la sua grandezza. La lettura completa tocca soltanto i file che hanno già superato tre prove.
Perché anche la coda
Il controllo sulla testa, da solo, fallisce proprio dove costerebbe di più. Due filmati della stessa videocamera e della stessa durata possono avere dimensione uguale e un'intestazione del contenitore identica: con la sola testa arriverebbero entrambi alla lettura completa, e si leggerebbero gigabyte per scoprire che sono diversi. La coda invece differisce quasi sempre, e controllarla costa un salto e 64 KiB. Sui file che stanno interi nei primi 64 KiB il passo si salta: riguarderebbe gli stessi byte già confrontati.
I filtri non possono perdere un duplicato
Un dubbio legittimo: e se un filtro rapido scartasse per errore due file identici? Non può succedere, per costruzione. Due file identici hanno per forza la stessa dimensione, gli stessi primi 64 KiB e gli stessi ultimi 64 KiB: superano tutti i filtri e arrivano insieme al confronto finale. I filtri scartano solo file che differiscono in byte già letti, cioè file certamente diversi.
Due esclusioni sono predefinite e si possono togliere: i file vuoti (sarebbero tutti «identici» fra loro, spesso a centinaia) e i file nascosti. Una invece è fissa: i collegamenti simbolici non si seguono, perché seguirli conterebbe due volte lo stesso contenuto e permetterebbe di toglierlo passando da un percorso che non è quello vero. Anche il modo di leggere si decide sui fatti: prima della passata costosa Probatio fa dodici letture di prova sul file più grande e, se la latenza mediana supera 2 ms, tratta il supporto come un disco a testine e legge un file alla volta, perché lì le letture in parallelo rallentano invece di accelerare. La scelta, e il motivo, finiscono nel rapporto. Se qualche cartella non si è potuta aprire, il conteggio compare prima dei risultati: «nessun duplicato» vale solo per ciò che è stato letto.
I collegamenti fissi non sono copie
Un file può avere più nomi. Un collegamento fisso (hard link) è un secondo percorso che punta allo stesso contenuto sul disco: due voci nelle cartelle, un solo inode, un solo spazio occupato. Hanno ovviamente lo stesso SHA-256, ma non sono due copie: cancellarne uno non libera nemmeno un byte, perché il contenuto resta raggiungibile dall'altro nome.
Probatio li riconosce dal volume e dal numero che il filesystem assegna al contenuto — l'inode su macOS e Linux, l'indice del file su Windows, letto aprendo i soli file che compaiono in un gruppo — segnala il gruppo come «collegamenti fissi» e calcola lo spazio recuperabile contando solo i contenuti distinti: dimensione per (contenuti distinti meno uno). Un gruppo di tre percorsi da 2 GB, due dei quali sono lo stesso inode, promette 2 GB recuperabili, non 4. Promettere spazio che non si libererà mai è un modo sottile di dare un dato falso.
L'unica funzione che cancella
Tutto il resto di Probatio legge. Questa funzione toglie file dell'utente, e le sue difese stanno nel motore, non solo nell'interfaccia: l'interfaccia le rispecchia, ma anche un piano costruito male viene fermato a valle.
Prima difesa: l'impronta si ricalcola al momento dell'azione
Fra la scansione e il clic su «Esegui» passano minuti, a volte ore. Nel frattempo un file può essere stato riaperto e salvato, aggiornato da un servizio di sincronizzazione, sostituito. È il classico problema detto time of check to time of use: la verifica è vera quando la fai, non necessariamente quando agisci. Se il programma si fidasse della scansione, toglierebbe un file che non è più il duplicato che hai visto.
Perciò, prima di spostare o cancellare qualsiasi file, Probatio ne ricalcola il SHA-256 leggendolo per intero e lo confronta con l'impronta del gruppo. Se coincide, procede. Se non coincide, il file non si tocca e nel risultato compare il motivo: «il contenuto è cambiato dopo la scansione: non rimosso». Se non si riesce a rileggerlo, non si tocca nemmeno. La rilettura integrale costa tempo, ed è voluta: è il punto in cui questa funzione smette di essere pericolosa.
Seconda difesa: un gruppo non resta mai vuoto
Nell'interfaccia l'ultima copia di un gruppo non si può marcare: deve restare almeno un file per ogni contenuto. Ma la garanzia vera è nel motore. Se gli arriva un gruppo in cui non c'è niente da tenere, lo rifiuta per intero, non a metà: cancellarne una parte e accorgersi dopo che non doveva succedere sarebbe il peggiore dei risultati. Anche un percorso indicato insieme da tenere e da togliere viene rifiutato.
Non basta però che nel piano ci sia una copia da tenere: deve esserci ancora, sul disco, e con lo stesso contenuto. Prima di togliere qualsiasi file di un gruppo, Probatio rilegge per intero la copia da conservare e ne ricalcola il SHA-256. Se è stata spostata, cancellata o modificata dopo la scansione, togliere le altre potrebbe lasciarti senza nessuna copia di quel contenuto: il gruppo è rifiutato per intero, con il motivo «la copia da conservare non c'è più o è cambiata dopo la scansione».
Terza difesa: l'azione predefinita non cancella
Di serie i file finiscono nel cestino di sistema, da cui si recuperano con i mezzi che l'utente conosce già. L'alternativa più prudente è la quarantena: una cartella scelta da te in cui ogni file viene spostato conservando la struttura del percorso d'origine. /Volumes/Lavoro/pratiche/2023/relazione.txt finisce in <quarantena>/Volumes/Lavoro/pratiche/2023/relazione.txt. Appiattire i nomi farebbe sovrascrivere due relazione.txt venuti da cartelle diverse, che è proprio il caso tipico di una deduplica. Se la quarantena sta su un altro volume e lo spostamento diretto non riesce, il file viene copiato, e l'originale si cancella solo se la copia è andata a buon fine.
La cancellazione definitiva esiste, ma va scelta e confermata a parte, con una spunta dedicata.
Per i dischi con migliaia di gruppi c'è una selezione automatica che tiene una copia per gruppo secondo una regola: la più vecchia, la più recente, o quella dal percorso più corto. Le date usate sono quelle del filesystem (creazione, oppure modifica se la prima manca), quindi «la più vecchia» è di norma l'originale, ma non sempre: un ripristino da backup rinnova le date. La selezione automatica prepara il piano, non lo esegue; guardarlo prima di premere «Esegui» resta compito tuo.
Il rapporto: che cosa è stato tolto e dove è finito
Il rapporto PDF distingue due istanti: quando è stata eseguita la scansione e, se si è agito, quando è stata eseguita l'azione, con il fuso orario. Fra i due possono passare ore, e sono due fatti diversi. Il rapporto dice poi su quali cartelle si è cercato, se sono state incluse le sottocartelle, come sono stati letti i file e con quale metodo (dimensione, primi e ultimi 64 KiB, SHA-256 completo); quanti file sono stati esaminati, quanti gruppi, quanto spazio recuperabile. Elenca i gruppi con il loro SHA-256 e i percorsi di ogni copia così come li ha trovati la scansione, prima di qualunque azione, e i percorsi che non si sono potuti leggere, col totale prima dell'elenco.
Se si è agito, aggiunge il trattamento (cestino, quarantena o cancellazione) e una tabella con, per ogni file tolto, percorso, dimensione, SHA-256 ricontrollato subito prima della rimozione e destinazione: il percorso in quarantena, oppure «cestino di sistema», oppure «cancellato definitivamente». Segue l'elenco dei file non toccati, con il motivo. L'operazione finisce anche nel registro attività dell'app, con il numero di file tolti, non toccati e i byte liberati.
Limiti da conoscere
- Identico vuol dire stesso SHA-256, non confronto byte per byte (vedi sopra).
- Le verifiche al momento dell'azione costano letture complete: la copia da tenere e ogni file da togliere vengono riletti per intero. Su gruppi di file grandi l'esecuzione dura quanto una lettura di quei file.
- Il punto di ripresa si scrive sul disco esaminato: per poter riprendere una scansione interrotta Probatio salva un file
.probatio-dedup.jsonnella prima cartella scelta, e lo toglie a fine lavoro. Su un volume in sola lettura il salvataggio fallisce senza bloccare la scansione. La Deduplica è uno strumento per le copie di lavoro, non per il supporto originale di un reperto. - Il rapporto ha dei confini: mostra al massimo 300 gruppi, e dichiara il troncamento; per il cestino registra il trattamento ma non la posizione dentro il cestino. Se ti serve sapere esattamente dove è finito ogni file, la quarantena è la scelta giusta.
Perché in perizia conta il rapporto
In un lavoro peritale un'operazione distruttiva non è vietata in sé: a volte è necessaria, per esempio per ridurre a una copia di lavoro gestibile un archivio pieno di doppioni. Diventa inaccettabile quando non si può ricostruire. Chi legge la relazione deve poter sapere che cosa c'era, che cosa è stato tolto, con quale criterio, e dove si trova adesso ciò che è stato tolto. Se una sola di queste risposte manca, l'operazione diventa un punto cieco, e un punto cieco in una catena di lavoro si paga al primo controesame.
Per questo la Deduplica di Probatio è costruita al contrario di un pulitore di disco: sposta invece di cancellare, si ferma quando il dato non è più quello verificato, non svuota mai un gruppo, e scrive tutto. Il risparmio di spazio è il risultato. Il rapporto è ciò che lo rende difendibile.