Classic 環境とも呼ばれる Mac OS 9 までのストレージは、System 3.1 の頃より、長い間に亙り MacOS 標準フォーマット(HFS)と呼ばれるファイルシステムによって管理されていた。 HFS はスケールに幅のあるファイルシステムであり、容量の小さいフロッピーディスク(FD)から容量の大きいハードディスク(HD)まで広く用いられていた。 その一方で、FAT で有名な様に、長い間使用された事によってサイズの大きいデータファイルを取り扱えないなどの HFS の設計の古さが顕在化しつつあった。 それらの問題を解決するため、Mac OS 8.1 において、HFS を改良した MacOS 拡張フォーマット(HFS+)が導入されたが、依然として HFS は利用され続けた。 Mac OS X が誕生してからも、古い Mac ユーザは FD や MO といった旧来のディスクからデータを取り出して使用する機会も多かったが、 Mac OS X v10.5 Leopard で起動ディスクに HFS が用いることができなくなった事を皮切りに、Mac OS X v10.6 Snow Leopard では HFS のストレージに書き込みができなくなるなど、 非推奨化が推し進められ、macOS Sierra v10.12 からはいよいよ HFS のストレージの読み取りができなくなり、macOS Catalina v10.15 からは HFS のイメージファイルのマウントもできなくなった。 HyperCard や ClarisWorks の書類など Classic 環境でのみ用いる事ができるデータもあるが、Excel や Illustrator、画像データといった現在でも読み取れるデータが存在している場合もある事だろう。 ここではそういった過去の遺産を抱えている HFS から HFS+ へと移行する方法について纏める。

HFS イメージの作成と HFS+ イメージへの変換

HFS のサポートは macOS Sierra v10.12 において打ち切られ、HFS イメージは最新の macOS Catalina v10.15 からはファイルシステムが認識されなくなった。従って、まず古いストレージを Mac に接続し HFS イメージの作成を行い、データを移行する必要がある。 認識できないファイルシステムであっても OS は接続さえできれば、マウントこそできないが、そのデータを読み取ることができる。最新の macOS でも SCSI のシステム機能拡張(KEXT)は残されているため、FD、MO、CD、HD とあらゆるストレージが利用可能であり、一部を除き、標準規格のおかげでほぼ全てのデバイスを macOS に接続することができる。 macOS に接続した後は HFS イメージにアーカイブし、HFS イメージが無事完成したとしても、日本語ファイル名が含まれていると、ファイル名が文字化けする。ファイルシステムとして文字エンコードを正しく処理する KEXT により処理する際には OS X El Capitan v10.11 までの OS が必要である。 古い OS が用意できない場合はスクリプトを用意する必要性があるため、それらの対処方法について論じる。また文字エンコードを正しく処理した後、ファイル属性値を可能な限り保全した上で、HFS イメージから HFS+ イメージへ変換する方法について検討する事にする。

アーカイブのための接続方法の検討

アーカイブしたいストレージに適した接続方法の検討が重要である。

SCSI HD:

SCSI 接続の HD は、入手困難ではあるが、対応する SCSI カードに接続することでアーカイブ化可能である。特に ATTO 製の SCSI カードは Apple の OEM 製品だったために標準ドライバが存在し、インストール不要で利用できるという。 ATTO ExpressPCI UL5D は PCI Express なので最新の macOS でも Thunderbolt 経由などで利用することができるだろう。ATTO ExpressPCI UL4D, UL3S などの SCSI カードは Power Mac G4, G5 などがある場合に利用できる。 SCSI は無数の端子形状が存在していた。SCSI カードによってはピンの変換が必要であるが、amazon.co.jp などで売られているアダプタの中には配線が間違っているアダプタもあるため、接続ピンの確認が必要である。

IDE HD:

UltraATA など IDE 接続の HD は、Seagate 製など一部 HD の相性問題はあるが、アキバや日本橋でんでんタウンなどのパーツショップで手に入る IDE/SATA 変換基盤を用いる事で簡単に接続できる。

MO:

MO の場合は、ドライバ不要の SCSI 機器であれば SCSI カードを用いる方法があるが、ATAPI 接続の内蔵ドライブを使用する方法がより簡便である。 ATAPI 接続の内蔵ドライブは IDE/SATA 変換基盤によって SATA 接続が可能になる場合があり、実際にATAPI 接続の富士通製 MO ドライブの MCR3230AP と IDE/SATA 変換基盤によって最新の macOS において MO の読み取りが可能である事を確認している。

FD:

