XZ Utils の使い方

矢田 晋

Abstract: XZ Utils は LZMA Utils の後継となるソフトウェアであり,Lempel–Ziv–Markov chain Algorithm(LZMA)の改良版である LZMA2 の実装になっています.圧縮には時間がかかるものの,bzip2 を上回る圧縮率を誇り,高速に伸長できるという利点から,tar に採用されるなど,普及が進んでいます.本記事は,C 言語から XZ Utils を利用する方法の解説になっています.

はじめに

XZ Utils のドキュメントは,公式サイトで配布されているアーカイブに同梱されています.FAQ,開発履歴,圧縮形式の解説は doc/ にあり,ライブラリのマニュアルは,src/liblzma/api/lzma/ にあるヘッダに記述されています.XZ Utils がインストールされている状況では,#include するヘッダは <lzma.h> となり,マニュアルについては /usr/include/lzma/*.h を参照することになります.

zlib と libbzip2 にはファイル入出力を簡単にするインタフェースが用意されていますが,XZ Utils には同様のインタフェースが用意されていません.将来的には,gzip 形式,bzip2 形式,xz 形式のファイルを同じように操作できるライブラリを提供することが計画されているようです.

以下,XZ Utils のインストール方法を紹介した後,メモリ上での圧縮・ について,基本的な使い方を説明します.また,XZ Utils を用いて圧縮・伸長をおこなうプログラムのサンプルコードを用意しました.

インストール

Debian 系の環境における XZ Utils のインストール

$ sudo aptitude install xz-utils liblzma-dev
$ sudo apt-get install xz-utils liblzma-dev

Debian 系の環境であれば,aptitude を使って簡単にインストールできます.インストールには root 権限が必要なので,必要に応じて sudosu を使ってください.古い環境であれば,apt-get になるかもしれません.xz-utils はコマンドラインツールのパッケージであり,liblzma-dev が開発用パッケージになっています.

Red Hat 系の環境における XZ Utils のインストール

$ sudo yum install xz xz-devel

Red Hat や CentOS であれば,yum を使って簡単にインストールできます.こちらも必要に応じて sudosu を使ってください.xz はコマンドラインツールのパッケージであり,xz-devel が開発用パッケージになっています.

ソースコードからのインストール

$ wget http://tukaani.org/xz/xz-5.0.3.tar.gz
$ tar zxf xz-5.0.3.tar.gz
$ cd xz-5.0.3
$ ./configure
$ make
$ make check
$ sudo make install

ソースコードの tarball は公式サイトからダウンロードできます.ブラウザや wget により tarball をダウンロードした後は,configure, make, make check, make install という一般的な手順でインストールします.xz を軽く試してみるのであれば,make check まで終わった時点で,src/liblzma/api/lzma*src/liblzma/.libs/liblzma.a のみをコピーして使うことも可能です.

インストールの確認

// test.c
#include <lzma.h>
int main(void) {
  lzma_stream strm = LZMA_STREAM_INIT;
  lzma_easy_encoder(&strm, 6, LZMA_CHECK_CRC64);
  lzma_end(&strm);
  return 0;
}
$ gcc -Wall test.c -llzma -Dlzma_attr_warn_unused_result=

簡単なソースコードをコンパイル・リンクしてみれば,XZ Utils が問題なくインストールされているかどうか分かります.リンクにおいては,XZ Utils を指定するオプション(-llzma)が必要になります.

XZ Utils では,__attribute__((warn_unused_result)) という属性により,返り値の未使用に対する警告が有効になっています.エラー処理の徹底には有用な手段ですが,少し試してみたいという状況では,-Dlzma_attr_warn_unused_result= というオプションで無効化しておいた方が良いかもしれません.

圧縮・伸長

圧縮・伸長の概要

// 外部から利用する機会のあるメンバを抜粋
typedef struct {
  const uint8_t *next_in;  // 入力位置
  size_t avail_in;         // 入力データの残りバイト数
  uint64_t total_in;       // 入力データの合計バイト数

  uint8_t *next_out;       // 出力位置
  size_t avail_out;        // 出力バッファの残りバイト数
  uint64_t total_out;      // 出力データの合計バイト数

  lzma_allocator *allocator;  // メモリを確保・解放する方法
} lzma_stream;
lzma_stream strm = LZMA_STREAM_INIT;
void InitStream(lzma_stream *strm) {
  *strm = (lzma_stream)LZMA_STREAM_INIT;
}

