cppmm 21.02.2012 08:17

СкриптыВосстановление базы установленных пакетов в debian

Сегодня ночью немного попрактиковался в костылеписании из-за собственной невнимательности. Подумал, может быть тут тоже кому будет интересно.
Под катом скрипт для восстановления базы установленных пакетов dpkg в debian'е(и краткая история его появления).

Решил тут перейти на нетбуке со стабильной ветки debian'а на тестинг. Так как на моей eeepc'шке всего 4 гига флешка внутри, места для dist-upgrade не хватило бы. Но я уже обновлял его с lenny до squeeze в своё время, так что это не проблема. Беру обычную флешку, форматирую в ext3, подключаю и тут я допускаю первую ошибку. Вместо того, чтобы просто примонтировать эту флешку как /var/cache/apt/archives, я копирую на неё содержимое /var/ и командую mount /dev/sdb1 /var/. Само по себе это тоже не проблема. Я спокойненько обновился, отмонтировал флешку и ребутнулся. Это было второй ошибкой. Дело в том, что помимо всего прочего на /var/ лежит база установленных пакетов dpkg. Находится она в /var/lib/dpkg/status. И тут я понимаю, что после того, как система обновилась, я допустил третью ошибку. Мне понадобилась флешка и я её снова форматнул.
Итак, ситуация. На нетбуке установлен Debian Wheezy. Но dpkg думает, что там squeeze(я же монтировал просто поверх /var/, и всё, что там было, осталось неизменным). В итоге dpkg и apt в ступоре. Они смотрят в базы установленных пакетов, сравнивают их с репами и понимают, что надо обновляться. Но при попытке что-нибудь установить или обновить, dpkg проверяет установленные файлы и понимает, что они не соответствуют тому, что у него в базе(версии-то разные) и отказывается что-либо делать. И я его понимаю. Например, он ругается на libc6. Он же видит, что у меня динамический линковщик ссылается на определённую библиотеку. Но у него в базе говорится, что стоит другая библиотека. Поэтому dpkg честно выдаёт "Ты там разберись сперва с тем, что в обход меня поставил, а потом уже обновляйся, а то я тебе всё сломать могу". Итого - система как бы работает, но пакетный менеджер как бы умер.
Есть два выхода. Каким-то образом восстановить dpkg или переустановить всё с нуля, забекапив конфиги. В случае со вторым вариантом, желательно ещё узнать, какие пакет у меня были установлены, чтобы потом не сидеть и не вспоминать, чего там где было. И пока я думал, как же вытащить список пакетов, в голову пришла идея "если у меня будет список пакетов, зачем переустанавливать? Можно же просто записать их в базу dpkg и работать дальше". На том и порешили.
Для начала надо получить список. Чем славится debian(ну и любой нормальный linux)? Правильно. Тем, что у него на каждый пакет обязательно существует документация. И находится она прямо в системе. Не нужно ни интернетов, ни толстых книжек. Всё с собой. А это значит, что мне нужно посмотреть, на что у меня есть доки и я узнаю, какие у меня стоят пакеты. Решение для первой части проблемы есть.
Но это даст мне только названия пакетов. А для dpkg нужно версии, зависимости и ещё целую кучу всякой информации. И лучшее место, где взять эту информацию - репы. Собственно, это всё тоже уже в системе. Мы получаем все версии, описания, зависимости и т.д., когда командуем apt-get update. И, к счастью, для этой команды не важно - есть там что-то в базе установленного или нет. Она работает и так.
Осталось дело за малым - написать скрипт, который пройдётся по докам, соберёт имена, а потом с этими именами пробежится по спискам пакетов из реп, распарсит это дело и сложит в нужном виде в базу dpkg.
Собственно, вот этот скрипт.
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#!/usr/bin/perl -w
#

my $docsdir = "usr/share/doc/";
my $listsdir = "var/lib/apt/lists/";

my (@packages, @lists);

# Get package names
print "Reading all installed packages.\n";
opendir(DOCSDIR, "$docsdir") or die "Couldn't open $docsdir : $!";
while (defined(my $packagename = readdir(DOCSDIR))) {
push(@packages, $packagename) if (!($packagename =~ m/([A-Z]|^debian$|\.+)/));
print ".";
}
print "\n";
closedir(DOCSDIR);
print "Reading complete.\n";
my $p_count = $#packages + 1;
print "There was $p_count packages found.\n";
undef $p_count;

