Image: AX日本語MS-DOSと2バイトコードを含むファイルパスの問題点

5年前、いわゆる日本語PC/AT互換機『AX』についての技術解説書『AXテクニカルレポート』(翔泳社、1988年)を読んでいたときに一つの疑問があった。AXのMS-DOSは英語と日本語のバイリンガルになっていて、再起動することなくMODEコマンドで英語モードと日本語モードを切り替えることができるが、その解説書 (p.66) にある次の文が気になっていた。

なお、2バイトコードのパス名の処理は、本来のMS-DOS英語バージョンではサポートされていませんので、英語モードはこのパス名が扱えるように英語バージョンのMS-DOSの機能が拡張されたものになっています。

つまり、日本語のファイルパスを扱うために、オリジナルの英語版MS-DOSに何らかの改変を加える必要があったことになる。だが、何を拡張したのか肝心な部分は書かれていない。私自身、ファイルパスに2バイトコードを含む問題点を今まで考えたことがなく、そこに興味がわいてしまった。

シフトJISコード第1バイト “E5h” の問題

最初に思い付いたのがこの問題だった。MS-DOSはファイルを削除するとき、ディレクトリエントリの先頭(ファイル名を示す8バイトのうち最初の1バイト)にE5hを書き込む仕様になっている。MS-DOS 2.11のソースコードによると、MS-DOS 1.25より前にリリースされたPC DOS 1.xでは、E5hが未使用のディレクトリエントリを示すコードとして使われていた。MS-DOS 1.25以降では最初のディスクフォーマット時に00hが書き込まれるようになり、未使用のディレクトリエントリは00h、削除済みのディレクトリエントリはE5hが書かれるようになった。

なぜE5hなのか。これはCP/M時代からの慣習で、その頃の新品の8インチフロッピーディスクはディスク全体がE5hで埋められていたらしい。幸いにも今はオンラインの文献が充実していて、これについて容易に調べることができる。Western Digital FD1791やIntel 8271などのフロッピーディスクコントローラーのデータシートを読むと、FormatコマンドはIBM 3740フォーマットと互換性を持つフォーマットで初期化し、データ領域にこのパターンを書き込むとしている。E5hはIBM機で用いられたEBCDIC文字セットでは文字’V’を表すが、それがどういう意味を持つか分からない。もっとも、”Inside CP/M” (CBS College Publishing、1982年) p.241という文献によると、この値はIBM標準の単密度ディスケットに用いられていたが、IBM標準の倍密度ディスケットでは4Eh (‘+’)という異なる値が使用されている。一方、CP/Mでは単純にE5hを未使用と判断しているため、IBMの倍密度ディスケットはCP/Mでは「ディスクに空きがない」と判断され、CP/M用に改めて初期化しないと使用できないとのこと。

本題に戻ると、シフトJISコードの2バイト文字は、第1バイトが81hから9FhおよびE0hからFChとなっており、E5hも含まれている。第1バイトがE5hから始まる文字コードにはJIS第二水準の漢字が収録されている。私にはどう発音すればいいのか分からない漢字ばかりだ。これらの漢字の使用頻度は非常に低いだろうが、このままではE5hから始まる漢字がディレクトリ名やファイル名として使えないことになる。

そこでMS-DOSでは、E5hから始まる名前を05hに置き換えてディスクに保存し、ディスクから読み出すときは05hをE5hに置き換えている。以下、MS-DOS/DEV.ASM at master · microsoft/MS-DOS · GitHubより。

; Inputs:
;       DS,ES:DOSGROUP
;       Filename in NAME1
; Function:
;       Determine if file is in list of I/O drivers
; Outputs:
;       Carry set if name not found
;       ELSE
;       Zero flag set
;       BH = Bit 7,6 = 1, bit 5 = 0 (cooked mode)
;            bits 0-4 set from low byte of attribute word
;       DEVPT = DWORD pointer to Device header of device
; Registers BX destroyed
        IF      KANJI
        PUSH    WORD PTR [NAME1]
        CMP     [NAME1],5
        JNZ     NOKTR
        MOV     [NAME1],0E5H
NOKTR:
        ENDIF

MS-DOS 2.11の時点でこのようなコードが含まれているということは、かなり早い段階でこの問題は対処されていたようだ。ただ、このOAKのソースコードでは “IF KANJI” というプリプロセッサがあることから、欧米圏でリリースされたMS-DOSはこの置換処理を含んでいないと予想される。

