Bei Debian wird jedem Softwarepaket eine Versionsnummer zugeordnet, um neuere Pakete von älteren zu unterscheiden. Seltsamerweise gibt es aber kaum zwei Programme in Debian, die sich einig sind, was die zeitliche Abfolge der Versionen betrifft. Solche Abweichungen können im Extremfall zu Inkonsistenzen in der Paketverwaltung führen.
Debian-Versionen bestehen grob aus drei Teilen: Am Anfang steht die sogenannte Epoche (eine Zahl), danach folgt ein Doppelpunkt „:“, danach die Upstream-Version, danach die Debian-interne Revisionsnummer, die durch einen Bindestrich „-“ abgetrennt wird. Die Epoche kann auch weggelassen werden (samt Doppelpunkt; das ist sogar der Regelfall), dann wird Null angenommen.
Die Reihenfolge der Sortierung der Versionsnummern ist im wesentlichen lexikographisch. Aufeinanderfolgende Ziffern in der Versionsnummer werden en bloc als Zahl verglichen, so daß „12“ größer als „9“ ist (was in einer rein lexikographischen Ordnung nicht der Fall wäre). Außerdem werden ein paar Sonderzeichen speziell behandelt.
Das Verfahren wird genauer in der Debian Policy beschrieben. Aber offenbar nicht genau genug.
Drei verschiedene Implementierungen dieses Algorithmus sind mir in letzter Zeit untergekommen, mit sich in Grenzfällen unterscheidendem Verhalten:
Der Paketmanager dpkg, der auf unterer Ebene für die Installation und Konfiguration der Pakete zuständig ist, hat einen eingebauten Maximalwert für die Epoche (für C-Programmierer: ULONG_MAX
). Wird dieser überschritten, wird stillschweigend der Maximalwert beim Versionsvergleich verwendet. Nicht nur, daß das nicht dem in den Richtlinien festgelegten Verhalten entspricht, manche Paketversionen sind deswegen laut dpkg auf 32-Bit-Plattformen gleich, aber nicht auf 64-Bit-Plattformen:
$ uname -m i686 $ dpkg --compare-versions 8888888888:1 = 9999999999:1 $ echo $? 0
$ uname -m ia64 $ dpkg --compare-versions 8888888888:1 = 9999999999:1 $ echo $? 1
(„0“ bedeutet hier „wahr“, „1“ „falsch“ – zugegenermaßen verwirrend, wenn man gerade noch C-Code angestarrt hat.)
APT, der auf dpkg aufsetzende Paketmanager, der unter anderem für die Systemaktualisierung aller Pakete und das Herunterladen von Paketen aus dem Netz (einschließlich aller Abhängigkeiten) zuständig ist, hat eine geringfügig andere Interpretation. Löblicherweise stellt APT eine Bibliothek zur Verfügung (ich testete nur das Debian-Paket python-apt
), aber oh weh:
$ cat > test.py import apt_pkg,sys,re,string apt_pkg.InitConfig() apt_pkg.InitSystem() print apt_pkg.VersionCompare("1.0-1", "0:1.0-1") ^D $ python test.py -1
APT sortiert also Versionen ohne Epoche früher ein als solche mit, obwohl die Richtlinien vorschreiben, daß die fehlende Epoche der Epoche 0 entspricht.
Da katie
, die Verwaltungssoftware für das Debian-Archiv, intern offenbar python-apt
verwendet, dürfte dieses Verhalten ziemlich maßgeblich sein (auch wenn dpkg solche Versionen korrekt als gleich interpretiert).
ara
, eine Reimplementierung weiter Teile von apt-cache
in Objective Caml, entfernt führende Nullen nicht von den numerischen Komponenten:
# compare_versions "1.1" "1.01";; - : int = -1 # compare_versions "1.0a0" "1.0a";; - : int = 1
(Hier heißt „-1“ „die erste Version ist früher“, und „1” „die zweite Version ist später“, also die strcmp
-Konvention.)
Laut Spezifikation (und APT und dpkg) sind solche Versionen aber gleich, auch wenn dies verwirrend erscheinen mag.
Glücklicherweise scheinen diese Abweichungen keine Rolle zu spielen, zumindest sind aus der Praxis noch keine Probleme daraus entstanden. Ärgerlich ist es trotzdem, wenn man den Versionsvergleich in eigener Software benötigt und das irgendwie implementieren muß.
Meine erste Python-Implementierung hatte den Fehler, daß sie die Sonderzeichen wie „.“ nicht gesondert behandelte. Das nahm ich als Warnung und wollte daher unbedingt offiziellen Code wiederverwenden (und zwar auf die gute, alte Freie-Software-Methode: per Cut & Paste). Mein Erstaunen war folglich groß, als ich die Abweichung zwischen APT und dpkg entdeckte, zumal der APT-Code auf besagte Weise aus dem dpkg-Code entstanden war. Im Moment verwende ich jedenfalls die Variante aus APT, da sie sich besser integrieren läßt und das Problem mit den großen Epochen einen kleinen Eingriff in dpkg erfordert, bevor es behoben werden kann. Ein Zweizeiler ist das nicht, vielmehr muß beispielsweise der für APT geänderte Code wieder zurückportiert werden.
Da sich das Verhalten von dpkg und APT sich nicht kurzfristig angleichen läßt, erscheint es sinnvoll, Versionsnummern mit bekanntermaßen widersprüchlichen Interpretationen zu verbieten. Debian Bug #336342 versucht, dies zu erreichen. Langfristig sollten natürlich alle Programme so angepaßt werden, daß sie dasselbe Verhalten zeigen. Derartige Änderungen werden aber erst nach der nächsten offiziellen Debian-Version wirksam (und wer Debian kennt, weiß, daß das wirklich längere Zeiträume sind), weil bei veröffentlichten Versionen solche doch etwas pikanten Änderungen völlig zurecht tabu sind.
Ich verstehe dieses kleine Abenteuer auch als Warnung, auch bei simplen Algorithmen stets das Verhalten in den Grenzbereichen über sorgfältige ausgewählte Tests festzunageln. Selbst wenn die Spezifikation ziemlich eindeutig ist, gibt es trotzdem jede Menge Abweichungen in den Details. Andererseits glaube ich nicht, daß hier das prinzipielle Problem bei der Wiederverwertung durch Cut & Paste liegt. Die Schnittstellenanpassung wäre auch bei moderneren Entwicklungsmethoden fällig gewesen, so daß sich vielleicht eine ähnliche Abweichung eingeschlichen hätte.
PS: Wie man auf so etwas stößt? Das geschieht, wenn man versionsbasierte Sicherheitshinweise für Debian implementiert.
2005-11-03 20:45: veröffentlicht
2005-12-19: Postskriptum hinzugefügt.