1use crate::Job;
6use crate::config::ElfProgramConfig;
7use crate::runtime_dir::RuntimeDirectory;
8use async_trait::async_trait;
9use fidl::endpoints::{ClientEnd, Proxy};
10use fidl_fuchsia_component_runner::ComponentControllerOnEscrowRequest;
11use fidl_fuchsia_io as fio;
12use fidl_fuchsia_process_lifecycle::{LifecycleEvent, LifecycleProxy};
13use fuchsia_async as fasync;
14use fuchsia_sync::Mutex;
15use futures::StreamExt;
16use futures::future::{BoxFuture, FutureExt};
17use futures::stream::BoxStream;
18use log::{error, warn};
19use moniker::Moniker;
20use runner::component::Controllable;
21use std::ops::DerefMut;
22use std::sync::Arc;
23use vfs::ExecutionScope;
24use zx::{Process, Task};
25
26pub struct ElfComponentInfo {
30 moniker: Moniker,
32
33 component_instance: zx::Event,
35
36 job: Arc<Job>,
39
40 main_process_critical: bool,
43
44 component_url: String,
46
47 outgoing_directory_for_memory_attribution: Option<ClientEnd<fio::DirectoryMarker>>,
52
53 program_config: ElfProgramConfig,
55}
56
57impl ElfComponentInfo {
58 pub fn get_url(&self) -> &String {
59 &self.component_url
60 }
61
62 pub fn get_moniker(&self) -> &Moniker {
63 &self.moniker
64 }
65
66 pub fn copy_job(&self) -> Arc<Job> {
68 self.job.clone()
69 }
70
71 pub fn get_outgoing_directory(&self) -> Option<&ClientEnd<fio::DirectoryMarker>> {
72 self.outgoing_directory_for_memory_attribution.as_ref()
73 }
74
75 pub fn get_program_config(&self) -> &ElfProgramConfig {
76 &self.program_config
77 }
78
79 pub fn copy_job_for_diagnostics(&self) -> Result<zx::Job, zx::Status> {
85 self.job.top().duplicate_handle(zx::Rights::BASIC)
86 }
87
88 pub fn copy_instance_token(&self) -> Result<zx::Event, zx::Status> {
89 self.component_instance.duplicate_handle(zx::Rights::SAME_RIGHTS)
90 }
91}
92
93pub struct ElfComponent {
95 _runtime_dir: RuntimeDirectory,
98
99 info: Arc<ElfComponentInfo>,
101
102 process: Option<Arc<Process>>,
104
105 lifecycle_channel: Option<LifecycleProxy>,
109
110 local_scope: ExecutionScope,
115
116 on_drop: Mutex<Option<Box<dyn FnOnce(&ElfComponentInfo) + Send + 'static>>>,
118}
119
120impl ElfComponent {
121 pub fn new(
122 _runtime_dir: RuntimeDirectory,
123 moniker: Moniker,
124 job: Job,
125 process: Process,
126 lifecycle_channel: Option<LifecycleProxy>,
127 main_process_critical: bool,
128 local_scope: ExecutionScope,
129 component_url: String,
130 outgoing_directory: Option<ClientEnd<fio::DirectoryMarker>>,
131 program_config: ElfProgramConfig,
132 component_instance: zx::Event,
133 ) -> Self {
134 Self {
135 _runtime_dir,
136 info: Arc::new(ElfComponentInfo {
137 moniker,
138 component_instance,
139 job: Arc::new(job),
140 main_process_critical,
141 component_url,
142 outgoing_directory_for_memory_attribution: outgoing_directory,
143 program_config,
144 }),
145 process: Some(Arc::new(process)),
146 lifecycle_channel,
147 local_scope,
148 on_drop: Mutex::new(None),
149 }
150 }
151
152 pub fn set_on_drop(&self, func: impl FnOnce(&ElfComponentInfo) + Send + 'static) {
154 let mut on_drop = self.on_drop.lock();
155 let previous = std::mem::replace(
156 on_drop.deref_mut(),
157 Some(Box::new(func) as Box<dyn FnOnce(&ElfComponentInfo) + Send + 'static>),
158 );
159 assert!(previous.is_none());
160 }
161
162 pub fn info(&self) -> &Arc<ElfComponentInfo> {
164 &self.info
165 }
166
167 pub fn copy_process(&self) -> Option<Arc<Process>> {
170 self.process.clone()
171 }
172}
173
174#[async_trait]
175impl Controllable for ElfComponent {
176 async fn kill(&mut self) {
177 if self.info().main_process_critical {
178 warn!(
179 "killing a component with 'main_process_critical', so this will also kill component_manager and all of its components"
180 );
181 }
182 self.info()
183 .job
184 .top()
185 .kill()
186 .unwrap_or_else(|error| error!(error:%; "failed killing job during kill"));
187 }
188
189 fn stop<'a>(&mut self) -> BoxFuture<'a, ()> {
190 if let Some(lifecycle_chan) = self.lifecycle_channel.take() {
191 lifecycle_chan.stop().unwrap_or_else(
192 |error| error!(error:%; "failed to stop lifecycle_chan during stop"),
193 );
194
195 let job = self.info().job.clone();
196
197 if self.info().main_process_critical {
201 if self.process.is_none() {
202 warn!(
206 "killing job of component with 'main_process_critical' set because component has lifecycle channel, but no process main process."
207 );
208 self.info().job.top().kill().unwrap_or_else(|error| {
209 error!(error:%; "failed killing job for component with no lifecycle channel")
210 });
211 return async {}.boxed();
212 }
213 let proc_handle = self.process.take().unwrap();
216
217 async move {
218 fasync::OnSignals::new(
219 &proc_handle.as_handle_ref(),
220 zx::Signals::PROCESS_TERMINATED,
221 )
222 .await
223 .map(|_: fidl::Signals| ()) .unwrap_or_else(|e| {
225 error!(
226 "killing component's job after failure waiting on process exit, err: {}",
227 e
228 )
229 });
230 job.top().kill().unwrap_or_else(|error| {
231 error!(error:%; "failed killing job in stop after lifecycle channel closed")
232 });
233 }
234 .boxed()
235 } else {
236 async move {
237 lifecycle_chan.on_closed()
238 .await
239 .map(|_: fidl::Signals| ()) .unwrap_or_else(|e| {
241 error!(
242 "killing component's job after failure waiting on lifecycle channel, err: {}",
243 e
244 )
245 });
246 job.top().kill().unwrap_or_else(|error| {
247 error!(error:%; "failed killing job in stop after lifecycle channel closed")
248 });
249 }
250 .boxed()
251 }
252 } else {
253 if self.info().main_process_critical {
254 warn!(
255 "killing job of component {} marked with 'main_process_critical' because \
256 component does not implement Lifecycle, so this will also kill component_manager \
257 and all of its components",
258 self.info().get_url()
259 );
260 }
261 self.info().job.top().kill().unwrap_or_else(|error| {
262 error!(error:%; "failed killing job for component with no lifecycle channel")
263 });
264 async {}.boxed()
265 }
266 }
267
268 fn teardown<'a>(&mut self) -> BoxFuture<'a, ()> {
269 let scope = self.local_scope.clone();
270 async move {
271 scope.wait().await;
272 }
273 .boxed()
274 }
275
276 fn on_escrow<'a>(&self) -> BoxStream<'a, ComponentControllerOnEscrowRequest> {
277 let Some(lifecycle) = &self.lifecycle_channel else {
278 return futures::stream::empty().boxed();
279 };
280 lifecycle
281 .take_event_stream()
282 .filter_map(|result| async {
283 match result {
284 Ok(LifecycleEvent::OnEscrow { payload }) => {
285 Some(ComponentControllerOnEscrowRequest {
286 outgoing_dir: payload.outgoing_dir,
287 escrowed_dictionary: payload.escrowed_dictionary,
288 escrowed_dictionary_handle: payload.escrowed_dictionary_handle,
289 ..Default::default()
290 })
291 }
292 Err(fidl::Error::ClientChannelClosed { .. }) => {
293 None
295 }
296 Err(error) => {
297 warn!(error:%; "error handling lifecycle channel events");
298 None
299 }
300 }
301 })
302 .boxed()
303 }
304}
305
306impl Drop for ElfComponent {
307 fn drop(&mut self) {
308 if let Some(on_drop) = self.on_drop.lock().take() {
310 on_drop(self.info().as_ref());
311 }
312 self.info()
314 .job
315 .top()
316 .kill()
317 .unwrap_or_else(|error| error!(error:%; "failed to kill job in drop"));
318 }
319}