Integration von Software

Übersicht

Während der Installation eines Linuxsystems erfolgte eine mehr oder minder detaillierte Selektion zu installierender Software. Der Bedarf der nachträglichen (De)Installation verschiedener Pakete wird irgend wann erwachsen, sei es, um die zu klein gewordenen Festplatte von unnötigem Ballast zu befreien oder ein Programm durch eine verbesserte Nachfolgeversion zu ersetzen. Vielleicht lag das gute Stück Software gar nicht meiner Distribution bei? Dann muss es nachträglich ins System eingespielt werden.

I.A. ist es nicht erforderlich, jede Version seiner favorisierten Distribution zu erwerben. Solange diese nicht an ihrer Basis schrauben (bspw. eine neue Version der glibc verwenden), schont die alleinige Aktualisierung der häufig verwendeten Programme nicht nur den Geldbeutel, sondern schließt auch den möglichen Ärger aus, den eine Neuinstallation (bzw. Update) so mit sich bringt. "Never changed a running system" lautet die Maxime des Profi!

Programme unter Unix bestehen nur in seltenen Fällen aus einer einzelnen Datei. Zum Paket zählen zumeist noch Dokumentationen, Bibliotheken, Konfigurationsdateien u.v.m. Als Struktur eines solchen Pakets haben sich im wesentlichen drei Formate etabliert:

Bei Rpm und Deb handelt es sich im Unterschied zu Tar-Archiven um Paket-Manager, d.h. diese Formate kombinieren die reine Archivierung zusammen gehöriger Paketkomponenten mit Methoden zur sauberen (De)Installation und Aktualisierung. Sie helfen ungemein, die Konsistenz der installierten Software zu wahren.

Kompilierte Versionen zu aktualisierter Software bieten die Großen der Branche meist wenige Tage nach deren Freigabe auf ihren Servern ein. Das Herunterladen und Installieren solcher Pakete im »richtigen« Format mit den distributionseigenen Werkzeugen birgt selten Probleme in sich und sollte stets bevorzugt werden. Nicht jede Distribution jedoch misst einem Programm die gleiche Aufmerksamkeit zu. Teils existieren vorkompilierte Pakete nur in einem Format, das ausgerechnet »meine« Distribution nicht unterstützt! In einem solchen Fall kann ein kleines Perl-Skript namens alien Wunder wirken, konvertiert es so manches Paket doch anstandslos in ein Fremdformat.

Wer aus dem vollen Fundus des reichhaltigen Angebots an freier Software schöpfen will, der wird vielfach den tar-Archiven (*.tar, *.tar.gz, *.tgz) begegnen. Der Vorteil des Formats liegt in seiner Unabhängigkeit von jeglicher Dateisystemstruktur. Der gravierende Nachteil gründet sich auf die zumeist fehlende Routine zur sauberen (De)Installation solcher Programme.

Das Tar-Archiv ist auch die bevorzugte Verpackung für Quellcode-Pakete. Hieraus ein lauffähiges Programm zu kompilieren, kann spielend leicht als auch frustrierend bis unmöglich sein. Der Neuling auf dem Gebiet der Programmierung wird einem Fehler hilflos gegenüber stehen. Die Prinzipien, nach denen sich eine Ursache des Fehler eingrenzen lässt, möchten wir daher erläutern.

Source-Pakete

Open Source lebt von der Mitarbeit Freiwilliger. Natürlich ist in erster Linie der Programmierer gefragt, aber die Beihilfe eines jeden in der Testphase garantiert das Aufspüren möglichst vieler Fehler (»Irgend jemand entdeckt den Fehler. Ein anderer wird ihn beheben können.«). Aus diesem Grund gehen die meisten Projekte bereits in einer sehr frühen Entwicklungsphase an die Öffentlichkeit und bieten Schnappschüsse des aktuellsten Standes auf ihren Servern an. Eine als Beta-Version gekennzeichnete Software muss nicht zwingend fehlerbehaftet sein. Oftmals fehlen noch wesentliche Teile, die die Entwickler für notwendig erachten, der eine oder andere Anwender aber nicht benötigt. Warum sollte man das Programm nicht doch schon nutzen?

Derartige Entwicklerversionen werden nahezu ausschließlich als Quelltext angeboten. Was tut man nun, nachdem das Paket auf die lokale Platte geladen wurde?

Das verbreitete Format für Quellcodepakete ist das Tape Archiv. Typische Endungen solcher Dateien lauten "*.tar" bzw. im Fall komprimierter Daten "*.tar.gz" oder "*.tgz". In neuerer Zeit findet man auch "*.tar.bz2", was auf eine Komprimierung mit dem effektiven Burrows-Wheeler-Algorithmus hindeutet. Auch in den Source-Rpm-Paketen ("*.src.rpm"), in welcher Form vielfach die Quellen fertiger Projekte verbreitet werden, stecken letztlich Tape-Archive, mit denen analog zur folgenden Beschreibung zu verfahren ist.

Beginnend mit dem Entpacken des Quellpakets betrachten wir die notwendigen Schritte zu einem lauffähigen Programm anhand von Galeon, einem auf Mozilla basierenden Browser-Projekt. Im Januar 2001 war die Version 09pre3 aktuell.

Entpacken der Quellen

user@sonne> tar xzf galeon-0.9pre3.tar.gz
user@sonne> cd galeon-0.9pre3
user@sonne> ls
ABOUT-NLS       INSTALL      TODO         configure       install-sh     po
AUTHORS         Makefile.am  acconfig.h   configure.in    intl           src
COPYING         Makefile.in  aclocal.m4   galeon.desktop  macros         stamp-h.in
COPYING.README  NEWS         anim         galeon.gnorba   missing        ui
ChangeLog       README       autogen.sh   galeon.spec     mkinstalldirs
FAQ             THANKS       config.h.in  galeon.spec.in  myportal.css

Eine Reihe der enthaltenen Dateien sind typisch für Quellcodepakete. INSTALL sollten Sie auf jeden Fall lesen, beinhaltet die Datei doch eine detaillierte Beschreibung der notwendigen Schritte bis zur Installation der kompilierten Programme. README beschreibt zumeist die Fähigkeiten des Programms und seine Anwendung. In NEWS erfahren Sie die wesentlichen Änderungen der Version gegenüber seinen Vorgängern und TODO gibt einen Ausblick auf die zukünftige Richtung. Falls Probleme bei den weiteren Schritten auftauchen, sollten Sie einen Blick in FAQ (»Häufig gestellte Fragen«) werfen, bevor Sie sich Hilfe suchend an die Projektmitglieder wenden.

Erzeugen des Makefiles

Befindet sich im Stammverzeichnis des Quellpakets keine Datei mit dem Namen »Makefile« (bzw. »makefile«), so muss diese zunächst erstellt werden. Hierfür existieren (fast) immer Shellskripte namens configure (manchmal auch kurz config oder config.sh). Starten Sie dieses:

user@sonne> ./configure
creating cache ./config.cache
checking for a BSD compatible install... /usr/bin/ginstall -c
checking whether build environment is sane... yes
checking whether make sets ${MAKE}... yes
checking for working aclocal... found
checking for working autoconf... found
checking for working automake... found
checking for working autoheader... found
checking for working makeinfo... found
checking for gnome-config... /opt/gnome/bin/gnome-config
...
checking for Mozilla... no
configure: error:
        *** Mozilla 0.7 is required
        *** A package for 0.7 is available here:
        *** http://people.redhat.com/blizzard/software/.

Sinn solcher configure-Skripten ist das Erzeugen eines auf die lokalen Gegebenheiten zugeschnittenen Makefiles. Was konkret passiert, hängt natürlich vom Verfasser des Skripts ab. Typische Punkte sind:

Unser Beispiel deckt eine der häufigsten Ursachen für einen gescheiterten "configure"-Aufruf auf: Das Fehlen eines Pakets (hier Mozilla der Version 0.7). Abhilfe sollte die nachträgliche Installation des erforderlichen Pakets bringen.

Nicht selten jedoch stottert »configure« über fehlende Dateien, die aber definitiv installiert sind. »configure« selbst sucht allerdings nur in Standardpfaden; wird es dort nicht fündig, ist die Fehlermeldung die Konsequenz. Spätestens hier sollten Sie die Option --help konsultieren. Eventuell findet sich eine Option, mit der »configure« der rechte Fleck gewiesen werden kann.

