Ключевые слова:ext3, ext2, fs, linux, debug, (найти похожие документы)
From: darkk <http://darkk.livejournal.com>
Date: Mon, 26 Nov 2007 18:21:07 +0000 (UTC)
Subject: Восстановление ext2/ext3 раздела при помощи debugfs.ext2
Оригинал: http://darkk.livejournal.com/28545.html
Дано: битый раздел, на котором лежит N месяцев работы. Бэкапов нет.
e2fsck теряет волю, ситуацию усугубляет растущее кол-во badblock-ов на
винте.
livecd storage # e2fsck -n /dev/hdb1
e2fsck 1.38 (30-Jun-2005)
Superblock has an invalid ext3 journal (inode 8).
Clear? no
e2fsck: Illegal inode number while checking ext3 journal for /dev/hdb1
При попытке сказать yes - e2fsck падал по подобию assert(), когда искал root, к
орневая директория была куском нулей
livecd storage # debugfs -c /dev/hdb1
debugfs 1.38 (30-Jun-2005)
/dev/hdb1: catastrophic mode - not reading inode or group bitmaps
debugfs: show_super_stats -h
...
Inode count: 7340032
Block count: 14659304
...
Узнаем свободные inode и получаем листинг условно-живых директорий
livecd storage # for ((i=2; $i <= 7340032; i++)); do echo testi '<'$i'>'; done
> testi.in
livecd storage # debugfs -f testi.in /dev/hdb1 > testi.out
livecd storage # grep 'Inode [0-9]* is marked in use' testi.out | grep -E -o '[
0-9]+' > used.inodes
livecd storage # cat used.inodes | sed 's,.*,ls <&>,' > ls.in
livecd storage # debugfs -f ls.in /dev/hdb1 > ls.out
livecd storage # bzip2 -c ls.out > ls.out.bz2
Крайне полезно этот ls.out залить в sql-базу (например так), чтобы
шустро собирать по нему статистику... У меня размер основной таблицы
entries (inode, name, curdir, size) индексированной базы (схема
прилагается) получился под 100 мегабайт с 60гигового раздела.
Затем можно пополнить базу следующими (НЕ абсолютно верными)
наблюдениями:
INSERT INTO directories SELECT inode, curdir FROM entries
GROUP BY inode HAVING COUNT(*) > 1;
INSERT INTO inodes SELECT inode, COUNT(*) FROM entries GROUP BY inode;
INSERT INTO broken_dirs SELECT entries.inode FROM entries
LEFT JOIN entries AS dirs ON (dirs.inode = entries.inode AND dirs.name = '.')
WHERE entries.name = '..' AND dirs.name IS NULL GROUP BY entries.inode;
INSERT INTO hardlinks SELECT inodes.inode FROM inodes
LEFT JOIN entries ON (inodes.inode = entries.inode AND (entries.name IN ('.', '..')))
WHERE nlink > 1 AND entries.name IS NULL;
Еще кстати будет перелить раздел на новый винт, который не сыпется с
околозвуковой скоростью, при том в двух экземплярах, один для
экспериментов, второй на всякий случай
Теперь ищем детей / (inode-2) и возвращаем их на место
mysql> select * from entries where inode = 2;
+-------+---------+------+------+
| inode | curdir | name | size | # комментарий, изходя из содержания
+-------+---------+------+------+
| 2 | 2392065 | .. | 12 | /var
| 2 | 6275073 | .. | 12 | /home
...
+-------+---------+------+------+
17 rows in set (0.00 sec)
mysql> select * from entries where curdir = 2392065;
+---------+---------+---------+------+
| inode | curdir | name | size |
+---------+---------+---------+------+
| 2392065 | 2392065 | . | 12 |
| 2 | 2392065 | .. | 12 |
| 2392066 | 2392065 | lock | 12 |
| 2392067 | 2392065 | run | 12 |
| 2392068 | 2392065 | backups | 16 |
| 2392069 | 2392065 | cache | 16 |
... # да, это явно /var
+---------+---------+---------+------+
15 rows in set (0.03 sec)
debugfs: clri <2>
debugfs: set_inode_field <2> mode 040755
debugfs: set_inode_field <2> links_count 17
debugfs: find_free_block 1
Free blocks found: 33869
debugfs: set_inode_field <2> bmap[0] 33869
debugfs: set_inode_field <2> blocks 8
debugfs: mkdir <2>
debugfs: ls <2>
12 (12) . 2 (12) .. 12 (4072) <2>
debugfs: unlink <2>/<2>
debugfs: unlink <2>/.
debugfs: unlink <2>/..
debugfs: ls
0 (4096) .
debugfs: ln <2> <2>/.
debugfs: ln <2> <2>/..
debugfs: ls
2 (12) . 2 (4084) ..
debugfs: ln <2392065> <2>/var
skipped
debugfs: ln <6275073> <2>/home
debugfs: ^D
livecd ~ # e2fsck /dev/hda1
А потом устраиваем большое переименование /lost+found
livecd ~ # ls /mnt/slave/lost+found | { echo "lock tables lostnfound
write;" ; sed 's,#,,;s,.*,insert into lostnfound values(&);,'; } |
mysql ext2backup
livecd ~ # { echo "select inode, curdir, name from lostnfound inner
join entries using (inode) where name != '.' and name != '..';" |
mysql ext2backup | while read inode curdir name; do
base="/mnt/slave/smth-found"; echo "mkdir -p \"$base/#$curdir\"; mv
\"/mnt/slave/lost+found/#${inode}\" \"$base/#$curdir/$name\""; done; }
| sed 1d > renamer.sh
livecd ~ # { echo "select lostnfound.inode as lostinode,
if(parent.name <=> null, concat('#', entries.inode), parent.name) from
lostnfound inner join entries on (entries.name = '..' and curdir =
lostnfound.inode) left join entries as parent on (parent.name != '.'
and parent.name != '..' and parent.inode = entries.inode);" | mysql
ext2backup | while read inode curdir; do base="/mnt/slave/smth-found";
echo "mkdir -p \"$base/$curdir\"; mv
\"/mnt/slave/lost+found/#${inode}\" \"$base/$curdir/#$inode\""; done;
} | sed 1d > renamer.sh
После чего почти все в почти читаемом виде с ФС почти восстановлено (в
lost+found у меня осталось после данной махинации процентов 15% от
первоначального объема).
debugfs-ls-scan.pl
#!/usr/bin/perl
use DBI;
use strict;
my $db = DBI->connect("dbi:mysql:database=ext2backup", "root", "")
or die $DBI::errstr;
my $inserter = $db->prepare(
"INSERT INTO `entries`(`inode`,`curdir`,`name`, `size`) VALUES (?,?,?,?)");
my $counter = 0;
my $curdir = undef;
while (<>) {
my @chunks = split / /;
foreach my $chunk (@chunks) {
$chunk =~ s/ +$//;
if ($chunk =~ /^ ?([0-9]+) \(([0-9]+)\) (.*)$/) {
my ($inode, $size, $name) = ($1, $2, $3);
if ($name eq ".") {
$curdir = $inode;
}
# print "($inode, $size, $name) at $curdir\n";
$inserter->execute($inode, $curdir, $name, $size)
or die $DBI::errstr;
}
else {
die "Invalid chunk: $chunk"
}
}
$counter++;
if (($counter % 100) == 0) {
print "$counter lines...\n";
}
}
# vim:set tabstop=4 softtabstop=4 shiftwidth=4:
# vim:set foldmethod=marker foldlevel=32 foldmarker={,}:
debugfs-mysql-schema.sql
-- MySQL dump 10.10
--
-- Host: localhost Database: ext2backup
-- ------------------------------------------------------
-- Server version 5.0.22-Debian_0ubuntu6.06.2-log
--
-- Table structure for table `broken_dirs`
--
DROP TABLE IF EXISTS `broken_dirs`;
CREATE TABLE `broken_dirs` (
`inode` int(11) NOT NULL,
PRIMARY KEY (`inode`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `entries`
--
DROP TABLE IF EXISTS `entries`;
CREATE TABLE `entries` (
`inode` int(11) NOT NULL,
`curdir` int(11) NOT NULL,
`name` varchar(255) character set utf8 collate utf8_unicode_ci NOT NULL,
`size` int(11) NOT NULL,
KEY `inode` (`inode`),
KEY `curdir` (`curdir`),
KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `hardlinks`
--
DROP TABLE IF EXISTS `hardlinks`;
CREATE TABLE `hardlinks` (
`inode` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `inodes`
--
DROP TABLE IF EXISTS `inodes`;
CREATE TABLE `inodes` (
`inode` int(11) NOT NULL,
`nlink` int(11) NOT NULL,
PRIMARY KEY (`inode`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `lostnfound`
--
DROP TABLE IF EXISTS `lostnfound`;
CREATE TABLE `lostnfound` (
`inode` int(11) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;