Wie kann man tar (etc.) davon überzeugen, Blockgeräteinhalte zu archivieren?

13

Ich habe sechs logische Linux-Volumes, die zusammen eine virtuelle Maschine sichern. Die VM wird derzeit heruntergefahren, sodass Sie problemlos konsistente Images davon erstellen können.

Ich möchte alle sechs Bilder in einem Archiv zusammenfassen. Trivialerweise könnte ich so etwas tun:

cp /dev/Zia/vm_lvraid_* /tmp/somedir
tar c /tmp/somedir | whatever

Aber das schafft natürlich eine zusätzliche Kopie. Ich möchte die zusätzliche Kopie vermeiden.

Der naheliegende Ansatz:

tar c /dev/Zia/vm_lvraid_* | whatever

funktioniert nicht, da tar die dateien als besondere erkennt (symlinks in diesem fall) und grundsätzlich ln -sim archiv ablegt. Oder, mit --dereferenceoder direkt darauf hingewiesen /dev/dm-X, erkennt es sie als spezielle (Gerätedateien) und speichert sie grundsätzlich mknodim Archiv.

Ich habe nach Befehlszeilenoptionen für tar gesucht, um dieses Verhalten zu überschreiben, und konnte keine finden. Ich habe auch versucht cpio, das gleiche Problem, und konnte dort auch keine Optionen finden, um es zu überschreiben. Ich habe es auch versucht 7z(ebenso). Gleiche mit pax. Ich habe es sogar versucht zip, was sich gerade selbst verwirrt hat.

edit: Wenn man sich den Quellcode von GNU tar und GNU cpio ansieht, sieht es so aus, als ob keiner von beiden dies kann. Zumindest nicht ohne ernsthafte Tricks (der spezielle Umgang mit Gerätedateien kann nicht deaktiviert werden). Vorschläge für ernsthafte Tricks wären also willkommen oder alternative Hilfsprogramme.

TLDR: Gibt es einen Archivierer, der mehrere Disk-Images (von Raw-Geräten) zusammenpackt und diese Ausgabe streamen kann, ohne zusätzliche Kopien auf der Festplatte anzufertigen? Meine Präferenz würde in einem gemeinsamen Format wie POSIX oder GNU tar ausgegeben.

derobert
quelle
Ich habe es überzeugt.
mikeserv

Antworten:

11

Also wollte ich das kürzlich mit machen tar. Einige Nachforschungen ergaben, dass es mehr als unsinnig war, dass ich es nicht konnte. Ich habe mir dieses seltsame split --filter="cat >file; tar -r ..."Ding ausgedacht , aber es war furchtbar langsam. Und je mehr ich darüber las, tardesto unsinniger schien es.

Sie sehen, tarist nur eine verkettete Liste von Datensätzen. Die konstituierenden Dateien werden in keiner Weise geändert - sie befinden sich vollständig im Archiv. Sie sind jedoch an 512-Byte- Blockgrenzen blockiert , und vor jeder Datei steht ein Header . Das ist es. Das Header-Format ist wirklich sehr einfach.

Also schrieb ich meine eigenen tar. Ich nenne es ... shitar.

z() (IFS=0; printf '%.s\\0' $(printf "%.$(($1-${#2}))d"))
chk() (IFS=${IFS#??}; set -f; set -- $(     
        printf "$(fmt)" "$n" "$@" '' "$un" "$gn"               
);  IFS=; a="$*"; printf %06o "$(($(
        while printf %d+ "'${a:?}"; do a=${a#?}; done 2>/dev/null
)0))")                                                                 
fmt() { printf '%s\\'"${1:-n}" %s "${1:+$(z 99 "$n")}%07d" \
    %07o %07o %011o %011o "%-${1:-7}s" ' 0' "${1:+$(z 99)}ustar  " %s \
    "${1:+$(z 31 "$un")}%s"
}

Das ist wirklich das Fleisch und die Kartoffeln. Es schreibt die Überschriften und berechnet die Chksum - was relativ gesehen der einzige schwierige Teil ist. Es macht das ustarHeader-Format ... vielleicht . Zumindest emuliert es das, was GNU tarfür das ustarHeader-Format hält, bis zu dem Punkt, dass es sich nicht beschwert. Und es steckt noch mehr dahinter, ich habe es nur noch nicht wirklich geronnen . Hier zeige ich Ihnen:

for f in 1 2; do echo hey > file$f; done
{ tar -cf - file[123]; echo .; } | tr \\0 \\n | grep -b .

