2015年9月18日金曜日

圧縮イメージをマウントする

2017/12/15 追記: Ubuntu16.04では、Lubuntu 16.04 でxz圧縮イメージをマウントする を参照せよ。
CFカードやSDカードにデータを記録する装置を作って、もう15年以上。
Flashメモリは寿命のあるデバイスである。CFカードもSDカードも中身はFlashメモリなので、装置運用時にカードが壊れてしまうことがある。
お客さんからそのようなカードが持ち込まれ、できる限りデータを回収して欲しいと依頼されることもある。
その装置で記録するデータの構造は比較的単純なので、イメージさえ入手できれば比較的簡単に取り出せる。

Flashメモリの寿命が原因ではなく不具合が原因かもしれず、それは調べてみなければわからない。
また、開発時の動作確認時や長期連続試験をしているときに、カードの記録データが壊れてしまうことがある。
特に長期に渡る試験のとき、事故発生前はカードがどういう状態だったのかを知りたいことがある。時間を遡ることはできないので、時々イメージをbackupしておく。
自分で作った装置なので責任もあるし、壊れ方から原因を理解したり、回復方法の習得にもなる。

と、いうことで調査のためカードのイメージをとっておく。
(お客さんのデータは、お客さんのものなので、早期に破棄する)
イメージをとっておけば、linuxならloopデバイスを使ってマウントでき、後から過去の状態を参照できる。

CFカードやSDカードとは言っても、最近は大容量なので、ストレージ領域を消費する。
もったいないので、圧縮しておきたいが、圧縮イメージは普通にはマウントできない。
read-onlyでいいからマウントできればいいのだが、gzipやbzip2はランダムアクセスのためのデータ構造を持っていないので、ランダムアクセスするためには全体を展開する必要がある。
近年のSDカードは巨大で数十GiBもあり、展開する時間も記憶域容量も馬鹿にならない。

gzipを拡張したdictzipというものがあり、ランダムアクセスのためのテーブルを持っている。しかし、そのテーブルをファイルの先頭に配置するため、パイプで処理できないという欠点がある。bzip2は、開発時にランダムアクセスの仕組みを入れるかどうかという議論があったが、やめたそうだ。ブロックをスキップしながらアクセスする方法もあるのだが、1つずつブロックをスキップしなければならないため、効果が低い。

数年前からxzという圧縮ツールがある。
圧縮ファイル内をblockと呼ばれる単位に分けることができ、そのblockのインデックス情報も持っている。
インデックス情報を参照することでblockの粒度でランダムアクセスができる。
dictzipに似るが、インデックス情報は、ファイルの末尾付近にある。
そのため、データの圧縮が終わった後で、インデックス情報を構築し末尾に格納することができ、パイプで処理できる。

ファイルの末尾にインデックス情報があると、インデックス情報を読むためには、全体を読まないといけないような気がするかもしれないが、そんなことはない。seekして、最後の方だけ読めばいい。
ランダムアクセスを行おうとしているのなら、ファイルのseekができるはずで、seekができるならファイルの末尾にインデックスがあっても全く問題ない。

