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移行が完全に済むまでの暫定対策として、システム起動時に同パラメータを設定する運用を推奨します。

参考資料