readdir()のエラーハンドリングを追加
bs_rb_scan_dir()にreaddir()のエラーハンドリングが追加され、ディレクトリスキャン中のシステムエラーを適切に検出できるようになりました。これにより、ストリーム終端とエラーの区別が明確になり、予期しないエラーの見落としを防げます。
背景
readdir()はNULLを返す場合に2つのケースがあります。ストリーム終端(正常終了)とエラー発生時です。エラー時にはerrnoが設定されますが、従来の実装ではこの区別を行っていませんでした。
POSIXの仕様では、readdir()がエラーでNULLを返す際にerrnoが設定されますが、正常なストリーム終端ではerrnoが変更されません。そのため、ループ終了時にerrnoをチェックすることで、異常終了を検出できます。ただし、readdir()呼び出し前に他の処理でerrnoが設定されている可能性があるため、呼び出し直前にerrnoをリセットする必要があります。
技術的な変更
whileループが無限ループ形式に書き換えられ、readdir()呼び出し直前にerrnoを0にリセットする処理が追加されました。
変更前:
errno = 0;
while ((entry = readdir(dirp))) {
if (entry->d_name[0] == '.') continue;
// ...
}
if (closedir(dirp)) {
bs_syserr_fail_path("closedir", errno, abspath);
return Qundef;
}
変更後:
while (1) {
errno = 0;
entry = readdir(dirp);
if (entry == NULL) break;
if (entry->d_name[0] == '.') continue;
// ...
}
if (errno) {
int err = errno;
closedir(dirp);
bs_syserr_fail_path("readdir", err, abspath);
} else if (closedir(dirp)) {
bs_syserr_fail_path("closedir", errno, abspath);
return Qundef;
}
entry == NULLでループを抜けた後、errnoの値を検査します。errnoが非ゼロの場合はreaddir()がエラーで終了したと判断し、bs_syserr_fail_path()でエラーを報告します。errnoがゼロの場合は正常なストリーム終端として、closedir()のエラーチェックのみを実行します。
fstatat()のエラーハンドリングも簡素化され、壊れたシンボリックリンク(ENOENT)の場合のコメントがインライン化されました。errnoの明示的なリセットは不要になり、コードの見通しが改善されています。
設計判断
while(1)とbreakによる無限ループ形式が採用されました。
この形式により、readdir()呼び出し直前にerrno = 0を配置できます。while ((entry = readdir(dirp)))形式では条件式内でreaddir()が評価されるため、その直前にerrnoをリセットする位置がありません。無限ループ+明示的なbreakにすることで、errnoのリセット、readdir()の呼び出し、NULLチェックを明確に分離しています。
エラー検出後はerrnoの値を一時変数errに退避してからclosedir()を呼び出す実装になっています。closedir()自体が失敗する可能性があるため、readdir()のエラー情報を失わないようにする配慮です。
本PRは、システムコールのエラーハンドリングの標準的なパターンを適用し、見落とされていたエラーケースを補完する変更です。errnoの初期化タイミングを制御できる制御構造に変更することで、POSIXのreaddir()仕様に完全に準拠した実装になりました。