コーディング練習: holeを意識したコピー

昨日に引き続きプログラミングの練習。網羅性とサイズの大きさで有名な次の本の演習のひとつを扱いました。

作るのはファイルをコピーするだけのプログラムです。もとのファイルにholeがあれば、コピー先ファイルにもholeを作る必要があります(ヌルバイトの連続によってストレージ領域を消費してはいけない)。

web上には次のような回答例がありました。

github.com

目的のプログラムを作る場合には通常lseek()のwhence引数にSEEK_HOLEとSEEK_DATAを指定することによってholeのありかを検出します(ここには書きませんがioctl()でも同様のことができます)。しかし、上記プログラムの作者の環境はこれらが使えないカーネルバージョンだったらしく、この方法を使わない単純な実装になっていました。連続するヌル文字を見つけたら、それらをただ書き込むのではなく、lseek()によって飛ばしてしまおうというものです。これは一見うまくいきそうですが、次のような問題があります。

  • もともとわざとヌル文字で埋めていたブロックもholeになってしまう
  • hole部分をフルスキャンする必要があるため、コピー所要時間が長くなる
  • ファイル末尾に連続するNULL文字あるいはholeがあった場合は、その部分がコピーされない。それら領域をlseek()によって飛ばした後に0バイトwrite()する(つまり実質何もしない)だけでファイルサイズを正しいものに更新しないため、ファイルサイズがコピー元ファイルより短くなってしまう

実際、次に示す簡単なテスト(ファイル内の色々な箇所にholeが空いているファイルをコピー)のほとんどに失敗しました。

github.com

その一方で、私の実装は次の通りです(上記プログラムは見ずに作ってます)。

github.com

このプログラムは、次のような特徴があります。

  • lseek()におけるSEEK_HOLEとSEEK_DATAを使っている。データ領域をコピーする処理と、その後に続くholeを飛ばす処理の繰り返しです。
  • エラー処理のためにgotoを用いている。gotoを見るのも嫌というかたがたもいると思いますが、こういうケースではgotoを使わないほうがむしろコードが見にくくなると私は考えています

こちらは前述のテストをすべてパスしました。ほかの観点でテストをしたらまだバグは潜んでいると思いますが、ひとまずは合格です。