markdown
はじめに (対象読者・この記事でわかること)
この記事は、組み込み機器やレガシー環境で32bitバイナリを動かし続けているC/C++開発者を対象にしています。
特に、NASやWindows共有フォルダをCIFSでマウントして運用している現場で、「ある日突然ディレクトリ一覧が取得できなくなった」という事象に悩まされている方に最適です。
この記事を読むことで、以下のことがわかります。
- 32bitプロセスがCIFSマウントポイントで
readdir()を呼ぶとNULLが返るメカニズム - 現象を最小構成で再現する手順
- カーネルパラメータ1つで回避する方法
- 今後同種のトラブルを未然に防ぐための設計指針
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- C言語での標準I/OとPOSIX APIの基礎
- Linuxで
mountコマンドを使ったCIFSマウントの経験 getconf LONG_BITでアーキテクチャのビット幅を調べられること
CIFS+32bitでreaddir()がNULLになる背景
Linuxカーネル5.15以降、CIFSクライアントが「64bit inode番号」をデフォルトで有効にしました。
inode番号はstat構造体のino_tメンバに格納されますが、32bitユーザ空間ではino_tが32bitに収まらず、リトルエンディアン環境で上位32bitが捨てられるため、重複したinode番号が生成されます。
カーネル側は「重複inode番号を検出」すると、ディレクトリエントリを返却せずreaddir()を早々に打ち切り、NULLを返します。
結果として、32bitバイナリからは「ディレクトリが空」あるいは「読み取れない」ように見えるという現象が起きます。
再現手順と回避策を実践する
ステップ1:環境を準備する
ホスト側は64bit Linux(カーネル5.15以上)を想定します。
まず、CIFSマウント先を用意し、大量のファイルを格納しておきます。
Bash# 64bitマシンで確認 $ uname -r 6.8.0-40-generic $ getconf LONG_BIT 64
適当な共有フォルダを/mnt/nasにマウントします。
Bash$ sudo mount -t cifs //192.168.1.100/share /mnt/nas \ -o username=user,vers=3.0,iocharset=utf8
ステップ2:32bitバイナリでreaddir()を呼ぶ
以下の最小コードをtest_readdir.cとして保存し、32bitバイナリをビルドします。
C// test_readdir.c #define _FILE_OFFSET_BITS 64 #include <dirent.h> #include <stdio.h> int main(void){ DIR* d = opendir("/mnt/nas"); if(!d){ perror("opendir"); return 1; } struct dirent* e; int cnt = 0; while((e = readdir(d))){ ++cnt; } closedir(d); printf("entries=%d\n", cnt); return 0; }
32bitバイナリを作るにはgcc-multilibが必要です。
Bash$ sudo apt install gcc-multilib $ gcc -m32 -o test_readdir test_readdir.c $ file test_readdir test_readdir: ELF 32-bit LSB executable, Intel 80386, ...
実行してみると、期待するファイル数ではなく0件になることが確認できます。
Bash$ ls /mnt/nas | wc -l 12000 $ ./test_readdir entries=0
ハマった点やエラー解決
dmesgを見てもエラーメッセージが出ないstraceしてもgetdents64は成功し、エラー番号は0- 同じバイナリを64bitでビルドすると正常にカウントできる
つまり、「32bitバイナリだけが影響を受ける」「エラー扱いにならない」ため、原因特定に非常に時間がかかります。
解決策
カーネルモジュールパラメータで「64bit inodeを無効化」します。
Bash# 即時反映(マウントし直し不要) echo N | sudo tee /sys/module/cifs/parameters/enable_64bit_inode # 永続化のため/etc/modprobe.d/local-cifs.confに追記 options cifs enable_64bit_inode=N
設定後、再度32bitバイナリを実行すると、無事に全エントリが読めるようになります。
Bash$ ./test_readdir entries=12002 # . と .. 分を含む
まとめ
本記事では、32bitバイナリがCIFSマウントポイントでreaddir()を呼ぶとNULLが返る現象を再現し、カーネルパラメータ1つで回避する方法を解説しました。
- 64bit inodeがデフォルト有効になったカーネル5.15以降で起きる
- 32bit空間の
ino_tに収まらず重複inodeと判定される enable_64bit_inode=Nで即座に回避可能
この記事を通して、レガシーな32bitバイナリでも最新カーネルでCIFSを安全に使う手法が身につきました。
今後は、64bit移行が完全に済むまでの暫定対策として、システム起動時に同パラメータを設定する運用を推奨します。
参考資料
- Linux CIFSモジュール公式ドキュメント
- Ubuntu Manpage - mount.cifs
- 「Linuxカーネルソース fs/cifs/inode.c」(特に
cifs_iget()周辺)