# Get package indexes
opendir(LISTSDIR, "$listsdir") or die "Couldn't open $listsdir : $!";
while (defined(my $listname = readdir(LISTSDIR))) {
push(@lists, $listname) if ($listname =~ m/Packages/);
}
closedir(LISTSDIR);

# Searching information and make new dpkg status file.
open(my $statusfile, '>', 'status');
foreach my $package (@packages) {
my %information;
foreach my $list (@lists) {
my $filepath = join('/', $listsdir, $list);
open(my $listfile, '<', $filepath);
my $write = 0;
my $multiline = 0;
while ( defined($line = readline($listfile)) ) {
if (grep(/^Package: $package$/, $line)) {
print "Package $package found in list $list.\n";
$write = 1;
}
if ( ($write) && (!$multiline) ) {
if ( $line =~ m/^Tag/ ) {
$multiline = 1;
} else {
my @data = split(/:\s/, $line);
$information{"$data[0]"} = $data[1];
}
}
if ( ($write) && ($multiline) ) {
if ( $line =~ m/^Tag/ ) {
my @data = split(/:\s/, $line);
$information{"$data[0]"} = $data[1];
} elsif ( $line =~ m/^\s/ ) {
$information{"Tag"} = join('', $information{"Tag"}, $line);
} else {
$multiple = 0;
}
}
$write = 0 if ($line =~ m/^$/);
}
close($listfile);
}
print { $statusfile } "Package: $information{'Package'}" if (defined($information{"Package"}));
print { $statusfile } "Status: install ok installed\n" if (defined($information{"Package"}));
print { $statusfile } "Priority: $information{'Priority'}" if (defined($information{"Priority"}));
print { $statusfile } "Section: $information{'Section'}" if (defined($information{"Section"}));
print { $statusfile } "Installed-Size: $information{'Installed-Size'}" if (defined($information{"Installed-Size"}));
print { $statusfile } "Maintainer: $information{'Maintainer'}" if (defined($information{"Maintainer"}));
print { $statusfile } "Architecture: $information{'Architecture'}" if (defined($information{"Architecture"}));
print { $statusfile } "Source: $information{'Source'}" if (defined($information{"Source"}));
print { $statusfile } "Version: $information{'Version'}" if (defined($information{"Version"}));
print { $statusfile } "Depends: $information{'Depends'}" if (defined($information{"Depends"}));
print { $statusfile } "Description-md5: $information{'Description-md5'}" if (defined($information{"Description-md5"}));
print { $statusfile } "Homepage: $information{'Homepage'}" if (defined($information{"Homepage"}));
print { $statusfile } "Multi-Arch: $information{'Multi-Arch'}" if (defined($information{"Multi-Arch"}));
print { $statusfile } "\n" if (defined($information{"Package"}));
}
close($statusfile);


Небольшие пояснения. Сначала мы указываем пути, где лежит документация и где списки пакетов, скачанные с реп при обновлении. Потом получаем список пакетов и начинаем по очереди для каждого из них искать информацию, складывать её в хеш, из которого потом в свою очередь записывать в файл в формате, необходимом dpkg. Вот и всё. Запускается скрипт без параметров, работает долго, на выходе выдаёт файл status, который надо положить в /var/lib/dpkg/.

Какие можно из всего этого сделать выводы?
1. Не начинайте делать серьёзных вещей в два-три часа ночи. По невнимательности можно допустить несколько казалось бы мелких ошибки, из-за которых потом придётся долго отдуваться.
2. GNU/Linux - система неубиваемая. Даже после конца света, она будет работать, хотя, может быть, людей уже и не останется.


Тэги: debian perl script
+ 11 -
Похожие Поделиться

Daria 21.02.2012 13:12 #
отличный интригуюший детектив. с удовольствием прочитала.
kmk 25.02.2012 23:24 #
Аналогично, прочитал с удовольствием, но поймал себя на мысли, что сам бы так не извращался, а просто переустановил бы систему за 15 минут. :)