2014-11-26

ssh を通じてパイプから読む tar にはなぜ -B が必要か

説明を書くことがあったので、なくさないように保存。間違いがあったら教えてください。

ファイルというものは、種類によって特性がいろいろあります。
特性とは、いいかえると、できることと、できないことです。

ディスクファイルは一番制約の少ないものです。
  • メディア容量いっぱいまで無制限に伸ばせる有限のバイト列が、1つのプロセスにつながっている
  • lseek(2) によって任意バイト目のに位置づけすることができて(後退も可能)、
  • その現在位置から任意個バイトを read(2) することができ、
  • ファイル終端で read するとゼロが返ることで、「読めるわけではないがエラーでもない状態」が通知され、
  • あるいは現在位置から任意個バイトを write(2) することもできてその後は破壊されず、
  • 要すれば現在位置から後の全データを truncate(2) によって破棄することもできる。
パイプはちょっと制約があります:
  • バイトストリーム(遡れない流れのイメージ)の両側がプロセスにつながっており、
    write する側と read する側が決まっている
  • write したバイト数だけパイプに入って、それ以下の数だけ read したときはその内容が得られる
    パイプに入っているバイト数を超えた数を read すると、入っているバイト数だけしか読めない
  • パイプに入るバイト数が決まっている(512バイトとか、Linuxだと4096バイトとか、そんなにデカくない)。
    満杯のパイプに書き込むと write がブロック(吸い出してもらえるまで止まる)し、
    空のパイプから読み出すと read がブロック(書き込んでもらえるまで停止)する
  • lseek(2) は常にエラーになり、つまり後退はできない
  • 書き込み側プロセスが先にパイプを閉じたとき、ファイルの終端と同じ状態になり、読み側 read() に 0 が返される
    逆に、読み込み側プロセスが先にパイプを閉じたとき、書き込み側が write(2) すると、シグナル SIGPIPE を受ける (ふつう13番だから exit code 141 に見える)
TCPソケットもパイプに似た特徴を持ちますが、書き込み側プロセスが書いたバイト列が一気にすぐ読み出し側プロセスに届くわけでなく(なにせ別ホストですからね)、read で何バイト取れるかはもっと予想がつきません。

テープは全然ちがいます:
  • ファイルはバイトではなくブロックの列
  • ブロックは「1回 write したバイト列」、昔は固定長だったみたいだが、いまどきは可変長、つまり書き込み側が指定できる(512バイトの倍数限定かも)
  • ブロックはテープに書かれていて、read はブロック単位でしか行えない。
    ブロック長以下の read はブロック先頭だけが読み出され、ブロック長を超える read はブロック長だけしか読み出せない
  • ファイル内の read 以外での前後移動はできないとおもったほうがよい。
  • テープ上のファイルの終わりは書き込みプロセスが close したら勝手にできる
    テープに書き込む際は、メディア上の後続のファイルは必ず破壊されるので、ftruncate にあたる別操作はない(必ず行われる)
  • テープ上のファイルの終わりでは read(2) が 0 を返すことはディスクファイルと同じ
  • 巻き戻しをしないテープデバイスでは、ファイルの終わりを読み出して close した直後に、次のファイルに移動している

このような特性は Linux なら st(7) や pipe(7) で説明されている、はずなのですが、あの文章では一見さんお断りですわね。
ともあれそれをふまえると、-B やら SIGPIPE の問題もよくわかると思います。

  • tar は、固定のブロック長で read/write を行う、つまり、1ブロックが1回で読めることを前提にしている
  • ブロック長は 512 バイトかける -b オプションの数
  • tar アーカイブに入れるファイル長がブロック長の倍数でないときは、末尾にゼロが埋められる(パディング)
  • テープから tar が読む場合、書き込んだときのブロック長が読み出されるので、自動的にブロック長を判定できる
  • パイプから tar が読む場合(特にネットワーク越しならなおさら)は、書き込んだときのブロック長はわからないし、1ブロックが1回で read できるとも限らない。
    1ブロック(と思い込んでいるバイト数)が1回で読めない場合、続きの読み出しをリトライさせるのが -B オプション。
  • また、パイプから読む tar はブロック長がわからないので、必要なファイルを解読したら終了してしまうようになっていると(gtar はそうではないと思う)、
    書き込み側はパディングを続けている間に読み出し側が終了して、SIGPIPE を受けて爆死、ということになるのでしょう。

0 件のコメント:

コメントを投稿