FD の場合は、現在入手可能な適当な USB 接続のドライブによって最新の macOS でも認識可能であるが、FD の形式によっては認識できないので注意が必要である。 現在入手可能なドライブでは Classic 環境用の FD は 2HD 形式の 1440K の FD 以外は読み取ることができない。この問題はハードウェア由来のためソフトウェアで解決することができず、古い Macintosh が必要である。 Macintosh では可変速スピンドルによる ZCAV (Zone Constant Angular Velocity) 方式で記録され、GCR (Group Code Recoding) 方式での符号化を採用していた。この方法は FD を効率よく使用することで容量を増やせる利点があり、1DD 形式や 2DD 形式の FD において採用された。 PC 互換機では固定速スピンドルの CAV (Constant Angular Velocity) 方式で記録され、MFM (Modified Frequency Modulation) 方式での符号化を採用し、Macintosh においても 2HD 形式の FD において採用され、1440K の FD にはハードウェア上の互換性が生まれた。 実際この様な違いから、2DD 形式の場合、現在 USB 接続のドライブで読み取り可能な FD は、CAV 方式で記録する 720K で初期化された FD であり、Macintosh 内蔵ドライブでは ZCAV 方式方式で記録する 800K で初期化された FD が使用され、互換性の問題が生じることとなった。 Apple は複数の方式に対応することから Macintosh 内蔵の FD ドライブを SuperDrive と呼称していたが、SuperDrive 互換の USB 接続のドライブは市販されていない。読み取り可能なコンピュータを調べるには、Apple が公開している古い Macintosh のデータシートを参照せねばならない。 内蔵ドライブが SuperDrive 400K/800K など書かれていれば、2DD などの FD が読み取れる可能性がある。PowerBook G3 "Wallstreet" までは 2DD 800K といった FD の読み取りができたという。 また System 7.6.1 以前のシステム用の 1DD 形式 400K の FD では MFS (Macintosh File System) というより古いファイルシステムが使われている可能性もある。 互換性の問題が明確な場合、古い Macintosh は AppleTalk の設定も面倒で接続速度も遅いことから、HD に複製した上で HD から取り出した方が簡単である。 古い FD は磁気記録が読み取れなくなっている可能性も高いため、アーカイブ対象の FD が古い場合は、実機を用意し、問題が互換性にあるかどうか切り分けられる様にしておいた方が良いだろう。

HFS イメージの作成によるアーカイブ化

何らかの方法で macOS にストレージを接続できた場合、いよいよ HFS イメージの作成が可能となる。但し場合によっては HFS フォーマットであったとしても macOS にとって認識できないフォーマットであると表示され、初期化するかどうか訊かれることがある。この場合はこの警告を無視しておく必要がある。 /dev/disk1, /dev/disk2 などの BSD 名を確認すれば、任意の場所に dd コマンドでストレージの内容を吸い出すとよい。 BSD 名は Disk Utility.app や df コマンド、hdiutil コマンドなどで確認できる。例えば disk1 を吸い出すには次のようにする。

dd if=/dev/disk1 of=~/backup.img bs=16m conv=sync,noerror
dd if=/dev/disk1 ibs=16m conv=sync,noerror | ssh user@domain dd obs=16m of=backup.img

実行には管理者権限が必要であり、/dev/disk1 の排他制御が必要な為にマウントされている場合はマウント解除が必要である。Disk Utility.app や umount コマンドで実施可能である。 二つのコマンド列は似た内容であるが、後者はパイプを使うことで ssh で別のサーバにデータを転送する方式である。ifof を間違えるとデータが上書きされてしまうので注意が必要である。

bs オプションは block size の略であり、dd コマンドで取り扱うセクタサイズを指定するもので、デフォルトは 512、単位は Bytes である。16m と書けば 16 MB を意味し、4k と書けば 4 KB を意味する。 セクタサイズは入力の ibs と出力の obs に分けて指定でき、ストレージの物理セクタサイズやフォーマットの論理セクタサイズと異なっていても良い。 因みに物理セクタサイズは一般に HD の場合は 512 Bytes か 4 KB、MO の場合は 512 Bytes か 2 KB、FD の場合はほぼ 512 Bytes である。
conv オプションは、エラーを抑制する c と、読み取りエラー時に読み取れなかった箇所からセクタサイズに到達するまで 0 で埋める sync を指定している。conv は古いストレージはセクタエラーが発生しやすいので必要である。 sync の意味は、喩えば dd コマンドのセクタサイズが 4 KB の時 1025-1536 Bytes 目で読み取りエラーが発生すると読み取れた 1-1024 Bytes 目はそのままで 1025-4096 Bytes 目までが 0 で埋め尽くされる。 セクタエラーが発生している場合は bs オプションを取りやめて 512 を指定した方が消失するデータが少なくて良い。
また場合によりパーティションの違いを表す disk1s1 などが存在し、各パーティションに対しても dd コマンドは実行可能であるが、今の場合はストレージ全体を指す disk1 に対して実施すれば良い。

