052 調査結果は封印されました
●052 調査結果は封印されました(初稿 2023/04/04 19:03)
ジャーナルファイルって何でしょうか?
ファイルシステムを更新するには、ファイルシステムの特殊なファイルの書き換えが複数回必要な場合があって、その途中でファイルシステムが落ちた場合に致命的な障害とならないようにするための仕組み、...という程度には誰でも理解していますが、具体的にはどういう仕組みなのでしょうか。
例えば、新規ファイルの作成なら、アロケーションブロックに実データを書き込む他に、カタログファイルへの登録、アロケーションファイルの更新、ボリュームヘッダの更新を行うことになるはずです(...どの順番で行うかは知らないけど)。
これらの特殊ファイルの更新を行う前に、元のデータをジャーナルファイルに保管して、更新状態フラグ(...のようなものがあるのでしょう)をオンして、特殊ファイルの更新を順番に行い、更新が正常に終了したら更新状態フラグをオフにして更新終了とする。
ここで、もしも途中で電源断などで正常に終了しなかった場合、次にファイルシステムが起動した時に、更新状態フラグがオンであることを確認したら、ジャーナルファイルの元データで特殊ファイルを更新前の状態に復旧する、...のでしょうか(...HFS Plus での実装は TN1150 によれば、ちょっと違います!)。
これで新規ファイルは失われるとしてもファイルシステム全体が動かなくなることは避けられます。複数個のファイルを更新するからには、その途中で事故が起こることはありえることですし、事故が起きた時に何か修復作業を行わないとファイルシステムが動かない(...ファイルを読み出せない!)のは避けたいことですね。
ということは、ジャーナルファイルには特殊ファイルの更新前のデータが残存している可能性が考えられます。カタログファイルの様にゼロクリヤしていること(...[030]です)もありますが、確認してみるべきでしょう。
***
TN1150 のジャーナルファイルの説明を(頭から、読み飛ばさずに、落ち着いて)読み直したら、ジャーナルファイルには、特殊ファイルの変更前のデータを保管するのではなくて、更新後のデータを保管することになっていました!
つまり、特殊ファイルを更新する前に、ジャーナルファイルに更新後のデータを保管して、保管が終了したら、特殊ファイルの更新を行う。特殊ファイルの更新が正常に終了しなかった場合には、ジャーナルファイルのデータで(再度の)更新を行う、...のでした。これでファイルシステムは更新後の状態で復活します。
なお、ジャーナルファイルへの保管が正常に終わらなかったときは、特殊ファイルの更新は行わないのです。この場合には、新規ファイル等は失われることになります(OSやアプリ側で書込みエラーを検出して、リトライできる余地はあるの?)。
***
このHDDの場合、アロケーションブロックの #3A37h 〜 #D236h に、".journal" というファイル名で、「ジャーナルファイル」が作成されている。
".journal" ファイルはカタログファイルにも登録されているが、カタログファイルが壊れている場合でも大丈夫な様にアクセス手段が用意されている。ボリュームヘッダの +40ch 〜 40fh のデータ(journalInfoBlock)で ".journal_info_block" ファイルのブロック番号を得て、".journal_info_block" ファイルからジャーナルファイルのブロック番号 #3A37h とサイズ 9800000h バイトを得ることができるのです。9800000h/4096 = 9800h ブロックなのでファイルの末尾は #(3A37h+9800h-1) = #D236h と分かる。
5つの特殊ファイルと同様にボリュームヘッダにエクステント情報を格納すればもっと直接的だと思いますが、ジャーナルファイルの仕組みが導入されたのは、HFS Plus ができた後なので、そんな変更は出来なかったのでしょうか。
ジャーナルファイルの先頭ブロック #3A37h は「ジャーナルヘッダー」です。具体的には次のデータが格納されている。あと 30h〜FFFh は全て 00h です。+2Ch からの 4 バイトは謎ですね。
+00h : 78 4c 4e 4a __ __ __ __ : magic (="JNLx")
+04h : 78 56 34 12 __ __ __ __ : endian (=12345678h)
+08h : 00 b0 0d 01 00 00 00 00 : start (=010db000h)
+10h : 00 b0 0d 01 00 00 00 00 : end (=010db000h)
+18h : 00 00 80 09 00 00 00 00 : size (=09800000h)
+20h : 00 10 00 00 __ __ __ __ : blhdr_size (=4096)
+24h : 6a 11 0f 09 __ __ __ __ : checksum
+28h : 00 10 00 00 __ __ __ __ : jhdr_size (=4096)
+2Ch : 61 2c 9e 00 __ __ __ __ : ?
この中で、start と end の2つのデータが重要です。この2つのデータが更新状態を示すのです。start == end ならば更新状態フラグはオフで、start != end ならばオンなのです。そして、start から end の間が特殊ファイル等のデータの保管場所です。
このHDDは(...ファイルを意図に反して削除してしまったとしても)、正常に更新終了しているので、start == end です。...ですが start とかの場所を確認して見ましょう。start の 010db000h とはジャーナルファイルでの相対バイト数のことで、アロケーションブロック番号では、#(3A37h+10DBh) です。ジャーナルファイルのブロック番号で +10DBh とも言えます。なお、endian のデータがリトルインデアンであることを示しています。
この start の指すブロックは「ブロックリストヘッダー」です。
先頭 16 バイトは固定の領域、残りは可変数個の「ブロック情報」です。ブロック情報の後の空いた部分は 5Ah で埋まっています(...+C0h〜+FFFhまでの全部が 5Ah です)。
# block list header
+00h : ff 00 __ __ : max_blocks (=255)
+02h : 0b 00 __ __ : num_blocks (=11)
+04h : 00 20 01 00 : bytes_used (=12000h = 4096*18)
+08h : 2f d6 c4 3e : checksum
+0Ch : 03 00 00 00 : pad (=3)
+10h : 0000000000000000 00000000 e91b9e00 : block info (= 0h, 0h, ?)
+20h : ee14000000000000 00100000 c5bb1144 : block info (= 14eeh, 1000h, ?)
+30h : c9880d0000000000 00200000 b3d894e6 : block info (= d88c9h, 2000h, ?)
+40h : 0000000000000000 00100000 88f83a7b : block info (= 0h, 1000h, ?)
+50h : 38d2000000000000 00100000 4d9f3b01 : block info (= d238h, 1000h, ?)
+60h : 49840d0000000000 00200000 7a14d124 : block info (= d8449h, 2000h, ?)
+70h : 39820d0000000000 00200000 b28ca17d : block info (= d8239h, 2000h, ?)
+80h : 7d8e0d0000000000 00200000 c7d3e5b5 : block info (= d8e7dh, 2000h, ?)
+90h : ad8e0d0000000000 00200000 5336ac9a : block info (= d8eadh, 2000h, ?)
+A0h : 37820d0000000000 00200000 e01598fe : block info (= d8237h, 2000h, ?)
+B0h : b1880d0000000000 00200000 0817d296 : block info (= d88b1h, 2000h, ?)
+C0h : 5a5a5a5a5a5a5a5a 5a5a5a5a 5a5a5a5a
+D0h : 5a5a5a5a5a5a5a5a 5a5a5a5a 5a5a5a5a
+E0h : 5a5a5a5a5a5a5a5a 5a5a5a5a 5a5a5a5a
+F0h : 5a5a5a5a5a5a5a5a 5a5a5a5a 5a5a5a5a
ブロックリストヘッダーの num_blocks がブロック情報の個数を示します。ブロック情報は1個 16 バイトで最大 255 個なので、ちょうど1ブロックに収まります(...16 + 16 * 255 = 4096)。
ブロック情報には3個の情報があります。1つ目は保管データのアロケーションブロック番号で8バイト、2つ目は保管データのバイト数で4バイトです。見た感じ 4096 の整数倍になっています。3つ目の情報は4バイトありますが、何か分かりませんでした。
このブロックの場合、ブロック情報は 11 個あります。
+B0h の アロケーションブロック番号 #D88B1h はカタログファイルです。(0xd88b1 - 0xd8237)/2 = 829 番のノードです。2ブロックを保管しています。+A0h のアロケーションブロック番号 #D8237h はカタログファイルの 0 番のノードですからヘッダーノードですね。私には分かります。
+50h の #D238h はエクステントオーバーフローファイルです。1ブロックです。
+40h の #0h はボリュームヘッダーで1ブロックです。
+20h はアロケーションファイルで1ブロックですね。
アロケーションファイル > カタログファイル > ボリュームヘッダー > エクステントオーバーフローファイル > 他に影響を受けたカタログファイルのノード達、...という順番でしょうか。順番は関係ないかも。
+10h のブロック情報は、他のブロック情報とは違うそうです。TN1150 の説明によれば、一度のファイルシステムの操作で 256 個以上の更新がある場合の情報らしいです。そんな更新が必要とは思え無かったのですが、もしも、ですね、もしも、ですが、数千個のファイルを含むフォルダを削除したら、あるかも知れないですね。そんな場合に必要なのかも知れません。恐ろしいです。
ブロック情報の2つ目の情報(保管データのバイト数)を合計すると 17 ブロック分(= 17*4096 バイト)で、これとブロックリストヘッダーを含むブロックを合わせると bytes_used の値になります。そして、start が示す 010db000h に bytes_used の値を加えると、次のブロックリストヘッダーが見つかるはずです。
それと、このブロックリストヘッダーに続く 17 ブロックは特殊ファイルの更新時のデータ保管場所で、ブロック情報の順番で、+20h で1ブロック目、+30hで2ブロック目と3ブロック目、+40h で4ブロック目、...+B0h で 17 ブロック目です。
ジャーナルファイルは 9800h ブロックあって、先頭の +0h のブロックはジャーナルヘッダーでしたね。
+1h〜+97FFh がブロックリストヘッダーのブロックと、保管場所のブロックです。ジャーナルファイルの末尾(+97FFh)の次は、+1h のブロックにつながります。リングバッファ的な奴ですね(...あれ、とすると現在 start の示すブロックがブロックリストヘッダーなのは、偶然では?)。
数えてみると、ブロックリストヘッダーは 4226 ブロックあり(うち9個が pad=1 のヘッダー)、保管データが 34685 ブロックありました。ジャーナルヘッダーが1つあって、4226 + 34685 + 1 = 0x9800 ですね。
***
+10h のブロック情報について、少し詳しく調べてみました。
+10h の1つ目の情報が 0 ではない場合が幾つかあります。その場合、次のブロックリストヘッダーは +0Ch の値(pad の場所)は 3 ではなく 1 になります。pad の値は違いますが、他のデータを見る限り、これもブロックリストヘッダーであるようです。
+10h のブロック情報は、ブロック情報の個数が 256 個以上になる等で、1つのブロックリストヘッダーに収まらない場合のための情報らしいのです。もしかしたら、+10h の「1つ目」の情報に 0 以外の値をセットして、ブロック情報に続きがあることを示す、...のかも知れません(...TN1150 には +10h の「3つ目」にセットすると書いていて少し違います、...悩むなー)。
それと、pad=3 はブロック情報の先頭であることを意味し、pad=1 は続きのブロック情報であることを意味する、のかも。TN1150 は単に境界合せのリザーブ領域としか説明していませんけど。すると +10h の情報が 0 だとブロック情報の末尾を意味するのか。
+10h の3つ目の情報だけを表示すると、カウンタか通番かのように前の値に +1 された値が続きます。pad=1 のときだけ前と同じ値です。これはファイルシステムの更新回数を意味するのでしょうか。
+10h 以外の3つ目の情報は、チェックサムのような気配を感じました。あとで確認しましょう。
+10h がある理由は、ジャーナルファイルの保管データでファイルシステムを復旧する際に、まとめて処理すべきデータを示すため、のように思われますが、それとジャーナルヘッダーの start と end の役割との違いが分かっていません。うーん、ファイルシステムの復旧中に、再度の障害が発生した場合のことを考慮したのでしょうか?
***
友「ちょっと、これで良いのですか、謎が増えている、分析が足りていません」
私「確かに、そうだけど、先に進みたいの」
***
最も重要なことですが、#(3A37h+010DBh) に続く、データの保管場所である 17 ブロックにはデータが残っていました!
ボリュームヘッダーの保管データをダンプしてみましょう。
+40h のブロック情報はアロケーションブロック番号が #0h となっていてボリュームヘッダーです。対応する保管データは4番目のブロックです。つまり、#(3A37h+010DBh+4) のアロケーションブロックを読み出します。読み出したデータは [030] で調べたときのスクリプトを使えば直ぐに確認できます。
x = read_allocation(0x3A37+0x010DB+4,1)
d = s_decode(x, s_VolumeHeader)[0]
print d
すると、+400h からのシグネチャやバージョンは正しい値が表示された。+414h からの 'modifyDate' や +430h からの 'freeBlocks' はファイルシステムの更新日時や空き容量で、これらもそれらしき値が出てきました。ボリュームヘッダーのデータで間違いなさそう。
ジャーナルファイルを順番に調べて、見つかったボリュームヘッダーから情報を集めて、日付と空き容量を図にしたのが次の Fig.J1 です。
横軸は日付で、縦軸はHDDの空き容量です。12/8 に実験データをまとめて書き込み、空き容量が 12 %程減っています。2TB の 12 %だと 240GB です。それが...。
そうです、それは 12/11 の朝でした。
ボリュームヘッダーの更新日時によれば、6時58分から空き容量が増え始めて、7時0分には削除が終了したようです(...目が霞んで、秒の桁が読み取れません)。
その後、12/14 の2時59分にファイルシステムをマウントしたことが記録されています。
***
私「嗚呼...、辛い記憶が、あの時、...辛い。...あれ、でも、ということは、カタログファイルの情報は?」
友「辛いのね、この記憶は封印しますね」
私「...アグ、アグ、Zzz」
:
:
:
***