domenica 3 aprile 2016

Montare in lettura/scrittura sotto Linux un file immagine.img Raspberry


Raspberry Pi utilizza una semplice scheda (mini)SD come fosse un disco fisso durante il suo funzionamento. Il boot del minipc, il caricamento del suo sistema operativo ed il salvataggio dei dati in elaborazione avvengono sfruttando, appunto, questo supporto di memoria.
La scheda deve disporre di almeno 2 partizioni, una delle quali (la prima) deve essere di pochi Mb (di solito circa 60Mb) ed essere formattata in FAT16 o FAT32 (visibile quindi anche a Windows). Qui trovano posto i files di boot del sistema e la prima cosa che RaspberryPi fa appena acceso è cercare tale partizione e tali files, essenziali per proseguire con il caricamento del sistema operativo.
Quest'ultimo si trova di solito a bordo della seconda partizione, che occupa tutta la parte rimanente della SDcard ed è in formato Linux ext4.

In alcuni progetti (ce ne sono talmente tanti!) per Raspberry è stato ritenuto opportuno creare ulteriori partizioni oltre alle 2 'canoniche' appena citate, vuoi per una questione di comodo, vuoi per la necessità di mantenere la partizione di sistema read-only (sola lettura). Quest'ultima opzione dovrebbe garantire (il condizionale è d'obbligo) una maggiore durata della SDcard, impedendo che files di sistema possano essere corrotti durante il normale funzionamento e relegando ad una apposita partizione supplementare il compito di contenere files il cui accesso è necessario anche in scrittura.


I files .img



E' possibile eseguire il salvataggio (backup) completo dei dati contenuti nella scheda SD mantenendo intatta la disposizione delle varie partizioni come se ne scattassimo una fotografia, esportandole in un unico file, di solito con estensione '.img'. Il metodo più semplice consiste nell'utilizzo del programma gratuito Win32DiskImager che gira sotto Windows: una volta avviato basta selezionare l'unità contrassegnata da una lettera che rappresenta la SDcard, indicare in che percorso del proprio pc si desidera salvare il file di dati ed avviare l'operazione. Dopo qualche minuto, a seconda della capacità della scheda, avremo a disposizione un file immagine (.img), riproduzione fedele della nostra SDcard anche nella dimensione stessa del file. Si tratta infatti della riproduzione 1:1 del contenuto di ciascun settore della scheda stessa.
Lo stesso programma offre naturalmente la funzione inversa, cioè quella di ricreare la struttura ed i dati di partenza in una scheda SD partendo da un file .img. Questo, tra l'altro, è anche il metodo su cui si basa, ad esempio, la distribuzione del sistema operativo Raspbian, versione Raspberry di Debian, scaricabile sotto forma di file .img dal sito RaspberryPi  (il file solitamente rappresenta la 'foto' di una SDcard di 4Gb ed è compresso (zippato) a circa 1Gb per consentire un download più a portata di umano).

Il risultato finale sarà una SDcard contenente l'esatta 'fotografia' della SDcard di partenza, con i pro ed i contro: se la SDcard di partenza aveva una capacità di 4Gb, allora per ripristinare la 'fotografia' ce ne servirà una di almeno altrettanti 4Gb; se invece ripristinassimo il file in una SDcard più grande, ad esempio da 16Gb, RaspberryPi una volta acceso 'vedrà' solo i 4Gb di partenza mentre tutto il rimanente spazio rimarrà non allocato e non visibile, come se non esistesse. Che spreco!

E' per questo che Raspbian mette a disposizione il tool raspi-config , che tra le sue tante funzioni dispone di quella che consente di ingrandire automaticamente la seconda partizione fino a farle ricoprire tutto lo spazio disponibile sulla SDcard. Una volta avviato il sistema operativo basterà avviare il comando sudo raspi-config e selezionare l'apposita funzione, dopo di che riavviare il RaspberryPi. Dopo il riavvio scopriremo che la 'foto' originariamente di 4Gb ora occupa l'intera nostra SDcard da 16Gb, naturalmente con un sacco di spazio libero in più a disposizione.