0:file1                      #filename - first 100 bytes
100:0000644                  #octal mode - next 8
108:0001750                  #octal uid,
116:0001750                  #gid - next 16
124:00000000004              #octal filesize - next 12
136:12401536267              #octal epoch mod time - next 12
148:012235                   #chksum - more on this
155: 0                       #file type - gnu is weird here - so is shitar
257:ustar                    #magic string - header type
265:mikeserv                 #owner
297:mikeserv                 #group - link name... others shitar doesnt do
512:hey                      #512-bytes - start of file   
1024:file2                   #512 more - start of header 2
1124:0000644
1132:0001750
1140:0001750
1148:00000000004
1160:12401536267
1172:012236
1179: 0
1281:ustar  
1289:mikeserv
1321:mikeserv
1536:hey
10240:.                     #default blocking factor 20 * 512

Das ist tar. Alles ist mit \0Nullen aufgefüllt, daher verwandle ich mich emzur \nbesseren Lesbarkeit in eWlines. Und shitar:

#the rest, kind of, calls z(), fmt(), chk() + gets $mdata and blocks w/ dd
for n in file[123]
do d=$n; un=$USER; gn=$(id --group --name)
   set -- $(stat --printf "%a\n%u\n%g\n%s\n%Y" "$n")
   printf "$(fmt 0)" "$n" "$@" "$(chk "$@")" "$un" "$gn"
   printf "$(z $((512-298)) "$gn")"; cat "$d"  
   printf "$(x=$(($4%512));z $(($4>512?($x>0?$x:512):512-$4)))"
done |
{ dd iflag=fullblock conv=sync bs=10240 2>/dev/null; echo .; } |
tr \\0 \\n | grep -b .

AUSGABE

0:file1                 #it's the same. I shortened it.
100:0000644             #but the whole first file is here
108:0001750
116:0001750
124:00000000004
136:12401536267
148:012235              #including its checksum
155: 0
257:ustar  
265:mikeserv
297:mikeserv
512:hey
1024:file2
...
1172:012236             #and file2s checksum
...
1536:hey
10240:.

Ich sage irgendwie da oben, weil das nicht shitardas Ziel ist - tarmacht das schon wunderbar. Ich wollte nur zeigen, wie es funktioniert - was bedeutet, ich muss auf die berühren chksum. Wenn es das nicht gäbe, würde ich einem einfach ddden Kopf abschneidentar Akte loswerden und damit fertig sein. Das mag manchmal sogar funktionieren, aber es wird chaotisch, wenn sich mehrere Mitglieder im Archiv befinden. Trotzdem ist die Chksum wirklich einfach.

Machen Sie zuerst 7 Leerzeichen (was eine seltsame Gnu-Sache ist, denke ich, wie in der Spezifikation 8 steht, aber was auch immer - ein Hack ist ein Hack) . Addieren Sie dann die Oktalwerte jedes Bytes im Header. Das ist dein Chksum. Sie benötigen also die Dateimetadaten, bevor Sie den Header erstellen, oder Sie haben keine Prüfsumme. Und das ist einustar meistens Archiv.

In Ordnung. Nun, was soll es tun:

cd /tmp; mkdir -p mnt     
for d in 1 2 3                                                
do  fallocate -l $((1024*1024*500)) disk$d
    lp=$(sudo losetup -f --show disk$d)
    sync
    sudo mkfs.vfat -n disk$d "$lp"
    sudo mount  "$lp" mnt
    echo disk$d file$d | sudo tee mnt/file$d
    sudo umount mnt
    sudo losetup -d "$lp"
done

Das macht drei 500-MB-Disk-Images, formatiert und hängt jede und schreibt eine Datei in jede.

for n in disk[123]
do d=$(sudo losetup -f --show "$n")
   un=$USER; gn=$(id --group --name)
   set -- $(stat --printf "%a\n%u\n%g\n$(lsblk -bno SIZE "$d")\n%Y" "$n")
   printf "$(fmt 0)" "$n" "$@" "$(chk "$@")" "$un" "$gn"
   printf "$(z $((512-298)) "$gn")"
   sudo cat "$d"
   sudo losetup -d "$d"
done | 
dd iflag=fullblock conv=sync bs=10240 2>/dev/null |
xz >disks.tar.xz

Hinweis - Anscheinend werden blockierte Geräte nur immer korrekt blockiert. Ziemlich praktisch.

Das ist tarder Inhalt der Datenträger-Gerätedateien im Stream und leitet die Ausgabe an xz.

ls -l disk*
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk1
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk2
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk3
-rw-r--r-- 1 mikeserv mikeserv    229796 Sep  3 01:05 disks.tar.xz

Nun, der Moment der Wahrheit ...

 xz -d <./disks.tar.xz| tar -tvf -
-rw-r--r-- mikeserv/mikeserv 524288000 2014-09-03 01:01 disk1
-rw-r--r-- mikeserv/mikeserv 524288000 2014-09-03 01:01 disk2
-rw-r--r-- mikeserv/mikeserv 524288000 2014-09-03 01:01 disk3

