高度な検索
Developer Connection
Member Login ログイン | ご入会 ADC連絡先


Technote 1089

HFS Elucidations Revisited


目次

常に 1 度だけクローズする

FCB レコードの上書きに関連するトラブルを防止する

.filename というファイル名は許されない

の TECHNOTE (オリジナルは「 FL 6 - HFS Elucidations 」) では、HFS (Hierarchical File System) を使用するときに発生する可能性のあるいくつかの問題を取り上げ、同時にそれらの問題を回避するための方法を説明します。

この TECHNOTE は、HFS に関連する問題のデバッグを行うデベロッパに役立ちます。これは、この TECHNOTE が最初に刊行されたときと同様、現在でも重要な問題です。

ここでは、次の問題について詳細な説明を行います。

  1. ファイルがどのようにオープンされ、またはどのようにクローズされるかということに注意を払うことは非常に重要です。ファイルをオープンするたびに 2 度以上クローズすることは厳禁です。

  2. ファイル名の代わりに .Bout、.Print、.Sony のようなドライバ名を使わないでください。そうしないと、システムが混乱する可能性があります。
これらの問題は、奇妙な現象の原因になると同時に、ユーザのトラブルの原因にもなります。これら究極の不法処理を実行しても、必ずしもシステムエラーが発生するわけではありませんが、場合によっては、発生した混乱がシステムエラーよりもはるかに悪い状況を招くことがあります。


常に 1 度だけクローズする

ファイルを 2 度クローズすると、ディスク上のファイルシステムを破壊してしまう可能性があります。ファイルシステムが現在オープンされているファイルへのアクセスパスを割り当てる方法を正しく理解していないと、ファイルのオープンとクローズに関して不作法な処理を行ってしまうこともあります。この TECHNOTE では、ファイルのオープンとクローズに関して十分に注意を払うことがなぜ必要なのかについて説明します。

Open 呼び出しを受け取るとき、File Manager はパラメータブロックの形で渡されたパラメータを見て、現在オープンされているファイルに対する新しいアクセスパスを作成します。アクセスパスは、File Manager が書き込まれたデータを送信する場所と、そのファイルから読み込んだデータを格納する場所を追跡する方法といえます。アクセスパスとは、まさに次のようなものです。

  1. ファイルシステムがデータの読み書きに使うバッファ。
  2. ファイルがディスク上に格納される方法を記述するファイルコントロールブロック (FCB)。
次のような呼び出しは、
ErrStuff = FSpOpenDF (fsspec, permission, firstRefNum);
バッファであり、かつ FCB バッファ内のファイルコントロールブロック (FCB) としてのアクセスパスを作成します。「FCB バッファ」という用語は大部分のマニュアルで使われていますが、これは実際にはバッファというよりも配列のように動作します。しかし、混乱を避けるため、この TECHNOTE では、従来通り「FCB 配列」ではなく「FCB バッファ」という用語を使います。

注意:
ここで示すサンプルは説明のためだけにあげてあると理解してください。このサンプルを実際に使用すると、将来のシステムソフトウェアとの間で互換性の問題が発生する場合があります。


FCBSPtr は、再配置できないブロックのアドレスを保持する下位メモリグローバル (0x034E) です。このブロックは FCB バッファであり、ブロック長を格納する 2 バイトのヘッダと、それに続く FCB レコードそのものから構成されています。このレコードは固定長であり、オープンファイルに関する詳細な情報を含んでいます。キューの構造は次のように図式化できます。

図1

この図からもわかるように、任意のレコードは、それよりも前にある FCB レコードの長さをブロックの先頭に加算し、2 バイトのヘッダに対応する 2 (つまり、レコード本体のオフセットを表す) を加算することによって取得することができます。ブロックのサイズ、つまり同時にオープンできるファイルの数は、システム起動時に決定され、その後は必要に応じて拡張されます。前述の fsspec によって参照されるファイルをオープンする呼び出しは、firstRefNum にファイル参照番号 (そのファイルへのアクセスパスを参照する) を返します。この番号は、これ以降、そのファイルにアクセスするために使用されます。File Manager は、FCB バッファへのオフセットを参照番号 (RefNum) として渡します。このオフセットは、キューの先頭からバッファ内にあるその FCB レコードまでのバイト数です。その FCB レコードは、オープンされたファイルを記述することになります。たとえば、RefNum として渡された数値が $1D8 であるとすると、これは、その FCB レコードが FCB ブロックの $1D8 バイト目にあることを意味します。

