Search jakarta

POIFSファイルシステム内部

POIFSファイルシステム内部

はじめに

POIFSファイルシステムは、本質的に、Java互換プラットフォームのネイティブファイルシステムに格納される通常のファイルです。典型的には、それらは名前の末尾4文字の拡張子によって、どのような種類のデータが含まれているかが特定されます。例えば、".xls"で終わるファイルは、おそらくスプレッドシートのデータを含むでしょうし、".doc"で終わるファイルは、おそらくワード処理のドキュメントを含む事でしょう。POIFSファイルシステムは、"file system"と呼ばれます。というのも、古典的なファイルシステムに似た様式の、複数の埋め込みファイル群だからです。機能面での方針に従うと、これらをPOIFSアーカイブ、と呼ぶほうがより正確でしょう。この文書の残りの部分では、POIFSが含む"files"との混同を避けるため、ファイルシステムと言及する事とします。

POIFSファイルシステムは、良く知られたソフトウェア会社の有名なオフィス内の生産性を高めるスイートやプログラムによって使われるドキュメント形式と互換性があり、互換性のあるデータを出力します。 POIFSファイルシステムは、圧縮機能・暗号化機能、その他値打ちのある機能を提供していないため、これらのプログラムとの相互実行可能性を必要としないなら良い選択とは言えないでしょう。

POIFSファイルシステムは、ドキュメント自体をエンコードしません。例えば、ワード処理のファイル(拡張子は".doc")をお持ちならば、そのファイルシステム内で圧縮されたドキュメントと共に、POIFSファイルシステムを実際に持っていることになります。

文書規則

この文書は、Java言語仕様書(http://java.sun.comで手に入ります)で記述される数値型を利用します。簡単に言えば:

  • byteは、符号付き8bit整数で、範囲は-128から127です。
  • shortは、符号付き16bit整数で、範囲は-32768から32767です。
  • intは、符号付き32bit整数で、範囲は-2147483648から2147483647です。
  • longは、符号付き64bit整数で、範囲は-9.22E18から9.22E18です。

Java言語仕様書は、これよりも多くの型について詳細に記述していますが、この文書では言及しないこととします。

この文書で、"endian conversion"(エンディアン間変換)を言及する場合、格納された数値のbyte order(順番)の事を指します。リトルエンディアンでの数値は、意味のあるbyteの小さいほうから先に格納されています。例えば、short型を適切に読み込むには 2つのbyteを読み込み、1つ目のバイトとの間にor操作を施す前に2つ目のbyteに8bit左シフトをかけます。以下のコードは、今の方法を示しています:

public int getShort (byte[] rec)
{
    return ((rec[1] << 8) | (rec[0] & 0x00ff));
}

ファイルシステム早わかり

このセクションは、POIFSファイルシステムの「早わかり」(攻略法?)であり、それがどのように構成されているか、を示すものです。とはいえ、簡潔な説明をする事を意図しているわけではなく、寧ろ全般的な構造の「俯瞰図」を示し、どのようにそれが解釈されるかを示す事を意図して書かれています。

POIFSファイルシステムは、ヘッダで始まります。このヘッダは、機能別にファイル内の位置を特定し、確かにファイルがPOIFSファイルシステムであるという確認を行うためのものです。

ヘッダの始めの64bitは、魔法数(マジックナンバー)と呼ばれる識別子で構成されています。この識別子は、クライアントソフトに対して、確かにPOIFSファイルシステムである、と言う事を通知し、そのように対処するよう求めるためにあるものです。これは、確実にPOIFSファイルシステムであり他の形式ではない、と言う事を保証する"sanity check"です。ヘッダには、ブロックナンバーの配列も含まれています。これらのブロックナンバーは、ファイル内のブロックの数を指し示すものです。これらのブロックが互いに読み込まれると、それらは"ブロック・アロケーション・テーブル"(BAT)を形成します。ヘッダには、「ルートエレメント」としても知られるプロパティテーブル内の最初の要素へのポインタや、小さなBAT(SBAT)へのポインタも含まれています。

ブロック・アロケーション・テーブルBAT)は、プロパティテーブルに加え、ファイルシステム内のどのブロックがどのファイルに所属しているかを指定するものです。ヘッダ・ブロックの後は、ファイルシステムは等しいサイズのデータ(0からファイルシステム内にいくらでも多くの数の)ブロックに分けられています。ファイルシステム内の各々のファイルでは、それぞれのプロパティテーブル内のエントリにブロックの配列内の最初のブロックのインデックスが含まれています。ブロックの配列内の各々のブロックのインデックスは、BATの中のインデックスでもあり、BAT内のインデックスにあるnteger値は、配列内の次のブロックのインデックスを与えます。(つまり、次のBAT値のインデックス)"ファイルの終わり(EOF)"を示す特別な値がBAT内に格納されています。

