Comment convaincre tar (etc.) d'archiver le contenu d'un périphérique de bloc?

13

J'ai six volumes logiques Linux qui soutiennent ensemble une machine virtuelle. La machine virtuelle est actuellement arrêtée, il est donc facile d'en prendre des images cohérentes.

Je voudrais regrouper les six images dans une archive. Trivialement, je pourrais faire quelque chose comme ça:

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

Mais cela crée bien sûr une copie supplémentaire. Je voudrais éviter la copie supplémentaire.

L'approche évidente:

tar c /dev/Zia/vm_lvraid_* | whatever

ne fonctionne pas, car tar reconnaît les fichiers comme spéciaux (liens symboliques dans ce cas) et les stocke essentiellement ln -sdans l'archive. Ou, avec --dereferenceou directement pointé /dev/dm-X, il les reconnaît comme spéciaux (fichiers de périphérique) et les stocke essentiellement mknoddans l'archive.

J'ai recherché des options de ligne de commande à tar pour remplacer ce comportement et je n'en ai trouvé aucune. J'ai également essayé cpio, même problème, et je n'ai trouvé aucune option pour le remplacer non plus. J'ai aussi essayé 7z(idem). Même chose avec pax. J'ai même essayé zip, ce qui est devenu confus.

edit: En regardant le code source de GNU tar et GNU cpio, il semble qu'aucun d'eux ne puisse le faire. Du moins, pas sans ruse sérieuse (la gestion spéciale des fichiers de périphérique ne peut pas être désactivée). Ainsi, des suggestions de ruse sérieuse seraient appréciées ou des utilitaires alternatifs.

TLDR: Existe - t-il un archiveur qui regroupera plusieurs images de disque (prises à partir de périphériques bruts) et diffusera cette sortie sans faire de copies supplémentaires sur le disque? Ma préférence serait la sortie dans un format commun, comme POSIX ou GNU tar.

derobert
la source
Je l'ai convaincu.
mikeserv

Réponses:

11

J'ai récemment voulu faire ça avec tar. Une enquête m'a indiqué que c'était plus qu'un peu absurde que je ne pouvais pas. J'ai trouvé cette split --filter="cat >file; tar -r ..."chose bizarre , mais bon, c'était terriblement lent. Et plus je lisais sur tarle plus insensé, il semblait.

Vous voyez, tarc'est juste une liste concaténée d'enregistrements. Les fichiers constituants ne sont en aucun cas modifiés - ils sont entiers dans l'archive. Mais ils sont bloqués sur des limites de bloc de 512 octets , et avant chaque fichier, il y a un en- tête . C'est ça. Le format d'en-tête est également très, très simple.

J'ai donc écrit le mien tar. Je l' appelle ... 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"
}

C'est vraiment la viande et les pommes de terre. Il écrit les en-têtes et calcule le chksum - qui, relativement parlant, est la seule partie difficile. Il fait le ustarformat d'en-tête ... peut-être . Au moins, il émule ce que GNU tarsemble penser être le ustarformat d'en-tête au point qu'il ne se plaint pas. Et il y a plus, c'est juste que je ne l'ai pas encore vraiment coagulé . Ici, je vais vous montrer:

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

Voilà tar. Tout est rembourré avec des valeurs \0nulles, donc je me transforme emen \nlignes électroniques pour plus de lisibilité. Et 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 .

PRODUCTION

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:.

Je dis un peu là-haut parce que ce n'est pas shitarle but - le fait tardéjà magnifiquement. Je voulais juste montrer comment cela fonctionne - ce qui signifie que je dois toucher le chksum. Si ce n'était pas pour ça, je serais juste en train de ddperdre la tête d'un tardossier et j'en aurais fini. Cela peut même parfois fonctionner, mais cela devient compliqué lorsqu'il y a plusieurs membres dans l'archive. Pourtant, le chksum est vraiment facile.

Tout d'abord, faites-en 7 espaces - (ce qui est une chose gnu bizarre, je pense, comme le spécifie 8, mais peu importe - un hack est un hack) . Additionnez ensuite les valeurs octales de chaque octet dans l'en-tête. Voilà votre chksum. Vous avez donc besoin des métadonnées du fichier avant de faire l'en-tête, ou vous n'avez pas de chksum. Et c'est ustarsurtout une archive.

D'accord. Maintenant, ce qu'il est censé faire:

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

Cela crée trois images disque de 500 millions de pixels, les formate et les monte chacune, et écrit un fichier sur chacune.

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

Remarque - apparemment, les périphériques bloqués seront toujours bloqués correctement. Assez pratique.

C'est tarle contenu des fichiers de périphérique de disque en flux et dirige la sortie vers 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

Maintenant, le moment de vérité ...

 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

Hourra! Extraction...

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

Comparaison...

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

Et la monture ...

