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_process_lifecycle::{LifecycleEvent, LifecycleProxy};
12use futures::StreamExt;
13use futures::future::{BoxFuture, FutureExt};
14use futures::stream::BoxStream;
15use log::{error, warn};
16use moniker::Moniker;
17use runner::component::Controllable;
18use std::ops::DerefMut;
19use std::sync::{Arc, Mutex};
20use vfs::ExecutionScope;
21use zx::{self as zx, AsHandleRef, HandleBased, Process, Task};
22use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
23
24pub struct ElfComponentInfo {
28 moniker: Moniker,
30
31 component_instance: zx::Event,
33
34 job: Arc<Job>,
37
38 main_process_critical: bool,
41
42 component_url: String,
44
45 outgoing_directory_for_memory_attribution: Option<ClientEnd<fio::DirectoryMarker>>,
50
51 program_config: ElfProgramConfig,
53}
54
55impl ElfComponentInfo {
56 pub fn get_url(&self) -> &String {
57 &self.component_url
58 }
59
60 pub fn get_moniker(&self) -> &Moniker {
61 &self.moniker
62 }
63
64 pub fn copy_job(&self) -> Arc<Job> {
66 self.job.clone()
67 }
68
69 pub fn get_outgoing_directory(&self) -> Option<&ClientEnd<fio::DirectoryMarker>> {
70 self.outgoing_directory_for_memory_attribution.as_ref()
71 }
72
73 pub fn get_program_config(&self) -> &ElfProgramConfig {
74 &self.program_config
75 }
76
77 pub fn copy_job_for_diagnostics(&self) -> Result<zx::Job, zx::Status> {
83 self.job.top().duplicate_handle(zx::Rights::BASIC)
84 }
85
86 pub fn copy_instance_token(&self) -> Result<zx::Event, zx::Status> {
87 self.component_instance.duplicate_handle(zx::Rights::SAME_RIGHTS)
88 }
89}
90
91pub struct ElfComponent {
93 _runtime_dir: RuntimeDirectory,
96
97 info: Arc<ElfComponentInfo>,
99
100 process: Option<Arc<Process>>,
102
103 lifecycle_channel: Option<LifecycleProxy>,
107
108 local_scope: ExecutionScope,
113
114 on_drop: Mutex<Option<Box<dyn FnOnce(&ElfComponentInfo) + Send + 'static>>>,
116}
117
118impl ElfComponent {
119 pub 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 local_scope: ExecutionScope,
127 component_url: String,
128 outgoing_directory: Option<ClientEnd<fio::DirectoryMarker>>,
129 program_config: ElfProgramConfig,
130 component_instance: zx::Event,
131 ) -> Self {
132 Self {
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 local_scope,
146 on_drop: Mutex::new(None),
147 }
148 }
149
150 pub fn set_on_drop(&self, func: impl FnOnce(&ElfComponentInfo) + Send + 'static) {
152 let mut on_drop = self.on_drop.lock().unwrap();
153 let previous = std::mem::replace(
154 on_drop.deref_mut(),
155 Some(Box::new(func) as Box<dyn FnOnce(&ElfComponentInfo) + Send + 'static>),
156 );
157 assert!(previous.is_none());
158 }
159
160 pub fn info(&self) -> &Arc<ElfComponentInfo> {
162 &self.info
163 }
164
165 pub fn copy_process(&self) -> Option<Arc<Process>> {
168 self.process.clone()
169 }
170}
171
172#[async_trait]
173impl Controllable for ElfComponent {
174 async fn kill(&mut self) {
175 if self.info().main_process_critical {
176 warn!(
177 "killing a component with 'main_process_critical', so this will also kill component_manager and all of its components"
178 );
179 }
180 self.info()
181 .job
182 .top()
183 .kill()
184 .unwrap_or_else(|error| error!(error:%; "failed killing job during kill"));
185 }
186
187 fn stop<'a>(&mut self) -> BoxFuture<'a, ()> {
188 if let Some(lifecycle_chan) = self.lifecycle_channel.take() {
189 lifecycle_chan.stop().unwrap_or_else(
190 |error| error!(error:%; "failed to stop lifecycle_chan during stop"),
191 );
192
193 let job = self.info().job.clone();
194
195 if self.info().main_process_critical {
199 if self.process.is_none() {
200 warn!(
204 "killing job of component with 'main_process_critical' set because component has lifecycle channel, but no process main process."
205 );
206 self.info().job.top().kill().unwrap_or_else(|error| {
207 error!(error:%; "failed killing job for component with no lifecycle channel")
208 });
209 return async {}.boxed();
210 }
211 let proc_handle = self.process.take().unwrap();
214
215 async move {
216 fasync::OnSignals::new(
217 &proc_handle.as_handle_ref(),
218 zx::Signals::PROCESS_TERMINATED,
219 )
220 .await
221 .map(|_: fidl::Signals| ()) .unwrap_or_else(|e| {
223 error!(
224 "killing component's job after failure waiting on process exit, err: {}",
225 e
226 )
227 });
228 job.top().kill().unwrap_or_else(|error| {
229 error!(error:%; "failed killing job in stop after lifecycle channel closed")
230 });
231 }
232 .boxed()
233 } else {
234 async move {
235 lifecycle_chan.on_closed()
236 .await
237 .map(|_: fidl::Signals| ()) .unwrap_or_else(|e| {
239 error!(
240 "killing component's job after failure waiting on lifecycle channel, err: {}",
241 e
242 )
243 });
244 job.top().kill().unwrap_or_else(|error| {
245 error!(error:%; "failed killing job in stop after lifecycle channel closed")
246 });
247 }
248 .boxed()
249 }
250 } else {
251 if self.info().main_process_critical {
252 warn!(
253 "killing job of component {} marked with 'main_process_critical' because \
254 component does not implement Lifecycle, so this will also kill component_manager \
255 and all of its components",
256 self.info().get_url()
257 );
258 }
259 self.info().job.top().kill().unwrap_or_else(|error| {
260 error!(error:%; "failed killing job for component with no lifecycle channel")
261 });
262 async {}.boxed()
263 }
264 }
265
266 fn teardown<'a>(&mut self) -> BoxFuture<'a, ()> {
267 let scope = self.local_scope.clone();
268 async move {
269 scope.wait().await;
270 }
271 .boxed()
272 }
273
274 fn on_escrow<'a>(&self) -> BoxStream<'a, ComponentControllerOnEscrowRequest> {
275 let Some(lifecycle) = &self.lifecycle_channel else {
276 return futures::stream::empty().boxed();
277 };
278 lifecycle
279 .take_event_stream()
280 .filter_map(|result| async {
281 match result {
282 Ok(LifecycleEvent::OnEscrow { payload }) => {
283 Some(ComponentControllerOnEscrowRequest {
284 outgoing_dir: payload.outgoing_dir,
285 escrowed_dictionary: payload.escrowed_dictionary,
286 ..Default::default()
287 })
288 }
289 Err(fidl::Error::ClientChannelClosed { .. }) => {
290 None
292 }
293 Err(error) => {
294 warn!(error:%; "error handling lifecycle channel events");
295 None
296 }
297 }
298 })
299 .boxed()
300 }
301}
302
303impl Drop for ElfComponent {
304 fn drop(&mut self) {
305 if let Some(on_drop) = self.on_drop.lock().unwrap().take() {
307 on_drop(self.info().as_ref());
308 }
309 self.info()
311 .job
312 .top()
313 .kill()
314 .unwrap_or_else(|error| error!(error:%; "failed to kill job in drop"));
315 }
316}