Когда dd подходит для копирования данных? (или, когда read () и write () частично)

60

Краткая версия: При каких обстоятельствах ddбезопасно использовать для копирования данных, это означает, что нет риска повреждения из-за частичного чтения или записи?

Длинная версия - преамбула: dd часто используется для копирования данных, особенно с или на устройство ( пример ). Иногда это объясняется мистическими свойствами доступа к устройствам на более низком уровне, чем у других инструментов (хотя на самом деле это файл устройства, который творит чудеса) - но dd if=/dev/sdaэто то же самое, что и cat /dev/sda. ddИногда думают, что это быстрее, но catможет побить его на практике . Тем не менее, ddобладает уникальными свойствами, которые иногда делают его действительно полезным .

Проблема: dd if=foo of=bar на самом деле это не то же самое, что cat <foo >bar. На большинстве офисов¹, ddделает один звонок read(). (Я нахожу POSIX нечетким в том, что представляет собой «чтение блока ввода» dd.) Если read()возвращается частичный результат (который, согласно POSIX и другим справочным документам, разрешается, если в документации реализации не указано иное), частичный блок копируется. Точно такая же проблема существует для write().

Наблюдения : на практике я обнаружил, что он ddможет справиться с блочными устройствами и обычными файлами, но это может быть просто из-за того, что я не слишком много использовал его. Когда дело доходит до труб, это не трудно поставить ddв вину; например попробуйте этот код :

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

и проверьте размер outфайла (вероятно, он будет меньше 10 МБ).

Вопрос : при каких обстоятельствах ddбезопасно использовать данные для копирования? Другими словами, какие условия относительно размеров блоков, реализации, типов файлов и т. Д. Могут обеспечить ddкопирование всех данных?

GNU dd есть fullblockфлаг, указывающий, что он должен вызываться read()или write()в цикле, чтобы передать полный блок. Так dd iflag=fullblockвсегда безопасно. Мой вопрос касается случая, когда эти флаги (которых нет в других реализациях) не используются .)

Checked Я проверил OpenBSD, GNU coreutils и BusyBox.

Жиль "ТАК - перестань быть злым"
источник
Я никогда не видел ни одной системы Unixy, которая действительно могла бы прочитать несколько MiB за одно чтение (2) ...
vonbrand
3
При использовании count, iflag=fullblockэто обязательно (или, альтернативно, iflag=count_bytes). Существует нет oflag=fullblock.
frostschutz

Ответы:

10

Из спецификации :

  • Если bs=exprоперанд определен и никаких преобразований, кроме sync, noerrorили notruncпросят, то данные , возвращенные из каждого входного блока должна быть записана в виде отдельного выходного блока; если read()возвращается меньше, чем полный блок, и syncпреобразование не указано, результирующий выходной блок должен быть того же размера, что и входной блок.

Так что это, вероятно, то, что вызывает ваше замешательство. Да, поскольку ddон предназначен для блокировки, по умолчанию частичные read()s будут отображаться в пропорции 1: 1 в частичные write()s, или же sync, bs=если conv=syncзадано значение NUL или символьный пробел в d, если задано.

Это означает , что ddявляется безопасным для использования для копирования данных (ж / без риска коррупции из - за частичное чтение или запись) , в каждом случае , кроме одного , в котором она произвольно ограничена по count=аргументу, поскольку в противном случае ddбудет счастливо write()его выход в одинаковом размере блоков к тем, в которых вход был read()до тех пор, пока он read()полностью не прошел через него. И даже это предостережение только справедливо , если bs=указан или obs=в не указан, как следующее предложение в спецификации говорится:

  • Если bs=exprоперанд не указан, или преобразования, кроме sync, noerrorили notruncзапрашиваются, вход должен быть обработан и собран в полноразмерные выходных блоки до конца ввода не будет достигнут.

Без ibs=и / или obs=аргументы , это не может дело - потому что ibsи obsоба тот же размер по умолчанию. Тем не менее, вы можете получить явные сведения о буферизации ввода, указав разные размеры для каждого и не указав bs= (потому что это имеет приоритет) .

Например, если вы делаете:

IN| dd ibs=1| OUT

... тогда POSIX ddбудет write()состоять из 512 байтов, собирая каждый read()отдельный байт в один выходной блок.

В противном случае, если вы делаете ...

IN| dd obs=1kx1k| OUT

