Skip to main content

elf_runner/
component.rs

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.
4
5use 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
26/// Immutable information about the component.
27///
28/// These information is shared with [`crate::ComponentSet`].
29pub struct ElfComponentInfo {
30    /// Moniker of the ELF component.
31    moniker: Moniker,
32
33    /// Instance token of the component.
34    component_instance: zx::Event,
35
36    /// Job in which the underlying process that represents the component is
37    /// running.
38    job: Arc<Job>,
39
40    /// We need to remember if we marked the main process as critical, because if we're asked to
41    /// kill a component that has such a marking it'll bring down everything.
42    main_process_critical: bool,
43
44    /// URL with which the component was launched.
45    component_url: String,
46
47    /// A connection to the component's outgoing directory.
48    ///
49    /// This option will be present if the ELF runner needs to open capabilities
50    /// exposed by the ELF component.
51    outgoing_directory_for_memory_attribution: Option<ClientEnd<fio::DirectoryMarker>>,
52
53    /// Configurations from the program block.
54    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    /// Return a pointer to the Job.
67    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    /// Return a handle to the Job containing the process for this component.
80    ///
81    /// The rights of the job will be set such that the resulting handle will be appropriate to
82    /// use for diagnostics-only purposes. Right now that is ZX_RIGHTS_BASIC (which includes
83    /// INSPECT).
84    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
93/// Structure representing a running elf component.
94pub struct ElfComponent {
95    /// Namespace directory for this component, kept just as a reference to
96    /// keep the namespace alive.
97    _runtime_dir: RuntimeDirectory,
98
99    /// Immutable information about the component.
100    info: Arc<ElfComponentInfo>,
101
102    /// Process made for the program binary defined for this component.
103    process: Option<Arc<Process>>,
104
105    /// Client end of the channel given to an ElfComponent which says it
106    /// implements the Lifecycle protocol. If the component does not implement
107    /// the protocol, this will be None.
108    lifecycle_channel: Option<LifecycleProxy>,
109
110    /// Scope that holds tasks to serve this component. For example, stdout and stderr
111    /// listeners are Task objects that live for the duration of the component's lifetime.
112    ///
113    /// Stop will block on these to complete.
114    local_scope: ExecutionScope,
115
116    /// A closure to be invoked when the object is dropped.
117    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    /// Sets a closure to be invoked when the object is dropped. Can only be done once.
153    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    /// Obtain immutable information about the component such as its job and URL.
163    pub fn info(&self) -> &Arc<ElfComponentInfo> {
164        &self.info
165    }
166
167    /// Return a pointer to the Process, returns None if the component has no
168    /// Process.
169    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 the component's main process is critical we must watch for
198            // the main process to exit, otherwise we could end up killing that
199            // process and therefore killing the root job.
200            if self.info().main_process_critical {
201                if self.process.is_none() {
202                    // This is a bit strange because there's no process, but there is a lifecycle
203                    // channel. Since there is no process it seems like killing it can't kill
204                    // component manager.
205                    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                // Try to duplicate the Process handle so we can us it to wait for
214                // process termination
215                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| ()) // Discard.
224                    .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| ())  // Discard.
240                    .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                        // The ELF program is expected to close the server endpoint.
294                        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        // notify others that this object is being dropped
309        if let Some(on_drop) = self.on_drop.lock().take() {
310            on_drop(self.info().as_ref());
311        }
312        // just in case we haven't killed the job already
313        self.info()
314            .job
315            .top()
316            .kill()
317            .unwrap_or_else(|error| error!(error:%; "failed to kill job in drop"));
318    }
319}