Safely wrapping libc's `__errno_location` function

Hi folks!

The __errno_location exposed by libc returns a raw pointer to the calling thread's last error code. Let's say I'm trying to create a safe wrapper for that function.

type Errno = i32;

extern "C" {
    fn __errno_location() -> *mut Errno;
}

fn get_last_error() -> Errno {
    // SAFETY:
    //  The only way to safely access the referenced errno is to use either
    //  `get_last_error` or `set_last_error`, ensuring that no one currently
    //  holds a mutable reference to the underlying value.
    unsafe { *libc::__errno_location() }
}

fn set_last_error(code: Errno) {
    // SAFETY:
    //  The only way to safely access the referenced errno is to use either
    //  `set_last_error` or `get_last_error`, ensuring that no one currently
    //  holds any reference to the underlying value.
    unsafe { *libc::__errno_location() = code };
}

But this pattern reminds me a lot of what Cell<T> do. They either copy the value, or modify it without giving out references. And Cell<T> needs an UnsafeCell<T>. Is the above code sound? It seems to emulate a Cell<T>, but does not seem to need an UnsafeCell<T>.

Would something like that be more correct?

fn get_errno_cell() -> &'static Cell<Errno> {
    // SAFETY:
    //  The `get_errno_cell` function is the only safe way to access the
    //  underlying value, ensuring that the implied `Cell<T>` "logically
    //  owns" the referenced value.
    unsafe { &*(libc::__errno_location() as *const Cell<Errno>) }
}

fn get_last_error() -> Errno {
    get_errno_cell().get()
}

fn set_last_error(err: Errno) {
    get_errno_cell().set(err);
}

Raw pointers are considered an interior mutability primitive, just like UnsafeCell. Therefore the compiler is not allowed to assume that a value behind a *mut T (or a *const T, for that matter!) does not change.1


1Well, this is not entirely true; it is true when the pointed value is not otherwise subject to the usual aliasing/mutability rules of Rust, for example because it is managed entirely by FFI. See Footnote 2 here for an example where only using a raw pointer does not exempt you from the rules, and mutation of a declared-immutable Rust variable still causes UB.

I'm guessing this has to do with the fact that a raw pointer still means indirection, while UnsafeCell doesn't – the latter contains the owned value directly in itself, so that by-value let declarations of type UnsafeCell can still reasonably be checked by the compiler, which is not the case when using a raw pointer.

6 Likes

fn errno() -> &'static Cell<i32> is probably unsound, because the errno location is a thread local. A &'static is thus incorrect, because the memory location will go away when the thread terminates. This is why std thread locals require the closure access pattern for LocalKey.

It might be sound because &Cell<i32> is locked to the owning thread and prevents you from getting access to a handle which could escape to another thread. But I wouldn't bet on it.

I'd stick to fn get_errno() -> i32 and fn set_errno(i32); it's nearly the identical API you'd get with &Cell<i32> anyway.

Here's where std does it for the unix OSes..

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.