Итак, ситуация. На нетбуке установлен Debian Wheezy. Но dpkg думает, что там squeeze(я же монтировал просто поверх /var/, и всё, что там было, осталось неизменным). В итоге dpkg и apt в ступоре. Они смотрят в базы установленных пакетов, сравнивают их с репами и понимают, что надо обновляться. Но при попытке что-нибудь установить или обновить, dpkg проверяет установленные файлы и понимает, что они не соответствуют тому, что у него в базе(версии-то разные) и отказывается что-либо делать. И я его понимаю. Например, он ругается на libc6. Он же видит, что у меня динамический линковщик ссылается на определённую библиотеку. Но у него в базе говорится, что стоит другая библиотека. Поэтому dpkg честно выдаёт "Ты там разберись сперва с тем, что в обход меня поставил, а потом уже обновляйся, а то я тебе всё сломать могу". Итого - система как бы работает, но пакетный менеджер как бы умер.
Есть два выхода. Каким-то образом восстановить dpkg или переустановить всё с нуля, забекапив конфиги. В случае со вторым вариантом, желательно ещё узнать, какие пакет у меня были установлены, чтобы потом не сидеть и не вспоминать, чего там где было. И пока я думал, как же вытащить список пакетов, в голову пришла идея "если у меня будет список пакетов, зачем переустанавливать? Можно же просто записать их в базу dpkg и работать дальше". На том и порешили.
Для начала надо получить список. Чем славится debian(ну и любой нормальный linux)? Правильно. Тем, что у него на каждый пакет обязательно существует документация. И находится она прямо в системе. Не нужно ни интернетов, ни толстых книжек. Всё с собой. А это значит, что мне нужно посмотреть, на что у меня есть доки и я узнаю, какие у меня стоят пакеты. Решение для первой части проблемы есть.
Но это даст мне только названия пакетов. А для dpkg нужно версии, зависимости и ещё целую кучу всякой информации. И лучшее место, где взять эту информацию - репы. Собственно, это всё тоже уже в системе. Мы получаем все версии, описания, зависимости и т.д., когда командуем apt-get update. И, к счастью, для этой команды не важно - есть там что-то в базе установленного или нет. Она работает и так.
Осталось дело за малым - написать скрипт, который пройдётся по докам, соберёт имена, а потом с этими именами пробежится по спискам пакетов из реп, распарсит это дело и сложит в нужном виде в базу dpkg.
Собственно, вот этот скрипт.
#!/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);
syntax highlighted by Code2HTML, v. 0.9.1
Небольшие пояснения. Сначала мы указываем пути, где лежит документация и где списки пакетов, скачанные с реп при обновлении. Потом получаем список пакетов и начинаем по очереди для каждого из них искать информацию, складывать её в хеш, из которого потом в свою очередь записывать в файл в формате, необходимом dpkg. Вот и всё. Запускается скрипт без параметров, работает долго, на выходе выдаёт файл status, который надо положить в /var/lib/dpkg/.
Какие можно из всего этого сделать выводы?
1. Не начинайте делать серьёзных вещей в два-три часа ночи. По невнимательности можно допустить несколько казалось бы мелких ошибки, из-за которых потом придётся долго отдуваться.
2. GNU/Linux - система неубиваемая. Даже после конца света, она будет работать, хотя, может быть, людей уже и не останется.