彷徨えるフジワラ

年がら年中さまよってます

FAT を正しく case insensitive にする方法

暫く前になるけれど、Linux 上における Mercurialcase insensitive filesystem (icasefs) に対する挙動の確認が必要になった。

僕の開発環境は、Windows ホスト上の VirtualBoxLinux を稼動させているので、ホスト側に挿した FAT フォーマットの USB メモリとかを仮想マシン側に見せる、という手も使えるけれど、一応正統派な方法で FAT 環境を作ることに。

# 要 dosfstools (mkfs.vfat コマンド)

作成する FAT 領域に関する仕様を以下のように仮定する:

  • /tmp/vfatdev 直下の 64MB (= 512 バイトブロックで 131072)ファイルをデバイス
  • /tmp/vfatmnt 直下にマウント
  • uid 1000 のユーザに対してアクセス権限を解放

作成手順は以下の通り:

# dd if=/dev/zero of=/tmp/vfatdev count=131072
131072+0 records in
131072+0 records out
67108864 bytes (67 MB) copied, 0.301944 s, 222 MB/s
# mkfs -t vfat /tmp/vfatdev
mkfs.vfat 3.0.9 (31 Jan 2010)
# mount -t vfat -o loop,uid=1000 /tmp/vfatdev /tmp/vfatmnt
#

"-o loop" 指定は、単なるファイルをブロックデバイスとして見せる(= ループバックデバイス化する)ためのもの。

ここまで出来たなら、ファイル名指定 'a' と 'A' が同一、つまり『case insensitive』になっていることを確認。

$ date > /tmp/vfatmnt/a
$ stat /tmp/vfatmnt/a
  File: `/tmp/vfatmnt/a'
  Size: 29              Blocks: 4          IO Block: 2048   regular file
Device: 700h/1792d      Inode: 2           Links: 1
Access: (0755/-rwxr-xr-x)  Uid: ( 1000/fujiwara)   Gid: (    0/    root)
Access: 2012-04-04 17:46:15.000000000 +0900
Modify: 2012-04-04 17:49:39.000000000 +0900
Change: 2012-04-04 17:49:39.000000000 +0900
$ stat /tmp/vfatmnt/A
  File: `/tmp/vfatmnt/A'
  Size: 29              Blocks: 4          IO Block: 2048   regular file
Device: 700h/1792d      Inode: 2           Links: 1
Access: (0755/-rwxr-xr-x)  Uid: ( 1000/fujiwara)   Gid: (    0/    root)
Access: 2012-04-04 17:46:15.000000000 +0900
Modify: 2012-04-04 17:49:39.000000000 +0900
Change: 2012-04-04 17:49:39.000000000 +0900
$

おぉ、'a' と 'A' のどちらでも stat(2) 出来ている。

では、"hg debugfsinfo" ではどうか?というと:

$ cd /tmp/vfatmnt
$ hg debugfsinfo
exec: no
symlink: no
case-sensitive: yes
$

はぁ? case sensitive〜?どーゆーことよ?

試行錯誤すること暫し …… "hg debugfsinfo" は、".debugfsinfo" というファイルの生成/アクセス可否で、case seitive か否かを判定しているのだが:

$ echo debugfsinfo > .debugfsinfo
$ stat .debugfsinfo
  File: `.debugfsinfo'
  Size: 12              Blocks: 4          IO Block: 2048   regular file
Device: 700h/1792d      Inode: 5           Links: 1
Access: (0755/-rwxr-xr-x)  Uid: ( 1000/fujiwara)   Gid: (    0/    root)
Access: 2012-04-04 17:57:27.000000000 +0900
Modify: 2012-04-04 17:57:27.000000000 +0900
Change: 2012-04-04 17:57:27.000000000 +0900
$ stat .DEBUGFSINFO
stat: cannot stat `.DEBUGFSINFO': No such file or directory
$

どうやら、このファイルの場合では stat(2) が出来ていない。ファイル名形式(「8+3で表現可能」)とかに依存してるのか?

"HoGe" で保存したファイルに、"HOGE" だと stat(2) できるけど、"hoge" だと駄目。うーむ、この程度で駄目だとすると、基本的には上手く機能していないと考えたほうが良いなぁ。

とりあえず、任意のファイル名で case insensitive にするにはどうすれば良いか、更に試行錯誤/ネット彷徨すること暫し …… あ、符号化形式指定の問題か?

そういえば、これまで cp932 絡みの case insensitive 系修正の確認では、いつも iocharset (+ codepage) とかを設定していて、且つちゃんと case insensitive になっていたけど、今回は日本語とか関係無いから、特に設定していなかったなぁ。

とりあえず暫定的に cp932 を設定してみると:

※ root 権限側
# mount -t vfat -o iocharset=cp932,loop,uid=1000 /tmp/vfatdev /tmp/vfatmnt

※ ユーザ側
$ echo debugfsinfo > /tmp/vfatmnt/.debugfsinfo
$ stat /tmp/vfatmnt/.debugfsinfo
  File: `.debugfsinfo'
  Size: 12              Blocks: 4          IO Block: 2048   regular file
Device: 700h/1792d      Inode: 5           Links: 1
Access: (0755/-rwxr-xr-x)  Uid: ( 1000/fujiwara)   Gid: (    0/    root)
Access: 2012-04-04 17:57:27.000000000 +0900
Modify: 2012-04-04 17:57:27.000000000 +0900
Change: 2012-04-04 17:57:27.000000000 +0900
$ stat /tmp/vfatmnt/.DEBUGFSINFO
  File: `/tmp/vfatmnt/.DEBUGFSINFO'
  Size: 12              Blocks: 4          IO Block: 2048   regular file
Device: 700h/1792d      Inode: 7           Links: 1
Access: (0755/-rwxr-xr-x)  Uid: ( 1000/fujiwara)   Gid: (    0/    root)
Access: 2012-04-04 09:00:00.000000000 +0900
Modify: 2012-04-04 18:24:54.000000000 +0900
Change: 2012-04-04 18:24:54.000000000 +0900
$

出来たー!

良く良く考えれば、『文字大小の変換』なんて、符号化方式が確定していなければ、実行しようが無いのだから、符号化方式指定が必要なのは当然なんだよねぇ。

ちなみに、iocharset 指定無しだと、コンソールに:

FAT: utf8 is not a recommended IO charset for FAT filesystems, \
    filesystem will be case sensitive

などと出ていたりする。このメッセージをちゃんと目にして、意味を理解していれば …… orz

ちなみに、先述した「"HoGe" に "HOGE" は OK で "hoge" は NG」ってのは、昨年末にバタバタ作業した際に、Matt から『Windows の case normalization 処理は、lower じゃなくて upper』と指摘されたのと、見事に符合している。

つまり、iocharset を指定しないと、指定ファイル名の normalization が行われないので、normalization 実施後相当の大文字指定でないと駄目、ということなのだな。

うーん、でも最初の不十分な設定の際に、 "a" に対して "A" が上手くいったのは、上記の推測で辻褄が合うけど、".debuginfo" に対して ".DEBUGINFO" が上手くいかなかったのは、これでは説明できないなぁ。なんとも謎な話だ……。