使用中のレコードと RefNum との関係は、次のように図式化できます。

図2

Base は、単純に FCB バッファである再配置できないブロックのアドレスを表します。FCBSPtr はこれを指しています。RefNum ($1D8 のような数値) は、Base に加算され、ブロック内のアドレスを表します。このアドレスは、ファイルシステムがオープンファイルの読み書きに使用するため、PBRead および PBWrite 呼び出しにはこの RefNum を渡す必要があります。このように、RefNum はバッファへのオフセットにすぎません。

次に、危険を伴う仮想的なシーケンスを実行してみて、FCB バッファ内の対象となるレコードに何が起こるかを見てみましょう。次のようなシーケンスを順に実行するとします。
ErrStuff = FSpOpenDF (fsspec, permission, firstRefNum);
ErrStuff = FSClose ( firstRefNum );
ErrStuff = FSpOpenDF (secondFileSpec, permission, secondRefNum);
ErrStuff = FSClose ( firstRefNum ); {不正なファイルがクローズされた!!!}
{上の行は、'FirstFile' ではなく 'SecondFile' をクローズする。'FirstFile' は既にクローズされている }
処理を実行する前、$1D8 のレコードは使用されていません。

図3

次の呼び出しを実行すると、
ErrStuff = FSpOpenDF (firstFileSpec, permission, firstRefNum);
firstRefNum = $1D8 となり、レコードは使用中になります。

図4

次の呼び出しを実行すると、
ErrStuff = FSClose (firstRefNum);
firstRefNum は $1D8 ですが、FCB レコードは使用されなくなります。

図4

次の呼び出しを実行すると、
ErrStuff = FSpOpenDF (secondFileSpec, permission, secondRefNum);
SecondRefNum = $1D8FirstRefNum = $1D8 となり、レコードは再使用されます。

図5

次の呼び出しを実行すると、
ErrStuff = FSClose (firstRefNum);
firstRefNum = $1D8secondRefNum = $1D8 となり、同時に FCB バッファの要素はクリアされます。この状況は、firstFile が既にクローズされている場合にも発生し、実際には secondFile がクローズされます。

図6

注意:
2 番目の Close では、元の RefNum が使用されます。2 番目の Close でもファイルはクローズされ、事実、実行結果として noErr が返されることになります。その後 secondRefNum にアクセスすると、ファイル 'secondFile' は既にクローズされているためエラーが返されます。ファイルコントロールブロック (FCB) は再使用され、それらはオフセットであるため、2 つの異なるファイルに対して同じファイル RefNum が取得される可能性があります。この例では、'secondFile' をオープンする前に 'firstFile' がクローズされ、同じ FCB レコードが 'secondFile' のために再使用されているため、firstRefNum == secondRefNum となります。


元の RefNum を再使用して 1 つのファイルを 2 度クローズする場合には、いくつかの厄介な問題が発生する可能性があります。通常のプログラミング作法では、エラーハンドラまたはクリーンアップルーチンを用意し、一部のファイルが既にクローズされている場合でも、プログラムが作成したファイルをチェックして、それらすべてをクローズする必要があります。FCB の要素が再使用されていないと、Close は期待通りの fnOpnErr を返します。一方、FCB が再使用されていると、Close は不正なファイルをクローズしてしまうことがあります。これは非常に危険な状況です。

