シンボリックリンクの中に記録された名前を読むreadlinkは、呼び出しが成功しても、その名前を全部受け取れたとは限りません。さらに、結果の末尾へC言語の文字列を終えるゼロバイトを付けてくれるわけでもありません。返されたバイト数と、渡した領域の大きさをどう比べるかが、読み取った内容を正しく扱う鍵になります。
簡単にまとめると?
- readlinkはリンクに記録された内容をバッファへコピーし、書き込んだバイト数を返します。成功時も末尾のNULは追加しません。
- 戻り値がバッファ容量より小さければ、その呼び出しでは内容が収まっています。容量と同じなら、ぴったり収まった場合と途中で切れた場合を区別できません。
- lstatで先に大きさを調べても、読み取りまでにリンクが変わる可能性があります。見積もりだけに頼らず、実際の戻り値と再試行の上限を確認します。
文字列を返すのではなく、バイト列を領域へ書き込む
readlink(path, buf, bufsiz)は、pathで指定したシンボリックリンクの内容を、bufが指す領域へ最大bufsizバイト書き込みます。Linux man-pagesのreadlink(2)は、成功したときの戻り値を、実際に書き込んだバイト数と定義しています。失敗時は−1を返し、errnoに理由を設定します。[1、DESCRIPTION、RETURN VALUE]
ここで読むのはリンクの内容です。リンク先のファイル本体を開いて中身を読む操作や、すべての別名を解決した絶対パスの生成と混同しないことが大切です。相対的な名前が記録されているなら、その名前の文字列が結果になります。
C言語では文字列の終わりをNUL、値が0のバイトで示すことが多くあります。しかしreadlinkは、その終端を追加しません。受け取った領域をそのまま終端付き文字列として扱うと、書き込まれた範囲を越えて読み進めるおそれがあります。まず「有効なデータは先頭から何バイトか」を戻り値で管理する必要があります。
戻り値が容量と等しいときは、二つの可能性が残る
リンクに記録されている内容を、ASCIIで9バイトのdocs/v1/aとします。8バイトの領域を渡すと、先頭8バイトのdocs/v1/だけが入り、戻り値は8です。小さい領域に合わせて内容が切り詰められますが、それだけでエラーになるわけではありません。[1、DESCRIPTION]
同じリンクに9バイトの領域を渡すと、docs/v1/aの全9バイトが入り、戻り値は9になります。この場合は全部入っていますが、呼び出した側が元の長さを知らないなら、戻り値だけでそれを確認できません。10バイト以上の内容が途中で切られた場合も、9バイトの領域に対する戻り値は9になり得るためです。
10バイトの領域なら、戻り値9は容量10より小さくなります。この比較から、その呼び出しで内容が収まったと判断できます。ただし、余った最後の1バイトへNULが自動で書かれるわけではありません。内容が収まることと、終端が付くことは別です。

この違いは、成功か失敗かだけを調べるコードでは見落とされます。戻り値が負かどうかに加えて、非負なら容量と比較する必要があります。Linux man-pagesも、戻り値がbufsizと等しい場合は切詰めが起きた可能性がある、と説明しています。
NULを足すなら、その場所も確保する
結果を長さ付きのデータとして処理するなら、返されたバイト数の範囲だけを扱います。終端付き文字列が必要なら、成功と容量の関係を確認してから、自分でNULを置きます。たとえば容量cをそのままreadlinkへ渡した場合、0≤n<cと確認した後ならbuf[n]は確保した領域内です。n=cのときにbuf[n]へ書くと、領域の外になります。
別の設計として、確保した領域のうち1バイトを終端用に残し、readlinkへは残りの容量を渡す方法があります。その場合も、切詰めの比較対象は「確保した総量」ではなく、実際にreadlinkへ渡したbufsizです。NULを安全に置ける余白があることは、リンク内容が切れていない証明にはなりません。
戻り値の型はssize_tです。−1のエラーを先に判定し、その後で必要な長さの型へ変換する順序にも意味があります。負の値を先に符号なしの大きさへ変換すると、エラーを巨大な長さのように扱ってしまうためです。実装では領域拡張時の整数の上限、確保失敗、エラー処理も含めて扱います。
lstatの見積もりと、実際の読み取りは同じ瞬間ではない
必要な大きさの手掛かりとして、リンク自体を調べるlstatのst_sizeを使えます。Linux man-pagesの例では、得た大きさへ1を足した領域を用意し、readlinkの戻り値をさらに確認しています。[1、NOTES、EXAMPLES] 足した1バイトには、結果が見積もりの大きさに収まったかを区別しやすくする意味があります。
しかし、lstatの後、readlinkの前にリンクが差し替えられ、内容が長くなる可能性があります。先ほど9バイトだったとして10バイトの領域を用意しても、読み取る時点で内容が12バイトなら、戻り値は10となり切詰めの可能性が残ります。独自モデルでは、この「見積もり時9、読み取り時12、容量10」のケースも確かめました。競合を実際に発生させた試験ではなく、二つの観測時点を分けた計算です。
また、Linuxの/procや/sysにある一部の特殊なリンクでは、st_sizeが0と報告されることがあります。[1、EXAMPLES] 0だからリンクの内容も空だと決めず、使える初期容量を用意し、結果を確認する必要があります。固定のPATH_MAXが常に定義されるとも限らないため、固定長の配列を使う前提も移植性に関わります。[1、NOTES]
再試行で確認できる範囲と、確認できない範囲
容量いっぱいの結果なら、領域を大きくして再試行する設計が考えられます。増やした後の戻り値が容量より小さければ、その読み取りでは内容が収まっています。ただし、呼び出しのたびにリンクが変わる環境で、いつか必ず安定するとは限りません。扱う最大長や試行回数を決め、超えた場合を失敗として返すなど、終わり方も必要です。
readlinkatは、相対パスを呼び出し元の現在のディレクトリではなく、指定したディレクトリのファイル記述子を基準に解釈できます。絶対パスならその基準は無視されます。[1、DESCRIPTION] 基準ディレクトリを明示する点は役立ちますが、同じ名前の差し替えが起こらなくなるという意味ではありません。LinuxにはO_PATHとO_NOFOLLOWで得たリンクの記述子に空のパスを指定する方法もありますが、ここではその利用手順や安全性の検証までは扱いません。
この稿の例は、Linux man-pages 6.19のreadlink(2)を読み、バイト数の境界を説明用モデルで確認したものです。実際のLinuxカーネル、ファイルシステム、並行更新下での動作を測った結果ではありません。読み取った内容が完全か、終端の領域があるか、読み取り後も同じリンクが保たれているかを分けることで、APIの成功を必要以上の保証として扱わずに済みます。
出典
- Linux man-pages, readlink(2)、man-pages 6.19、ページ日付2026-02-08、HTML作成2026-09-09。DESCRIPTION、RETURN VALUE、NOTES、EXAMPLESを参照。2026年10月23日確認。本文のバイト列モデルと図は独自作成で、原資料のCコードは転載していない。
