テクノロジー · 実用ガイド

Linux の「Text file busy」— 上書きや再試行の前に実行中 inode を特定する

ETXTBSY は、実行中ファイルへの書き込みだけでなく、書き込み用に開いたままのファイルを実行しようとした場合にも起きます。終了前に失敗した操作、プロセス、inode、名前空間を保存し、競合を修正するかパスを原子的に置換します。

実行中 inode と拒否された上書きの図
オリジナルイラスト:パス変更前に実行中 inode を証明します。 Watch at Ease

ETXTBSY は、実行中ファイルへの書き込みだけでなく、書き込み用に開いたままのファイルを実行しようとした場合にも起きます。終了前に失敗した操作、プロセス、inode、名前空間を保存し、競合を修正するかパスを原子的に置換します。

実行対象が書き込み用に開かれていると execve() は ETXTBSY を返すことがあります。 同じ inode が実行中なら書き込みや切り詰めが拒否されることがあります。

1. 方向、パス、inode を保存する

完全なエラー、時刻、コマンド、PID、コンテナまたは mount 名前空間、失敗した操作を記録します。exec、open、truncate、copy、rename を分け、再起動前に stat でデバイス番号と inode を保存します。

再起動、pkill -f、無制限リトライから始めません。置換後は同じパスが別 inode を指し、コンテナからホスト側所有者が見えない場合もあります。失敗側の名前空間で inode を比較します。

2. 正しい名前空間で実行者か書き手を証明する

実行中ファイルを疑う場合は /proc/<pid>/exe と限定した fuser -v 相当の証拠を確認します。実行マーカーと書き込みマーカーは別の問いに答えるため、権限と不要な情報の公開に注意します。

exec 失敗なら可書き記述子を保持するプロセスと記述子の対象・フラグを調べます。close-on-exec がない記述子は並行 spawn で子に継承されるため、親子を確認し、証明した所有者だけを止めます。

3. 書き込み後実行の競合を隠さず修正する

最小手順として同じディレクトリに新ファイルを作り、全量を書き、mode を設定し、閉じてから実行します。並行負荷ではキャッシュ展開を直列化するか、バージョン別の一時パスを使います。

記述子の寿命を原因箇所で直し、必要なら close-on-exec を使います。ETXTBSY だけの短い有界リトライは残存競合を緩和できますが、書き手を閉じる修正の代わりにはなりません。

4. 同じディレクトリの新ファイルを原子的に置換する

実行中ファイルをその場で切り詰めません。同一ファイルシステムの新ファイルへ書き、所有者・mode・hash を検証して直接試験し、rename で宛先を置換します。旧プロセスは旧 inode で動き続けます。

クラッシュ耐久性が必要なら所定のファイル・ディレクトリ同期も行います。置換後は対象サービスだけを再起動または reload し、ロールバック用の旧成果物をすぐ削除しません。

5. プロセスと inode で復旧を受け入れる

新 PID の /proc/<pid>/exe が期待パス、デバイス、inode、内容 hash に一致することを確認します。旧 PID を排出まで残す場合は旧 inode を参照していることも確認します。

以前 ETXTBSY を起こした並行書き込み・起動試験を実行します。busy エラーなし、可書き記述子なし、権限と hash が正しく、ロールバック成功が受け入れ条件です。失敗なら旧パスへ戻します。

検証済み新ファイルを原子的に rename する図
オリジナルイラスト:新ファイルを検証し、原子的に切り替えて旧 inode を排出します。 Watch at Ease