Mittwoch, 11. Januar 2023

SD Card Image anpassen

Man hat ein Image einer großen SD Card gemacht und will dieses Image auf eine kleinere SD Card speichern. Wie kann man das Image an den eigentlichen Platzbedarf anpassen ohne den Inhalt der SD Card zu verlieren? Ich habe sogar ein paar Einzeiler im Netz gefunden, aber die haben alle nicht funktioniert. Ich habe mit Hilfe dieses Artikels ein paar Schritte entwickelt die hoffentlich leicht nachzuvollziehen und eindeutig sind.

Zunächst ganz vereinfacht ein paar Grundlagen

  • Eine gesteckte SD Card stellt ein Blockdevice dar, ein Image davon kann auch als Blockdevice eingebunden werden. Ein Blockdevice enthält Partitionen, eine Partion enthält ein Filesystem.
  • Gespeichert wird in Sektoren (Sectorsize 512 Byte), die Sektoren werden in Blöcken zu 4096 Byte (bzw. 4K Blocksize) organisiert. 
  • Die typischen Zahlenangaben für das Filesystem sind Blöcke, für die Partition sind es Sektoren. Eine 16GB SD Card hat 3.906.250 Blöcke und 31.250.000 Sektoren.
  • Die Angabe GB basiert auf 1000³, die Angabe GiB basiert auf  1024³ (2^30). 16GB -> 14,9 GiB

Die gesamte Arbeit erfordert im wesentlichen root Kontext (sudo su). Ich erarbeite hier Einzeiler und kein komplettes Script, ich habe Ausgabezeilen eingefügt damit man die Abläufe verfolgen kann. 

Ich empfehle jede Zeile einzeln auszuführen! Tipp: Meine Codeblöcke sind editierbar: eigene Pfad- und Dateinamen reinschreiben und direkt copy & paste.

Einbindung Image als Blockdevice

Wo die Imagedatei liegt ist zweitrangig, dies kann auch Netzwerkshare sein. Die beiden Variablen am Anfang legen den Ort und Namen fest, die notwendigen Informationen werden ermittelt und auch in Variablen abgelegt. Damit man nicht blind "fährt" habe ich ein paar Ausgabezeilen eingebaut.

mp="/mnt/Sicherung"
img="$mp/Images/sdimage.img"
ldev=$(losetup --show -f -P "$img")
arr=($(ls ${ldev}p*))
blk_size=$(lsblk -b|grep ^${ldev##*/}|  awk -F' '  '{ print $4 }')
printf "Image %s is connected as %s \ncontains %s partitions: %s \nwith whole space of %.1e\n" ${img##*/} ${ldev} ${#arr[*]} ${arr[*]} ${blk_size}

Anmerkung: Das Blockdevice wird eingebunden, dass Filesystem wird nicht gemountet. Prinzipiell können alle folgenden Schritte auch mit jedem eingebundenem Blockdevice durchgeführt werden: ldev=/dev/sdx . Durch die vorherige Erzeugung einer Imagedatei von einem Blockdevice (Filesystem nicht gemountet!) kann die Auswirkung am physikalischen Device auch virtuell geübt werden!

Letzte Partition bearbeiten

Für die Größe des Images ist die letzte Partition bestimmend, nur diese wird untersucht und bearbeitet. Die minimale Größe des Filesystems und die Blockgröße wird ermittelt. Mit einem Aufschlag von 20%  wird die neue Größe des Filesystems bestimmt. 

size=$(resize2fs -P ${arr[-1]} 2>/dev/null | tee /dev/tty | grep -oE '[0-9]+')
block_size=$(blockdev --getbsz ${arr[-1]})
sector_size=$(blockdev --getss ${arr[-1]})
newsize=$((${size}+${size}/5))
printf "last partition %s \nestimated minimum : %s blocks %.1e bytes\nproposal size: %s blocks %.1e bytes\n" ${arr[-1]} ${size} $((${size}*${block_size})) ${newsize} $((${newsize}*${block_size}))

Alternativ kann man hier auch eine sinnvolle Vorgabe machen, um dem Zielimage eine bestimmte Größe zu geben z.B: newsize=$((8000000000/${block_size})).

Das Filesystem wird verkleinert

Achtung: Ab hier werden wirklich Veränderungen am Blockdevice durchgeführt! 

e2fsck -p -f ${arr[-1]} && resize2fs ${arr[-1]} ${newsize}

Wenn die obige Zeile einen Fehler liefert, stimmt etwas mit dem Filesystem nicht. Man kann versuchen mit e2fsck -f ${arr[-1]} eine Reparatur anzustoßen.

Die Partition an das Filesystem anpassen

Die Partitionsgröße in Sektoren wird errechnet und mittels der scriptfähigen Variante von fdisk die Partition neu gesetzt. Anmerkung: die Partitionsgröße kann auch in 4K Blöcken angegeben werden. Der Startsector wird durch "weglassen" von sfdisk übernommen (echo "start,size"|...).

part_size=$((${newsize}*${block_size}/${sector_size}))
printf "Repartitioning: Device %s, PartNumber %s \nPart Size %s sectors %.1e bytes\n" ${ldev} ${#arr[*]} ${part_size} $((${part_size}*${sector_size}))
echo ", ${part_size}" | sfdisk ${ldev} -N ${#arr[*]}

Die Datei abschneiden

Zum Schluss noch die Größe der Imagedatei anpassen. Dazu muss die Einbindung aufgehoben werden

losetup -d ${ldev}

Mit fdisk wird das Ende der Partitionen ermittelt und dann die Datei nach dem nächsten Sektor gekürzt.

END=$(fdisk -l $img | grep ^${img}${#arr[*]} |  awk -F" "  '{ print $3 }')
truncate -s $(((END+1)*512)) $img

Notizen

Imagedatei erzeugen

mp="/mnt/Sicherung"
dd if=/dev/sdb of=$mp/Images/sdcard.img bs=1M status=progress

Filesystem mounten

ldev="/dev/sdb"
for part in $(ls ${ldev}?) ; do 
    mkdir -p /mnt/${part##*/}
    mount ${part} /mnt/${part##*/}
done

Netzwerkshare mounten

mp="/mnt/daten"
mkdir -p $mp
mount -t cifs -o username=UserName,password=Passwort //Server/Share $mp

ToDo

Code Block


Keine Kommentare:

Kommentar veröffentlichen