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