backup.img の作成はストレージの容量次第でそれなりの時間を要する。作成後は次の hdiutil コマンドを実施することによって適宜イメージのサイズを圧縮することができる。

hdiutil convert backup.img -format UDZO -o compressed-backup.dmg
hdiutil convert backup.img -format Rdxx -imagekey encrypted-encoding-version=1 -o compressed-backup.img

前者は macOS 向けの zlib により圧縮する UDZO 形式と呼ばれる形式で compressed-backup.dmg を作成している。後者は Classic 環境の Disk Copy 6.3.3 向けに圧縮する Rdxx 形式と呼ばれる形式で compressed-backup.img を作成している。 いずれも読み取り専用のイメージファイルとなる。SheepShaver などの仮想 Classic 環境の運用を考えている場合は Rdxx 形式のイメージファイルを別途保管しておくことをオススメする。 この様にして作成した Rdxx 形式のイメージファイルが Classic 環境で使えないという話もあり、実際その場合は Disk Copy によって「イメージのマウントは完了できませんでした。」といったエラーが表示される。 その理由は、man hdiutil に互換性情報として記載されているが、イメージファイルの符号化方式はバージョン 2 に更新され Mac OS X v10.5 Leopard 以降は全て自動的にバージョン 2 が選択されているためである。 従って Classic 環境や Mac OS X v10.2 Jaguar 以前との互換性を考える場合は -imagekey オプションでバージョン 1 の符号化方式を明示しておく必要性がある。またイメージファイルにする場合は、パーティションマップが存在しているとマウントに失敗する点も注意が必要である。 HFS にせよ HFS+ にせよ Mac OS X で作られたイメージファイルを Classic 環境で認識させる場合、hdiutil create -layout NONE ... とパーティションマップのないイメージを作った上で Rdxx 形式に変換せねばならない。 2 GB を超える場合は Rdxx 形式に変換できないため、2 GB を超える HFS をイメージ化する場合は HFS が作成できた Mac OS X v10.5 Leopard までの Mac OS X で HFS の分割を行う必要がある。 macOS の Disk Utility.app で作成される圧縮形式のイメージファイルは互換性のためか UDZO 形式が取られているようであるが、hdiutil ではより新しい圧縮形式も選択できる。 なお man hdiutil によれば、このコマンドでは macOS Catalina v10.15 において次の -format オプションが選べる。

hdiutil コマンドで指定できるイメージファイル形式

UDRW
UDIF read/write image
UDRO
UDIF read-only image
UDCO
UDIF ADC-compressed image
UDZO
UDIF zlib-compressed image
ULFO
UDIF lzfse-compressed image (OS X 10.11+ only)
ULMO
UDIF lzma-compressed image (macOS 10.15+ only)
UDBZ
UDIF bzip2-compressed image (Mac OS X 10.4+ only)
UDTO
DVD/CD-R master for export
UDSP
SPARSE (grows with content)
UDSB
SPARSEBUNDLE (grows with content; bundle-backed)
UFBI
UDIF entire image with MD5 checksum
UDRo
UDIF read-only (obsolete format)
UDCo
UDIF compressed (obsolete format)
RdWr
NDIF read/write image (deprecated)
Rdxx
NDIF read-only image (Disk Copy 6.3.3 format; deprecated)
ROCo
NDIF compressed image (deprecated)
Rken
NDIF compressed (obsolete format)
DC42
Disk Copy 4.2 image (obsolete format)

さて、次説からはこの様にして作成した UDZO 形式のイメージファイル compressed-backup.dmg (以下では単に backup.dmg と呼ぶことにする) をマウントし、そこから HFS+ のイメージを作成する方法を考えていくことにする。

HFS イメージと文字コードを考慮したマウント方法

HFS イメージを使用する場合に頻発する問題として文字コードの問題がある。HFS では使用していた環境によって埋め込まれている文字コードが異なっている。 HFS+ や最新の APFS では Unicode を使用しているため、NFD 問題など正規化の問題はあっても、使用環境によって文字化けすることはないが、HFS では Unicode とは異なる Apple による文字コードが使われている。 日本語環境では MacJapanese エンコーディングという Shift_JIS の亜種が用いられており、喩えば三点リーダー…や囲み文字①など Shift_JIS にはないが需要のあった文字が追加されているため、絵文字同様、Shift_JIS として単純に処理することができない。

HFS で MacJapanese エンコーディングを利用できる macOS

文字化け問題に簡単に対処する方法は macOS に用意されている処理系を利用する方法である。Mac OS X では HFS を MacJapanese で処理するための KEXT として HFS_MacJapanese.kext が用意されていた。 OS X Yosemite v10.10 までは /System/Library/Filesystems/hfs.fs/Encodings に、OS X El Capitan v10.11 以降は /System/Library/Filesystems/hfs.fs/Contents/Resources/Encodings に存在しているが、macOS Sierra v10.12 以降では HFS_MacJapanese.kext を利用することができない。このことは次のコマンドを管理者権限で実行することでわかる。

