elf_runner/
runtime_dir.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 fidl::endpoints::ServerEnd;
6use fidl_fuchsia_io as fio;
7use pseudo_fs::{
8    LazyPseudoDirectory, LazyPseudoDirectoryState, PseudoDirectory, PseudoFile, ToPseudoDirectory,
9};
10use std::sync::Arc;
11use vfs::ToObjectRequest;
12use vfs::directory::entry::{DirectoryEntry, OpenRequest};
13use vfs::directory::helper::DirectlyMutable;
14use vfs::execution_scope::ExecutionScope;
15
16// Simple directory type which is used to implement `ComponentStartInfo.runtime_directory`.
17pub struct RuntimeDirectory(Arc<LazyPseudoDirectory<RuntimeDirectoryInfo>>);
18
19struct RuntimeDirectoryInfo {
20    args: Box<[Box<str>]>,
21    job_id: Option<u64>,
22    process_id: Option<u64>,
23    process_start_time: Option<i64>,
24    process_start_time_utc_estimate: Option<Box<str>>,
25}
26
27impl ToPseudoDirectory for RuntimeDirectoryInfo {
28    fn to_pseudo_directory(self) -> Arc<PseudoDirectory> {
29        // Create the runtime tree structure:
30        //
31        // runtime
32        // |- args
33        // |  |- 0
34        // |  |- 1
35        // |  \- ...
36        // \- elf
37        //    |- job_id
38        //    |- process_id
39        //    |- process_start_time
40        //    \- process_start_time_utc_estimate
41
42        let args_dir = PseudoDirectory::new();
43        for (i, arg) in self.args.iter().enumerate() {
44            args_dir
45                .add_entry(i.to_string(), PseudoFile::from_data(arg.as_bytes()))
46                .expect("Failed to add arg to runtime/args directory");
47        }
48
49        let elf_dir = PseudoDirectory::new();
50        if let Some(job_id) = self.job_id {
51            elf_dir
52                .add_entry("job_id", PseudoFile::from_data(job_id.to_string()))
53                .expect("Failed to add job_id to runtime/elf directory");
54        }
55        if let Some(process_id) = self.process_id {
56            elf_dir
57                .add_entry("process_id", PseudoFile::from_data(process_id.to_string()))
58                .expect("Failed to add process_id to runtime/elf directory");
59        }
60        if let Some(process_start_time) = self.process_start_time {
61            elf_dir
62                .add_entry(
63                    "process_start_time",
64                    PseudoFile::from_data(process_start_time.to_string()),
65                )
66                .expect("Failed to add process_start_time to runtime/elf directory");
67        }
68        if let Some(process_start_time_utc_estimate) = self.process_start_time_utc_estimate {
69            elf_dir
70                .add_entry(
71                    "process_start_time_utc_estimate",
72                    PseudoFile::from_data(process_start_time_utc_estimate.as_bytes()),
73                )
74                .expect("Failed to add process_start_time_utc_estimate to runtime/elf directory");
75        }
76
77        let dir = PseudoDirectory::new();
78        dir.add_entry("args", args_dir).expect("Failed to add args directory to runtime directory");
79        dir.add_entry("elf", elf_dir).expect("Failed to add elf directory to runtime directory");
80        dir
81    }
82}
83
84impl RuntimeDirectory {
85    pub fn add_process_id(&self, value: u64) {
86        match self.0.state() {
87            LazyPseudoDirectoryState::Data(mut data) => data.process_id = Some(value),
88            LazyPseudoDirectoryState::Directory(dir) => get_elf_dir(&*dir)
89                .add_entry("process_id", PseudoFile::from_data(value.to_string()))
90                .expect("failed to add process_id"),
91        }
92    }
93
94    pub fn add_process_start_time(&self, value: i64) {
95        match self.0.state() {
96            LazyPseudoDirectoryState::Data(mut data) => data.process_start_time = Some(value),
97            LazyPseudoDirectoryState::Directory(dir) => get_elf_dir(&*dir)
98                .add_entry("process_start_time", PseudoFile::from_data(value.to_string()))
99                .expect("failed to add process_start_time"),
100        }
101    }
102
103    pub fn add_process_start_time_utc_estimate(&self, value: String) {
104        match self.0.state() {
105            LazyPseudoDirectoryState::Data(mut data) => {
106                data.process_start_time_utc_estimate = Some(value.into_boxed_str())
107            }
108            LazyPseudoDirectoryState::Directory(dir) => get_elf_dir(&*dir)
109                .add_entry("process_start_time_utc_estimate", PseudoFile::from_data(value))
110                .expect("failed to add process_start_time_utc_estimate"),
111        }
112    }
113
114    // Create an empty runtime directory, for test purpose only.
115    #[cfg(test)]
116    pub fn empty() -> Self {
117        RuntimeDirectory(LazyPseudoDirectory::new(RuntimeDirectoryInfo {
118            args: Box::default(),
119            job_id: None,
120            process_id: None,
121            process_start_time: None,
122            process_start_time_utc_estimate: None,
123        }))
124    }
125}
126
127pub struct RuntimeDirBuilder {
128    args: Box<[Box<str>]>,
129    job_id: Option<u64>,
130    server_end: ServerEnd<fio::DirectoryMarker>,
131}
132
133impl RuntimeDirBuilder {
134    pub fn new(server_end: ServerEnd<fio::DirectoryMarker>) -> Self {
135        Self { args: Box::default(), job_id: None, server_end }
136    }
137
138    pub fn args(mut self, args: Vec<String>) -> Self {
139        self.args = args.into_iter().map(|arg| arg.into_boxed_str()).collect();
140        self
141    }
142
143    pub fn job_id(mut self, job_id: u64) -> Self {
144        self.job_id = Some(job_id);
145        self
146    }
147
148    pub fn serve(self) -> RuntimeDirectory {
149        let runtime_directory = LazyPseudoDirectory::new(RuntimeDirectoryInfo {
150            args: self.args,
151            job_id: self.job_id,
152            process_id: None,
153            process_start_time: None,
154            process_start_time_utc_estimate: None,
155        });
156        let flags = fio::PERM_READABLE | fio::PERM_WRITABLE;
157        let object_request = flags.to_object_request(self.server_end);
158        object_request.handle(|object_request| {
159            let open_request =
160                OpenRequest::new(ExecutionScope::new(), flags, vfs::Path::dot(), object_request);
161            runtime_directory.clone().open_entry(open_request)
162        });
163
164        RuntimeDirectory(runtime_directory)
165    }
166}
167
168fn get_elf_dir(dir: &PseudoDirectory) -> Arc<PseudoDirectory> {
169    dir.get_entry("elf")
170        .expect("elf directory should be present")
171        .into_any()
172        .downcast::<PseudoDirectory>()
173        .expect("could not downcast elf to a directory")
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use fuchsia_fs::directory::read_file_to_string;
180
181    async fn force_create_directory(client: &fio::DirectoryProxy) {
182        fuchsia_fs::directory::open_directory(client, "elf", fio::PERM_READABLE)
183            .await
184            .expect("failed to open elf directory");
185    }
186
187    #[fuchsia::test]
188    async fn test_read_job_id() {
189        let (client, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
190        RuntimeDirBuilder::new(server_end).job_id(1234).serve();
191
192        assert_eq!(
193            read_file_to_string(&client, "elf/job_id").await.expect("failed to read file"),
194            "1234"
195        );
196    }
197
198    #[fuchsia::test]
199    async fn test_read_args() {
200        let (client, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
201        RuntimeDirBuilder::new(server_end)
202            .args(vec!["arg1".to_string(), "arg2".to_string()])
203            .serve();
204
205        assert_eq!(
206            read_file_to_string(&client, "args/0").await.expect("failed to read file"),
207            "arg1"
208        );
209        assert_eq!(
210            read_file_to_string(&client, "args/1").await.expect("failed to read file"),
211            "arg2"
212        );
213    }
214
215    #[fuchsia::test]
216    async fn test_add_process_id_before_connecting() {
217        let (client, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
218        let runtime_directory = RuntimeDirBuilder::new(server_end).serve();
219        assert!(runtime_directory.0.state().is_data());
220
221        runtime_directory.add_process_id(1234);
222        assert_eq!(
223            read_file_to_string(&client, "elf/process_id").await.expect("failed to read file"),
224            "1234"
225        );
226    }
227
228    #[fuchsia::test]
229    async fn test_add_process_id_after_connecting() {
230        let (client, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
231        let runtime_directory = RuntimeDirBuilder::new(server_end).serve();
232        force_create_directory(&client).await;
233        assert!(runtime_directory.0.state().is_directory());
234
235        runtime_directory.add_process_id(1234);
236        assert_eq!(
237            read_file_to_string(&client, "elf/process_id").await.expect("failed to read file"),
238            "1234"
239        );
240    }
241
242    #[fuchsia::test]
243    async fn test_add_process_start_time_before_connecting() {
244        let (client, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
245        let runtime_directory = RuntimeDirBuilder::new(server_end).serve();
246        assert!(runtime_directory.0.state().is_data());
247
248        runtime_directory.add_process_start_time(1234);
249        assert_eq!(
250            read_file_to_string(&client, "elf/process_start_time")
251                .await
252                .expect("failed to read file"),
253            "1234"
254        );
255    }
256
257    #[fuchsia::test]
258    async fn test_add_process_start_time_after_connecting() {
259        let (client, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
260        let runtime_directory = RuntimeDirBuilder::new(server_end).serve();
261        force_create_directory(&client).await;
262        assert!(runtime_directory.0.state().is_directory());
263
264        runtime_directory.add_process_start_time(1234);
265        assert_eq!(
266            read_file_to_string(&client, "elf/process_start_time")
267                .await
268                .expect("failed to read file"),
269            "1234"
270        );
271    }
272
273    #[fuchsia::test]
274    async fn test_add_process_start_time_utc_estimate_before_connecting() {
275        let (client, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
276        let runtime_directory = RuntimeDirBuilder::new(server_end).serve();
277        assert!(runtime_directory.0.state().is_data());
278
279        runtime_directory.add_process_start_time_utc_estimate("start-time".to_string());
280        assert_eq!(
281            read_file_to_string(&client, "elf/process_start_time_utc_estimate")
282                .await
283                .expect("failed to read file"),
284            "start-time"
285        );
286    }
287
288    #[fuchsia::test]
289    async fn test_add_process_start_time_utc_estimate_after_connecting() {
290        let (client, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
291        let runtime_directory = RuntimeDirBuilder::new(server_end).serve();
292        force_create_directory(&client).await;
293        assert!(runtime_directory.0.state().is_directory());
294
295        runtime_directory
296            .add_process_start_time_utc_estimate("start-time-utc-estimate".to_string());
297        assert_eq!(
298            read_file_to_string(&client, "elf/process_start_time_utc_estimate")
299                .await
300                .expect("failed to read file"),
301            "start-time-utc-estimate"
302        );
303    }
304}