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