kextutil -tn /System/Library/Filesystems/hfs.fs/Contents/Resources/Encodings/HFS_MacJapanese.kext

このコマンドを実行すると KEXT の読み込み試験が行われるが、macOS Sierra v10.12 以降では次の内容の警告を出力して終了する。

kxld[com.apple.kext.HFS_MacJapanese]: The following symbols are unresolved for this kext:
kxld[com.apple.kext.HFS_MacJapanese]: 	_hfs_addconverter
kxld[com.apple.kext.HFS_MacJapanese]: 	_hfs_remconverter
Link failed (error code 5).
Check library declarations for your kext with kextlibs(8).

このエラーはライブラリなどから HFS の函数が削除されていることを意味しており、macOS Sierra v10.12 以降では HFS_MacJapanese.kext を利用することができないことを明確に示している。 従って、日本語環境のイメージから文字化けすることなくデータを取り出すには、OS X El Capitan までの OS を用意するか MacJapanese を変換する仕組みが必要である。ここでは OS X El Capitan までの OS を利用してコマンドなどで逐次処理する方法を紹介する。 新しい OS しかない場合は文字化け前提でマウントしてから Ruby などのスクリプトで変換するか、Apple が用意している File Name Encoding Repair Utility.app を利用するといいだろう。

以上の問題に加え、アラビア語 HFS_MacArabic.kext が macOS Catalina v10.15 から削除されるなど、サポート終了に伴ってデータも減ってきている。実際、macOS Catalina からは HFS イメージのマウントもできなくなってしまったため、データ移行が必要であることは明らかである。

スクリプトを用いた HFS における文字化けの修正方法の考察と修正コードのサンプル

HFS のファイル名は MacJapanese でエンコードされているが、その修正は少し面倒である。macOS がストレージを認識する際に MacJapanese を UTF-8 に変換してしまうからである。 例えば「あ」は MacJapanese では Shift_JIS と共通で 0x82A0 とエンコードされているが、macOS はその文字を UTF-8 で認識して「dž」と出力し 0xC387E280A0 とエンコードする。 「Ç」は UTF-8 では 0xC387 であり Unicode では U+00C7 である。「†」は UTF-8 では 0xE280A0 であり Unicode では U+2020 である。 この挙動はかなり奇妙であり、0x82A0 を直接 UTF-8 と解釈したわけでも、Unicode と解釈したわけでもないようである。しかも面白いことに 2 バイト文字の筈が 5 バイトに増えてしまっている。 他の平仮名も 4 バイトに増えたりしているようである。このことは HFS に記録されているファイル名は 1 バイト単位で記録されており、それを多バイト文字に解釈していると考えることができる。その仕組みを考えるにあたり、Apple が公開している HFS の実装が参考になる。 hfs_japanese/hfs_japanese.kmodproj/hfs_japanese.c には、次のようにある。

int
MacJapaneseToUnicode(Str31 hfs_str, UniChar *uni_str, UniCharCount maxCharLen, UniCharCount *usedCharLen)
{
	UInt32 processedChars;

	processedChars = __CFFromMacJapanese(kCFStringEncodingUseCanonical | kCFStringEncodingUseHFSPlusCanonical,
					&hfs_str[1],
					hfs_str[0],
					uni_str,
					maxCharLen,
					usedCharLen);

	if (processedChars == (UInt32)hfs_str[0])
		return (0);
	else
		return (-1);
}

int
UnicodeToMacJapanese(UniChar *uni_str, UniCharCount unicodeChars, Str31 hfs_str)
{
	UniCharCount srcCharsUsed;
	UInt32 usedByteLen = 0;

	srcCharsUsed = __CFToMacJapanese(kCFStringEncodingComposeCombinings | kCFStringEncodingUseHFSPlusCanonical,
					uni_str,
					unicodeChars,
					(UInt8*)&hfs_str[1],
					sizeof(Str31) - 1,
					&usedByteLen);

	hfs_str[0] = usedByteLen;

	if (srcCharsUsed == unicodeChars)
		return (0);
	else
		return (-1);
}


__private_extern__ int
hfs_japanese_start(kmod_info_t *ki, void *data)
{
	int result;

	result = hfs_addconverter(ki->id, kCFStringEncodingMacJapanese,
			MacJapaneseToUnicode, UnicodeToMacJapanese);

	return (result == 0 ? KERN_SUCCESS : KERN_FAILURE);
}