xzは比較的新しい圧縮方法だが、LinuxのLiveCDで使われるsquashfsでもサポートされるなど、様々なシーンで利用されており、実績も徐々に上がってきている。
Ubuntu12.04のxz-utils(xzのバイナリパッケージ)のchangelogをみても、
$ apt-get changelog xz-utils | head -n 30
取得:1 xz-utils (http://changelogs.ubuntu.com/changelogs/pool/main/x/xz-utils/xz-utils_5.1.1alpha+20110809-3/changelog) の変更履歴 [25.5 kB]
xz-utils (5.1.1alpha+20110809-3) unstable; urgency=low

  * liblzma: Match upstream ABI.
    - Remove the lzma_chunk_size() function.
    - A few ABI tweaks to reserved space in structures.
    - Enable ELF symbol versioning.
    - Bump soname to 5.
    - Continue to leave out threading support, since the relevant
      interfaces in liblzma are not yet stable.
  * xz-utils/README.Debian: Remove note on ABI differences.
  * Remove liblzma/README.Debian.
  * liblzma: Introduce a lzma_code@Base compatibility symbol to ensure
    programs linked against unversioned symbols from liblzma2 can
    share a process image with liblzma5 without breaking.
  * debian/symbols: XZ_5.0 symbols come from liblzma5.  Build-Depends:
    dpkg-dev (>= 1.15.6); thanks to Jakub Wilk for a reminder.
  * debian/symbols: The lzma_code@Base symbol is not guaranteed to
    continue to exist in the future, so tell dpkg-shlibdeps to produce
    an error if some package manages to use it.

 -- Jonathan Nieder   Thu, 20 Oct 2011 21:31:31 -0500

xz-utils (5.1.1alpha+20110809-2) unstable; urgency=low

  * debian/rules build-arch: Do not trigger an infinite "make"
    recursion loop when DEB_BUILD_OPTIONS=nocheck.  Closes: #638071.
    Thanks to Thorsten Glaser.

 -- Jonathan Nieder   Tue, 16 Aug 2011 18:11:47 -0500
という感じで、最後の変更は2011年10月20日だ。利用されているのに変更や更新はない。バージョンに"alpha"の文字が見られるものの、安定していると言える。
最新のwilyでも"5.1.1alpha+20120614-2ubuntu2"で、更新がほぼ無い(alphaのままだ)。

実は、このxzで圧縮したイメージをマウントする方法がある。
圧縮イメージを、NBD(Network Block Device)経由で、ブロックデバイスにしてマウントするのだ。
この場合、書き込みはできないのだが、CFカードやSDカードのイメージを壊したくはないので、むしろ都合がいい。

必要なものを入手する

NBDを動かすには、nbd-server, nbd-client が必要だ。
xz圧縮イメージをNBDで扱うには、nbdkit も必要だ。
しかしながら、Ubuntu12.04(NautilusでCompactが使えるので未だに使い続けている)の場合、nbdkitのパッケージが無い。
そのため .debは諦めて .tar.gz を入手して、自分でビルドしてインストールする。
ただし、これをビルドするには、libmodule-build-perl と libperl-dev パッケージが必要になる。

まずは、必要な.debパッケージをapt-getで入手する。
$ sudo apt-get install nbd-server nbd-client libmodule-build-perl libperl-dev
nbdkitの .tar.gz を入手する。 このページの下の方のTarBallsへのリンクから、nbdkit-1.1.10.tar.gzを得る。 wgetで入手するなら、
$ wget http://libguestfs.org/download/nbdkit/nbdkit-1.1.10.tar.gz

nbdkitのビルドとインストール

適当なディレクトリを作って、ビルドする。
$ mkdir xxxx
$ cd xxxx
$ tar -zxvf ../nbdkit-1.1.10.tar.gz
      (サブディレクトリnbdkit-1.1.10ができる)
$ cd nbdkit-1.1.10
$ ./configure
    (いろいろ表示される)
$ make
    (いろいろ表示される)
$ make check
  : (省略)
============================================================================
Testsuite summary for nbdkit 1.1.10
============================================================================
# TOTAL: 8
# PASS:  8
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================
  : (省略)
    (全部パスしたようだ)
$ sudo make install
    (いろいろ表示される)

xz圧縮イメージの用意

xz圧縮イメージなら、何でもいいというわけではない。
block単位でしかseekできないという事を忘れてはいけない。xzはデフォルトではデータ全体を1つのブロックとして圧縮してしまう。この場合、全体を展開しないといけないので、本末転倒だ。
小さなブロックに分けるのが良い。
ネット上を検索すると、プリセットレベル -9 ブロックサイズ16MiBで行う例がよく見られるが、これには疑問を感じる。
まず、xz の man page上で以下のような書き込みがある。
          : (省略)
   -0 ... -9
      Select a compression preset level.  The default is -6.  If  mul‐
      tiple  preset  levels  are specified, the last one takes effect.
      If a custom filter chain was already specified, setting  a  com‐
      pression preset level clears the custom filter chain.

      The  differences  between  the presets are more significant than
      with gzip(1) and bzip2(1).  The  selected  compression  settings
      determine  the  memory  requirements  of  the decompressor, thus
      using a too high preset level might make it  painful  to  decom‐
      press  the file on an old system with little RAM.  Specifically,
      it's not a good idea to blindly use -9 for  everything  like  it
      often is with gzip(1) and bzip2(1).
          : (省略)
自分なりに訳してみると、
          : (省略)
   -0 ... -9
      圧縮プリセットレベルを選択。デフォルトは-6だ。複数のプリセット
      レベルが指定されたら、最後の1つが効果をもつ。カスタムフィルタ
      チェーンがすでに指定されていたら、圧縮プリセットレベルの設定は
      カスタムフィルタチェーンをクリアする。

      プリセット間の違いは、gzip(1)やbzip2(1)でのよりも意味深い。
      選択された圧縮設定はデコンプレッサのメモリ要求を決定し、そのため
      高すぎるプリセットレベルは、少ないRAMの古いシステムで、その
      ファイルを展開するのに痛みをともなわせることになるだろう。特に
      gzip(1)やbzip2(1)でしばしば見られるように、なんにでも盲目的に
      -9を使うのは良い考えではない。
          : (省略)
また、ブロックサイズは辞書サイズの3倍ぐらいが良いとどこかに書いてあった。
(すっかり忘れっぽくなったものだ。)
特に辞書サイズよりもブロックサイズが小さいと、辞書のために確保したメモリが無駄になってしまうためだ。
プリセットレベル -9 の辞書サイズは 64MiBであり、16MiBとは完全に逆転している。
という訳で、プリセットレベルが -9 で、ブロックサイズが16MiBがおかしいのは解ってもらえたと思う。

じゃ、どのへんが良いだろうか?
私はこの古いVAIOも使っている。これでもそこそこ使えないと困る。
このマシンはメモリが極めて少なく非力でもあるので、プリセットレベル -0 ブロックサイズ 1,048,576(1MiB)とする。

具体的な圧縮方法が決まったので、以下のようにしてxz圧縮する:
$ zcat sd64GB.img.gz | xz -0 --block-size=1048576 > sd64GB.img.xz
上記は元データがgzipで圧縮されていた場合の例だ。

NBDの提供

圧縮ファイルの用意ができたら、圧縮ファイルをNBD(Network Block Device)として提供しよう。
提供するには、nbdkitを使う。
$ sudo nbdkit --user xxx --group xxx -i 127.0.0.1 xz file=sd64GB.img.xz
xxxはユーザ名だ。 子プロセスが起動し、バックグラウンドでndbサーバが動作する。
デフォルトではポート番号は10809になる。-pで任意のポート番号を指定することもできる。
後で止めるには、psで、nbdkitを探してkillせよ。

デバイスノードを作る

サーバが動き出したので、ndb-clientでデバイスノードを作る。
$ sudo nbd-client 127.0.0.1 10809 /dev/nbd0 -nofork
これで/dev/nbd0でブロックデバイスが見えるようになる。
パーティションが/dev/nbd0p1で参照できる。

マウントする

デバイスノードができたらマウントだ。
$ mkdir sd64GB
$ sudo mount /dev/nbd0p1 sd64GB -o ro,uid=xxx,-gid=xxx 
xxxはユーザ名だ。適切なユーザ名を指定せよ。 FATならこれでいいのだが、ジャーナリング機能をもつext3やext4では、イメージが汚れている場合マウントできない。
read-onlyなので、ジャーナルをリプレイできないため、汚れをきれいにできないのだ。
この場合、noloadオプションを使って、マウント時のジャーナルの再生を抑制する。
$ sudo mount /dev/nbd0p1 sd64GB -o ro,noload 

これで、圧縮イメージ内を参照できるようになる。
ext3やext4のマウント方法も書いたが、これらの内容を圧縮したいなら、ブロックデバイスイメージを圧縮するよりも、squashfs等を使ったほうがいいだろう。

私は職業柄、問題調査のためFATフォーマットされたCFカードやSDカードのイメージを、未割り当て領域も含めて保存し、後から参照したいので、このような手段を使っている。

2015/09/19 一部訂正。

2015年9月5日土曜日

今日のケーニッヒ

中心付近の葉っぱは、13枚目の葉っぱ。
先週出てきた。次の芽が続かない。


12枚目の葉っぱが、虎模様のようになってる。
急に大きくなったからか?今度は何だろう。

2015年9月1日火曜日

2週連続ダイビング

先週に続いて、今週もお泊りダイビングに行ってきた。

当初の予定では、IOP(伊豆海洋公園)と井田の予定だったが、天気がイマイチだったので、大瀬崎と井田になった。
大瀬崎は先週も潜っていたが、先週の2日目は二日酔いで2本目をキャンセルしていたので、消化不良気味だったので、ちょうど良かった。

潜ってみると、先週よりも水温と透明度が上がり、いい感じになっていた。
さらに、先週は「大瀬崎・先端」が遊泳禁止だったが、今週は潜れるようになっていた。

ツノダシ


ミジンベニハゼ

先端では、キンギョハナダイやスズメダイが元気だったが、ちょこまか動くのでうまく撮影できなかった。
それにしても、動画の手ブレがひどい。
こういうのは、カメラを固定して撮らないとダメかも。

結局、大瀬崎では4本潜った。
その後、井田の天野荘へ移動して、一泊。

次の日、起きると外は雨。しかし、海を見ると、波も少なく良さそう。
潜ってみると、ぶち抜け。下の方は、透明度20mぐらい。
鉄パイプの枠組みから、下の二股の木がはっきり見える。すごい透明度なのに水温が高い。
おそらく、黒潮が来たのだろう。何度も井田で潜っているが、こんなにすごいのは初めてだ。

透明度が高いので、中層の群れがすごくいい。
しかし、もともと井田の群れはすごいので、動画で撮影しようとしてもうまく撮影出来なかった。

失敗覚悟で、ピンナップを色々撮った。

キンギョハナダイ

ソラスズメダイ

ナガサキスズメダイ

ネンブツダイ

ハタタテ

透明で暖かく、楽しいダイビングだった。