Quand dd est-il adapté à la copy de données? (ou, quand sont read () et write () partial)

Version abrégée: dans quelles circonstances est-il possible d'utiliser la copy de données en toute security, ce qui signifie qu'il n'y a pas de risque de corruption en raison d'une lecture ou d'une écriture partielle?

Version longue – préambule: dd est souvent utilisé pour copyr des données, en particulier depuis ou vers un périphérique ( exemple ). Il est parfois atsortingbué 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 file de périphérique qui fait la magie) – pourtant dd if=/dev/sda est la même chose que cat /dev/sda . dd est parfois pensé pour être plus rapide, mais le cat peut le battre dans la pratique . Néanless, dd possède des propriétés uniques qui le rendent parfois utile .

Problème: dd if=foo of=bar n'est en fait pas la même chose que la cat <foo >bar . Sur la plupart des Unix¹, dd fait un seul appel à read() . (Je trouve POSIX floue sur ce qui constitue la lecture d'un bloc d'input dans dd ). Si read() renvoie un résultat partiel (qui, selon POSIX et d'autres documents de reference, est autorisé sauf si la documentation d'implémentation dit le contraire) le bloc est copié. Exactement le même problème existe pour write() .

Observations : En pratique, j'ai trouvé que dd peut faire face à des périphériques de bloc et des files réguliers, mais c'est peut-être que je n'ai pas beaucoup exercé. Quand il s'agit de tuyaux, ce n'est pas difficile de mettre dd en faute; par exemple essayez ce code :

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