プロパティテーブルは本質的にファイルシステム用のディレクトリ格納庫です。ファイルやディレクトリの名前、ファイルシステムとBATの両方の始まりのブロック、そしてその実際のサイズで構成されています。プロパティテーブルの最初の要素はルート要素です。それには2つの目的があります:ディレクトリエントリとなること(ディレクトリツリーのルート、特に)、そして小さいブロックデータの始めのブロックを保持すること。

小さいブロックデータは、4kByte以下の小さいファイルのデータを含む特別なファイルです。それは、更に小さなブロックに分かれ、特別な"small block allocation table"が(より大きいファイル用の主たるBATと同じように)小さなファイルをその小さなブロックにマッピングするのに使われます。

ヘッダ・ブロック

POIFSファイルシステムは、ヘッダブロックで始まります。ヘッダの最初64bitは、long型のファイル種別IDあるいは0xE11AB1A1E011CFD0Lという値のマジックナンバー識別子です。これは、基本的に、"sanity check"です。もし、これがヘッダの最初に無ければ(従って、ファイルシステムに)、これがPOIFSファイルシステムでは無い事を意味し、他のライブラリを使って読み込まなければならない、という事になります。

ヘッダ情報の中で最も重要な部分について知っておく必要があります。それらについては、このセクションの残りで論じていきます。

BATs

オフセット0x2Cは、BAT配列内の要素のナンバーを特定するためのint値です。0x4Cにある配列は、int配列です。この配列には、Block Allocation Table内の全てのブロックのインデックスが含まれています。

XBATs

非常に大きいPOIFSアーカイブは、ヘッダブロックで列挙されるBATブロックでアドレス指定されるよりも多くのブロックを必要とする事があるかもしれません。どのくらいの大きさでしょう?ええと、ヘッダ内のBAT配列は109要素のBATブロックのインデックスまで収納出来ます;各々のBATブロックは128個のブロックを参照し、各々のブロックは512バイトです。ですから、(訳注:ヘッダブロックで列挙しうるBATでは)109 * 128 * 512 = 6.8MB が限度、という事になります。まあ、(6.8MBのファイルというのは)非常に悪くないドキュメントではないですか!ですが、今日ではGBドライブも大変安くなっていますし、より大きなデータを扱うこともあるでしょう。ですから、そうなった場合は、BATを拡張することができるのです。ヘッダ内のオフセット0x44のinteger値は、最初の拡張BAT(XBAT)のインデックスです。ヘッダ内のオフセット0x48には、XBATブロックがどのくらいあるかを指定するint値があります。XBATブロックは、POIFSファイルシステムを構成しているブロック配列内の特定のインデックスで始まり、XBATブロックの指定された数のところまで連続して続きます。

各々のXBATブロックには、128個までのBATブロックのインデックスを含めることが出来ます。ですから、ドキュメントのサイズは、一つのXBATブロックあたり8MB(訳注:128*128*512バイト)まで拡張することが可能という事になります。 XBATブロックでインデックス指定されるBATブロックは、ヘッダブロックで列挙されるBATブロックのリストの後に追加されます。ですから、ヘッダブロックで列挙されるBATは、BATブロック0から108であり、一番目のXBATブロックで列挙されるBATブロックはBATブロック109から236であり、二番目のXBATブロックで列挙されるBATブロックはBATブロック237から364であり....と続いていくのです。

XBATブロックを全て使い果たすと、ドキュメントサイズの全体的な上限は、4-byte(2^32bit)のブロックインデックスにより制限されるサイズとなります;もしそのインデックスが符号無しintであれば、最大ファイルサイズは2テラバイトとなり、符号付きintとして扱われれば1テラバイトとなります(訳注:512byte = 2^9byte であり、(2^9 byte) * (2^32) = 2^41 byte = 2 * (1024)^4 byte = 2 Tbyte。符号付きの場合はその1/2)。どちらにせよ、通常のオフィスサプライ店で、それほど大きなファイルを格納できるディスクドライブを見たことはありませんけど。

