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