Das TAR Dateiformat
« | 31 Jul 2021 | »Ich bin (leider) nicht alt genug, als dass ich den Einsatz von Bandkassetten zur Datenspeicherung live mitbekommen hätte. Disketten waren für mich von 1995 bis 2003 das “älteste” Medium.
Doch dank Unix und seinen Linux-Sprösslingen haben “Band-Archive” bzw. Tape-Archives, kurz TAR, als primitives Dateiformat bis heute überlebt.
Grund genug dieses Format selbst zu implementieren.
Als Windows-Kind ist das ZIP Format für mich die absolute Nummer eins wenn es um die komprimierte Archivierung von Verzeichnissen geht. Denn seit Windows XP kann der Explorer diesen Dateityp ohne Zusatzprogramme öffnen und bearbeiten.
Tatsächlich habe ich aber TAR
schon vor ZIP
genutzt, denn vor meinem
Einstieg ins Internet gegen 2001 habe ich privat überhaupt keine “Archive”
benutzt und alles unkomprimiert gespeichert.
(OK, die Disketten-Orgie mit MSBACKUP
sei hier mal schnell verdrängt.)
Und es war wohl ein paar Jahre vor dem Jahrtausendwechsel, als ich meine erste
Linux-Test-Installation von den damals
üblichen PC-Heft-CDs aufsetzte.
Es kann sich heute keiner mehr vorstellen was für ein unerträglicher Krampf
das mit Linux war, wo auf CD Nr.1 ein Basis-Linux per Start-Diskette auf
den Rechner gewurschtelt wurde und auf CD Nr. 2. fand man dann jede Menge
“Archive”, die man manuell entpacken und kompilieren musste.
Und so führte ich tatsächlich ein tar -xf irgendwas.tar.gz
wie im Heft
beschrieben aus, lange bevor ich meine Dateien per Windows Explorer als ZIP
verstaute.
TAR selbst gemacht
Kurz auf gnu.org/software/tar geblickt:
1struct posix_header 2{ 3 char name[100]; /* path: "path/to/file.txt" */ 4 char mode[8]; /* octal file-mode bits */ 5 char uid[8]; /* owner user id */ 6 char gid[8]; /* owner group id */ 7 char size[12]; /* octal file size */ 8 char mtime[12]; /* octal unix timestamp */ 9 char chksum[8]; 10 char typeflag; /* '0'==file, '5'==directory, 'L'==long-link */ 11 char linkname[100]; /* "path/to/linked.file" */ 12 char magic[6]; /* "ustar\0" */ 13 char version[2]; /* "00" */ 14 char uname[32]; /* owner user name */ 15 char gname[32]; /* owner group name */ 16 char devmajor[8]; 17 char devminor[8]; 18 char prefix[155]; 19};
Wie alt und primitiv TAR ist, sieht man schon an seinem Header, der immer 512 Bytes groß sein muss und mit genau 100 Bytes für den Dateinamen beginnt. Hmm … 100 Zeichen für einen ganzen Pfad? Da konnte selbst DOS mit 255 mehr. Zahlen für Dateiattribute, Größe und Zeitstempel liegen als ASCII-formatierte Oktalzahlen vor.
Dass dieses Format aus der Unix-Welt stammt sieht man dann an der UID
und GID
, sowie weiteren 100 Zeichen für einen Link-Namen.
In den Anfängen von Unix waren auch dort die Dateisysteme nicht in der Lage,
mehr als ein paar Zeichen pro Datei zu speichern und so empfand man ganze 100
Bytes als üppig dimensioniert.
Doch wegen dieser Einfachheit lässt sich das Format mit wenigen C-Zeilen selbst implementieren. Man füllt einen 512 Header aus, der die nachfolgende Datei beschreibt und danach werden einfach die Rohdaten hinten angestellt. Ist die Dateigröße kein genaues Vielfaches von 512, dann müssen so viele Null-Bytes nachgeschoben werden, bis ein 512er Block voll ist. Und im Anschluss daran folgt der nächste Datei-Header der auch wieder von den Rohdaten gefolgt wird. Am Ende schreibt man dann noch genau ein Kilobyte Nullen daran (also zwei leere 512-Byte Blöcke) und die TAR Datei ist fertig.
Auf zwei Dinge muss man aufpassen:
- TAR-interne Dateipfade müssen Slashes zur Pfadkomponententrennung nutzen
- Und wir MÜSSEN eine Prüfsumme über den Header berechnen.
Die Prüfsumme ist mir bei der Implementierung sehr auf die Nerven gegangen.
Man muss dafür den Header voll ausfüllen und die Bytes der Prüfsumme “leer”
lassen. Und mit “leer” sind “Blank” also Leerzeichen (Space) notwendig.
Ich hatte den Bereich aber ausgenullt und wunderte mich dann, weshalb
7-Zip und das tar
Tool die Datei
als korrupt zurückwiesen.
Lange Dateinamen
Am meisten interessiert mich ja, wie Workarounds aussehen, um “historische”
Limits zu umgehen.
TAR löst das 100 Zeichen pro Datei-Limit durch die Möglichkeit von
“Vendor-Extentions”. Und eine bekannte GNU
Erweiterung dafür sind LongLink Header.
Man erstellt also einen üblichen 512-Byte Dateiheader, doch anstatt des
Dateinamens steht in den ersten 13 Bytes der Token ././@LongLink
und das
Header-Type Byte steht auf L
. Und das Größen-Feld wird auf die Länge
des vollständigen Dateinamens gesetzt. Nach dem LongLink-Block kommt dann der
Dateiname genau so wie bei den Rohdaten in 512-Byte Häppchen und der letzte
Block muss dann auch wieder mit Nullen aufgefüllt werden.
Nun folgt der “richtige” Header unserer Datei, wo in den ersten 100 Zeichen
der Anfang des Dateinamens abgebildet ist. Alle anderen Felder sind genau
so ein gesetzt, wie für die Rohdaten notwendig und danach werden ebenso
die Rohdaten (wieder mit 512-Byte Vervollständigung) angefügt.
Man hat also quasi einen Präfix-Block + Meta-Daten und danach den eigentlichen Datei-Header und die echten Daten, die zusammengefasst dann die endgültige Datei beschreiben.
Naja…. offen gesagt schon etwas primitiv … aber so ist das eben bei den alten Dateiformaten.
Fazit
Bisher hatte ich TAR
nur über Bibliotheken und CLI
Tools genutzt. Doch wenn man diesen Kuchen selbst backt, merkt man erst,
wie krümelig er innen drinnen ist.
Erweiterungen und “andere Interpretationen” gibt es leider einige und somit ist man wohl besser beraten, eine fertig Lib zu nutzen, die alle Raffinessen dafür bereits drauf hat.
Doch wenn es nur darum geht, die eigenen Dateien schnell und unkompliziert
zu archivieren, so lege ich jedem ans Herz diesen Implementierungs-Tag mal
zu investieren.
Denn viele Libs und Tools sind heute mit Zusatzfunktionen und Abhängigkeiten
gespickt, die einem unkompliziertem Deployment im Wege stehen.
Da ziehe ich dann mein 400 Zeilen Eigenfabrikat vor, das nur stdio.h
und
string.h
braucht um TAR
Archive zu erzeugen.
Dann fährt noch die ZLIB darüber und
schon haben wir ein TAR.GZ
, so wie es im Buche steht.
Wieder was gelernt und daher einen schönen Tag gehabt! So kann’s gerne weitergehen.