Quand dd convient-il à la copie de données? (ou, quand sont read () et write () partiel)

60

Version courte: Dans quelles circonstances peut-on ddutiliser en toute sécurité pour copier des données, ce qui signifie qu'il n'y a aucun risque de corruption dû à une lecture ou à une écriture partielle?

Version longue - préambule: dd est souvent utilisé pour copier des données, en particulier depuis ou vers un périphérique ( exemple ). On lui attribue parfois des propriétés mystiques de pouvoir accéder à des périphériques à un niveau inférieur à celui d'autres outils (alors que c'est le fichier de périphérique qui fait la magie) - c'est pourtant dd if=/dev/sdala même chose que cat /dev/sda. ddest parfois pensé pour être plus rapide, mais catpeut le battre dans la pratique . Néanmoins, ddpossède des propriétés uniques qui le rendent réellement utile parfois .

Problème: dd if=foo of=bar n'est pas, en fait, le même que cat <foo >bar. Sur la plupart des bureaux¹, ddfait un appel unique à read(). (Je trouve POSIX flou sur ce qui constitue "la lecture d'un bloc d'entrée" dd.) Si read()renvoie un résultat partiel (qui, selon POSIX et d'autres documents de référence, est autorisé à moins que la documentation d'implémentation ne l'indique autrement), un bloc partiel est copié. Exactement le même problème existe pour write().

Observations : Dans la pratique, j'ai constaté que je pouvais ddgérer les périphériques en mode bloc et les fichiers normaux, mais c'est peut-être simplement que je ne l'ai pas beaucoup utilisé. En ce qui concerne les tuyaux, il n’est pas difficile de s’y prendre dd; par exemple essayez ce code :

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

et vérifiez la taille du outfichier (il sera probablement bien en dessous de 10 Mo).

Question : Dans quelles circonstances peut-on ddutiliser en toute sécurité pour copier des données? En d'autres termes, quelles conditions sur la taille des blocs, sur l'implémentation, sur les types de fichiers, etc., peuvent garantir que ddtoutes les données seront copiées?

( GNU dd a un fullblockdrapeau pour lui dire d’appeler read()ou write()dans une boucle afin de transférer un bloc complet. Donc, cela dd iflag=fullblockest toujours sûr. Ma question concerne le cas où ces drapeaux (qui n’existent pas sur d’autres implémentations) ne sont pas utilisés .)

¹ J'ai vérifié sur OpenBSD, GNU coreutils et BusyBox.

Gilles, arrête de faire le mal
la source
Je n'ai jamais vu de système Unixy capable de lire quelques MiB en une seule lecture (2) ...
vonbrand
3
Lors de l'utilisation count, le iflag=fullblockest obligatoire (ou alternativement iflag=count_bytes). Il n'y a pas oflag=fullblock.
Frostschutz

Réponses:

10

De la spec :

  • Si l' bs=expropérande est spécifié et qu'aucune conversion autre que sync, noerrorou notruncn'est demandée, les données renvoyées par chaque bloc d'entrée doivent être écrites comme un bloc de sortie séparé. si le nombre read()retourné est inférieur à un bloc complet et que la syncconversion n'est pas spécifiée, le bloc de sortie résultant aura la même taille que le bloc d'entrée.

Donc, c'est probablement ce qui cause votre confusion. Oui, car il ddest conçu pour le blocage, les read()s partiels sont par défaut mappés de 1: 1 à write()s partiels , ou bien syncd out sur le remplissage de queue NUL ou les caractères d'espace à la bs=taille conv=syncspécifiée.

