Skip to main content

driver_manager_driver_host/
runtime_dir.rs

1// Copyright 2026 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 async_lock::OnceCell;
6use std::sync::Arc;
7use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
8use vfs::directory::simple::Simple;
9use vfs::execution_scope::ExecutionScope;
10use vfs::file::{FidlIoConnection, File, FileIo, FileLike, FileOptions, SyncMode, read_only};
11use vfs::node::Node;
12use vfs::{ObjectRequestRef, immutable_attributes, pseudo_directory};
13use zx::Status;
14use {fidl_fuchsia_driver_host as fdh, fidl_fuchsia_io as fio};
15
16#[derive(Clone)]
17pub struct ProcessInfo {
18    pub job_koid: zx::Koid,
19    pub process_koid: zx::Koid,
20    pub main_thread_koid: zx::Koid,
21    pub threads: Vec<fdh::ThreadInfo>,
22    pub dispatchers: Vec<fdh::DispatcherInfo>,
23}
24
25pub(crate) struct CachedProcessInfo {
26    cell: OnceCell<ProcessInfo>,
27    driver_host: fdh::DriverHostProxy,
28}
29
30impl CachedProcessInfo {
31    pub(crate) fn new(driver_host: fdh::DriverHostProxy) -> Self {
32        Self { cell: OnceCell::new(), driver_host }
33    }
34
35    pub(crate) async fn get(&self) -> Result<&ProcessInfo, zx::Status> {
36        self.cell
37            .get_or_try_init(|| async {
38                match self.driver_host.get_process_info().await {
39                    Ok(Ok((job_koid, process_koid, main_thread_koid, threads, dispatchers))) => {
40                        Ok(ProcessInfo {
41                            job_koid: zx::Koid::from_raw(job_koid),
42                            process_koid: zx::Koid::from_raw(process_koid),
43                            main_thread_koid: zx::Koid::from_raw(main_thread_koid),
44                            threads,
45                            dispatchers,
46                        })
47                    }
48                    Ok(Err(e)) => Err(zx::Status::from_raw(e)),
49                    Err(e) => {
50                        log::error!("FIDL error GetProcessInfo: {:?}", e);
51                        Err(zx::Status::INTERNAL)
52                    }
53                }
54            })
55            .await
56    }
57}
58
59/// An implementation of `vfs::File` that reads its contents from the driver host's process info.
60struct ElfFile {
61    process_info: Arc<CachedProcessInfo>,
62    info_extractor: fn(&ProcessInfo) -> String,
63}
64
65impl DirectoryEntry for ElfFile {
66    fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
67        request.open_file(self)
68    }
69}
70
71impl GetEntryInfo for ElfFile {
72    fn entry_info(&self) -> EntryInfo {
73        EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)
74    }
75}
76
77impl Node for ElfFile {
78    async fn get_attributes(
79        &self,
80        requested_attributes: fio::NodeAttributesQuery,
81    ) -> Result<fio::NodeAttributes2, Status> {
82        let info = self.process_info.get().await?;
83        let content = (self.info_extractor)(info);
84        let content_size = content.len() as u64;
85        Ok(immutable_attributes!(
86            requested_attributes,
87            Immutable {
88                protocols: fio::NodeProtocolKinds::FILE,
89                abilities: fio::Operations::GET_ATTRIBUTES | fio::Operations::READ_BYTES,
90                content_size: content_size,
91                storage_size: content_size,
92            }
93        ))
94    }
95}
96
97impl FileIo for ElfFile {
98    async fn read_at(&self, offset: u64, buffer: &mut [u8]) -> Result<u64, Status> {
99        let info = self.process_info.get().await?;
100        let content = (self.info_extractor)(info);
101        let bytes = content.as_bytes();
102        let content_size = bytes.len() as u64;
103
104        if offset >= content_size {
105            return Ok(0u64);
106        }
107
108        let start = offset as usize;
109        let read_len = std::cmp::min(bytes.len() - start, buffer.len());
110        buffer[..read_len].copy_from_slice(&bytes[start..][..read_len]);
111        Ok(read_len as u64)
112    }
113
114    async fn write_at(&self, _offset: u64, _content: &[u8]) -> Result<u64, Status> {
115        Err(Status::NOT_SUPPORTED)
116    }
117
118    async fn append(&self, _content: &[u8]) -> Result<(u64, u64), Status> {
119        Err(Status::NOT_SUPPORTED)
120    }
121}
122
123impl File for ElfFile {
124    fn readable(&self) -> bool {
125        true
126    }
127
128    fn writable(&self) -> bool {
129        false
130    }
131
132    fn executable(&self) -> bool {
133        false
134    }
135
136    async fn open_file(&self, _options: &FileOptions) -> Result<(), Status> {
137        Ok(())
138    }
139
140    async fn truncate(&self, _length: u64) -> Result<(), Status> {
141        Err(Status::NOT_SUPPORTED)
142    }
143
144    async fn get_size(&self) -> Result<u64, Status> {
145        let info = self.process_info.get().await?;
146        let content = (self.info_extractor)(info);
147        Ok(content.len() as u64)
148    }
149
150    async fn update_attributes(
151        &self,
152        _attributes: fio::MutableNodeAttributes,
153    ) -> Result<(), Status> {
154        Err(Status::NOT_SUPPORTED)
155    }
156
157    async fn sync(&self, _mode: SyncMode) -> Result<(), Status> {
158        Ok(())
159    }
160}
161
162impl FileLike for ElfFile {
163    fn open(
164        self: Arc<Self>,
165        scope: ExecutionScope,
166        options: FileOptions,
167        object_request: ObjectRequestRef<'_>,
168    ) -> Result<(), Status> {
169        FidlIoConnection::create_sync(scope, self, options, object_request.take());
170        Ok(())
171    }
172}
173
174/// Creates the runtime directory that is served to the driver host.
175/// This directory contains information about the driver host process that can be used by debugging
176/// tools like zxdb.
177pub(crate) fn create_runtime_dir(process_info: Arc<CachedProcessInfo>) -> Arc<Simple> {
178    let now = zx::MonotonicInstant::get().into_nanos().to_string();
179    let process_start_time = read_only(now.into_bytes());
180
181    let job_id = Arc::new(ElfFile {
182        process_info: process_info.clone(),
183        info_extractor: |info| info.job_koid.raw_koid().to_string(),
184    });
185
186    let process_id = Arc::new(ElfFile {
187        process_info,
188        info_extractor: |info| info.process_koid.raw_koid().to_string(),
189    });
190
191    pseudo_directory! {
192        "elf" => pseudo_directory! {
193            "process_start_time" => process_start_time,
194            "job_id" => job_id,
195            "process_id" => process_id,
196        }
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    use fidl::endpoints::{create_proxy, create_proxy_and_stream};
204    use futures::stream::StreamExt;
205
206    #[fuchsia::test]
207    async fn test_runtime_dir_creation() {
208        let (proxy, mut stream) = create_proxy_and_stream::<fdh::DriverHostMarker>();
209        let process_info = Arc::new(CachedProcessInfo::new(proxy));
210
211        // Mock the get_process_info call
212        fuchsia_async::Task::spawn(async move {
213            if let Some(Ok(fdh::DriverHostRequest::GetProcessInfo { responder })) =
214                stream.next().await
215            {
216                responder.send(Ok((123, 456, 789, &[], &[]))).unwrap();
217            }
218        })
219        .detach();
220
221        let dir = create_runtime_dir(process_info);
222
223        let scope = ExecutionScope::new();
224        let (root_proxy, root_server) = create_proxy::<fio::DirectoryMarker>();
225        vfs::directory::serve_on(dir, fio::Flags::PERM_READ_BYTES, scope, root_server);
226
227        let entries = fuchsia_fs::directory::readdir(&root_proxy).await.unwrap();
228        // Check for "elf" directory
229        assert!(entries.iter().any(|e| e.name == "elf"));
230
231        let elf_proxy =
232            fuchsia_fs::directory::open_directory(&root_proxy, "elf", fio::Flags::PERM_READ_BYTES)
233                .await
234                .unwrap();
235        let elf_entries = fuchsia_fs::directory::readdir(&elf_proxy).await.unwrap();
236        assert!(elf_entries.iter().any(|e| e.name == "process_start_time"));
237        assert!(elf_entries.iter().any(|e| e.name == "job_id"));
238        assert!(elf_entries.iter().any(|e| e.name == "process_id"));
239
240        let job_id_file =
241            fuchsia_fs::directory::open_file(&elf_proxy, "job_id", fio::Flags::PERM_READ_BYTES)
242                .await
243                .unwrap();
244        let job_id = fuchsia_fs::file::read_to_string(&job_id_file).await.unwrap();
245        assert_eq!(job_id, "123");
246
247        let process_id_file =
248            fuchsia_fs::directory::open_file(&elf_proxy, "process_id", fio::Flags::PERM_READ_BYTES)
249                .await
250                .unwrap();
251        let process_id = fuchsia_fs::file::read_to_string(&process_id_file).await.unwrap();
252        assert_eq!(process_id, "456");
253    }
254}