ふと思って調べたところ色々と面白いことが分かったのでメモ。
TARという形式がある。Tape ARchiveの略で、元々はテープドライブにファイルシステムを記録するためのフォーマットだが、ファイルシステムを1つのファイルに結合できるという利便性から、現代ではアーカイブ目的やファイルの配布目的として広く使われている。これを読んでいるあなたも、多分一度は使ったことがあるはず。
さて、 ファイルシステムをそのままテープに記録するからには、ファイルの内容はもちろん、ファイル・パーミッションも保存されるし、オーナー情報も保存される。そして、ファイル名も保存されるのだが、ファイル名はテープドライブにどのようなエンコーディングで保存されるのだろうか?UTF-8?それとも別の形式だろうか?
ちなみに、GNUのtar(1)
は日本語をファイル名に含むアーカイブを作成できる:
windymelt% tar cvf cat.tar 猫 猫/ 猫/猫.txt
僕の実行環境では$LANG=ja_JP.UTF-8
で、UTF-8では猫
はe7 8c ab
に該当する。
windymelt% hexyl cat.tar (snip) │00000a00│ e7 8c ab 2f e7 8c ab 2e ┊ 74 78 74 00 00 00 00 00 │×××/×××.┊txt00000│ │00000a10│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│ │* │ ┊ │ ┊ │ │00000a60│ 00 00 00 00 30 30 30 30 ┊ 36 34 34 00 30 30 30 31 │00000000┊64400001│ │00000a70│ 37 35 30 00 30 30 30 30 ┊ 31 34 34 00 30 30 30 30 │75000000┊14400000│ │00000a80│ 30 30 30 30 30 31 37 00 ┊ 31 34 33 32 31 35 33 32 │00000170┊14321532│ │00000a90│ 32 35 30 00 30 31 35 32 ┊ 37 37 00 20 30 00 00 00 │25000152┊770 0000│ │00000aa0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│ │* │ ┊ │ ┊ │ │00000b00│ 00 75 73 74 61 72 00 30 ┊ 30 77 69 6e 64 79 6d 65 │0ustar00┊0windyme│ │00000b10│ 6c 74 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │lt000000┊00000000│ │00000b20│ 00 00 00 00 00 00 00 00 ┊ 00 75 73 65 72 73 00 00 │00000000┊0users00│ │00000b30│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│ │00000b40│ 00 00 00 00 00 00 00 00 ┊ 00 30 30 30 30 30 30 30 │00000000┊00000000│ │00000b50│ 00 30 30 30 30 30 30 30 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│ │00000b60│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│ │* │ ┊ │ ┊ │ │00000c00│ 6d 65 6f 77 20 6d 65 6f ┊ 77 20 6d 65 6f 77 0a 00 │meow meo┊w meow_0│ │00000c10│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│ │* │ ┊ │ ┊ │ │00002800│ ┊ │ ┊ │ └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
そっくりそのまま、e7 8c ab
が登場した。TARはUTF-8でファイル名をエンコードするのだろうか?それとも、ファイルシステムが返すバイト列をそのまま格納するのだろうか?
ちなみにバイナリビューワにはHexylを使っている。便利なのでおすすめ。
tarはファイル名を関知しない
結論から言えば、tarはファイルシステムが返すファイル名のバイト列をそのまま記録するようだ。これは挙動から推測したもので、決定的なドキュメントを見付けたわけではない。255文字を超えないNULL終端文字列であればなんでも良いようだ。
その一方で、非-ASCIIなファイル名はtar の構造によれば規格外になるようだ。特に、POSIXが定めるFilenaming conventionsを遵守することが望ましいようだ。
要するに、規格外だがGNU Tarではなぜか動く、といった状態なのだが、今や誰もがGNU Tarを使っていて、非-ASCIIファイル名を付ける人間が少ないのであまり困ったことにはなっていない、という状況のようだ。
PAX(1)コマンド
ちなみに、tar(1)
はPOSIX標準ではない(マジかよ!)らしいので、POSIXに揃えるならば、きちんと仕様が定義されたファイルを作成できるpax(1)
を使うべきだろう(生成されるファイルはtar同等で、普通にtarで解凍できる)。昔の人は、tarファイルの相互運用で相当苦労したことが伺える。paxは原則としてPOSIXに書かれていない機能は提供しない。
windymelt% pax -w 猫 > cat2.tar windymelt% tar xvf cat2.tar 猫/ 猫/猫.txt
Paxというネーミングは、Pax Romana同様、平和を意味しているとのこと。ファイルフォーマットをめぐる争いに平和をもたらしたかったらしい。
じゃあファイルシステムのファイル名のエンコーディングはどうなっているのか
ではファイルシステム自体はどのようにファイル名をエンコーディングするのか?という疑問が当然浮かぶのだが、以下の記事によれば、大抵のLinuxにおけるファイルシステムはエンコーディングを関知しない(つまり、許可された任意のバイト列を格納してよい)ということらしい。
例えばbtrfsではNULと '/' を除くすべての文字
を許容している。したがってBtrfsを使っているなら、NUL
と/
を除くすべてのバイトからなる255文字を超えないバイト列をファイル名としてtarに格納可能だ。
NTFS
ところで、特筆すべき例外として、NTFSはファイル名のエンコーディングを指定している。NTFSでは、ファイル名としてUTF-16エンコーディングを使う。より厳密には、UTF-16LE(Little Endian)を使用し、BOMは付与されない(BOMが付いていたらそれは単にUTF-16と呼ぶ)。
そして、UTF-16でエンコードされた文字列にはnull byteが含まれるおそれがあり(UTF-8ではそうはならない)、ここれによりtarの作成や解凍に失敗するかもしれない(例えば、W
はUTF-16LEにおいて0x57 0x00
にエンコードされる)。これは実験していないので実際のところどうなるのかは分からない。
これには先行研究が存在する:
wanderingengineer.hatenablog.com
UTF-16のBOMについてはこちら:
tar vs cpio物語: