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}