私が可能な範囲で調べた限りでは、英語版PC DOS 3.3やWindows XPで作成したMS-DOS起動ディスク(Windows Meの英語環境起動ディスクと同等)はこの置換処理を含んでいることが判明した(実験結果は後述)。また、Windows NT系ではこの処理が言語を問わず標準で行われていることが、WDK (Windows Driver Kit) のサンプルコード Fast FAT から分かる。

#define FAT_DIRENT_REALLY_0E5            0x05
    //  If there was a base part, copy it and check the case.  Don't forget
    //  if the first character needs to be changed from 0x05 to 0xe5.
    if (BaseLength != 0) {
        RtlCopyMemory( OutputString->Buffer, Dirent->FileName, BaseLength );
        if (OutputString->Buffer[0] == FAT_DIRENT_REALLY_0E5) {
            OutputString->Buffer[0] = (CHAR)0xe5;
        }

MS-DOS 3.2以前については分からない。MS-DOS 3.2英語版がサポートしていなかったとすれば、例の記述はAXのMS-DOS 3.21がこのコードを含むようビルドされたことを指すのかもしれない。

情報源は忘れたが、NEC PC-98版MS-DOS 3.1の一部のバージョンにはこの処理が正しく行えない不具合があったと聞いたことがある。MS-DOS 3.1と言っても、私が知っているだけで4つのバージョンと2つのバグフィックスがあり、どれが該当するのか調べるのは難しそう。PC-98版MS-DOS 2.11(一太郎Ver.3付属のもの)では問題ないことを確認できた。

シフトJISコード第2バイト “5Ch” の問題

5ChはASCIIコードでは\(日本語環境では¥)となり、MS-DOSではパス区切りとして認識される。シフトJISコードは第2バイトに5Chを含む文字があるため、2バイト文字かどうか判定しながらパスを処理する必要がある。次はMS-DOS 2.11のOAK (FCB.ASM) より、2バイト文字を判断するプロシージャ。

        IF      KANJI
        procedure   TESTKANJ,NEAR
        CMP     AL,81H
        JB      NOTLEAD
        CMP     AL,9FH
        JBE     ISLEAD
        CMP     AL,0E0H
        JB      NOTLEAD
        CMP     AL,0FCH
        JBE     ISLEAD
NOTLEAD:
        PUSH    AX
        XOR     AX,AX           ;Set zero
        POP     AX
        return

ISLEAD:
        PUSH    AX
        XOR     AX,AX           ;Set zero
        INC     AX              ;Reset zero
        POP     AX
        return
TESTKANJ  ENDP
        ENDIF

第1バイトが81hから9FhおよびE0hからFChとなるのは、あくまで日本語版のMS-DOSが使用するシフトJISコードの話であって、韓国語や中国語などの他の文字コード体系(コードページ)では変わる可能性がある。しかし、この時点のMS-DOSは数字をコードに埋め込んでおり、複数のコードページに対応することまで考えていなかったようだ。MS-DOS 2.25と3.21では、これをAPIで取得および設定するためのファンクション INT 21h, AX=6300h(DOS の DBCS ベクタを取得)などが追加されている。これらのファンクションはMS-DOS 4.0以降で標準的にサポートされる。

MS-DOS 3.21の英語モードは日本語のファイルパスを処理できない

例の文面を読んで「AXのMS-DOS 3.21は英語モードで日本語のファイルパスを扱えるのか?」という疑問が生じた。英語モードでは2バイト文字はグラフィック文字などに文字化けして表示されるが、バッチやアプリケーションでファイルのオープン、クローズ、コピー、削除などの操作はできるようになっているのかしらと思った。実際には、この考えは間違っていることが判明した。

以下の名前のファイルを作成するバッチを各環境で実行してみる。

  1. (((1.txt 81 69 81 69 81 69 31
  2. ソソソ2.txt 83 5C 83 5C 83 5C 32
  3. ヤヤヤ3.txt 83 84 83 84 83 84 33
  4. 漾漾漾4.txt E0 40 E0 40 E0 40 34
  5. 蕁蕁蕁5.txt E5 40 E5 40 E5 40 35
1 2 3 4 5 テスト環境
O O O O O NEC MS-DOS 2.11 (T98-NEXT上)
O O O O O NEC MS-DOS 3.3C (T98-NEXT上)
O O O O O 沖電気 MS-DOS 3.21 日本語モード
d X d O O 沖電気 MS-DOS 3.21 英語モード
d X d O O IBM PC DOS 3.3 (英語版)
O O O O O IBM DOS J5.0/V 日本語モード
d X d O O IBM DOS J5.0/V 英語モード
d X d O O Windows Me MS-DOS(英語環境)起動ディスク

まず、2番の ‘ソ’ (83 5C)はパス区切りの\を含むため、英語環境ではパスが正しくないというエラーが返ってくる。それは既に分かっているとして、問題は1と3だ。英語環境では1の「(((1.txt」は「唔唔唔1.TXT」として、「ヤヤヤ3.txt」は「A拶拶・.TXT」として作成される。なぜこんなことになるのか。

MS-DOSではファイルパスに含まれるアルファベットの小文字は大文字に変換される。また、日本語版のMS-DOSはデフォルトの文字セットとしてコードページ932(シフトJISの実装の一つ)を使用するのに対し、英語版MS-DOSはコードページ437を使用する。コードページ437にはabcなどのラテン文字だけでなく、âといったフランス語などで使われるアクセント符号付きラテン文字が含まれていて、これも大文字に変換される。

‘(’ 81 69はCP437では”üi”となり、これは大文字の”ÜI” 9A 49に変換される。シフトJISでの’唔’になる。また、’ヤ’ 83 84はCP437では”âä”となり、これは大文字の”AÄ” 41 8Eに変換される。シフトJISでは41は1バイト文字の’A’となり、その次の8Eは2バイト文字の第1バイトと解釈されるため、以後第1バイトに1バイト文字が出てくるまでは文字の解釈が1バイトずつずれる。

このような小文字から大文字への変換があるため、コードページの指定が誤っているとファイルパスが意図したものと異なる文字に変換されることがある。この問題は2バイト文字セットに限らず発生する。

小文字が大文字に変換される仕様は、これもまたCP/Mに由来する。CP/MのCCP(Console Command Processor, CP/Mにおけるコマンドプロンプトの役割を持つ)は入力された文字を全て大文字として解釈する仕様になっていた。MS-DOSと異なるのは、アプリケーションからは小文字を含むファイル名を扱えたことである。ただ、CCPからコマンド引数として小文字を含むファイル名を指定できないという問題がある。

話は逸れるが、上記5つのファイルに対して “del *.txt” を実行してファイルを削除したときの挙動が異なるのが興味深かった。DOSの英語モードでは5つのファイルを全て削除できたのに対し、Windows MeのMS-DOSモード起動ディスクでは2番のパス区切りを含むファイル(ソソソ2.TXT)を削除できなかった。

件の文面が伝えたかったこととは?

改めて本の文章を読み直してみる。

MODEコマンドで行われるモード切り換えは、表示や入力方法、コマンドの実行を管理する外部インターフェースと、コード体系や内部テーブルなどの内部インターフェースに影響します。たとえば英語モードでは、漢字ファイル名の表示などはグラフィック文字で表現されますので、英語モードと日本語モードを比較した場合、日本語モードで設定したMS-DOSシステムの動作環境のすべてが、英語モードで継承されるわけではありません。
なお、2バイトコードのパス名の処理は、本来のMS-DOS英語バージョンではサポートされていませんので、英語モードはこのパス名が扱えるように英語バージョンのMS-DOSの機能が拡張されたものになっています。
逆に、AX用MS-DOSの英語モードで設定したMS-DOSシステムの動作環境はすべて日本語モードに継承されます。

結局の所、この文面はそれ以上の意味を持たなかった。ただ私が裏を読みすぎた、あるいは勝手に推測を加えて読み間違えただけだった。

この書き手が暗に伝えたかったことは次の2点だ。

  1. AX用MS-DOS 3.21は日本語モードと英語モードで共通のカーネル (IO.SYS, MSDOS.SYS) を使用している。
  2. AX用MS-DOS 3.21のカーネルは、2バイトコードのパス名をサポートしたことを除けば、英語版MS-DOS (PC/AT互換機用) のカーネルと同等である。

これは2バイトコードのパスをMS-DOSの内部的に使用可能になったという話であって、ファイル名がどのモードでも同じように扱えることを意味するものではない。ファイルシステムでファイル名にUnicodeが使われるようになったのはWindows NTのNTFSからで、それ以前の場合はコードページがファイル作成時のものと一致している必要がある(Windows 9xは長いファイル名でサポート)。A-ZなどASCIIコードのほとんどの文字はどのコードページも共通で持っているが、コードページ932ではさらに2バイトコードとの切り分けが必要だ。これに加え、DOSの場合はファイルパスが大文字に変換されるため、コードページが正しくないと内部表現(バイトコード)すらMS-DOS内部で意図せず変わる可能性がある。

そして、日本語版MS-DOS 3.21における英語版MS-DOS 3.2に対する機能の拡張とは、2バイトコード判定のサポートと、パス区切り文字 E5hの問題への対応(これは以前から対応してた可能性あり)である。

参考文献


※コメント欄が表示されない場合はdisqusについてJavascriptが有効であることを確認して下さい.