et vérifier la taille du file de out (il est susceptible d'être bien inférieur à 10 Mo).

Question : Dans quelles circonstances est-ce que dd est sûr à utiliser pour copyr des données? En d'autres termes, quelles conditions sur les tailles de blocs, sur l'implémentation, sur les types de files, etc., peuvent garantir que dd copy toutes datatables?

( GNU dd a un flag fullblock pour lui dire d'appeler read() ou write() dans une boucle afin de transférer un bloc complet. n'existent pas sur d'autres implémentations) ne sont pas utilisés.)

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

    La différence entre la cat <input >output et dd if=input of=output est dans la taille de bloc qu'ils utiliseront. La valeur par défaut pour dd si aucun paramètre bs est donnée est de 512 octets (la taille d'un secteur de disque), alors que cat utilisera tout ce qui est codé pour fonctionner, ce qui est basé sur une inspection rapide du code source soit 32 Ko la valeur st_blksize renvoyée par stat() sur le file d'input, la valeur la plus grande étant retenue. L'effet de ceci peut parfois être vu lors de la lecture d'un lecteur de bande, où la taille du bloc de bande est plus grande que la valeur par défaut utilisée par cat , auquel cas cat /dev/st0 | <some command> cat /dev/st0 | <some command> échouera avec une erreur de lecture de données, alors que dd if=/dev/st0 bs=1M | <same command> dd if=/dev/st0 bs=1M | <same command> fonctionnera bien.

    Je peux confirmer le comportement que vous avez mentionné avec yes , dans lequel dd copyra les blocs partiels s'il ne peut pas en lire un entier, et le file de sortie se termine à 104 Ko si le iflag=fullblock n'est pas donné et le 10 attendu MB si c'est le cas. Cependant, ceci n'est qu'un problème si vous countz sur une combinaison des parameters bs et count pour déterminer la quantité de données copiées, ce que vous êtes dans ce cas. Lors de la copy d'un file ou d'un périphérique dans son intégralité (sans paramètre de count ), peu importe si des blocs partiels sont copiés car le process ne sera terminé que lorsque toutes datatables auront été lues.

    La conclusion est donc que dd est toujours sûr à utiliser pour copyr un file ou un périphérique entier, car il se lira jusqu'à la fin quelle que soit la taille de bloc, tandis que dd bs=<something> count=<something> peut copyr less que prévu si le le périphérique source renvoie less que la taille totale du bloc dans un ou plusieurs appels read() . Cela est probablement rare lors de la lecture à partir d'un file ou d'un périphérique comme /dev/zero , mais selon la page man pour read(2) il est théoriquement possible lors d'un appel de lecture (par exemple si le process est interrompu par un signal). Si une copy inférieure à la quantité de données demandée est inacceptable, le paramètre iflag=fullblock peut être utilisé pour éviter cela.

    Les informations sur les blocs copiés peuvent être visualisées dans la sortie de diagnostic de dd , qui répertorie séparément les blocs complet et partiel:

     $ yes | dd bs=1M count=10 of=outfile 0+10 records in 0+10 records out 180224 bytes (180 kB) copyd, 0.00213842 s, 84.3 MB/s $ yes | dd bs=1M count=10 of=outfile iflag=fullblock 10+0 records in 10+0 records out 10485760 bytes (10 MB) copyd, 0.132989 s, 78.8 MB/s 

    Le nombre de blocs est répertorié comme <full>+<partial> , donc dans le premier cas, vous pouvez voir que 10 blocs partiels ont été copiés et que le file de sortie contient less de données que prévu.

    De la spec :

    • Si l'opérande bs= expr est spécifié et qu'aucune conversion autre que sync , noerror ou notrunc n'est demandée, datatables renvoyées de chaque bloc d'input doivent être écrites en tant que bloc de sortie séparé; si read() renvoie less d'un bloc complet et que la conversion de sync n'est pas spécifiée, le bloc de sortie résultant doit avoir la même taille que le bloc d'input.

    Donc, c'est probablement ce qui cause votre confusion. Oui, parce que dd est conçu pour le blocage, par défaut, read() s sera mappé 1: 1 à write() s, ou bien sync d out sur padding de queue NUL ou espace chars à bs= size lorsque conv=sync est spécifié.

    Cela signifie que dd est sûr à utiliser pour copyr des données (sans risque de corruption dû à une lecture ou une écriture partielle) dans tous les cas sauf un dans lequel il est arbitrairement limité par un count= argument, sinon dd écrira heureusement write() sa sortie dans des blocs de taille identique à ceux dans lesquels son input a été read() jusqu'à ce qu'elle read() s complètement à travers elle. Et même cette mise en garde n'est vraie que si bs= est spécifié ou obs= n'est pas spécifié, comme la phrase suivante dans la spécification:

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

    Sans les ibs= et / ou obs= cela ne peut pas être important – car ibs et obs sont tous deux de la même taille par défaut. Cependant, vous pouvez get des explications sur la mise en memory tampon des inputs en spécifiant différentes tailles pour l'un et l'autre sans spécifier bs= (car il a priorité) .

    Par exemple, si vous le faites:

     IN| dd ibs=1| OUT 

    … alors un POSIX dd write() en morceaux de 512 octets en collectant chaque octet readly read() dans un seul bloc de sortie.

    Sinon, si vous le faites …

     IN| dd obs=1kx1k| OUT 

    … un POSIX dd read() au maximum 512 octets à la fois, mais write() tous les blocs de sortie de taille de mégaoctets (le kernel permettant et à l'exception peut-être le dernier – parce que c'est EOF) blocs de sortie .

    Aussi de la spec, cependant:

    • count=n
      • Ne copyr que n blocs d'input.

    count= correspond à i?bs= blocs, et donc pour gérer une limite arbitraire sur count= portablement vous aurez besoin de deux dd s. La façon la plus pratique de le faire avec deux dd s est de canaliser la sortie de l'un dans l'input d'un autre, ce qui nous place sûrement dans le domaine de la lecture / écriture d'un file spécial quel que soit le type d'input d'origine.

    Un canal IPC signifie que lorsque vous spécifiez [io]bs= args pour que vous puissiez le faire en toute security, vous devez conserver ces valeurs dans la limite PIPE_BUF définie par le système. POSIX indique que le kernel du système ne doit garantir que les read() atomiques read() s et write() s dans les limites de PIPE_BUF définies dans limits.h . POSIX garantit que PIPE_BUF soit au less

    • {_POSIX_PIPE_BUF}
      • Nombre maximal d'octets garantis atomiques lors de l'écriture dans un tube.
      • Valeur: 512

    (qui se trouve aussi être la valeur par défaut dd i / o blocksize) , mais la valeur réelle est généralement d'au less 4k. Sur un système linux mis à jour, il est, par défaut, 64k.

    Ainsi, lorsque vous configurez vos process dd , vous devez le faire sur un facteur de bloc basé sur trois valeurs:

    1. bs = (obs = PIPE_BUF ou less)
    2. n = nombre total d'octets désiré
    3. count = 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) copyd, 0.1143 s, 91.7 MB/s 

    Vous devez synchroniser i / ow / dd pour gérer les inputs non recherchables. En d'autres termes, faites des tampons de pipe explicites et ils cessent d'être un problème. C'est ce que dd est pour. La quantité inconnue ici est la taille de tampon de yes – mais si vous bloquez cette quantité vers une quantité connue avec un autre dd une petite multiplication informée peut rendre dd utilisable pour la copy des données (sans risque de corruption dû à une lecture partielle ou écrire) même en limitant arbitrairement l'input w / count= w / tout type d'input arbitraire 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'input, en octets, par expr (par défaut 512) .
    • obs= expr
      • Spécifiez la taille du bloc de sortie, en octets, par expr (par défaut 512) .
    • bs= expr
      • Définissez les tailles de bloc d'input et de sortie sur expr octets, remplaçant ibs= et obs= . Si aucune conversion autre que sync , noerror et notrunc n'est spécifiée, chaque bloc d'input doit être copié sur la sortie en tant que bloc unique sans agrégation de blocs courts.

    Vous findez également certaines de ces explications mieux ici .

    Avec les sockets, pipes ou ttys, read () et write () peuvent transférer less que la taille demandée, donc si vous utilisez dd, vous avez besoin du flag fullblock. Cependant, avec les files réguliers et les périphériques en mode bloc, il n'y a que deux fois qu'ils peuvent faire une lecture / écriture courte: quand vous atteignez EOF, ou s'il y a une erreur. C'est pourquoi les anciennes implémentations de dd sans le flag fullblock étaient sûres à utiliser pour la duplication de disque.