特に厄介な例として、プログラムがファイルをクローズしようとしているときにユーザが HFS フロッピーディスクを挿入すると何が起こるかを考えてみましょう。FCB は、その HFS ディスク上のカタログファイルのために再使用されます。プログラムに、それが作成したファイルすべてをクローズするジェネリックなエラーハンドラが用意されている場合、誤って「その」ファイルをもう一度クローズしてしまいます。「その」ファイルがまだオープンしていると見なした場合、プログラムはそのファイルのクローズを実行し、HFS ディスク上のカタログファイルがクローズされてしまいます。この状況は、ディスクにとって致命的なものとなります。というのも、カタログファイルが矛盾した状態でクローズされることになるためです。その結果、このディスクは再フォーマットを必要とする不正なディスクということになってしまいます。

FCB レコードの上書きに関連するトラブルを防止する

非常にシンプルなテクニックとして、クローズを行うたびに RefNum を単純にクリアするという方法があります。クローズを行うたびにプログラムが使用していた変数をクリアすれば、そのプログラムでは RefNum を再使用できなくなります。次に、このテクニックの例を示します。

ErrStuff = FSpOpenDF (firstFileSpec, permission, firstRefNum);
ErrStuff = FSClose (firstRefNum);
firstRefNum = 0; { このファイルをクローズし、refnum はクリアされる }
ErrStuff = FSpOpenDF (secondFileSpec, permission, secondRefNum);
ErrStuff = FSClose (firstRefNum); { エラーを返す }
こうすれば、2 番目の Close はエラーを返します。この場合、2 番目の Close は RefNum = 0 をクローズしようとしますが、rfNumErr を返し、特に被害を与えることはありません。

注意:
必ず 0 を使うようにしてください。先頭の FCB エントリは、FCB バッファ長を格納したワードの次にあるため、0 が有効な RefNum になることはありません。ただし、この 0 と、Resource Manager がシステムファイルを表すために使う 0 とを混同しないでください。


このように、エラーハンドラがオープンファイルをできるかぎりクリーンアップすると、既にクローズされているファイルに対してはエラーが返されるため、既知のファイルをすべて確実にクローズすることができます。ただし、この処理は自動的に実行されるわけではありません。プログラマ自身が常にファイルのオープンとクローズに十分な注意を払う必要があります。たとえば、10 個のファイルのシーケンスをオープンしている最中にエラーを受け取った場合、問題はかなり複雑なものになります。クローズを行うたびに格納されている RefNum を単純にクリアしておけば、どのファイルがオープンしていて、どのファイルがクローズされているかをチェックするという複雑な処理を避けることができます。

.filename というファイル名は許されない

FSOpenPBHOpen、および PBOpen などの評判の悪い Open 呼び出しを使う場合は、ファイル名とドライバ名とがコンフリクトを起こす潜在的な可能性があります。ファイル名が .Bout、.Print、または .Sony などの場合、実行した呼び出しはファイルの代わりに対応するドライバをオープンしてしまいます。ドライバの方がファイルよりも優先順位が高いため、常にファイルよりも前に同じ名前のドライバがオープンされます。つまり、この種のファイルをオープンするときには、アプリケーションがエラーを受け取るということです。またなお悪いことに、呼び出しからドライバの RefNum が返されてしまいます。アプリケーションがファイルをオープンしようとして実行した呼び出しが、実際にはドライバをオープンする呼び出しになってしまいます。プログラムがこのアクセスパスをファイルの RefNum として使用すると、非常に奇妙な現象が発生することがあります。たとえば、.Sony がオープンされている場合、ファイルの RefNum の代わりに、Sony ドライバの RefNum が渡されます。アプリケーションがその RefNum を使って Write 呼び出しを実行すると、それは実際にはドライバ呼び出しになり、その時点でたまたまパラメータブロックにあったパラメータが使われます。このような処理を実行すると、ディスクは新しい対象を永遠に検索し続けます。プログラムがファイルを作成するときは、名前が '.' で始まるファイルの作成を許可しないようにしてください。

重要:
FSOpenDFPBHOpenDF のような新しい Open 呼び出しを使うときには、このような問題は発生しません。このため、FSOpen の代わりにこれらの新しい Open 呼び出しを使うことを強くお勧めします。

参考文献
Inside Macintosh: Files, Ch 2, File Manager
Technical Note FL22 - HFS Ruminations

更新日: 1996 年 12 月 20 日