Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

お前誰? 鹿児島県出身 学生時代に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

Slide 4

Slide 4 text

dd convert and copy a file

Slide 5

Slide 5 text

基本的な使い方 ブロックデバイスを読み書きする際に非常に便利で、ストレージ装置のバックアップ やクローン作成、データ消去などに利用できる。 /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

Slide 6

Slide 6 text

option if=FILE, of=FILE 入力ファイル, 出力ファイルの指定。 bs=BYTES, ibs=BYTES, obs=BYTES 入出力、入力、出力のバイト単位指定。デフォルトは512Bytes。 count=N 入力ファイルから、ibsバイトを、count回(ブロック)読み込む。 iflag=FLAGS, oflag=FLAGS 入力に対するオプションフラグ、出力に対するオプションフラグ。 direct DIRECT I/O を用いる。

Slide 7

Slide 7 text

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) が完了している。

Slide 8

Slide 8 text

partial read ?

Slide 9

Slide 9 text

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の警告が表示されている。

Slide 10

Slide 10 text

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では無い不完全な複 製となっている。

Slide 11

Slide 11 text

発生する理由 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 に書き込んだサイズは 戻り値 で知ることができる。

Slide 12

Slide 12 text

read(2)が指定されたcount(bytes)だけ読み切らない代表的な理由 1. fdのEOFにぶつかった。 2. 遅いデバイスからの読み出しでI/Oでブロックされている間にシグナルハンドラが 動作した。なおここでの遅いデバイスはローカルディスクを含まない。 i. シグナルハンドラからreadに処理が戻った時点で、まだ何も読み出せてない 場合、戻り値として負数をセットし errno == EINTR がセットされる。 ii. シグナルハンドラからreadに処理が戻った時点で、既に読み込んだ内容が有 る場合、指定されたサイズ(count)を読み切っていなくても、既に読み込んだ サイズを戻す。 1 のケースは想像しやすい事象で、続けてread(2)を発行すれば 0 が戻ってくるはず です。当然ながら今回の事象の原因とはなりません。 2-2 のケースが今回の事象の原因となります。

Slide 13

Slide 13 text

発生しないパターンと対策 dd の count=N 指定が無い場合は、if=FILE もしくは of=FILE のEOFまで読み切るの で、欠落は発生しない。ただ、デバイスのブロックサイズ等を元にbsを指定している 場合は、アライメントがズレるためパフォーマンスが低下する。 サジェストされている iflag=fullblock オプションを用いると、指定されたサイズ (size_t count)を全て読み切らなかった場合に、残りを全て読み切るようにread(2)を 再試行する。

Slide 14

Slide 14 text

コード v8.32 https://github.com/coreutils/coreutils/blob/v8.32/src/dd.c

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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にします。

Slide 18

Slide 18 text

これらの条件を満たすのは、前回の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なの で、警告を表示する。

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

flag=fullblock とした場合には、 iread ラップの iread_fullblock が用いられま す。 iread を呼び size に満たない戻り値だった場合に、size を満たすまで iread を繰 り返し呼び続ける(但し0もしくは負値を除く)

Slide 21

Slide 21 text

終わりに stderrの警告はちゃんと読もう。でも、スクリプト + cronとかにしていると気がつき にくいかもだから、気を付けよう。