Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ddコマンドによるpartial readとデータ破損について / dd-partial-read

ddコマンドによるpartial readとデータ破損について / dd-partial-read

データ破損と記載しましたが、複製が不完全となるのみで、元ファイルが破損する事象では有りません。
また、警告も表示されますし、発生には条件が必要です。

イベント発表資料

鹿児島Linux勉強会 2021.07(オンライン開催)
https://kagolug.connpass.com/event/217340/

Blog version: https://blog.yamano.dev/posts/2021/06/dd-partial-read/

1b7f1c11e139b8011217c30e16b37b4f?s=128

Yoshihisa Yamano

July 18, 2021
Tweet

Transcript

  1. ddコマンドによるpartial readとデータ破損について 鹿児島Linux勉強会 2021.07(オンライン開催) https://kagolug.connpass.com/event/217340/ ブログ記事版 https://blog.yamano.dev/posts/2021/06/dd-partial-read/ Yoshihisa Yamano

  2. お断わり データ破損と記載しましたが、複製が不完全となるのみで、元ファイルが破損する事 象では有りません。 また、警告も表示されますし、発生には条件が必要です。

  3. お前誰? 鹿児島県出身 学生時代にkagolugの第0回より第13回(2016年03月)まで現地参加していた。 現在は東京都在住で、最近になってオンライン開催にお邪魔しております。 好きなディストリビューションはArch Linux。 https://www.yamano.dev/ 最近、マシンを買い変えて Haswell(第4世代) から

    Rocket Lake(第11世代) になっ た。快適。しかし、iGPUのDisplayPort出力に問題が有って、少し面倒な生活をしてま す。(新しいもの有る有る) https://twitter.com/yoshihisa_ya/status/1414920175179091972
  4. dd convert and copy a file

  5. 基本的な使い方 ブロックデバイスを読み書きする際に非常に便利で、ストレージ装置のバックアップ やクローン作成、データ消去などに利用できる。 /dev/sdb のディスクを /dev/sdc のディスクに複製する例 ]$ sudo dd

    if=/dev/sdb of=/dev/sdc bs=4k oflag=direct LVM の origin LV を、リモートホストにイメージデータとしてバックアップする例 ]$ dd if=/dev/vg0/origin | ssh file.yamano.dev "dd of=/tank/backup/vg0-origin.img" /dev/sda を 0埋めする例 ]$ sudo dd if=/dev/zero of=/dev/sda obs=4k oflag=direct iflug=fullblock
  6. option if=FILE, of=FILE 入力ファイル, 出力ファイルの指定。 bs=BYTES, ibs=BYTES, obs=BYTES 入出力、入力、出力のバイト単位指定。デフォルトは512Bytes。 count=N

    入力ファイルから、ibsバイトを、count回(ブロック)読み込む。 iflag=FLAGS, oflag=FLAGS 入力に対するオプションフラグ、出力に対するオプションフラグ。 direct DIRECT I/O を用いる。
  7. SIGNAL SIGUSR1を受け付けると、現在の入出力状況を標準エラー出力に出力する。 [yoshihisa@desktop ~]$ dd if=/dev/urandom of=file bs=4M count=100 41+0

    レコード入力 41+0 レコード出力 171966464 bytes (172 MB, 164 MiB) copied, 1.96944 s, 87.3 MB/s ランダムで埋め付くされた 400MiB(4M x 100) のファイル file を作成する。 SIGUSR1を受けた時点で 164 MiB(4M x 41) が完了している。
  8. partial read ?

  9. partial read warning 部分読み込みの警告。指定されたibsバイトを全て読み切らなかった事を示す。 データ破損には条件が必要。 [yoshihisa@desktop ~]$ dd if=/dev/urandom of=file

    bs=4M count=100 41+0 レコード入力 41+0 レコード出力 171966464 bytes (172 MB, 164 MiB) copied, 1.96944 s, 87.3 MB/s dd: warning: partial read (2854848 bytes); suggest iflag=fullblock 99+1 レコード入力 99+1 レコード出力 418090944 bytes (418 MB, 399 MiB) copied, 4.75245 s, 88.0 MB/s [yoshihisa@desktop ~]$ SIGUSR1 により途中経過を出力した後に、partial readの警告が表示されている。
  10. 99+1 レコード入力 99+1 レコード出力 418090944 bytes (418 MB, 399 MiB)

    copied, 4.75245 s, 88.0 MB/s 99 個の完全なブロック + 1 個の不完全なブロック を入出力した事を示す。 399 MiB(418090944 bytes = 398.72… MiB)となっており、400MiBでは無い不完全な複 製となっている。
  11. 発生する理由 dd は、ibsサイズをread(2) システムコール の読み込みサイズとして指定し、count 回だけ繰り返す。read(2)が指定サイズを読み切らなくても1回とカウントし、リトラ イはしない。これが原因。 bs=4M count=100 とした場合、1回でも2Mの時点でread(2)が戻ると、末尾の2M(4M-

    2M=2M)が扱われずddが終了する。 read(2) ssize_t read(int fd, void *buf, size_t count); fd ... 読み込むファイルディスクリプタ *buf ... 読み込んだデータを書き込むバッファ count ... 読み込むサイズ(bytes) 戻り値は、実際に読み込んだサイズ(bytes) read(2)は指定されたサイズ(size_t count)を全て読み切る保証が無い。 (size_t count)は *buf のサイズを指定し、オーバーフローを防ぐため。 実際に読み *buf に書き込んだサイズは 戻り値 で知ることができる。
  12. read(2)が指定されたcount(bytes)だけ読み切らない代表的な理由 1. fdのEOFにぶつかった。 2. 遅いデバイスからの読み出しでI/Oでブロックされている間にシグナルハンドラが 動作した。なおここでの遅いデバイスはローカルディスクを含まない。 i. シグナルハンドラからreadに処理が戻った時点で、まだ何も読み出せてない 場合、戻り値として負数をセットし errno

    == EINTR がセットされる。 ii. シグナルハンドラからreadに処理が戻った時点で、既に読み込んだ内容が有 る場合、指定されたサイズ(count)を読み切っていなくても、既に読み込んだ サイズを戻す。 1 のケースは想像しやすい事象で、続けてread(2)を発行すれば 0 が戻ってくるはず です。当然ながら今回の事象の原因とはなりません。 2-2 のケースが今回の事象の原因となります。
  13. 発生しないパターンと対策 dd の count=N 指定が無い場合は、if=FILE もしくは of=FILE のEOFまで読み切るの で、欠落は発生しない。ただ、デバイスのブロックサイズ等を元にbsを指定している 場合は、アライメントがズレるためパフォーマンスが低下する。

    サジェストされている iflag=fullblock オプションを用いると、指定されたサイズ (size_t count)を全て読み切らなかった場合に、残りを全て読み切るようにread(2)を 再試行する。
  14. コード v8.32 https://github.com/coreutils/coreutils/blob/v8.32/src/dd.c

  15. iflag=fullblock コマンドライン iflagにfullblockが指定されている場合は、iread_fncとしてiread_fullblockを、オ プションが指定されていない場合はireadを指定。 1640 iread_fnc = ((input_flags & O_FULLBLOCK)

    1641 ? iread_fullblock 1642 : iread); 1643 input_flags &= ~O_FULLBLOCK; https://github.com/coreutils/coreutils/blob/v8.32/src/dd.c#L1640-L1643
  16. iread 1128 ssize_t nread; 1129 static ssize_t prev_nread; 1134 nread

    = read (fd, buf, size); 1151 if (0 < nread && warn_partial_read) 1152 { 1153 if (0 < prev_nread && prev_nread < size) 1154 { 1155 uintmax_t prev = prev_nread; 1156 if (status_level != STATUS_NONE) 1157 error (0, 0, ngettext (("warning: partial read (%"PRIuMAX" byte); " 1158 "suggest iflag=fullblock"), 1159 ("warning: partial read (%"PRIuMAX" bytes); " 1160 "suggest iflag=fullblock"), 1161 select_plural (prev)), 1162 prev); 1163 warn_partial_read = false; 1164 } 1165 } 1166 1167 prev_nread = nread; https://github.com/coreutils/coreutils/blob/v8.32/src/dd.c#L1121-L1169
  17. 1151 if (0 < nread && warn_partial_read) 1152 { L1151

    の条件に存在する warn_partial_read は、partial readが発生した場合に警告 を表示する為のフラグで、初期値はtrueです。そのためこの条件は、read(2)によって 1byteでも読み込まれていればtrueとなります。警告を表示したらfalseにする事で、 このifに入らないようにし、警告を1回だけ表示します。 1153 if (0 < prev_nread && prev_nread < size) 1154 { L1153 の条件に存在する prev_nread は前回のnread戻り値で、これが 0 < prev_nread < size であった時、つまりshort readであった場合にtrueとなります。 その場合にpartial readの警告を表示し、warn_partial_readをfalseにします。
  18. これらの条件を満たすのは、前回のread(2)がshort readであり、今回のread(2)が 1byteでも読み込めた場合。つまり、前回のread(2)がshort readで有った場合に警告 を表示する。 read(2)のshort readが発生した際に、fdのEOFに到達した為に発生したshort readで有るか否かを、次のread(2)の戻り値で判定する為。 次のread(2)の戻り値が0であれば、fdのEOFに到達した事によるshort readで有り

    問題無い。 次のread(2)の戻り値が1以上で有れば、EOFに到達した以外によるshort readなの で、警告を表示する。
  19. iread_fullblock 1171 /* Wrapper around iread function to accumulate full

    blocks. */ 1172 static ssize_t 1173 iread_fullblock (int fd, char *buf, size_t size) 1174 { 1175 ssize_t nread = 0; 1176 1177 while (0 < size) 1178 { 1179 ssize_t ncurr = iread (fd, buf, size); 1180 if (ncurr < 0) 1181 return ncurr; 1182 if (ncurr == 0) 1183 break; 1184 nread += ncurr; 1185 buf += ncurr; 1186 size -= ncurr; 1187 } 1188 1189 return nread; 1190 } https://github.com/coreutils/coreutils/blob/v8.32/src/dd.c#L1171-L1190
  20. flag=fullblock とした場合には、 iread ラップの iread_fullblock が用いられま す。 iread を呼び size

    に満たない戻り値だった場合に、size を満たすまで iread を繰 り返し呼び続ける(但し0もしくは負値を除く)
  21. 終わりに stderrの警告はちゃんと読もう。でも、スクリプト + cronとかにしていると気がつき にくいかもだから、気を付けよう。