__private_extern__ int
hfs_japanese_stop(kmod_info_t *ki, void *data)
{
	int result;

	result = hfs_remconverter(ki->id, kCFStringEncodingMacJapanese);

	return (result == 0 ? KERN_SUCCESS : KERN_FAILURE);
}

文字列を処理する型はそれぞれ利用される形態に応じた実装が存在している。Str31 型は HFS のファイル名で用いられるもので、HFS+ では Unicode に対応した HFSUniStr255 型を用いる。UniChar 型は Core Foundation で御馴染みの CFString クラスにおける実装で、その中身は Unicode である。 -hfs_japanese_start-hfs_japanese_stop を呼び出している箇所を見ると、函数ポインタを設定することでエンコードの変更に対応していると見られる。 -MacJapaneseToUnicode-UnicodeToMacJapanesehfs_japanese/hfs_japanese.kmodproj/JapaneseConverter.c における実装によってエンコード MacJapanese から Unicode へ変換・逆変換する。 -__CFFromMacJapanese を呼び出す際に HFS の文字列ポインタから &hfs_str[1] というポインタを文字列として、hfs_str[0] を文字列の長さとして渡している。これは文字列の先頭にその文字列の長さを記載する Pascal 文字列と呼ばれる実装で、HFS ではファイル名は Pascal 文字列で記録されることになっていると云う。 更にその先の -__CFFromMacJapaneseCore などを読み進めてみると、多バイト文字の「あ」の場合は -ShiftJISToJIS0208 によって 0x2422 に変換される。これは当時 JIS X 0208 を参照していたようである。最終的には埋め込み表の __CFFromJIS0208CharMap によって 0x2422 から 0x3042 に変換される。0x3042 は「あ」の Unicode である U+3042 と一致している。 ここまで見てきても「あ」が「dž」と文字化けする理由ははっきりとしない。UTF-8 の表現「dž」は JIS X 0208 とも一致しないバイト列になっているからであり、この過程ではそもそも文字列のバイト数が増える実装にはなっていない。 これらの疑問を解消するきっかけは hfs_encodings/hfs_encodings.c を参照することで与えられる。HFS のエンコードを変更する際に -hfs_addconverter-hfs_remconverter を呼び出している。これらは初期化ルーチンにおいて次のように呼び出されている。


void
hfs_converterinit(void)
{
	SLIST_INIT(&hfs_encoding_list);

	encodinglst_lck_grp_attr= lck_grp_attr_alloc_init();
	encodinglst_lck_grp  = lck_grp_alloc_init("cnode_hash", encodinglst_lck_grp_attr);
	encodinglst_lck_attr = lck_attr_alloc_init();

	lck_mtx_init(&encodinglst_mutex, encodinglst_lck_grp, encodinglst_lck_attr);

	/*
	 * add resident MacRoman converter and take a reference
	 * since its always "loaded". MacRoman is the default converter
	 * for HFS standard volumes.
	 *
	 * Only do this if we are actually supporting HFS standard 
	 * volumes. The converter is not used on configurations 
	 * that do not support HFS standard. 
	 */
	hfs_addconverter(0, kTextEncodingMacRoman, mac_roman_to_unicode, unicode_to_mac_roman);
	SLIST_FIRST(&hfs_encoding_list)->refcount++;
}

-mac_roman_to_unicode-unicode_to_mac_roman は HFS において文字コードに MacRoman を選んだ場合の変換用の函数であり、HFS の初期設定は MacRoman であったから、文字化けしている場合はこの函数が使用されているとわかる。 注目すべきはエンコードの処理方法を変更する -hfs_addconverter-hfs_remconverter の呼び方と実装である。MacJapanese に切り替える場合と MacRoman の場合とを比べても特別なことは何もしていない。 従って「あ」が「dž」と文字化けする場合、その仕組みとして次のものを仮定することができる。macOS は HFS のファイル名を解釈する場合に MacRoman であると解釈する。システム上のファイルパスは Unicode で処理されるため、MacRoman は Unicode に一度変換される。Unicode は必要に応じて UTF-8 に変換され、我々が目にすることになる。 このように考えると、MacRoman では「Ç」が 0x82 と「†」が 0xA0 とエンコードされるため、繫げると 0x82A0 である。これは MacJapanese の「あ」の 0x82A0 となることから説明がついた。従って次のように処理する。

#!/usr/bin/ruby -Ku

fallbackTable = {
	"\u2318" => "\x11".force_encoding("MacRoman"),
	"\u2713" => "\x12".force_encoding("MacRoman"),
	"\u2666" => "\x13".force_encoding("MacRoman"),
	#"\uF8FF" => "\x14".force_encoding("MacRoman"),
	"\u03A9" => "\xBD".force_encoding("MacRoman"),
	"\u20AC" => "\xDB".force_encoding("MacRoman"),
	"\uF8FF" => "\xF0".force_encoding("MacRoman")
}

