Quando o dd é adequado para copiar dados? (ou, quando são lidos () e gravados () parciais)

60

Versão curta: Em que circunstâncias é ddseguro usar para copiar dados, o que significa que não há risco de corrupção devido a uma leitura ou gravação parcial?

Versão longa - preâmbulo: dd geralmente é usado para copiar dados, especialmente de ou para um dispositivo ( exemplo ). Às vezes, são atribuídas propriedades místicas de poder acessar dispositivos em um nível mais baixo do que outras ferramentas (quando na verdade é o arquivo do dispositivo que está fazendo a mágica) - mas dd if=/dev/sdaé a mesma coisa que cat /dev/sda. ddàs vezes é pensado para ser mais rápido, mas catpode superá-lo na prática . No entanto, ddpossui propriedades únicas que o tornam genuinamente útil às vezes .

Problema: dd if=foo of=bar não é, de fato, o mesmo que cat <foo >bar. Na maioria dos unices¹, ddfaz uma única chamada para read(). (Acho POSIX confuso sobre o que constitui “lendo um bloco de entrada” em dd.) Se read()retornar um resultado parcial (que, de acordo com o POSIX e outros documentos de referência, é permitido, a menos que a documentação de implementação diga o contrário), um bloco parcial será copiado. Exatamente o mesmo problema existe para write().

Observações : Na prática, descobri que é ddcapaz de lidar com dispositivos de bloco e arquivos regulares, mas pode ser que não tenha exercido muito. Quando se trata de tubos, não é difícil colocar dda culpa; por exemplo, tente este código :

yes | dd of=out bs=1024k count=10

e verifique o tamanho do outarquivo (é provável que esteja bem abaixo de 10 MB).

Pergunta : Em que circunstâncias é ddseguro usar para copiar dados? Em outras palavras, quais condições nos tamanhos dos blocos, na implementação, nos tipos de arquivo, etc., podem garantir que ddcopie todos os dados?

(O GNU dd tem um fullblocksinalizador para dizer para chamar read()ou write()em um loop para transferir um bloco completo. Portanto, dd iflag=fullblocké sempre seguro. Minha pergunta é sobre o caso em que esses sinalizadores (que não existem em outras implementações) não são usados .)

¹ Verifiquei no OpenBSD, GNU coreutils e BusyBox.

Gilles 'SO- parar de ser mau'
fonte
Eu nunca vi nenhum sistema Unixy que realmente pudesse ler alguns MiB em uma única leitura (2) ...
vonbrand
3
Ao usar count, iflag=fullblocké obrigatório (ou, alternativamente, iflag=count_bytes). Não existe oflag=fullblock.
Frostschutz

Respostas:

10

Das especificações :

  • Se o bs=exproperando for especificado e nenhuma conversão além de sync, noerrorou notruncfor solicitada, os dados retornados de cada bloco de entrada serão gravados como um bloco de saída separado; se o read()retorno for menor que um bloco completo e a syncconversão não for especificada, o bloco de saída resultante deverá ter o mesmo tamanho que o bloco de entrada.

Então é provavelmente isso que causa sua confusão. Sim, por ddser projetado para bloqueio, por padrão, os parciais read()s serão mapeados 1: 1 para os parciais write(), ou então serão syncpreenchidos com NUL de preenchimento de cauda ou caracteres de bs=tamanho de espaço quando conv=syncespecificados.

Isso significa que ddé seguro usar para copiar dados (sem risco de corrupção devido a uma leitura ou gravação parcial) em todos os casos, mas aquele em que ele é arbitrariamente limitado por um count=argumento, porque, caso contrário, ddserá feliz write()sua saída em blocos de tamanho idêntico para aqueles em que sua entrada era read()até que ela read()passasse completamente. E mesmo esta ressalva é verdade apenas quando bs=for especificado ou obs=se não especificado, como a próxima sentença nos estados de especificação:

  • Se o bs=exproperando não for especificado, ou uma conversão diferente de sync, noerrorou notruncfor solicitada, a entrada deve ser processada e coletada em blocos de saída de tamanho normal até que o final da entrada seja alcançado.

Sem ibs=e / ou obs=argumentos, isso não importa - porque ibse obsambos são do mesmo tamanho por padrão. No entanto, você pode ser explícito sobre o buffer de entrada especificando tamanhos diferentes para um e para não especificar bs= (porque tem precedência) .

Por exemplo, se você fizer:

IN| dd ibs=1| OUT

... então um POSIX ddirá write()em pedaços de 512 bytes, coletando cada read()byte isolado em um único bloco de saída.

Caso contrário, se você fizer ...

IN| dd obs=1kx1k| OUT

... um POSIX ddterá read() no máximo 512 bytes de cada vez, mas write()cada bloco de saída do tamanho de megabytes (o kernel permite e exceto, possivelmente, o último - porque esse é EOF) por completo, coletando entrada em blocos de saída de tamanho completo .

