Como convencer o tar (etc.) a arquivar o conteúdo do dispositivo de bloco?

13

Eu tenho seis volumes lógicos do Linux que, juntos, apoiam uma máquina virtual. Atualmente, a VM está desligada, portanto é fácil capturar imagens consistentes delas.

Eu gostaria de agrupar todas as seis imagens em um arquivo. Trivialmente, eu poderia fazer algo assim:

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

Mas é claro que isso cria uma cópia extra. Eu gostaria de evitar a cópia extra.

A abordagem óbvia:

tar c /dev/Zia/vm_lvraid_* | whatever

não funciona, pois o tar reconhece os arquivos como especiais (neste caso, os links simbólicos) e basicamente os armazena ln -sno arquivo morto. Ou, com --dereferenceou diretamente apontado /dev/dm-X, ele os reconhece como especiais (arquivos de dispositivo) e basicamente os armazena mknodno arquivo morto.

Procurei opções de linha de comando para tar para substituir esse comportamento e não consegui encontrar nenhuma. Também tentei o cpiomesmo problema e também não encontrei nenhuma opção para substituí-lo. Eu também tentei 7z(idem). O mesmo com pax. Eu até tentei zip, o que ficou confuso.

edit: Examinando o código fonte do GNU tar e GNU cpio, parece que nenhum deles pode fazer isso. Pelo menos, não sem truques sérios (o tratamento especial dos arquivos do dispositivo não pode ser desativado). Portanto, sugestões de truques sérios seriam apreciadas ou utilidades alternativas.

TLDR: Existe algum arquivador que agrupe várias imagens de disco (extraídas de dispositivos brutos) e transmita essa saída, sem fazer cópias extras em disco? Minha preferência seria de saída em um formato comum, como POSIX ou GNU tar.

derobert
fonte
Eu o convenci.
mikeserv

Respostas:

11

Então, recentemente, eu queria fazer isso tar. Alguma investigação me indicou que era mais do que um pouco absurdo que eu não podia. Eu inventei essa split --filter="cat >file; tar -r ..."coisa estranha , mas, bem, era terrivelmente lenta. E quanto mais eu leio, tarmais absurdo parecia.

Você vê, taré apenas uma lista concatenada de registros. Os arquivos constituintes não são alterados de forma alguma - eles estão inteiros no arquivo morto. Mas eles são bloqueados nos limites do bloco de 512 bytes e, precedendo cada arquivo, existe um cabeçalho . É isso aí. O formato do cabeçalho também é muito, muito simples.

Então, eu escrevi o meu tar. Eu chamo-lhe ... 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"
}

Essa é a carne e as batatas, na verdade. Ele escreve os cabeçalhos e calcula o chksum - que, relativamente falando, é a única parte difícil. Faz o ustarformato do cabeçalho ... talvez . Pelo menos, emula o que o GNU tarparece pensar que é o ustarformato do cabeçalho, a ponto de não reclamar. E tem mais, é que eu ainda não coagulei ainda. Aqui, eu vou te mostrar:

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

É isso tar. Tudo está preenchido com \0valores nulos, então eu apenas me transformo emem \newlines para facilitar a leitura. E 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 .

RESULTADO

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

Eu digo tipo lá em cima porque esse não shitaré o objetivo - tarjá faz isso lindamente. Eu só queria mostrar como ele funciona - o que significa que eu preciso tocar no chksum. Se não fosse por isso, eu estaria ddsaindo do cabeçalho de um tararquivo e pronto. Às vezes, isso pode até funcionar, mas fica confuso quando há vários membros no arquivo. Ainda assim, o chksum é realmente fácil.

Primeiro, faça 7 espaços - (que é uma coisa estranha de gnu, eu acho, como a especificação diz 8, mas tanto faz - um hack é um hack) . Em seguida, adicione os valores octais de cada byte no cabeçalho. Esse é o seu chksum. Portanto, você precisa dos metadados do arquivo antes de executar o cabeçalho ou não possui um chksum. E isso é um ustararquivo, principalmente.

Está bem. Agora, o que se pretende fazer:

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

Isso cria três imagens de disco de 500M, formata e monta cada uma e grava um arquivo em cada uma.

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

Nota - aparentemente os dispositivos de bloqueio sempre bloqueiam corretamente. Muito útil.

Esse taré o conteúdo dos arquivos do dispositivo de disco in-stream e canaliza a saída para 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

Agora, o momento da verdade ...

 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

Viva! Extração...

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

Comparação...

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

E o monte ...