XZ Utils の基本的な使い方では,<lzma/base.h> で定義されている lzma_stream という構造体を利用することになります.lzma_stream の役割は zlib における z_stream に相当しますが,初期化には LZMA_STREAM_INIT というマクロを使います.変数の定義と同時に初期化をする場合は単純な代入文のように記述できますが,ポインタを受け取って初期化する場合などは,LZMA_STREAM_INIT により初期化したオブジェクトを作成して代入するという手順が必要になります.

typedef struct {
  void *(*alloc)(void *opaque, size_t nmemb, size_t size);
  void (*free)(void *opaque, void *ptr);
  void *opaque;
} lzma_allocator;

メモリを確保・解放する方法については,lzma_allocator という構造体により指定できます.malloc(), free() を用いるデフォルトの動作では困るという場合にのみ変更してください.lzma_allocator については,<lzma/base.h> に詳細な説明があります.

lzma_stream のメンバを初期化した後は,圧縮・伸長の初期設定をおこないます.圧縮するときに呼び出す関数は lzma_easy_encoder(),伸長するときに呼び出す関数は lzma_stream_decoder() です.これらの関数については,<lzma/container.h> に詳細な説明があります.圧縮については lzma_stream_encoder() という関数もありますが,設定は難しそうです.気になる方は <lzma/filter.h> を参照してください.

初期設定の後に呼び出す関数は共通であり,繰り返し lzma_code() を呼び出して圧縮・伸長をおこない,lzma_end() によって終了処理をおこなうようになっています.これらの関数については,<lzma/base.h> に詳細な説明があります.

xz 形式の圧縮・伸長には lzma_easy_encoder(), lzma_stream_decoder() を使いますが,lzma 形式の圧縮・伸長には lzma_alone_encoder(), lzma_alone_decoder() を使います.圧縮形式が分かっていないデータを伸長するときは,lzma_auto_decoder() により自動判別することも可能です.

圧縮前後のデータをすべてメモリ上に展開できるときや,メモリマップト I/O によりファイル全体をメモリ上にマップできるときなど,データをひとまとめにして圧縮できる状況では,lzma_easy_buffer_encode() を使って一息に圧縮することが可能です.同様に,lzma_stream_buffer_decode() を使って一息に伸長することも可能です.これらの関数を用いる場合,lzma_stream は不要になります.

圧縮

lzma_ret lzma_easy_encoder(lzma_stream *strm,
                           uint32_t preset,
                           lzma_check check);
lzma_ret ret = lzma_easy_encoder(&strm, 6, LZMA_CHECK_CRC64);
assert(ret == LZMA_OK);

圧縮の初期設定には lzma_easy_encoder() を使います.第 1 引数には lzma_stream のオブジェクト,第 2 引数である preset には圧縮レベルとフラグ,第 3 引数である check には整合性の確認に使用する誤り検出符号を指定するようになってします.指定できる圧縮レベルは 0 以上 9 以下の整数です.数値を大きくすると,圧縮率は向上するものの,圧縮にかかる時間が長くなり,メモリ消費が大きくなります.コマンドラインツールである xz が用いるデフォルトの圧縮レベルは 6 です.また,LZMA_PRESET_EXTREME というフラグを与えることにより,圧縮にかかる時間は長くなるものの,圧縮率を少しだけ向上させることができます.誤り検出符号については,LZMA_CHECK_NONE, LZMA_CHECK_CRC32, LZMA_CHECK_CRC64, LZMA_CHECK_SHA256 の中から選ぶようになっています.xz が用いるデフォルトの確認方法は LZMA_CHECK_CRC64 です.<lzma/check.h> に少しだけ説明があります.lzma_easy_encoder() は失敗すると LZMA_OK 以外の値を返します.

lzma_ret lzma_code(lzma_stream *strm,
                   lzma_action action);