SBATs

POIFSアーカイブに含まれるファイルが4096byteよりも小さい場合、小ブロック内に格納されます。小ブロックは長さ64byteを持ち、大ブロック(8個の大ブロックまで)の中に格納されます。メインのBATは大ブロックの配列をナビゲートするのに使われるのと同様、小ブロックのAllocation Table(SBAT)は小ブロックの配列をナビゲートするのに使われます。SBATの開始のインデックスはヘッダブロックのオフセット0x3Cで見つかります。また、SBATを構成するブロックの残りの部分は、POIFSファイルシステム内の通常のファイルであるかのように、メインのBATを巡回すると見つかります(このプロセスは以下で説明されます)。

Property Table Start Index

アドレス0x30にあるinteger値は、プロパティテーブルの始まりのインデックスを表します。このintegerは、"ブロックインデックス"として明記されます。プロパティテーブルは(殆ど全てのPOIFSファイルシステムがそうであるように)大きなブロック内にあり、BATを通じて巡回されます。プロパティテーブルに関しては、以下に記述されています。

プロパティテーブル

プロパティテーブルは、本質的にディレクトリシステム以上の何者でもありません。プロパティは512byteブロック内に含まれる128byteレコードです。最初のプロパティは常にルートエントリです。プロパティテーブル内の個々のプロパティは以下のようになっています:

  • プロパティのオフセット0x00は、"name"です。これは、圧縮されていない16bitのユニコード文字列として格納されています。一口に言えば、その他全てのbyteは、"ASCII"キャラクタに対応しています。この文字列のサイズは、short値でオフセット0x40文字列サイズ)に格納されています。
  • オフセット0x42は、プロパティのタイプ(byte)です。 タイプ1はディレクトリであり、2はファイルであり、5はルートエントリ用にあります。
  • オフセット0x43は、ノードのカラー(byte)です。カラーは1(黒)あるいは0(赤)です。プロパティは黒/赤のバイナリツリーで明白に配列されなければならないようになっており、以下のルールに従います:
    1. ツリーのルートは常に黒
    2. 連続する2つのノードは両方が赤にはなれない
    3. 名前の長さが他のプロパティの名前の長さより短いプロパティはそのプロパティの前にくる
    4. 2つのプロパティの名前の長さが同じであれば、ソート順番は、プロパティ名のソート順番によって決定される
  • オフセット0x44は、前のプロパティのインデックス(int)です。
  • オフセット0x48は、次のプロパティのインデックス(int)です。
  • オフセット0x4Cは、最初のディレクトリエントリのインデックス(int)であり、ディレクトリエントリによって使用されます。
  • オフセット0x74は、このプロパティによって表現されるファイルの開始ブロックを表すintegerです。このインデックスは、ファイルの最初のブロックのインデックス同様、BAT(あるいはSBAT)であるインデックス配列のインデックスに対応しています。ファイル及びルートエントリに使われます。
  • オフセット0x78は、このプロパティで指し示すファイルの実際のサイズの合計を表すintegerです。もしファイルサイズが4096よりも小さければ、ファイルは小ブロックで格納され、ファイルを組成するのに小ブロックを渡り歩く為にSBATが使われます。もしファイルサイズが4096以上の大きさであれば、ファイルは大ブロックで格納され、ファイルを組成するのに大ブロックを渡り歩く為にBATが使われます。この原則に対する例外は、ルートエントリであり、そのサイズにかかわらず常に大ブロックで格納され、この特別なファイルを組成するのに大ブロックを渡り歩く為にメインのBATが使われます。

ルートエントリ

プロパティテーブル内のルートエントリには、小さいファイル(4096バイト長以下のファイル)を読み込んだり書き込んだりするために必要な情報が入っています。ルートエントリの開始ブロックフィールドは、小ブロック配列のインデックスの開始点であり、POIFSファイルシステムの他のファイルと同様に読み込まれます。 SBATは小ブロック配列が無い限り使えないので、ルートエントリはBlock Allocation Tableを使って読み書きされなければなりません。小ブロック配列を構成するブロックは、64バイトからルートエントリで示されるサイズ(常に64の乗数であるべきです)までの小ブロックに分けられます。

