Theoretisch ist dies möglich. Aber es ist sehr hässlich und beinhaltet im Wesentlichen die Erstellung unseres Archivs von Hand.
Womit wir es zu tun haben
Das tar
Format arbeitet mit 512-Byte-Blöcken . Diese Größe ist fest und soll der herkömmlichen Größe des Festplattensektors entsprechen. Beim Speichern einer Datei in einem Archiv ist der erste 512-Byte-Block ein Header, der Dateimetadaten (Name, Größe, Typ usw.) enthält. Die folgenden Blöcke enthalten den Dateiinhalt. Unsere archivierten Daten werden also um 512 Bytes falsch ausgerichtet.
Die Blockgröße ("--sectorsize") von btrfs beträgt typischerweise 4096 Bytes . Theoretisch können wir dies wählen, aber in der Praxis sieht es so aus, als müsste es der Seitengröße unserer CPU entsprechen. Wir können also die Blöcke von btrfs nicht verkleinern.
Das tar
Programm hat ein Konzept einer größeren "Datensatzgröße", definiert als ein Vielfaches der Blockgröße, was fast so aussieht, als wäre es nützlich. Es stellt sich heraus, dass dies die Sektorgröße eines bestimmten Bandlaufwerks angeben soll, damit das tar
Schreiben von Teilbanddatensätzen vermieden wird. Die Daten werden jedoch immer noch in Einheiten von 512 Bytes erstellt und gepackt, sodass wir diese nicht verwenden können, um tar
die Blöcke zu vergrößern , wie Sie es sich erhofft hatten.
Ein letzter Punkt der Daten zu wissen ist , dass tar
‚s End-of-Archiv Marker zwei aufeinanderfolgenden all-Nullen - Blöcke ist, es sei denn, diese Blöcke innerhalb von Dateidaten. Daher werden naive Polsterblöcke wahrscheinlich nicht akzeptiert.
Der Hack
Wir können Auffülldateien einfügen. Bevor wir die Datei hinzufügen, die wir deduplizieren möchten (nennen dup
wir sie ), fügen wir zu Beginn unseres Archivs eine Datei hinzu pad
, deren Größe so ist
pad's header + pad's data + dup's header = 4096 bytes.
Auf diese Weise beginnen dup
die Daten an einer Blockgrenze und können dedupliziert werden.
Dann müssen wir für jede nachfolgende Datei auch die Größe der vorherigen Datei verfolgen, um die richtige Auffüllung zu berechnen. Wir müssen auch vorhersagen, ob eine Art Header-Erweiterung erforderlich sein wird: Beispielsweise bietet der grundlegende Tar-Header nur Platz für 100 Byte Dateipfad, sodass längere Pfade mit einer strukturell speziell benannten Datei codiert werden, deren Daten sind der volle Weg. Im Allgemeinen ist die Vorhersage der tar
Headergröße sehr komplex - das Dateiformat enthält viele Informationen aus mehreren historischen Implementierungen.
Ein kleiner Silberstreifen ist, dass alle Auffülldateien denselben Namen haben können. Wenn wir also entpacken, erhalten wir nur eine zusätzliche Datei mit einer Größe von weniger als 4096 Bytes.
Der sauberste Weg, ein solches Archiv zuverlässig zu erstellen, besteht wahrscheinlich darin, das GNU- tar
Programm zu ändern . Wenn Sie jedoch auf Kosten der CPU- und E / A-Zeit schnell und schmutzig sein möchten, können Sie für jede Datei Folgendes tun:
#!/bin/bash
# Proof of concept and probably buggy.
# If I ever find this script in a production environment,
# I don't know whether I'll laugh or cry.
my_file="$2"
my_archive="$1"
file_size="$(wc -c <"$my_file")"
arch_size="$(tar cb 1 "$my_file" | wc -c)" # "b 1": Remember that record size I mentioned? Set it to equal the block size so we can measure usefully.
end_marker_size=1024 # End-of-archive marker: 2 blocks' worth of 0 bytes
hdr_size="$(( (arch_size - file_size - end_marker_size) % 4096 ))"
pad_size="$(( (4096 - 512 - hdr_size) % 4096 ))"
(( pad_size < 512 )) && pad_size="$(( pad_size + 4096 ))"
# Assume the pre-existing archive is already a multiple of 4096 bytes long
# (not including the end-of-archive marker), and add extra padding to the end
# so that it stays that way.
file_blocks_size="$(( ((file_size+511) / 512) * 512 ))"
end_pad_size="$(( 4096 - 512 - (file_blocks_size % 4096) ))"
(( end_pad_size < 512 )) && end_pad_size="$(( end_pad_size + 4096 ))"
head -c $pad_size /dev/zero > _PADDING_
tar rf "$my_archive" _PADDING_ "$my_file"
head -c $end_pad_size /dev/zero > _PADDING_
tar rf "$my_archive" _PADDING_