Wiederherstellung Daten Softwareraid nach missglückter RAID-1 -> RAID-5 Migration

If you need an English translation of this little HOWTO, give me a shout at are@lihas.de

Ausgangssituation

 * Linux Softwareraid RAID-1 mit 2 Platten, auf den Platten waren die Daten in LVM-Partitionen.
 * Die Platten wurden zu klein und deshalb sollte das RAID-1 online in ein RAID-5 mit 3 Platten umgerechnet werden mittels 'mdadm --grow'
 * Leider war die neue Platte defekt, d.h. die Datenübertragung ging extrem langsam, aber ohne Fehler und schliesslich blieb der ganze Rechner stehen.
 * Das / Verzeichnis war ein LVM, installiert war ein Debian Squeeze mit selbst kompiliertem 2.6.35er Kernel

Ansatz

 * Die Platte war super langsam (1.2KB/s-1.2MB/s), es konnten noch nicht viele Daten wirklich weg sein
 * Am Anfang der Platte waren 8GB swap als Logical Volume (LV) angelegt, am Ende der Platte waren noch einige GB Platz. Damit müssen die Daten, egal ob von vorne oder hinten umgerechnet wird, vorhanden sein.

Probleme

 * Es war unklar, welche Art RAID Superblock für das RAID-1 verwendet wurde und damit an welcher Stelle das Physical Volume (PV) tatächlich beginnt
 * Es existierte kein Backup der LVM Metadaten
 * Programme zur Partitionswiederherstellung kommen nicht mit LVM an sich klar und bestehen auf (C/H/S) Adressierungen

