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.

Esempio illustrativo — una cartella di lavoro da 120.000 file 1 · percorso dell'albero solo metadati: nome e dimensione 120.000 file 2 · stessa dimensione nessun file viene aperto 18.400 101.600 scartati senza aprirli 3 · primi 64 KiB SHA-256 di 64 KiB per file 3.100 15.300 scartati: testa diversa 4 · ultimi 64 KiB un salto a fine file e 64 KiB 2.700 400 scartati: stessa testa, coda diversa (tipico dei video) 5 · SHA-256 completo il file intero, a blocchi di 1 MiB 2.700 gli unici letti dall'inizio alla fine gruppi stesso SHA-256 = stesso contenuto 1.150 gruppi 2.610 file · 90 erano diversi solo nel mezzo Un duplicato non può sfuggire ai filtri: due file identici hanno per forza stessa dimensione, stessa testa e stessa coda. Numeri di esempio, non una misura. Le barre non sono in scala.
Più si scende, più il passo costa e meno file lo pagano. La lettura completa, l'unica davvero cara, tocca solo chi ha superato tre prove.
PassoChe cosa leggeChe cosa scarta
1. Percorso e raggruppamento per dimensioneSolo i metadati del filesystemI file di dimensione unica: non possono avere un gemello
2. Impronta dei primi 64 KiB64 KiB per fileI file che differiscono all'inizio
3. Impronta degli ultimi 64 KiBUn salto a fine file e 64 KiB, solo sui file più grandi di 64 KiBI file uguali in testa ma diversi in coda
4. SHA-256 completoIl file interoNiente: 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.

per ogni file marcato da togliere il piano dice che cosa resta e che cosa va 1 · la copia da tenere c'è ancora ed è intatta? 2 · ricalcola il SHA-256 leggendo il file intero 3 · coincide con l'impronta del gruppo? 4 · sposta cestino o quarantena (elimina: spunta a parte) sì sì no, sparita o cambiata l'intero gruppo è rifiutato nessun suo file viene toccato, nemmeno in parte no, o file illeggibile il file non si tocca «il contenuto è cambiato dopo la scansione» il motivo va nel risultato e nel rapporto nel rapporto: percorso, dimensione, impronta e destinazione Il caso di prova del codice: un file contiene «identico», poi viene riscritto in «CAMBIATO» prima dell'azione. impronta del gruppo bc28084a285d2ac6… (SHA-256 di «identico») ricalcolata ora 60225798d470a469… (SHA-256 di «CAMBIATO») Si decide sul file com'è adesso, non com'era alla scansione. Esito: non rimosso.
Le due uscite rosse non sono errori del programma: sono il programma che si rifiuta di agire su un dato che non è più vero.

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.json nella 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.