string = "dž"
string.encode!("MacRoman", "UTF-8", {:fallback => fallbackTable})
string.force_encoding("SJIS")
string.encode!("UTF-8", "SJIS")
print string

このサンプルコードは Ruby で書かれており、コードは UTF-8 で保存されることを予想している。このコードを実行すると「あ」と出力される筈である。初めの "UTF-8" は HFS の NFD などの正規化処理を反映した "UTF-8-MAC" を使用した方がいい可能性があるが詳細は確認していない。 半角片仮名のファイル名などを変換すればわかるが、Ruby の実装では一部の文字、「 (U+F8FF)」など Mac OS 以外では表示が難しい字などは UTF-8 から MacRoman に変換できないので、明示的に変換表を与えて解決している。 変換表において 「 (U+F8FF)」は二つ存在し MacRoman には 0x14 ないし 0xF0 へ変換されるとされているが、ここでは 0xF0 への変換を採用した。不具合があれば 0x14 の方を採用すればいいだろう。 また SJIS 即ち Shift_JIS を指定しているが、実際には MacJapanese を正しく変換できないので注意が必要である。MacJapanese を処理する場合は Ruby Gem の mac_japanese が必要である。 value = MacJapanese.to_utf8(value.force_encoding("ASCII-8BIT")) などとすると良いだろう。詳しくは次説で触れる。

古い Macintosh で利用していた NAS のストレージについて

文字化けの問題は古い環境で利用していた NAS のストレージにおいてもみられる問題である。NAS の場合は、Linux 系 OS で処理されていることが多く、HD は ext2 など macOS で取り扱えないフォーマットになっていることが多い。 NAS の HD を取り出してイメージファイルを作成し、仮想環境で構築した Linux 系 OS に接続して NFS で macOS と共有してデータを取り出す方法が考えられる。macOS とわざわざ共有する理由は resource fork が失われてしまう問題があるためである。 /Volumes/NFS の MacJapanese を UTF-8 化して出力する Ruby のサンプルコードを以下に示した。Ruby では Encoding::MacJapanese が定義されているが変換テーブルは用意されていないため、MacJapanese を処理できる Ruby Gem の mac_japanese が必要である。 この Gem をシステムにインストールせず .gem を .gz とみなして展開しスクリプトと同じディレクトリに配置しても使える様にもなっている。適宜書き換えて使用するといいだろう。

#!/usr/bin/ruby -Ku

sharedTarget = "/Volumes/NFS"

class String
	$:.unshift File.dirname(__FILE__)
	require "mac_japanese"
	
	def UTF8StringAsEscapedMacJapaneseEncoding(sequence = ':')
		sequenceCode = sequence.ord
		string = ""
		
		i = 0
		j = 0
		while i < length
			code = self[i].ord
			if code == sequenceCode then
				string[j] = self[i+1,2].to_i(16).chr
				i = i + 2
			else string[j] = code.chr end
			i = i + 1
			j = j + 1
		end
		
		# return string.encode(Encoding::UTF_8, Encoding::Shift_JIS)
		return MacJapanese.to_utf8(string)
	end
	
	def stringByResolvingEscapesInPath(sequence = ':')
		components = split("/")
		components.map!{|component| component.UTF8StringAsEscapedMacJapaneseEncoding(sequence).split("/").join(":") }
		return components.join("/")
	end
	def stringByResolvingEscapesInPathOfDeletedLastPathComponent(sequence = ':')
		return stringByDeletingLastPathComponent.stringByResolvingEscapesInPath(sequence)
	end
	def stringByResolvingEscapesInPathOfLastPathComponent(sequence = ':')
		return lastPathComponent.stringByResolvingEscapesInPath(sequence)
	end
	
	def stringByAppendingPathComponent(component)
		return [ self, component ].join("/")
	end
	def stringByDeletingLastPathComponent
		return File.dirname(self)
	end
	def lastPathComponent
		return File.basename(self)
	end
end

directoryPaths = [ sharedTarget ]

while ! directoryPaths.length.zero?
	directoryPath = directoryPaths.pop
	
	#p directoryPath.stringByResolvingEscapesInPathOfLastPathComponent
	
	Dir.glob("#{directoryPath}/*"){|filePath|
		if FileTest.directory?(filePath)
			directoryPaths.push(filePath)
		else
			unless filePath.stringByResolvingEscapesInPath == filePath
				print filePath.stringByResolvingEscapesInPath
			end
		end
	}
end

