1// Copyright 2021 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.
45use crate::config::ElfProgramConfig;
6use crate::runtime_dir::RuntimeDirectory;
7use crate::Job;
8use async_trait::async_trait;
9use fidl::endpoints::{ClientEnd, Proxy};
10use fidl_fuchsia_component_runner::ComponentControllerOnEscrowRequest;
11use fidl_fuchsia_process_lifecycle::{LifecycleEvent, LifecycleProxy};
12use futures::future::{join_all, BoxFuture, FutureExt};
13use futures::stream::BoxStream;
14use futures::StreamExt;
15use log::{error, warn};
16use moniker::Moniker;
17use runner::component::Controllable;
18use std::ops::DerefMut;
19use std::sync::{Arc, Mutex};
20use zx::{self as zx, AsHandleRef, HandleBased, Process, Task};
21use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
2223/// Immutable information about the component.
24///
25/// These information is shared with [`crate::ComponentSet`].
26pub struct ElfComponentInfo {
27/// Moniker of the ELF component.
28moniker: Moniker,
2930/// Instance token of the component.
31component_instance: zx::Event,
3233/// Job in which the underlying process that represents the component is
34 /// running.
35job: Arc<Job>,
3637/// We need to remember if we marked the main process as critical, because if we're asked to
38 /// kill a component that has such a marking it'll bring down everything.
39main_process_critical: bool,
4041/// URL with which the component was launched.
42component_url: String,
4344/// A connection to the component's outgoing directory.
45 ///
46 /// This option will be present if the ELF runner needs to open capabilities
47 /// exposed by the ELF component.
48outgoing_directory_for_memory_attribution: Option<ClientEnd<fio::DirectoryMarker>>,
4950/// Configurations from the program block.
51program_config: ElfProgramConfig,
52}
5354impl ElfComponentInfo {
55pub fn get_url(&self) -> &String {
56&self.component_url
57 }
5859pub fn get_moniker(&self) -> &Moniker {
60&self.moniker
61 }
6263/// Return a pointer to the Job.
64pub fn copy_job(&self) -> Arc<Job> {
65self.job.clone()
66 }
6768pub fn get_outgoing_directory(&self) -> Option<&ClientEnd<fio::DirectoryMarker>> {
69self.outgoing_directory_for_memory_attribution.as_ref()
70 }
7172pub fn get_program_config(&self) -> &ElfProgramConfig {
73&self.program_config
74 }
7576/// Return a handle to the Job containing the process for this component.
77 ///
78 /// The rights of the job will be set such that the resulting handle will be appropriate to
79 /// use for diagnostics-only purposes. Right now that is ZX_RIGHTS_BASIC (which includes
80 /// INSPECT).
81pub fn copy_job_for_diagnostics(&self) -> Result<zx::Job, zx::Status> {
82self.job.top().duplicate_handle(zx::Rights::BASIC)
83 }
8485pub fn copy_instance_token(&self) -> Result<zx::Event, zx::Status> {
86self.component_instance.duplicate_handle(zx::Rights::SAME_RIGHTS)
87 }
88}
8990/// Structure representing a running elf component.
91pub struct ElfComponent {
92/// Namespace directory for this component, kept just as a reference to
93 /// keep the namespace alive.
94_runtime_dir: RuntimeDirectory,
9596/// Immutable information about the component.
97info: Arc<ElfComponentInfo>,
9899/// Process made for the program binary defined for this component.
100process: Option<Arc<Process>>,
101102/// Client end of the channel given to an ElfComponent which says it
103 /// implements the Lifecycle protocol. If the component does not implement
104 /// the protocol, this will be None.
105lifecycle_channel: Option<LifecycleProxy>,
106107/// Any tasks spawned to serve this component. For example, stdout and stderr
108 /// listeners are Task objects that live for the duration of the component's
109 /// lifetime.
110 ///
111 /// Stop will block on these to complete.
112tasks: Option<Vec<fasync::Task<()>>>,
113114/// A closure to be invoked when the object is dropped.
115on_drop: Mutex<Option<Box<dyn FnOnce(&ElfComponentInfo) + Send + 'static>>>,
116}
117118impl ElfComponent {
119pub fn new(
120 _runtime_dir: RuntimeDirectory,
121 moniker: Moniker,
122 job: Job,
123 process: Process,
124 lifecycle_channel: Option<LifecycleProxy>,
125 main_process_critical: bool,
126 tasks: Vec<fasync::Task<()>>,
127 component_url: String,
128 outgoing_directory: Option<ClientEnd<fio::DirectoryMarker>>,
129 program_config: ElfProgramConfig,
130 component_instance: zx::Event,
131 ) -> Self {
132Self {
133 _runtime_dir,
134 info: Arc::new(ElfComponentInfo {
135 moniker,
136 component_instance,
137 job: Arc::new(job),
138 main_process_critical,
139 component_url,
140 outgoing_directory_for_memory_attribution: outgoing_directory,
141 program_config,
142 }),
143 process: Some(Arc::new(process)),
144 lifecycle_channel,
145 tasks: Some(tasks),
146 on_drop: Mutex::new(None),
147 }
148 }
149150/// Sets a closure to be invoked when the object is dropped. Can only be done once.
151pub fn set_on_drop(&self, func: impl FnOnce(&ElfComponentInfo) + Send + 'static) {
152let mut on_drop = self.on_drop.lock().unwrap();
153let previous = std::mem::replace(
154 on_drop.deref_mut(),
155Some(Box::new(func) as Box<dyn FnOnce(&ElfComponentInfo) + Send + 'static>),
156 );
157assert!(previous.is_none());
158 }
159160/// Obtain immutable information about the component such as its job and URL.
161pub fn info(&self) -> &Arc<ElfComponentInfo> {
162&self.info
163 }
164165/// Return a pointer to the Process, returns None if the component has no
166 /// Process.
167pub fn copy_process(&self) -> Option<Arc<Process>> {
168self.process.clone()
169 }
170}
171172#[async_trait]
173impl Controllable for ElfComponent {
174async fn kill(&mut self) {
175if self.info().main_process_critical {
176warn!("killing a component with 'main_process_critical', so this will also kill component_manager and all of its components");
177 }
178self.info()
179 .job
180 .top()
181 .kill()
182 .unwrap_or_else(|error| error!(error:%; "failed killing job during kill"));
183 }
184185fn stop<'a>(&mut self) -> BoxFuture<'a, ()> {
186if let Some(lifecycle_chan) = self.lifecycle_channel.take() {
187 lifecycle_chan.stop().unwrap_or_else(
188 |error| error!(error:%; "failed to stop lifecycle_chan during stop"),
189 );
190191let job = self.info().job.clone();
192193// If the component's main process is critical we must watch for
194 // the main process to exit, otherwise we could end up killing that
195 // process and therefore killing the root job.
196if self.info().main_process_critical {
197if self.process.is_none() {
198// This is a bit strange because there's no process, but there is a lifecycle
199 // channel. Since there is no process it seems like killing it can't kill
200 // component manager.
201warn!("killing job of component with 'main_process_critical' set because component has lifecycle channel, but no process main process.");
202self.info().job.top().kill().unwrap_or_else(|error| {
203error!(error:%; "failed killing job for component with no lifecycle channel")
204 });
205return async {}.boxed();
206 }
207// Try to duplicate the Process handle so we can us it to wait for
208 // process termination
209let proc_handle = self.process.take().unwrap();
210211async move {
212 fasync::OnSignals::new(
213&proc_handle.as_handle_ref(),
214 zx::Signals::PROCESS_TERMINATED,
215 )
216 .await
217.map(|_: fidl::Signals| ()) // Discard.
218.unwrap_or_else(|e| {
219error!(
220"killing component's job after failure waiting on process exit, err: {}",
221 e
222 )
223 });
224 job.top().kill().unwrap_or_else(|error| {
225error!(error:%; "failed killing job in stop after lifecycle channel closed")
226 });
227 }
228 .boxed()
229 } else {
230async move {
231 lifecycle_chan.on_closed()
232 .await
233.map(|_: fidl::Signals| ()) // Discard.
234.unwrap_or_else(|e| {
235error!(
236"killing component's job after failure waiting on lifecycle channel, err: {}",
237 e
238 )
239 });
240 job.top().kill().unwrap_or_else(|error| {
241error!(error:%; "failed killing job in stop after lifecycle channel closed")
242 });
243 }
244 .boxed()
245 }
246 } else {
247if self.info().main_process_critical {
248warn!(
249"killing job of component {} marked with 'main_process_critical' because \
250 component does not implement Lifecycle, so this will also kill component_manager \
251 and all of its components",
252self.info().get_url()
253 );
254 }
255self.info().job.top().kill().unwrap_or_else(|error| {
256error!(error:%; "failed killing job for component with no lifecycle channel")
257 });
258async {}.boxed()
259 }
260 }
261262fn teardown<'a>(&mut self) -> BoxFuture<'a, ()> {
263let tasks = self.tasks.take().unwrap_or_default();
264async move {
265 join_all(tasks).await;
266 }
267 .boxed()
268 }
269270fn on_escrow<'a>(&self) -> BoxStream<'a, ComponentControllerOnEscrowRequest> {
271let Some(lifecycle) = &self.lifecycle_channel else {
272return futures::stream::empty().boxed();
273 };
274 lifecycle
275 .take_event_stream()
276 .filter_map(|result| async {
277match result {
278Ok(LifecycleEvent::OnEscrow { payload }) => {
279Some(ComponentControllerOnEscrowRequest {
280 outgoing_dir: payload.outgoing_dir,
281 escrowed_dictionary: payload.escrowed_dictionary,
282 ..Default::default()
283 })
284 }
285Err(fidl::Error::ClientChannelClosed { .. }) => {
286// The ELF program is expected to close the server endpoint.
287None
288}
289Err(error) => {
290warn!(error:%; "error handling lifecycle channel events");
291None
292}
293 }
294 })
295 .boxed()
296 }
297}
298299impl Drop for ElfComponent {
300fn drop(&mut self) {
301// notify others that this object is being dropped
302if let Some(on_drop) = self.on_drop.lock().unwrap().take() {
303 on_drop(self.info().as_ref());
304 }
305// just in case we haven't killed the job already
306self.info()
307 .job
308 .top()
309 .kill()
310 .unwrap_or_else(|error| error!(error:%; "failed to kill job in drop"));
311 }
312}