Cela signifie que ddest sûr à utiliser pour la copie des données (w / aucun risque de corruption en raison d'une lecture partielle ou écriture) dans tous les cas , mais celui dans lequel il est arbitrairement limité par un count=argument parce que sinon se ddfera un plaisir de write()sa sortie en blocs de taille identique à ceux dans lesquels son entrée a été read()jusqu’à ce qu’elle soit read()complètement passée à travers elle. Et même cette mise en garde est que vrai quand bs=est spécifié ou obs=est non spécifié, comme la phrase suivante dans les états spécifications:

  • Si l' bs=expropérande n'est pas spécifié, ou si une conversion autre que sync, noerrorou notruncest demandée, l'entrée doit être traitée et collectée dans des blocs de sortie de taille normale jusqu'à la fin de l'entrée.

Sans ibs=et / ou obs=arguments, cela n'a pas d'importance - ibset obsont la même taille par défaut. Cependant, vous pouvez expliquer explicitement la mise en mémoire tampon des entrées en spécifiant des tailles différentes pour l'une ou l'autre et non pour la spécification bs= (car elle est prioritaire) .

Par exemple, si vous le faites:

IN| dd ibs=1| OUT

... alors un POSIX ddsera write()composé de 512 octets en regroupant chaque read()octet unique dans un seul bloc de sortie.

Sinon, si vous le faites ...

IN| dd obs=1kx1k| OUT

... un Posix ddsera read() au maximum de 512 octets à la fois, mais write()chaque méga - octet de taille bloc de sortie (noyau permettant et sauf peut - être la dernière - parce que ce EOF) dans son intégralité par la collecte d' entrée en blocs de sortie pleine grandeur .

Aussi de la spécification, cependant:

  • count=n
    • Copier uniquement n blocs d'entrée.

count=mappe à des i?bs=blocs, et donc afin de gérer une limite arbitraire sur count=portable, vous aurez besoin de deux dds. La manière la plus pratique de le faire avec deux dds est de canaliser la sortie de l'un vers l'entrée de l'autre, ce qui nous place sûrement dans le domaine de la lecture / écriture d'un fichier spécial, quel que soit le type d'entrée d'origine.

Un canal IPC signifie que lorsque vous spécifiez des [io]bs=arguments, vous devez conserver ces valeurs dans les PIPE_BUFlimites définies par le système, de manière sécurisée . POSIX indique que le noyau système ne doit garantir que les read()s atomiques et les write()s dans les limites PIPE_BUFdéfinies par limits.h. POSIX garantit que ce PIPE_BUFsoit au moins ...

  • {_POSIX_PIPE_BUF}
    • Nombre maximal d'octets dont l'atome est garanti lors de l'écriture dans un canal.
    • Valeur: 512

... (qui se trouve être également la ddtaille de bloc par défaut d'e / s) , mais la valeur réelle est généralement d'au moins 4k. Sur un système Linux à jour, il est de 64k par défaut.

Ainsi, lorsque vous configurez vos ddprocessus, vous devez le faire sur un facteur de blocage basé sur trois valeurs:

  1. bs = (obs = PIPE_BUFou moins)
  2. n = nombre total d'octets lus souhaités
  3. compte = n / bs

Comme:

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

Vous devez synchroniser les ddentrées / sorties / pour gérer les entrées non recherchées. En d'autres termes, explicitez les tampons de canaux et ces problèmes cessent. C'est pour ça dd. La quantité inconnue ici est yesla taille du tampon de - mais si vous bloquez cette quantité avec une autre quantité connue,dd une multiplication un peu informée peut être dd utilisée de manière sûre pour la copie de données (sans risque de corruption dû à une lecture ou une écriture partielle). même lorsque vous limitez arbitrairement l'entrée avec count=n'importe quel type d'entrée sur n'importe quel système POSIX et sans manquer un seul octet.

Voici un extrait de la spécification POSIX :

  • ibs=expr
    • Spécifiez la taille du bloc d'entrée, en octets, par (512 par défaut) .expr
  • obs=expr
    • Spécifiez la taille du bloc de sortie, en octets, par (512 par défaut) .expr
  • bs=expr
    • Définissez les tailles de bloc d'entrée et de sortie sur exproctets, remplaçants ibs=et obs=. Si aucune conversion autre que sync, noerroret notruncn'est spécifiée, chaque bloc d'entrée doit être copié dans la sortie sous la forme d'un bloc unique sans agréger les blocs courts.

Vous trouverez également une partie de ceci expliqué mieux ici .

Mikeserv
la source
5

Avec les sockets, les pipes ou les ttys, read () et write () peuvent transférer moins que la taille demandée. Ainsi, lorsque vous utilisez dd sur ces derniers, vous avez besoin de l'indicateur fullblock. Cependant, avec des fichiers normaux et des périphériques en mode bloc, ils ne peuvent effectuer qu’une courte lecture / écriture: lorsque vous atteignez EOF ou en cas d’erreur. C’est pourquoi les anciennes implémentations de dd sans l’indicateur fullblock pouvaient être utilisées en toute sécurité pour la duplication de disque.

psusi
la source
Est-ce vrai de tous les unices modernes? (Je sais que ce n’était pas vrai à un moment donné avec Linux, peut-être jusqu’à 2.0.x ou 2.2.x. Je mke2fsne me souviens pas d’échouer en silence car il appelle write()avec une taille non-power-of-2 (3kB IIRC) et le noyau est arrondi. jusqu'à une puissance de 2.)
Gilles 'SO - arrête d'être méchant'
@ Gilles cela semble être un tout autre problème. Vous devez toujours utiliser un multiple de la taille de bloc appropriée avec les périphériques en mode bloc. Je suis à peu près sûr que cela est vrai pour tous les ordinateurs, et cela est également vrai pour Windows.
psusi
Hormis les bandes, la taille de bloc d’un périphérique appartient au noyau, ou pas. cat </dev/sda >/dev/sdbfonctionne très bien pour cloner un disque.
Gilles, arrête de faire le mal '25
@Gilles, c’est parce que cat utilise la taille de bloc appropriée, comme OrbWeaver l’a noté dans sa réponse.
psusi
Non, il n'y a pas de «taille de bloc appropriée». catchoisit une taille de tampon pour la performance; il ne reçoit aucune information relative au périphérique du noyau. En dehors des bandes, vous pouvez read()et write()à un dispositif de bloc avec toute taille. Sous Linux au moins, cela st_blksizene dépend que du système de fichiers où se trouve l'inode du périphérique en mode bloc, et non du périphérique sous-jacent.
Gilles, arrête de faire le mal '25