sudo mount disk13 mnt
cat mnt/*
disk3 file3

Et donc, dans ce cas, shitarfonctionne bien, je suppose. Je préfère ne pas entrer dans toutes les choses que cela ne fera pas bien. Mais, je dirai - ne faites pas au moins de nouvelles lignes dans les noms de fichiers.

Vous pouvez également le faire - et peut-être devriez-vous, compte tenu des alternatives que j'ai proposées - avec cela squashfs. Non seulement vous obtenez l'archive unique construite à partir du flux - mais elle est mountcapable et intégrée au noyau vfs:

Du pseudo-fichier.exemple :

# 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

Vous pouvez également utiliser btrfs (send|receive)pour diffuser un sous- stdinvolume dans le compresseur capable de votre choix. Ce sous-volume n'a pas besoin d'exister avant que vous décidiez de l'utiliser comme conteneur de compression, bien sûr.

Pourtant, à propos de squashfs...

Je ne crois pas que je fais cette justice. Voici un exemple très simple:

 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

Ce n'est que l' -pargument en ligne pour mksquash. Vous pouvez créer un fichier -pfcontenant autant de fichiers que vous le souhaitez. Le format est simple - vous définissez le nom / chemin d'un fichier cible dans le système de fichiers de la nouvelle archive, vous lui donnez un mode et un propriétaire, puis vous lui dites à partir de quel processus exécuter et lire stdout. Vous pouvez en créer autant que vous le souhaitez - et vous pouvez utiliser LZMA, GZIP, LZ4, XZ ... hmm il y a plus ... de formats de compression que vous le souhaitez. Et le résultat final est une archive dans laquelle vous cd.

Plus d'informations sur le format cependant:

Bien sûr, il ne s'agit pas seulement d' une archive - c'est une image compressée et montable du système de fichiers Linux. Son format est celui du noyau Linux - c'est un système de fichiers pris en charge par le noyau vanilla. De cette façon, il est aussi commun que le noyau Linux vanilla. Donc, si vous me disiez que vous utilisiez un système Linux vanilla sur lequel le tarprogramme n'était pas installé, je serais douteux - mais je vous croirais probablement. Mais si vous me disiez que vous utilisiez un système Linux vanilla sur lequel le squashfssystème de fichiers n'était pas pris en charge, je ne vous croirais pas.

mikeserv
la source
Mike, pourrions-nous vous déranger pour créer un petit exemple autonome afin que les gens puissent l'expérimenter? Il semble que vous fassiez au moins une partie de cela ci-dessus, mais je ne suis pas sûr. Dans input f 444 root root dd if=/dev/sda1 bs=1024 count=10f est l'entrée du fichier? Peut-être serait-il préférable de créer un appareil jouet, de le remplir de données et d'écrire à partir de lui? Et tout cela nécessite-t-il un root?
Faheem Mitha
@FaheemMitha - oui je peux le faire, mais je ne l'ai pas fait ici. Le lien est vers la documentation officielle - elle est tirée directement d'elle. Ce serait mieux si je faisais un exemple de commande. Je l'ai déjà fait - c'est plutôt cool. Quoi qu'il en soit - le inputfichier est un fichier dans l' squashfsarchive - l'image du système de fichiers qui résulte de l'exécution de la commande. Lorsque vous le faites, mksquashvous pouvez spécifier ces commandes de pseudofichier pour les commandes qui sont exécutées et à partir desquelles le stdoutest capturé au moment de la compression.
mikeserv
@FaheemMitha - oh, et il n'a pas besoin de root pour faire la compression , bien qu'il puisse faire le montage - c'est une image de système de fichiers qui en résulte. C'est le même système de fichiers que tous les disques Linux Live utilisent. En fait - une chose très cool - est que vous pouvez créer une image appartenant à la racine en utilisant ces pseudo-fichiers sans être root - comme définir les fichiers de votre appareil et les nombres MAJ: MIN arbitraires.
mikeserv
Je suppose qu'il devrait être possible de créer un fichier de périphérique, d'y écrire, puis de le créer sans jamais le monter, non? Donc, peut-être qu'il ne nécessite pas de root, ce qui serait évidemment préférable.
Faheem Mitha
Eh bien, aucun btrfs n'est impliqué ici, donc cela ne fonctionnera pas. Mais squashfs est assez fou pour que ça marche. Bien qu'il ait l'inconvénient de ne pas être un format d'archive commun.
derobert
4

Votre problème m'a intrigué pendant un certain temps, et je pense avoir trouvé une solution qui fonctionnerait.

Je pense que vous pouvez réaliser ce que vous voulez avec 7z en utilisant le -si{NAME}drapeau.

Vous pourrez vous adapter à votre besoin.

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

EDIT : supprimer l'utilisation inutile de chat

Tony
la source
Il serait utile d'avoir un petit exemple que les gens peuvent essayer. Par exemple, créez un périphérique bloc, écrivez-y, puis écrivez-le. Ne pas avoir besoin de root serait un plus.
Faheem Mitha
Dans l'exemple / dev / sda1 est un périphérique bloc. La commande cat a pour but de vider le contenu du périphérique vers stdout. Ensuite, 7z crée (ou met à jour) l'archive et stocke les données dans le nom de fichier spécifié par le paramètre -si depuis stdin. Le résultat dans l'archive est le contenu de chaque périphérique de bloc. La commande "cat" a besoin de root pour lire les données de l'appareil.
Tony
C'est une utilisation inutile de Cat , mais sinon, cela correspond assez bien au projet de loi. Curieusement, ma 7zpage de manuel ne mentionne pas -si peut prendre un nom de fichier, mais cela fonctionne. Ce n'est pas parfait (la sortie ne peut pas être canalisée quelque part), mais c'est certainement le meilleur jusqu'à présent qui sort dans un format commun.
derobert
@FaheemMitha nécessitant ou non root va dépendre des paramètres d'autorisation sur votre système, bien que seul root puisse créer de nouveaux périphériques de bloc.
derobert
@derobert Supprimé le chat :)
Tony