user@sonne> ./configure --help
Usage: configure [options] [host]
Options: [defaults in brackets after descriptions]
Configuration:
  --cache-file=FILE       cache test results in FILE
  --help                  print this message
  --no-create             do not create output files
  --quiet, --silent       do not print `checking...' messages
  --version               print the version of autoconf that created configure
Directory and file names:
  --prefix=PREFIX         install architecture-independent files in PREFIX
                          [/usr/local]
  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
                          [same as prefix]
  --bindir=DIR            user executables in DIR [EPREFIX/bin]
...

Dem (Shell-)Programmierer bleibt als letzte Option noch die direkte Suche des Kodeabschnitts, der den Fehler verursachte. Ein hartes Setzen der dortigen Variablen kann über so manche Klippe helfen, erfordert jedoch tiefe Kenntnisse der Programmierung.

Wenn »configure« mit der Systemumgebung zufrieden ist, wird es abschließend ein Makefile erzeugen:

user@sonne> ./configure
...
creating Makefile
user@sonne>

Übersetzen

Bei dem im vorigen Schritt erzeugten Makefile handelt es sich um eine Steuerdatei, die die weiteren Schritte zur Kompilierung automatisiert. I.d.R. beinhaltet sie eine Reihe so genannter »Ziele« (targets). Jedes dieser Targets führt zu einem anderen Kontrollfluss. Detailliertere Informationen finden Sie im Kapitel Unix-Werkzeuge.

Das erste im Makefile formulierte Ziel ist das Default-Target, d.h. wird make ohne den Namen eines Ziels aufgerufen, beginnt es mit der Ausführung dieses ersten Targets. Die Übersetzung des Paketes beginnt somit i.A. durch den Aufruf von make:

user@sonne> make
cd /home/user/galeon-0.9pre3/src
...

Die zahlreichen Ausgaben während des »Builds« protokollieren die Tätigkeit von make. Schritte des Kompilierens finden (bei C- und C++-Programmen) in den mit »gcc ...« bzw. »g++...« beginnenden Zeilen statt. Interessant sind die Ausgaben wohl eher für den Programmierer. Bewahren Sie die Ruhe, wenn »Warnings« über den Bildschirm rauschen. Die Compiler weisen hier auf mögliche Fallstricke des Programms hin, die (zumeist;-) vom Programmierer beabsichtigt sind.

Ein Abbruch des Vorgangs mit einer »Error«-Meldung ist hingegen ein schwerwiegender Fehler. Bei der Fülle der Ursachen würde eine detaillierte Diskussion dieser einen einführenden Programmierkurs bedingen, aber zu einigen Fehlerklassen lassen sich prinzipielle Ansatzpunkte zur Fehlereingrenzung angeben:

Datei oder Verzeichnis nicht gefunden

In file included from foobar.c:2:
/opt/gnome/include/gnome.h:12: gnomesupport.h: Datei oder Verzeichnis nicht gefunden

Eine typische Ausgabe für eine fehlende Datei oder eine Datei, die nicht gefunden werden kann (»gnomesupport.h« im Beispiel). Hier sollen Sie zunächst feststellen, ob die Datei im System existiert: »locate Dateiname«. Falls nicht, so durchsuchen Sie die Installations-CDs bzw. im Web nach dem Paket, das diese Datei enthält, und installieren es nach.

Ist die Datei allerdings vorhanden, so bieten sich im Falle einer Header-Datei (Endung ».h«) drei Lösungswege an:

Eine Bibliothek wurde nicht gefunden

/usr/i486-suse-linux/bin/ld: cannot find -lgnome
collect2: ld returned 1 exit status

Der Linker kann eine Bibliothek nicht lokalisieren. »-lgnome« ist eine Compileranweisung, die Bibliothek »libgnome.a« hinzuzulinken (Beachten Sie die Namensgebung: Als Compileroption wird vom Dateinamen der Bibliothek sowohl das Prefix »lib« als auch das Suffix ».a« weggelassen!). Falls die alleinige Installation des die Bibliothek enthaltenden Pakets das Problem nicht beheben kann, sollte die Variable »LIBRARY_PATH« gesetzt und exportiert werden (mehrere Pfade lassen sich durch Doppelpunkt getrennt angeben):

user@sonne> locate libgnome.a
/opt/gnome/lib/libgnome.a
user@sonne> export LIBRARY_PATH=$(LIBRARY_PATH):/opt/gnome/lib

Undefined Reference...

/opt/gnome/lib/libgnome.a(gnome-config.o): In function `_gnome_config_set_vector':
gnome-config.o(.text+0x2630): undefined reference to `g_free'/

Derartige Meldungen können so ziemlich alles bedeuten. Für 90% der Fälle zeichnet eine falsche Bibliotheksversion dafür verantwortlich; eine Aktualisierung kann notwendig werden. Wer über etwas Programmiererfahrung verfügt, könnte vorerst das Kommando nm bemühen und die Bibliothek suchen, die das fehlende Symbol (im Beispiel »g_free«) definiert. Existiert sie, so kann die Compilerzeile im Makefile um die Anweisung des Linkens dieser Bibliothek ergänzt werden.

Sie merken schon, dass die Behandlung von Compiler-Fehlern besser dem Experten vorbehalten sein sollte. Mit Ausnahme der nicht gefundenen Bibliothek erfordert eine saubere Lösung stets den Eingriff in die Quellen des Pakets (zumindest des Makefiles). Ich möchte es bei diesem kleinen Ausflug in die Trickkiste der Programmierer belassen...

Installation

Die genaue Anweisung kann der Datei INSTALL aus dem Quellverzeichnis entnommen werden. Der Ort der Installation lässt sich häufig beim einführenden »configure« per Option variieren, der Vorgang selbst wird üblicherweise im Makefile realisiert:

root@sonne> make install

Weniger verbreitet ist die Beigabe eines Skripts install oder install.sh. Falls vorhanden, finden Sie dieses im Basisverzeichnis des Pakets oder unterhalb des dortigen ./bin-Verzeichnisses.

Deinstallation

Nur wenige Projekte bringen eine eigene Routine zum Entfernen der ins System eingespielten Dateien mit. Denn fast ebenso wie die Dokumentation scheut der Entwickler derartige Nebensächlichkeiten, sodass automatische »Hilfsroutinen« - falls überhaupt - erst bei Abschluss eines Projekts zu diesem hinzugefügt werden.

Für den Fall, dass eine Deinstallation per Skript vorgesehen ist, finden Sie im Basisverzeichnis des Quellpakets ein Skript uninstall bzw. uninstall.sh, in manch anderen Fällen beinhaltet das Makefile ein »Ziel« zur Deinstallation:

# Nur selten bietet ein Makefile dieses...
root@sonne> make uninstall

So sehr der Anwender die Möglichkeit der automatischen Deinstallation auch missen mag; die Entwickler haben teils bewusst auf deren Implementierung verzichtet. Die Problematik steckt in der fehlenden Datenbasis, die die Zugehörigkeiten und Abhängigkeiten der einzelnen Dateien protokolliert. Stellen Sie sich vor, was passiert, wenn Sie Bibliotheken deinstallieren, die von einer Reihe anderer Anwendungen gefordert werden. Diese Anwendungen würden nicht mehr funktionieren!

Wünschenswert wäre, wenn per Makefile aus den kompilierten Quellen ein RPM- oder DEB-Paket erzeugt werden würde. Dass dies mit relativ vertretbarem Aufwand möglich ist, soll in späteren Abschnitten demonstriert werden. Vielleicht inspiriert dies den Leser, die Installation solcher »handerzeugter« Pakete über den Bau eines eigenen RPM-Pakets zu realisieren...

Patch

Wenn Entwickler eines Projekts es für sinnvoll erachten, eine neue Version des Quellkodes zu veröffentlichen, so ist der Weg des geringsten Aufwands das Schnüren eines aktualisierten Komplettpakets.

Bei geringem Datenaufkommen ist das Verfahren durchaus legitim, aber bei mehrere Megabytes umfassenden Quellkode-Dateien wird so mancher freiwillige Tester das langwierige und kostspielige Herunterladen der letzten Version scheuen. Effizient wäre, ein Paket nur einmalig komplett zu übertragen und nachfolgend einzig die Änderungen zwischen den Versionen einzupflegen. Ein solches Verfahren wird als Patchen (»korrigieren«) bezeichnet und die Datei, die die Unterschiede zwischen zwei Versionen beinhaltet, nennt sich Patch.

Erstellen eines Patches

Nichts schult das Verständnis für die Funktionsweise eines Programms eindrucksvoller als ein Beispiel. Deshalb soll dem »Patchen« eine kurze Abhandlung über das Erstellen der benötigten Patchdateien voran stehen. Sicher wird nur der Programmierer in die Verlegenheit gelangen, Patches zu erstellen. Deswegen werden wir uns auf das minimal erforderliche Wissen beschränken.

Ausgangspunkt sei eine einfache Textdatei:

user@sonne> cat ErsteVersion.txt
Ein einfaches Beispiel,
um die Arbeitsweise von "patch"
zu demonstrieren.

Aufgrund der immensen Wichtigkeit der Aussagen des Inhalts der Datei kopieren sie sich eine Menge Leute. Der Autor indess modifiziert den Text in einer seiner kreativen Phasen:

user@sonne> cat ZweiteVersion.txt
Dies ist ein einfaches Beispiel,
um die Arbeitsweise von "patch"
zu demonstrieren.

Allen Eignern der Kopie der ersten Version die neue Datei zukommen zu lassen ist bei solch kleinen »Projekten« praktikabel. Aber ebenso genügt es, die Änderungen kund zu geben. Und hierbei gelangt das Kommando diff zum Einsatz:

user@sonne> diff -u ErsteVersion.txt ZweiteVersion.txt
--- ErsteVersion.txt    Wed Apr 25 21:06:20 2001
+++ ZweiteVersion.txt   Wed Apr 25 21:06:42 2001
@@ -1,3 +1,3 @@
-Ein einfaches Beispiel,
+Dies ist ein einfaches Beispiel,
um die Arbeitsweise von "patch"
zu demonstrieren.

Zugegeben... das Beispiel ist schlecht gewählt, da die Ausgabe von »diff« den Umfang der »neuen« Datei bereits überschreitet. Aber bekanntlich heiligt der Zweck die Mittel.

Die Ausgabe von diff bedarf einer Aufbereitung, um patch die notwendigen Informationen zukommen zu lassen. Im Beispiel wählten wir das unified Ausgabeformat (Option -u), das eine »leserliche« Form des context Formats (Option -c) darstellt. Genau diesen »Kontext« benötigt später patch, um die Änderungen an den richtigen Positionen der Zieldatei einpflegen zu können.

Mit den vorgestellten Optionen werden neben den Unterschieden auch die ersten drei unveränderten Zeilen in die Ausgabe von diff übernommen. Sie dienen als »Anhaltspunkt« für patch, um auch dann noch »sinngemäß korrekte« Korrekturen zu ermöglichen, wenn Zeilen in das Original eingefügt oder daraus entfernt wurden. Genügt der Kontext nicht, um eine eindeutige Abbildung zu erzielen, kann er mittels -U n (unified) bzw. -C n auf n Zeilen gesetzt werden.

Nun bestehen große Projekte nicht nur aus einer Datei. Und für jede einen eigenen Patch zu erstellen, würde das Interesse am Patchen schnell schwinden lassen. Aus diesem Grund kann diff auch auf Verzeichnisse angesetzt werden. Ist nur eine der beiden Angaben in der Kommandozeile ein Verzeichnis, so wird der Name der anderen Datei in diesem Verzeichnis gesucht. Ist diff fündig, vergleicht es diese beiden Dateien.

Handelt es sich bei beiden Argumenten um Verzeichnisse, vergleicht diff alle Dateien mit identischen Namen und erzeugt eine gemeinsame Ausgabe. Dateien, die nur in einem Verzeichnis vorhanden sind, erscheinen als »Only in Verzeichnis: Datei« in der Ausgabe. Hier kann die Option --new-file hilfreich sein, um »neue« Dateien (die aus dem zweiten Verzeichnis) vollständig in die Ausgabe einfließen zu lassen (diff arbeitet so, als wäre die Datei vorhanden aber leer). Schließlich werden mit der Option -r auch Unterverzeichnisse rekursiv betrachtet.

Um endlich zum Erstellen des notwendigen Patches zu gelangen... leiten wir die Ausgabe von diff in eine Datei:

user@sonne> diff -u ErsteVersion.txt ZweiteVersion.txt > patch_ErsteVersion

Bei umfangreichen Patchdateien lohnt sich das Komprimieren derselben.

user@sonne> gzip patch_ErsteVersion
user@sonne> ls
patch_ErsteVersion.gz

Einspielen eines Patches

Die Aktualisierung der Originaldatei anhand des Patches gestaltet sich für unser Beispiel äußerst einfach. Die Datei mit dem Patch ist zu entpacken und dem Programm patch über die Standardeingabe zuzuführen:

user@sonne> ls
ErsteVersion.txt
user@sonne> gunzip -c patch_ErsteVersion.gz | patch
patching file ErsteVersion.txt

Wichtig ist, dass der Kommandoaufruf aus dem Verzeichnis mit der Originaldatei heraus erfolgt. Sonst würde patch die zu »patchende« Datei nicht finden.

Patchdateien, die rekursiv über Verzeichnisse erzeugt wurden, enthalten anstatt des reinen Dateinamens den kompletten Pfad zu jeder zu »patchenden« Datei. Stimmen nun die Verzeichnisstrukturen beim Patch-Erzeuger und Patch-Anwender nicht überein, so wird »patch« die zu korrigierende Datei nicht finden und reagiert leicht verärgert:

user@sonne> pwd
/home/user/sonstwo
user@sonne> gunzip -c ../linuxfibel-0.6.0-0.7.0.patch.gz | patch
can't find file to patch at input line 4
Perhaps you should have used the -p or --strip option?
The text leading up to this was:
--------------------------
|diff -u -r --new-file fibel_old/access.htm linuxfibel/access.htm
|--- fibel_old/access.htm       Sun May 6 17:14:57 2001
|+++ linuxfibel/access.htm       Fri Jun 29 15:08:22 2001
--------------------------
File to patch:

Die erste Option, die Ihnen bleibt, wäre nun tatsächlich, den Zugriffspfad zur betreffenden Datei an der Eingabeaufforderung anzugeben. Bei wenigen Dateien ein legitimes Vorgehen; bei umfangreichen Patches artet die Methode nur zu schnell in echte Arbeit aus. Die Alternative, die lokale Verzeichnisstruktur dem des Patch-Erzeuges anzugleichen, ist ein gangbarer, aber ebenso unbequemer Weg. Besser, wir bemühen die Option -pAnzahl, die »patch« veranlasst, die angegebene Anzahl von Verzeichnisebenen aus den Pfadangaben in der Patchdatei zu entfernen. Aus einer Angabe von -p1 in obigem Beispiel resultiert, dass sowohl die Dateinamen »fibel_old/access.htm« als auch »linuxfibel/access.htm« als »access.htm« angenommen werden. Befinden Sie sich also im »Wurzelverzeichnis« des zu patchenden Zweigs, ist »patch« nun in der Lage, alle Dateien zu finden.

Patchen ist genau das, was dieser Abschnitt vermuten lässt: Es ist schwierig und fehleranfällig. Aus diesem Grund sollte eine Sicherung der originalen Daten zum allgemeinen Vorgehen zählen; es genügt die Option -b (Backup), um die Arbeit von »patch« persönlich erledigen zu lassen. Für jede Datei, die nachfolgend verändert wird, wird zuvor eine Kopie als »Dateiname.orig« erzeugt (Suffix per Option änderbar). Im Fehlerfall - und bei etwas Kenntnis der Shellprogrammierung - richtet ein kleiner Einzeiler den Schaden:

user@sonne> for i in `find . -name "*.orig"`; do mv $i ${i%*.orig}; done

Nicht jeder Fehler ist so schwerwiegend, als dass der ganze Patch gleich über den Haufen geworfen werden müsste. Manchmal betrifft es nur einzelne Dateien, die das Kommando nicht korrekt zu behandeln weiß. Gerade wer gern mit neuesten Kernelpatches experimentiert, wird einmal Patches einspielen, die sich mit den Änderungen eines früher angewandten Patches nicht vertragen. Kann »patch« Korrekturen nur zum Teil eindeutig in eine Datei einarbeiten, so legt es alle Bestandteile des Patches, mit denen es nichts anzufangen weiß, in eine Datei »Dateiname.rej« ab. Eventuell kann dieser »Rest« manuell an die richtigen Positionen geschrieben werden...

Linuxfibel-Patch

Exemplarisch soll das Erzeugen und Einspielen eines Patches anhand der Linuxfibel demonstriert werden. Hierbei liegen die alten Quellen im Verzeichnis »fibel_old « und die neue Version befindet sich unterhalb von »linuxfibel«. Für »diff« verwenden wir die Optionen -u zum Erzeugen eines Kontexts, -r zum rekursiven Absteigen in die Unterverzeichnisse und --new-file zur Übernahme neuer Dateien in den Patch. Da es sich bei der Ausgabe von »diff« um Text handelt, lohnt sich das anschließende Komprimieren enorm:

user@sonne> diff -u -r --new-file fibel_old linuxfibel > linuxfibel-0.6.0-0.7.0.patch
user@sonne> ls -l linuxfibel-0.6.0-0.7.0.patch
-rw-r--r--   1 user      users      2141291 Jul  5 16:44 linuxfibel-0.6.0-0.7.0.patch
user@sonne> gzip -9 linuxfibel-0.6.0-0.7.0.patch
user@sonne> ls -l linuxfibel-0.6.0-0.7.0.patch
-rw-r--r--   1 user      users       363441 Jul  5 16:49 linuxfibel-0.6.0-0.7.0.patch

Für das Einspielen des Patches nehmen wir nachfolgend an, dass sich die Dateien unterhalb von /usr/share/doc/linuxfibel und der Patch im Heimatverzeichnis von »user« befinden. Der folgende Aufruf aktualisiert die Dateien:

# vermutlich darf nur Root in /usr/share/doc/linuxfibel schreiben...
root@sonne> cd /usr/share/doc/linuxfibel
root@sonne> gunzip -c ~user/linuxfibel-0.6.0-0.7.0.patch.gz | patch -p1
patching file access.htm
patching file allekapitel.htm
patching file anxious.htm
patching file archiv.htm
...

 

Tar-Archive

Seit dem Durchbruch der Paketformate Rpm und Deb hat die Bereitstellung kompilierter Software in Form von Tape Archiven stark abgenommen; die Gründe sind u.a. in der Einleitung zum Abschnitt des RedHat Package Managers zu lesen. Von den namhaften Distributionen ist Slackware als einzige diesem historischen Format treu geblieben.

Sollten Sie in die Zwangslage gelangen, ein solches Paket in ihrem (Nicht-Slackware-) System zu installieren, dann vergewissern Sie sich auf jeden Fall, dass die Verzeichnisstruktur im Paket auch relative Pfadangaben verwendet:

root@sonne> tar tzf SoftwarePaket.bin.tgz
./SoftwarePaket/README
./SoftwarePaket/INSTALL
./SoftwarePaket/bin/proggy
...

Nutzt das Paket absolute Pfadangaben (mit dem Backslash beginnend), so verwenden Sie sicherheitshalber die Option -C /Installationspfad, um die Dateien des Pakets nicht gleich ohne vorherigen Test direkt im Dateisystem zu verstreuen. Gerade hier handeln Sie sich eine Menge Probleme ein, wenn das Paket nicht auf die Verzeichnisstruktur Ihrer Distribution abgestimmt wurde.

Nach der Installation (Option -x anstatt -t bei tar verwenden) sollten Sie zunächst das Programm in Augenschein nehmen, ob es Ihren Ansprüchen gerecht wird. Ist es erst einmal im Dateisystem installiert, ist das Entfernen aller zum Paket gehöriger Dateien doch vielfach recht aufwändig.

Pakete mit enthaltenen relativen Pfadangaben beinhalten häufig ein Skript zur Installation desselben, typische Namen lauten »install« oder »install.sh«. Trotz solcher Installationshilfen bleibt das Entfernen schwierig, da jegliche Protokollierung zur Installation ausblieb. Sicherer ist die Konvertierung des Paketformats nach *.rpm oder *.deb mit Hilfe des Kommandos alien und anschließende Verwendung eines »richtigen« Paketverwaltungswerkzeugs.

Bei Slackware - und ebenso in älteren Versionen nahezu aller weiteren Distributionen - finden Sie noch die Programme installpkg und removepkg, die - der Name spricht für sich - die Installation/Deinstallation vereinfachen. Beide Werkzeuge vermerken die Paketzugehörigkeit in Listen, sodass zumindest das saubere Entfernen eines Pakets gewährleistet ist. Und dennoch fehlt diesen Programmen die Mächtigkeit eines modernen Paketmanagers, um auch Abhängigkeiten zwischen verschiedenen Paketen auflösen zu können.

Ebenfalls in die Werkzeuggruppe zur Verwaltung von "tgz"-Paketen gehörend, ist pkgtool, das letztlich nur eine augenfällige Verpackung für installpkg und removepkg ist:

pkgtool von Slackware

Nicht zuletzt Sicherheitslücken, die diesen Programmen anhaften, führten zu ihrem heutigen Schattendasein...

Rpm-Pakete

Management von Softwarepaketen

Sowohl bei Quellkode als auch bei fertig kompilierten Programmen, die in Form von Tape Archiven verbreitet werden, entpuppt sich die Verträglichkeit mit bereits installierten Komponenten als Quelle häufigen Ärgers. Quellkodepakete schwächen das Problem ab, indem zumeist ein beiliegendes Skript zwingend abzuarbeiten ist (»configure«), sodass die Kompilierung und Installation erst gelingt, wenn gewisse Bedingungen erfüllt sind. Bei Binärpaketen offenbart sich die Lauffähigkeit meist erst nach der Installation. Hat ein solches Paket seine Dateien erst einmal in den Verzeichnissen des Systems verstreut, gleicht dessen saubere Entfernung oft einem Hürdenlauf...

Den ersten Ansatz, durch eine Verwaltung der Dateilisten an zentraler Stelle den Problemen zu entgegnen, präsentierte »Slackware« mit dem schon genannten »pkgtool«. Bei Bezug auf die Pakete einer einzigen Distribution und einer einzigen Version (der von Slackware) war die saubere (De)Installation zwar gegeben, jedoch fehlten Mechanismen zur Versionsverwaltung, die erst ein Update - und damit eine der wesentlichen Anforderungen an eine Paketverwaltung - ermöglichten. Erst »RedHat« schuf mit dem RedHat Package Manager rpm eine ausgefeilte Lösung.

Rpm selbst ist ein Programm, das mannigfaltige Aufgaben wahr nimmt, u.a.:

Versionsabfrage

Sämtliche Abfragen werden durch die Option -q (query) eingeleitet:

rpm -q [Optionen]

Abfragen können sich auf Einträge aus der RPM-Datenbank oder auf RPM-Pakete beziehen oder, anders ausgedrückt, auf installierte bzw. nicht installierte Pakete. Wichtig ist die Angabe der Quelle, insofern sich die Abfrage nicht auf ein installiertes Paket bezieht!

Eine Auskunft über alle in der Datenbank erfassten RPM-Pakete samt ihrer Versionsnummern veranlasst die Option -a:

user@sonne> rpm -qa
aaa_base-2000.7.24-4
aaa_dir-2000.7.29-0
aaa_skel-2000.7.16-1
at-3.1.8-225
...

Eine solche Anfrage ist bei der Suche nach einem installierten Paket sinnvoll, falls der genaue Name des Pakets unbekannt ist.

Häufiger interessiert man sich für die Version einer installierten Komponente. Hierzu ist der »Query-Option« -q der Paketname nach zu stellen. Im Falle des gezielten Bezugs auf eine konkrete Version (wenn mehrere parallel installiert sind) kann der Name um Versions- und/oder Revisionsnummer ergänzt werden. Zulässige Anfragen bez. des Pakets »cpio« sind bspw:

user@sonne> rpm -q cpio            # Paketname
cpio-2.4.2-237
user@sonne> rpm -q cpio-2.4.2      # Paketname + Version
cpio-2.4.2-237
user@sonne> rpm -q cpio-2.4.2-237  # Paketname + Version + Revision
cpio-2.4.2-237

Bezieht sich eine Anfrage auf eine RPM-Datei, so ist neben deren Namen die Option -p zu verwenden:

user@sonne> rpm -q linuxfibel_basis*rpm  # -p fehlt!
Paket linuxfibel_basis-0.6-0.i386.rpm ist nicht installiert
user@sonne> rpm -qp linuxfibel_basis*rpm
linuxfibel_basis-0.6-0

Beachten Sie, dass sich der Name einer Rpm-Datei nicht zwingend mit dem Namen des Pakets decken muss!

Detailliertere Auskunft

Mit der reinen Versionsabfrage sind die Möglichkeiten noch lange nicht erschöpft. Aus Anwendersicht sind zunächst die Zugehörigkeiten von Dateien zu Paketen von Interesse. So setzt bspw. die Suche nach der Dokumentation zu einem Paket günstiger Weise bei einem Blick auf die Dateiliste an:

user@sonne> rpm -qld ext2fs
/usr/share/doc/packages/ext2fs/RELEASE-NOTES
/usr/share/info/libext2fs.info.gz
/usr/share/man/man1/chattr.1.gz
/usr/share/man/man1/lsattr.1.gz
/usr/share/man/man1/uuidgen.1.gz
/usr/share/man/man8/badblocks.8.gz
/usr/share/man/man8/debugfs.8.gz
/usr/share/man/man8/dumpe2fs.8.gz
/usr/share/man/man8/e2fsck.8.gz
/usr/share/man/man8/e2label.8.gz
/usr/share/man/man8/fsck.8.gz
/usr/share/man/man8/mke2fs.8.gz
/usr/share/man/man8/mklost+found.8.gz
/usr/share/man/man8/tune2fs.8.gz

Die Option -l zeigt alle zu einem Paket gehörigen Daten mit vollständigem Pfad an; -d beschränkt die Ausgabe auf die zugehörigen Dokumentationsdateien.

Obiges Vorgehen bedingt allerdings die Kenntnis des Paketnamens, zu dem eine Datei gehört. Auch für diese Anfrage sieht rpm eine Option vor (-f), wobei die komplette Pfadangabe zur Datei erforderlich ist:

user@sonne> rpm -qf /usr/bin/chattr
ext2fs-1.18-125

Der Vollständigkeit halber soll an dieser Stelle die Abfrage einer »SPEC«-Datei Erwähnung finden. Diese enthalten letztlich die Informationen zum Erzeugen eines Rpm-Pakets (ihr Aufbau wird uns im Rahmen des Paketbaus beschäftigen). Eine Anfrage liefert den Namen des Pakets, das durch diese SPEC-Datei erstellt werden würde:

user@sonne> rpm -q --specfile linuxfibel_basis.spec
linuxfibel_basis-0.6-0

Tiefer in den Gründen des Pakets forscht die Option -i, die neben den bereits genannten Informationen Weiteres zum Inhalt offenbart:

user@sonne> rpm -qi ext2fs
Name        : ext2fs                       Relocations: (not relocateable)
Version     : 1.18                              Vendor: SuSE GmbH, Nuernberg, Germany
Release     : 125                           Build Date: Sam 29 Jul 2000 16:33:15 CEST
Install date: Die 27 Mär 2001 19:47:57 CEST      Build Host: Euklid.suse.de
Group       : System Environment/Base       Source RPM: ext2fs-1.18-125.src.rpm
Size        : 519223                           License: Remy Card, Theodore Ts'o
Packager    : feedback@suse.de
Summary     : Utilities for the second extended file system
Description :
Utilities needed to create and maintain ext2 filesystems under Linux.
Included in this package are: chattr, lsattr, mke2fs, mklost+found,
tune2fs, e2fsck, and badblocks.

Authors:
--------
    Remy Card <card@masi.ibp.fr>
    Theodore Ts'o <tytso@mit.edu>

SuSE series: a

Um den Status der zu einem Paket gehörigen Dateien zu erfahren, bedient man sich der Option -s:

user@sonne> rpm -qfs /sbin/fsck
normal        /lib/libcom_err.so.2
normal        /lib/libcom_err.so.2.0
normal        /lib/libe2p.so.2
normal        /lib/libe2p.so.2.3
...

Die Angaben entsprechen der Information in der Datenbank und decken sich nicht zwangsläufig mit den Gegebenheiten im Dateisystem. So besagt »normal«, dass - aus Sicht der Datenbank - die Datei so vorhanden ist, wie sie mit dem Paket installiert wurde. Ein »not installed« wird sichtbar, wenn ein Paket nur teilweise installiert wurde (gewisse »exclude«-Optionen); »replaced« erscheint bei Dateien, die durch eine Version aus einem Fremdpaket ersetzt wurden.

Abfrage von Abhängigkeiten

Es macht i.A. selten Sinn, Programme zu installieren, während von diesen benötigte Bibliotheken, Programme oder Interpreter fehlen. Sie würden ja doch nicht laufen. Das Rpm-Paket beinhaltet daher die Informationen, welche Pakete in welcher Version zuvor installiert sein müssen.

Rpm wertet u.a. während der Installation und Deinstallation die Bedingungen für ein Paket intern aus, sodass sich die manuelle Abfrage oft erübrigt. In manchen Situationen finden sich allerdings Abhängigkeiten, die sich nicht automatisch auflösen lassen, bspw. wenn Paket A Fähigkeiten eines Pakets B benötigt, Paket B bedingt aber die vorherige Installation von Paket A. Natürlich vermag man solche Pakete auch unter Missachtung der Abhängigkeiten installieren. Günstig ist dennoch, durch vorherige Abfrage sicher zu stellen, dass im Nachhinein eine konsistente Installation vorliegt.

user@sonne> rpm -q --requires rsync
/bin/sh
/usr/bin/perl
ld-linux.so.2
libc.so.6
libc.so.6(GLIBC_2.0)
libc.so.6(GLIBC_2.1)

Wenn ein Paket bestimmte Anforderungen stellt, so sollte ein auch Paket existieren, das sie erfüllt. Der logische Schritt zur Verifizierung, ob sich ein Paket zur Installation eignet, ist somit der Test, ob die notwendige Software auf dem System existiert. Bei einer sauber geführten locatedb sollte auch ein Aufruf von »locate <Datei>« die Lösung offenbaren. Sicher ist jedoch die Anfrage an die Rpm-Datenbank:

user@sonne> rpm -q --whatprovides /bin/sh
bash-2.04-30
user@sonne> rpm -q --whatprovides /usr/bin/perl
perl-5.005_03-182

Umgangssprachlich ließe sich letztere Abfrage als »Welches Paket bietet diese Fähigkeit?« interpretieren. Die Frage nach »Welche Fähigkeiten bietet dieses Paket an?«, würde eine --provide-Anfrage beantworten:

user@sonne> rpm -q --provides rsync
rsync

Und letztlich ist auch die Fragestellung »Welche Pakete benötigen diese Fähigkeit?« legitim, bspw. um festzustellen, ob ein Paket gefahrlos gelöscht werden kann:

user@sonne> rpm -q --whatrequires /usr/bin/perl
aaa_base-2000.7.24-4
groff-1.16-26
lilo-21-132
ps-2000.7.16-4
rpm-3.0.4-68
texinfo-4.0-112
util-2.10m-41
...

Natürlich beschränken sich die Abhängigkeiten nicht allein auf den Paketnamen, sondern sie berücksichtigen ebenso Versionen, denn neuere Software bedingt zumeist auch neuere Bibliotheken...

Alles in Ordnung?

Wer die durch eine Distribution vorgegebenen Pfade zur Administration verlässt, der wird hier und da durch manuelle Eingriffe ins System nicht zuletzt die Belange der Rpm-Paketverwaltung berühren. Einfachstes Beispiel hierfür ist die Aktualisierung eines Pakets, das wiederum nur als Quellkode vorlag und somit unter Umgehung von rpm ins System gelangte. Oder aber bewusst »gelockerte« Rechte, um minder-privilegierten Benutzern das Ausführen mancher Programme zu ermöglichen.

»Das Genie beherrscht das Chaos«. Doch da die Wenigsten aus unserer Mitte den Status eines Genies genießen und Dokumentation »per Voreinstellung« stiefmütterlich vernachlässigt wird, wird der Überblick über all die Änderungen bald verloren gehen. Kein Problem... bis zum Auftreten eines Problems. Dann ist es an der Suche, welche Änderung das Übel verbockt haben könnte.

Damit derartige Integritätstests möglich werden, enthalten Rpm-Pakete mehrere Informationen. Eine Anfrage mit der Option --dump ist zwar eher zum internen Gebrauch von rpm gedacht, schult aber den Sinn für die nachfolgend vorgestellten Tests:

user@sonne> rpm -q --dump ext2fs | head
/lib/libcom_err.so.2 17 964881186 0120777 root root 0 0 0 libcom_err.so.2.0
/lib/libcom_err.so.2.0 8133 964881186 c682f94b908a606d053430e2a8078669 0100755 root root 0 0 0 X
/lib/libe2p.so.2 13 964881187 0120777 root root 0 0 0 libe2p.so.2.3
/lib/libe2p.so.2.3 17466 964881187 709a01c3824feccf4b3b35090211902f 0100755 root root 0 0 0 X
/lib/libext2fs.so.2 16 964881187 0120777 root root 0 0 12296 libext2fs.so.2.4
/lib/libext2fs.so.2.4 83724 964881187 3ea426260c3f6c02b629bec7ba636ae6 0100755 root root 0 0 41476 X
/lib/libss.so.2 12 964881186 0120777 root root 0 0 0 libss.so.2.0
/lib/libss.so.2.0 22186 964881186 203ab396745ad6160e318c024734da3d 0100755 root root 0 0 0 X
/lib/libuuid.so.1 14 964881187 0120777 root root 0 0 0 libuuid.so.1.2
/lib/libuuid.so.1.2 11889 964881187 2447a19ed4091e1a22541f1fa2d2e8cb 0100755 root root 0 0 3405 X

Von links nach rechts bedeuten die einzelnen Felder:

Aus Sicht des Administrators sind einzig die Änderungen relevant, die sich zwischen den Informationen in der Datenbank und der tatsächlichen Installation ergeben und hierfür bietet sich die Option -V an. Alle Dateien zu einem angegebenen Paket (bzw. zu allen installierten Paketen bei Kombination mit -a) werden einer Reihe von Tests unterzogen, wobei nur Dateien in der Ausgabe erscheinen, die mindestens einen der Test nicht bestanden haben.

user@sonne> rpm -V xf86
......G.    /usr/X11R6/bin
S.5....T    /usr/X11R6/bin/SuperProbe
......GT    /usr/X11R6/bin/Xmark
S.5...GT    /usr/X11R6/bin/appres
S.5...GT    /usr/X11R6/bin/atobm
...

Der Punkt steht hierbei für einen bestandenen Test, die Buchstaben - bzw. die eine Ziffer - kodiert den Grund für ein Scheitern:

S Abweichende Dateigröße
M Die Rechte oder der Dateityp wurden verändert
5 Die MD5-Prüfsumme stimmt nicht
D Eine Gerätedatei hat sich verändert
L Ein symbolischer Link zeigt auf eine andere Datei
U Der Eigentümer hat sich geändert
G Die besitzende Gruppe hat sich geändert
T Datei besitzt andere Modifikationszeit

Konfigurationsdateien sind zusätzlich durch ein c vor dem Dateinamen gekennzeichnet, sodass schnell entschieden werden kann, ob eine Abweichung von den Informationen aus der Datenbank gewollt ist. Vermisste Dateien enthalten anstatt der Testdaten ein »missing«.

Rpm-Dateien, die aus unbekannter Quelle stammen, sollten im Zweifelsfall auf ihre Unversehrheit hin untersucht werden. Ein checksig stellt durch Überprüfung der MD5-Prüfsumme die Originalität eines Pakets sicher:

user@sonne> rpm --checksig linuxfibel_basis-0.6.i386.rpm
linuxfibel_basis-0.6-0.i386.rpm: md5 OK

Reparatur installierter Pakete

»Wo gearbeitet wird, fallen auch Späne.«, erklärt ein bekanntes Sprichwort. Und wer hin und wieder am System manipuliert, wird auch Verluste verzeichnen. Irgend wann geht irgend etwas mit Gewissheit schief. Wenn dann gar nichts mehr geht, beginnt man besser von vorn, also mit der Neuinstallation der »verbastelten« Pakete. Eine der Installationsoptionen (siehe nachfolgenden Abschnitt) in Verbindung mit --replacepkgs erweisen sich als schlagkräftiges Argument:

root@sonne> rpm -U --replacepkgs linuxfibel_basis-0.5.i386.rpm

Für den verbreiteten Fall, dass ein sorgloser Umgang mit den Dateirechten die Integrität des Systems in Frage stellt, lassen sich auch diese auf den »Originalzustand« zurück versetzen. Verwenden Sie hierzu die Option --setperms, um die Rechte eines Pakets zu restaurieren:

root@sonne> rpm --setperms linuxfibel_basis-0.5.i386.rpm

Um Eigentümer und Gruppenzugehörigkeit wieder herzustellen, ist --setugids die rechte Option.

Quasi den »Supergau« bedeutet eine beschädigte RPM-Datenbank. Da sich nahezu die gesamte Abfragefunktionalität von rpm auf diese bezieht, ist ohne Datenbasis die gesamte Verwaltung nutzlos. rpm ist daher selbst in der Lage, eine bestehende Datenbank neu zu befüllen bzw. eine neue zu erzeugen:

# Erzeugen einer neuen Datenbank
root@sonne> rpm --initdb
# Aktualisieren einer bestehenden Datenbank
root@sonne> rpm --rebuilddb

In beiden Fällen kann per --dbpath ein zu /var/lib/rpm abweichende Pfad zur Datenbank angegeben werden.

Installation von RPM-Paketen

rpm -i [Optionen] <Paket>
rpm -U [Optionen] <Paket>
rpm -F [Optionen] <Paket>

Die Installation eines Pakets umfasst dessen Erstinstallation als auch die Aktualisierung bereits existierender Pakete. In letzterem Fall wird das alte Paket zuvor implizit aus dem System entfernt.

Die Optionen zum Installieren sind -i und zur Aktualisierung -U (update). Da letztere Option im Falle, dass das Paket noch gar nicht installiert ist, wie -i arbeitet, sollte ihr Einsatz bevorzugt werden.

Bei der erstmaligen Installation ist die Wirkung beider Aufrufe identisch:

root@sonne> rpm -i linuxfibel_basis-0.6.i386.rpm
# oder...
root@sonne> rpm -U linuxfibel_basis-0.6.i386.rpm

Existiert das Paket hingegen bereits, scheitert -i:

# linuxfibel_basis wurde bereits installiert...
root@sonne> rpm -i linuxfibel_basis-0.6.i386.rpm
Fehler...

Bez. der Aktualisierung von Paketen besteht jedoch oft der Wunsch, einzig bereits installierte Pakete zu ersetzen, ohne dieses ggf. zu installieren. In einem solchen Fall muss die Option -F (freshen) anstatt -U verwendet werden.

Werkzeuge, die die Paketverwaltung in grafische Masken oder Skripten packen, präsentieren den Installationsvorgang häufig durch einen fort schreitenden Balken. Rpm trägt dafür selbst Sorge, wenn eine der Installationsoptionen (-i|-U|-F) mit -h (hash) kombiniert wird. Der Zusatz von -v schreibt den Paketnamen vor den Balken:

root@sonne> rpm -Uhv linuxfibel_basis-0.6.i386.rpm
linuxfibel_basis #######################################

Ohne eine Installation zu vollziehen, arbeitet die Option --test. Sie kann zur Kontrolle heran gezogen werden, um von vorn herein Konflikte auszuschließen.

Mitunter erweist sich erst in der Praxis, ob eine neuere Version einer Software das hält, was man sich von ihr verspricht. Dass sie mitunter mehr schadet als nutzt, ist leider nur allzu oft zu verzeichnen. Der Wunsch, auf die alte Version zurückzustellen, wird erwachsen. Es sollte einleuchten, dass -U oder -F eine »Aktualisierung« auf eine ältere Version abweisen und der Weg über eine vorherige Deinstallation ist dann doch unnötig weit. Einfacher ist der Verwendung der Option --oldpackage:

root@sonne> rpm -U --oldpackage linuxfibel_basis-0.5.i386.rpm

Im Zusammenhang mit der Abfrage von Paketabhängigkeiten wurde die Problematik wechselseitiger Bedingungen von Paketen bereits erörtert. Es findet sich mitunter keine Installationsreihenfolge bei mehreren voneinander abhängigen Paketen, sodass obige Mechanismen jeden Versuch einer Installation oder Aktualisierung abweisen. Per --nodeps lässt sich die Überprüfung durch rpm deaktivieren.

Eine weitere explizite Aufforderung an rpm wird nötig, wenn ein Paket Dateien eines anderen Pakets entfernt, dessen Dateien in der RPM-Datenbank erfasst sind. --replacefiles zwingt rpm dieselben durch die »neu« Version zu ersetzen.

Die »brutalste« Option, die --replacefiles, --replacepkg und --oldpackage in sich vereint, ist --force. Kombinieren Sie diese gar mit --nodeps, verdammen Sie rpm zum unbedingten Gehorsam...

RPM-Pakete speichern neben den eigentlichen Dateien auch den Installationspfad, unter dem diese ins System zu spielen sind. Nicht zuletzt diese Festlegung erschwert zuweilen die Portierung eines für Distribution A erzeugten Rpm-Pakets auf die Distribution B, da diese womöglich eine abweichende Verzeichnisstruktur bevorzugt. Zumindest Pakete mit rein distributionsunabhängigen Inhalten (bspw. Dokumentationen) bedingen aber keinerlei Annahmen ob des eigentlichen Installationsorts, sodass deren Installationspfad (oft) als »änderbar« (engl.: relocatable) vermerkt ist.

»Relocatable« Pakete lassen sich sowohl mit --prefix <Pfad> unterhalb eines Verzeichnisses installieren, wobei der komplette Installationspfad dann in diesem erscheint, als auch per --relocate <Alter_Pfad>=<Neuer_Pfad> komplett überschreiben.

Um beim Beispiel des Linuxfibel-Basispakets zu bleiben, könnte ein alternativer Installationspfad wie folgt versucht werden:

root@sonne> rpm -U --relocate /usr/share/doc/linuxfibel=/usr/local/httpd/htdocs linuxfibel_basis-0.6-0.i386.rpm
path /usr/src/linuxfibel is not relocateable for package linuxfibel_basis-0.6-0

Der Versuch scheitert leider, da das Paket nicht als »relocatable« gekennzeichnet wurde. Aber auch hierfür existiert eine Lösung in Form der Option --badreloc, die den Pfadwechsel erzwingt:

root@sonne> rpm -U --badreloc --relocate /usr/share/doc/linuxfibel=/usr/local/httpd/htdocs linuxfibel_basis-0.6-0.i386.rpm

Entfernen von RPM-Paketen

Zum Entfernen von Paketen dient die Option -e:

root@sonne> rpm -e linuxfibel_basis
Fehler: Das Entfernen dieser Pakete würde Paket-Abhängigkeiten missachten:
        linuxfibel_basis wird von linuxfibel_kap10-0.6-0 gebraucht
        linuxfibel_basis wird von linuxfibel_kap1-0.6-0 gebraucht
...

Das Beispiel verdeutlicht, dass rpm auch hierbei die Abhängigkeiten zu weiteren installierten Paketen berücksichtigt. Die Deinstallation glückt erst, wenn kein anderes Paket mehr das zu entfernende bedingt. Natürlich kann hier ebenso mit --nodeps nachgeholfen werden, was allerdings zu unbrauchbaren Paketen führen kann.

Ist ein Paket gleichzeitig in mehreren Versionen installiert, entfernt -e in Kombination mit --allmatches alle Vorkommen; einen sehr ausführlichen Report zu den einzelnen Abhängigkeiten bringen die Optionen -e --test --vv zum Vorschein.

Rpm-Pakete selbstgebaut

Voraussetzungen

Die meisten Rpm-Pakete beinhalten Programme nebst zugehöriger Bibliotheken, Dokumentationen, Konfigurationsdateien usw. Beim Erzeuger eines Pakets handelt es sich i.A. auch um den Entwickler des jeweiligen Programms, deshalb werden Rpm-Pakete zumeist direkt aus den Quellen gebaut, d.h. die zu verpackenden Dateien werden erst zum Zeitpunkt des Paketbaus erzeugt.

Somit ergibt sich als erste Anforderung, die zum Erstellen eines Pakets erfüllt sein muss, dass die Quellen auf dem System korrekt kompilieren müssen (für das nachfolgend diskutierte Beispiel der Linuxfibel erübrigt sich diese Forderung, da sie keine Kompilierung bedingt).

Als weitere Voraussetzung ist eine korrekte Konfigurationsdatei zu nennen. Der Reihe nach entnimmt rpm seine Informationen den Dateien /usr/lib/rpmrc, /etc/rpmrc bzw. ~/.rpmrc, wobei nicht jede Datei zwingend existieren muss. Wird eine Option in einer späteren Datei wiederholt definiert, so gilt diese Einstellung. Ist keine der Dateien (auch nicht in anderen Verzeichnissen) vorhanden, könnte es an einer unvollständigen Installation liegen. Zumindest in neueren RedHat-basierten Distributionen ist die Funktionalität zum Bau oft in ein »rpm-devel-Paket« ausgelagert.

Das Erstellen eigener RPM-Pakete bedingt die Existenz mehrerer Verzeichnisse, deren Lage aus den Angaben in /usr/lib/rpm/macros hervorgeht. Änderungen, falls notwendig, werden in der Datei /etc/rpm/macros (systemweit) bzw. .rpmmacros (nutzerspezifisch) vollzogen. Die Namen der anzupassenden Variablen ist der Originaldatei zu entnehmen:

# Auszug aus /usr/lib/rpm/macros
...
%_usr                   /usr
%_usrsrc                %{_usr}/src
...
%_topdir                %{_usrsrc}/packages
...
%_rpmdir                %{_topdir}/RPMS
%_sourcedir             %{_topdir}/SOURCES
%_specdir               %{_topdir}/SPECS
%_srcrpmdir             %{_topdir}/SRPMS

Für die nachfolgende Ausführung gehen wir von folgender Verzeichnisstruktur (SuSE 7.2) aus:

/usr/src/packages/BUILD
         In diesem Verzeichnis werden die Dateien vor dem Paketbau erzeugt
/usr/src/packages/RPMS
         Enthält die fertigen RPM-Pakete
/usr/src/packages/SOURCES
         Enthält die Quellen und Patches
/usr/src/packages/SPECS
         Enthält die SPEC-Dateien zu jedem Paket (quasi die Anleitung, wie ein Paket zu erzeugen ist).
/usr/src/packages/SRPMS
         Enthält die fertigen SRPM-Pakete (Quellen)

Der Bauplan

Die entscheidende Datei für den Paketbau ist die so genannte SPEC-Datei, die allgemeine Informationen und eine Art Bauanleitung zum Paket beinhaltet. Die Datei selbst besteht aus mehreren Sektionen:

Die Präampel

Die Präampel enthält allgemeine Informationen zum RPM-Paket und zum Bau. Jeder Eintrag besitzt die Form:

<Name> : <Beschreibung>

wobei folgende Namen (»Tag«) möglich sind:

Buildroot
         (Alternatives) Verzeichnis, indem das Paket gebildet wird.
Copyright
         Copyright-Informationen zur enthaltenen Software.
Conflicts
         Namen von Paketen, die sich mit diesem RPM-Paket nicht vertragen. Die Installation wird bei einem Konflikt abgelehnt, kann aber dennoch mittels --nodeps erzwungen werden.
Distribution
         Linuxdistribution, für die das Paket erzeugt wurde.
Group
         Art der enthaltenen Software (mögliche Einträge werden von RedHat vorgegeben)
Name
         Name des RPM-Pakets
Packager
         Name des Erzeugers des Pakets
Patch
         Name einer Patchdatei, die zum Bau des Pakets erforderlich ist; mehrere Patches können mittels mehrerer Patch-Einträge angegeben werden.
Prefix
         »Empfohlener« Installationspfad für ein verschiebbares Paket
Provides
         Ein virtueller Paketname, der eine Einordnung in eine Anwendungsgruppe ermöglichen soll. Somit kann der Anwender in manchen Installationsprogrammen aus einer Reihe von Programmen auswählen, die nahezu dasselbe tun (bspw. bieten sowohl »sendmail« als auf »qmail« einen »mail-server«).
Release
         Versionsnummer des Pakets; "0" wenn es erstmals gepackt wird, "1" beim zweiten Mal.... D.h. Versionsnummern sollten erhöht werden, wenn das Paket neu erzeugt wird ohne das sich die Version der enthaltenen Software geändert hat.
Requires
         Paketnamen und Versionen, die auf dem System vorhanden sein müssen (Paketabhängigkeiten). Hier können sowohl die Namen virtueller Paketgruppen stehen, falls kein konkretes Programm bedingt wird. Bez. Versionsnummern sind auch relative Angaben erlaubt: requires perl >= 5.0.
Source
         Name des enthaltenen Quell-Archivs; mehrere Archive lassen sich mittels mehrerer Source-Tags angeben
Summary
         Kurze Beschreibung zum Inhalt des Pakets
Url
         URL mit weiterführenden Informationen zum Paket.
Vendor
         Anbieter der Software
Version
         Versionsnummer der enthaltenen Software

Der entsprechende Abschnitt aus der SPEC-Datei zum Linuxfibel-RPM-Paket sieht wie folg aus:

Vendor:         Saxonia Systems AG
Distribution:   Gnu-Linux (i386)
Name:           linuxfibel
Version:        0.7
Release:        0
Packager:       thomas.ermer@saxsys.de
Copyright:      GPL
Summary:        Installiert das Linuxfibel Paket
Url:            http://www.linuxfibel.de
Group:          Linux/Dokumentation
Provides:       Linux Basiswissen
Source:         linuxfibel-%{version}.%{release}.tgz
BuildRoot:      /tmp/linuxfibel-%{version}.%{release}-root

Die %description-Sektion

Eine aussagekräftige Beschreibung zum Inhalt des Pakets ermöglicht die Sektion »description«. Der Text kann auf beliebig viele Zeilen ausgedehnt werden.

%description
Die Linuxfibel ist ein distributionsunabhängiges Lehrbuch und Referenz zu Themen rund um Linux.

Die %prep-Sektion

Hier findet die Vorbereitung zum Bau des Pakets statt. Letztlich beinhaltet die Sektion verschiedene Shellbefehle, die die Quellen für den anschließenden Kompiliervorgang - sofern ein solcher erforderlich ist - aufbereiten.

Ein gebräuchlicher erster Schritt ist das Entfernen der »Reste« früherer Paketbau-Vorgänge. Dann werden i.d.R. die Quelldateien ins BUILD-Verzeichnis entpackt. Ggf. kann in einem weiteren Schritt das Einspielen von Patches erfolgen.

%define INSTALL_DIR     /usr/share/doc/linuxfibel

%prep
rm -rf %{name}-%{version}.%{release}
mkdir %{name}-%{version}.%{release}
cd %{name}-%{version}.%{release}
mkdir -p ./%{INSTALL_DIR}
tar xzvf ../../SOURCES/%{name}-%{version}.%{release}.tgz -C ./%{INSTALL_DIR}

Die %build-Sektion

Im Falle von Quellpaketen muss die zu installierende Software erst erzeugt werden. Genügt hierfür ein einfacher Aufruf von make, kann die build-Sektion entfallen. Im anderen Fall werden die auszuführenden Schritte angegeben.

%changelog

Beinhaltet Informationen über die wesentlichen Änderungen der enthaltenen Software.

%clean

RPM entsorgt die »Überreste« der Installation stets selbst, mit der Ausnahme der Verwendung eines buildroot-Verzeichnisses. Ein solches sollte im clean-Eintrag entfernt werden.

%clean
rm -r %{buildroot}

Die %files-Sektion

Die %install-Sektion

%port, %postun

Die Sektionen beinhalten Namen von dem RPM-Paket beiligenden Skripten, die nach der Installation (%post) bzw. nach dem Löschen (%postun) des Pakets ausgeführt werden sollen.

%pre, %preun

Die Sektionen beinhalten Namen von dem RPM-Paket beiligenden Skripten, die vor der Installation (%pre) bzw. vor dem Löschen (%preun) des Pakets ausgeführt werden sollen.

Der eigentliche Bau

Dieser Abschnitt wird bearbeitet...

Deb-Pakete

Der feine Unterschied

Gäbe es keine Debian-Distribution, so könnte der RedHat Package Manager zu recht den Anspruch eines Standard-Linux-Paketformats erheben. Und würde Debian nicht ausgerechnet bei eingefleischten Linuxkennern so hoch im Kurs stehen, so würden die häufig erklingenden Aussagen, »das Debian-Paket-Format sei dem von RedHat überlegen«, als Pauschalmeinung von Unwissenden ungehört in der Senke des Vergessens verhallen. Doch was unterscheidet dieses Format so von Rpm, dass die Debianer vortrefflich ob der Vorzüge diskutieren mögen?

Deb-Pakete selbstgebaut

 

Alien

Falls ein Programm nicht in einem von »meiner« Distribution unterstützten Format vorliegt, so kann eine Formatwandlung eine Installation doch noch ermöglichen.

Als Voraussetzung für den Einsatz des Perl-Skripts alien müssen die Programme zur Verwaltung der »Fremdformate« installiert sein, also:

Zum Lesen/Erzeugen eines Rpm-Pakets:rpm
Zum Lesen/Erzeugen eines Tgz-Pakets:tar
Zum Lesen/Erzeugen eines Deb-Pakets:debmake, dpkg-dev, dpks, make und gcc

Eine Paketverwaltung für ein System

Auch wenn die Programme zur direkten Installation von »Fremdpaketen« vorhanden sind, sollten Sie diese niemals zum Installieren verwenden! Paketverwaltungen basieren auf einer Datenbank und jede Verwaltung verwendet ihre eigene. Leider wissen die Programme nichts von der Existenz der »Konkurrenz«, sodass Vorteile wie Berücksichtigung von Abhängigkeiten, Paketaktualisierung, sauberes Entfernen usw. nicht mehr gegeben sind.

Des Weiteren ist eine Wandlung oft mit einem Verlust an Information behaftet. Deb- und Rpm-Pakete unterscheiden sich im internen Aufbau, sodass manche Aspekte bei der Konvertierung nicht berücksichtigt werden können. Das Tgz-Format beinhaltet gar nur die zu installierenden Dateien ohne jegliche Hinweise auf Version, Abhängigkeiten... Eine Wandlung von Tgz in ein anderes Format ermöglicht einzig die (De)Installation der Daten, aber mehr auch nicht!

Und so gehts...

Die Handhabung von alien gestaltet sich spielend einfach. Das Format des Eingabepakets ermittelt das Programm anhand dessen Struktur. Ohne Angabe weiterer Argumente wird das Debian-Format erzeugt. Alternative Formate sind:

--to-tgzErzeugen des Tgz-Formats
--to-rpmErzeugen des Rpm-Formats
--to-debErzeugen des Deb-Formats (Voreinstellung)
--to-slpErzeugen des Slp-Formats

Das nachfolgende Beispiel wandelt das Linuxfibel-Basis-RPM-Paket in das Tgz-Format:

user@sonne> alien --to-tgz linuxfibel_basis-0.6-0.i386.rpm
Warning: alien is not running as root!
Ownerships of files in the generated packages will probably be messed up.
linuxfibel_basis-0.6.tgz generated

Die Warnung ist nicht weiter tragisch. Sie weist nur darauf hin, dass die erzeugte Datei nicht Root gehört und somit ein Sicherheitsrisiko darstellen könnte (da nicht nur Root das Paket bearbeiten darf).

Das Erzeugen von deb-, rpm- oder slp-Formaten bleibt dann aber einzig Root vorbehalten. Um ein Format von alien lesen bzw. schreiben zu können, muss das entsprechende Werkzeug (bspw. rpm oder debmake) installiert sein. Natürlich kann eine solche Konvertierung nicht sätliche Informationen sauber in ein Paket integrieren, die diese »normalerweise« enthalten würde. Gerade die Erzeugung der ausgereiften rpm- oder deb-Formate aus einem Tape Archiv kann nur eine saubere (De)Installation der Dateien garantieren. Abhä,ngigkeiten o.Ä. bleiben vollends unberücksichtigt:

root@sonne> alien --to-rpm linuxfibel_basis-0.6.tgz
linuxfibel_basis-0.6.2.noarch.rpm generated

Zumindest die fehlende Paketbeschreibung kann bei Konvertierung aus dem tgz-Format per Kommandozeile ergänzt werden. Geben Sie hierzu die Option --description=Text an.

Auch können Sie das Paket unverzüglich installieren, indem Sie die Option -i verwenden.

Bibliotheken

Mit dem Einspielen von Bibliotheken ins System ist es oft noch nicht getan. Was nützt es, wenn ein Programm abbricht, weil es »seine« Bibliotheken nicht finden kann?

Zunächst bedarf es eines kleinen Ausflugs, wie in Unix der Umgang mit Bibliotheken erfolgt. Nachfolgend diskutieren wir die Möglichkeiten, um Fehlerausgaben, wie die Folgende, zu vermeiden:

user@sonne> ./MyProgram
ld.so: MyProg: fatal: libMylib.so: can't open file

Statisch, dynamisch, shared

Ein erster naiver Erklärungsversuch könnte »statisch« mit »unveränderbar« gleich setzen. Hieraus ließe sich schlussfolgern, dass dynamische gelinkte Programme sich ändern (können), doch zum Glück ist dem dann doch nicht so.

Gebräuchlicher ist die Umschreibung eines statisch gelinkten Programms als ein Programm, das zur Laufzeit eine konstante Größe an Hauptspeicher verkonsumiert. Aus diesem Blickwinkel betrachtet, wäre der Speicherbedarf dynamischer gelinkter Programme änderbar. Und tatsächlich entspricht diese Betrachtungsweise dem wahren Wesen statisch bzw. dynamisch gelinkter Programme. Dem statisch gelinkten Programm wurde die komplette Funktionalität bereits zur Kompilierzeit einverleibt. Es ist unabhängig von jeglichen Bibliotheken und somit prinzipiell auf jedem System lauffähig, das das Programmformat unterstützt. Das dynamisch gelinkte Programm beinhaltet anstatt »verbreiteter« Funktionen nur die Schnittstellen zu diesen; die Funktionen selbst liegen in Form von Bibliotheksroutinen vor. Es ist klar, dass das dynamisch gelinkte Programm i.d.R. weniger Platz auf der Festplatte beansprucht, als es ein statisch gelinktes Pedant tut, das dieselbe Funktionalität erfüllt.

Der Nutzen der dynamischen Realisierung wird erst recht deutlich, wenn man die Gemeinsamkeiten zahlreicher Programme betrachtet. So greift nahezu jedes Programm auf Routinen der Ein- und Ausgabe zu, die meisten Programme verwenden eine dynamische Speicherverwaltung usw. Kandidaten für Funktionen, die besser in Bibliotheken realisiert werden sollten, finden sich reichlich und tatsächlich existieren in einem Linuxsystem nur noch wenige statisch gelinkte Programme (der dynamische Linker ist eines davon).

Die dynamisch gelinkten Programme bedingen jedoch die Anwesenheit der benötigten Bibliotheken. Je nach Zeitpunkt, wann eine Bibliothek geladen wird, werden »dynamisch ladbare« und »geteilte« (engl.: shared) Bibliotheken unterschieden. Diese begriffliche Trennung ist etwas unglücklich, da sich beide Bibliothekenstypen nicht unterscheiden. Ob sie nun als dynamisch ladbare oder als shared Bibliothek fungieren, hängt einzig vom Funktionsaufruf des Programms ab. Lädt dieses eine Bibliotheksfunktion als »shared«, so wird die Bibliothek zum Zeitpunkt des Ladens des Programms in den Hauptspeicher geladen (falls ein anderes Programm diese Bibliothek bereits geladen hatte, entfällt dieser Schritt). Im Falle eines »dynamischen« Aufrufs erfolgt das Laden erst, wenn tatsächlich auf eine Funktion aus dieser Bibliothek zugegriffen wird.

Um noch einmal auf den Unterschied zwischen statisch und dynamisch gelinkten Programmen einzugehen, so lässt sich ein weiterer Vorteil der Bibliotheken nennen. Sie sind durch andere Versionen austauschbar, solange die Schnittstellen der Funktionen und die Datenstrukturen beibehalten werden. Somit ist es bspw. möglich, einer grafischen Anwendung unter X ein anderes Aussehen zu verleihen, indem einfach die zuständige Grafikbibliothek gewechselt wird.

Ein Übel verbirgt sich dennoch bei dynamisch gelinkten Programmen. Ohne die benötigten Bibliotheken sind sie nicht lauffähig. Der nächste Schritt, die Bibliotheken ins System einzuspielen, genügt meist nicht. Das Programm muss sie auch noch finden können...

Wie sucht ein Programm dynamische Bibliotheken?

Das Aufspüren der Bibliotheken obliegt einem dynamisch gelinkten Programm selbst. Hierzu beinhaltet es den Dateinamen des dynamischen Linkers ld-linux.so.2 (Glibc2) zu Beginn seines Programmkodes:

user@sonne> strings /lib/cat | head -1
/lib/ld-linux.so.2

ld-linux.so.2 selbst zeigt als symbolischer Link auf die aktuelle Version des Linkers.

Startet das Programm, lädt der Kernel diesen dynamischen Linker. Jede dynamische Bibliothek, die ein Programm verwendet, hinterlässt in diesem seinen so genannten »so-Namen«. Dabei handelt es sich um den Präfix lib, gefolgt vom Namen der Bibliothek, wiederum gefolgt von .so. und der Hauptversionsnummer (bspw.: libXt.so.6). Im Dateisystem selbst ist eine dynamische Bibliothek unter ihrem so-Namen, gefolgt von einer Nebenversionsnummer und einer optionalen Patchnummer gespeichert (bspw.: libXt.so.6.1.1).

Der dynamische Linker akzeptiert die Anforderung einer Bibliothek, wenn die so-Namen übereinstimmen. Denn eine abweichende Hauptversionsnummer bedeutet eine geänderte Schnittstelle, womit ein Programm mit einer solchen Bibliothek nicht zusammenarbeiten kann.

Der Linker lädt nun die geforderte Bibliothek - falls sie nicht schon dort ist - in den Hauptspeicher. Er kennt nun die relative Lage (Adresse) der Funktionen und Variablen, die das Programm aus der Bibliothek benötigt und manipuliert die Verweise in der »Global Offset Table« (globale Variablen) bzw. in der »Procedure Linkage Table« (Funktionen) des Programms.

Zur Suche einer Bibliothek bedient sich der Linker mehrfacher Mechanismen. Zunächst darf ein dynamisches gelinktes Programm auch die kompletten Pfade zu den Bibliotheken eingebunden haben, womit der Linker genau diese verwenden wird. Ein solches Vorgehen erschwert u.a. die Installation von Programmen einer Distribution auf einem »Fremdsystem«, sodass i.d.R. darauf verzichtet wird.

Als nächstes betrachtet der Linker den Inhalt verschiedener globaler Umgebungsvariablen. Selten von den Distributionen angewandt - aber möglich - ist die Belegung der Variablen LD_PRELOAD mit den vollständigen Pfadangaben zu Bibliotheken, die vor allen anderen zu laden sind. Somit ist es denkbar, konkrete Funktionen aus anderen Bibliotheken zu überschreiben, da der Linker eine einmal geladene Funktion nicht wieder überschreibt. Aus Sicherheitsgründen wird das Verfahren bei setuid/setgid-Programmen nur zugelassen, wenn Benutzer-ID (UID) und effektive Benutzer-ID (EUID) - bez. Gruppen-ID (GID) - und Gruppen-ID (EGID) übereinstimmen.

Vor den Standardpfaden werden auf der Suche nach dynamischen Bibliotheken anschließend die in der Umgebungsvariablen LD_LIBRARY_PATH aufgeführten Pfade betrachtet.

Schließlich kommen die Bibliotheken aus den Standardpfaden zum Zuge. Aus Gründen der Effizienz erfolgt die Suche nicht in den Pfaden aus /etc/ld.so.conf sondern einzig in der Datei /etc/ld.so.cache, die eine Liste alle Bibliotheken aus diesen Pfaden enthält. Das Erzeugen dieser Cache-Datei obliegt dem Kommando ldconfig, das uns später noch interessieren soll.

Test mit ldd

Natürlich wird der Aufruf eines Programms, das eine Bibliothek vermisst, den Namen dieser ausgeben, womit fehlende Abhängigkeiten automatisch ans Licht gelangen. Darüber hinaus gestattet das Kommando ldd aber auch einen Einblick in die bereits erfüllten Voraussetzungen, indem es zu den auf der Kommandozeile angegebenen Programmen oder Bibliotheken die benötigten Bibliotheken ausgibt:

user@sonne> ldd /bin/ls
        libtermcap.so.2 => /lib/libtermcap.so.2 (0x40024000)
        librt.so.1 => /lib/librt.so.1 (0x40028000)
        libc.so.6 => /lib/libc.so.6 (0x4003a000)
        libpthread.so.0 => /lib/libpthread.so.0 (0x40167000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
user@sonne> ldd -v /lib/libc.so.6
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

        Version information:
        /lib/libc.so.6:
                ld-linux.so.2 (GLIBC_2.1.1) => /lib/ld-linux.so.2
                ld-linux.so.2 (GLIBC_2.1) => /lib/ld-linux.so.2
                ld-linux.so.2 (GLIBC_2.2) => /lib/ld-linux.so.2
                ld-linux.so.2 (GLIBC_2.0) => /lib/ld-linux.so.2

Die im Beispiel angewandte Option -v gibt einige erweitere Informationen preis.

Konfiguration des dynamischen Linkers

Wie bereits erwähnt, wird die Arbeitsweise des Linkers sowohl durch Umgebungsvariablen als auch durch die Datei /etc/ld.so.cache beeinflusst. Letztere Datei wiederum wird durch das Programm ldconfig erzeugt, welches in /etc/ld.so.config konfiguriert wird.

Somit bieten sich bei der Installation neuer Bibliotheken zwei Wege an, damit der Linker diese in Zukunft finden kann:

  1. Erzeugen einer neuen Datei /etc/ld.so.cache (ldconfig)
  2. Setzen der Variablen LD_LIBRARY_PATH

Die Belegung von LD_LIBRARY_PATH ist die »schnelle Methode«, wenn ein Programm mal eben getestet werden soll und das Laden der Bibliothek durch den Linker scheitert, weil dieser sie nicht finden kann. Ansonsten hängt diesem Vorgehen derselbe Nachteil an, der zum Caching-Mechanismus führte: eine Suche im Dateisystem kostet Zeit. Ein Vorteil wäre dennoch zu nennen: mittels LD_LIBRARY_PATH darf jeder Benutzer Einfluss auf den Linker nehmen, während eine Manipulation von /etc/ld.so.cache einzig dem Administrator vorbehalten ist. Die Syntax zur Belegung der Variablen erfolgt analog zur Variablen PATH.

Um die Cache-Datei /etc/ld.so.cache des Linkers zu aktualisieren, genügt nach der Installation neuer Bibliotheken zumeist ein Aufruf von ldconfig:

root@sonne> ldconfig

ldconfig durchsucht zuerst die Verzeichnisse /usr/lib und /lib und nachfolgend alle in der Datei /etc/ld.so.conf aufgeführten Pfade. Zu jeder gefundenen neuen Bibliothek erzeugt ldconfig selbsttätig den symbolischen Link des so-Namens auf den Bibliotheksnamen (also bspw. libX.so.6 als Link auf libX.so.6.1) und aktualisiert die Datei /etc/ld.so.cache.

Das Erzeugen des Links kann mittels der Option -l verhindert werden. Mit -n Pfad[e] werden einzig die angegebenen Verzeichnisse betrachtet (also auch nicht /usr/lib und /lib).

Eine Konfiguration ist - wenn überhaupt - nur durch Hinzufügen weiterer Suchpfade in die Datei /etc/ld.so.conf erforderlich. Jeder Pfad erscheint in dieser auf einer eigenen Zeile:

root@sonne> vi /etc/ld.so.conf
/usr/X11R6/lib
/usr/local/lib

Vergessen Sie nicht, nach Manipulation dieser Datei die Änderung durch Aufruf von ldconfig auch zu aktivieren. Auch sollten Sie beachten, dass die Pfade in der aufgeführten Reihenfolge durchsucht und bei gleichnamigen Bibliotheken stets nur die »erste« gefunden wird.

Wohl eher für Entwickler interessant ist die Datei /etc/ld.so.preload, die eine Liste durch Leerzeichen getrennter Bibliotheken enthält, welche vor allen anderen Bibliotheken geladen werden. Analog zur Variablen LD_PRELOAD ist somit das Überschreiben einzelnen Funktionen möglich.