#!/bin/bash # # In diesem Beispiel wird unter Linux ein Mulitimedia-Card-Reader (/dev/mmcblk0) # verwendet. # # Verfahren: zunächst eine Image-Datei von der SD-Karte erzeugen. # # Vorher ist es günstig, auf dem Pi alle "Datenleichen" zu löschen, # z.B. # apt-get clean # # Effizient kopieren (Blockgröße/Puffer 1MB): # dd if=/dev/mmcblk0 of=data.img bs=1M # # Einfach: # cp /dev/mmcblk0 data.img # # Ergebnis: Eine Image-Datei data.img mit der kompletten Größe der SD-Karte # # Bekanntes Problem: Kopieren dieser Datei auf eine neue SD-Karte # funktioniert oft nicht, wenn die neue SD-Karte ein # paar Sektoren weniger Arbeitsspeicher hat (also mehr "Redundanz"). # Passt das Image nicht komplett auf den neuen Stick, dann stimmen die # Partitionsgrößen nicht mehr, und es gibt später Schreibfehler # Außerdem: Zum Übertragen möchte man eigentlich nur die wirklich BELEGTEN # Daten haben, nicht die "leeren" Bereiche. # # Nebenbei: Um ein Image effizient zu komprimieren, kann VOR # dem Imagen eine Datei mit Nullen auf der SD-Karte angelegt werden, # die den ganzen freien Platz belegt, dann diese Datei wieder löschen. # Das erzeugte Image enthält dann viele Nullen und lässt sich daher # gut mit zip oder gzip komprimieren. # cp /dev/zero nullen.img; rm nullen.img # # Das Programm kpartx kann Partitionen eines Image "wie Festplattenpartitionen" # einblenden nach /dev/mapper: # sudo kpartx -avs data.img # Unter /dev/mapper/loop0p1 und /dev/mapper/loop0p2 sind jetzt die beiden # Partitionen sichtbar, das komplette Image heißt hingegen /dev/loop0 # # Prüfen/reparieren der 2. Partition: # e2fsck -y -f /dev/mapper/loop0p2 # # Nun: Größe der 2. Partition minimieren # resize2fs -f -Mp /dev/mapper/loop0p2 # # Das Dateisystem ist jetzt kleiner, nun muss noch die # letzte Partition verkleinert werden! # Man kann ausrechnen, wie viele Sektoren die letzte Partition # haben muss, oder wir erledigen dies graphisch mit gparted. ;-) # sudo gparted /dev/loop0 # Hier mit rechte Maustaste -> "Resize" die Partitionsgrenze ganz nach # links schieben. Im folenden Skript wird dies automatisch berechnet # und mit parted durchgeführt. # # Den letzten Sektor der letzten Partition # in gparted nachschauen (1 Sektor = 512 Bytes) # # Den Rest der Datei wegschneiden. # dd if=/dev/null of=data.img bs=512 seek=letzter_sektor # # Hier folgt das Skript, das alles automatisch macht (Benutzung # auf eigene Gefahr) # Als Parameter übergebene Datei prüfen ob vorhanden und schreibbar [ ! -f "$1" -o ! -w "$1" ] && { echo "Usage: $0 imagefile"; exit 1; } # Mit kpartx den Inhalt von sd.img nach /dev/mapper als Partitionen darstellen # Dabei Namen des Device der letzten Partition in "lastpart" merken. lastpart="$(sudo kpartx -avs "$1" | awk 'match($0,/loop[0-9]+p[0-9]+/) {p=substr($0,RSTART,RLENGTH)}END{print p}')" # Die zu verkleinernde Partition liegt jetzt beispielsweise # als /dev/mapper/loop0p2 vor # Falls das nicht geklappt hat, ist etwas schief gegangen -> Ende [ -n "$lastpart" ] || { echo "Can't map image."; exit 1; } # Nummer der letzten Partition ist der Teil hinter loop0p... lastpartno="${lastpart#loop*p}" # Nun können wir die letzte Partition verkleinern, Returncode in rc merken e2fsck -y -f /dev/mapper/"$lastpart" resize2fs -f -Mp /dev/mapper/"$lastpart"; rc="$?" # Bis hier ist alles noch relativ ungefährlich gewesen. # Herausfinden, wie groß das ext4-Dateisystem der letzten Partition # geworden ist (in Bytes), und diese Zahl in der Variable lastsize # speichern lastsize="$(dumpe2fs /dev/mapper/"$lastpart" 2>&1 | awk -F: '/Block count/{count=$2} /Block size/{size=$2} END{print count*size}')" # Jetzt können wir das Partitions-Mapping schon wieder aufheben, # damit ist die Image-Datei nicht mehr "in Benutzung" sudo kpartx -d "$1" # Falls beim Resize weiter oben etwas schief gegangen ist: Abbrechen [ "$rc" = 0 ] || exit 1 # Auslesen von Partitions-Start mit parted, Berechnen des neuen # Partitions-Endes start="$(sudo LC_ALL=C parted -s "$1" unit B print | awk '/^ *'"$lastpartno"' /{print $2}')"; start="${start%B}" let end="$start + $lastsize" echo "Letzte Partition start: $start, end: $end, size=$lastsize" # Nun der gefährliche Teil. Wir tragen die neue Partitionsgröße # in die Partitionstabelle im Image ein... echo 'Yes' | sudo LC_ALL=C parted ---pretend-input-tty "$1" resizepart "${lastpartno}" "${end}B" # Ende in Anzahl 512-Byte Sektoren für dd (=letzter Sektor + 1) let end="$end / 512 + 1" # Den Rest der Datei wegschneiden. # Den letzten Sektor haben wir oben berechnet. dd if=/dev/null of="$1" bs=512 seek="$end" # Fertig echo "Fertig." ls -ld "$1"