Разные, на мой взгляд, интересные замечания, связанные с компьютерами в частности и техникой вообще.

вторник, 21 февраля 2012 г.

Восстановление базы установленных пакетов в 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.

Собственно, вот этот скрипт.

#!/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 - система неубиваемая. Даже после конца света, она будет работать, хотя, может быть, людей уже и не останется.

3 комментария:

  1. ...И что мы тут видим....? - правильно, что после девяти вечера от меня никакого толку.... (с)Шелдон Купер

    p.s.тем не менее работа проделана не слаааабая... =)

    ОтветитьУдалить
  2. Скрипт выдает ошибки в 12 и 62 строках...
    (Name "main::multiple" used only once: possible typo at ./recov2.sh line 62.
    Reading all installed packages.
    Couldn't open usr/share/doc/ : No such file or directory at ./recov2.sh line 12.)

    ОтветитьУдалить