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 in order to call these
147// 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 `[Discoverable]` annotation
169        /// in the FIDL source.
170        ///
171        /// # Panics
172        ///
173        /// Panics if any node has already been added with the discoverable protocol name.
174        pub fn add_service_connector<F, P>(&mut self, service: F) -> &mut Self
175        where
176            F: FnMut(ServerEnd<P>) -> ServiceObjTy::Output,
177            P: DiscoverableProtocolMarker,
178            FidlServiceServerConnector<F, P, ServiceObjTy::Output>: Into<ServiceObjTy>,
179        {
180            self.add_service_at(
181                Name::from_static(P::PROTOCOL_NAME),
182                FidlServiceServerConnector::from(service),
183            )
184        }
185
186        /// Adds a service to the directory with the given name.
187        ///
188        /// # Panics
189        ///
190        /// Panics if `name` is not a valid `fuchsia.io` [`Name`], or if any node has already been
191        /// added with the given name.
192        pub fn add_service_at(
193            &mut self,
194            name: impl TryInto<Name, Error: std::fmt::Debug>,
195            service: impl Into<ServiceObjTy>,
196        ) -> &mut Self {
197            let index = self.fs().services.len();
198            self.fs().services.push(service.into());
199            let sender = self.fs().new_connection_sender.clone();
200            self.add_entry_at(
201                name,
202                endpoint(move |_, channel| {
203                    // It's possible for this send to fail in the case where ServiceFs has been
204                    // dropped.  When that happens, ServiceFs will drop ExecutionScope which
205                    // contains the RemoteHandle for this task which will then cause this task to be
206                    // dropped but not necessarily immediately.  This will only occur when ServiceFs
207                    // has been dropped, so it's safe to ignore the error here.
208                    let _ = sender.unbounded_send((index, channel.into()));
209                }),
210            )
211        }
212
213        /// Adds a FIDL service to the directory.
214        ///
215        /// `service` is a closure that accepts a `RequestStream`.
216        /// Each service being served must return an instance of the same type
217        /// (`ServiceObjTy::Output`). This is necessary in order to multiplex
218        /// multiple services over the same dispatcher code. The typical way
219        /// to do this is to create an `enum` with variants for each service
220        /// you want to serve.
221        ///
222        /// ```rust
223        /// enum MyServices {
224        ///     EchoServer(EchoRequestStream),
225        ///     CustomServer(CustomRequestStream),
226        ///     // ...
227        /// }
228        /// ```
229        ///
230        /// The constructor for a variant of the `MyServices` enum can be passed
231        /// as the `service` parameter.
232        ///
233        /// ```rust
234        /// let mut fs = ServiceFs::new_local();
235        /// fs
236        ///     .add_fidl_service(MyServices::EchoServer)
237        ///     .add_fidl_service(MyServices::CustomServer)
238        ///     .take_and_serve_directory_handle()?;
239        /// ```
240        ///
241        /// `ServiceFs` can now be treated as a `Stream` of type `MyServices`.
242        ///
243        /// ```rust
244        /// const MAX_CONCURRENT: usize = 10_000;
245        /// fs.for_each_concurrent(MAX_CONCURRENT, |request: MyServices| {
246        ///     match request {
247        ///         MyServices::EchoServer(request) => handle_echo(request),
248        ///         MyServices::CustomServer(request) => handle_custom(request),
249        ///     }
250        /// }).await;
251        /// ```
252        ///
253        /// The FIDL service will be hosted at the name provided by the
254        /// `[Discoverable]` annotation in the FIDL source.
255        ///
256        /// # Panics
257        ///
258        /// Panics if any node has already been added with the discoverable protocol name.
259        pub fn add_fidl_service<F, RS>(&mut self, service: F) -> &mut Self
260        where
261            F: FnMut(RS) -> ServiceObjTy::Output,
262            RS: RequestStream,
263            RS::Protocol: DiscoverableProtocolMarker,
264            FidlService<F, RS, ServiceObjTy::Output>: Into<ServiceObjTy>,
265        {
266            self.add_fidl_service_at(Name::from_static(RS::Protocol::PROTOCOL_NAME), service)
267        }
268
269        /// Adds a FIDL service to the directory with the given name.
270        ///
271        /// See [`add_fidl_service`](#method.add_fidl_service) for details.
272        ///
273        /// # Panics
274        ///
275        /// Panics if `name` is not a valid `fuchsia.io` [`Name`], or if any node has already
276        /// been added with the given name.
277        pub fn add_fidl_service_at<F, RS>(
278            &mut self,
279            name: impl TryInto<Name, Error: std::fmt::Debug>,
280            service: F,
281        ) -> &mut Self
282        where
283            F: FnMut(RS) -> ServiceObjTy::Output,
284            RS: RequestStream,
285            RS::Protocol: DiscoverableProtocolMarker,
286            FidlService<F, RS, ServiceObjTy::Output>: Into<ServiceObjTy>,
287        {
288            self.add_service_at(name, FidlService::from(service))
289        }
290
291        /// Adds a named instance of a FIDL service to the directory.
292        ///
293        /// The FIDL service will be hosted at `[SERVICE_NAME]/[instance]/` where `SERVICE_NAME` is
294        /// constructed from the FIDL library path and the name of the FIDL service.
295        ///
296        /// # Example
297        ///
298        /// For the following FIDL definition,
299        /// ```fidl
300        /// library lib.foo;
301        ///
302        /// service Bar {
303        ///   ...
304        /// }
305        /// ```
306        ///
307        /// The `SERVICE_NAME` of FIDL Service `Bar` would be `lib.foo.Bar`.
308        ///
309        /// # Panics
310        ///
311        /// Panics if `instance` is not a valid `fuchsia.io` [`Name`], or if an entry has already
312        /// been added that conflicts with the service or instance directory structure.
313        pub fn add_fidl_service_instance<F, SR>(
314            &mut self,
315            instance: impl TryInto<Name, Error: std::fmt::Debug>,
316            service: F,
317        ) -> &mut Self
318        where
319            F: Fn(SR) -> ServiceObjTy::Output,
320            F: Clone,
321            SR: ServiceRequest,
322            FidlServiceMember<F, SR, ServiceObjTy::Output>: Into<ServiceObjTy>,
323        {
324            self.add_fidl_service_instance_at(
325                Name::from_static(SR::Service::SERVICE_NAME),
326                instance,
327                service,
328            )
329        }
330
331        /// Adds a named instance of a FIDL service to the directory with the given name.
332        ///
333        /// The FIDL service will be hosted at `[name]/[instance]/`.
334        ///
335        /// # Panics
336        ///
337        /// Panics if `name` or `instance` is not a valid `fuchsia.io` [`Name`], or if an entry has
338        /// already been added that conflicts with the service or instance directory structure.
339        pub fn add_fidl_service_instance_at<F, SR>(
340            &mut self,
341            name: impl TryInto<Name, Error: std::fmt::Debug>,
342            instance: impl TryInto<Name, Error: std::fmt::Debug>,
343            service: F,
344        ) -> &mut Self
345        where
346            F: Fn(SR) -> ServiceObjTy::Output,
347            F: Clone,
348            SR: ServiceRequest,
349            FidlServiceMember<F, SR, ServiceObjTy::Output>: Into<ServiceObjTy>,
350        {
351            // Create the service directory, with an instance subdirectory.
352            let mut dir = self.dir(name);
353            let mut dir = dir.dir(instance);
354
355            // Attach member protocols under the instance directory.
356            for member in SR::member_names() {
357                dir.add_service_at(*member, FidlServiceMember::new(service.clone(), member));
358            }
359            self
360        }
361
362        /// Adds a service that proxies requests to the current environment.
363        ///
364        /// The FIDL service will be hosted at the name provided by the `[Discoverable]` annotation
365        /// in the FIDL source.
366        ///
367        /// # Panics
368        ///
369        /// Panics if any node has already been added with the discoverable protocol name.
370        // NOTE: we'd like to be able to remove the type parameter `O` here,
371        //  but unfortunately the bound `ServiceObjTy: From<Proxy<P, ServiceObjTy::Output>>`
372        //  makes type checking angry.
373        pub fn add_proxy_service<P: DiscoverableProtocolMarker, O>(&mut self) -> &mut Self
374        where
375            ServiceObjTy: From<Proxy<P, O>>,
376            ServiceObjTy: ServiceObjTrait<Output = O>,
377        {
378            self.add_service_at(
379                Name::from_static(P::PROTOCOL_NAME),
380                Proxy::<P, ServiceObjTy::Output>(PhantomData),
381            )
382        }
383
384        /// Adds a service that proxies requests to the given component.
385        ///
386        /// The FIDL service will be hosted at the name provided by the `[Discoverable]` annotation
387        /// in the FIDL source.
388        ///
389        /// # Panics
390        ///
391        /// Panics if any node has already been added with the discoverable protocol name.
392        // NOTE: we'd like to be able to remove the type parameter `O` here,
393        //  but unfortunately the bound `ServiceObjTy: From<Proxy<P, ServiceObjTy::Output>>`
394        //  makes type checking angry.
395        pub fn add_proxy_service_to<P: DiscoverableProtocolMarker, O>(
396            &mut self,
397            directory_request: Arc<fidl::endpoints::ClientEnd<fio::DirectoryMarker>>,
398        ) -> &mut Self
399        where
400            ServiceObjTy: From<ProxyTo<P, O>>,
401            ServiceObjTy: ServiceObjTrait<Output = O>,
402        {
403            self.add_service_at(
404                Name::from_static(P::PROTOCOL_NAME),
405                ProxyTo::<P, ServiceObjTy::Output> { directory_request, _phantom: PhantomData },
406            )
407        }
408
409        /// Adds a VMO file to the directory with the given name.
410        ///
411        /// The vmo should have content size set as required.
412        ///
413        /// # Panics
414        ///
415        /// Panics if `name` is not a valid `fuchsia.io` [`Name`], or if any node has already
416        /// been added with the given name.
417        pub fn add_vmo_file_at(
418            &mut self,
419            name: impl TryInto<Name, Error: std::fmt::Debug>,
420            vmo: zx::Vmo,
421        ) -> &mut Self {
422            self.add_entry_at(name, VmoFile::new(vmo))
423        }
424
425        /// Adds an entry to the directory with the given name.
426        ///
427        /// # Panics
428        ///
429        /// Panics if `name` is not a valid `fuchsia.io` [`Name`], or if any node has already
430        /// been added with the given name.
431        pub fn add_entry_at(
432            &mut self,
433            name: impl TryInto<Name, Error: std::fmt::Debug>,
434            entry: Arc<dyn DirectoryEntry>,
435        ) -> &mut Self {
436            let name: Name = name.try_into().expect("Invalid name");
437            // This will fail if the name is invalid or already exists.
438            self.dir.add_entry_impl(name, entry, false).expect("Unable to add entry");
439            self
440        }
441
442        /// Returns a reference to the subdirectory with the given name, creating one if none exists.
443        ///
444        /// # Panics
445        ///
446        /// Panics if `name` is not a valid `fuchsia.io` [`Name`], or if an entry has already
447        /// been added with the given name and it is not a directory.
448        pub fn dir(
449            &mut self,
450            name: impl TryInto<Name, Error: std::fmt::Debug>,
451        ) -> ServiceFsDir<'_, ServiceObjTy> {
452            let name: Name = name.try_into().expect("Invalid name");
453            let dir = Arc::downcast(self.dir.get_or_insert(name, new_simple_dir).into_any())
454                .unwrap_or_else(|_| panic!("Not a directory"));
455            ServiceFsDir { fs: self.fs(), dir }
456        }
457
458        /// Adds a new remote directory served over the given DirectoryProxy.
459        ///
460        /// # Panics
461        ///
462        /// Panics if `name` is not a valid `fuchsia.io` [`Name`], or if any node has already
463        /// been added with the given name.
464        pub fn add_remote(
465            &mut self,
466            name: impl TryInto<Name, Error: std::fmt::Debug>,
467            proxy: fio::DirectoryProxy,
468        ) -> &mut Self {
469            let name: Name = name.try_into().expect("Invalid name");
470            self.dir.add_entry_impl(name, remote_dir(proxy), false).expect("Unable to add entry");
471            self
472        }
473
474        /// Adds a FIDL protocol to the directory.
475        ///
476        /// The FIDL protocol will be hosted at the name provided by the `Discoverable` annotation
477        /// in the FIDL source.
478        ///
479        /// # Panics
480        ///
481        /// Panics if any node has already been added with the discoverable protocol name.
482        pub fn add_fidl_next_protocol<P, H>(
483            &mut self,
484            create_handler: impl Fn(::fidl_next::Server<P, zx::Channel>) -> H
485            + Send
486            + Sync
487            + Clone
488            + 'static,
489        ) -> &mut Self
490        where
491            P: ::fidl_next::Discoverable + ::fidl_next::DispatchServerMessage<H, zx::Channel>,
492            H: Send + Sync + 'static,
493        {
494            self.dir
495                .add_entry_impl(
496                    Name::from_static(P::PROTOCOL_NAME),
497                    endpoint(move |_, channel| {
498                        let create_handler = create_handler.clone();
499                        fasync::Task::spawn(async move {
500                            // TODO: logging?
501                            let server_end = ::fidl_next::ServerEnd::<P, zx::Channel>::from_untyped(
502                                channel.into_zx_channel(),
503                            );
504                            let dispatcher = ::fidl_next::ServerDispatcher::new(server_end);
505                            let handler = create_handler(dispatcher.server());
506                            dispatcher
507                                .run(handler)
508                                .await
509                                .expect("Protocol service was terminated ");
510                        })
511                        .detach();
512                    }),
513                    false,
514                )
515                .expect("Unable to add entry");
516            self
517        }
518
519        /// Adds a FIDL service to the directory.
520        ///
521        /// The FIDL service will be hosted at the name provided by the `Discoverable` annotation in
522        /// the FIDL source under `[instance_name]`.
523        ///
524        /// # Panics
525        ///
526        /// Panics if `instance_name` is not a valid `fuchsia.io` [`Name`], or if an entry has
527        /// already been added that conflicts with the service or instance directory structure.
528        pub fn add_fidl_next_service_instance<S, H>(
529            &mut self,
530            instance_name: impl TryInto<Name, Error: std::fmt::Debug>,
531            handler: H,
532        ) -> &mut Self
533        where
534            S: ::fidl_next::DiscoverableService
535                + ::fidl_next::DispatchServiceHandler<H, zx::Channel>
536                + 'static,
537            H: Send + Sync + 'static,
538        {
539            self.add_fidl_next_service_instance_at::<S, H>(
540                Name::from_static(S::SERVICE_NAME),
541                instance_name,
542                handler,
543            )
544        }
545
546        /// Adds a FIDL service to the directory with a custom service name.
547        ///
548        /// The FIDL service will be hosted at `[name]/[instance_name]/`.
549        ///
550        /// # Panics
551        ///
552        /// Panics if `name` or `instance_name` is not a valid `fuchsia.io` [`Name`], or if an entry
553        /// has already been added that conflicts with the service or instance directory structure.
554        pub fn add_fidl_next_service_instance_at<S, H>(
555            &mut self,
556            name: impl TryInto<Name, Error: std::fmt::Debug>,
557            instance_name: impl TryInto<Name, Error: std::fmt::Debug>,
558            handler: H,
559        ) -> &mut Self
560        where
561            S: ::fidl_next::DiscoverableService
562                + ::fidl_next::DispatchServiceHandler<H, zx::Channel>
563                + 'static,
564            H: Send + Sync + 'static,
565        {
566            // Create the service directory, with an instance subdirectory
567            let mut dir = self.dir(name);
568            let mut dir = dir.dir(instance_name);
569
570            let handler = std::sync::Arc::new(
571                ::fidl_next::ServiceHandlerAdapter::<S, H>::from_untyped(handler),
572            );
573
574            // Attach member protocols under the instance directory
575            for member_name in S::MEMBER_NAMES {
576                let handler = handler.clone();
577                dir.add_entry_at(
578                    Name::from_static(member_name),
579                    endpoint(move |_, channel| {
580                        ::fidl_next::protocol::ServiceHandler::on_connection(
581                            &*handler,
582                            member_name,
583                            channel.into_zx_channel(),
584                        );
585                    }),
586                );
587            }
588            self
589        }
590    };
591}
592
593impl<ServiceObjTy: ServiceObjTrait> ServiceFsDir<'_, ServiceObjTy> {
594    fn fs(&mut self) -> &mut ServiceFs<ServiceObjTy> {
595        self.fs
596    }
597
598    add_functions!();
599}
600
601impl<ServiceObjTy: ServiceObjTrait> ServiceFs<ServiceObjTy> {
602    fn new_impl() -> Self {
603        let (new_connection_sender, new_connection_receiver) = mpsc::unbounded();
604        let scope = ExecutionScope::new();
605        let dir = new_simple_dir();
606        Self {
607            scope: scope.clone(),
608            dir,
609            new_connection_sender,
610            new_connection_receiver,
611            services: Vec::new(),
612            shutdown: async move { scope.wait().await }.boxed(),
613            channel_queue: Some(Vec::new()),
614        }
615    }
616
617    fn fs(&mut self) -> &mut ServiceFs<ServiceObjTy> {
618        self
619    }
620
621    /// Get a reference to the root directory as a `ServiceFsDir`.
622    ///
623    /// This can be useful when writing code which hosts some set of services on
624    /// a directory and wants to be agnostic to whether that directory
625    /// is the root `ServiceFs` or a subdirectory.
626    ///
627    /// Such a function can take an `&mut ServiceFsDir<...>` as an argument,
628    /// allowing callers to provide either a subdirectory or `fs.root_dir()`.
629    pub fn root_dir(&mut self) -> ServiceFsDir<'_, ServiceObjTy> {
630        let dir = self.dir.clone();
631        ServiceFsDir { fs: self, dir }
632    }
633
634    add_functions!();
635
636    /// When a connection is first made to the `ServiceFs` in the absence of a parent connection,
637    /// it will be granted these rights.
638    const fn base_connection_flags() -> fio::Flags {
639        return fio::Flags::PROTOCOL_DIRECTORY
640            .union(fio::PERM_READABLE)
641            .union(fio::PERM_WRITABLE)
642            .union(fio::PERM_EXECUTABLE);
643    }
644
645    fn serve_connection_impl(&self, chan: fidl::endpoints::ServerEnd<fio::DirectoryMarker>) {
646        vfs::directory::serve_on(
647            self.dir.clone(),
648            Self::base_connection_flags(),
649            self.scope.clone(),
650            chan,
651        );
652    }
653
654    /// Creates a protocol connector that can access the capabilities exposed by this ServiceFs.
655    pub fn create_protocol_connector<O>(&mut self) -> Result<ProtocolConnector, Error>
656    where
657        ServiceObjTy: ServiceObjTrait<Output = O>,
658    {
659        let (directory_request, directory_server_end) = fidl::endpoints::create_endpoints();
660        self.serve_connection(directory_server_end)?;
661
662        Ok(ProtocolConnector { directory_request })
663    }
664}
665
666fn new_simple_dir() -> Arc<PseudoDir> {
667    PseudoDir::new_with_not_found_handler(move |name| {
668        warn!(
669            "ServiceFs received request for `{}` but has not been configured to serve this entry.",
670            name
671        );
672    })
673}
674
675/// `ProtocolConnector` allows connecting to capabilities exposed by ServiceFs
676pub struct ProtocolConnector {
677    directory_request: fidl::endpoints::ClientEnd<fio::DirectoryMarker>,
678}
679
680impl ProtocolConnector {
681    /// Connect to a protocol provided by this environment.
682    #[inline]
683    pub fn connect_to_service<P: DiscoverableProtocolMarker>(&self) -> Result<P::Proxy, Error> {
684        self.connect_to_protocol::<P>()
685    }
686
687    /// Connect to a protocol provided by this environment.
688    #[inline]
689    pub fn connect_to_protocol<P: DiscoverableProtocolMarker>(&self) -> Result<P::Proxy, Error> {
690        let (client_channel, server_channel) = zx::Channel::create();
691        self.pass_to_protocol::<P>(server_channel)?;
692        Ok(P::Proxy::from_channel(fasync::Channel::from_channel(client_channel)))
693    }
694
695    /// Connect to a protocol by passing a channel for the server.
696    #[inline]
697    pub fn pass_to_protocol<P: DiscoverableProtocolMarker>(
698        &self,
699        server_channel: zx::Channel,
700    ) -> Result<(), Error> {
701        self.pass_to_named_protocol(P::PROTOCOL_NAME, server_channel)
702    }
703
704    /// Connect to a protocol by name.
705    #[inline]
706    pub fn pass_to_named_protocol(
707        &self,
708        protocol_name: &str,
709        server_channel: zx::Channel,
710    ) -> Result<(), Error> {
711        fdio::service_connect_at(self.directory_request.channel(), protocol_name, server_channel)?;
712        Ok(())
713    }
714}
715
716/// An error indicating the startup handle on which the FIDL server
717/// attempted to start was missing.
718#[derive(Debug, Error)]
719#[error("The startup handle on which the FIDL server attempted to start was missing.")]
720pub struct MissingStartupHandle;
721
722impl<ServiceObjTy: ServiceObjTrait> ServiceFs<ServiceObjTy> {
723    /// Removes the `DirectoryRequest` startup handle for the current component and connects it to
724    /// this `ServiceFs` as a client.
725    ///
726    /// Multiple calls to this function from the same component will result in
727    /// `Err(MissingStartupHandle)`.
728    pub fn take_and_serve_directory_handle(&mut self) -> Result<&mut Self, Error> {
729        let startup_handle = fuchsia_runtime::take_startup_handle(
730            fuchsia_runtime::HandleType::DirectoryRequest.into(),
731        )
732        .ok_or(MissingStartupHandle)?;
733
734        self.serve_connection(fidl::endpoints::ServerEnd::new(zx::Channel::from(startup_handle)))
735    }
736
737    /// Add a channel to serve this `ServiceFs` filesystem on. The `ServiceFs`
738    /// will continue to be provided over previously added channels, including
739    /// the one added if `take_and_serve_directory_handle` was called.
740    pub fn serve_connection(
741        &mut self,
742        chan: fidl::endpoints::ServerEnd<fio::DirectoryMarker>,
743    ) -> Result<&mut Self, Error> {
744        if let Some(channels) = &mut self.channel_queue {
745            channels.push(chan);
746        } else {
747            self.serve_connection_impl(chan);
748        }
749        Ok(self)
750    }
751
752    /// TODO(https://fxbug.dev/326626515): this is an experimental method to run a FIDL
753    /// directory connection until stalled, with the purpose to cleanly stop a component.
754    /// We'll expect to revisit how this works to generalize to all connections later.
755    /// Try not to use this function for other purposes.
756    ///
757    /// Normally the [`ServiceFs`] stream will block until all connections are closed.
758    /// In order to escrow the outgoing directory server endpoint, you may use this
759    /// function to get a [`StallableServiceFs`] that detects when no new requests
760    /// hit the outgoing directory for `debounce_interval`, and all hosted protocols
761    /// and other VFS connections to finish, then yield back the outgoing directory handle.
762    ///
763    /// The [`ServiceFs`] stream yields [`ServiceObjTy::Output`], which could be an enum
764    /// of FIDL connection requests in a typical component. By contrast, [`StallableServiceFs`]
765    /// yields an enum of either the request, or the unbound outgoing directory endpoint,
766    /// allowing you to escrow it back to `component_manager` before exiting the component.
767    pub fn until_stalled(
768        self,
769        debounce_interval: MonotonicDuration,
770    ) -> StallableServiceFs<ServiceObjTy> {
771        StallableServiceFs::<ServiceObjTy>::new(self, debounce_interval)
772    }
773}
774
775impl<ServiceObjTy: ServiceObjTrait> Stream for ServiceFs<ServiceObjTy> {
776    type Item = ServiceObjTy::Output;
777
778    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
779        // NOTE: Normally, it isn't safe to poll a stream after it returns None, but we support this
780        // and StallableServiceFs depends on this.
781        if let Some(channels) = self.channel_queue.take() {
782            for chan in channels {
783                self.serve_connection_impl(chan);
784            }
785        }
786        while let Poll::Ready(Some((index, channel))) =
787            self.new_connection_receiver.poll_next_unpin(cx)
788        {
789            if let Some(stream) = self.services[index].service().connect(channel) {
790                return Poll::Ready(Some(stream));
791            }
792        }
793        self.shutdown.poll_unpin(cx).map(|_| None)
794    }
795}