4からFirefoxやThunderbirdへの移行のお手伝いもしています。今回は、NC4.5からThunderbirdへの移行の際に特に問題となった、NC4.5のユーザプロファイル一覧の取得方法の調査の解説を通じて、クリアコードでのオープンソース製品のサポート業務の一例をご紹介したいと思います。本題の前に、まずは背景事情を説明しましょう。Messenger)の子孫と言えますが、製品としてはNC4の後継ではありません。そのため、ThunderbirdのNC4からの移行支援の対象は非常に限られており、実際に「設定とデータのインポート」機能ではNC4のユーザ情報からはローカルに保存されたメールしかインポートできません。それ以上の情報をインポートしようと思うと、現状ではそのための仕組みを独自に用意する必要があります。この時に問題になる事の1つとして、NC4のプロファイルの一覧やそれぞれのプロファイルの内容の保存場所をどのように取得すればよいのか?という事が挙げられます。Data")内にユーザごとの設定を保存するのが当たり前になっています。昔のWindowsは「1台のPCを使うのは1人だけ」という前提で設計されていたため、複数人で1台のPCを共用しているなどの理由で複数のユーザ設定を使い分けたい場合には、アプリケーションの側で独自にマルチユーザ対応のための仕組みを持つ必要がありました。以下に保存されるようになっています。サブフォルダを辿るだけでは見落としが発生してしまいます。すべてのプロファイルをもれなく把握するためには、そのPC上にあるプロファイルの一覧を取得するための正しい手順を踏まなくてはなりません。3までの間でそのAPIは削除されてしまったので、今回は代わりの方法を考える必要がありました。いうバイナリファイルに情報が保存されるように仕様が変更されました。よって、NC4.5以降からThunderbirdへの移行を支援するためには、このバイナリファイルに立ち向かう必要があります。前置きが長くなりましたが、このバイナリファイル(nsreg.dat)はどのような形式になっていて、どのようにすれば内容を読み取る事ができるのか、というのがこの記事での主題となります。nsreg.datの内容を読み解くヒントを探すバイナリファイルの内容自体は、以下のようにしてバイト列として読み込む事ができます。bytes;}この関数によって読み込まれた内容は、1バイト(8ビット)単位の内容がNumberとして格納されたArrayとして返されます*1。後はこの単純なバイト列の中に埋め込まれた「意味」を読み取ればよいという事になります。nsreg.datの内容について解説したドキュメントは無いものかと思いまずは簡単に検索してみましたが、断片的な情報はいくつか見つかるものの、ちゃんとした技術情報には辿り着けませんでした。ファイルの中身を見てみる事にしました。このファイルをバイナリエディタ*2で開くと、「ProfileLocation」やプロファイルの位置と思われる文字列がNULL文字(終端文字)を伴う形で、一定の規則に則って埋め込まれている事が見て取れます。そこで、最初はこれらを単純にパターンマッチングで取り出してプロファイルの一覧として列挙するという事を試みました。実際に使い込まれた環境のnsreg.datで検証すると、この方法では同じ名前のプロファイルが何度も列挙されるといった結果になってしまいました。nsreg.datの内容は新しい情報が追記されていく一方で古い情報には削除フラグが立てられるだけという作りになっている模様で、単純なパターンマッチングでは余計な情報が大量にヒットしてしまう事があるようです。どこが削除フラグなのか、そもそもどこからどこまでが正確に1つの情報の単位なのか、という事をここから探り当てるのは非常に困難です。Seamonkeyのソースツリーの中からnsreg.datのパースに関する処理を探し出す事にしました*3。探索にあたって、まず単純にMXRで「nsreg.dat」を文字列検索してみたところ、nsDogbertProfileMigratorというファイルが見つかりました。MozillaプロジェクトではNetscape製品の事は何故かDogbertと呼ばれていたようなのですが、このモジュールはその名の通り、NC4のプロファイルを移行するための諸々の処理を実装しているようです。この中でさらにプロファイルの一覧を取得しているらしい関数を探して中身を見てみた所、今度はreg.h(ヘッダファイル)とreg.c(実装)というさらに低レベルのAPIに辿り着きました。nsDogbertProfileMigrator、もしくはさらに低層のAPIだけをコンパイルしてバイナリ形式のコンポーネントにするという手も無くはないのですが、ビルドのための環境を整えるのには手間がかかりすぎる。2.0以降)では色々と面倒が多い。何より、インターフェース的にもあまり使いやすい物では無さそうである。いう事から、今後*4のためにも、これに相当する使いやすいモジュールをJavaScriptで実装し直してみる事にしました。nsDogbertProfileMigrator.cppは2200行程、reg.cは4100行程あって、全部を読んで把握するというのはちょっと面倒です。実際のnsreg.datとソースコードの両面から、重要そうと思われるポイントに絞って調査を進めていきました。nsreg.datのレジストリエントリ先のソースコードにおけるnsreg.datの読み取り処理と、実際のnsreg.datの内容とを比較しながら見ていったところ、ファイルにはdescriptionと呼ばれる単位でレジストリのエントリが保存されている模様である事が分かりました。descriptionの中身は以下のようになっているという事も分かりました。description全体の長さ:32バイト1バイト目〜4バイト目(location):そのdescriptionの先頭の位置(符号無し32ビット整数)5バイト目〜8バイト目(name):「名前」の先頭の位置(符号無し32ビット整数)9バイト目〜10バイト目(nameLength):nameの長さ(符号無し16ビット整数)11バイト目〜12バイト目(type):ノードの種類のフラグ(符号無し16ビット整数)13バイト目〜16バイト目(left):同じ階層にある次のノード(兄弟ノード)にあたるdescriptionの先頭の位置(符号無し32ビット整数)17バイト目〜20バイト目(down):最初の子ノードにあたるdescriptionの先頭の位置(符号無し32ビット整数)21バイト目〜24バイト目(value):「値」の先頭の位置(符号無し32ビット整数)25バイト目〜28バイト目(valueLength):valueの長さ(符号無し32ビット整数)29バイト目〜32バイト目(parent):親ノードにあたるdescriptionの先頭の位置(符号無し32ビット整数)それぞれの値は8ビット毎のリトルエンディアン形式*5で格納されており、下位の8ビットから順に並べ替える事で実際の値を得られました。実際にnsreg.datの中をもう一度見てみた所、プロファイルの位置と思われる文字列の近くに32ビットの長さのバイナリ部があり、先頭の4バイトがそのバイナリ部の位置そのものを示している事を確認できました。nameやvalueが指している位置にはUTF-8バイト列として文字列が格納されており、終端文字まで含めた長さがnameLengthおよびvalueLengthの長さとして格納されていました*6。前述の「削除フラグ」は、この中のtypeという部分に含まれていました。この部分で特定のビットが立っていると、そのdescriptionは削除済みであるということになるようです。nsreg.datの中にはツリーの形で情報が保存されており、left、down、parentの各フィールドからツリー構造を辿っていける模様である事も分かりました。DOM的にnsreg.datの内容を見るための実装と、ここまで分かった所で、このdescriptionをDOM風のオブジェクトとして扱うための実装を試験的に作成してみる事にしました。バイト列をJavaScriptの数値や文字列(UCS2)に変換するユーティリティを定義します。表現されている数値を得られる。UTF-8バイト列としての文字列に一旦変換する。decodeURIComponent(escape(aUTF8Octets));}次に、これらのユーティリティを使って、「値の内容」「次のノード」などを自ら見つけ出すようなDescriptionクラスを試験的に作成しました。保持しておかないといけない。ここでは位置の情報だけを保持しておく。位置と長さの情報だけを保持しておく。両方の可能性を考慮する。オブジェクト的な表現での実態に即しているから。deletedなdescriptionは実質的には無い物として扱うバージョン。あるのかどうかはまだ分からないが、念のため。deletedでない子ノードを収集する。あるのかどうかはまだ分からないが、念のため。無限ループに陥ってしまわないよう、念のため。それを簡単に取得するためのユーティリティ。}};これを先のreadBinaryFrom()と組み合わせれば、description単位での調査はずっと容易になります。nsreg.datの全容ここまでの調査の過程で、nsreg.datの中には全てのユーザ情報のルートとなるエントリが存在しており、nsDogbertProfileMigratorのGetSourceProfiles()というメソッドの中でそれを起点としてその子ノードを走査しているらしいという事が分かっていました。いう事は、その「ユーザ情報のルート」から芋蔓的にプロファイルの一覧を見つけられる(しかも削除済みのレジストリエントリを除外した形で)と考えられます。残る問題は、そのレジストリエントリがどこにあるのかという事です。ファイルの先頭から32バイトが全体のルートになるdescriptionなのでは?とも一瞬考えましたが、実際のファイルの先頭には先頭のアドレス(0000)とは似ても似つかない値が入っていたので、この可能性は無いようです。この調子で2バイト目から順番に見ていってもきりが無いですし、そもそもそれで見つかったとしても常にその位置に最初のdescriptionがあるとは限らないので、ここはやはり既存の実装を見るのが一番確実そうです。number」といったコメントが付いた、REGHDRといういかにも怪しい名前の構造体の定義がある事に気がつきました*8。MAGIC_NUMBERという定数の定義を見ると、nsreg.datの先頭4バイトがまさにこのMAGIC_NUMBERと一致しています。REGHDRの中でrootというフィールドにあたる位置(先頭から13バイト目~16バイト目)に書き込まれていた値をdescriptionの位置として参照してみた所、全体のルートとなるdescriptionらしき内容に行き当たりました(このdescriptionの子ノードの中には先の「ユーザ情報のルート」となるdescriptionが含まれている事も確認できました)。駄目押しで既存の実装から「REGHDR」を参照している箇所を探してみた所、レジストリファイルをオープンする処理の中で固定のオフセットとしてファイルの先頭からREGHDR分の長さだけ内容を読み込んでくるという処理も見つかりました。これがルートと見て間違いなさそうです。残りの実装これでようやく、nsreg.datの中を機械的に辿ってユーザプロファイルの一覧を取得する目処が立ちました。後は不足している部分を実装するだけです。nsreg.datの内容全体をバイト列として渡すとルートにあたるレジストリエントリを返す関数を定義します。bytesToNumber(root));}次に、それを使ってユーザのプロファイル一覧(名前とパスの組)を取得して返す関数を定義します。profiles;}最後に、システムのnsreg.datを自動検出してgetProfilesFromBinary()に渡し、その結果を返す関数を定義します*9。[];}以上で必要な実装は揃いました。後はこれらを1つのファイルにまとめてJavaScriptコードモジュールにしておけば、今後またNC4からの移行案件があっても安心して対応できますね。Seamonkeyのソースツリーから見つけられる事、そしてDOM的なアプローチでのパーサの再実装の流れを紹介しました。これ自体は非常に限定的な局面でしか役に立たない情報ですが、クリアコードのMozillaサポート事業やオープンソースソフトウェアのサポート事業では実際にどのような事をやっているのか?という事例紹介*10も兼ねて記事にしてみた次第です。古い製品であってもソースコードさえあれば、後の時代からでもこのように目的を達成できる可能性が高くなります。ソースが無いと、ここまですんなりとはいかなかったでしょう(本格的なリバースエンジニアリングをしないといけないとなると、どれだけの手間がかかるか考えたくもないですね)。「クローズドソースのパッケージ製品を重宝していたにも関わらず、提供元が倒産等で存在しなくなってしまい、もはやサポートを受けられなくなってしまった」という状況に陥ってしまう将来的なリスクを非常に重く見る場合、オープンソースな製品を導入するというのも現実的な選択肢の1つなのではないでしょうか。使った方が良いかも知れませんね。Ubuntu上で「Hexエディタ」を使いました。Suiteに含まれていました。Seamonkeyとして開発が継続されています。8ビットを超える長さのデータを表現する際の表現形式で、8ビットを1桁と考えた時に上の桁から順に格納する方式をビッグエンディアン、下の桁から順に格納する方式をリトルエンディアンと言います。ビッグエンディアンは人間にとって分かりやすく、リトルエンディアンはコンピュータにとって分かりやすいという利点がそれぞれあります。valueLengthは0になっている場合があり、その場合はvalueの位置から32バイトの内容がまた別のdescriptionになっている、という構造になっていました。定数値の中でも、それ単体を見ても意味が全く分からない上に、他に何の説明も無い物。一意な識別子として使われる事がある。「hdr」はThunderbirdのソース中では「header」または「handler」の意味で使われている事が多いです。OS/UNIXの3プラットフォームに対応できそうです。純粋に調査のみの依頼の場合もあれば、今回のように移行案件の中で必要に迫られて行う場合もあります。4.0とGFDLのデュアルライセンスで自由に利用できます。クリアコードはプログラミングが楽しいソフトウェア開発者を1名募集しています。クリアコードは「クリアコードをいい感じにする人」を1名募集しています。クリアコードはフリーソフトウェア開発で培った技術力を提供しています。Thunderbird)とRubyとGroonga(全文検索)に関連した開発を得意としています。