Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ndk: Use BorrowedFd and OwnedFd to clarify ownership transitions #417

Merged
merged 1 commit into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ndk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- **Breaking:** media_codec: Add support for asynchronous notification callbacks. (#410)
- Add panic guards to callbacks. (#412)
- looper: Add `remove_fd()` to unregister events/callbacks for a file descriptor. (#416)
- **Breaking:** Use `BorrowedFd` and `OwnedFd` to clarify possible ownership transitions. (#417)
- **Breaking:** Upgrade to [`ndk-sys 0.5.0`](../ndk-sys/CHANGELOG.md#050-beta0-2023-08-15). (#420)

# 0.7.0 (2022-07-24)
Expand Down
48 changes: 31 additions & 17 deletions ndk/src/hardware_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ use crate::utils::status_to_io_result;
pub use super::hardware_buffer_format::HardwareBufferFormat;
use jni_sys::{jobject, JNIEnv};
use std::{
io::Result, mem::MaybeUninit, ops::Deref, os::raw::c_void, os::unix::io::RawFd, ptr::NonNull,
io::Result,
mem::MaybeUninit,
ops::Deref,
os::{
raw::c_void,
// TODO: Import from std::os::fd::{} since Rust 1.66
unix::io::{AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd},
},
ptr::NonNull,
};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -263,10 +271,10 @@ impl HardwareBuffer {
pub fn lock(
&self,
usage: HardwareBufferUsage,
fence: Option<RawFd>,
fence: Option<OwnedFd>,
rect: Option<Rect>,
) -> Result<*mut c_void> {
let fence = fence.unwrap_or(-1);
let fence = fence.map_or(-1, IntoRawFd::into_raw_fd);
let rect = match rect {
Some(rect) => &rect,
None => std::ptr::null(),
Expand All @@ -287,10 +295,10 @@ impl HardwareBuffer {
pub fn lock_and_get_info(
&self,
usage: HardwareBufferUsage,
fence: Option<RawFd>,
fence: Option<OwnedFd>,
rect: Option<Rect>,
) -> Result<LockedPlaneInfo> {
let fence = fence.unwrap_or(-1);
let fence = fence.map_or(-1, IntoRawFd::into_raw_fd);
let rect = match rect {
Some(rect) => &rect,
None => std::ptr::null(),
Expand Down Expand Up @@ -339,10 +347,10 @@ impl HardwareBuffer {
pub fn lock_planes(
&self,
usage: HardwareBufferUsage,
fence: Option<RawFd>,
fence: Option<OwnedFd>,
rect: Option<Rect>,
) -> Result<HardwareBufferPlanes> {
let fence = fence.unwrap_or(-1);
let fence = fence.map_or(-1, IntoRawFd::into_raw_fd);
let rect = match rect {
Some(rect) => &rect,
None => std::ptr::null(),
Expand Down Expand Up @@ -373,32 +381,38 @@ impl HardwareBuffer {
/// [`None`] if unlocking is already finished. The caller is responsible for closing the file
/// descriptor once it's no longer needed. See [`unlock()`][Self::unlock()] for a variant that
/// blocks instead.
pub fn unlock_async(&self) -> Result<Option<RawFd>> {
pub fn unlock_async(&self) -> Result<Option<OwnedFd>> {
let fence = construct(|res| unsafe { ffi::AHardwareBuffer_unlock(self.as_ptr(), res) })?;
Ok(match fence {
-1 => None,
fence => Some(fence),
fence => Some(unsafe { OwnedFd::from_raw_fd(fence) }),
})
}

/// Receive a [`HardwareBuffer`] from an `AF_UNIX` socket.
///
/// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] in Rust.
pub fn recv_handle_from_unix_socket(socket_fd: RawFd) -> Result<Self> {
/// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] and
/// [`std::os::unix::net::UnixStream`] in Rust and have a corresponding
/// [`std::os::unix::io::AsFd::as_fd()`] implementation.
pub fn recv_handle_from_unix_socket(socket_fd: BorrowedFd<'_>) -> Result<Self> {
unsafe {
let ptr =
construct(|res| ffi::AHardwareBuffer_recvHandleFromUnixSocket(socket_fd, res))?;
let ptr = construct(|res| {
ffi::AHardwareBuffer_recvHandleFromUnixSocket(socket_fd.as_raw_fd(), res)
})?;

Ok(Self::from_ptr(NonNull::new_unchecked(ptr)))
}
}

/// Send the [`HardwareBuffer`] to an `AF_UNIX` socket.
///
/// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] in Rust.
pub fn send_handle_to_unix_socket(&self, socket_fd: RawFd) -> Result<()> {
let status =
unsafe { ffi::AHardwareBuffer_sendHandleToUnixSocket(self.as_ptr(), socket_fd) };
/// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] and
/// [`std::os::unix::net::UnixStream`] in Rust and have a corresponding
/// [`std::os::unix::io::AsFd::as_fd()`] implementation.
pub fn send_handle_to_unix_socket(&self, socket_fd: BorrowedFd<'_>) -> Result<()> {
let status = unsafe {
ffi::AHardwareBuffer_sendHandleToUnixSocket(self.as_ptr(), socket_fd.as_raw_fd())
};
status_to_io_result(status, ())
}

Expand Down
92 changes: 58 additions & 34 deletions ndk/src/looper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
use bitflags::bitflags;
use std::mem::ManuallyDrop;
use std::os::raw::c_void;
use std::os::unix::io::RawFd;
// TODO: Import from std::os::fd::{} since Rust 1.66
use std::os::unix::io::{AsRawFd, BorrowedFd, RawFd};
use std::ptr;
use std::ptr::NonNull;
use std::time::Duration;
use thiserror::Error;

Expand Down Expand Up @@ -42,7 +42,7 @@ bitflags! {

/// The poll result from a [`ThreadLooper`].
#[derive(Debug)]
pub enum Poll {
pub enum Poll<'fd> {
/// This looper was woken using [`ForeignLooper::wake()`]
Wake,
/// For [`ThreadLooper::poll_once*()`][ThreadLooper::poll_once()], an event was received and processed using a callback.
Expand All @@ -52,7 +52,10 @@ pub enum Poll {
/// An event was received
Event {
ident: i32,
fd: RawFd,
/// # Safety
/// The caller should guarantee that this file descriptor remains open after it was added
/// via [`ForeignLooper::add_fd()`] or [`ForeignLooper::add_fd_with_callback()`].
fd: BorrowedFd<'fd>,
Comment on lines +55 to +58
Copy link
Member Author

@MarijnS95 MarijnS95 Aug 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will think about this a bit more before merging (cc @rib).

This is an fd the user gave to us (as a BorrowedFd, though) so they're already required to make sure it isn't closed until it is removed from the Looper. Second, as written above the unsafe block that creates this:

                // SAFETY: Even though this FD at least shouldn't outlive self, a user could have
                // closed it after calling add_fd or add_fd_with_callback.

There's barely any relevance to self here; the user can totally keep using their own fd outside the scope of Looper, we really have no clue what its lifetime should be.

Most importantly, let's check if winit can easily handle the lifetime that we've now added to Poll<'fd>.

events: FdEvent,
data: *mut c_void,
},
Expand All @@ -67,7 +70,7 @@ impl ThreadLooper {
pub fn prepare() -> Self {
unsafe {
let ptr = ffi::ALooper_prepare(ffi::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as _);
let foreign = ForeignLooper::from_ptr(NonNull::new(ptr).expect("looper non null"));
let foreign = ForeignLooper::from_ptr(ptr::NonNull::new(ptr).expect("looper non null"));
Self {
_marker: std::marker::PhantomData,
foreign,
Expand All @@ -83,9 +86,11 @@ impl ThreadLooper {
})
}

fn poll_once_ms(&self, ms: i32) -> Result<Poll, LooperError> {
let mut fd: RawFd = -1;
let mut events: i32 = -1;
/// Polls the looper, blocking on processing an event, but with a timeout in milliseconds.
/// Give a timeout of `0` to make this non-blocking.
fn poll_once_ms(&self, ms: i32) -> Result<Poll<'_>, LooperError> {
let mut fd = -1;
let mut events = -1;
let mut data: *mut c_void = ptr::null_mut();
match unsafe { ffi::ALooper_pollOnce(ms, &mut fd, &mut events, &mut data) } {
ffi::ALOOPER_POLL_WAKE => Ok(Poll::Wake),
Expand All @@ -94,7 +99,9 @@ impl ThreadLooper {
ffi::ALOOPER_POLL_ERROR => Err(LooperError),
ident if ident >= 0 => Ok(Poll::Event {
ident,
fd,
// SAFETY: Even though this FD at least shouldn't outlive self, a user could have
// closed it after calling add_fd or add_fd_with_callback.
fd: unsafe { BorrowedFd::borrow_raw(fd) },
events: FdEvent::from_bits(events as u32)
.expect("poll event contains unknown bits"),
data,
Expand All @@ -105,17 +112,17 @@ impl ThreadLooper {

/// Polls the looper, blocking on processing an event.
#[inline]
pub fn poll_once(&self) -> Result<Poll, LooperError> {
pub fn poll_once(&self) -> Result<Poll<'_>, LooperError> {
self.poll_once_ms(-1)
}

/// Polls the looper, blocking on processing an event, but with a timeout. Give a timeout of 0
/// to make this non-blocking.
/// Polls the looper, blocking on processing an event, but with a timeout. Give a timeout of
/// [`Duration::ZERO`] to make this non-blocking.
///
/// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25
/// days).
#[inline]
pub fn poll_once_timeout(&self, timeout: Duration) -> Result<Poll, LooperError> {
pub fn poll_once_timeout(&self, timeout: Duration) -> Result<Poll<'_>, LooperError> {
self.poll_once_ms(
timeout
.as_millis()
Expand All @@ -124,17 +131,23 @@ impl ThreadLooper {
)
}

fn poll_all_ms(&self, ms: i32) -> Result<Poll, LooperError> {
let mut fd: RawFd = -1;
let mut events: i32 = -1;
/// Repeatedly polls the looper, blocking on processing an event, but with a timeout in
/// milliseconds. Give a timeout of `0` to make this non-blocking.
///
/// This function will never return [`Poll::Callback`].
fn poll_all_ms(&self, ms: i32) -> Result<Poll<'_>, LooperError> {
let mut fd = -1;
let mut events = -1;
let mut data: *mut c_void = ptr::null_mut();
match unsafe { ffi::ALooper_pollAll(ms, &mut fd, &mut events, &mut data) } {
ffi::ALOOPER_POLL_WAKE => Ok(Poll::Wake),
ffi::ALOOPER_POLL_TIMEOUT => Ok(Poll::Timeout),
ffi::ALOOPER_POLL_ERROR => Err(LooperError),
ident if ident >= 0 => Ok(Poll::Event {
ident,
fd,
// SAFETY: Even though this FD at least shouldn't outlive self, a user could have
// closed it after calling add_fd or add_fd_with_callback.
fd: unsafe { BorrowedFd::borrow_raw(fd) },
events: FdEvent::from_bits(events as u32)
.expect("poll event contains unknown bits"),
data,
Expand All @@ -147,19 +160,19 @@ impl ThreadLooper {
///
/// This function will never return [`Poll::Callback`].
#[inline]
pub fn poll_all(&self) -> Result<Poll, LooperError> {
pub fn poll_all(&self) -> Result<Poll<'_>, LooperError> {
self.poll_all_ms(-1)
}

/// Repeatedly polls the looper, blocking on processing an event, but with a timeout. Give a
/// timeout of 0 to make this non-blocking.
/// Repeatedly polls the looper, blocking on processing an event, but with a timeout. Give a
/// timeout of [`Duration::ZERO`] to make this non-blocking.
///
/// This function will never return [`Poll::Callback`].
///
/// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25
/// days).
#[inline]
pub fn poll_all_timeout(&self, timeout: Duration) -> Result<Poll, LooperError> {
pub fn poll_all_timeout(&self, timeout: Duration) -> Result<Poll<'_>, LooperError> {
self.poll_all_ms(
timeout
.as_millis()
Expand All @@ -183,7 +196,7 @@ impl ThreadLooper {
/// [`ALooper *`]: https://developer.android.com/ndk/reference/group/looper#alooper
#[derive(Debug)]
pub struct ForeignLooper {
ptr: NonNull<ffi::ALooper>,
ptr: ptr::NonNull<ffi::ALooper>,
}

unsafe impl Send for ForeignLooper {}
Expand All @@ -208,7 +221,8 @@ impl ForeignLooper {
/// Returns the looper associated with the current thread, if any.
#[inline]
pub fn for_thread() -> Option<Self> {
NonNull::new(unsafe { ffi::ALooper_forThread() }).map(|ptr| unsafe { Self::from_ptr(ptr) })
ptr::NonNull::new(unsafe { ffi::ALooper_forThread() })
.map(|ptr| unsafe { Self::from_ptr(ptr) })
}

/// Construct a [`ForeignLooper`] object from the given pointer.
Expand All @@ -217,14 +231,14 @@ impl ForeignLooper {
/// By calling this function, you guarantee that the pointer is a valid, non-null pointer to an
/// NDK [`ffi::ALooper`].
#[inline]
pub unsafe fn from_ptr(ptr: NonNull<ffi::ALooper>) -> Self {
pub unsafe fn from_ptr(ptr: ptr::NonNull<ffi::ALooper>) -> Self {
ffi::ALooper_acquire(ptr.as_ptr());
Self { ptr }
}

/// Returns a pointer to the NDK `ALooper` object.
#[inline]
pub fn ptr(&self) -> NonNull<ffi::ALooper> {
pub fn ptr(&self) -> ptr::NonNull<ffi::ALooper> {
self.ptr
}

Expand All @@ -237,21 +251,26 @@ impl ForeignLooper {
///
/// See also [the NDK
/// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd).
///
/// # Safety
/// The caller should guarantee that this file descriptor stays open until it is removed via
/// [`remove_fd()`][Self::remove_fd()], and for however long the caller wishes to use this file
/// descriptor when it is returned in [`Poll::Event::fd`].

// `ALooper_addFd` won't dereference `data`; it will only pass it on to the event.
// Optionally dereferencing it there already enforces `unsafe` context.
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn add_fd(
&self,
fd: RawFd,
fd: BorrowedFd<'_>,
ident: i32,
events: FdEvent,
data: *mut c_void,
) -> Result<(), LooperError> {
match unsafe {
ffi::ALooper_addFd(
self.ptr.as_ptr(),
fd,
fd.as_raw_fd(),
ident,
events.bits() as i32,
None,
Expand All @@ -274,20 +293,25 @@ impl ForeignLooper {
///
/// Note that this will leak a [`Box`] unless the callback returns [`false`] to unregister
/// itself.
pub fn add_fd_with_callback<F: FnMut(RawFd) -> bool>(
///
/// # Safety
/// The caller should guarantee that this file descriptor stays open until it is removed via
/// [`remove_fd()`][Self::remove_fd()] or by returning [`false`] from the callback, and for
/// however long the caller wishes to use this file descriptor inside and after the callback.
pub fn add_fd_with_callback<F: FnMut(BorrowedFd<'_>) -> bool>(
&self,
fd: RawFd,
fd: BorrowedFd<'_>,
events: FdEvent,
callback: F,
) -> Result<(), LooperError> {
extern "C" fn cb_handler<F: FnMut(RawFd) -> bool>(
extern "C" fn cb_handler<F: FnMut(BorrowedFd<'_>) -> bool>(
fd: RawFd,
_events: i32,
data: *mut c_void,
) -> i32 {
unsafe {
let mut cb = ManuallyDrop::new(Box::<F>::from_raw(data as *mut _));
let keep_registered = cb(fd);
let keep_registered = cb(BorrowedFd::borrow_raw(fd));
if !keep_registered {
ManuallyDrop::into_inner(cb);
}
Expand All @@ -298,7 +322,7 @@ impl ForeignLooper {
match unsafe {
ffi::ALooper_addFd(
self.ptr.as_ptr(),
fd,
fd.as_raw_fd(),
ffi::ALOOPER_POLL_CALLBACK,
events.bits() as i32,
Some(cb_handler::<F>),
Expand Down Expand Up @@ -328,8 +352,8 @@ impl ForeignLooper {
/// Note that unregistering a file descriptor with callback will leak a [`Box`] created in
/// [`add_fd_with_callback()`][Self::add_fd_with_callback()]. Consider returning [`false`]
/// from the callback instead to drop it.
pub fn remove_fd(&self, fd: RawFd) -> Result<bool, LooperError> {
match unsafe { ffi::ALooper_removeFd(self.ptr.as_ptr(), fd) } {
pub fn remove_fd(&self, fd: BorrowedFd<'_>) -> Result<bool, LooperError> {
match unsafe { ffi::ALooper_removeFd(self.ptr.as_ptr(), fd.as_raw_fd()) } {
1 => Ok(true),
0 => Ok(false),
-1 => Err(LooperError),
Expand Down
Loading