Também das especificações, no entanto:

  • count=n
    • Copie apenas n blocos de entrada.

count=mapas para i?bs=blocos e, portanto, para lidar com um limite arbitrário em count=portabilidade, você precisará de dois dds. A maneira mais prática de fazer isso com dois dds é canalizar a saída de um para a entrada de outro, o que certamente nos coloca na área de leitura / gravação de um arquivo especial, independentemente do tipo de entrada original.

Um canal IPC significa que, ao especificar [io]bs=argumentos que, para fazê-lo com segurança, você deve manter esses valores dentro do PIPE_BUFlimite definido pelo sistema . O POSIX afirma que o kernel do sistema deve garantir apenas atômica read()s e write()s dentro dos limites PIPE_BUFdefinidos em limits.h. POSIX garante que PIPE_BUFseja pelo menos ...

  • {_POSIX_PIPE_BUF}
    • Número máximo de bytes que são garantidos como atômicos ao gravar em um pipe.
    • Valor: 512

... (que também é ddo tamanho de bloco de E / S padrão ) , mas o valor real geralmente é de pelo menos 4k. Em um sistema linux atualizado, é, por padrão, 64k.

Portanto, quando você configura seus ddprocessos, deve fazê-lo em um fator de bloco com base em três valores:

  1. bs = (obs = PIPE_BUFou menor)
  2. n = número total desejado de bytes lidos
  3. contagem = n / bs

Gostar:

yes | dd obs=1k | dd bs=1k count=10k of=/dev/null
10240+0 records in
10240+0 records out
10485760 bytes (10 MB) copied, 0.1143 s, 91.7 MB/s

Você precisa sincronizar o i / ow / ddpara manipular entradas não procuráveis. Em outras palavras, torne os buffers de pipe explícitos e eles deixam de ser um problema. É para isso que ddserve. A quantidade desconhecida aqui é yeso tamanho do buffer - mas se você bloquear uma quantidade conhecida com outra dd, uma pequena multiplicação informada poderá ser dd segura para copiar dados (sem risco de corrupção devido a leitura ou gravação parcial) mesmo ao limitar arbitrariamente a entrada com count=qualquer tipo de entrada arbitrária em qualquer sistema POSIX e sem perder um único byte.

Aqui está um trecho da especificação POSIX :

  • ibs=expr
    • Especifique o tamanho do bloco de entrada, em bytes, por (o padrão é 512) .expr
  • obs=expr
    • Especifique o tamanho do bloco de saída, em bytes, por (o padrão é 512) .expr
  • bs=expr
    • Defina os tamanhos dos blocos de entrada e saída como exprbytes, substituindo ibs=e obs=. Se nenhuma conversão diferente de sync, noerrore notruncfor especificada, cada bloco de entrada deve ser copiado para a saída como um único bloco sem agregar blocos curtos.

Você também encontrará algumas dessas explicações melhor aqui .

mikeserv
fonte
5

Com soquetes, tubulações ou ttys, read () e write () podem transferir menos do que o tamanho solicitado, portanto, ao usar dd neles, você precisa do sinalizador fullblock. No entanto, com arquivos regulares e dispositivos de bloco, há apenas duas vezes em que eles podem fazer uma breve leitura / gravação: quando você alcança o EOF ou se houver um erro. É por isso que implementações mais antigas do dd sem o sinalizador fullblock eram seguras para a duplicação de disco.

psusi
fonte
Isso é verdade para todos os órgãos modernos? (Eu sei que não era verdade em relação ao Linux em algum momento, possivelmente até 2.0.x ou 2.2.x. Lembro-me de mke2fsfalhar silenciosamente porque chamou write()com algum tamanho que não fosse o tamanho 2 (3kB IIRC) e o kernel arredondado até uma potência de 2.)
Gilles 'SO- stop be evil'
@ Gilles, que soa como um problema completamente diferente. Você sempre deve usar um múltiplo do tamanho de bloco adequado nos dispositivos de bloco. Tenho certeza de que isso vale para todos os unicórnios e também para o Windows.
Psusi
Além das fitas, o tamanho do bloco de um dispositivo é apenas para o kernel se preocupar ou não. cat </dev/sda >/dev/sdbfunciona bem para clonar um disco.
Gilles 'SO- stop be evil'
@Gilles é porque o gato usa o tamanho de bloco apropriado, como OrbWeaver observou em sua resposta.
Psusi
Não, não há "tamanho de bloco apropriado". catescolhe um tamanho de buffer para desempenho; ele não obtém nenhuma informação relacionada ao dispositivo do kernel. Além de fitas, você pode read()e write()para um dispositivo de bloco de qualquer tamanho. No Linux, pelo menos, st_blksizedepende apenas do sistema de arquivos em que o inode do dispositivo de bloco está localizado, não do dispositivo subjacente.
Gilles 'SO- stop be evil'