Lösung

 * Um den Rücken frei zu haben habe ich auf andere Platten ein Debian Squeeze mit 2.6.32er Kernel installiert, ein Rescue-System geht prinzipiell auch, allerdings ging es hier um 2TB Daten und ich habe das Recovery größtenteils via ssh durchgeführt und während meiner Versuche ab und zu mal neu gestartet
 * Erstellen eines neuen RAID-1 als /dev/md9 auf einer der ehemaligen Platten, diese läuft bei mir als /dev/sdd, die andere Platte habe ich mal in Ruhe gelassen, nicht unbedingt notwendig, ich hatte mir allerdings erhofft dann einfacher an die wirklichen Startbloecke des LVM zu kommen:
  * mdadm -C -l1 -n2 -x0 /dev/md9 /dev/sdd2 missing
 * Einlesen der ersten paar Sektoren um die LVM-Informationen zurück zu gewinnen (http://www.linuxjournal.com/article/8874?page=0,1):
  * dd if=/dev/sdd2 bs=512 count=255 skip=1 of=/root/rescue-md2-start.dd
  * vi /root/rescue-md2-start.dd
   * von Hand nach LVM-Metadaten Ausschau halten und die gefühlt neuste Version sichern, in meinem Fall war die Info für 'root2', meinem alten / besonders wichtig. Darauf findet sich das normale Backup der echten Metadaten unter /etc/lvm/backup. Diese Partition habe ich auch nie vergrößert und die ist deshalb am Stück. Das erleichtert das weitere Vorgehen.
 * testdisk Einsatz auf /dev/md9. testdisk arbeitet auf Zylinderebene, lässt sich aber auf Sektoren umstellen. Ich habe außerdem als Geometrie 4 Sektoren, 2 Köpfe und dann eben die resultierenden Zylinder angegeben. Das führt zu einer für mich leichteren Umrechnung auf Sektoren. Die testdisk Ausgabe mit den gefundenen 'Partitionen' habe ich von Hand in eine Datei kopiert. Das ist unschön, allerdings hat sich das testdisk bei jeglichem weiterführenden Menütastendruck verabschiedet. 8500 verschiedene Partitionen sind für ein Programm, das sich mit DOS Disklabels beschäftigt wohl etwas viel. Ich hatte natürlich keine 8500 Logical Volums (LVs) auf dem System, allerdings werden die Partition sehr häufig gefunden. Dass die meisten meiner Dateisysteme mit einem Label versehen waren, hat die Identifizierung sehr erleichtert. Mein root-Dateisystem hatte so das Tag [root2]. Im ersten Ansatz habe ich einfach die erste Partition mit dem passenden Tag genommen, das hat auch gut funktioniert. Um damit was anfangen zu können, muss das von (C/H/S) in Sektoren oder Byte umgerechnet werden:
  * Sektoren: =((C*2*4)+(H*4)+S+1)
  * Byte: Sektoren von oben * 512
 * Recovery der LVM-Daten
  * Ich habe ext4 verwendet, da das System hart ausgeschaltet wurde, muss das Journal nachgezogen werden. Dazu wird Schreibzugriff benötigt, der Squeeze 2.6.32er Kernel erlaubt das nicht für Loopbackmounts von Device-Ausschnitten, deshalb habe ich mir ein Image der Daten gezogen:
   * Testdisk Ausgabe:
TestDisk 6.11, Data Recovery Utility, April 2009
Christophe GRENIER <grenier@cgsecurity.org>
http://www.cgsecurity.org

Disk /dev/md1 - 2000 GB / 1862 GiB - CHS 488311712 2 4
     Partition               Start        End    Size in sectors
P Linux LVM2               0   0  1 488311711   1  4 3906493696
P ext4                    80   1  3 2621520   1  2   20971520
P Linux SWAP 2            81   0  1 2097230   1  4   16777200
P ext4                    81   0  1 2621520   1  4   20971520
P ext4                 2097232   1  3 4718672   1  2   20971520 [root]
P ext4                 2097233   0  1 4718672   1  4   20971520 [root]
P FAT12                2283089   0  1 2283448   1  4       2880 [BOOT622]
P FAT12                2283601   0  1 2283960   1  4       2880 [BOOT622]
...
P ext4                 3211338   0  1 5832777   1  4   20971520
P FAT12                3499767   1  1 3500127   0  4       2880 [dban-1.0.7]
P FAT12                4239029   0  4 4239389   0  3       2880 [NO NAME]
P FAT12                4239825   0  4 4240185   0  3       2880 [NO NAME]
P HFS                  4349924   0  3 372082214   1  3 2941858325 [^B~?^WI/^RM-@~T^B DeM-J^D~_7:^P^AM-@t~J^R^^]
P ext4                 4718672   1  3 135790672   1  2 1048576000 [mythtv]
P ext4                 4718673   0  1 135790672   1  4 1048576000 [mythtv]
^D0^E^CG^B~?^[ ~Z]     18861385   0  3 90510564   0  1  573193431 [^PZ
   * Resultierender Sektor: 2097232*4 + 1*4 + 3 + 1 = 16777864
   * Resultierendes dd: dd if=/dev/md9 bs=512 skip=16777864 count=20971520 of=root.dd
   * fsck.ext4 root.dd
   * mount -o loop,noatime,barrier=0,nobh,commit=100 root.dd /mnt
    * Daten kopieren, z.B. mit rsync
    * war nur zum Üben, das Label 'root' ist eine alte Version von einer Migration. Ich habe dann noch mit anderen Dateisystemen geübt.
  * Mein 2.6.35er Kernel kann auch direkt mit dem Loopmount auf das Device und den Journal recovern, da werden dann die Byte benötigt, spart das 'dd' für Partitionen die am Stück sind:
   * mount -o loop,noatime,barrier=0,nobh,commit=100,offset=$((16777864*512)) /dev/md9 /mnt
 * Aus den (C/H/S), bzw. Sektoren lässt sich dann der wirkliche Offset der LVM-Daten auf dem PV mit Hilfe der wieder gewonnenen LVM Metadaten errechnen:
vg_bofh {
        id = "cyMmsn-S77F-qwVg-xzqm-Q7U6-G8PB-CkBiey"
        seqno = 32
        status = ["RESIZEABLE", "READ", "WRITE"]
        flags = []
        extent_size = 8192              # 4 Megabytes
        max_lv = 0
        max_pv = 0

        physical_volumes {

                pv0 {
                        id = "0usYte-po0G-fR6O-lIm2-bBRl-kMN2-nQhFNu"
                        device = "/dev/md2"     # Hint only

                        status = ["ALLOCATABLE"]
                        flags = []
                        dev_size = 3906493656   # 1.8191 Terabytes
                        pe_start = 384
                        pe_count = 476866       # 1.8191 Terabytes
                }
        }
        logical_volumes {
...
                root {
                        id = "oYfRi1-24lV-r6MT-lVKD-ztmK-3dih-jBDVVx"
                        status = ["READ", "WRITE", "VISIBLE"]
                        flags = []
                        segment_count = 1

                        segment1 {
                                start_extent = 0
                                extent_count = 2560     # 10 Gigabytes

                                type = "striped"
                                stripe_count = 1        # linear

                                stripes = [
                                        "pv0", 2048
                                ]
                        }
                }
...
        }
}
Anhand obiger Sektordaten für 'root' (2097232*4 + 1*4 + 3 + 1 = 16777864):
  16777864 = n + pe_start + stripes*extent_size = n + 384 + 2048*8192
  n = 264

Recovery LVM aus mehreren Segmenten

LVM Metadaten:
                mythtv2 {
                        id = "aw6EOt-FxCm-I1WW-3GeF-ryTB-nV3b-qypN01"
                        status = ["READ", "WRITE", "VISIBLE"]
                        flags = []
                        segment_count = 4

                        segment1 {
                                start_extent = 0
                                extent_count = 30720    # 120 Gigabytes

                                type = "striped"
                                stripe_count = 1        # linear

                                stripes = [
                                        "pv0", 351488
                                ]
                        }
                        segment2 {
                                start_extent = 30720
                                extent_count = 2560     # 10 Gigabytes

                                type = "striped"
                                stripe_count = 1        # linear

                                stripes = [
                                        "pv0", 135168
                                ]
                        }
                        segment3 {
                                start_extent = 33280
                                extent_count = 2560     # 10 Gigabytes

                                type = "striped"
                                stripe_count = 1        # linear

                                stripes = [
                                        "pv0", 217088
                                ]
                        }
                        segment4 {
                                start_extent = 35840
                                extent_count = 25600    # 100 Gigabytes

                                type = "striped"
                                stripe_count = 1        # linear

                                stripes = [
                                        "pv0", 450048
                                ]
                        }
                }
führen zu folgenden dd-Aufrufen:
dd if=/dev/md9 bs=512 count=$((30720*8192)) skip=$(($((8192*351488))+264+384)) seek=0 of=mythtv2.dd
dd if=/dev/md9 bs=512 count=$((2560*8192)) skip=$(($((8192*135168))+264+384)) seek=$((30720*8192)) of=mythtv2.dd
dd if=/dev/md9 bs=512 count=$((2560*8192)) skip=$(($((8192*217088))+264+384)) seek=$((33280*8192)) of=mythtv2.dd
dd if=/dev/md9 bs=512 count=$((25600*8192)) skip=$(($((8192*450048))+264+384)) seek=$((35840*8192)) of=mythtv2.dd

Das lässt sich anschließend wieder mit '-o loop' mounten und auslesen.

Hilfsmittel

 * testdisk http://www.cgsecurity.org/wiki/TestDisk
 * http://smartmontools.sourceforge.net/badblockhowto.html#lvm, das hat mich in meine Annahme bestätigt, dass das alles recht linear abgelegt wird
 * http://www.linuxjournal.com/article/8874?page=0,1 für den Hinweis auf die LVM Metdaten am PV Partitionsanfang