Mac 標準のファイルシステム APFS (Apple File System) のデフォルトは、ファイル名の大文字・小文字を区別せず扱う case-insensitive となっています。一方、Linux のファイルシステムでは大文字・小文字を区別する case-sensitive が標準的1であるため、Linux のファイルを Mac で扱う際に問題となる場合があります。この記事ではディスクイメージを使った解決方法を紹介します。
大文字・小文字が問題になるケース (Linux カーネルの例)
大文字・小文字の区別が特に問題になるは、同じディレクトリ内に大文字・小文字だけが異なるファイルがある場合です。
具体的に自分が困ったのは Linux カーネルのソースコードです。例えば include/uapi/linux/netfilter というディレクトリには、xt_CONNMARK.h と xt_connmark.h という大文字・小文字だけが異なる 2 つのファイルが存在しています。
大文字・小文字を区別しないデフォルトの APFS では、これらを同一のファイルとして扱うため、どちらか一方しか同じディレクトリに存在できません。そのため、Linux カーネルのコードを clone した直後からファイルの衝突によってダーティな状態となります。以下はコミット 58720809f527 をデフォルトの APFS 上に clone した直後の結果です。ダーティな状態であるためブランチの切り替えなどの git 操作ができません。
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: include/uapi/linux/netfilter/xt_CONNMARK.h
modified: include/uapi/linux/netfilter/xt_DSCP.h
modified: include/uapi/linux/netfilter/xt_MARK.h
modified: include/uapi/linux/netfilter/xt_RATEEST.h
modified: include/uapi/linux/netfilter/xt_TCPMSS.h
modified: include/uapi/linux/netfilter_ipv4/ipt_ECN.h
modified: include/uapi/linux/netfilter_ipv4/ipt_TTL.h
modified: include/uapi/linux/netfilter_ipv6/ip6t_HL.h
modified: net/netfilter/xt_DSCP.c
modified: net/netfilter/xt_HL.c
modified: net/netfilter/xt_RATEEST.c
modified: net/netfilter/xt_TCPMSS.c
modified: tools/memory-model/litmus-tests/Z6.0+pooncelock+poonceLock+pombonce.litmus
ディスクイメージを使った解決
APFS には大文字小文字を区別する Case-sensitive APFS
というフォーマットが用意されているので、このファイルシステムを使えばこの問題は解決します。既存のディスクのファイルシステムを変更するのは影響が大きいため、ここではディスクイメージを使った方法を紹介します。
まず、大文字小文字を区別する Case-sensitive APFS
のディスクイメージを ディスクユーティリティ.app
2 または CLI の hdiutil
コマンドで作成します。ここでは hdiutil
を使った作成例を紹介します。
次のコマンドは 100 GB の Case-sensitive APFS
のディスクイメージを SPARSEBUNDLE
形式で $HOME/src-case.sparsebundle
というファイル3に作成する例です。SPARSEBUNDLE
は スパースファイルの一種であるため、実際に使用するまで容量を消費しません。
hdiutil create -type SPARSEBUNDLE -fs 'Case-sensitive APFS' -size 100g \
-volname src-case $HOME/src-case.sparsebundle
次にこのファイルをアタッチします。下記の例ではマウント先を $HOME/src-case
に設定しています。自分は普段 $HOME/src
以下でソースコードを管理しているので、大文字小文字の区別が必要なリポジトリだけを $HOME/src-case
に置き、$HOME/src
にシンボリックリンクを貼る運用にしました。
hdiutil attach $HOME/src-case.sparsebundle -mountpoint $HOME/src-case
$HOME/src-case
で大文字・小文字が区別されることを確認してみます。
$ touch $HOME/src-case/helloworld.txt
$ touch $HOME/src-case/HELLOWORLD.TXT
大文字・小文字だけが異なるファイルが共存できました。
$ ls $HOME/src-case/
HELLOWORLD.TXT helloworld.txt
このアタッチ状態は再起動すると失われます。Mac の起動時に自動的にアタッチしたい場合は、Launchd でアタッチコマンドを実行するようにします。以下は自分の設定例です。ユーザごとの launchd の設定を置く ~/Library/LaunchAgents
以下に attach-src-case.plist
という名前で保存しました。設定の詳細は man launchd.plist
をご覧ください。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>attach-src-case</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>hdiutil attach $HOME/src-case.sparsebundle -mountpoint $HOME/src-case</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>ProcessType</key>
<string>Interactive</string>
</dict>
</plist>
ProcessType を Interactive に設定している理由
Launchd の Job はデフォルトで CPU・I/O にスロットルがあり、ProcessType を指定しない場合(Standard
)はこのスロットルによりアタッチに時間がかかります。Interactive を設定するとこの制限がなくなります。詳細は man launchd.plist
をご覧ください。
補足: case-insensitive と case-preserving
APFS のデフォルトではファイル名の大文字・小文字を区別せず扱う case-insensitive です。一方、ファイル名としては大文字・小文字は区別されて保存されています。これは case-preserving と呼ばれます。それぞれ別のコンセプトであるため注意が必要です。
例えば heLLoWorLD.TXT
という大文字・小文字が混在したファイルを作成・表示すると、APFS のデフォルトでもファイル名としては大文字・小文字が区別され保存されていることがわかります。これが case-preserving です。
$ echo 1 > heLLoWorLD.TXT
$ ls
heLLoWorLD.TXT
一方、大文字小文字だけが異なるファイル名を指定したときに同一のファイルとして扱われる動作が case-insensitive です。
$ echo 1 > heLLoWorLD.TXT
$ echo 2 > HELLOWORLD.TXT
$ ls
heLLoWorLD.TXT
$ cat heLLoWorLD.TXT
2
case-sensitive のファイルシステムの場合は、下記のように別のファイルとして区別されます。
$ echo 1 > heLLoWorLD.TXT
$ echo 2 > HELLOWORLD.TXT
$ ls
HELLOWORLD.TXT heLLoWorLD.TXT
$ cat heLLoWorLD.TXT
1
$ cat HELLOWORLD.TXT
2