Hurra! Extraktion...

xz -d <./disks.tar.xz| tar -xf - --xform='s/[123]/1&/'  
ls -l disk*
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk1
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk11
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk12
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk13
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk2
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk3
-rw-r--r-- 1 mikeserv mikeserv    229796 Sep  3 01:05 disks.tar.xz

Vergleich...

cmp disk1 disk11 && echo yay || echo shite
yay

Und der Berg ...

sudo mount disk13 mnt
cat mnt/*
disk3 file3

Und so, in diesem Fall, shitarfunktioniert es, denke ich. Ich möchte lieber nicht auf all die Dinge eingehen, die es nicht gut machen wird. Aber ich werde sagen - mach zumindest keine Zeilenumbrüche in den Dateinamen.

Sie können dies auch tun - und sollten es vielleicht tun, wenn Sie die Alternativen in Betracht ziehen, die ich Ihnen angeboten habe squashfs. Nicht nur erhalten Sie das einzelne Archiv aus dem Stream gebaut - aber es ist mountmöglich und builtin den Kernelvfs :

Aus pseudo-file.example :

# Copy 10K from the device /dev/sda1 into the file input.  Ordinarily
# Mksquashfs given a device, fifo, or named socket will place that special file
# within the Squashfs filesystem, this allows input from these special
# files to be captured and placed in the Squashfs filesystem.
input f 444 root root dd if=/dev/sda1 bs=1024 count=10

# Creating a block or character device examples

# Create a character device "chr_dev" with major:minor 100:1 and
# a block device "blk_dev" with major:minor 200:200, both with root
# uid/gid and a mode of rw-rw-rw.
chr_dev c 666 root root 100 1
blk_dev b 666 0 0 200 200

Sie können auch verwenden btrfs (send|receive), um ein Sub-Volume in einen beliebigen stdin-fähigen Kompressor zu streamen . Dieses Subvolume muss nicht existieren, bevor Sie es als Kompressionscontainer verwenden möchten.

Trotzdem über squashfs...

Ich glaube nicht, dass ich dies gerecht mache. Hier ist ein sehr einfaches Beispiel:

 cd /tmp; mkdir ./emptydir
 mksquashfs ./emptydir /tmp/tmp.sfs -p \
    'file f 644 mikeserv mikeserv echo "this is the contents of file"'                             

Parallel mksquashfs: Using 6 processors
Creating 4.0 filesystem on /tmp/tmp.sfs, block size 131072.
[==================================================================================|] 1/1 100%
Exportable Squashfs 4.0 filesystem, gzip compressed, data block size 131072
        compressed data, compressed metadata, compressed fragments,... 
###...
###AND SO ON
###...

echo '/tmp/tmp.sfs /tmp/imgmnt squashfs loop,defaults,user 0 0'|
    sudo tee -a /etc/fstab >/dev/null

mount ./tmp.sfs     
cd ./imgmnt
ls

total 1
-rw-r--r-- 1 mikeserv mikeserv 29 Aug 20 11:34 file

cat file

this is the contents of file

cd ..
umount ./imgmnt

Das ist nur das Inline- -pArgument für mksquash. Sie können eine Datei mit beliebig -pfvielen Quellen erstellen . Das Format ist einfach: Sie definieren den Namen / Pfad einer Zieldatei im Dateisystem des neuen Archivs, geben ihm einen Modus und einen Eigentümer und legen fest, von welchem ​​Prozess stdout ausgeführt und gelesen werden soll. Sie können so viele erstellen, wie Sie möchten - und Sie können LZMA, GZIP, LZ4, XZ ... verwenden. Hmm, es gibt mehr ... Komprimierungsformate, als Sie möchten. Und das Endergebnis ist ein Archiv, in das Siecd .

Mehr zum Format:

Dies ist natürlich nicht nur ein Archiv, sondern ein komprimiertes, einbindbares Linux-Dateisystem-Image. Das Format ist das des Linux-Kernels - es ist ein von Vanille-Kernel unterstütztes Dateisystem. Auf diese Weise ist es genauso verbreitet wie der Vanilla Linux-Kernel. Wenn Sie mir also sagen würden, dass Sie ein Vanilla-Linux-System verwenden, auf dem das tarProgramm nicht installiert ist, wäre ich zweifelhaft - aber ich würde Ihnen wahrscheinlich glauben. Aber wenn Sie mir sagen würden, dass Sie ein Vanilla Linux-System verwenden, auf dem das squashfsDateisystem nicht unterstützt wird, würde ich Ihnen nicht glauben.

mikeserv
quelle
Mike, könnten wir Sie beunruhigen, ein kleines, in sich geschlossenes Beispiel zu erstellen, damit die Leute damit experimentieren können? Es sieht so aus, als würdest du zumindest einen Teil davon machen, aber ich bin mir nicht sicher. In input f 444 root root dd if=/dev/sda1 bs=1024 count=10ist f die Dateieingabe? Vielleicht ist es besser, ein Spielzeuggerät zu erstellen, es mit Daten zu füllen und daraus zu schreiben? Und braucht das alles root?
Faheem Mitha
@FaheemMitha - ja, das kann ich, aber hier habe ich es nicht getan. Der Link führt zur offiziellen Dokumentation - direkt daraus entnommen. Es wäre jedoch besser, wenn ich ein Befehlsbeispiel machen würde. Ich habe es schon mal gemacht - es ist ziemlich cool. Wie auch immer - die inputDatei ist eine Datei im squashfsArchiv - das Dateisystem-Image, das sich aus der Ausführung des Befehls ergibt. Wenn Sie dies tun mksquash, können Sie diese Pseudodateibefehle für Befehle angeben, die ausgeführt werden und von denen der stdoutbeim Komprimieren erfasst wird.
mikeserv
@FaheemMitha - oh, und es ist kein root erforderlich, um die Komprimierung durchzuführen , auch wenn dies möglicherweise für das Mounten erforderlich ist - es handelt sich um ein Dateisystem-Image, das sich ergibt. Es ist dasselbe Dateisystem, das alle Linux Live-Disks verwenden. In der Tat - eine sehr coole Sache - ist, dass Sie ein Root-Image erstellen können, indem Sie diese Pseudodateien verwenden, ohne root zu sein - wie wenn Sie Ihre Gerätedateien und beliebige MAJ: MIN-Nummern festlegen.
mikeserv
Ich denke, es sollte möglich sein, eine Gerätedatei zu erstellen, darauf zu schreiben und dann daraus zu erstellen, ohne sie jemals zu mounten, oder? Vielleicht erfordert es keine Wurzel, was natürlich vorzuziehen wäre.
Faheem Mitha
Nun, hier sind keine BTRFS beteiligt, das wird also nicht funktionieren. Aber Squashfs ist verrückt genug, es könnte funktionieren. Es hat jedoch den Nachteil, dass es kein gängiges Archivformat ist.
Derobert
4

Ihr Problem hat mich einige Zeit verwirrt, und ich glaube, ich habe eine Lösung gefunden, die funktionieren würde.

Ich denke, Sie können mit 7z mit der -si{NAME}Flagge erreichen, was Sie wollen .

Sie können sich an Ihre Bedürfnisse anpassen.

7z a test.7z -siSDA2.txt < /dev/sda1
7z a test.7z -siSDA2.txt < /dev/sda2

7z l test.7z 

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,8 CPUs)

Listing archive: test.7z

--
Path = test.7z
Type = 7z
Method = LZMA
Solid = -
Blocks = 2
Physical Size = 1770
Headers Size = 162

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2014-08-19 22:01:08 .....         6314          804  SDA1.txt
2014-08-19 22:01:11 .....         6314          804  SDA2.txt
------------------- ----- ------------ ------------  ------------------------
                                 12628         1608  2 files, 0 folders

BEARBEITEN : Entfernen Sie die nutzlose Verwendung von Katze

Tony
quelle
Es wäre hilfreich, ein kleines Beispiel zu haben, das die Leute ausprobieren können. Erstellen Sie z. B. ein Blockgerät, schreiben Sie darauf und schreiben Sie dann davon aus. Root nicht zu benötigen wäre ein Plus.
Faheem Mitha
Im Beispiel ist / dev / sda1 ein Blockgerät. Der Befehl cat hat den Zweck, den Inhalt des Geräts in stdout abzulegen. Anschließend erstellen (oder aktualisieren) Sie das Archiv und speichern die Daten in dem durch den Parameter -si angegebenen Dateinamen von stdin. Das Ergebnis innerhalb des Archivs ist der Inhalt der einzelnen Blockgeräte. Der Befehl "cat" benötigt root, um die Daten vom Gerät zu lesen.
Tony
Das ist eine nutzlose Verwendung von Cat , passt aber ansonsten ziemlich gut in die Rechnung. Seltsamerweise erwähnt meine 7zManpage nicht, dass -si einen Dateinamen annehmen kann, aber es funktioniert. Es ist nicht perfekt (die Ausgabe kann nicht irgendwo weitergeleitet werden), aber es ist definitiv das Beste, was bisher in einem gemeinsamen Format ausgegeben wird.
Derobert
@FaheemMitha, das root erfordert oder nicht, hängt von den Berechtigungseinstellungen auf Ihrem System ab, obwohl nur root neue Blockgeräte erstellen kann.
Derobert
@derobert Entfernte die Katze :)
Tony