このコードではファイル名が NAS のシステムによって適宜エスケープされている場合を想定している。手元の NAS では ext2 で使用できない文字は全て所謂 Quoted-printable の亜種として実装された方式でエスケープされていた。 Quoted-printable では "=" を使うところを ":" とする実装である。Mac OS ではファイル名に "/" が使われていた場合にディレクトリと区別するため ":" へ置換されるようになっている。この置換は ":" が Mac OS のディレクトリの区切りとして使われていた名残であり、実際に AppleScript では HFS 形式のパスとして古式ゆかしく使用できる。 ":" は予約文字となっており、ファイル名に使うことはできないため、エスケープに使用できると考えられたのであろう。この仕様が NAS の制御システムのものか、NAS のファイル共有プロトコルの仕様かはわからない。 この NAS では、喩えば「空」という漢字は Shift_JIS では 0x8bf3 に該当するので、ファイル名としては「:8b:f3」として処理された。「品」という漢字は Shift_JIS では 0x8a77 であるが、0x77 は ASCII コードの「w」に等しいので、ファイル名としては「:8aw」として処理された。 .DS_Store などの "." で始まるファイルは「:2e」とエスケープされている一方で、.jpg など拡張子の "." はエスケープされていないなど、挙動にも僅かに違いがある。resource fork と区別している可能性がある。 また "/" は ":" へと置換することなく「:2f」とエスケープされて保管されているため、"/" を ":" に戻す処理も追加している。なおこのエスケープ手法は CAP (Columbia AppleTalk Package) と呼ばれる AppleTalk の UNIX 実装に由来するものらしく、CAP Encoding と呼ばれている。

HFS イメージの文字化けしないマウント方法

HFS イメージを Finder でクリックしてマウントする際、OS X Yosemite v10.10 からは文字コードが自動判定されずしばしば文字化けするようになった。 OS X Yosemite では Finder でエンコーディングを修正することもできたが、OS X El Capitan v10.11 からはその選択肢も選べなくなった。 Finder では選べないが、OS X El Capitan でも HFS_MacJapanese.kext を使用することはできる。文字コードを明示してマウントする場合はコマンドで次のようにする。

hdiutil attach -nomount backup.dmg
mount_hfs -e Japanese -u 501 -g 20 -m 755 -o nodev -o noowners -o nosuid -o rdonly /dev/disk1 /Volumes/HFSData

macOS では attach によりストレージと接続し、mount によってストレージをマウントする流れをとる。 attach だけでマウントすることも可能であるが、文字コードを指定するために -nomount オプションによってマウントを抑制している。 hdiutil コマンドで attach するとストレージの BSD 名が決定されて出力される。mount_hfs コマンドは KEXT を読み込むために管理者権限で実行する必要があり、 ここでは BSD 名が /dev/disk1 であった場合に /Volumes/HFSData へマウントする際のコマンドを示した。/Volumes/HFSData は適宜 mkdir コマンドなどで作成する必要性がある。 -e オプションは文字コードを指定しているもので、Arabic, ChineseSimp, ChineseTrad, Croatian, Cyrillic, Greek, Hebrew, Icelandic, Japanese, Korean, Roman, Romanian, Thai, Turkish が選べる。デフォルトは Roman である。 -u, -g オプション で UID を 501、GID を 20 に指定しているが、ログインしているユーザのものが良い。UID と GID は id コマンドで確認できる。 -o rdonly オプションは読み取り専用を指定するもので、UDZO 形式など読み取り専用の場合に指定が必要である。もし指定しないと mount_hfs: Permission denied でマウントに失敗する。 mount_hfs コマンドは HFS パーティションを明示する必要性があるので、このイメージが複数のパーティションを有していると mount_hfs: error on mount(): error = -1.; mount_hfs: Invalid argument といった警告が出て、マウントに失敗する。 attach した際の出力内容に注意を払うようにし、喩えば次の様に出力されていた場合は Apple_HFS とある /dev/disk1s3 に対してマウントするようにする。

/dev/disk1          	Apple_partition_scheme         	
/dev/disk1s1        	Apple_partition_map            	
/dev/disk1s3        	Apple_HFS                      	

ストレージを使用し終わった時は、次のコマンドを使うと umount によってマウントを解除し、detach によりストレージを取り外すことができる。 attach する場合と同様に detach だけで直接マウントを解除して取り外すことも可能である。/Volumes/HFSData は不要な場合は適宜削除する必要性があるが、hdiutil コマンドによって削除されることもある。

umount /Volumes/HFSData
hdiutil detach /dev/disk1

HFS イメージの HFS+ イメージへの変換

文字化けせずに HFS イメージをマウントできたら、いよいよ HFS+ イメージへ変換することを考える。 Disk Utility.app や hdiutil コマンドにはフォルダやストレージから新規イメージファイルを作る機能が存在するが、この機能は resource fork を完全にコピーするものではない。 従って、まずここでは書き込み可能な空の HFS+ イメージを作成し、そこにデータを追加、HFS+ イメージを読み取り専用に変換する段階を踏むことにする。空の HFS+ イメージは次のように作成する。

