以下の内容はhttps://numb86-tech.hatenablog.com/entry/2026/02/22/195712より取得しました。


Rust の libc や nix クレートで errno を確認する方法

カーネルは、発生したエラーなどの内容を番号で表現しそれを伝える仕組みを持っている。
状況に応じて適切な番号をカーネルが返してくれるので、開発者はその番号を見ることで状況や原因を知ることができる。

libcという C のライブラリを通してシステムコールを呼んだ場合、カーネルが返した番号がerrnoという変数にセットされる。この値を取得することで、適切なエラーハンドリングやロギングが可能になる。

この記事では、 Rust でerrnoを取得する方法やその具体例を見ていく。

動作確認は以下の環境で行った。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04.4 LTS
Release:    24.04
Codename:   noble
$ rustc --version
rustc 1.91.0 (f8297e351 2025-10-28)
$ cargo --version
cargo 1.91.0 (ea2d97820 2025-10-10)

Rust の Edition は2024

使っているクレートとそのバージョンは以下。

  • libc@0.2.179
  • nix@0.29.0

errno の値を取得する

libcクレートの__errno_location()関数で、errno変数が指し示しているメモリアドレスを取得できる。
そのためそのメモリアドレスをデリファレンスすることで、errnoの値を取得できる。

fn main() {
    let errno_address = unsafe { libc::__errno_location() };
    println!("errno_address: {:?}", errno_address); // errno_address: 0x7f55f5d036b0 この値は実行する度に変わる
    let errno = unsafe { *errno_address };
    println!("errno: {}", errno); // errno: 0
}

番号に割り当てられた名前とメッセージを取得する

先程の例だとerrnoの値は0だったが、これはerrnoの初期値。
エラーなどの種類ごとに番号が用意されており、システムコールなどでエラーが発生すると、その内容に応じた番号がerrnoにセットされる。

各番号にはそれを指し示す名前、そして内容を説明するメッセージが割り当てられている。
libcクレートのstrerror()関数に番号を渡すことでメッセージを取得できる。ただ、それをそのまま Rust の文字列として扱うことはできないため、変換処理が必要になる。

fn main() {
    let errno = unsafe { *libc::__errno_location() };
    println!("errno: {}", errno); // errno: 0

    let msg = unsafe { libc::strerror(errno) };
    let msg_str = unsafe { std::ffi::CStr::from_ptr(msg) };
    println!("message: {}", msg_str.to_str().unwrap()); // message: Success
}

メッセージは上記の方法で取得できるが、番号から名前を取得することはlibcクレートではできない。
定数として定義されているので名前から番号を導くことはできるが、逆はできない。

fn main() {
    println!("{}", libc::ENOENT); // 2
    println!("{}", libc::EACCES); // 13
    println!("{}", libc::EPIPE); // 32
}

番号から名前を取得する仕組みが欲しい場合、以下のようなコードを自分で用意しないといけない。

fn errno_name(errno: i32) -> &'static str {
    match errno {
        libc::EPERM => "EPERM",
        libc::ENOENT => "ENOENT",
        libc::ESRCH => "ESRCH",
        // 上記の要領で定数と名前の組み合わせを書いていく
        _ => "UNKNOWN",
    }
}

nixクレートには、番号から名前を取得する関数が用意されている。メッセージも簡単に取得できる。
具体的には、nix::errno::Errno::from_raw()に番号を渡すと、その番号に対応した名前やメッセージを取得できるようになる。

fn main() {
    let errno = nix::errno::Errno::from_raw(0);
    println!("{}", errno); // UnknownErrno: Unknown errno
    println!("{:?}", errno); // UnknownErrno
    println!("{}", errno.desc()); // Unknown errno

    let errno = nix::errno::Errno::from_raw(1);
    println!("{}", errno); // EPERM: Operation not permitted
    println!("{:?}", errno); // EPERM
    println!("{}", errno.desc()); // Operation not permitted
}

nix::errno::Errno::from_raw()の返り値を、フォーマット指定子{}で出力すると名前: メッセージが、{:?}で出力すると名前が、出力される。メッセージのみを得たい場合はdesc()メソッドを使う。
番号0のときのメッセージがlibc::strerror()と異なっているので注意。SuccessではなくUnknown errnoになる。

errno を使って適切な処理を実行する

次に、プログラムのなかで具体的にどのようにerrnoを使えるのか見ていく。

以下は、ファイルをopen()で開き、そのファイルディスクリプタを出力するプログラム。
open()は失敗すると-1を返すので、それをチェックすることで、「失敗したかどうか」は分かる。しかし「なぜ失敗したか」は分からない。

fn main() {
    let fd = unsafe { libc::open(b"foo.txt\0".as_ptr() as *const libc::c_char, libc::O_RDONLY) };
    if fd == -1 {
        // open の返り値である fd が -1 かどうかをチェックすることで、エラーが発生したかどうかは分かる
        // しかし、なぜエラーが発生したのかはわからない
        println!("Failed to open file");
        return;
    }
    println!("fd: {}", fd);
}

open()が失敗したとき、その理由に応じた番号がerrnoにセットされる。なので、それを使うことで、「なぜ失敗したか」が分かるようになる。

fn main() {
    let fd = unsafe { libc::open(b"foo.txt\0".as_ptr() as *const libc::c_char, libc::O_RDONLY) };
    if fd == -1 {
        let errno_address = unsafe { libc::__errno_location() };
        let errno = nix::errno::Errno::from_raw(unsafe { *errno_address });
        println!("{}", errno);
        return;
    }
    println!("fd: {}", fd);
}

筆者が用意した環境で上記のプログラムを実行すると、ENOENT: No such file or directoryと表示される。つまり、指定したファイルがなかったためopen()に失敗したことがわかる。

以下のコマンドで、foo.txtを作り、読み取り権限を外す。その状態で再度プログラムを実行してみる。

$ touch foo.txt
$ chmod 000 foo.txt

そうすると今度はEACCES: Permission deniedと表示される。
このように、errnoを使うことで、状況や原因を知ることができる。そしてerrnoの値に応じた処理を書くことで、適切なエラーハンドリングやロギングを実現できる。

fn main() {
    let fd = unsafe { libc::open(b"foo.txt\0".as_ptr() as *const libc::c_char, libc::O_RDONLY) };
    if fd == -1 {
        let errno = unsafe { *libc::__errno_location() };
        match errno {
            libc::ENOENT => {
                // ここにファイルが存在しない場合のロジックを書く
                println!("File not found");
            }
            libc::EACCES => {
                // ここにアクセス権がない場合のロジックを書く
                println!("Permission denied");
            }
            _ => {
                println!("Unknown error: {}", errno);
            }
        }
        return;
    }
    println!("fd: {}", fd);
}

errno が更新されるのは、「エラー発生時」のみではない

その名に反して、errnoが更新されるのはいわゆる「エラー」が発生したときのみではない。
例えば、対象のファイルディスクリプタがノンブロッキングモードのときにconnect()accept()を呼び出すと、すぐに完了できない場合は-1になり、errnoの値が更新される。
この状況は一般的な意味での「エラー」とは異なる。

ノンブロッキングモードにおけるconnect()accept()の挙動については、以下の記事を参照のこと。

numb86-tech.hatenablog.com




以上の内容はhttps://numb86-tech.hatenablog.com/entry/2026/02/22/195712より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14