Questo tool però ha anche i suoi limiti: se le partizioni da ripristinare sono più di due (le due 'canoniche'), si arresterà con un messaggio di errore e non vorrà saperne di collaborare. In casi come questo che fare?

Oppure potrebbe capitare che ci serva qualche file contenuto nel backup, ma non abbiamo nessuna SDcard libera per ripristinare l'intero file .img. Come fare per estrarre i files che ci servono direttamente dal file .img?

O anche: abbiamo la necessità di aggiornare spesso alcuni files contenuti in un file immagine da 32Gb che utilizziamo in un nostro progetto. Occorre per forza ricrearlo daccapo ogni volta con Win32DiskImager partendo dalla scheda SD, con i tempi biblici che ciò comporta?

E se volessimo, potremmo creare da zero un file immagine per poi creare al suo interno le partizioni che ci servono ed infine copiarvi dentro files di boot, sistema operativo con partizione in sola lettura e files di progetto in una partizione separata, più una partizione di ripristino di scorta?

La soluzione a queste domande che tolgono il sonno a milioni di miliardi di persone in tutto il mondo è quella di montare direttamente il file .img in lettura/scrittura in un ambiente Linux, ritrovandocelo tra i dischi di sistema e consentendoci quindi di modificarne il contenuto a nostro piacimento. 
Ubuntu andrà benissimo, ecco come si fa.


Montare un file .img in lettura/scrittura




