Spaß mit Sparse Files
Man was zum Lachen für IT-Insider.
Was ein Sparse File ist, wisst Ihr? Unix-…. naja, nicht Grundwissen, schon fortgeschritten, gab es meines Wissens auch nicht ganz von Anfang an, sondern erst in einer späteren Entwicklungsstufe.
Also, gut, ich erklär’s.
Da stellen wir uns mal ganz dumm und fragen: Was ist ein Dateisystem?
Datenspeicher für den freien Zugriff, typischerweise und traditionell Festplatten, aber eben auch Disketten, SSDs, USB-Sticks, Speicherkarten, sind sogenannte „Block-Devices“. Das sind Geräte, in denen man die Daten blockweise liest und schreibt. Die haben eine feste Blockgröße – früher meist 512 Byte, aber seit es große schnelle Platten und SSDs und so etwas gibt, auch andere, wie 4096 Byte, und bei manchen kann man das sogar einstellen. Und dann kann man nicht einzelne Bytes lesen, sondern immer nur so einen ganzen Block. Man sagt auch Sektor, weil das früher bei den Festplatten einen Sektor der Spur darstellte, weil die runden, sich drehenden Platten nicht nur in Spuren, sondern diese ähnlich wie Kuchenstücke in Sektoren eingeteilt waren, in denen dann jeweils so viele Daten stehen, wie die Blockgröße beträgt. Im Prinzip hatten auch schon Lochkarten eine Blockgröße, nämlich 80 Zeichen (es gab sie auch in anderen Breiten). Die Sektoren sind (seit Jahrzehnten) durchnummeriert. In der Frühzeit der Technik musste man die Sektoren nach Platte, Spur und Sektornummer innerhalb der Spur ansprechen.
Nun ist es technisch schön und erforderlich, aber totaaaal unpraktisch und fehleranfällig, seine Daten in solchen Blöcken zu schreiben. Was, wenn die Daten nicht in so einen Block passen? Muss man sich dann selbst aufschreiben, in welchen Blöcken man sie hat? Und was macht man, wenn man Daten anhängen will, aber direkt danach schon andere Daten stehen? Alles nicht ganz einfach.
Obwohl: Wer kennt noch Forth? Eine seit 1969 entstandene Programmiersprache, die gleichzeitig auch Interpreter und Betriebssytem in einem war, und entwickelt wurde, um damals astronomische Fernrohre zu steuern – in der Computersteinzeit, als Computer nach gaaaaanz langsam und gaaaanz schwach waren, man noch keine richtigen Betriebssysteme hatte und alles in ein paar Byte unterkommen musste. Forth hat damals Programme und Daten direkt in Sektoren über die Sektorennummern geschrieben. Kurz gesagt: Forth war genial und hat mir damals (auf dem C64) eine Menge Spaß gemacht, weil es so kompakt und effizient war und so wenig dran war, aber das mit dem Schreiben von Daten direkt in Sektoren das war nicht schön und eben Computersteinzeit. Obwohl Forth im Prinzip nicht tot ist, es gibt es immer noch, etwa für den ESP32. Für Miniaturanwendungen ist das immer noch ganz nett.
Man möchte natürlich lieber „ganz normale“ Dateien haben, wie wir sie heute von jedem Betriebssystem kennen. Mach einfach eine Datei unter einem Namen auf, stecke sie in Verzeichnisse und so weiter und so fort.
Und das ist das, was ein Dateisystem macht: Es benutzt ein Speichergerät, das aus festen Blöcken aufgebaut ist, wie eine Festplatte oder einen USB-Stick, und verwaltet den so, dass es nach oben, zum Benutzer hin, so aussieht, als hätte man Dateien und Dateiverzeichnisse, in die man einfach so beliebig schreiben und daraus lesen kann. Nach oben schönes Dateisystem, nach unten Blockgerät. Ganz wichtiges und großes Thema.
Was passiert, wenn ich beispielsweise in einem Order namens Blogartikel eine Datei Beispiel.txt anlege und da etwas reinschreibe?
Es müssen vier Informationsgruppen gespeichert werden:
- die Daten selbst, die in in diese Datei schreibe, müssen in Blöcke geschrieben werden. Schreibe ich da also 800 Byte rein und das Gerät hat eine Blockgröße von 512 Byte, benötige ich zwei Blöcke.
- Dann braucht man die Verwaltungsorganisation, welche Blöcke überhaupt noch frei oder schon von anderen Daten belegt sind (und wenn keine mehr frei sind, gibt es eine Fehlermeldung „disk full“ oder „out of disk space“), und welche der belegen Blöcke in welcher Reihenfolge zu dieser Datei gehören.
- Meta-Informationen wie der Dateiname, das Dateidatum, Zugriffsrechte, manchmal noch Dateitypen und vieles andere mehr, dazu auch die Dateigröße. Denn wenn die Datei zwei Blöcke zu 512 Byte umfasst, weiß ich ja nicht, wieviele Bytes im letzten Block zur Datei gehören, denn die Datei könnte ja zwischen 513 und 1024 Byte groß sein, wenn sie zwei Blöcke belegt.
- Und wir müssen wissen, dass wo sie im Verzeichnisbaum liegt, hier also im Ordner Blogartikel.
Ich kann eine neue Datei aufmachen, Daten reinschreiben, und das Dateisystem belegt die Blöcke und schreibt die Daten da rein.
Was passiert aber, wenn ich eine neue Datei aufmache, aber nicht direkt Daten schreibe, sondern an die Position 1000000 springe, und dann Daten schreibe?
Kommt aufs Dateisystem an. Viele, vor allem ältere Dateisysteme haben so ein Springen gar nicht oder antworten mit einer Fehlermeldung „Du Idiot, die Datei ist doch nur 0 Byte groß, Du kannst gar nicht an die Position 1000000 springen!“
Neue Dateisysteme machen das aber.
Angenommen, ich mache eine neue Datei auf, springe an die Position 1000000 und schreibe dann 1 Byte da rein, dann wird die Datei so groß, und das Dateisystem zeigt mir an, dass die Datei 1000001 Byte groß sei, aber frage ich mit du, wieviel Blöcke die Datei belegt, ist die Antwort (je nach Dateisystem) 1. Weil bei moderneren Dateisystemen Blöcke erst dann belegt werden, wenn man da auch tatsächlich etwas reinschreibt. Man hat also eine Datei, die 1000001 Byte groß ist und trotzdem nur einen Block (oder wenige Blocks, die Verwaltung gehört ja noch dazu) belegt.
Man kann so etwas leicht programmieren, es gibt aber auch Kommandozeilenprogramme, die so etwas machen.
Beispiel:
Das Programm truncate kann so etwas.
Mache ich ein (ext4 Dateisystem)
truncate -s 1G windbeutel.txt
bekomme ich eine Datei
-rw-r----- 1 benutzer benutzer 1073741824 Apr 22 22:24 windbeutel.txt
die 1 GByte groß ist, aber überraschenderweise keine Daten belegt:
% du windbeutel.txt 0 windbeutel.txt
keinen einzigen Block belegt. Man kann sie sogar lexen: 1 GByte Nullen. Nämlich weil Blöcke, die nicht belegt sind („unalloziert“) immer gelesen werden, als bestünden sie nur aus Nullen.
Tiefergehendes Beispiel:
Mit fallocate -l 1000000 beispiel.txt
erzeugt man eine Datei, die dann wie
-rw-r—– 1 benutzer benutzer 1000000 Apr 22 22:11 beispiel.txt
aussieht, und auch genau so viele Blöcke belegt (ext4 Dateisystem)
% du beispiel.txt 980 beispiel.txt
auch 980 Blöcke zu 1024 Byte belegt, also alle Blöcke belegt hat.
fallocate hat sogar eine Option -d mit der man Blöcke wieder freigeben kann, das können aber nur manche Dateisysteme. Bei den meisten gilt, dass wenn ein Block einmal belegt ist, der immer belegt bleibt, bis die Datei wieder gelöscht wird.
Macht man dann aber ein
cp --sparse=always beispiel.txt beispiel2.txt
dann erkennt cp Folgen von Nullen in den Daten und überspringt das Schreiben, dieser Nullen, weil unbelegte Blöcke/Sektoren ja wie Nullen gelesen werden. Und Ergebnis: Obwohl beispiel2.txt eine identische Kopie von beispiel.txt ist, braucht beispiel2.txt keinen einen Block, weil man um Nullen zu lesen keinen Block braucht und cp die nicht geschrieben hat.
% du beispiel2.txt 0 beispiel2.txt
Vorteile und Gefahren
Das hat Vorteile, wenn man nur die Blöcke belegt, die man wirklich braucht, weil es viele „dünn besetzte“ Dateien gibt, denen das reicht.
Es ist aber auch höllengefährlich, weil man damit viel mehr Daten auf eine Festplatte oder einen USB-Stick schreiben kann, als die Platte groß ist – nämlich so lange es Nullen sind und dafür keine Blöcke belegt werden. Man hat laut Verzeichnis viel, viel, viel mehr „Daten“, als die Platte speichern kann. Macht man dann beim Kopieren oder Schreiben irgendeine Fehler, etwa ein Programm, das nicht mit solchen Sparse-Dateien umgehen kann, dann macht es Bumm, weil das Betriebssystem keine Daten mehr schreiben kann, wenn die Platte voll ist.
Das sollte man also nur verwenden, wenn man genau weiß, was man tut.
Man verwendet so etwas beispielsweise, wenn man virtuelle Maschinen betreibt und deren virtuelle „Festplatten“ in einer Sparse-Datei ablegt. Wenn man nur die Sektoren belegt, auf die man tatsächlich Daten schreibt, dann nennt man das heute „thin provisioned“, aber das kann ganz fürchterlich knallen, wenn man mehr Speicherplatz vergibt, als man hat, und darauf spekuliert, dass er eh nicht ganz belegt werden wird. Verwendet man oft bei virtuellen Laufwerken und virtuellen Maschinen. Wenn man also eine virtuelle Maschine installieren will, von der man weiß, dass sie so vielleicht ungefähr 5 oder 6 GB Platz belegt, man das vorher aber nicht so ganz genau sagen, kann, nur die Größenordnung kennt, kann man der beispielsweise 20 oder 50 GB Plattenplatz zuweisen, als „sparse“ oder eben „thin provisioned“, damit man nicht an die Grenze stößt, wenn man etwa nur 8GB geben würde, aber auch nicht die vollen 20 oder 50 GB belegen will, obwohl man sie vermutlich nicht braucht. Das ist eine gängige Praxis, aber sie jetzt voraus, dass man eben ganz genau weiß, was man tut.
Und dazu gehört dann auch, dass man solche Dateien nur mit solchen tools kopiert oder auf Backups schreibt, die mit Sparse-Files umgehen können und das erkenne und berücksichtigen.
Und man kann damit so richtig fiese Denial-of-Service Angriffe treiben. Und Leute verarschen.
Auf Heise beschreibt das einer:
Bekannter reist in die USA beruflich ein. Entsprechend zahlreicher Warnungen hat er sein Notebook auf Neustand zurückgesetzt damit die Beamten garnicht erst versuchen irgendwas zu finden, ergo, blankes Windows.
Und dann hatte er auf seiner – ebenfalls leeren – externen 16TByte HD eine Datei namens blank mit 1PByte. Nochmal: Ein PETA-Byte. Die armen US-Beamten verzweifelten drei Tage daran diese Datei zu öffnen – denn mal ehrlich, welcher Editor kann eine 1PByte große Datei bearbeiten?
Der Fairnesshalber, er musste die drei Tage nicht vor Ort warten. Nach drei Tagen kontaktiert ihn das FBI (!!!) und fragt was es denn mit der Datei auf sich habe… er versucht sich zu erinnern… oh, das ist ein leeres Sparse-File für eine virtuelle Maschine. Da sind nur Nullen drin. Ja, kann man leicht mit hexdump blank unter Linux untersuchen. Wird allerdings ein paar Tage dauern (Linux arbeitet Sparse zwar schneller ab als die mechanische Geschwindigkeit des Laufwerks, ein guter PC schafft trotzdem nur 5-10GByte/s)
Bei der Ausreise zwei Wochen später erhielt der das Ding zurück, mit drei Aufklebern von verschiedenen Behörden markiert. Der Zollbeamte merkte leicht verbittert an dass sie da ca. 100 Mannstunden dran verschwendet haben.
Das nennt man fehlende Sachkunde, oder auch neudeutsch „quality is a myth“, denn wenn da eine Datei herumliegt, die größer als das Speichermedium ist, muss man sofort wissen, dass das entweder nur eine Sparse-Datei sein kann oder das Dateisystem kaputt ist, und man an die nicht mit einem Editor drangehen kann, weil Editoren Sparse-Dateien nicht unterstützen und nur ganz viele Nullen sehen. Sehr viele Nullen. Und Editoren neigen dazu, die Datei vollständig in den Hauptspeicher lesen zu wollen.
Es könnte zum Beispiel auch ein (Bug oder absichtlich provoiziert) Fehler im Dateisystem sein, indem man zwar Sektoren tatsächlich beschreibt, aber dafür sorgt, dass die in einer fast-endlos-Schleife immer wieder gelesen werden.
Wenn man in so etwas 100 Mannstunden versenkt, und nicht versteht, was da vor sich geht, dann ist das einfach der falsche Job. Dann beherrscht man die Aufgabe nicht.