lzma_action action = LZMA_RUN;
lzma_ret ret = LZMA_OK;
do {
  strm.next_in = (const uint8_t *)...;
  strm.avail_in = ...;
  if (/* 入力が尽きていれば */) {
    action = LZMA_FINISH;
  }
  do {
    strm.next_out = (uint8_t *)...;
    strm.avail_out = ...;
    ret = lzma_code(&strm, action);
    assert((ret == LZMA_OK) ||
           (ret == LZMA_STREAM_END));
    // 出力バッファの中身を処理
  } while (strm.avail_out == 0);
  assert(strm.avail_in == 0);
} while (action != LZMA_FINISH);
assert(ret == LZMA_STREAM_END);

圧縮には lzma_code() を使います.第 1 引数には入力データと出力バッファを設定した lzma_stream のオブジェクト,第 2 引数には LZMA_RUN を指定します.ただし,すべての入力データを指定した後は,圧縮を完了させるために LZMA_FINISH を指定します.なお,LZMA_SYNC_FLUSHLZMA_FULL_FLUSH を指定することにより,内部状態として保持されているデータの出力を強制したり,内部状態を初期化したりすることも可能ですが,これらの機能を使う機会は少ないと思います.それぞれの動作については,<lzma/base.h> に詳細な説明があります.

入力データの設定については,lzma_stream のメンバである next_in, avail_in に対しておこないます.出力バッファの設定については,lzma_stream のメンバである next_out, avail_out に対しておこないます.next_in, next_outuint8_t のポインタになっているので,明示的に型変換しないとコンパイラに警告されるかもしれません.また,next_in には const 修飾子が付与されているので,zlib や libbzip2 と同じように扱えないことがあります.

lzma_code() の返り値は,おそらく LZMA_OK, LZMA_STREAM_END, LZMA_MEM_ERROR, LZMA_OPTIONS_ERROR, LZMA_DATA_ERROR, LZMA_BUF_ERROR, LZMA_PROG_ERROR の 7 種類です.圧縮が問題なく継続していれば LZMA_OK になります.第 2 引数に LZMA_RUN 以外の動作を指定するときは,内部状態の変化に際して LZMA_STREAM_END が返り値になるまで,同じ動作を継続する必要があります.動作が LZMA_FINISH であれば,LZMA_STREAM_END は圧縮の完了を意味するので,lzma_end() の呼び出しを待つ状態になります.

LZMA_MEM_ERROR はメモリの確保に失敗したことを示します.LZMA_OPTIONS_ERROR は圧縮の設定と lzma_code() の引数が矛盾していることを示します.LZMA_DATA_ERROR はサイズが上限に達したことを示す返り値ですが,上限は約 8EiB(263 bytes)であることから,バグなどによるものと考える方が自然です.LZMA_BUF_ERROR は,入出力の進捗をともなわない lzma_code() の呼び出しが連続したことを示します.入出力を設定しなおせば復帰できるという点は zlib, libbzip2 と同じですが,連続でなければ LZMA_OK になるという点が異なります.最後に,LZMA_PROG_ERROR は,引数が間違っているときや,入出力位置が NULL になっているとき,あるいは lzma_end() の後で lzma_code() を呼び出したときなどに返り値となります.エラー番号については,<lzma/base.h> に詳細な説明があります.

void lzma_end(lzma_stream *strm);

圧縮が完了したとき,あるいは圧縮を中断するときは,lzma_end() を呼び出して,lzma_stream の内部状態に割り当てられているメモリを解放する必要があります.

伸長

lzma_ret lzma_stream_decoder(lzma_stream *strm,
                             uint64_t memlimit,
                             uint32_t flags);
uint64_t lzma_easy_decoder_memusage(uint32_t preset);
uint64_t memlimit = 128 << 20;
lzma_ret ret = lzma_stream_decoder(&strm, memlimit, 0);
assert(ret == LZMA_OK);

伸長の初期設定には lzma_stream_decoder() を使います.第 1 引数には lzma_stream のオブジェクト,第 2 引数である memlimit には割り当てるメモリの上限,第 3 引数である flags には動作を切り替えるフラグを指定するようになっています.memlimit が足りないときはエラーになるので,メモリの容量に余裕があるのであれば,大きめの値にしておく方が無難です.実際に割り当てられるメモリの大きさは,lzma_easy_decoder_memusage() により確認できるようになっていて,圧縮レベルにが最大の 9 であれば,64MiB より少し大きくなります.128MiB を上限にしておけば,不足することはないと思います.

