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}