Wer auf Debian- und Ubuntu-Rechnern ein Upgrade anschiebt, setzt im Hintergrund eine Reihe unsichtbarer Mechanismen in Gang, die sich gegenseitig bedingen und die Apt-Spezialist und -Maintainer Michael Vogt in diesem Artikel im Detail erklärt.
Apt ist nichtirgendein Werkzeug zur Softwareverwaltung, sondern schlicht das Standardwerkzeug zur Paketverwaltung auf Debian-basierten Systemen wie Ubuntu, Mint und Debian selbst. Mit »apt-rpm« existiert zudem eine RPM-Portierung. Apt entstand 1998, eine Gruppe von Debian- und Ubuntu-Programmierern entwickelt es seitdem kontinuierlich weiter. Eine seiner zentralen Aufgaben besteht im Installieren und Aktualisieren von Software.
Architektonisches
Apt arbeitet mit dem Low-Level-Tool »dpkg« zusammen. Apt ruft es auf, um lokale ».deb« -Pakete auf dem Dateisystem zu installieren oder zu löschen. Außerdem überprüft »dpkg« , ob Dateipfade kollidieren und ob es jede Datei einem Paket zuordnen kann. Daneben ruft es vor oder nach einem Lösch- oder Installationsprozess die so genannten Maintainer-Skripte Preinst, Postinst, Prerm und Postrm auf und kümmert sich um das Verarbeiten von Triggern, die in bestimmten Installationsfällen Aktionen auslösen, beispielsweise die Manpage aktualisieren.
Apt selbst behält hingegen die größeren Zusammenhänge im Auge. Es gruppiert Pakete in Repositories und stellt sicher, dass diese sich darin authentifizieren lassen. Es erlaubt zudem, in den lokalisierten Paketbeschreibungen zu suchen, und löst Abhängigkeiten zwischen Paketen auf. Es ist auch dafür zuständig, Pakete auf das lokale System zu kopieren. Dazu stehen Apt verschiedene Transportprotokolle zur Verfügung, etwa HTTP(S), FTP, SSH oder bewegliche Medien wie CD-ROMs und DVDs.
Aktualisierungen
Zu den großen Aufgaben von Apt gehört aber, die Software eines Rechners aktuell zu halten. Dazu prüft es, welche Version eines Pakets lokal installiert ist und welche in einer der konfigurierten Paketquellen steckt. Grob lassen sich zwei Aktualisierungsvarianten unterscheiden. Zum einen spielt Apt Updates und Upgrades für die installierte Software einer Distribution auf den Rechner. Zum anderen kümmert es sich um Upgrades auf eine neue Version der Distribution.
Das Einspielen von Aktualisierungen für eine installierte Distribution ist einfach und erfolgt über »sudo apt-get upgrade« . Dabei aktualisiert Apt nur vorhandene Softwarepakete, installiert aber keine neuen oder löscht überflüssige. Vereinzelt bestehen für Pakete, etwa den Kernel bei ABI-Änderungen, jedoch neue Abhängigkeiten, dann ist der Aufruf von »sudo apt-get dist-upgrade« notwendig.
Aber nicht nur dazu: Auch die Aktualisierung auf eine neue Version einer Distribution funktioniert über »sudo apt-get dist-upgrade« , allerdings erst nach dem Anpassen der Datei »/etc/apt/sources.list« . Hierbei aktualisieren Debian beziehungsweise Ubuntu eine große Zahl von Paketen, es kommen zahlreiche neue Abhängigkeiten hinzu, welche die Distributionen auflösen, indem sie neue Pakete einspielen. Dabei kommt es zu wiederholten Paketkonflikten, die Apt unter anderem löst, indem es zahlreiche veraltete Pakete löscht.
Aufbau des Cache
Intern führt Apt ein Verzeichnis mit den aktuellen Paketinformationen (Abbildung 1). Dazu liest es alle Indexdateien der aktivierten Paketquellen aus dem Verzeichnis »/var/lib/apt/lists/« und bildet diese in einer internen Struktur ab. Zusätzlich verinnerlicht Apt die Statusinformationen der lokal installierten Pakete über die Datei »/var/lib/dpkg/status« .

