Skip to main content

fuchsia_component_server/
lib.rs

1// Copyright 2019 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
5//! Tools for providing Fuchsia services.
6
7#![deny(missing_docs)]
8
9use anyhow::Error;
10use fidl::endpoints::{
11    DiscoverableProtocolMarker, Proxy as _, RequestStream, ServerEnd, ServiceMarker, ServiceRequest,
12};
13use fidl_fuchsia_io as fio;
14use fuchsia_async as fasync;
15use fuchsia_component_client::connect_channel_to_protocol;
16use futures::channel::mpsc;
17use futures::future::BoxFuture;
18use futures::{FutureExt, Stream, StreamExt};
19use log::warn;
20use pin_project::pin_project;
21use std::marker::PhantomData;
22use std::pin::Pin;
23use std::sync::Arc;
24use std::task::{Context, Poll};
25use thiserror::Error;
26use vfs::directory::entry::DirectoryEntry;
27use vfs::directory::helper::DirectlyMutable;
28use vfs::directory::immutable::Simple as PseudoDir;
29use vfs::execution_scope::ExecutionScope;
30use vfs::file::vmo::VmoFile;
31use vfs::name::Name;
32use vfs::remote::remote_dir;
33use vfs::service::endpoint;
34use zx::MonotonicDuration;
35
36mod service;
37pub use service::{
38    FidlService, FidlServiceMember, FidlServiceServerConnector, Service, ServiceObj,
39    ServiceObjLocal, ServiceObjTrait,
40};
41mod until_stalled;
42pub use until_stalled::{Item, StallableServiceFs};
43
44/// A filesystem which connects clients to services.
45///
46/// This type implements the `Stream` trait and will yield the values
47/// returned from calling `Service::connect` on the services it hosts.
48///
49/// This can be used to, for example, yield streams of channels, request
50/// streams, futures to run, or any other value that should be processed
51/// as the result of a request.
52#[must_use]
53#[pin_project]
54pub struct ServiceFs<ServiceObjTy: ServiceObjTrait> {
55    // The execution scope for the backing VFS.
56    scope: ExecutionScope,
57
58    // The root directory.
59    dir: Arc<PseudoDir>,
60
61    // New connections are sent via an mpsc. The tuple is (index, channel) where index is the index
62    // into the `services` member.
63    new_connection_sender: mpsc::UnboundedSender<(usize, zx::Channel)>,
64    new_connection_receiver: mpsc::UnboundedReceiver<(usize, zx::Channel)>,
65
66    // A collection of objects that are able to handle new connections and convert them into a
67    // stream of ServiceObjTy::Output requests.  There will be one for each service in the
68    // filesystem (irrespective of its place in the hierarchy).
69    services: Vec<ServiceObjTy>,
70
71    // A future that completes when the VFS no longer has any connections.  These connections are
72    // distinct from connections that might be to services or remotes within this filesystem.
73    shutdown: BoxFuture<'static, ()>,
74
75    // The filesystem does not start servicing any requests until ServiceFs is first polled.  This
76    // preserves behaviour of ServiceFs from when it didn't use the Rust VFS, and is relied upon in
77    // some cases.  The queue is used until first polled.  After that, `channel_queue` will be None
78    // and requests to service channels will be actioned immediately (potentially on different
79    // threads depending on the executor).
80    channel_queue: Option<Vec<fidl::endpoints::ServerEnd<fio::DirectoryMarker>>>,
81}
82
83impl<'a, Output: 'a> ServiceFs<ServiceObjLocal<'a, Output>> {
84    /// Create a new `ServiceFs` that is singlethreaded-only and does not
85    /// require services to implement `Send`.
86    pub fn new_local() -> Self {
87        Self::new_impl()
88    }
89}
90
91impl<'a, Output: 'a> ServiceFs<ServiceObj<'a, Output>> {
92    /// Create a new `ServiceFs` that is multithreaded-capable and requires
93    /// services to implement `Send`.
94    pub fn new() -> Self {
95        Self::new_impl()
96    }
97}
98
99/// A directory within a `ServiceFs`.
100///
101/// Services and subdirectories can be added to it.
102pub struct ServiceFsDir<'a, ServiceObjTy: ServiceObjTrait> {
103    fs: &'a mut ServiceFs<ServiceObjTy>,
104    dir: Arc<PseudoDir>,
105}
106
107/// A `Service` implementation that proxies requests
108/// to the outside environment.
109///
110/// Not intended for direct use. Use the `add_proxy_service`
111/// function instead.
112#[doc(hidden)]
113pub struct Proxy<P, O>(PhantomData<(P, fn() -> O)>);
114
115impl<P: DiscoverableProtocolMarker, O> Service for Proxy<P, O> {
116    type Output = O;
117    fn connect(&mut self, channel: zx::Channel) -> Option<O> {
118        if let Err(e) = connect_channel_to_protocol::<P>(channel) {
119            eprintln!("failed to proxy request to {}: {:?}", P::PROTOCOL_NAME, e);
120        }
121        None
122    }
123}
124
125/// A `Service` implementation that proxies requests to the given component.
126///
127/// Not intended for direct use. Use the `add_proxy_service_to` function instead.
128#[doc(hidden)]
129pub struct ProxyTo<P, O> {
130    directory_request: Arc<fidl::endpoints::ClientEnd<fio::DirectoryMarker>>,
131    _phantom: PhantomData<(P, fn() -> O)>,
132}
133
134impl<P: DiscoverableProtocolMarker, O> Service for ProxyTo<P, O> {
135    type Output = O;
136    fn connect(&mut self, channel: zx::Channel) -> Option<O> {
137        if let Err(e) =
138            fdio::service_connect_at(self.directory_request.channel(), P::PROTOCOL_NAME, channel)
139        {
140            eprintln!("failed to proxy request to {}: {:?}", P::PROTOCOL_NAME, e);
141        }
142        None
143    }
144}
145
146// Not part of a trait so that clients won't have to import a trait
147// in order to call these functions.
148macro_rules! add_functions {
149    () => {
150        /// Adds a service connector to the directory.
151        ///
152        /// ```rust
153        /// let mut fs = ServiceFs::new_local();
154        /// fs
155        ///     .add_service_connector(|server_end: ServerEnd<EchoMarker>| {
156        ///         connect_channel_to_protocol::<EchoMarker>(
157        ///             server_end.into_channel(),
158        ///         )
159        ///     })
160        ///     .add_service_connector(|server_end: ServerEnd<CustomMarker>| {
161        ///         connect_channel_to_protocol::<CustomMarker>(
162        ///             server_end.into_channel(),
163        ///         )
164        ///     })
165        ///     .take_and_serve_directory_handle()?;
166        /// ```
167        ///
168        /// The FIDL service will be hosted at the name provided by the
169        /// `[Discoverable]` annotation in the FIDL source.
170        pub fn add_service_connector<F, P>(&mut self, service: F) -> &mut Self
171        where
172            F: FnMut(ServerEnd<P>) -> ServiceObjTy::Output,
173            P: DiscoverableProtocolMarker,
174            FidlServiceServerConnector<F, P, ServiceObjTy::Output>: Into<ServiceObjTy>,
175        {
176            self.add_service_at(P::PROTOCOL_NAME, FidlServiceServerConnector::from(service))
177        }
178
179        /// Adds a service to the directory at the given path.
180        ///
181        /// The path must be a single component containing no `/` characters.
182        ///
183        /// Panics if any node has already been added at the given path.
184        pub fn add_service_at(
185            &mut self,
186            path: impl Into<String>,
187            service: impl Into<ServiceObjTy>,
188        ) -> &mut Self {
189            let index = self.fs().services.len();
190            self.fs().services.push(service.into());
191            let sender = self.fs().new_connection_sender.clone();
192            self.add_entry_at(
193                path,
194                endpoint(move |_, channel| {
195                    // It's possible for this send to fail in the case where ServiceFs has been
196                    // dropped.  When that happens, ServiceFs will drop ExecutionScope which
197                    // contains the RemoteHandle for this task which will then cause this task to be
198                    // dropped but not necessarily immediately.  This will only occur when ServiceFs
199                    // has been dropped, so it's safe to ignore the error here.
200                    let _ = sender.unbounded_send((index, channel.into()));
201                }),
202            )
203        }
204
205        /// Adds a FIDL service to the directory.
206        ///
207        /// `service` is a closure that accepts a `RequestStream`.
208        /// Each service being served must return an instance of the same type
209        /// (`ServiceObjTy::Output`). This is necessary in order to multiplex
210        /// multiple services over the same dispatcher code. The typical way
211        /// to do this is to create an `enum` with variants for each service
212        /// you want to serve.
213        ///
214        /// ```rust
215        /// enum MyServices {
216        ///     EchoServer(EchoRequestStream),
217        ///     CustomServer(CustomRequestStream),
218        ///     // ...
219        /// }
220        /// ```
221        ///
222        /// The constructor for a variant of the `MyServices` enum can be passed
223        /// as the `service` parameter.
224        ///
225        /// ```rust
226        /// let mut fs = ServiceFs::new_local();
227        /// fs
228        ///     .add_fidl_service(MyServices::EchoServer)
229        ///     .add_fidl_service(MyServices::CustomServer)
230        ///     .take_and_serve_directory_handle()?;
231        /// ```
232        ///
233        /// `ServiceFs` can now be treated as a `Stream` of type `MyServices`.
234        ///
235        /// ```rust
236        /// const MAX_CONCURRENT: usize = 10_000;
237        /// fs.for_each_concurrent(MAX_CONCURRENT, |request: MyServices| {
238        ///     match request {
239        ///         MyServices::EchoServer(request) => handle_echo(request),
240        ///         MyServices::CustomServer(request) => handle_custom(request),
241        ///     }
242        /// }).await;
243        /// ```
244        ///
245        /// The FIDL service will be hosted at the name provided by the
246        /// `[Discoverable]` annotation in the FIDL source.
247        pub fn add_fidl_service<F, RS>(&mut self, service: F) -> &mut Self
248        where
249            F: FnMut(RS) -> ServiceObjTy::Output,
250            RS: RequestStream,
251            RS::Protocol: DiscoverableProtocolMarker,
252            FidlService<F, RS, ServiceObjTy::Output>: Into<ServiceObjTy>,
253        {
254            self.add_fidl_service_at(RS::Protocol::PROTOCOL_NAME, service)
255        }
256
257        /// Adds a FIDL service to the directory at the given path.
258        ///
259        /// The path must be a single component containing no `/` characters.
260        ///
261        /// See [`add_fidl_service`](#method.add_fidl_service) for details.
262        pub fn add_fidl_service_at<F, RS>(
263            &mut self,
264            path: impl Into<String>,
265            service: F,
266        ) -> &mut Self
267        where
268            F: FnMut(RS) -> ServiceObjTy::Output,
269            RS: RequestStream,
270            RS::Protocol: DiscoverableProtocolMarker,
271            FidlService<F, RS, ServiceObjTy::Output>: Into<ServiceObjTy>,
272        {
273            self.add_service_at(path, FidlService::from(service))
274        }
275
276        /// Adds a named instance of a FIDL service to the directory.
277        ///
278        /// The FIDL service will be hosted at `[SERVICE_NAME]/[instance]/` where `SERVICE_NAME` is
279        /// constructed from the FIDL library path and the name of the FIDL service.
280        ///
281        /// The `instance` must be a single component containing no `/` characters.
282        ///
283        /// # Example
284        ///
285        /// For the following FIDL definition,
286        /// ```fidl
287        /// library lib.foo;
288        ///
289        /// service Bar {
290        ///   ...
291        /// }
292        /// ```
293        ///
294        /// The `SERVICE_NAME` of FIDL Service `Bar` would be `lib.foo.Bar`.
295        pub fn add_fidl_service_instance<F, SR>(
296            &mut self,
297            instance: impl Into<String>,
298            service: F,
299        ) -> &mut Self
300        where
301            F: Fn(SR) -> ServiceObjTy::Output,
302            F: Clone,
303            SR: ServiceRequest,
304            FidlServiceMember<F, SR, ServiceObjTy::Output>: Into<ServiceObjTy>,
305        {
306            self.add_fidl_service_instance_at(SR::Service::SERVICE_NAME, instance, service)
307        }
308
309        /// Adds a named instance of a FIDL service to the directory at the given path.
310        ///
311        /// The FIDL service will be hosted at `[path]/[instance]/`.
312        ///
313        /// The `path` and `instance` must be single components containing no `/` characters.
314        pub fn add_fidl_service_instance_at<F, SR>(
315            &mut self,
316            path: impl Into<String>,
317            instance: impl Into<String>,
318            service: F,
319        ) -> &mut Self
320        where
321            F: Fn(SR) -> ServiceObjTy::Output,
322            F: Clone,
323            SR: ServiceRequest,
324            FidlServiceMember<F, SR, ServiceObjTy::Output>: Into<ServiceObjTy>,
325        {
326            // Create the service directory, with an instance subdirectory.
327            let mut dir = self.dir(path);
328            let mut dir = dir.dir(instance);
329
330            // Attach member protocols under the instance directory.
331            for member in SR::member_names() {
332                dir.add_service_at(*member, FidlServiceMember::new(service.clone(), member));
333            }
334            self
335        }
336
337        /// Adds a service that proxies requests to the current environment.
338        // NOTE: we'd like to be able to remove the type parameter `O` here,
339        //  but unfortunately the bound `ServiceObjTy: From<Proxy<P, ServiceObjTy::Output>>`
340        //  makes type checking angry.
341        pub fn add_proxy_service<P: DiscoverableProtocolMarker, O>(&mut self) -> &mut Self
342        where
343            ServiceObjTy: From<Proxy<P, O>>,
344            ServiceObjTy: ServiceObjTrait<Output = O>,
345        {
346            self.add_service_at(P::PROTOCOL_NAME, Proxy::<P, ServiceObjTy::Output>(PhantomData))
347        }
348
349        /// Adds a service that proxies requests to the given component.
350        // NOTE: we'd like to be able to remove the type parameter `O` here,
351        //  but unfortunately the bound `ServiceObjTy: From<Proxy<P, ServiceObjTy::Output>>`
352        //  makes type checking angry.
353        pub fn add_proxy_service_to<P: DiscoverableProtocolMarker, O>(
354            &mut self,
355            directory_request: Arc<fidl::endpoints::ClientEnd<fio::DirectoryMarker>>,
356        ) -> &mut Self
357        where
358            ServiceObjTy: From<ProxyTo<P, O>>,
359            ServiceObjTy: ServiceObjTrait<Output = O>,
360        {
361            self.add_service_at(
362                P::PROTOCOL_NAME,
363                ProxyTo::<P, ServiceObjTy::Output> { directory_request, _phantom: PhantomData },
364            )
365        }
366
367        /// Adds a VMO file to the directory at the given path.
368        ///
369        /// The path must be a single component containing no `/` characters. The vmo should have
370        /// content size set as required.
371        ///
372        /// Panics if any node has already been added at the given path.
373        pub fn add_vmo_file_at(&mut self, path: impl Into<String>, vmo: zx::Vmo) -> &mut Self {
374            self.add_entry_at(path, VmoFile::new(vmo))
375        }
376
377        /// Adds an entry to the directory at the given path.
378        ///
379        /// The path must be a single component.
380        /// The path must be a valid `fuchsia.io` [`Name`].
381        ///
382        /// Panics if any node has already been added at the given path.
383        pub fn add_entry_at(
384            &mut self,
385            path: impl Into<String>,
386            entry: Arc<dyn DirectoryEntry>,
387        ) -> &mut Self {
388            let path: String = path.into();
389            let name: Name = path.try_into().expect("Invalid path");
390            // This will fail if the name is invalid or already exists.
391            self.dir.add_entry_impl(name, entry, false).expect("Unable to add entry");
392            self
393        }
394
395        /// Returns a reference to the subdirectory at the given path,
396        /// creating one if none exists.
397        ///
398        /// The path must be a single component.
399        /// The path must be a valid `fuchsia.io` [`Name`].
400        ///
401        /// Panics if a service has already been added at the given path.
402        pub fn dir(&mut self, path: impl Into<String>) -> ServiceFsDir<'_, ServiceObjTy> {
403            let path: String = path.into();
404            let name: Name = path.try_into().expect("Invalid path");
405            let dir = Arc::downcast(self.dir.get_or_insert(name, new_simple_dir).into_any())
406                .unwrap_or_else(|_| panic!("Not a directory"));
407            ServiceFsDir { fs: self.fs(), dir }
408        }
409
410        /// Adds a new remote directory served over the given DirectoryProxy.
411        ///
412        /// The name must be a valid `fuchsia.io` [`Name`].
413        pub fn add_remote(
414            &mut self,
415            name: impl Into<String>,
416            proxy: fio::DirectoryProxy,
417        ) -> &mut Self {
418            let name: String = name.into();
419            let name: Name = name.try_into().expect("Invalid path");
420            self.dir.add_entry_impl(name, remote_dir(proxy), false).expect("Unable to add entry");
421            self
422        }
423
424        /// Adds a FIDL protocol to the directory.
425        ///
426        /// The FIDL protocol will be hosted at the name provided by the
427        /// `Discoverable` annotation in the FIDL source.
428        pub fn add_fidl_next_protocol<P, H>(
429            &mut self,
430            create_handler: impl Fn(::fidl_next::Server<P, zx::Channel>) -> H
431            + Send
432            + Sync
433            + Clone
434            + 'static,
435        ) -> &mut Self
436        where
437            P: ::fidl_next::Discoverable + ::fidl_next::DispatchServerMessage<H, zx::Channel>,
438            H: Send + Sync + 'static,
439        {
440            self.dir
441                .add_entry_impl(
442                    String::from(P::PROTOCOL_NAME).try_into().expect("Invalid path"),
443                    endpoint(move |_, channel| {
444                        let create_handler = create_handler.clone();
445                        fasync::Task::spawn(async move {
446                            // TODO: logging?
447                            let server_end = ::fidl_next::ServerEnd::<P, zx::Channel>::from_untyped(
448                                channel.into_zx_channel(),
449                            );
450                            let dispatcher = ::fidl_next::ServerDispatcher::new(server_end);
451                            let handler = create_handler(dispatcher.server());
452                            dispatcher
453                                .run(handler)
454                                .await
455                                .expect("Protocol service was terminated ");
456                        })
457                        .detach();
458                    }),
459                    false,
460                )
461                .expect("Unable to add entry");
462            self
463        }
464
465        /// Adds a FIDL service to the directory.
466        ///
467        /// The FIDL service will be hosted at the name provided by the
468        /// `Discoverable` annotation in the FIDL source.
469        pub fn add_fidl_next_service_instance<S, H>(
470            &mut self,
471            instance_name: impl Into<String>,
472            handler: H,
473        ) -> &mut Self
474        where
475            S: ::fidl_next::DiscoverableService
476                + ::fidl_next::DispatchServiceHandler<H, zx::Channel>
477                + 'static,
478            H: Send + Sync + 'static,
479        {
480            self.add_fidl_next_service_instance_at::<S, H>(S::SERVICE_NAME, instance_name, handler)
481        }
482
483        /// Adds a FIDL service to the directory with a custom service name.
484        pub fn add_fidl_next_service_instance_at<S, H>(
485            &mut self,
486            path: impl Into<String>,
487            instance_name: impl Into<String>,
488            handler: H,
489        ) -> &mut Self
490        where
491            S: ::fidl_next::DiscoverableService
492                + ::fidl_next::DispatchServiceHandler<H, zx::Channel>
493                + 'static,
494            H: Send + Sync + 'static,
495        {
496            // Create the service directory, with an instance subdirectory
497            let mut dir = self.dir(path);
498            let mut dir = dir.dir(instance_name);
499
500            let handler = std::sync::Arc::new(
501                ::fidl_next::ServiceHandlerAdapter::<S, H>::from_untyped(handler),
502            );
503
504            // Attach member protocols under the instance directory
505            for member_name in S::MEMBER_NAMES {
506                let handler = handler.clone();
507                dir.add_entry_at(
508                    *member_name,
509                    endpoint(move |_, channel| {
510                        ::fidl_next::protocol::ServiceHandler::on_connection(
511                            &*handler,
512                            member_name,
513                            channel.into_zx_channel(),
514                        );
515                    }),
516                );
517            }
518            self
519        }
520    };
521}
522
523impl<ServiceObjTy: ServiceObjTrait> ServiceFsDir<'_, ServiceObjTy> {
524    fn fs(&mut self) -> &mut ServiceFs<ServiceObjTy> {
525        self.fs
526    }
527
528    add_functions!();
529}
530
531impl<ServiceObjTy: ServiceObjTrait> ServiceFs<ServiceObjTy> {
532    fn new_impl() -> Self {
533        let (new_connection_sender, new_connection_receiver) = mpsc::unbounded();
534        let scope = ExecutionScope::new();
535        let dir = new_simple_dir();
536        Self {
537            scope: scope.clone(),
538            dir,
539            new_connection_sender,
540            new_connection_receiver,
541            services: Vec::new(),
542            shutdown: async move { scope.wait().await }.boxed(),
543            channel_queue: Some(Vec::new()),
544        }
545    }
546
547    fn fs(&mut self) -> &mut ServiceFs<ServiceObjTy> {
548        self
549    }
550
551    /// Get a reference to the root directory as a `ServiceFsDir`.
552    ///
553    /// This can be useful when writing code which hosts some set of services on
554    /// a directory and wants to be agnostic to whether that directory
555    /// is the root `ServiceFs` or a subdirectory.
556    ///
557    /// Such a function can take an `&mut ServiceFsDir<...>` as an argument,
558    /// allowing callers to provide either a subdirectory or `fs.root_dir()`.
559    pub fn root_dir(&mut self) -> ServiceFsDir<'_, ServiceObjTy> {
560        let dir = self.dir.clone();
561        ServiceFsDir { fs: self, dir }
562    }
563
564    add_functions!();
565
566    /// When a connection is first made to the `ServiceFs` in the absence of a parent connection,
567    /// it will be granted these rights.
568    const fn base_connection_flags() -> fio::Flags {
569        return fio::Flags::PROTOCOL_DIRECTORY
570            .union(fio::PERM_READABLE)
571            .union(fio::PERM_WRITABLE)
572            .union(fio::PERM_EXECUTABLE);
573    }
574
575    fn serve_connection_impl(&self, chan: fidl::endpoints::ServerEnd<fio::DirectoryMarker>) {
576        vfs::directory::serve_on(
577            self.dir.clone(),
578            Self::base_connection_flags(),
579            self.scope.clone(),
580            chan,
581        );
582    }
583
584    /// Creates a protocol connector that can access the capabilities exposed by this ServiceFs.
585    pub fn create_protocol_connector<O>(&mut self) -> Result<ProtocolConnector, Error>
586    where
587        ServiceObjTy: ServiceObjTrait<Output = O>,
588    {
589        let (directory_request, directory_server_end) = fidl::endpoints::create_endpoints();
590        self.serve_connection(directory_server_end)?;
591
592        Ok(ProtocolConnector { directory_request })
593    }
594}
595
596fn new_simple_dir() -> Arc<PseudoDir> {
597    PseudoDir::new_with_not_found_handler(move |path| {
598        warn!(
599            "ServiceFs received request to `{}` but has not been configured to serve this path.",
600            path
601        );
602    })
603}
604
605/// `ProtocolConnector` allows connecting to capabilities exposed by ServiceFs
606pub struct ProtocolConnector {
607    directory_request: fidl::endpoints::ClientEnd<fio::DirectoryMarker>,
608}
609
610impl ProtocolConnector {
611    /// Connect to a protocol provided by this environment.
612    #[inline]
613    pub fn connect_to_service<P: DiscoverableProtocolMarker>(&self) -> Result<P::Proxy, Error> {
614        self.connect_to_protocol::<P>()
615    }
616
617    /// Connect to a protocol provided by this environment.
618    #[inline]
619    pub fn connect_to_protocol<P: DiscoverableProtocolMarker>(&self) -> Result<P::Proxy, Error> {
620        let (client_channel, server_channel) = zx::Channel::create();
621        self.pass_to_protocol::<P>(server_channel)?;
622        Ok(P::Proxy::from_channel(fasync::Channel::from_channel(client_channel)))
623    }
624
625    /// Connect to a protocol by passing a channel for the server.
626    #[inline]
627    pub fn pass_to_protocol<P: DiscoverableProtocolMarker>(
628        &self,
629        server_channel: zx::Channel,
630    ) -> Result<(), Error> {
631        self.pass_to_named_protocol(P::PROTOCOL_NAME, server_channel)
632    }
633
634    /// Connect to a protocol by name.
635    #[inline]
636    pub fn pass_to_named_protocol(
637        &self,
638        protocol_name: &str,
639        server_channel: zx::Channel,
640    ) -> Result<(), Error> {
641        fdio::service_connect_at(self.directory_request.channel(), protocol_name, server_channel)?;
642        Ok(())
643    }
644}
645
646/// An error indicating the startup handle on which the FIDL server
647/// attempted to start was missing.
648#[derive(Debug, Error)]
649#[error("The startup handle on which the FIDL server attempted to start was missing.")]
650pub struct MissingStartupHandle;
651
652impl<ServiceObjTy: ServiceObjTrait> ServiceFs<ServiceObjTy> {
653    /// Removes the `DirectoryRequest` startup handle for the current
654    /// component and adds connects it to this `ServiceFs` as a client.
655    ///
656    /// Multiple calls to this function from the same component will
657    /// result in `Err(MissingStartupHandle)`.
658    pub fn take_and_serve_directory_handle(&mut self) -> Result<&mut Self, Error> {
659        let startup_handle = fuchsia_runtime::take_startup_handle(
660            fuchsia_runtime::HandleType::DirectoryRequest.into(),
661        )
662        .ok_or(MissingStartupHandle)?;
663
664        self.serve_connection(fidl::endpoints::ServerEnd::new(zx::Channel::from(startup_handle)))
665    }
666
667    /// Add a channel to serve this `ServiceFs` filesystem on. The `ServiceFs`
668    /// will continue to be provided over previously added channels, including
669    /// the one added if `take_and_serve_directory_handle` was called.
670    pub fn serve_connection(
671        &mut self,
672        chan: fidl::endpoints::ServerEnd<fio::DirectoryMarker>,
673    ) -> Result<&mut Self, Error> {
674        if let Some(channels) = &mut self.channel_queue {
675            channels.push(chan);
676        } else {
677            self.serve_connection_impl(chan);
678        }
679        Ok(self)
680    }
681
682    /// TODO(https://fxbug.dev/326626515): this is an experimental method to run a FIDL
683    /// directory connection until stalled, with the purpose to cleanly stop a component.
684    /// We'll expect to revisit how this works to generalize to all connections later.
685    /// Try not to use this function for other purposes.
686    ///
687    /// Normally the [`ServiceFs`] stream will block until all connections are closed.
688    /// In order to escrow the outgoing directory server endpoint, you may use this
689    /// function to get a [`StallableServiceFs`] that detects when no new requests
690    /// hit the outgoing directory for `debounce_interval`, and all hosted protocols
691    /// and other VFS connections to finish, then yield back the outgoing directory handle.
692    ///
693    /// The [`ServiceFs`] stream yields [`ServiceObjTy::Output`], which could be an enum
694    /// of FIDL connection requests in a typical component. By contrast, [`StallableServiceFs`]
695    /// yields an enum of either the request, or the unbound outgoing directory endpoint,
696    /// allowing you to escrow it back to `component_manager` before exiting the component.
697    pub fn until_stalled(
698        self,
699        debounce_interval: MonotonicDuration,
700    ) -> StallableServiceFs<ServiceObjTy> {
701        StallableServiceFs::<ServiceObjTy>::new(self, debounce_interval)
702    }
703}
704
705impl<ServiceObjTy: ServiceObjTrait> Stream for ServiceFs<ServiceObjTy> {
706    type Item = ServiceObjTy::Output;
707
708    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
709        // NOTE: Normally, it isn't safe to poll a stream after it returns None, but we support this
710        // and StallabkeServiceFs depends on this.
711        if let Some(channels) = self.channel_queue.take() {
712            for chan in channels {
713                self.serve_connection_impl(chan);
714            }
715        }
716        while let Poll::Ready(Some((index, channel))) =
717            self.new_connection_receiver.poll_next_unpin(cx)
718        {
719            if let Some(stream) = self.services[index].service().connect(channel) {
720                return Poll::Ready(Some(stream));
721            }
722        }
723        self.shutdown.poll_unpin(cx).map(|_| None)
724    }
725}