flags については,LZMA_TELL_NO_CHECK, LZMA_TELL_UNSUPPORTED_CHECK, LZMA_TELL_ANY_CHECK, LZMA_CONCATENATED を組み合わせて指定できるようになっています.LZMA_TELL_NO_CHECK を指定すると,lzma_code() は誤り検出符号が使われていないデータに対して LZMA_NO_CHECK を返すようになります.LZMA_TELL_UNSUPPORTED_CHECK を指定したときは,対応していない誤り検出符号を見つけたときに LZMA_UNSUPPORTED_CHECK を返すようになります.LZMA_TELL_ANY_CHECK を指定した場合,誤り検出符号の種類を lzma_get_check() により取得できるようになったタイミングで LZMA_GET_CHECK を返します.最後に,LZMA_CONCATENATED を指定したときは,複数のデータが連結されている場合においても,単一のデータであるかのように伸長をおこないます.この状態では,LZMA_FINISH を指定しない限り,lzma_code()LZMA_STREAM_END を返しません.

// 圧縮と同じ
lzma_action action = LZMA_RUN;
lzma_ret ret = LZMA_OK;
do {
  strm.next_in = (const uint8_t *)...;
  strm.avail_in = ...;
  if (/* 入力が尽きていれば */) {
    action = LZMA_FINISH;
  }
  do {
    strm.next_out = (uint8_t *)...;
    strm.avail_out = ...;
    ret = lzma_code(&strm, action);
    assert((ret == LZMA_OK) ||
           (ret == LZMA_STREAM_END));
    // 出力バッファの中身を処理
  } while (strm.avail_out == 0);
  assert(strm.avail_in == 0);
} while (action != LZMA_FINISH);
assert(ret == LZMA_STREAM_END);

初期設定の後は,入力データと出力バッファを設定した lzma_stream のオブジェクトを第 1 引数として lzma_code() を呼び出し,圧縮されたデータを伸長します.第 2 引数には LZMA_RUN を指定します.初期設定において LZMA_CONCATENATED を指定していない限り,LZMA_FINISH は必要ありません.入出力の設定方法は,圧縮されたデータが入力となり,伸長されたデータが出力となることを除けば,圧縮の場合と同じです.next_in, avail_in に入力データ,next_out, avail_out に出力バッファを設定します.

lzma_code() の返り値は,圧縮するときの返り値に,誤り検出符号に関する 3 種類の返り値と,LZMA_MEMLIMIT_ERROR, LZMA_FORMAT_ERROR を加えた 12 種類になります.LZMA_MEMLIMIT_ERROR はメモリの上限が不足した場合の返り値であり,LZMA_FORMAT_ERROR は入力データが xz 形式になっていない場合の返り値です.また,入力データが破損している場合,返り値は LZMA_DATA_ERROR になります.

伸長が完了したとき,あるいは伸長を中断するときは,lzma_end() を呼び出して,lzma_stream の内部状態に割り当てられているメモリを解放する必要があります.

lzma 形式の圧縮・伸長

XZ Utils では xz 形式の前身にあたる lzma 形式の圧縮・伸長にも対応しています.ただし,lzma 形式から xz 形式への移行が強く推奨されているので,特別な事情がない限り,lzma 形式への圧縮は控えておくべきです.

lzma_ret lzma_alone_encoder(lzma_stream *strm,
                            const lzma_options_lzma *options);
lzma_bool lzma_lzma_preset(lzma_options_lzma *options,
                           uint32_t preset);
lzma_options_lzma options;
lzma_bool is_failed = lzma_lzma_preset(&options, 6);
assert(is_failed == false);
lzma_ret ret = lzma_alone_encoder(&strm, &options);
assert(ret == LZMA_OK);

lzma 形式への圧縮には lzma_alone_encoder() という関数を使います.第 1 引数には lzma_stream のオブジェクト,第 2 引数である options には lzma 形式のオプションを指定するようになっています.そして,圧縮レベルとフラグからオプションを作成する関数が lzma_lzma_preset() です.第 2 引数である preset に圧縮レベルとフラグを渡すことにより,第 1 引数である options にオプションの内容が設定されます.lzma_lzma_preset() は成功すると true を返し,失敗すると false を返すことに注意してください.初期化後の手順は xz 形式への圧縮と同じです.ただし,lzma 形式の機能には制限があり,LZMA_SYNC_FLUSH に対応していません.