... POSIX ddбудет read() максимум 512 байт за раз, но write()каждый выходной блок размером с мегабайт (ядро разрешает и исключает, возможно, последний - потому что это EOF) полностью, собирая входные данные в полноразмерные выходные блоки .

Также из спецификации, хотя:

  • count=n
    • Скопируйте только n входных блоков.

count=сопоставляется с i?bs=блоками, и поэтому для обработки произвольного ограничения на count=переносимость вам понадобится два dds. Наиболее практичный способ сделать это с двумя dds - это передать вывод одного на вход другого, что, несомненно, ставит нас в область чтения / записи специального файла независимо от исходного типа ввода.

Канал IPC означает, что при указании [io]bs=аргументов, чтобы сделать это безопасно, вы должны держать такие значения в пределах определенного PIPE_BUFпредела системы. POSIX заявляет, что ядро ​​системы должно гарантировать только атомарные read()s и write()s в пределах, PIPE_BUFопределенных в limits.h. POSIX гарантирует, что PIPE_BUFбудет хотя бы ...

  • {_POSIX_PIPE_BUF}
    • Максимальное количество байтов, которое гарантированно будет атомарным при записи в канал.
    • Значение: 512

... (который также является ddразмером блока ввода-вывода по умолчанию ) , но фактическое значение обычно составляет не менее 4k. В современной системе Linux это по умолчанию 64 КБ.

Поэтому, когда вы настраиваете свои ddпроцессы, вы должны делать это с блочным фактором на основе трех значений:

  1. bs = (obs = PIPE_BUFили меньше)
  2. n = общее количество прочитанных байтов
  3. count = n / bs

Подобно:

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

Вы должны синхронизировать I / OW / ddдля обработки входов без поиска. Другими словами, сделайте конвейерные буферы явными, и они перестанут быть проблемой. Вот для чего dd. Неизвестное количество здесь - yesэто размер буфера, но если вы заблокируете его в известном количестве с другим, ddто небольшое информированное умножение может быть dd безопасно использовано для копирования данных (без риска повреждения из-за частичного чтения или записи) даже когда произвольно ограничивает ввод count=с любым произвольным типом ввода в любой системе POSIX и не пропускает ни одного байта.

Вот фрагмент из спецификации POSIX :

  • ibs=expr
    • Укажите размер входного блока в байтах (по умолчанию 512) .expr
  • obs=expr
    • Укажите размер выходного блока в байтах (по умолчанию 512) .expr
  • bs=expr
    • Установите размеры входного и выходного блоков в exprбайтах, заменяя ibs=и obs=. Если не указано никакого преобразования, кроме sync, noerrorи notrunc, каждый входной блок должен быть скопирован на выход как один блок без агрегирования коротких блоков.

Вы также найдете некоторые из этих объяснений лучше здесь .

mikeserv
источник
5

С сокетами, pipe или ttys read () и write () могут передавать меньше, чем запрошенный размер, поэтому при использовании dd для них вам нужен флаг fullblock. Однако с обычными файлами и блочными устройствами они могут выполнить короткое чтение / запись только два раза: когда вы достигнете EOF или возникнет ошибка. Вот почему более старые реализации dd без флага fullblock были безопасны для дублирования диска.

psusi
источник
Это верно для всех современных объединений? (Я знаю , что это не было верно Linux в какой - то момент, возможно , до 2.0.x или 2.2.x. я rembember mke2fsнеисправного молча , потому что это называется write()с некоторым Неэнергетические-из-2 размера (3KB IIRC) и ядро округлой до степени 2.)
Жиль "ТАК - перестань быть злым"
@ Жиль, это звучит совсем иначе. Вы всегда должны использовать кратные правильного размера блока с блочными устройствами. Я почти уверен, что это верно для всех политик, и это также верно для Windows.
Псуси
Помимо лент, размер блока устройства предназначен исключительно для ядра, или нет. cat </dev/sda >/dev/sdbпрекрасно работает, чтобы клонировать диск.
Жиль "ТАК - перестань быть злым"
@ Жиль, потому что cat использует соответствующий размер блока, как отметил OrbWeaver в своем ответе.
Псуси
Нет, «соответствующего размера блока» не существует. catвыбирает размер буфера для производительности; он не получает никакой информации об устройстве из ядра. Помимо кассет, можно read()и write()на блочное устройство любого размера. В Linux, по крайней мере, st_blksizeзависит только от файловой системы, в которой находится inode блочного устройства, а не от базового устройства.
Жиль "ТАК - перестань быть злым"