openがファイル記述子を返したのに、readすると失敗する。LinuxのO_PATHでは、これは必ずしも異常ではありません。O_PATHは、対象を読み書きするためではなく、ファイルシステム内の対象を参照するための番号を取得する指定です。
どの対象を指すかと、その中身にどの操作ができるかを分けると、ディレクトリを基準にした処理や、内容を読まずに属性を調べる処理を整理できます。本稿では通常のファイルとディレクトリを対象に、読み取り用のopenとの違いを考えます。
簡単にまとめると?
- O_PATHで得た記述子は対象への参照です。その番号でreadやwriteを行うとEBADFになり、読み書き用の番号とは用途が違います。
- 属性を調べるfstatや、ディレクトリを基準にするopenatなど、対応した操作には利用できます。使える操作とLinuxの版を確認します。
- 参照を取得できても、その後の操作に必要な権限がすべて揃うわけではありません。O_PATHはアクセス制御を無効にする指定ではありません。
「対象を指す」と「中身を読む」は別の役割
ファイル記述子は、プロセスがカーネルの管理する対象を指定するときに使う小さな整数です。番号が返ったことだけでは、どんな操作にも使えるとは言えません。どの条件で取得した番号かを、呼出し側が知っておく必要があります。
O_RDONLYなら、対象を読み取り用に開くことを求めます。対してO_PATHは、ファイルシステムの木の中で対象を指す参照を取得する指定です。その記述子に対するread、write、mmapなどはEBADFで失敗します。EBADFは無効な記述子などを表すエラーですが、この場合は番号が存在しないのではなく、要求した入出力に使えないことが理由です。[1]
たとえば、設定ファイルの内容を解析する関数へ番号を渡すなら、読み取り可能な記述子が必要です。一方、対象の属性を確認するだけの段階で、内容まで読み込む必要があるとは限りません。二つの段階を同じ「open済み」という印だけで管理すると、後でreadが失敗した理由を追いにくくなります。
同じ四文字のファイルで、三つの操作を比べる
内容がABCD、長さが4バイトのファイルを考えます。まず読み取り用に開いた番号Rについて、fstatで属性を確認し、readで4バイト読む場合を考えます。対象が変化せず、必要な権限があり、読取りが正常に4バイト返るという説明用の条件なら、属性の長さは4、読んだ内容はABCDです。
同じ対象へのO_PATHの番号Pでも、対応するLinuxの版ではfstatによる属性確認ができます。しかし、Pをreadへ渡してもABCDは得られず、EBADFになります。Pをdupで複製した番号Qへ変えても、読み取り用の開き方へ変換されたわけではありません。複製は、参照する開いた状態を増やす操作とは区別します。

この比較で確認するのは、同じ対象への参照でも、内容の読取りまで許されるとは限らないことです。必要なのが内容なら、読み取り用に開く処理とその失敗の扱いを用意します。番号を増やすことや、取得に成功したという事実だけでは、その代わりになりません。
ディレクトリを、後続の処理の基準にする
O_PATHは、ディレクトリへの参照を保持するときにも使えます。たとえばディレクトリをO_PATHとO_DIRECTORYで取得し、その番号をopenatのdirfdへ渡す構成です。O_DIRECTORYは、対象がディレクトリでなければ失敗させる指定です。[1]
openatへ相対パスを渡すと、そのパスはdirfdが参照するディレクトリを基準に解釈されます。たとえば「設定ディレクトリの参照D」と相対名configを組にすれば、プロセス全体の現在の作業ディレクトリに頼らず、Dを起点に対象を探せます。絶対パスを渡した場合はdirfdが無視されるため、二つの呼び方を混ぜないようにします。
ここでも、Dそのものをreadして設定ファイルの中身を得るわけではありません。Dを基準に、configを別途読み取り用に開き、その新しい番号から読みます。「基準となるディレクトリへの参照」と「選んだ子ファイルの内容へのアクセス」は二段階です。途中のopenatが失敗したなら、基準の取得に成功していたことだけで処理全体を成功とは扱えません。
また、ディレクトリの参照を持つことは、その中の名前や内容を凍結することではありません。子の名前が付け替えられる場合や、内容が更新される場合には、それぞれの操作時点で何を参照したかを考えます。この方式だけで、任意の相対パスを安全な範囲へ閉じ込めたり、複数の処理を一つの取引にしたりできるとは主張しません。
権限の確認を飛ばす指定ではない
O_PATHで対象を取得する際、対象そのものの読取り許可は要求されません。ただし、そこへ至る途中のディレクトリをたどるための実行許可は必要です。その後の操作に応じて、別の権限確認も行われます。たとえばfchdirでは、参照先ディレクトリの実行許可が必要です。[1]
この違いは、何をするために権限が必要かで読むと理解しやすくなります。内容を読むことと、対象を指すこと、ディレクトリを作業場所へ設定することは同じ操作ではありません。前の段階の成功を、その後の操作すべてへの許可証として扱わず、それぞれの戻り値を確認します。
O_PATHと組み合わせたフラグにも注意が必要です。O_CLOEXEC、O_DIRECTORY、O_NOFOLLOW以外のフラグは無視されます。O_RDWRを一緒に書けば読書きも可能になる、という解釈はできません。意図した用途をコードと記録に残し、後段が必要とする操作に合った記述子を渡します。
採用前に確認する条件
O_PATHはLinux 2.6.39からの機能です。利用可能な後続操作には追加された時期の違いがあり、fchdirは3.5、fstatは3.6からO_PATHの記述子へ対応しています。本稿の比較はLinux man-pages 6.19に掲載された仕様を参照しており、ほかのOSへ同じ名前と挙動を仮定しません。
設計では、まず必要なのが対象への参照だけなのか、内容の読取りまで含むのかを決めます。次に、その番号を受け取る関数が行う操作を並べ、O_PATHに対応するかを確認します。最後に、相対パスの基準、後続の権限確認、エラー時に手放す番号を整理します。通常のファイルならreadが短く終わる場合もあるため、図の4バイト例をそのまま完全な読取り処理とは扱いません。
O_PATHの役割は、ファイルを読めるようにすることの代用品ではありません。内容を扱う前段で参照を保持したい場面に用途を絞ると、「番号がある」「属性を調べられた」「内容を読めた」を区別でき、失敗した段階も明確になります。
出典
- Linux man-pages 6.19 — open(2)、DESCRIPTIONのO_PATH・O_DIRECTORY・openat、およびNOTESのOpen file descriptions。取得した掲載版を資料庫へ保全。操作比較の表と4バイト例は独自の説明モデル。