sudo mount disk13 mnt
cat mnt/*
disk3 file3

E assim, neste caso, shitarexecuta bem, eu acho. Eu prefiro não entrar em todas as coisas que não farão bem. Mas eu direi - não faça novas linhas nos nomes de arquivos pelo menos.

Você também pode fazer - e talvez deva, considerando as alternativas que eu ofereci - com isso squashfs. Você não apenas constrói o único arquivo morto a partir do fluxo, mas também é mountcapaz e incorporado aos arquivos do kernel vfs:

No pseudo-arquivo.exemplo :

# 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

Você também pode usar btrfs (send|receive)para transmitir um subvolume para o stdincompressor que você gosta. Esse subvolume não precisa existir antes de você decidir usá-lo como contêiner de compactação, é claro.

Ainda assim, sobre squashfs...

Eu não acredito que estou fazendo isso justiça. Aqui está um exemplo muito simples:

 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

Esse é apenas o -pargumento embutido para mksquash. Você pode criar um arquivo -pfcontendo quantos deles desejar. O formato é simples - você define o nome / caminho de um arquivo de destino no novo sistema de arquivos, fornece um modo e um proprietário e, em seguida, informa sobre qual processo executar e ler o stdout. Você pode criar quantos quiser - e pode usar LZMA, GZIP, LZ4, XZ ... hmm, existem mais ... formatos de compactação que desejar. E o resultado final é um arquivo no qual você cd.

Mais sobre o formato:

Obviamente, isso não é apenas um arquivo morto - é uma imagem do sistema de arquivos Linux montável e compactada. Seu formato é do kernel do Linux - é um sistema de arquivos suportado pelo kernel vanilla. Dessa maneira, é tão comum quanto o kernel Linux vanilla. Portanto, se você me dissesse que estava executando um sistema vanilla Linux no qual o tarprograma não estava instalado, eu ficaria duvidoso - mas provavelmente acreditaria em você. Mas se você me dissesse que estava executando um sistema Linux baunilha no qual o squashfssistema de arquivos não era suportado, eu não acreditaria em você.

mikeserv
fonte
Mike, podemos incomodá-lo a criar um pequeno exemplo independente para que as pessoas possam experimentar? Parece que você pode estar fazendo pelo menos parte disso acima, mas não tenho certeza. In input f 444 root root dd if=/dev/sda1 bs=1024 count=10é a entrada do arquivo? Talvez seja melhor criar um dispositivo de brinquedo, preenchê-lo com dados e escrever a partir dele? E tudo isso requer raiz?
Faheem Mitha
@FaheemMitha - sim, eu posso fazer isso, mas não fiz aqui. O link é para a documentação oficial - foi retirado diretamente dele. Seria melhor se eu fizesse um exemplo de comando. Eu já fiz isso antes - é bem legal. De qualquer forma - o inputarquivo é um arquivo no squashfsarquivo morto - a imagem do sistema de arquivos resultante da execução do comando. Ao fazer isso, mksquashvocê pode especificar esses comandos pseudofile para comandos executados e a partir dos quais eles stdoutsão capturados no momento da compactação.
mikeserv
@FaheemMitha - ah, e não requer raiz para compactar , embora possa fazer a montagem - é uma imagem do sistema de arquivos resultante. É o mesmo sistema de arquivos que todos os discos do Linux Live usam. De fato - uma coisa muito legal - é que você pode criar uma imagem de propriedade de raiz usando esses pseudo-arquivos sem ser raiz - como definir os arquivos do dispositivo e números arbitrários de MAJ: MIN.
precisa saber é o seguinte
Eu acho que deveria ser possível criar um arquivo de dispositivo, escrever nele e depois dele sem nunca montá-lo, certo? Portanto, talvez não exija raiz, o que obviamente seria preferível.
Faheem Mitha
Bem, não há btrfs envolvidos aqui, então isso não funcionará. Mas o squashfs é louco o suficiente para funcionar. Embora tenha a desvantagem de não ser um formato de arquivo comum.
derobert
4

Seu problema me intrigou por algum tempo e acho que encontrei uma solução que funcionaria.

Eu acho que você pode conseguir o que deseja com 7z usando a -si{NAME}bandeira.

Você será capaz de se adaptar às suas necessidades.

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 : Remover o uso inútil de gato

Tony
fonte
Seria útil ter um pequeno exemplo que as pessoas possam experimentar. Por exemplo, crie um dispositivo de bloco, escreva para ele e depois escreva. Não exigir raiz seria uma vantagem.
Faheem Mitha
No exemplo / dev / sda1 é um dispositivo de bloco. O comando cat tem o objetivo de descarregar o conteúdo do dispositivo para o stdout. Em seguida, o 7z cria (ou atualiza) o arquivo morto e armazena os dados no nome do arquivo especificado pelo parâmetro -si do stdin. O resultado dentro do arquivo morto é o conteúdo de cada dispositivo (s) de bloco. O comando "cat" precisa de raiz para ler os dados do dispositivo.
Tony Tony
Esse é um uso inútil do gato , mas, de outra forma, se encaixa perfeitamente. Estranhamente, minha página de 7zmanual não menciona -si pode usar um nome de arquivo, mas funciona. Não é perfeito (a saída não pode ser canalizada em algum lugar), mas é definitivamente a melhor até agora que sai em um formato comum.
precisa saber é
O @FaheemMitha que requer root ou não dependerá das configurações de permissão do seu sistema, embora apenas o root possa criar novos dispositivos de bloco.
derobert
@derobert Removido o gato :)
Tony