NSFItemInfoNext対応の悪影響
1年半前の記事で、「NSFItemInfoNext」関数についての対応策について書いた。
これについて、その後特に問題もなく順調にいっていたのだが、つい先日、とうとうこの対応が別の形で悪さをする事案に陥った。
以前の記事の概略
前回の対応をかいつまんで説明する
NSFItemInfoNext関数の第2引数はBLOCKIDという構造体を指定する。
typedef WORD BLOCK; /* pool block handle */ typedef struct /* Pointer to any block in any pool */ { DHANDLE pool; /* pool handle */ BLOCK block; /* block handle */ } BLOCKID;
ところが、DominoサーバWindows64ビット版においては、この「BLOCK」は2バイトの「WORD」型ではなく、4バイトの「DWORD」型を指すという事実だ。
#ifdef W64 typedef DWORD BLOCK; #else typedef WORD BLOCK; #endif
こうしないと、Win64においては必ずクラッシュする。幸い、Win32版、Mac版やLinux版ではこれを意識しなくてもいい。
今回発生した問題
このまま1年半使い続けてきたが、極めて限定的な条件の時にのみ、この対応がDominoサーバのクラッシュを生むらしい。私の認識している範囲で言えば、レプリカ直後の文書において、「特殊な」アクセスを試みたときに起こる(詳細は割愛する)。
NSFItemInfo関数は、文書内のアイテム情報(フィールド)を取得する。NSFItemInfoNext関数は、同一フィールド名の2番目以降を取得する。添付ファイルなどの特殊な場合を除き、1つのアイテムには64キロバイトの制限があるので、大きいサイズのフィールドはいくつかのブロックに分割して保存する。前出の「BLOCKID」はそれらを識別するための仕組みである。
ブロックからフィールドデータを得るためには、ブロックをロックして、ポインタを取得する。
void far * LNPUBLIC OSLockObject (DHANDLE Handle); #define OSLock(blocktype,handle) ((blocktype far *) OSLockObject(handle)) #define OSLockBlock(type,blockid) \ ((type far *)(OSLock(char,(blockid).pool) + (blockid).block))
OSLockObject関数でハンドルからポインタを得る。
OSLockマクロは、単に得られたポインタを指定した型のポインタにキャストする。
OSLockBlockマクロは、BLOCKIDのpoolハンドルから得たcharポインタから、blockバイト分移動した場所を指すようにして返す。
一般的な使い方であればこれで問題がなかった。しかし、「特殊なアクセス」でこのOSLockBlockマクロを使ってポインタを得ると、とんでもないことになる。
問題の根本は、BLOCKID.blockの型を2バイトから4バイトにしたことにある。特殊なアクセスをすると、この4バイトの上位2バイトに得体の知れない値「0xc000」が含まれてしまうのである。元々blockは2バイトなので、OSLockBlockマクロが64キロバイト以上移動した場所を指すことはない。しかし、NSFItemInfoNext関数の「実装バグ」対応でBLOCKID.blockを4バイトにしたWin64 C APIプログラムでは、4バイトのblock値を受け取ってしまう。上位に「0xc000」という値が入ってしまえば、OSLockBlockマクロはとんでもない場所をポインタとして返してしまう。もちろんこのポインタでメモリアクセスすれば、保護違反でクラッシュする。幸い今まで上位2バイトに0以外が入ったことがなかっただけで、0以外が入らない保証はなく、この危険性は大いにあったわけである。
DBLOCK、DBLOCKIDの存在
この事案に直面した折、あらためてC APIヘッダファイルを確認したところ、BLOCK、BLOCKID以外に4バイト版とも言うべき以下の型を発見した。いずれもpool.hヘッダファイルである。
typedef DWORD DBLOCK; /* dpool block handle */ typedef struct /* Pointer to any block in any pool */ { DHANDLE pool; /* pool handle */ DBLOCK block; /* block handle */ } DBLOCKID;
これらがWindows64ビット版で活用されてもいいところだが、残念ながら、APIツールキット9.0.1上では、これらは宣言されているだけで、NSFItemInfoNext関数などには全く利用されていない。
う〜む、またしても小手先で対応しなければならないではないか!
対応策
今回も、Windows64ビット版限定の対応とする。
// pool.hの54行目 #define OSLockBlock(type,blockid) \ ((type far *)(OSLock(char,(blockid).pool) + (blockid).block))
これを次のように書き換える。
#ifdef W64 #define OSLockBlock(type,blockid) \ ((type far *)(OSLock(char,(blockid).pool) + ((blockid).block & 0x0000ffff))) #else #define OSLockBlock(type,blockid) \ ((type far *)(OSLock(char,(blockid).pool) + (blockid).block)) #endif
BLOCKID.blockの値上位ビットをビットマスクでバッサリ切り捨てる。そもそもマクロやビットマスクの対応も、C++11以降のモダンC++の世界でどうかと思うが、まあこれはCのためのものなので、出しゃばったまねはしないようにする。
まとめ
クラッシュの原因特定まで時間を要した。「特殊なアクセス」という限定的な条件のときだけ起こるクラッシュ。その差分からとんちんかんな方向にも時間を費やした。最終的に、自分が書いた昔の記事に助けられた。こんな状況、海外も含めて同志がいないのもなかなかさみしいものだが、また一つ、自分の成長を実感できたことは嬉しい。
最後にもう一つ、HCLのNotes/Domino担当の皆さん、C APIのサポートってどうなっていますか?