| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| super::{error::ElfRunnerError, runtime_dir::RuntimeDirectory}, |
| async_trait::async_trait, |
| fidl::endpoints::Proxy, |
| fidl_fuchsia_process_lifecycle::LifecycleProxy, |
| fuchsia_async as fasync, |
| fuchsia_zircon::{self as zx, AsHandleRef, Job, Process, Task}, |
| futures::future::{BoxFuture, FutureExt}, |
| log::{error, warn}, |
| runner::component::Controllable, |
| std::sync::Arc, |
| }; |
| |
| /// Structure representing a running elf component. |
| pub struct ElfComponent { |
| /// Namespace directory for this component, kept just as a reference to |
| /// keep the namespace alive. |
| _runtime_dir: RuntimeDirectory, |
| |
| /// Job in which the underlying process that represents the component is |
| /// running. |
| job: Arc<Job>, |
| |
| /// Process made for the program binary defined for this component. |
| process: Option<Arc<Process>>, |
| |
| /// Client end of the channel given to an ElfComponent which says it |
| /// implements the Lifecycle protocol. If the component does not implement |
| /// the protocol, this will be None. |
| lifecycle_channel: Option<LifecycleProxy>, |
| |
| /// We need to remember if we marked the main process as critical, because if we're asked to |
| /// kill a component that has such a marking it'll bring down everything. |
| main_process_critical: bool, |
| |
| /// Any tasks spawned to serve this component. For example, stdout and stderr |
| /// listeners are Task objects that live for the duration of the component's |
| /// lifetime. |
| _tasks: Vec<fasync::Task<()>>, |
| |
| /// URL with which the component was launched. |
| component_url: String, |
| } |
| |
| impl ElfComponent { |
| pub fn new( |
| _runtime_dir: RuntimeDirectory, |
| job: Job, |
| process: Process, |
| lifecycle_channel: Option<LifecycleProxy>, |
| main_process_critical: bool, |
| tasks: Vec<fasync::Task<()>>, |
| component_url: String, |
| ) -> Self { |
| Self { |
| _runtime_dir, |
| job: Arc::new(job), |
| process: Some(Arc::new(process)), |
| lifecycle_channel, |
| main_process_critical, |
| _tasks: tasks, |
| component_url, |
| } |
| } |
| |
| /// Return a pointer to the Process, returns None if the component has no |
| /// Process. |
| pub fn copy_process(&self) -> Option<Arc<Process>> { |
| self.process.clone() |
| } |
| |
| /// Return a handle to the Job containing the process for this component. |
| /// |
| /// The rights of the job will be set such that the resulting handle will be apppropriate to |
| /// use for diagnostics-only purposes. Right now that is ZX_RIGHTS_BASIC (which includes |
| /// INSPECT). |
| pub fn copy_job_for_diagnostics(&self) -> Result<Job, ElfRunnerError> { |
| self.job.as_handle_ref().duplicate(zx::Rights::BASIC).map(|h| Job::from(h)).map_err( |
| |status| { |
| ElfRunnerError::component_job_duplication_error(self.component_url.clone(), status) |
| }, |
| ) |
| } |
| } |
| |
| #[async_trait] |
| impl Controllable for ElfComponent { |
| async fn kill(mut self) { |
| if self.main_process_critical { |
| warn!("killing a component with 'main_process_critical', so this will also kill component_manager and all of its components"); |
| } |
| self.job.kill().unwrap_or_else(|e| error!("failed killing job during kill: {}", e)); |
| } |
| |
| fn stop<'a>(&mut self) -> BoxFuture<'a, ()> { |
| if let Some(lifecycle_chan) = self.lifecycle_channel.take() { |
| lifecycle_chan |
| .stop() |
| .unwrap_or_else(|e| error!("failed to stop lifecycle_chan during stop: {}", e)); |
| |
| let job = self.job.clone(); |
| |
| // If the component's main process is critical we must watch for |
| // the main process to exit, otherwise we could end up killing that |
| // process and therefore killing the root job. |
| if self.main_process_critical { |
| if self.process.is_none() { |
| // This is a bit strange because there's no process, but there is a lifecycle |
| // channel. Since there is no process it seems like killing it can't kill |
| // component manager. |
| warn!("killing job of component with 'main_process_critical' set because component has lifecycle channel, but no process main process."); |
| self.job.kill().unwrap_or_else(|e| { |
| error!("failed killing job for component with no lifecycle channel: {}", e) |
| }); |
| return async {}.boxed(); |
| } |
| // Try to duplicate the Process handle so we can us it to wait for |
| // process termination |
| let proc_handle = self.process.take().unwrap(); |
| |
| async move { |
| fasync::OnSignals::new( |
| &proc_handle.as_handle_ref(), |
| zx::Signals::PROCESS_TERMINATED, |
| ) |
| .await |
| .map(|_: fidl::Signals| ()) // Discard. |
| .unwrap_or_else(|e| { |
| error!( |
| "killing component's job after failure waiting on process exit, err: {}", |
| e |
| ) |
| }); |
| job.kill().unwrap_or_else(|e| { |
| error!("failed killing job in stop after lifecycle channel closed: {}", e) |
| }); |
| } |
| .boxed() |
| } else { |
| async move { |
| lifecycle_chan.on_closed() |
| .await |
| .map(|_: fidl::Signals| ()) // Discard. |
| .unwrap_or_else(|e| { |
| error!( |
| "killing component's job after failure waiting on lifecycle channel, err: {}", |
| e |
| ) |
| }); |
| job.kill().unwrap_or_else(|e| { |
| error!("failed killing job in stop after lifecycle channel closed: {}", e) |
| }); |
| } |
| .boxed() |
| } |
| } else { |
| if self.main_process_critical { |
| warn!( |
| "killing job of component {} marked with 'main_process_critical' because \ |
| component does not implement Lifecycle, so this will also kill component_manager \ |
| and all of its components", |
| self.component_url |
| ); |
| } |
| self.job.kill().unwrap_or_else(|e| { |
| error!("failed killing job for component with no lifecycle channel: {}", e) |
| }); |
| async {}.boxed() |
| } |
| } |
| } |
| |
| impl Drop for ElfComponent { |
| fn drop(&mut self) { |
| // just in case we haven't killed the job already |
| self.job.kill().unwrap_or_else(|e| error!("failed to kill job in drop: {}", e)); |
| } |
| } |