Estou usando este pipeline para gravar um arquivo de imagem para dirigir $drive
:
wget -o logfile -O - https://route/to/image.gz | \
gunzip -c | \
dd of="$drive" bs=4M conv=fdatasync 2>/dev/null
logfile
é criado para monitorar o progresso.
Tenho um mau pressentimento sobre isso e não consigo me convencer de que é infalível. A imagem em si é sempre um múltiplo de 4 MB, então isso não é um problema, mas dd
é potencialmente problemático (veja esta resposta da U&L , por exemplo).
Estou sendo paranoico ou existe uma maneira melhor de fazer isso?
EDITAR
Seguindo os comentários (obrigado), eu fiz um benchmarking head -c
e dd bs=1
para escrever uma imagem em uma unidade. TL;DR: dd
é basicamente inútil nesta aplicação. A imagem no servidor remoto compacta em gzip para cerca de 46M, então dd
é usada com bs=1
, então isso talvez seja um pouco injusto com dd
. A imagem é recuperada com wget
, gunzipada na hora e então gravada na unidade com head -c
ou dd bs=1
:
Opção 1:
# time wget -o logfile -O - https://path/to/foo.img.gz | \
gunzip -c | \
dd of=/dev/sda bs=1 conv=fdatasync 2>/dev/null
real 1m55.665s
user 0m32.323s
sys 2m20.841s
Opção 2:
# time wget -o logfile -O - https://path/to/foo.img.gz | \
gunzip -c | \
cat > /dev/sda 2>/dev/null
real 0m7.419s
user 0m0.646s
sys 0m0.507s
Ambas as opções foram testadas obtendo-se o md5sum
and sha256sum
dos primeiros 48159047 bytes da unidade, e ambas forneceram a pré-compressão correta md5sum
, sha256sum
conforme encontrado no servidor:
# time head -c 48159047 /dev/sda | md5sum
b3df12b61df3121ad112f825cc6fe8b7 -
real 0m0.222s
user 0m0.075s
sys 0m0.049s
# time dd status=none if=/dev/sda bs=1 count=48159047 | md5sum
b3df12b61df3121ad112f825cc6fe8b7 -
real 1m31.627s
user 0m49.218s
sys 1m45.406s
Os sha256sum
resultados foram praticamente os mesmos: cerca de 0,25 s de tempo real para head -c
, e 1 m32 s para dd
.
dd
é uma interface simples e bruta para as chamadas de sistemaread()
ewrite()
.Em:
Você está dizendo
dd
para fazern = read(0, buf, 4*1024*1024)
, seguido porwrite(outfd, buf, n)
.No entanto,
read()
um pipe normalmente retorna quantos bytes há atualmente no buffer do pipe, ou o tamanho solicitado se for menor, mas um buffer de pipe no Linux é de 64 KiB por padrão, então aqui os blocos quedd
serão lidos e gravados serão de tamanhos variados, o que dependerá de comogunzip
a saída é gravada no pipe e da rapidez comdd
que o pipe será esvaziado.Usando
strace
, vejo que meugunzip
grava sua saída em blocos de 32 KiB, que é um divisor do tamanho do pipe. É provável que inicialmentedd
seja muito mais rápido ler do pipe do quegunzip
escrever nele, então muito provavelmente as leituras e gravações serão de 32 KiB.Quando os buffers do kernel
$drive
estiverem cheios (assumindo que$drive
é mais lento para gravar do quegunzip
para descompactar os dados, o que é provável) após algumas centenas de mebibytes,dd
eles ficarão mais lentos egunzip
poderão preencher o pipe (e também ficarão mais lentos como resultado). Então você verádd
blocos de gravação de 64 KiB (o tamanho do buffer do pipe).A menos que você aumente o tamanho do buffer do pipe, as gravações nunca serão de 4 MiB. Para isso, você precisaria
iflag=fullblock
fazerdd
quantas read()s forem necessárias para preencher um bloco de 4 MiB antes de escrevê-lo.Em qualquer caso, a única coisa útil nesse comando é o
conv=fdatasync
que faz umfdatasync(outfd)
no final para garantir que, quandodd
sai, todos os dados tenham sido liberados no disco e explica a diferença de tempo (nocat
caso, o sistema continuará gravando no disco muito depois decat
ter retornado), mas o loop de leitura+gravação quedd
(oucat
) faz é uma sobrecarga completamente desnecessária.Aqui, você pode fazer:
Onde
gunzip
grava diretamente no dispositivo de bloco (em blocos de 32 KiB com o meugunzip
estdbuf -o4M
não faz diferença) edd
faz o flush no final.Note que outra vantagem dessa sintaxe é que se
$drive
não puder ser aberto para escrita,wget
não será iniciado e o arquivo (pelo menos o começo dele) não será baixado à toa.Para erros (por
wget
,gunzip
,dd
e a abertura do arquivo de saída) a serem relatados no status de saída, para que você obtenha um código de sucesso caso os dados tenham sido baixados, descompactados e gravados no disco com sucesso:Acima ainda chamando um
fdatasync()
ifwget
ougunzip
failed, mas você também pode simplificar para:Para pular o
fdatasync()
ifwget
ougunzip
falha.