Abbiamo visto che un file immagine (.img) altro non è che una 'foto' dell'SDcard di partenza. E' composto da una lunga ed ordinata fila di settori che a loro volta contengono (semplificando un po'):
  • l'inizio dell'intera SDcard (settore 0);
  • l'inizio della prima partizione;
  • i files contenuti all'interno della prima partizione;
  • l'inizio della seconda partizione;
  • i files contenuti nella seconda partizione;
e così via fino all'ultimo settore dell'ultima partizione.
Ogni settore contiene un tot di dati che dipende dalle impostazioni utilizzate al momento della formattazione della SDcard (usualmente 512 bytes).
Vediamo ad esempio cosa contiene il file 
"2014-09-09-wheezy-raspbian.img", versione un po' vecchiotta di Raspbian a suo tempo scaricata dal sito ufficiale Raspberry.  Lo facciamo utilizzando il comando fdisk direttamente sul file:



root@debian:/opt# fdisk -l 2014-09-09-wheezy-raspbian.img 


Disk 2014-09-09-wheezy-raspbian.img: 3,1 GiB, 3276800000 bytes, 6400000 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00090806

Device                          Boot  Start     End Sectors Size Id Type
2014-09-09-wheezy-raspbian.img1        8192  122879  114688  56M  c W95 FAT32 (LBA)
2014-09-09-wheezy-raspbian.img2      122880 6399999 6277120   3G 83 Linux

 

Vediamo subito che contiene le nostre due partizioni 'canoniche' (le ho evidenziate in giallo). Vediamo inoltre che la prima partizione (quella di boot, in formato FAT32) inizia al settore 8192 e termina al settore 122879, per un totale di 114688 settori (va contato anche il primo, 8192, che fa parte a tutti gli effetti della partizione); la seconda (riportata come tipo 'Linux') inizia al settore 122880 e termina al settore 6399999 compresi. 
Sector size: 512 bytes, ogni settore contiene 512 bytes di dati.

Sappiamo ora che esistono in questo file due partizioni e disponiamo del numero di settore di inizio di ciascuna partizione (8192 e 122880).
Proviamo a montare questo file nel filesystem di Ubuntu (o del vostro sistema Linux preferito) come se si trattasse di una periferica di memorizzazione vera e propria, ma anziché usare mount lo facciamo con un comando furbo ad hoc, che è losetup.
Losetup creerà un device virtuale (loop, da qui il nome del comando), al quale collegherà il file che gli indicheremo:


losetup /dev/loop0 2014-09-09-wheezy-raspbian.img


Ora per curiosità apriamo quella specie di "Esplora risorse" (Nautilus?) che accompagna sia Ubuntu che qualsiasi os Linux recente (in modalità grafica). Scopriremo che abbiamo una nuova periferica che si chiama "boot". E' la prima partizione contenuta nel nostro file, quella di boot appunto, e possiamo già leggere e scrivere al suo interno.
Ci manca ancora l'altra, e per montarla dovremo fare un'ulteriore furbata: la collegheremo ad un nuovo device di loop, che sarà /loop1, ma perchè il sistema la individui all'interno del file dovremo indicargli a che punto del file inizia la partizione. Al termine dell'operazione avremo la prima partizione che sarà accessibile in /dev/loop0 e la seconda in /dev/loop1. Vediamo come si fa a specificare il settore di inizio della partizione a losetup... 
IN TEORIA dovrebbe bastare un comando che dica a losetup di predisporre /dev/loop1 e collegarlo al file 2014-09-09-wheezy-raspbian.img

Però vogliamo che il file non venga collegato dall'inizio (settore 0), ma dal settore n. 122880, ossia dall'inizio della seconda partizione in poi. Stiamo cioè impostando un OFFSET (-o). Siccome però losetup non ragiona in termini di settori ma in termini di bytes, dobbiamo moltiplicare il numero del settore di partenza per 512 (che abbiamo visto essere il numero di bytes per settore):


   122880 * 512 = 62914560


Ecco che abbiamo il comando da dare a losetup: 


losetup /dev/loop1 2014-09-09-wheezy-raspbian.img -o 62914560


O in alternativa possiamo far fare il calcolo al pc (il risultato è lo stesso):


losetup /dev/loop1 2014-09-09-wheezy-raspbian.img -o $((122880*512)



E' come se dicessimo a losetup: collega il file 2014-09-09-wheezy... al dispositivo /dev/loop1, aprendo il file con un offset (punto di partenza) di 62914560 anziché dall'inizio (0).
 
Se tutto è andato come dovrebbe, troverete nell' "esplora risorse" di Linux una nuova unità contenente il filesystem presente nella partizione 2 del file .img . Con lo stesso sistema si potranno montare ed eventualmente modificare ulteriori partizioni.



Creare un file .img partendo da zero



Un bel giorno un vostro amico che ha creato un progetto che vi piace da impazzire ha prodotto un bel file .img della sua preziosa SDcard da 8Gb e molto generosamente ve lo ha passato. Con gridolini di gioia vi apprestate a trasferirlo sulla vostra SDcard quand'ecco che vi accorgete che la vostra SD è di marca diversa, ed i vostri 8Gb sono in realtà 7.99Gb (mi è capitato per davvero!) ed il file .img originale non ci sta. Inoltre per farla completa il file contiene 3 partizioni e quindi raspi-config non lo espanderà nel caso lo installaste in una SD più grande. Depressione! Fallimento ed angoscia!!!
A parte l'andarvi a comprare appositamente una SD da 8Gb della stessa marca (sperando che ciò risolva il problema), potreste decidere di sclerare con le istruzioni che trovate qua. Auguri!
Oppure stavolta il file ve lo fate voi ad hoc partendo da zero, che è pure istruttivo e dà soddisfazione. Una volta creato basterà montare l' img dell'amico nel modo che abbiamo visto poc'anzi, ed il nuovo img appena creato nel modo che vedremo tra poco. A quel punto si tratterà solo di copiare i files rispettivamente dalle partizioni dell'amico alle partizioni da noi appena create. Io ho optato per questa scelta ed ho fatto così:
Per prima cosa occorre creare un file vuoto. Linux mette a disposizione questo bel comando: 
root@debian:/opt# dd if=/dev/zero of=./immagine.img bs=1M count=7840
7840+0 record dentro
7840+0 record fuori
8220835840 byte (8,2 GB) copiati, 5,14662 s, 1,6 GB/s
Non ci facciamo fuorviare dal fatto che sembra sia stato creato un file da 8,2 Gb... La vera capacità del file la vediamo con:


root@debian:/opt# fdisk -l immagine.img

Disk immagine.img: 7,7 GiB, 8220835840 bytes, 16056320 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes



7,7Gb vanno bene, è meglio non esagerare altrimenti c'è il rischio di creare un file troppo grande che non ci starà nella nostra SD da 7.99Gb. Per indicare la dimensione del file da creare il parametro da cambiare è quello evidenziato:

dd if=/dev/zero of=./immagine.img bs=1M count=7840


Come si può intuire si tratta  della grandezza desiderata (in Mb) del file che stiamo creando. 
Il file appena creato è vuoto, dobbiamo perciò partizionarlo. Io mi trovo bene con GParted (sudo apt-get install gparted). Per operare però dobbiamo montare il file vuoto in loop, e stavolta lo faremo partire dal settore 0. Prima però sarà bene chiudere tutte le eventuali finestre di "Esplora risorse" o come lo vogliamo chiamare, perchè dovremo smontare eventuali /loop attivi dalle precedenti prove. Il comando per smontare (ad esempio) /loop0 è:

losetup -d /dev/loop0


Se ripetendo il comando ricevete un messaggio di errore, significa che /loop0 non esiste più, e possiamo procedere con il mount del file vuoto appena creato:

losetup /dev/loop0 immagine.img -o 0


Per inciso -o 0 sta a significare che il file va montato dal suo esatto inizio (offset al byte 0).
Siamo pronti per lanciare GParted, con il quale creeremo le partizioni che desideriamo nel file vuoto:


gparted /dev/loop0

Meraviglioso programma, ci fa vedere che disponiamo di un file intonso, che non dispone né di partizioni né tanto meno di tabella delle partizioni! Ed è questa che andremo a creare per prima. 
Dal menu Dispositivo --> Crea tabella partizioni...:










Applica, naturalmente.

Ora creeremo la prima partizione, quella di boot in formato FAT16. Attenzione, non la faremo partire dall'inizio del file (settore 0) ma dal 5° Mb. In altre parole lasceremo vuoti i primi 4 Mb della SD, questo va fatto per motivi tecnici.



Dal menu Partizione selezioniamo Nuova....









Occhio ai valori da cambiare (cerchiati in rosso). Da così:








... A così:





Il nome 'BOOT' che avremo digitato ci servirà più tardi per individuare meglio la partizione.
Ora un bel clic sul pulsante Aggiungi.
Non è ancora finita: l'ultimo passo è il clic da fare sul segno di spunta per eseguire davvero l'operazione:


 






... Certo che sì!!




Ora dobbiamo creare la partizione destinata a contenere il sistema operativo vero e proprio. Io l'ho creata da 3 Gb, deve essere sufficiente a contenere il sistema operativo contenuto nella seconda partizione del mio amico:






Sempre seguito dal clic sul segno di spunta verde.

Infine l'ultima partizione, che andrà ad occupare tutto lo spazio rimanente:






Questo è il risultato finale:



 





Se apriamo ora l' "Esplora risorse" o come lo vogliamo chiamare, scopriamo che le tre partizioni sono già visibili al sistema e pronte all'uso!






Non resta che copiarvi dentro i dati provenienti dalle partizioni dell' img del nostro amico, precedentemente montate nei relativi /loop.

Al termine dell'intera operazione disporremo del file immagine.img, pronto ad essere iniettato nella nostra SD da 7.99Gb con Win32DiskImager .




Al prossimo pasticcio!