lzma_ret lzma_alone_decoder(lzma_stream *strm,
                            uint64_t memlimit);

lzma 形式のデータを伸長するには lzma_alone_decoder() という関数を使います.第 1 引数には lzma_stream のオブジェクト,第 2 引数である memlimit には割り当てるメモリの上限を指定するようになっています.lzma 形式には誤り検出機能がないので,lzma_easy_decoder() とは異なり,第 3 引数である flags がなくなっています.初期化後の手順は xz からの伸長と同じです.

lzma_ret lzma_auto_decoder(lzma_stream *strm,
                           uint64_t memlimit,
                           uint32_t flags);

lzma_auto_decoder() を用いることにより,圧縮形式を自動的に判別して伸長の初期設定をすることも可能です.引数は lzma_easy_decoder() と同じであり,基本的には,第 2 引数である memlimit128MiB,第 3 引数である flags0 を渡しておけば問題ありません.

一括圧縮・一括伸長

lzma_ret lzma_easy_buffer_encode(uint32_t preset,
                                 lzma_check check,
                                 lzma_allocator *allocator,
                                 const uint8_t *in,
                                 size_t in_size,
                                 uint8_t *out,
                                 size_t *out_pos,
                                 size_t out_size);
size_t lzma_stream_buffer_bound(size_t uncompressed_size);
lzma_ret lzma_stream_buffer_decode(uint64_t *memlimit,
                                   uint32_t flags,
                                   lzma_allocator *allocator,
                                   const uint8_t *in,
                                   size_t *in_pos,
                                   size_t in_size,
                                   uint8_t *out,
                                   size_t *out_pos,
                                   size_t out_size);

入力データや出力バッファを分割する必要がない状況では,lzma_easy_buffer_encode() を用いることにより,lzma_stream を介することなく圧縮が可能です.lzma_stream_buffer_bound() により得られるサイズの領域を出力バッファとして確保した後で lzma_easy_buffer_encode() を呼び出すことになります.第 1 引数である preset と第 2 引数である check は,内部で lzma_easy_encoder() に渡す引数です.第 3 引数である allocator には,メモリの確保・解放に用いる方法を指定します.残っているのは入力データと出力バッファの指定に用いる引数です.出力の開始位置が out + *out_pos になり,出力の後で out_pos の参照先が更新されることに注意してください.

伸長には lzma_stream_buffer_decode() を利用します.出力バッファのサイズを求める手段は用意されていないので,圧縮の段階で元のサイズを保存しておく必要があります.第 1 引数である memlimit の参照先は,内部で LZMA_MEMLIMIT_ERROR が発生した場合に更新されることがあります.入力データと出力バッファの指定については,in_pos が追加されていることに注意してください.lzma_easy_buffer_encode() における out_pos と同様に,入力位置の受け渡しに利用されます.

サンプルコード

$ gcc -Wall -O2 -std=c99 xz-utils-test.c -llzma -o xz-utils-test

xz 形式の圧縮・伸長をおこなうプログラムのサンプルコードとして xz-utils-test.c を用意しました.C99 の機能を使っているので,gcc には -std=c99 を渡すようにしてください.

$ ./xz-utils-test --help
Usage: ./xz-utils-test [OPTION]... [FILE]...
Version: xz-utils-5.0.3
Options:
  -e, --encoder  圧縮します (default)
  -d, --decoder  伸長します
  -p, --preset=[0-9][e]  圧縮レベルとフラグを指定します (default: 6)
  -c, --check=[NONE, CRC32, CRC64, SHA256]
                         誤り検出符号を指定します (default: CRC64)
  -o, --output=[FILE]    出力ファイルを指定します (default: stdout)
  -h, --help     このヘルプを表示します

コマンドライン引数による圧縮形式や圧縮レベルの切り替えが可能になっています.-h もしくは --help をオプションとして渡すことにより,XZ Utils のバージョンとコマンドライン引数の一覧を確認することができます.

おわりに

本記事では XZ Utils の基本的な使い方を説明しました.大抵のアプリケーションについては対処できるようになっています.より高度な使い方については,公式サイトで配布されているアーカイブに同梱のドキュメントおよびヘッダファイルを参照してください.