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