hdiutil create -sectors 446000 -fs HFS+ -volname "Macintosh Data" backup-temporary.dmg

ここではセクタ数を 230 MB の MO で使われていた 446000、ボリューム名を Macintosh Data とした。前節でマウントした backup.dmg と同じものに揃えたい場合は hdiutil imageinfo backup.dmg で調べることができる。 セクタ数は Sector Count:、ボリューム名は partition-filesystems: HFS: として出力される。ただしボリューム名が日本語の場合は MacJapanese なので文字化けする。 そこから HFS+ イメージにデータを複製するには次のコマンドを順次実行する。

mkdir /Volumes/HFSPlusData
hdiutil attach backup-temporary.dmg -nobrowse -mountpoint /Volumes/HFSPlusData
rm -r /Volumes/HFSPlusData/.Trashes
scp -rpE /Volumes/HFSData/ /Volumes/HFSPlusData
touch -r /Volumes/HFSData /Volumes/HFSPlusData
SetFile -d "01/01/1999 00:00:00" /Volumes/HFSPlusData
hdiutil detach /Volumes/HFSPlusData
rm -r /Volumes/HFSPlusData
hdiutil detach /Volumes/HFSData

backup-temporary.dmg をマウントする際に -nobrowse オプションを指定すると Finder 上でストレージとしては認識されなくなる。 Finder 上でストレージとして認識されると .fseventsd という FSEvents 用の隠しフォルダが作成されるため、それを回避している。またマウントした時点で必ずゴミ箱が作成されるので消去している。 ここでは scp コマンドを使用しているが、macOS の scp コマンドは -E オプションによって resource fork や ACL といったファイル属性を複製することができる。 かつては Xcode.app に附属する Command Line Tools の CpMac コマンドなどが必要であったが、現在の macOS の cp コマンドもまたファイル属性の複製に対応しているので、scp コマンドでなくとも良い。 touch コマンドによってストレージの変更日、Command Line Tools の SetFile コマンドによってストレージの作成日を揃えている。 SetFile コマンドで設定する作成日は、ここでは平成11年1月1日午前零時としているが、Command Line Tools の GetFileInfo コマンドを使用すると created: として作成日を取得できるので、そのまま指定すれば良い。 これらのコマンドによって backup-temporary.dmg には必要なファイルが全て複製されている。最後に読み取り専用の HFS+ イメージとして UDZO 形式の backup-hfsplus.dmg を作成して作業は終了である。

hdiutil convert backup-temporary.dmg -format UDZO -o backup-hfsplus.dmg

必要なければ backup-temporary.dmg を削除すれば良い。backup.dmg もまた HFS フォーマットながら Classic 環境では認識できないので、Classic 環境用には Rdxx 形式のイメージを残しておき、作業完了後には HFS イメージの backup.dmg は削除すると良いだろう。

HFS+ イメージと Classic 環境

HFS+ イメージは Mac OS 8.1 以降の Classic 環境であっても使用することができるが、最新の macOS で作成した HFS+ イメージはしばしば Classic 環境で文字化けする。 文字化けした HFS+ に Classic 環境で日本語ファイルを付け足したとしても macOS 側では文字化けすることはない。現在、調査中である。

  1. 10.3.4にしたら文字化けします
  2. El CapitanのHFSサポートがひどい
  3. Mac標準フォーマットのお話
  4. 丸真商店
  5. Accessing Mac Formatted Floppy Disks without a Kryoflux
  6. Floppy Disk Compatibility and Incompatibility in the Mac World
  7. Working with Macintosh Floppy Disks in the New Millennium
  8. Macintosh PowerBook 190 - 技術仕様
  9. hdiutil とは - 生物学者はこんなことを考えている
  10. hdiutil の使い方
  11. hdiutil (1)
  12. APFSで再燃したNFD問題
  13. Mac OS X の NFD 問題での対策諸々
  14. HFS+のNFD
  15. Apple Developer Connection Technote 1150 - HFS Plus Volume Format (mirror.informatimago.com による複製記事)
  16. MacOS 開発支援 (FSSpec vs. FSRef 他参照)
  17. MacJapanese, CP932, Shift_JIS の違いと Ruby での取り扱い
  18. Apple Open Source - hfs
  19. MacJapanese と UTF-8 を相互変換する gem をつくりました
  20. File Name Encoding Repair Utility v1.0
  21. Macの「ターミナル」でファイルを移動する/コピーする

技術考󠄁 > MacOS 標準フォーマット(HFS)からの移行
Copyright© R2[2020]. All Rights Reserved.