Fehlerhafte Festplattensektoren neu zuweisen
Festplatten mit SMART lassen sich mit den
Betroffene Datei ermitteln
Mit mount kontrolliere ich, dass die ermittelte Partition wirklich vom Typ ext2 bzw. ext3 ist. Dann ermittle ich den zugehörigen Dateisystemblock. Um diesen zu berechnen, muss ich mit tune2fs ermitteln, wie groß die Blockgröße des Dateisystems auf dieser Partition ist.
open(MOUNT,"mount|")
or die "Could not mount: $!";
while (<MOUNT>) {
if (m{ ^$part
\s+on\s+
(\S+)
\s+type\s+
(\S+)
\s+
}x) {
$mpoint = $1;
$ptype = $2;
last;
}
}
die "Don't handle $ptype"
unless ($ptype =~ /^ext[23]$/);
$tune2fs_cmd = "tune2fs -l $part";
open(TUNE2FS,"$tune2fs_cmd|")
or die "Could not tune2fs $!";
while (<TUNE2FS>) {
if (m{ ^Block\ size:\s+
(\d+)
\s*$
}x) {
$bsize = $1;
last;
}
}
$fsb = int($poffset*512/$bsize);
Ich habe jetzt die Blockgröße des Dateisystems in $bsize und die Nummer des Blocks in $fsb. Als nächstes versuche ich mit debugfs rauszukriegen, welche Datei den Inode an Positon $poffset in Partition $part (bzw. den Block $fsb im Dateisystem) belegt. Das ist etwas kniffelig, da debugfs eigentlich interaktiv bedient wird. Hier kommt mir expect zu Hilfe, genauer Expect.pm.
Vorher noch ein kleiner Ausflug zu debugfs. Wenn ich das Programm aufrufe, habe ich eine Shell, in der ich mit Dateisystemen interessante Sachen machen kann. Mit open /dev/hda1 sage ich debugfs z.B., dass ich mit der ersten Partition der ersten IDE-Platte arbeiten möchte. Mit icheck 33 teile ich ihm mit, dass ich wissen möchte, welcher Inode den Dateisystemblock 33 benutzt. Die Aussage lautet dann z.B. 33 11, was bedeutet, dass der Inode 11 den Block 33 benutzt. Oder ich bekomme zurück 33 <block not found>, was bedeutet, dass der Dateisystemblock im Moment nicht verwendet wird. In diesem Fall kann ich abbrechen. Habe ich eine Inode-Nummer bekommen, kann ich z.B. mit ncheck 11 herausbekommen, welche Datei sich hinter Inode 11 dieses Dateisystems verbirgt. Das ist genau, was ich wissen will:
use Expect;
$robot = Expect->spawn('debugfs');
@pattern = $robot->expect(10
,'-re'
,'debugfs:\s+');
$robot->send_slow(0, "open $part\n");
@pattern = $robot->expect(10
,'-re'
,'debugfs:\s+');
$robot->send_slow(0, "icheck $fsb\n");
@pattern = $robot->expect(100
,'-re'
,'debugfs:\s+');
if ($pattern[3] =~ /^$fsb\s+(\d+)\s*$/m) {
my $inode = $1;
$robot->send_slow(0, "ncheck $inode\n");
@pattern = $robot->expect(100
,'-re'
,'debugfs:\s+');
if ($pattern[3]
=~ m{ \s*$inode\s+
(\S+)
\s*
}msx) {
$fname = $1;
}
else {
$fname = "<got $pattern[3]>";
}
}
elsif ($pattern[3]
=~ m{ \s*$fsb\s+
<block\ not\ found>\s*
}msx) {
$fname = "<no inode at $fsb>";
}
else {
$fname = "<got $pattern[3]>";
}
$robot->send("quit\n");
$robot->soft_close();
$robot->hard_close();
Damit habe ich die letzte benötigte Information, den Dateinamen (oder <no inode uses block ...>) in $fname. Falls debugfs unerwartet anders reagiert, als ich es in meiner Einfalt erwartet habe, wird mir auch das mitgeteilt.
Block neu zuweisen
Der Rest ist einfach, ich ziehe es jedoch vor, den Block explizit selbst reallozieren zu lassen, und lasse mir nur eine Empfehlung ausgeben:
if ($fname =~ /^<.+>$/s) {
print "\n$fname\n";
} else {
print "\nBlock $fsb belongs"
. " to $mpoint$fname\n";
}
print <<"EOT";
To reallocate bad block do (as root):
dd if=/dev/zero of=$part bs=$bsize count=1 seek=$fsb
sync
smartctl -t long $disk
and then start over.
EOT
Das war's dann auch schon. Das Skript ermittelt mir in wenigen Sekunden, ob und wenn ja welche Dateien von dem schlechten Block betroffen sind und teilt mir außerdem mit, wie ich den Block durch die Platte ersetzen lassen kann. Dabei schreibt 'dd' auf den Dateisystemblock der Festplatte, der den defekten Plattenblock enthält und sync sorgt dafür, dass der Plattenplatz auch wirklich auf die Festplatte geschrieben wird.
Das komplette Skript finden Sie hier: reallocate_badblocks. Es muss noch mit chmod a+x reallocate_badblocks ausführbar gemacht werden.
Hinweis
Dieser Artikel ist ursprünglich in der Uptimes erschienen, der Mitgliederzeitschrift der German Unix User Group.
Dank
Lars Langhans erinnerte mich daran, dass mittlerweile etliche Programme an verschiedene Sprachen angepasst sind und dann andere Ausgaben bringen. Um dem zu begegnen, wird am Anfang des Skripts $ENV{LC_ALL} = 'C'; gesetzt.
Dieter Stüken machte mich darauf aufmerksam, dass neuere Versionen von dd die Option oflag=direct unterstützen. Damit ist es möglich, am Ende die Platte nur mit 512 Bytes (der Größe eines Festplattensektors) statt mit 1024, 2048 oder 4096 Bytes (der Größe eines Filesystemblocks) zu beschreiben. Von ihm kam auch der Code, der testet, ob das vorhandene dd diese Optionen kennt, und die Empfehlung, am Ende einen Filesystem-Check laufen zu lassen und vor dem Schreiben mit dd durch einen Lesezugriff zu prüfen, ob das Skript die richtige Stelle ermittelt hat.