プロパティテーブルのノードの巡回

個々のプロパティは、ルートエントリをディレクトリツリーのルートとしたディレクトリツリーを構成します(以下の図で表されているように)。各々のノードの括弧内の数値を押えておいてください;プロパティの配列のノードインデックスを表しています。NEXT_PROPPREVIOUS_PROPCHILD_PROPフィールドは、それらのインデックスを保持しており、ツリーをナビゲートするのに使われます。

property set

各々のディレクトリエントリ(即ち、種類がディレクトリ或いはルートエントリであるプロパティ)は、CHILD_PROPを用いてその下位の(子の)プロパティの一つを指し示します。それが指し示す子がどのようなタイプであるかは関係ないようです。ですから、上の図で言えば、ルートエントリのCHILD_PROPフィールドは1と4あるいはその他子ノードの一つのインデックスを有しているということです。同じように、ディレクトリノード(インデックス番号1)は、CHILD_PROPフィールドに2、3あるいはその他子ノードの一つを有してるでしょう。

所与のディレクトリプロパティの子は、同様に、NEXT_PROPPREVIOUS_PROPを使うことで、お互いを指し示します。

未使用のNEXT_PROPPREVIOUS_PROPCHILD_PROPフィールドは、マーカーの値として-1を持っています。例えば、全てのファイルプロパティは、CHILD_PROPフィールドに-1を持っています(訳注:ファイルには、子となるノードが無いので)。

Block Allocation Table

ヘッダに含まれる(及び、必要であればXBATブロックで追加される)BAT配列によって、BATブロックが指し示されます。これらのブロックは、integerの大きいテーブルを組成します。これらのintegerはブロック番号です。Block Allocation Tableはこれらintegerのチェーンを有しています。これらのチェーンは、-2の値で終了します。これらのチェーンの要素はファイル内のブロックを参照しています。ファイルの始まりのブロックはBAT内では指定されておらず、所与のファイル用のプロパティで指定されています。このBAT内の要素は、ブロック番号(ヘッダ以外のファイル内)及び次のBAT要素の数字が、チェーンを成すように入っています。ブロックのリンクリストであると思ってもらっても良いでしょう。BAT配列は一つのブロックから次々と連なっていくリンクを有しており、チェーンマーカーの「終了」も含みます。

ここで例を提示します:BATが以下のように始まると仮定しましょう:

BAT[ 0 ] = 2

BAT[ 1 ] = 5

BAT[ 2 ] = 3

BAT[ 3 ] = 4

BAT[ 4 ] = 6

BAT[ 5 ] = -2

BAT[ 6 ] = 7

BAT[ 7 ] = -2

...

さて。もしプロパティテーブルのエントリによりインデックスが0で始まると指示されているファイルがあるとすると、BAT配列を渡り歩き、ファイルはブロック0(始まりのブロックは0なので),2,3,4,6,7(BAT[0]=2;BAT[2]=3;BAT[3]=4;BAT[4]=6;BAT[6]=7と、チェーンを追っていくと分かる)で構成されていることがわかります。チェーンマーカーの「終わり」を表す数字である-2がBAT[7]にありますので、ブロック7で終了します。

同様に、インデックス1で始まるファイルは、ブロック1と5で構成されます(訳注:BAT[1] = 5 ; BAT[5] = -2 // 終了)。

BAT配列におけるその他特別な数値としては:

  • -1は、未使用ブロックをさします
  • -3は、”特別な"ブロック、例えばSmall Block Arrayやプロパティテーブル、メインのBATあるいはSBATといったブロックを構成するのに使われるブロックです。

ファイルシステム構造

以下、基本的なファイルシステムの概要を示します

Header (block 1) -- 512 (0x200) bytes

フィールド 説明 オフセット 長さ デフォルト値/定数
FILETYPE POIFSファイルシステムである、ということを特定するための魔法数 0x0000 Long 0xE11AB1A1E011CFD0
UK1 未知の定数 0x0008 Integer 0
UK2 未知の定数 0x000C Integer 0
UK3 未知の定数 0x0014 Integer 0
UK4 未知の定数 (履歴?) 0x0018 Short 0x003B
UK5 未知の定数 (バー