Pulizie d'autunno
Si avvicina il tempo dell'autunno ed è il momento di pulizie dei dischi e dell'ambiente circostante i dischi, cioè lo spazio vitale.
Tra i meandri più
oscuri di tale spazio vitale, ho ritrovato decine di obsoleti CD e DVD e
addirittura dei floppy disk, con tracce di antico codice TurboC
scritto da giovane e di altissimo valore sentimentale, ormai semi
irrecuperabili.
Ripensandoci mi è venuto in mente di salvare il salvabile da qualche parte, che non significa semplicemente salvarlo su un disco o una chiavetta a sua volta da dimenticare da qualche parte, in attesa che venga perso o cancellato, ma di organizzare un sistema di backup più modulare di quello normalmente usato per il disco e
ampiamente discusso in passati articoli.
L'archiviatore ufficiale del mondo Linux come sappiamo è il
Tape ARchiver che, dopo il passaggio per diverse strutture e versioni, è e sarà universalmente riconosciuto da tutti i sistemi operativi come uno standard.
Abbiamo anche visto come col
tar , grazie al fatto che non si tratta di un semplice
archiviatore ma di un
serializzatore di oggetti del File System, sia praticamente possibile far di tutto, compreso
far volare un pinguino da un computer all'altro.
Qui analizzeremo il
tar dal punto di vista del backup incrementale superando alcune sostanziali inadeguatezze e costruendo uno
script particolarmente interessante per chiunque voglia memorizzare i propri progetti, anche in modo incrementale, in un
singolo file portabile e standard.
Come funziona il tar incrementale ?
Non è mia intenzione causare reazioni eccessive e rabbiose da parte dei puristi dei comandi
*nix e nemmeno elevarmi al di sopra delle parti e giudicare severamente strumenti in uso da decenni, ma mi sento di dire che l'utilizzo di
tar come sistema di
backup incrementale è tecnicamente "
un casino".
Sia chiaro che questo è dovuto all'estrema versatilità del comando
tar e delle infinite possibilità che ci offre e grazie alle quali vedremo come risolvere parte dei nostri problemi, ma ciò non toglie che il suo comportamento standard sia piuttosto complesso da capire, tanto che molti articoli su
Internet, pretendono di spiegarlo ma non sono del tutto corretti.
Vediamo allora di spiegarlo correttamente, cercando di capirne il funzionamento.
Il
tar come ho detto, è un semplice serializzatore di oggetti del
File System, quindi scansiona
i rami del
File System e scrive in uno
stream una sequenza, che può essere recuperata in modo da ricostruire altrove e/o poi gli stessi
rami. L'archiviatore in sé, non comprime ma passa lo
stream ad un semplice filtro.
Questo porta ad innumerevoli vantaggi in fatto di versatilità ma anche come fattore di compressione, specie quando si ha a che fare con molti files di testo in quanto, a differenza per esempio da un semplice
zip, dove i files sono compressi singolarmente e poi aggiunti, qui i files sono compressi nella globalità e considerato che il buffer di un
bzip2 (opzione
best) è di circa 1M, consente di scendere ad un entropia infinitesimale.
Per contro si deve dire che a differenza di uno
zip, una volta che si ha il prodotto compresso, non è possibile modificarlo (aggiungere, togliere o fare update), senza prima decomprimerlo e ciò può essere molto scomodo se non si ha abbastanza spazio.
Per quanto riguarda il
backup incrementale il
tar offre un semplice flag denominato
--listed-incremental, da non confondersi con
--incremental che è riferito al vecchio sistema, e già il fatto che siano due fa capire che tale vicenda è molto travagliata.
Capire cosa fa esattamente questo
flag che deve essere usato con un nome
file, cioè come :
--listed-incremental=<file>
è importante, perché molti lo trattano come se compisse mirabili operazioni nascoste.
In realtà produce una semplice
lista, che non è nemmeno in formato testo ma è una specie di
dump binario delle strutture indice, e non dà quasi nessuna indicazione sul file salvato.
È in sostanza una lista simile a quella che si potrebbe ricavare col un semplice
find anche se con qualche controllo in più.
Fase 1 - Il primo tarball
Consideriamo l'ipotetica cartella "
saveme".
Per eseguire un
tar incrementale si può procedere in questo modo :
tar cvfz saveme-20130826123545.tgz \
--listed-incremental=saveme-20130826123545.idx \
saveme
Il prodotto della suddetta operazione saranno ben due files :
saveme-20130826123545.tgz
saveme-20130826123545.idx
Il primo contenente il vero
tarball e il secondo con l'indice dei files o lo stato del file system o in qualsiasi altro modo volete chiamarlo.
Per chi se lo chiedesse il valore
20130826123545 si ricava così :
date +%Y%m%d%H%M%S
ed è una comodissima "
data ordinabile".
Fase 2 - Aggiornamento
Ipotizziamo di fare alcune modifiche alla cartella "
saveme", aggiungendo, spostando e togliendo files.
Per aggiornare il backup ci serve il vecchio indice, cioè l'ultimo che abbiamo salvato.
In caso
non volessimo tenere una storia di tutto il
file system, ma usare un semplice backup incrementale, allora potremmo usare un indice unico, ma non conviene, in quanto gli indici sono di dimensioni molto ridotte, e con essi è possibile tenersi l'effettiva storia di una cartella e ripristinarla in qualsiasi momento del suo passato
La scelta più semplice è quindi trovare la nuova data e copiare l'indice :
cp saveme-20130826123545.idx saveme-20130826152632.idx
e quindi
tar cvfz saveme-20130826152632.tgz \
--listed-incremental=saveme-20130826152632.idx \
saveme
Quest'ultima operazione, prenderà in considerazione l'indice appena copiato ed eseguirà il backup dei
soli files modificati in base al
timestamp, quindi
aggiornerà l'indice con il nuovo stato (di tutti i files), con i files in più e omettendo chiaramente quelli cancellati.
E i files presenti diventeranno quattro :
saveme-20130826123545.tgz
saveme-20130826123545.idx
saveme-20130826152632.tgz
saveme-20130826152632.idx
Nonostante la considerazione che i
tarball sono comunque semplici
tarball e possono essere estratti indipendentemente dagli
indici, il fatto che files aumentino a dismisura e che tali
indici restino separati, non è affatto una cosa positiva e introduce evidenti svantaggi dal punto di vista della gestibilità del backup e nella sua archiviazione.
Fase 3 - Restore
Per ripristinare lo stato di un progetto in un certo istante temporale, per esempio le 15.26.32 del 26/08/2013 (cioè l'ultimo), dobbiamo procedere
solo sull'indice interessato ed estrarre
tutti i backup fino a questo indice, in questo modo :
tar xvfz saveme-20130826123545.tgz \
--listed-incremental=saveme-20130826152632.idx \
saveme
tar xvfz saveme-20130826152632.tgz \
--listed-incremental=saveme-20130826152632.idx \
saveme
La logica di questa operazione è
molto particolare, infatti il
tar usa l'indice,
solo per cancellare dal file system i files che non si trovano nell'indice stesso.
Per spiegarci meglio, immaginiamo che al primo backup in
saveme, ci fossero tre files ,
A ,
B e
C .
Immaginiamo che prima di fare il secondo backup, avessimo
tolto B ,
aggiornato C e
aggiunto un file D.
L'indice
20130826152632 a questo punto, conterrebbe l'elenco :
A,
C, D .
Durante l'estrazione del primo
tarball, che conterrebbe
A,
B e
C ,
tutti i files sarebbero estratti, in quanto non ci sarebbe nulla da cancellare, curiosamente anche
B che non si trova nell'indice.
Al secondo passaggio
B sarebbe cancellato, e sarebbero estratti sempre
tutti i files contenuti nel secondo
tarball e cioè
C che sarebbe così
sovrascritto e
D come file nuovo, ripristinando lo stato del sistema.
Quindi l'archiviatore, non
ricostruisce lo stato del sistema ma cerca di
ripristinarlo con strani passaggi cancellando e sovrascrivendo più volte, con la scusa che l'hard disk di solito è trasparente a queste azioni (grazie alle fantastiche buffer cache).
Bisognerebbe chiedere a quelli che usano i
DOM o in generale le
Flash, magari con appositi
file systems a rotazione se sono d'accordo, ma come al solito non si può pretendere tutto dalla vita.
Un po`d'ordine nel chaos
Cercando di comprendere il tutto e di fare un po`d'ordine nel chaos, mi rifaccio a uno dei primi progetti che ho descritto tra queste pagine, cioè
Time Archive che usa un sistema di
patch differenziali, sui tar binari di progetto e che si è rivelato ottimo per piccole modifiche, ma piuttosto dispendioso su grosse variazioni.
L'idea è quella di introdurre praticamente gli stessi comandi, anzi volendo di creare una
nuova versione di
Time Archive , e di chiamare quella vecchia per esempio
DiffArchive o qualcosa del genere, usando semplicemente e solamente il concetto di
tar incrementale .
I comandi da implementare sono i
4 di
Time Archive , cioè
U per
update,
R per
rollback ,
X per e
xtract e
T per
trace.
Gli scopi di questo progetto sono interessanti :
- Definire uno standard per i backup.
- Automatizzare le procedure.
- Mantenere l'affidabilità e il formato del tar con compressione bzip2.
- Usare un solo file per ogni progetto che mantenga la storia e le versioni.
In particolare per il punto 4, useremo un semplice tar non compresso che conterrà tutti i tar compressi, in ordine con indici e descrizioni. Il fatto che non sia compresso implicherà la sua facile modificabilità e affidabilità.
TimeArchive II
Il comando Update
Il comando
update per questo
timearchive si definisce come nella precedente versione :
timearchive U <archive> <folder> [ "<description>" ]
dove
archive è il nome dell'archivio arbitrario che dovrà ospitare tutti gli altri archivi,
folder è la cartella da archiviare e
description è una breve descrizione opzionale della versione corrente.
Dal punto di vista logico, si individuano innanzi tutto il
folder e l'
archivio in termini assoluti, poi si può procedere con un codice come questo :
tmpdir=`mktemp -d`
todate=`date +%Y%m%d%H%M%S`
if [ -e "$archfull" ]
then
lastidx=`tar tf "$archfull" | sort | grep idx | tail -n 1`
tar xf $archfull $lastidx -O >$tmpdir/$todate.idx
fi
tar cfj $tmpdir/$todate.tb2 -C "${dirfull%/*}" --listed-incremental=$tmpdir/$todate.idx "${dirfull##*/}"
# Update archive
echo $4 >$tmpdir/$todate.txt
tar -C $tmpdir -rf $archfull $todate.tb2
tar -C $tmpdir -rf $archfull $todate.txt
tar -C $tmpdir -rf $archfull $todate.idx
rm $tmpdir/*
rmdir $tmpdir
In sostanza si crea una cartella temporanea di lavoro e si determina la data. Se l'archivio esiste già, è estratto direttamente dal
tar l'ultimo indice salvato, in questo modo :
lastidx=`tar tf "$archfull" | sort | grep idx | tail -n 1`
Se invece l'archivio non esiste, l'indice sarà creato.
Poi c'è un
tar che esegue il backup incrementale e alla fine i due files
tarball ,
indice e un file contenente semplicemente l'
echo della
description, sono appesi al file (
-rf) archivio principale, che se non c'è è ovviamente creato sul momento.
Quindi per ogni versione avremo l'aggiunta di tre files ad un file archivio principale, facilmente verificabile con :
tar tvf archive
Il comando eXtract
La sintassi del comando è :
timearchive X <archive> <folder> [ <date> ]
Dove
archive è il solito archivio,
folder è il folder dove sarà estratta la cartella del progetto indicata prima, cioè se la cartella era per esempio
Pictures indicando
/tmp , troveremo in
/tmp la
Picures. Per ripristinarla nella cartella corrente basta indicare come folder il "
." .
Infine
date è la data che si vuol ripristinare ed è opzionale.
Estrarre il contenuto dell'archivio richiede l'estrazione di
tutti i tarball fino ad una data specifica ed è espressa dal seguente blocco di codice :
tmpfile=`mktemp`
if [ -z "$4" ]
then
lastidx=`tar tf "$archfull" | sort | grep idx | tail -n 1`
else
lastidx=`tar tf "$archfull" | grep "$4.idx"`
if [ -z "$lastidx" ]
then
echo "Date does not match"
exit
fi
fi
tar xf $archfull $lastidx -O >$tmpfile
cd "$dirfull"
tar tf $archfull | grep tb2 | tarext "$archfull" "$tmpfile" "$4"
rm $tmpfile
A parte la creazione del file temporaneo che ci servirà per l'indice, con
-z "$4" si verifica se il quarto parametro e cioè la
Date è vuota o meno.
Se è vuota si ricava
l'ultimo indice, come nel caso dell'
update, altrimenti l'indice è cercato esplicitamente (e se non si trova si ha un errore).
Estratto l'indice nel file temporaneo si analizza semplicemente il contenuto dell'archivio, si ordina in ordine crescente di data e si passano i nomi dei relativi
tarball ad una funzione chiamata
tarext .
Il
tarext fa questo :
function tarext
{
while read a
do
tar xfp "$1" "$a" -O | tar xj --listed-incremental="$2"
if [ ! -z "$3" ]
then
if [ "$3.tb2" == $a ]
then
return
fi
fi
done
}
ossia prosegue estraendo coerentemente tutti i
tar sull'indice indicato fino a quando ha estratto quello giusto (che può essere anche l'ultimo), ricostruendo così lo stato in un preciso istante di tempo.
Notare che il
tarball, in questo caso
non è copiato su disco ma estratto in una pipe e il folder ricostruito senza occupare ulteriore spazio.
Il comando Rollback
Il comando rollback ripristina l'archivio ad una data prescelta che significa la
cancellazione di tutti le successive versioni. È un comando che si usa raramente, per esempio quando si sbaglia a fare un update.
La sintassi è semplicemente:
timearchive R <archive> <date>
Il funzionamento è piuttosto simile a
extract :
if [ -z "$3" ]
then
echo "Missing rollback date"
exit
fi
lastidx=`tar tf "$archfull" | grep "$3.idx"`
if [ -z "$lastidx" ]
then
echo "Date does not match"
exit
fi
tar tf $archfull | sort -r | rollback "$archfull" "$3"
Si noti però che il sort è
rovesciato, quindi sono cancellati tutti i files a partire dal fondo fino alla data scelta. Chi li cancella è la seguente procedura :
function rollback
{
while read a
do
if [ "$a" == "$2.txt" ]
then
return
fi
tar --delete --file "$1" "$a"
done
}
Anche qui simile alla precedente ma tenendo conto dell'ordine inverso e dell'uscita
prima e non
dopo la data prescelta.
Non si può ovviamente fare il rollback ad una data inesistente, e cancellare il primo record, per fare quello basta cancellare l'archivio.
Il comando Trace
Mostra il contenuto dell'archivio. La sua sintassi è :
timearchive T <archive>
e il suo funzionamento è basato su una riga di codice :
tar tf $2 | sort | grep txt | showcontent "$2"
e una funzione :
function showcontent
{
while read l
do
echo -n ${l%%\.*}" "
tar xf $1 $l -O
done
}
la quale, come possiamo osservare, mostra la data e il commento opzionale inserito durante l'update.
Se il commento non è stato inserito, il file è comunque presente in quanto
echo, come una stringa vuota.
Lo script finale
Ed eccoci al consueto script complessivo che implementa il comando :
#!/bin/bash
function showcontent
{
while read l
do
echo -n ${l%%\.*}" "
tar xf $1 $l -O
done
}
function rollback
{
while read a
do
if [ "$a" == "$2.txt" ]
then
return
fi
tar --delete --file "$1" "$a"
done
}
function tarext
{
while read a
do
tar xfp "$1" "$a" -O | tar xj --listed-incremental="$2"
if [ ! -z "$3" ]
then
if [ "$3.tb2" == $a ]
then
return
fi
fi
done
}
case $1 in
U)
dirfull=$3
if [ "${dirfull:0:1}" != "/" ]
then
dirfull=`pwd`/$dirfull
fi
if [ ! -d "$dirfull" ]
then
echo "$3 must be an existing directory"
exit
fi
archfull=$2
if [ "${archfull:0:1}" != "/" ]
then
archfull=`pwd`/$archfull
fi
tmpdir=`mktemp -d`
todate=`date +%Y%m%d%H%M%S`
if [ -e "$archfull" ]
then
lastidx=`tar tf "$archfull" | sort | grep idx | tail -n 1`
tar xf $archfull $lastidx -O >$tmpdir/$todate.idx
fi
tar cfj $tmpdir/$todate.tb2 -C "${dirfull%/*}" --listed-incremental=$tmpdir/$todate.idx "${dirfull##*/}"
# Update archive
echo $4 >$tmpdir/$todate.txt
tar -C $tmpdir -rf $archfull $todate.tb2
tar -C $tmpdir -rf $archfull $todate.txt
tar -C $tmpdir -rf $archfull $todate.idx
rm $tmpdir/*
rmdir $tmpdir
;;
X)
dirfull=$3
if [ "${dirfull:0:1}" != "/" ]
then
dirfull=`pwd`/$dirfull
fi
if [ ! -d $dirfull ]
then
echo "$3 must be an existing directory"
exit
fi
archfull=$2
if [ "${archfull:0:1}" != "/" ]
then
archfull=`pwd`/$archfull
fi
if [ ! -e $archfull ]
then
echo "Archive not found"
exit
fi
tmpfile=`mktemp`
if [ -z "$4" ]
then
lastidx=`tar tf "$archfull" | sort | grep idx | tail -n 1`
else
lastidx=`tar tf "$archfull" | grep "$4.idx"`
if [ -z "$lastidx" ]
then
echo "Date does not match"
exit
fi
fi
tar xf $archfull $lastidx -O >$tmpfile
cd "$dirfull"
tar tf $archfull | grep tb2 | tarext "$archfull" "$tmpfile" "$4"
rm $tmpfile
;;
R)
archfull=$2
if [ "${archfull:0:1}" != "/" ]
then
archfull=`pwd`/$archfull
fi
if [ ! -e $archfull ]
then
echo "Archive not found"
exit
fi
if [ -z "$3" ]
then
echo "Missing rollback date"
exit
fi
lastidx=`tar tf "$archfull" | grep "$3.idx"`
if [ -z "$lastidx" ]
then
echo "Date does not match"
exit
fi
tar tf $archfull | sort -r | rollback "$archfull" "$3"
;;
T)
tar tf $2 | sort | grep txt | showcontent "$2"
;;
*)
echo Bad paramenter, please use U,T,R or X.
;;
esac
con questa evoluzione del
timearchive, ho ottenuto ottimi risultati specie su backup significativi e complessi e il risultato è comunque un tar, facilmente gestibile in caso di emergenza o trasferimento su altri sistemi.