Abbildung 1: Apt verschafft sich mit Hilfe verschiedener Indexdateien einen Überblick der vorhandenen Pakete und ihrer Abhängigkeiten untereinander und erzeugt daraus den Dependency-Cache.
Diese Daten speichert es dann per Memory Mapping (MMap) in »/var/cache/apt/pkgcache.bin« . Ändern sich die Paketinformationen nicht, nutzt das System diesen MMap-Cache, was wiederholte Zugriffe beschleunigt. Danach erzeugt Apt dynamisch einen Dependency-Cache, der Informationen über Abhängigkeiten der Pakete untereinander enthält.
Apt kennt Paketgruppen, Pakete und Versionen. Da moderne x86-Prozessoren sowohl 32- als auch 64-Bit-Programme ausführen, kann eine Paketgruppe Pakete für verschiedene Architekturen enthalten. Das einzelne Paket ist dagegen einer Architektur zugeordnet, kann aber in mehreren Versionen vorliegen.
Apt nutzt eine konfigurierbare Policy, um die Candidate-Version zu finden, wobei die neueste Version in der Regel auch der gesuchte Kandidat ist. Zugleich liest Apt aus dem »dpkg-status« , welche Paketversion auf einem Rechner installiert ist, und stellt diese Information im Cache zur Verfügung.
Graduelle Abhängigkeiten
Um Beziehungen zwischen Paketen zu definieren, nutzt Debians Paketmanagement ein feingliedriges System (siehe Kasten “Abhängigkeiten im Detail”) von Attributen wie Depends, Recommends, Suggests und Enhances, Konflikte definiert das System über Conflicts oder Breaks. Diese Attribute legen für ein Paket dessen Abhängigkeiten fest.
Abhängigkeiten im Detail
Bei der Installation speichert »dpkg« für jedes Paket den aktuellen Status. Wird das Deb-Paket zunächst ausgepackt, setzt »dpkg« dessen internen Zustand auf »unpacked« . Anschließend konfiguriert es das Paket, indem es das Maintainer-Skript »DEBIAN/postinst« aufruft.
Erst wenn dies fehlerfrei geschehen ist, weist »dpkg« dem Paket den Zustand »installed« zu. In der Datei »/var/log/dpkg.log« lassen sich die Übergänge zwischen den (internen) »dpkg« -Zuständen beobachten.
Das Depends-Attribut signalisiert eine starke Abhängigkeit: Die dort genannten Pakete müssen zwingend installiert sein, damit ein Paket funktioniert. Das Recommends-Attribut stellt eine schwächere Form der Abhängigkeit dar. Hier funktioniert ein Paket auch dann, wenn die Abhängigkeit nicht installiert ist, aber eventuell fehlt es dann an wichtiger Funktionalität. Apt installiert Recommends-Empfehlungen immer mit, über die Option »–no-install-recommends« lässt sich diese Option jedoch manuell abschalten.
Wer nachschauen will, welche Recommends derzeit nicht installiert sind, verwendet den Befehl
sudo apt-get install --fix-policy
Suggests unterliegen einer sehr schwachen Form von Abhängigkeit. Der Admin installiert solche Pakete, indem er die Option »–install-suggests« aktiviert, und
sudo apt-get install --install-suggests --fix-policy
schaut nach, welche »suggests« auf dem System fehlen – in der Regel sind das recht viele.
Kommt es zu Konflikten, wird zwischen Conflicts und Breaks unterschieden. Ein Konflikt tritt auf, wenn sich zwei Pakete, die identische Dateien enthalten, zur selben Zeit auf dem Dateisystem befinden, etwa »/usr/sbin/sendmail« . Ein Break weist auf einen schwächeren Konflikt hin: Er tritt auf, wenn sich zwei Pakete zur gleichen Zeit im Installed-Zustand befinden – unpacked dürfen sie hingegen sein.
Typischerweise nutzen Breaks eine Versionsnummer, zum Beispiel »Break: foo (<< 1.0)« . So kann ein Paket mitteilen, dass es zum Beispiel ein Interface verändert, wodurch es für ältere Versionen anderer Software, die dieses Interface ebenfalls nutzen, unbenutzbar wird. Apt versucht daher im obigen Beispiel, das Paket »foo« auf eine Version 1.0 oder besser zu aktualisieren.
Installation und Upgrades
Gibt der User eine Installation oder ein Upgrade in Auftrag, schaut Apt zunächst in seinem Cache nach, ob es den Paketnamen kennt. Abhängig von der Policy wählt es die Candidate-Version aus und markiert diese für eine Installation oder ein Upgrade. Als Nächstes iteriert Apt über die Liste der Abhängigkeiten und markiert alle noch nicht erfüllten oder neuen Abhängigkeiten zur Installation. Alle negativen Abhängigkeiten (wie Conflicts und Breaks) markiert es zum Löschen.
Der Admin kann Apt bei diesem Teil der Arbeit beobachten, indem er das Flag »-o Debug::pkgDepCache::AutoInstall=true« setzt. Listing 1 visualisiert über die eingerückten »Installing« -Anweisungen, wie Libapt die Abhängigkeiten auflöst, indem sie Schritt für Schritt den Abhängigkeitsbaum durchwandert. Noch weitere Details fördert »-o Debug::pkgDepCache::Marker=1« zutage.
Listing 1
Abhängigkeitsauflösung
01 $ apt-get -o Debug::pkgDepCache::AutoInstall=true install apt-clone 02 Reading package lists... Done 03 Building dependency tree 04 Reading state information... Done 05 Installing python2.7 as Depends of apt-clone 06 Installing mime-support as Depends of python2.7 07 Installing libexpat1 as Depends of python2.7 08 Installing python as Depends of apt-clone 09 Installing lsb-release as Depends of apt-clone 10 Installing python-apt as Depends of apt-clone 11 Installing libapt-inst1.4 as Depends of python-apt 12 Installing python-apt-common as Depends of python-apt 13 [...]
Anders als bei einer Installation betrifft ein Upgrade meist eine große Zahl von Paketen. Das Kommandozeilentool Apt-get unterscheidet zwei Arten von Upgrades: »sudo apt-get upgrade« installiert alle neuen Versionen von Software, die nicht die Installation oder das Löschen weiterer Pakete erfordern. Beim Kommando »sudo apt-get dist-upgrade« gibt es diese Einschränkung nicht. Weil etwa ein neuer Kernel aufgrund von ABI-Änderungen auch die stabilen Varianten neuer Pakete einfordert, ist ein »sudo apt-get dist-upgrade« für Upgrades im Allgemeinen die bessere Wahl.
Der Algorithmus für »upgrade« arbeitet sehr einfach: Er iteriert über alle installierten Pakete und markiert sie für ein Upgrade, wobei er die Option »AutoInstall« explizit ausklammert. Der »ProblemResolver« setzt dann problematische Abhängigkeit zurück auf »keep« .
Der Algorithmus für »dist-upgrade« arbeitet ähnlich, allerdings nutzt er »AutoInstall« , um neue Abhängigkeiten zu installieren. Auch neue Pakete vom Typ »Essential: yes« landen damit auf dem Rechner. Anschließend startet der »ProblemResolver« , um die verbleibenden fehlerhaften Abhängigkeiten in Ordnung zu bringen.
Der Problemlöser
Operationen wie »install« , »dist-upgrade« oder »remove« führen unter Umständen dazu, dass der Cache inkonsistent wird, das heißt, Abhängigkeiten verletzt oder Konflikte nicht aufgelöst werden. Dies kann zum Beispiel passieren, wenn die Installation von Paket A ein Paket B installiert, das in Konflikt mit einem bereits installierten Paket steht.
An dieser Stelle kommt der so genannte Problem-Resolver von Apt ins Spiel. Er soll eine Lösung für die Inkonsistenzen finden, seine Handlungen lassen sich über »-o Debug::pkgProblemResolver=true« beobachten. Neben dem Problem-Resolver gibt es noch weitere Implementierungen: So bringt Aptitude eine eigene Variante mit, daneben gibt es externe Tools wie »aspcud« .
Pakete mit Punkten
Der Problem-Resolver weist jedem Paket einen Punktewert zu (Score), der seine Wichtigkeit ausdrückt. Eine hohe Zahl verweist auf ein wichtiges Paket. Um die Scores einzelner Pakete zu betrachten, ruft man die Option »-o Debug::pkgProblemResolver::ShowScores=true« auf. Die Berechnung dieses Wertes ist relativ kompliziert, denn es fließen einige Faktoren ein wie die Priority und das »Essential: yes« -Flag. Auch erhalten installierte Pakete eine höhere Priorität.
Als Nächstes wird untersucht, wie viele andere Pakete ein bestimmtes Paket als Abhängigkeiten voraussetzen (Reverse Dependencies). Jede Abhängigkeit erhöht den Score. Dies stellt sicher, dass zentrale Bibliotheken einen hohen Stellenwert erhalten. So hat etwa die »libgcc1« auf einem typischen Desktop einen Wert von mehr als 2000.
An dieser Stelle werden auch Provides berücksichtigt. Zum Beispiel erhalten Pakete, die den »notification-daemon« in ihrer Provides-Liste führen, einen höheren Score, da viele Pakete vom Notification-Daemon abhängen, es aber kein echtes Paket dafür gibt. Eine weitere Rolle spielen die Wünsche des Users: Wählt er ein Paket explizit zur Installation oder zum Löschen aus, steigt der Wert, um den Wunsch zu berücksichtigen.
Löschen oder behalten
Ist die Wertigkeit der Pakete festgestellt, beginnt die eigentliche Arbeit des Problem-Resolvers. Er iteriert über die nach Punkten sortierten Pakete. Trifft er auf nicht erfüllte Abhängigkeiten oder Konflikte, übernimmt Apt und prüft die am stärksten betroffenen Exemplare einzeln. Es versucht nicht erfüllte Abhängigkeiten mit Hilfe von »keep« oder »delete« zu lösen. Bleibt dies erfolglos, versucht Apt als Nächstes, das Paket mit den nicht erfüllten Abhängigkeiten zurückzuhalten oder zu löschen. Dies wiederholt es so lange, bis der Paket-Cache einen konsistenten Zustand erreicht.
Listing 2 zeigt den Problem-Resolver von Ubuntu 12.04 in einer Pbuilder-Chroot-Umgebung bei der Arbeit. Hier lässt sich gut beobachten, wie das Löschen von »dpkg-dev« dazu führt, dass das ebenfalls installierte Paket »build-essential« jetzt eine verletzte Abhängigkeit aufweist (es hängt von »dpkg-dev« ab). Der Resolver entscheidet daraufhin »build-essential« ebenfalls zu löschen, um das System wieder in einen konsistenten Zustand zu versetzen.
Listing 2
Der Problemlöser
01 $ apt-get remove -o Debug::pkgProblemResolver=true dpkg-dev 02 Reading package lists... Done 03 Building dependency tree 04 Reading state information... Done 05 Starting 06 Starting 2 07 Investigating (0) build-essential [ amd64 ] < 11.5ubuntu2.1 > ( devel ) 08 Broken build-essential:amd64 Depends on dpkg-dev [ amd64 ] < 1.16.1.2ubuntu7.1 > ( utils ) (>= 1.13.5) 09 Considering dpkg-dev:amd64 10001 as a solution to build-essential:amd64 0 10 Removing build-essential:amd64 rather than change dpkg-dev:amd64 11 Done
Die Zahlen »10001« hinter »dpkg-dev« und »0« hinter »build-essential« geben jeweils die Wertigkeit des Pakets an. Der hohe Wert von »dpkg-dev« rührt daher, dass ein expliziter Wunsch des Users existiert, das Paket zu löschen: Der Problemlöser verändert seinen Zustand daher nicht, sondern passt vielmehr die anderen Pakete mit niedrigerer Wertigkeit an.
Auf los! geht’s los
Befindet sich der Cache endlich in einem konsistenten Zustand, pflegt Apt die Änderungen ein. Hierzu unterteilt das Tool die Installation in die Schritte »remove« , »unpack« und »configure« , um dann »dpkg« für die einzelnen Pakete aufzurufen. Dabei fasst Apt Pakete in Gruppen zusammen, sodass sich »dpkg« auf größere Gruppen von ihnen anwenden lässt, was die Installation beschleunigt. Dank der Option »-o Debug::pkgDpkgPM=1« kann ein Admin im Vorfeld verfolgen, was »dpkg« tun würde.
In Listing 3 lässt sich beobachten, dass Apt »dpkg« aufruft und damit explizit die Architektur im Paketnamen angibt, um auf einem Multiarchitektur-System eindeutige Paketnamen zu erzeugen.
Listing 3
Apt löscht
01 $ apt-get remove -o Debug::pkgDpkgPM=true dpkg-dev 02 Reading package lists... Done 03 Building dependency tree 04 Reading state information... Done 05 The following packages will be REMOVED: 06 build-essential dpkg-dev 07 0 upgraded, 0 newly installed, 2 to remove and 0 not upgraded. 08 After this operation, 1228 kB disk space will be freed. 09 Do you want to continue [Y/n]? 10 /usr/bin/dpkg --status-fd 57 --force-depends --force-remove-essential --remove build-essential:amd64 dpkg-dev:all
Spezialfälle
Für Ubuntu gibt es zusätzlich das Paket »ubuntu-release-upgrader« . Es bringt das Kommando »sudo do-release-upgrade« mit, über das ein Ubuntu-Nutzer sein System auf eine neue Version aktualisiert. Ergänzt er die Option »–devel« , springt Ubuntu auf die Entwicklungsversion, zurzeit die 13.10.
Der Release-Upgrader übernimmt Aufgaben, die sonst ein Admin erledigen müsste, etwa das Aktualisieren der Konfigurationsdatei für die Paketquellen (»/etc/apt/sources.list« ). Zudem löst er Sonderfälle beim Berechnen der Aktualisierung. So stellt er sicher, dass auf einem Desktop-Ubuntu stets das Metapaket »ubuntu-desktop« existiert.
Daneben deckt der Release-Upgrader Spezialfälle ab. Ein solcher tritt zum Beispiel ein, wenn ein Nutzer vom proprietären »fglrx« – auf den freien »ati« -Grafikkartentreiber wechselt, weil die neue Version des proprietären Treibers die Hardware des Rechners nicht mehr unterstützt. Intern benutzt der Release-Upgrader die Bibliothek Libapt, um das Upgrade zu berechnen und Konflikte zu lösen.
Unattended Upgrades
Ein weiterer Spezialfall entsteht mit dem Wunsch, Updates automatisch vom System installieren zu lassen. Dafür sorgen Unattended Upgrades – wenn man sie entsprechend einrichtet. Sie installieren meist Sicherheitsupdates für stabile Versionen von Debian oder Ubuntu. Automatische Sicherheitsupdates sind per
sudo dpkg-reconfigure -plow unattended-upgrades
zu aktivieren. Es gibt noch weitere Konfigurationsoptionen, die der Admin in der Datei »/etc/apt/apt.conf.d/50unattended -upgrades« einrichtet. Mehr Informationen zum Thema Paketmanager gibt der Artikel zu Beginn des Schwerpunkts.
Apt plus Python
Neben den Debug-Optionen für »apt-get« lässt sich nicht zuletzt mittels »python-apt« genau beobachten, wie das System arbeitet. Das Skript aus Listing 4 verdeutlicht dies, indem es »ProblemResolver« aktiviert (Zeile 3), das Paket »dbus« löscht (Zeile 6), die kaputten Abhängigkeiten im Cache betrachtet (Zeile 7) und den »ProblemResolver« erneut startet, um das Hindernis zu beseitigen (Zeile 9). Ein finaler Check (Zeile 10) zeigt, dass der Cache keine kaputten Pakete mehr enthält.
Listing 4
Python und Apt
01 #!/usr/bin/python
02 import apt
03 apt.apt_pkg.config.set("Debug::pkgProblemResolver", "1")
04 cache = apt.Cache()
05 pkgname="dbus"
06 cache[pkgname].mark_delete(auto_fix=False)
07 print cache.broken_count
08 resolver=apt.cache.ProblemResolver(cache)
09 resolver.resolve()
10 print cache.broken_count






