Skip to content

Commit 5c89cf6

Browse files
authored
ndk: Use BorrowedFd and OwnedFd to clarify ownership transitions (#417)
Some functions consume a file descriptor (will close them on their own regard) or return ownership over a file descriptor (expect the caller to close it), but this is not always clarified in the documentation nor upheld by the caller. Use the new stabilized `BorrowedFd` and `OwnedFd` types since Rust 1.63 to clarify this in the API, noting that `OwnedFd` will instinctively `close()` the file descriptor on drop and doesn't implement `Copy` nor `Clone` (but does provide a `try_clone()` helper using `fcntl()` to create a new owned file descriptor if needed, and if possible by the kernel). For example, while not obvious from `AHardwareBuffer_lock()` docs (though there are hints in the [graphics sync docs]) the source for gralloc buffer locking many function calls down clarifies that the [`acquireFence` is indeed owned and will be closed]. The same [applies to `AImageReader` and its async aquire functions]. [graphics sync docs]: https://source.android.com/docs/core/graphics/sync [`acquireFence` is indeed owned and will be closed]: https://cs.android.com/android/platform/superproject/main/+/refs/heads/main:frameworks/native/libs/ui/Gralloc4.cpp;l=320-323;drc=34edaadf5297f2c066d2cb09a5cc9366dc35b24b [applies to `AImageReader` and its async aquire functions]: https://cs.android.com/android/platform/superproject/main/+/refs/heads/main:frameworks/av/media/ndk/NdkImageReader.cpp;l=498-501;drc=34edaadf5297f2c066d2cb09a5cc9366dc35b24b
1 parent 74d8d7e commit 5c89cf6

File tree

4 files changed

+108
-60
lines changed

4 files changed

+108
-60
lines changed

ndk/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- **Breaking:** media_codec: Add support for asynchronous notification callbacks. (#410)
2121
- Add panic guards to callbacks. (#412)
2222
- looper: Add `remove_fd()` to unregister events/callbacks for a file descriptor. (#416)
23+
- **Breaking:** Use `BorrowedFd` and `OwnedFd` to clarify possible ownership transitions. (#417)
2324
- **Breaking:** Upgrade to [`ndk-sys 0.5.0`](../ndk-sys/CHANGELOG.md#050-beta0-2023-08-15). (#420)
2425

2526
# 0.7.0 (2022-07-24)

ndk/src/hardware_buffer.rs

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@ use crate::utils::status_to_io_result;
99
pub use super::hardware_buffer_format::HardwareBufferFormat;
1010
use jni_sys::{jobject, JNIEnv};
1111
use std::{
12-
io::Result, mem::MaybeUninit, ops::Deref, os::raw::c_void, os::unix::io::RawFd, ptr::NonNull,
12+
io::Result,
13+
mem::MaybeUninit,
14+
ops::Deref,
15+
os::{
16+
raw::c_void,
17+
// TODO: Import from std::os::fd::{} since Rust 1.66
18+
unix::io::{AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd},
19+
},
20+
ptr::NonNull,
1321
};
1422

1523
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -263,10 +271,10 @@ impl HardwareBuffer {
263271
pub fn lock(
264272
&self,
265273
usage: HardwareBufferUsage,
266-
fence: Option<RawFd>,
274+
fence: Option<OwnedFd>,
267275
rect: Option<Rect>,
268276
) -> Result<*mut c_void> {
269-
let fence = fence.unwrap_or(-1);
277+
let fence = fence.map_or(-1, IntoRawFd::into_raw_fd);
270278
let rect = match rect {
271279
Some(rect) => &rect,
272280
None => std::ptr::null(),
@@ -287,10 +295,10 @@ impl HardwareBuffer {
287295
pub fn lock_and_get_info(
288296
&self,
289297
usage: HardwareBufferUsage,
290-
fence: Option<RawFd>,
298+
fence: Option<OwnedFd>,
291299
rect: Option<Rect>,
292300
) -> Result<LockedPlaneInfo> {
293-
let fence = fence.unwrap_or(-1);
301+
let fence = fence.map_or(-1, IntoRawFd::into_raw_fd);
294302
let rect = match rect {
295303
Some(rect) => &rect,
296304
None => std::ptr::null(),
@@ -339,10 +347,10 @@ impl HardwareBuffer {
339347
pub fn lock_planes(
340348
&self,
341349
usage: HardwareBufferUsage,
342-
fence: Option<RawFd>,
350+
fence: Option<OwnedFd>,
343351
rect: Option<Rect>,
344352
) -> Result<HardwareBufferPlanes> {
345-
let fence = fence.unwrap_or(-1);
353+
let fence = fence.map_or(-1, IntoRawFd::into_raw_fd);
346354
let rect = match rect {
347355
Some(rect) => &rect,
348356
None => std::ptr::null(),
@@ -373,32 +381,38 @@ impl HardwareBuffer {
373381
/// [`None`] if unlocking is already finished. The caller is responsible for closing the file
374382
/// descriptor once it's no longer needed. See [`unlock()`][Self::unlock()] for a variant that
375383
/// blocks instead.
376-
pub fn unlock_async(&self) -> Result<Option<RawFd>> {
384+
pub fn unlock_async(&self) -> Result<Option<OwnedFd>> {
377385
let fence = construct(|res| unsafe { ffi::AHardwareBuffer_unlock(self.as_ptr(), res) })?;
378386
Ok(match fence {
379387
-1 => None,
380-
fence => Some(fence),
388+
fence => Some(unsafe { OwnedFd::from_raw_fd(fence) }),
381389
})
382390
}
383391

384392
/// Receive a [`HardwareBuffer`] from an `AF_UNIX` socket.
385393
///
386-
/// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] in Rust.
387-
pub fn recv_handle_from_unix_socket(socket_fd: RawFd) -> Result<Self> {
394+
/// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] and
395+
/// [`std::os::unix::net::UnixStream`] in Rust and have a corresponding
396+
/// [`std::os::unix::io::AsFd::as_fd()`] implementation.
397+
pub fn recv_handle_from_unix_socket(socket_fd: BorrowedFd<'_>) -> Result<Self> {
388398
unsafe {
389-
let ptr =
390-
construct(|res| ffi::AHardwareBuffer_recvHandleFromUnixSocket(socket_fd, res))?;
399+
let ptr = construct(|res| {
400+
ffi::AHardwareBuffer_recvHandleFromUnixSocket(socket_fd.as_raw_fd(), res)
401+
})?;
391402

392403
Ok(Self::from_ptr(NonNull::new_unchecked(ptr)))
393404
}
394405
}
395406

396407
/// Send the [`HardwareBuffer`] to an `AF_UNIX` socket.
397408
///
398-
/// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] in Rust.
399-
pub fn send_handle_to_unix_socket(&self, socket_fd: RawFd) -> Result<()> {
400-
let status =
401-
unsafe { ffi::AHardwareBuffer_sendHandleToUnixSocket(self.as_ptr(), socket_fd) };
409+
/// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] and
410+
/// [`std::os::unix::net::UnixStream`] in Rust and have a corresponding
411+
/// [`std::os::unix::io::AsFd::as_fd()`] implementation.
412+
pub fn send_handle_to_unix_socket(&self, socket_fd: BorrowedFd<'_>) -> Result<()> {
413+
let status = unsafe {
414+
ffi::AHardwareBuffer_sendHandleToUnixSocket(self.as_ptr(), socket_fd.as_raw_fd())
415+
};
402416
status_to_io_result(status, ())
403417
}
404418

ndk/src/looper.rs

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
use bitflags::bitflags;
1313
use std::mem::ManuallyDrop;
1414
use std::os::raw::c_void;
15-
use std::os::unix::io::RawFd;
15+
// TODO: Import from std::os::fd::{} since Rust 1.66
16+
use std::os::unix::io::{AsRawFd, BorrowedFd, RawFd};
1617
use std::ptr;
17-
use std::ptr::NonNull;
1818
use std::time::Duration;
1919
use thiserror::Error;
2020

@@ -42,7 +42,7 @@ bitflags! {
4242

4343
/// The poll result from a [`ThreadLooper`].
4444
#[derive(Debug)]
45-
pub enum Poll {
45+
pub enum Poll<'fd> {
4646
/// This looper was woken using [`ForeignLooper::wake()`]
4747
Wake,
4848
/// For [`ThreadLooper::poll_once*()`][ThreadLooper::poll_once()], an event was received and processed using a callback.
@@ -52,7 +52,10 @@ pub enum Poll {
5252
/// An event was received
5353
Event {
5454
ident: i32,
55-
fd: RawFd,
55+
/// # Safety
56+
/// The caller should guarantee that this file descriptor remains open after it was added
57+
/// via [`ForeignLooper::add_fd()`] or [`ForeignLooper::add_fd_with_callback()`].
58+
fd: BorrowedFd<'fd>,
5659
events: FdEvent,
5760
data: *mut c_void,
5861
},
@@ -67,7 +70,7 @@ impl ThreadLooper {
6770
pub fn prepare() -> Self {
6871
unsafe {
6972
let ptr = ffi::ALooper_prepare(ffi::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as _);
70-
let foreign = ForeignLooper::from_ptr(NonNull::new(ptr).expect("looper non null"));
73+
let foreign = ForeignLooper::from_ptr(ptr::NonNull::new(ptr).expect("looper non null"));
7174
Self {
7275
_marker: std::marker::PhantomData,
7376
foreign,
@@ -83,9 +86,11 @@ impl ThreadLooper {
8386
})
8487
}
8588

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

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

112-
/// Polls the looper, blocking on processing an event, but with a timeout. Give a timeout of 0
113-
/// to make this non-blocking.
119+
/// Polls the looper, blocking on processing an event, but with a timeout. Give a timeout of
120+
/// [`Duration::ZERO`] to make this non-blocking.
114121
///
115122
/// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25
116123
/// days).
117124
#[inline]
118-
pub fn poll_once_timeout(&self, timeout: Duration) -> Result<Poll, LooperError> {
125+
pub fn poll_once_timeout(&self, timeout: Duration) -> Result<Poll<'_>, LooperError> {
119126
self.poll_once_ms(
120127
timeout
121128
.as_millis()
@@ -124,17 +131,23 @@ impl ThreadLooper {
124131
)
125132
}
126133

127-
fn poll_all_ms(&self, ms: i32) -> Result<Poll, LooperError> {
128-
let mut fd: RawFd = -1;
129-
let mut events: i32 = -1;
134+
/// Repeatedly polls the looper, blocking on processing an event, but with a timeout in
135+
/// milliseconds. Give a timeout of `0` to make this non-blocking.
136+
///
137+
/// This function will never return [`Poll::Callback`].
138+
fn poll_all_ms(&self, ms: i32) -> Result<Poll<'_>, LooperError> {
139+
let mut fd = -1;
140+
let mut events = -1;
130141
let mut data: *mut c_void = ptr::null_mut();
131142
match unsafe { ffi::ALooper_pollAll(ms, &mut fd, &mut events, &mut data) } {
132143
ffi::ALOOPER_POLL_WAKE => Ok(Poll::Wake),
133144
ffi::ALOOPER_POLL_TIMEOUT => Ok(Poll::Timeout),
134145
ffi::ALOOPER_POLL_ERROR => Err(LooperError),
135146
ident if ident >= 0 => Ok(Poll::Event {
136147
ident,
137-
fd,
148+
// SAFETY: Even though this FD at least shouldn't outlive self, a user could have
149+
// closed it after calling add_fd or add_fd_with_callback.
150+
fd: unsafe { BorrowedFd::borrow_raw(fd) },
138151
events: FdEvent::from_bits(events as u32)
139152
.expect("poll event contains unknown bits"),
140153
data,
@@ -147,19 +160,19 @@ impl ThreadLooper {
147160
///
148161
/// This function will never return [`Poll::Callback`].
149162
#[inline]
150-
pub fn poll_all(&self) -> Result<Poll, LooperError> {
163+
pub fn poll_all(&self) -> Result<Poll<'_>, LooperError> {
151164
self.poll_all_ms(-1)
152165
}
153166

154-
/// Repeatedly polls the looper, blocking on processing an event, but with a timeout. Give a
155-
/// timeout of 0 to make this non-blocking.
167+
/// Repeatedly polls the looper, blocking on processing an event, but with a timeout. Give a
168+
/// timeout of [`Duration::ZERO`] to make this non-blocking.
156169
///
157170
/// This function will never return [`Poll::Callback`].
158171
///
159172
/// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25
160173
/// days).
161174
#[inline]
162-
pub fn poll_all_timeout(&self, timeout: Duration) -> Result<Poll, LooperError> {
175+
pub fn poll_all_timeout(&self, timeout: Duration) -> Result<Poll<'_>, LooperError> {
163176
self.poll_all_ms(
164177
timeout
165178
.as_millis()
@@ -183,7 +196,7 @@ impl ThreadLooper {
183196
/// [`ALooper *`]: https://developer.android.com/ndk/reference/group/looper#alooper
184197
#[derive(Debug)]
185198
pub struct ForeignLooper {
186-
ptr: NonNull<ffi::ALooper>,
199+
ptr: ptr::NonNull<ffi::ALooper>,
187200
}
188201

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

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

225239
/// Returns a pointer to the NDK `ALooper` object.
226240
#[inline]
227-
pub fn ptr(&self) -> NonNull<ffi::ALooper> {
241+
pub fn ptr(&self) -> ptr::NonNull<ffi::ALooper> {
228242
self.ptr
229243
}
230244

@@ -237,21 +251,26 @@ impl ForeignLooper {
237251
///
238252
/// See also [the NDK
239253
/// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd).
254+
///
255+
/// # Safety
256+
/// The caller should guarantee that this file descriptor stays open until it is removed via
257+
/// [`remove_fd()`][Self::remove_fd()], and for however long the caller wishes to use this file
258+
/// descriptor when it is returned in [`Poll::Event::fd`].
240259
241260
// `ALooper_addFd` won't dereference `data`; it will only pass it on to the event.
242261
// Optionally dereferencing it there already enforces `unsafe` context.
243262
#[allow(clippy::not_unsafe_ptr_arg_deref)]
244263
pub fn add_fd(
245264
&self,
246-
fd: RawFd,
265+
fd: BorrowedFd<'_>,
247266
ident: i32,
248267
events: FdEvent,
249268
data: *mut c_void,
250269
) -> Result<(), LooperError> {
251270
match unsafe {
252271
ffi::ALooper_addFd(
253272
self.ptr.as_ptr(),
254-
fd,
273+
fd.as_raw_fd(),
255274
ident,
256275
events.bits() as i32,
257276
None,
@@ -274,20 +293,25 @@ impl ForeignLooper {
274293
///
275294
/// Note that this will leak a [`Box`] unless the callback returns [`false`] to unregister
276295
/// itself.
277-
pub fn add_fd_with_callback<F: FnMut(RawFd) -> bool>(
296+
///
297+
/// # Safety
298+
/// The caller should guarantee that this file descriptor stays open until it is removed via
299+
/// [`remove_fd()`][Self::remove_fd()] or by returning [`false`] from the callback, and for
300+
/// however long the caller wishes to use this file descriptor inside and after the callback.
301+
pub fn add_fd_with_callback<F: FnMut(BorrowedFd<'_>) -> bool>(
278302
&self,
279-
fd: RawFd,
303+
fd: BorrowedFd<'_>,
280304
events: FdEvent,
281305
callback: F,
282306
) -> Result<(), LooperError> {
283-
extern "C" fn cb_handler<F: FnMut(RawFd) -> bool>(
307+
extern "C" fn cb_handler<F: FnMut(BorrowedFd<'_>) -> bool>(
284308
fd: RawFd,
285309
_events: i32,
286310
data: *mut c_void,
287311
) -> i32 {
288312
unsafe {
289313
let mut cb = ManuallyDrop::new(Box::<F>::from_raw(data as *mut _));
290-
let keep_registered = cb(fd);
314+
let keep_registered = cb(BorrowedFd::borrow_raw(fd));
291315
if !keep_registered {
292316
ManuallyDrop::into_inner(cb);
293317
}
@@ -298,7 +322,7 @@ impl ForeignLooper {
298322
match unsafe {
299323
ffi::ALooper_addFd(
300324
self.ptr.as_ptr(),
301-
fd,
325+
fd.as_raw_fd(),
302326
ffi::ALOOPER_POLL_CALLBACK,
303327
events.bits() as i32,
304328
Some(cb_handler::<F>),
@@ -328,8 +352,8 @@ impl ForeignLooper {
328352
/// Note that unregistering a file descriptor with callback will leak a [`Box`] created in
329353
/// [`add_fd_with_callback()`][Self::add_fd_with_callback()]. Consider returning [`false`]
330354
/// from the callback instead to drop it.
331-
pub fn remove_fd(&self, fd: RawFd) -> Result<bool, LooperError> {
332-
match unsafe { ffi::ALooper_removeFd(self.ptr.as_ptr(), fd) } {
355+
pub fn remove_fd(&self, fd: BorrowedFd<'_>) -> Result<bool, LooperError> {
356+
match unsafe { ffi::ALooper_removeFd(self.ptr.as_ptr(), fd.as_raw_fd()) } {
333357
1 => Ok(true),
334358
0 => Ok(false),
335359
-1 => Err(LooperError),

0 commit comments

Comments
 (0)