Skip to main content

block_server/
c_interface.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use super::{Operation, RequestId, TraceFlowId};
6use crate::{IntoOrchestrator, callback_interface};
7use fidl::endpoints::RequestStream;
8use fidl_fuchsia_storage_block as fblock;
9use fidl_fuchsia_storage_block::MAX_TRANSFER_UNBOUNDED;
10use fuchsia_async as fasync;
11use fuchsia_sync::{Condvar, Mutex};
12use futures::stream::{AbortHandle, Abortable};
13use std::borrow::{Borrow, Cow};
14use std::ffi::{CStr, c_char, c_void};
15use std::num::NonZero;
16use std::sync::Arc;
17
18/// cbindgen:no-export
19pub type Session = callback_interface::Session<InterfaceAdapter>;
20
21#[repr(C)]
22pub struct Callbacks {
23    /// An opaque context object retained by this library.  The library will pass this back into all
24    /// callbacks.  The memory pointed to by `context` must last until [`block_server_delete`] is
25    /// called.
26    pub context: *mut c_void,
27    /// Starts a thread.  The implementation must call [`block_server_thread`] on this newly created
28    /// thread, providing `arg`.  Once this completes, the implementation must NOT use the block
29    /// server for which the thread was started, as it could be destroyed at any time.
30    /// The implementation must call [`block_server_thread_release`] after [`block_server_thread`]
31    /// completes (but before [`block_server_delete`] is called).
32    pub start_thread: unsafe extern "C" fn(context: *mut c_void, arg: *const c_void),
33    /// Notifies the implementation of a new session.  The implementation must call
34    /// [`block_server_session_run`] on a separate thread, and must call
35    /// [`block_server_session_release`] after [`block_server_session_run`] (but before
36    /// [`block_server_delete`] is called).
37    pub on_new_session: unsafe extern "C" fn(context: *mut c_void, session: *const Session),
38    /// Submits a batch of requests to be handled by the implementation.  The implementation must
39    /// not retain references to `requests` after it returns.  The implementation must ensure that
40    /// [`block_server_send_reply`] is called exactly once with the request ID of each entry in
41    /// `requests`, regardless of its status; this call can be asynchronous but must occur before
42    /// [`block_server_delete`] is called.  Note that a reply must be sent for every request before
43    /// shutdown.
44    pub on_requests:
45        unsafe extern "C" fn(context: *mut c_void, requests: *mut Request, request_count: usize),
46    /// Logs `message` to the implementation's logger.  The implementation must not retain
47    /// references to `message`.
48    pub log: unsafe extern "C" fn(context: *mut c_void, message: *const c_char, message_len: usize),
49}
50
51impl Callbacks {
52    #[allow(dead_code)]
53    fn log(&self, msg: &str) {
54        let msg = msg.as_bytes();
55        // SAFETY: This is safe if `context` and `log` are good.
56        unsafe {
57            (self.log)(self.context, msg.as_ptr() as *const c_char, msg.len());
58        }
59    }
60}
61
62/// cbindgen:no-export
63#[allow(dead_code)]
64pub struct UnownedVmo(zx::sys::zx_handle_t);
65
66#[repr(C)]
67pub struct Request {
68    pub request_id: RequestId,
69    pub operation: Operation,
70    pub trace_flow_id: TraceFlowId,
71    pub vmo: UnownedVmo,
72}
73
74unsafe impl Send for Callbacks {}
75unsafe impl Sync for Callbacks {}
76
77/// Implements [`callback_interface::Interface`] using C callbacks.
78pub struct InterfaceAdapter {
79    callbacks: Callbacks,
80    info: super::DeviceInfo,
81}
82
83impl callback_interface::Interface for InterfaceAdapter {
84    type Orchestrator = Orchestrator;
85
86    fn get_info(&self) -> Cow<'_, super::DeviceInfo> {
87        Cow::Borrowed(&self.info)
88    }
89
90    fn spawn_session(&self, session: Arc<Session>) {
91        unsafe {
92            (self.callbacks.on_new_session)(self.callbacks.context, Arc::into_raw(session));
93        }
94    }
95
96    fn on_requests(&self, requests: &[callback_interface::Request]) {
97        let mut c_requests = Vec::with_capacity(requests.len());
98        for req in requests {
99            c_requests.push(Request {
100                request_id: req.request_id,
101                operation: req.operation.clone(),
102                trace_flow_id: req.trace_flow_id,
103                // We are handing out unowned references to the VMO here.  This is safe because the
104                // VMO bin holds references to any closed VMOs until all preceding operations have
105                // finished.
106                vmo: UnownedVmo(
107                    req.vmo.as_ref().map(|v| v.raw_handle()).unwrap_or(zx::sys::ZX_HANDLE_INVALID),
108                ),
109            });
110        }
111        unsafe {
112            (self.callbacks.on_requests)(
113                self.callbacks.context,
114                c_requests.as_mut_ptr(),
115                c_requests.len(),
116            )
117        }
118    }
119}
120
121#[repr(C)]
122pub struct PartitionInfo {
123    pub device_flags: u32,
124    pub start_block: u64,
125    pub block_count: u64,
126    pub block_size: u32,
127    pub type_guid: [u8; 16],
128    pub instance_guid: [u8; 16],
129    pub name: *const c_char,
130    pub flags: u64,
131    pub max_transfer_size: u32,
132}
133
134/// cbindgen:no-export
135#[allow(non_camel_case_types)]
136type zx_handle_t = zx::sys::zx_handle_t;
137
138/// cbindgen:no-export
139#[allow(non_camel_case_types)]
140type zx_status_t = zx::sys::zx_status_t;
141
142impl PartitionInfo {
143    /// # Safety
144    ///
145    /// [`self.name`] must point to valid, null-terminated C-string, or be a nullptr.
146    unsafe fn to_rust(&self) -> super::DeviceInfo {
147        super::DeviceInfo::Partition(super::PartitionInfo {
148            device_flags: fblock::DeviceFlag::from_bits_truncate(self.device_flags),
149            start_block_offset: Some(self.start_block),
150            block_count: self.block_count,
151            type_guid: self.type_guid,
152            instance_guid: self.instance_guid,
153            name: if self.name.is_null() {
154                "".to_string()
155            } else {
156                String::from_utf8_lossy(unsafe { CStr::from_ptr(self.name).to_bytes() }).to_string()
157            },
158            flags: Some(self.flags),
159            max_transfer_blocks: if self.max_transfer_size != MAX_TRANSFER_UNBOUNDED {
160                NonZero::new(self.max_transfer_size / self.block_size)
161            } else {
162                None
163            },
164        })
165    }
166}
167
168struct ExecutorMailbox(Mutex<Mail>, Condvar);
169
170impl ExecutorMailbox {
171    fn post(&self, mail: Mail) -> Mail {
172        let old = std::mem::replace(&mut *self.0.lock(), mail);
173        self.1.notify_all();
174        old
175    }
176
177    fn new() -> Self {
178        Self(Mutex::default(), Condvar::new())
179    }
180}
181
182type ShutdownCallback = unsafe extern "C" fn(*mut c_void);
183
184#[derive(Default)]
185enum Mail {
186    #[default]
187    None,
188    Initialized(fasync::ScopeHandle, AbortHandle),
189    AsyncShutdown(*const BlockServer, ShutdownCallback, *mut c_void),
190    ThreadFinished(*const BlockServer, ShutdownCallback, *mut c_void),
191    Finished,
192}
193
194// SAFETY: `Mail::AsyncShutdown` is thread-safe.
195unsafe impl Send for Mail {}
196
197pub struct Orchestrator {
198    session_manager: callback_interface::SessionManager<InterfaceAdapter>,
199    mbox: ExecutorMailbox,
200}
201
202impl IntoOrchestrator for Arc<Orchestrator> {
203    type SM = callback_interface::SessionManager<InterfaceAdapter>;
204
205    fn into_orchestrator(self) -> Arc<Orchestrator> {
206        self
207    }
208}
209
210impl Borrow<callback_interface::SessionManager<InterfaceAdapter>> for Orchestrator {
211    fn borrow(&self) -> &callback_interface::SessionManager<InterfaceAdapter> {
212        &self.session_manager
213    }
214}
215
216pub struct BlockServer {
217    server: super::BlockServer<callback_interface::SessionManager<InterfaceAdapter>>,
218    scope: fasync::ScopeHandle,
219    abort_handle: AbortHandle,
220    orchestrator: Arc<Orchestrator>,
221}
222
223/// Creates a new block server.  Returns nullptr on failure (e.g. if the thread to run the block
224/// server failed to start).
225///
226/// # Safety
227///
228/// All callbacks in `callbacks` must be safe.
229#[unsafe(no_mangle)]
230pub unsafe extern "C" fn block_server_new(
231    partition_info: &PartitionInfo,
232    callbacks: Callbacks,
233) -> *mut BlockServer {
234    let start_thread = callbacks.start_thread;
235    let context = callbacks.context;
236
237    let session_manager = callback_interface::SessionManager::new(Arc::new(InterfaceAdapter {
238        callbacks,
239        info: unsafe { partition_info.to_rust() },
240    }));
241
242    let orchestrator = Arc::new(Orchestrator { session_manager, mbox: ExecutorMailbox::new() });
243
244    unsafe {
245        (start_thread)(context, Arc::into_raw(orchestrator.clone()) as *const c_void);
246    }
247
248    let mbox = &orchestrator.mbox;
249    let mail = {
250        let mut mail = mbox.0.lock();
251        mbox.1.wait_while(&mut mail, |mail| matches!(mail, Mail::None));
252        std::mem::replace(&mut *mail, Mail::None)
253    };
254
255    let block_size = partition_info.block_size;
256    match mail {
257        Mail::Initialized(scope, abort_handle) => Box::into_raw(Box::new(BlockServer {
258            server: super::BlockServer::new(block_size, orchestrator.clone()),
259            scope,
260            abort_handle,
261            orchestrator: orchestrator.clone(),
262        })),
263        Mail::Finished => std::ptr::null_mut(),
264        _ => unreachable!(),
265    }
266}
267
268/// Runs the main loop to handle FIDL requests for the block server.  Blocks until the server is
269/// shutting down.
270///
271/// After this returns, the caller *must* call [`block_server_thread_release`] on the same thread.
272///
273/// # Safety
274///
275/// `arg` must be the value passed to the `start_thread` callback.
276#[unsafe(no_mangle)]
277pub unsafe extern "C" fn block_server_thread(arg: *const c_void) {
278    let orchestrator = unsafe { &*(arg as *const Orchestrator) };
279
280    let mut executor = fasync::LocalExecutor::default();
281    let scope = fasync::Scope::new();
282
283    // Create a future which will run until `abort_handle` is aborted, so that the scope will run
284    // that long as well.
285    let (abort_handle, registration) = AbortHandle::new_pair();
286    let root_task = scope.spawn(async move {
287        let _ = Abortable::new(std::future::pending::<()>(), registration).await;
288    });
289    orchestrator.mbox.post(Mail::Initialized(scope.clone(), abort_handle));
290
291    // Block until the abort handle is fired.  This is the main entry point, tasks are spawned on
292    // this executor.
293    let _ = executor.run_singlethreaded(root_task);
294
295    // At this point, the abort handle was fired, which happens when shutdown begins.
296    {
297        let mut mbox = orchestrator.mbox.0.lock();
298        let mail = std::mem::take(&mut *mbox);
299        if let Mail::AsyncShutdown(block_server, callback, arg) = mail {
300            *mbox = Mail::ThreadFinished(block_server, callback, arg);
301            orchestrator.mbox.1.notify_all();
302        } else {
303            *mbox = mail;
304        }
305    }
306
307    // Synchronously cancel the scope which is processing FIDL requests.
308    let _ = executor.run_singlethreaded(scope.cancel());
309
310    // No more sessions can be created.  Before we drop the `BlockServer` instance we must make
311    // sure there are no sessions running because otherwise there could be outstanding responses
312    // that would result in `block_server_send_reply` being called.
313    orchestrator.session_manager.terminate();
314}
315
316/// Called to release the thread.  This *must* always be called on the thread spawned by
317/// [`Callbacks::start_thread`], regardless of whether [`block_server_thread`] is called or not.
318///
319/// # Safety
320///
321/// `arg` must be the value passed to the `start_thread` callback.
322#[unsafe(no_mangle)]
323pub unsafe extern "C" fn block_server_thread_release(arg: *const c_void) {
324    // SAFETY: This balances the `into_raw` in `block_server_new`.
325    let orchestrator = unsafe { Arc::from_raw(arg as *const Orchestrator) };
326
327    let mail = orchestrator.mbox.post(Mail::Finished);
328    match mail {
329        Mail::None | Mail::Finished => {}
330        Mail::ThreadFinished(block_server, callback, arg) => {
331            // SAFETY: No other threads are running now, so it should be safe to drop the
332            // `BlockServer` instance.
333            let _ = unsafe { Box::from_raw(block_server as *mut BlockServer) };
334
335            // SAFETY: Whoever supplied the callback must guarantee it's safe.
336            unsafe {
337                callback(arg);
338            }
339        }
340        _ => panic!("block_server_thread_release called while thread is still running"),
341    }
342}
343
344/// # Safety
345///
346/// `block_server` must be valid and either `block_server_delete` or `block_server_delete_async` may
347/// only be called once.
348#[unsafe(no_mangle)]
349pub unsafe extern "C" fn block_server_delete(block_server: *const BlockServer) {
350    {
351        // SAFETY: The caller asserts that `block_server` is valid.
352        let server = unsafe { &*block_server };
353
354        // NOTE: The order here is important.  We must terminate the server's main thread first
355        // before terminating sessions to avoid races that can happen when a session has been just
356        // created.
357
358        // Start by terminating the main server thread.
359        server.abort_handle.abort();
360        {
361            let mbox = &server.orchestrator.mbox;
362            let mut mail = mbox.0.lock();
363            mbox.1.wait_while(&mut mail, |mbox| !matches!(mbox, Mail::Finished));
364        }
365
366        // Now that is done, no more sessions can be created, so now we can terminate all sessions.
367        Borrow::<callback_interface::SessionManager<InterfaceAdapter>>::borrow(
368            server.orchestrator.as_ref(),
369        )
370        .terminate();
371    }
372
373    // SAFETY: No other threads are running, so we can drop the `BlockServer` instance.
374    let _ = unsafe { Box::from_raw(block_server as *mut BlockServer) };
375}
376
377/// # Safety
378///
379/// `block_server` must be valid and either `block_server_delete` or `block_server_delete_async` may
380/// only be called once.
381#[unsafe(no_mangle)]
382pub unsafe extern "C" fn block_server_delete_async(
383    block_server: *const BlockServer,
384    callback: ShutdownCallback,
385    arg: *mut c_void,
386) {
387    let abort_handle = {
388        // SAFETY: The caller asserts that `block_server` is valid.
389        let server = unsafe { &*block_server };
390
391        // We must post to the mailbox before we call abort to ensure that the callback is correctly
392        // called.
393        assert!(!matches!(
394            server.orchestrator.mbox.post(Mail::AsyncShutdown(block_server, callback, arg)),
395            Mail::Finished
396        ));
397
398        server.abort_handle.clone()
399    };
400
401    // As soon as we call `abort`, we must assume the `BlockServer` instance has been dropped.
402    abort_handle.abort();
403}
404
405/// Serves the Volume protocol for this server.  `handle` is consumed.
406///
407/// # Safety
408///
409/// `block_server` and `handle` must be valid.
410#[unsafe(no_mangle)]
411pub unsafe extern "C" fn block_server_serve(block_server: *const BlockServer, handle: zx_handle_t) {
412    let block_server = unsafe { &*block_server };
413    let handle = unsafe { zx::NullableHandle::from_raw(handle) };
414    block_server.scope.spawn(async move {
415        let _ = block_server
416            .server
417            .handle_requests(fblock::BlockRequestStream::from_channel(
418                fasync::Channel::from_channel(handle.into()),
419            ))
420            .await;
421    });
422}
423
424/// # Safety
425///
426/// `session` must be valid.
427#[unsafe(no_mangle)]
428pub unsafe extern "C" fn block_server_session_run(session: &Session) {
429    let session = unsafe { Arc::from_raw(session) };
430    session.run();
431    let _ = Arc::into_raw(session);
432}
433
434/// # Safety
435///
436/// `session` must be valid.
437#[unsafe(no_mangle)]
438pub unsafe extern "C" fn block_server_session_release(session: &Session) {
439    session.terminate_async();
440    unsafe { Arc::from_raw(session) };
441}
442
443/// # Safety
444///
445/// `block_server` must be valid.
446#[unsafe(no_mangle)]
447pub unsafe extern "C" fn block_server_send_reply(
448    block_server: &BlockServer,
449    request_id: RequestId,
450    status: zx_status_t,
451) {
452    block_server
453        .orchestrator
454        .session_manager
455        .complete_request(request_id, zx::Status::from_raw(status));
456}