Skip to main content

block_server/
async_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::{
6    ActiveRequests, DecodedRequest, DeviceInfo, FIFO_MAX_REQUESTS, HandleRequestResult,
7    IntoOrchestrator, OffsetMap, Operation, SessionHelper, TraceFlowId,
8};
9use anyhow::Error;
10use block_protocol::{BlockFifoRequest, BlockFifoResponse, ReadOptions, WriteFlags, WriteOptions};
11use fidl_fuchsia_storage_block as fblock;
12use fidl_fuchsia_storage_block::DeviceFlag;
13use fuchsia_async as fasync;
14use fuchsia_sync::Mutex;
15use futures::future::{Fuse, FusedFuture, join};
16use futures::stream::FuturesUnordered;
17use futures::{FutureExt, StreamExt, select_biased};
18use std::borrow::Cow;
19use std::collections::VecDeque;
20use std::future::{Future, poll_fn};
21use std::mem::MaybeUninit;
22use std::pin::pin;
23use std::sync::{Arc, OnceLock};
24use std::task::{Poll, ready};
25use storage_device::buffer::Buffer;
26use storage_device::buffer_allocator::{BufferAllocator, BufferSource};
27
28pub trait Interface: Send + Sync + Unpin + 'static {
29    /// Runs `stream` to completion.
30    ///
31    /// `offset_map` is provided by the client, and is used to remap requests. The extents in
32    /// `offset_map` are already validated to be within the range of the partition (as determined by
33    /// the [`PartitionInfo::block_count`] field). The implementation is expected to apply these
34    /// mappings to all FIFO requests, and to ensure all FIFO requests fit within the logical
35    /// extents of the offset map. Note that the default implementation does this for you, and that
36    /// is correct for most implementations.
37    ///
38    /// Implementors can override this method if they want to create a passthrough session instead
39    /// (and can use [`PassthroughSession`] below to do so). Generally, a passthrough session would
40    /// open a session to its underlying device with an offset map applied
41    /// (`fuchsia.storage.block.Block/OpenSessionWithOptions`), which remaps and restricts
42    /// requests to the range the partition should have access to.
43    ///
44    /// Nested mappings (i.e. the case when `offset_map` is non-empty, but the implementation
45    /// creates a passthrough session with its own mapping) would need to be composed by the
46    /// implementation. At this time, no implementations of passthrough sessions support nested
47    /// mappings, but we can add support as needed.
48    ///
49    /// If the implementor uses a [`PassthroughSession`], the following Interface methods
50    /// will not be called, and can be stubbed out:
51    ///   - on_attach_vmo
52    ///   - on_detach_vmo
53    ///   - read
54    ///   - write
55    ///   - flush
56    ///   - trim
57    fn open_session(
58        &self,
59        session_manager: Arc<SessionManager<Self>>,
60        stream: fblock::SessionRequestStream,
61        offset_map: OffsetMap,
62        block_size: u32,
63    ) -> impl Future<Output = Result<(), Error>> + Send {
64        // By default, serve the session rather than forwarding it.
65        session_manager.serve_session(
66            stream,
67            offset_map,
68            self.get_info().max_transfer_blocks(),
69            block_size,
70        )
71    }
72
73    /// Called whenever a VMO is attached, prior to the VMO's usage in any other methods. Whilst
74    /// the VMO is attached, `vmo` will keep the same address so it is safe to use the pointer
75    /// value (as, say, a key into a HashMap).
76    fn on_attach_vmo(&self, _vmo: &zx::Vmo) -> impl Future<Output = Result<(), zx::Status>> + Send {
77        async { Ok(()) }
78    }
79
80    /// Called whenever a VMO is detached.
81    fn on_detach_vmo(&self, _vmo: &zx::Vmo) {}
82
83    /// Called to get block/partition information.
84    fn get_info(&self) -> Cow<'_, DeviceInfo>;
85
86    /// Called for a request to read bytes.
87    ///
88    /// Implementations are responsible for checking that the request block range
89    /// (`[device_block_offset, device_block_offset + block_count)`) falls within valid
90    /// device/partition bounds, and returning `Err(zx::Status::OUT_OF_RANGE)` if it is out of
91    /// bounds.
92    fn read(
93        &self,
94        device_block_offset: u64,
95        block_count: u32,
96        vmo: &Arc<zx::Vmo>,
97        vmo_offset: u64, // *bytes* not blocks
98        opts: ReadOptions,
99        trace_flow_id: TraceFlowId,
100    ) -> impl Future<Output = Result<(), zx::Status>> + Send;
101
102    /// Called for a request to write bytes.
103    ///
104    /// Implementations are responsible for checking that the request block range
105    /// (`[device_block_offset, device_block_offset + block_count)`) falls within valid
106    /// device/partition bounds, and returning `Err(zx::Status::OUT_OF_RANGE)` if it is out of
107    /// bounds.
108    fn write(
109        &self,
110        device_block_offset: u64,
111        block_count: u32,
112        vmo: &Arc<zx::Vmo>,
113        vmo_offset: u64, // *bytes* not blocks
114        opts: WriteOptions,
115        trace_flow_id: TraceFlowId,
116    ) -> impl Future<Output = Result<(), zx::Status>> + Send;
117
118    /// Called to flush the device.
119    fn flush(
120        &self,
121        trace_flow_id: TraceFlowId,
122    ) -> impl Future<Output = Result<(), zx::Status>> + Send;
123
124    /// Called to trim a region.
125    ///
126    /// Implementations are responsible for checking that the request block range
127    /// (`[device_block_offset, device_block_offset + block_count)`) falls within valid
128    /// device/partition bounds, and returning `Err(zx::Status::OUT_OF_RANGE)` if it is out of
129    /// bounds.
130    fn trim(
131        &self,
132        device_block_offset: u64,
133        block_count: u32,
134        trace_flow_id: TraceFlowId,
135    ) -> impl Future<Output = Result<(), zx::Status>> + Send;
136
137    /// Called to handle the GetVolumeInfo FIDL call.
138    fn get_volume_info(
139        &self,
140    ) -> impl Future<Output = Result<(fblock::VolumeManagerInfo, fblock::VolumeInfo), zx::Status>> + Send
141    {
142        async { Err(zx::Status::NOT_SUPPORTED) }
143    }
144
145    /// Called to handle the QuerySlices FIDL call.
146    fn query_slices(
147        &self,
148        _start_slices: &[u64],
149    ) -> impl Future<Output = Result<Vec<fblock::VsliceRange>, zx::Status>> + Send {
150        async { Err(zx::Status::NOT_SUPPORTED) }
151    }
152
153    /// Called to handle the Extend FIDL call.
154    fn extend(
155        &self,
156        _start_slice: u64,
157        _slice_count: u64,
158    ) -> impl Future<Output = Result<(), zx::Status>> + Send {
159        async { Err(zx::Status::NOT_SUPPORTED) }
160    }
161
162    /// Called to handle the Shrink FIDL call.
163    fn shrink(
164        &self,
165        _start_slice: u64,
166        _slice_count: u64,
167    ) -> impl Future<Output = Result<(), zx::Status>> + Send {
168        async { Err(zx::Status::NOT_SUPPORTED) }
169    }
170}
171
172/// A helper object to run a passthrough (proxy) session.
173pub struct PassthroughSession(fblock::SessionProxy);
174
175impl PassthroughSession {
176    pub fn new(proxy: fblock::SessionProxy) -> Self {
177        Self(proxy)
178    }
179
180    async fn handle_request(&self, request: fblock::SessionRequest) -> Result<(), Error> {
181        match request {
182            fblock::SessionRequest::GetFifo { responder } => {
183                responder.send(self.0.get_fifo().await?)?;
184            }
185            fblock::SessionRequest::AttachVmo { vmo, responder } => {
186                responder.send(self.0.attach_vmo(vmo).await?.as_ref().map_err(|s| *s))?;
187            }
188            fblock::SessionRequest::Close { responder } => {
189                responder.send(self.0.close().await?)?;
190            }
191        }
192        Ok(())
193    }
194
195    /// Runs `stream` until completion.
196    pub async fn serve(&self, mut stream: fblock::SessionRequestStream) -> Result<(), Error> {
197        while let Some(Ok(request)) = stream.next().await {
198            if let Err(error) = self.handle_request(request).await {
199                log::warn!(error:?; "FIDL error");
200            }
201        }
202        Ok(())
203    }
204}
205
206pub struct SessionManager<I: Interface + ?Sized> {
207    interface: Arc<I>,
208    active_requests: ActiveRequests<usize>,
209
210    // NOTE: This must be dropped *after* `active_requests` because we store `Buffer<'_>` with an
211    // erased ('static) lifetime in `ActiveRequest`.
212    buffer_allocator: OnceLock<BufferAllocator>,
213}
214
215impl<I: Interface + ?Sized> Drop for SessionManager<I> {
216    fn drop(&mut self) {
217        if let Some(allocator) = self.buffer_allocator.get() {
218            self.interface.on_detach_vmo(allocator.buffer_source().vmo());
219        }
220    }
221}
222
223impl<I: Interface + ?Sized> SessionManager<I> {
224    pub fn new(interface: Arc<I>) -> Self {
225        Self {
226            interface,
227            active_requests: ActiveRequests::default(),
228            buffer_allocator: OnceLock::new(),
229        }
230    }
231
232    pub fn interface(&self) -> &I {
233        self.interface.as_ref()
234    }
235
236    /// Runs `stream` until completion.
237    pub async fn serve_session(
238        self: Arc<Self>,
239        stream: fblock::SessionRequestStream,
240        offset_map: OffsetMap,
241        max_transfer_blocks: Option<std::num::NonZero<u32>>,
242        block_size: u32,
243    ) -> Result<(), Error> {
244        let (helper, fifo) =
245            SessionHelper::new(self.clone(), offset_map, max_transfer_blocks, block_size)?;
246        let session = Arc::new(Session {
247            helper: Arc::new(helper),
248            interface: self.interface.clone(),
249            close_callback: Mutex::new(None),
250        });
251
252        let (stop_sender, stop_receiver) = futures::channel::oneshot::channel();
253
254        let mut stream = stream.fuse();
255        let scope = fasync::Scope::new();
256        let session_clone = session.clone();
257        let mut fifo_task = scope
258            .spawn(async move {
259                if let Err(status) = session_clone.run_fifo(fifo, stop_receiver).await {
260                    if status != zx::Status::PEER_CLOSED {
261                        log::error!(status:?; "FIFO error");
262                    }
263                }
264            })
265            .fuse();
266
267        // Make sure we detach VMOs when we go out of scope.
268        scopeguard::defer! {
269            for (_, (vmo, _)) in session.helper.take_vmos() {
270                self.interface.on_detach_vmo(&vmo);
271            }
272        }
273
274        let mut closing = false;
275        let mut stop_sender = Some(stop_sender);
276
277        loop {
278            futures::select! {
279                maybe_req = if closing {
280                    futures::future::pending().left_future()
281                } else {
282                    stream.next().right_future()
283                } => {
284                    if let Some(req) = maybe_req {
285                        match session.helper.handle_request(req?).await? {
286                            HandleRequestResult::Ok => {},
287                            HandleRequestResult::Closed(callback) => {
288                                *session.close_callback.lock() = Some(callback);
289                                // Client explicitly closed stream, stop processing.
290                                if let Some(sender) = stop_sender.take() {
291                                    let _ = sender.send(());
292                                }
293                                closing = true;
294                            }
295                        }
296                    } else {
297                        // Client end of stream dropped, stop processing.
298                        if let Some(sender) = stop_sender.take() {
299                            let _ = sender.send(());
300                        }
301                        closing = true;
302                    }
303                }
304                _ = fifo_task => break,
305            }
306        }
307        Ok(())
308    }
309}
310
311pub struct Session<I: Interface + ?Sized> {
312    interface: Arc<I>,
313    helper: Arc<SessionHelper<SessionManager<I>>>,
314    close_callback: Mutex<Option<Box<dyn FnOnce() + Send + 'static>>>,
315}
316
317impl<I: Interface + ?Sized> Session<I> {
318    // A task loop for receiving and responding to FIFO requests.
319    async fn run_fifo(
320        &self,
321        fifo: zx::Fifo<BlockFifoRequest, BlockFifoResponse>,
322        stop_signal: futures::channel::oneshot::Receiver<()>,
323    ) -> Result<(), zx::Status> {
324        scopeguard::defer! {
325            // Ensure that we always clean up active requests for this session upon FIFO
326            // termination.
327            self.helper.drop_active_requests(|session| *session == self as *const _ as usize);
328        }
329
330        // The FIFO has to be processed by a single task due to implementation constraints on
331        // fuchsia_async::Fifo.  Thus, we use an event loop to drive the FIFO.  FIFO reads and
332        // writes can happen in batch, and request processing is parallel.
333        //
334        // The general flow is:
335        //  - Read messages from the FIFO, write into `requests`.
336        //  - Read `requests`, decode them, and spawn a task to process them in `active_requests`,
337        //    which will eventually write them into `responses`.
338        //  - Read `responses` and write out to the FIFO.
339        let mut fifo = fasync::Fifo::from_fifo(fifo);
340        let (mut reader, mut writer) = fifo.async_io();
341        let mut requests = [MaybeUninit::<BlockFifoRequest>::uninit(); FIFO_MAX_REQUESTS];
342        let active_requests = &self.helper.session_manager().active_requests;
343        let mut active_request_futures = FuturesUnordered::new();
344        let mut responses = Vec::new();
345
346        // We map requests using a single future `map_future`.  `pending_mappings` is used to queue
347        // up requests that need to be mapped.  This will serialise how mappings occur which might
348        // make updating mapping caches simpler.  If this proves to be a performance issue, we can
349        // optimise it.
350        let mut map_future = pin!(Fuse::terminated());
351        let mut pending_mappings: VecDeque<DecodedRequest> = VecDeque::new();
352
353        // When `stop_signal` is received, we stop reading from the FIFO and wait for in-flight
354        // tasks to complete.
355        let mut stop_signal = pin!(stop_signal.fuse());
356        let mut is_closed = false;
357
358        loop {
359            let new_requests = {
360                // We provide some flow control by limiting how many in-flight requests we will
361                // allow.
362                let pending_requests = active_request_futures.len() + responses.len();
363
364                if is_closed
365                    && pending_requests == 0
366                    && map_future.is_terminated()
367                    && pending_mappings.is_empty()
368                {
369                    return Ok(());
370                }
371
372                let count = requests.len().saturating_sub(pending_requests);
373                let mut receive_requests = pin!(if count == 0 || is_closed {
374                    Fuse::terminated()
375                } else {
376                    reader.read_entries(&mut requests[..count]).fuse()
377                });
378                let mut send_responses = pin!(if responses.is_empty() {
379                    Fuse::terminated()
380                } else {
381                    poll_fn(|cx| -> Poll<Result<(), zx::Status>> {
382                        match ready!(writer.try_write(cx, &responses[..])) {
383                            Ok(written) => {
384                                responses.drain(..written);
385                                Poll::Ready(Ok(()))
386                            }
387                            Err(status) => Poll::Ready(Err(status)),
388                        }
389                    })
390                    .fuse()
391                });
392
393                // Order is important here.  We want to prioritize sending results on the FIFO and
394                // processing FIFO messages over receiving new ones, to provide flow control.
395                select_biased!(
396                    res = send_responses => {
397                        res?;
398                        0
399                    },
400                    response = active_request_futures.select_next_some() => {
401                        responses.extend(response);
402                        0
403                    }
404                    result = map_future => {
405                        match result {
406                            Ok((request, remainder, commit_decompression_buffers)) => {
407                                active_request_futures.push(self.process_fifo_request(
408                                    request,
409                                    commit_decompression_buffers,
410                                ));
411                                if let Some(remainder) = remainder {
412                                    map_future.set(
413                                        self.map_request_or_get_response(remainder).fuse()
414                                    );
415                                }
416                            }
417                            Err(response) => responses.extend(response),
418                        }
419                        if map_future.is_terminated() {
420                            if let Some(request) = pending_mappings.pop_front() {
421                                map_future.set(self.map_request_or_get_response(request).fuse());
422                            }
423                        }
424                        0
425                    }
426                    _ = stop_signal => {
427                        is_closed = true;
428                        0
429                    }
430                    count = receive_requests => {
431                        count?
432                    }
433                )
434            };
435
436            // NB: It is very important that there are no `await`s for the rest of the loop body, as
437            // otherwise active requests might become stalled.
438            for request in &mut requests[..new_requests] {
439                match self.helper.decode_fifo_request(self as *const _ as usize, unsafe {
440                    request.assume_init_mut()
441                }) {
442                    Ok(DecodedRequest {
443                        operation: Operation::CloseVmo, vmo, request_id, ..
444                    }) => {
445                        if let Some(vmo) = vmo {
446                            self.interface.on_detach_vmo(vmo.as_ref());
447                        }
448                        responses.extend(
449                            active_requests
450                                .complete_and_take_response(request_id, zx::Status::OK)
451                                .map(|(_, response)| response),
452                        );
453                    }
454                    Ok(request) => {
455                        if map_future.is_terminated() {
456                            map_future.set(self.map_request_or_get_response(request).fuse());
457                        } else {
458                            pending_mappings.push_back(request);
459                        }
460                    }
461                    Err(None) => {}
462                    Err(Some(response)) => responses.push(response),
463                }
464            }
465        }
466    }
467
468    async fn map_request_or_get_response(
469        &self,
470        request: DecodedRequest,
471    ) -> Result<(DecodedRequest, Option<DecodedRequest>, bool), Option<BlockFifoResponse>> {
472        let request_id = request.request_id;
473        self.map_request(request).await.map_err(|status| {
474            self.helper
475                .orchestrator
476                .active_requests
477                .complete_and_take_response(request_id, status)
478                .map(|(_, r)| r)
479        })
480    }
481
482    // NOTE: The implementation of this currently assumes that we are only processing a single map
483    // request at a time.
484    async fn map_request(
485        &self,
486        mut request: DecodedRequest,
487    ) -> Result<(DecodedRequest, Option<DecodedRequest>, bool), zx::Status> {
488        let mut active_requests;
489        let active_request;
490        let mut commit_decompression_buffers = false;
491        let flags = self.interface.get_info().as_ref().device_flags();
492        // Strip the PRE_BARRIER flag if we don't support it, and simulate the barrier with a
493        // pre-flush.
494        if !flags.contains(DeviceFlag::BARRIER_SUPPORT)
495            && request.operation.take_write_flag(WriteFlags::PRE_BARRIER)
496        {
497            if let Some(id) = request.trace_flow_id {
498                fuchsia_trace::async_instant!(
499                    fuchsia_trace::Id::from(id.get()),
500                    "storage",
501                    "block_server::SimulatedBarrier",
502                    "request_id" => request.request_id.0
503                );
504            }
505            self.interface.flush(request.trace_flow_id).await?;
506        }
507
508        // Handle decompressed read operations by turning them into regular read operations.
509        match request.operation {
510            Operation::StartDecompressedRead {
511                required_buffer_size,
512                device_block_offset,
513                block_count,
514                options,
515            } => {
516                let allocator = match self.helper.session_manager().buffer_allocator.get() {
517                    Some(a) => a,
518                    None => {
519                        // This isn't racy because there should only be one `map_request` future
520                        // running at any one time.
521                        let source = BufferSource::new(fblock::MAX_DECOMPRESSED_BYTES as usize);
522                        self.interface.on_attach_vmo(&source.vmo()).await?;
523                        let allocator = BufferAllocator::new(
524                            std::cmp::max(
525                                self.helper.block_size as usize,
526                                zx::system_get_page_size() as usize,
527                            ),
528                            source,
529                        );
530                        self.helper.session_manager().buffer_allocator.set(allocator).unwrap();
531                        self.helper.session_manager().buffer_allocator.get().unwrap()
532                    }
533                };
534
535                if required_buffer_size > fblock::MAX_DECOMPRESSED_BYTES as usize {
536                    return Err(zx::Status::OUT_OF_RANGE);
537                }
538
539                let buffer = allocator.allocate_buffer(required_buffer_size).await;
540                let vmo_offset = buffer.range().start as u64;
541
542                // # Safety
543                //
544                // See below.
545                unsafe fn remove_lifetime(buffer: Buffer<'_>) -> Buffer<'static> {
546                    unsafe { std::mem::transmute(buffer) }
547                }
548
549                active_requests = self.helper.session_manager().active_requests.0.lock();
550                active_request = &mut active_requests.requests[request.request_id.0];
551
552                // SAFETY: We guarantee that `buffer_allocator` is dropped after `active_requests`,
553                // so this should be safe.
554                active_request.decompression_info.as_mut().unwrap().buffer =
555                    Some(unsafe { remove_lifetime(buffer) });
556
557                request.operation = Operation::Read {
558                    device_block_offset,
559                    block_count,
560                    _unused: 0,
561                    vmo_offset,
562                    options,
563                };
564                request.vmo = Some(allocator.buffer_source().vmo().clone());
565
566                commit_decompression_buffers = true;
567            }
568            Operation::ContinueDecompressedRead {
569                offset,
570                device_block_offset,
571                block_count,
572                options,
573            } => {
574                active_requests = self.helper.session_manager().active_requests.0.lock();
575                active_request = &mut active_requests.requests[request.request_id.0];
576
577                let buffer =
578                    active_request.decompression_info.as_ref().unwrap().buffer.as_ref().unwrap();
579
580                // Make sure this read won't overflow our buffer.
581                if offset >= buffer.len() as u64
582                    || buffer.len() as u64 - offset
583                        < block_count as u64 * self.helper.block_size as u64
584                {
585                    return Err(zx::Status::OUT_OF_RANGE);
586                }
587
588                request.operation = Operation::Read {
589                    device_block_offset,
590                    block_count,
591                    _unused: 0,
592                    vmo_offset: buffer.range().start as u64 + offset,
593                    options,
594                };
595
596                let allocator = self.helper.session_manager().buffer_allocator.get().unwrap();
597                request.vmo = Some(allocator.buffer_source().vmo().clone());
598            }
599            _ => {
600                active_requests = self.helper.session_manager().active_requests.0.lock();
601                active_request = &mut active_requests.requests[request.request_id.0];
602            }
603        }
604
605        // NB: We propagate the FORCE_ACCESS flag to *both* request and remainder, even if we're
606        // using simulated FUA.  However, in `process_fifo_request`, we'll only do the post-flush
607        // once the last request completes.
608        self.helper
609            .map_request(request, active_request)
610            .map(|(request, remainder)| (request, remainder, commit_decompression_buffers))
611    }
612
613    /// Processes a fifo request.
614    async fn process_fifo_request(
615        &self,
616        DecodedRequest { request_id, operation, vmo, trace_flow_id }: DecodedRequest,
617        commit_decompression_buffers: bool,
618    ) -> Option<BlockFifoResponse> {
619        let mut needs_postflush = false;
620        let result = match operation {
621            Operation::Read { device_block_offset, block_count, _unused, vmo_offset, options } => {
622                join(
623                    self.interface.read(
624                        device_block_offset,
625                        block_count,
626                        vmo.as_ref().unwrap(),
627                        vmo_offset,
628                        options,
629                        trace_flow_id,
630                    ),
631                    async {
632                        if commit_decompression_buffers {
633                            let (target_slice, buffer_slice, buffer_range) = {
634                                let active_request = self
635                                    .helper
636                                    .session_manager()
637                                    .active_requests
638                                    .request(request_id);
639                                let info = active_request.decompression_info.as_ref().unwrap();
640                                (
641                                    info.uncompressed_slice(),
642                                    self.helper
643                                        .orchestrator
644                                        .buffer_allocator
645                                        .get()
646                                        .unwrap()
647                                        .buffer_source()
648                                        .slice(),
649                                    info.buffer.as_ref().unwrap().range(),
650                                )
651                            };
652                            let vmar = fuchsia_runtime::vmar_root_self();
653                            // The target slice might not be page aligned.
654                            let addr = target_slice.addr();
655                            let unaligned = addr % zx::system_get_page_size() as usize;
656                            if let Err(error) = vmar.op_range(
657                                zx::VmarOp::COMMIT,
658                                addr - unaligned,
659                                target_slice.len() + unaligned,
660                            ) {
661                                log::warn!(error:?; "Unable to commit target range");
662                            }
663                            // But the buffer range should be.
664                            if let Err(error) = vmar.op_range(
665                                zx::VmarOp::PREFETCH,
666                                buffer_slice.addr() + buffer_range.start,
667                                buffer_range.len(),
668                            ) {
669                                log::warn!(
670                                    error:?,
671                                    buffer_range:?;
672                                    "Unable to prefetch source range",
673                                );
674                            }
675                        }
676                    },
677                )
678                .await
679                .0
680            }
681            Operation::Write {
682                device_block_offset,
683                block_count,
684                _unused,
685                vmo_offset,
686                mut options,
687            } => {
688                // Strip the FORCE_ACCESS flag if we don't support it, and simulate the FUA with a
689                // post-flush.
690                if options.flags.contains(WriteFlags::FORCE_ACCESS) {
691                    let flags = self.interface.get_info().as_ref().device_flags();
692                    if !flags.contains(DeviceFlag::FUA_SUPPORT) {
693                        options.flags.remove(WriteFlags::FORCE_ACCESS);
694                        needs_postflush = true;
695                    }
696                }
697                self.interface
698                    .write(
699                        device_block_offset,
700                        block_count,
701                        vmo.as_ref().unwrap(),
702                        vmo_offset,
703                        options,
704                        trace_flow_id,
705                    )
706                    .await
707            }
708            Operation::Flush => self.interface.flush(trace_flow_id).await,
709            Operation::Trim { device_block_offset, block_count } => {
710                self.interface.trim(device_block_offset, block_count, trace_flow_id).await
711            }
712            Operation::CloseVmo
713            | Operation::StartDecompressedRead { .. }
714            | Operation::ContinueDecompressedRead { .. } => {
715                // Handled in main request loop
716                unreachable!()
717            }
718        };
719        let response = self
720            .helper
721            .orchestrator
722            .active_requests
723            .complete_and_take_response(request_id, result.into())
724            .map(|(_, r)| r);
725        if let Some(mut response) = response {
726            // Only do the post-flush on the very last request, and only if successful.
727            if zx::Status::from_raw(response.status) == zx::Status::OK && needs_postflush {
728                if let Some(id) = trace_flow_id {
729                    fuchsia_trace::async_instant!(
730                        fuchsia_trace::Id::from(id.get()),
731                        "storage",
732                        "block_server::SimulatedFUA",
733                        "request_id" => request_id.0
734                    );
735                }
736                response.status =
737                    zx::Status::from(self.interface.flush(trace_flow_id).await).into_raw();
738            }
739            Some(response)
740        } else {
741            response
742        }
743    }
744}
745
746impl<I: Interface + ?Sized> super::SessionManager for SessionManager<I> {
747    type Orchestrator = Self;
748
749    const SUPPORTS_DECOMPRESSION: bool = true;
750
751    // We don't need the session, we just need something unique to identify the session.
752    type Session = usize;
753
754    fn session_eq(a: &usize, b: &usize) -> bool {
755        a == b
756    }
757
758    async fn on_attach_vmo(orchestrator: Arc<Self>, vmo: &Arc<zx::Vmo>) -> Result<(), zx::Status> {
759        I::on_attach_vmo(&orchestrator.interface, vmo).await
760    }
761
762    async fn open_session(
763        orchestrator: Arc<Self>,
764        stream: fblock::SessionRequestStream,
765        offset_map: OffsetMap,
766        block_size: u32,
767    ) -> Result<(), Error> {
768        I::open_session(
769            &orchestrator.interface,
770            orchestrator.clone(),
771            stream,
772            offset_map,
773            block_size,
774        )
775        .await
776    }
777
778    fn get_info(&self) -> Cow<'_, DeviceInfo> {
779        self.interface.get_info()
780    }
781
782    async fn get_volume_info(
783        &self,
784    ) -> Result<(fblock::VolumeManagerInfo, fblock::VolumeInfo), zx::Status> {
785        self.interface.get_volume_info().await
786    }
787
788    async fn query_slices(
789        &self,
790        start_slices: &[u64],
791    ) -> Result<Vec<fblock::VsliceRange>, zx::Status> {
792        self.interface.query_slices(start_slices).await
793    }
794
795    async fn extend(&self, start_slice: u64, slice_count: u64) -> Result<(), zx::Status> {
796        self.interface.extend(start_slice, slice_count).await
797    }
798
799    async fn shrink(&self, start_slice: u64, slice_count: u64) -> Result<(), zx::Status> {
800        self.interface.shrink(start_slice, slice_count).await
801    }
802
803    fn active_requests(&self) -> &ActiveRequests<Self::Session> {
804        return &self.active_requests;
805    }
806}
807
808impl<I: Interface + ?Sized> Drop for Session<I> {
809    fn drop(&mut self) {
810        let callback = std::mem::take(&mut *self.close_callback.lock());
811        if let Some(callback) = callback {
812            callback();
813        }
814    }
815}
816
817impl<I: Interface> IntoOrchestrator for Arc<I> {
818    type SM = SessionManager<I>;
819
820    fn into_orchestrator(self) -> Arc<Self::SM> {
821        Arc::new(SessionManager {
822            interface: self,
823            active_requests: ActiveRequests::default(),
824            buffer_allocator: OnceLock::new(),
825        })
826    }
827}