Skip to main content

fuchsia_component_directory/
lib.rs

1// Copyright 2025 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
5//! Crate for directory operations.
6//
7// TODO(https://fxbug.dev/42083023): These operations can be merged into `fuchsia-fs` if Rust FIDL bindings
8// support making one-way calls on a client endpoint without turning it into a proxy.
9
10#![deny(missing_docs)]
11
12use anyhow::{Context, Error};
13use fidl::endpoints::ClientEnd;
14use fidl_fuchsia_io as fio;
15use std::sync::Arc;
16
17/// A trait for opening filesystem nodes.
18pub trait Directory {
19    /// Open a node relative to the directory.
20    fn open(&self, path: &str, flags: fio::Flags, server_end: zx::Channel) -> Result<(), Error>;
21}
22
23impl Directory for fio::DirectoryProxy {
24    fn open(&self, path: &str, flags: fio::Flags, server_end: zx::Channel) -> Result<(), Error> {
25        #[cfg(fuchsia_api_level_at_least = "27")]
26        let () = self.open(path, flags, &fio::Options::default(), server_end.into())?;
27        #[cfg(not(fuchsia_api_level_at_least = "27"))]
28        let () = self.open3(path, flags, &fio::Options::default(), server_end.into())?;
29        Ok(())
30    }
31}
32
33impl Directory for fio::DirectorySynchronousProxy {
34    fn open(&self, path: &str, flags: fio::Flags, server_end: zx::Channel) -> Result<(), Error> {
35        #[cfg(fuchsia_api_level_at_least = "27")]
36        let () = self.open(path, flags, &fio::Options::default(), server_end.into())?;
37        #[cfg(not(fuchsia_api_level_at_least = "27"))]
38        let () = self.open3(path, flags, &fio::Options::default(), server_end.into())?;
39        Ok(())
40    }
41}
42
43impl Directory for ClientEnd<fio::DirectoryMarker> {
44    fn open(&self, path: &str, flags: fio::Flags, server_end: zx::Channel) -> Result<(), Error> {
45        let raw_handle = self.channel().as_handle_ref().raw_handle();
46        // Safety: we call forget on objects that reference `raw_handle` leaving it usable.
47        unsafe {
48            let borrowed: zx::Channel = zx::NullableHandle::from_raw(raw_handle).into();
49            let proxy = fio::DirectorySynchronousProxy::new(borrowed);
50            #[cfg(fuchsia_api_level_at_least = "27")]
51            let res = proxy.open(path, flags, &fio::Options::default(), server_end.into());
52            #[cfg(not(fuchsia_api_level_at_least = "27"))]
53            let res = proxy.open3(path, flags, &fio::Options::default(), server_end.into());
54            std::mem::forget(proxy.into_channel());
55            res?;
56        }
57        Ok(())
58    }
59}
60
61/// A trait for types that can vend out a [`Directory`] reference.
62///
63/// A new trait is needed because both `DirectoryProxy` and `AsRef` are external types.
64/// As a result, implementing `AsRef<&dyn Directory>` for `DirectoryProxy` is not allowed
65/// under coherence rules.
66pub trait AsRefDirectory {
67    /// Get a [`Directory`] reference.
68    fn as_ref_directory(&self) -> &dyn Directory;
69}
70
71impl AsRefDirectory for fio::DirectoryProxy {
72    fn as_ref_directory(&self) -> &dyn Directory {
73        self
74    }
75}
76
77impl AsRefDirectory for fio::DirectorySynchronousProxy {
78    fn as_ref_directory(&self) -> &dyn Directory {
79        self
80    }
81}
82
83impl AsRefDirectory for ClientEnd<fio::DirectoryMarker> {
84    fn as_ref_directory(&self) -> &dyn Directory {
85        self
86    }
87}
88
89impl<T: Directory> AsRefDirectory for Box<T> {
90    fn as_ref_directory(&self) -> &dyn Directory {
91        &**self
92    }
93}
94
95impl<T: Directory> AsRefDirectory for Arc<T> {
96    fn as_ref_directory(&self) -> &dyn Directory {
97        &**self
98    }
99}
100
101impl<T: Directory> AsRefDirectory for &T {
102    fn as_ref_directory(&self) -> &dyn Directory {
103        *self
104    }
105}
106
107/// Opens the given `path` from the given `parent` directory as a [`DirectoryProxy`] asynchronously.
108pub fn open_directory_async(
109    parent: &impl AsRefDirectory,
110    path: &str,
111    rights: fio::Rights,
112) -> Result<fio::DirectoryProxy, Error> {
113    let (dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
114
115    let flags = fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::from_bits_truncate(rights.bits());
116    let () = parent
117        .as_ref_directory()
118        .open(path, flags, server_end.into_channel())
119        .context("opening directory without describe")?;
120
121    Ok(dir)
122}
123
124/// Opens the given `path` from the given `parent` directory as a [`FileProxy`] asynchronously.
125pub fn open_file_async(
126    parent: &impl AsRefDirectory,
127    path: &str,
128    rights: fio::Rights,
129) -> Result<fio::FileProxy, Error> {
130    let (file, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
131
132    let flags = fio::Flags::PROTOCOL_FILE | fio::Flags::from_bits_truncate(rights.bits());
133    let () = parent
134        .as_ref_directory()
135        .open(path, flags, server_end.into_channel())
136        .context("opening file without describe")?;
137
138    Ok(file)
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use assert_matches::assert_matches;
145    use fuchsia_async as fasync;
146    use vfs::directory::immutable::simple;
147    use vfs::execution_scope::ExecutionScope;
148    use vfs::file::vmo::read_only;
149    use vfs::pseudo_directory;
150
151    #[fasync::run_singlethreaded(test)]
152    async fn open_directory_async_real() {
153        let dir = pseudo_directory! {
154            "dir" => simple(),
155        };
156        let dir = vfs::directory::serve_read_only(dir, ExecutionScope::new());
157        let dir = open_directory_async(&dir, "dir", fio::Rights::empty()).unwrap();
158        fuchsia_fs::directory::close(dir).await.unwrap();
159    }
160
161    #[fasync::run_singlethreaded(test)]
162    async fn open_directory_async_fake() {
163        let dir = pseudo_directory! {
164            "dir" => simple(),
165        };
166        let dir = vfs::directory::serve_read_only(dir, ExecutionScope::new());
167        let dir = open_directory_async(&dir, "fake", fio::Rights::empty()).unwrap();
168        // The open error is not detected until the proxy is interacted with.
169        assert_matches!(fuchsia_fs::directory::close(dir).await, Err(_));
170    }
171
172    #[fasync::run_singlethreaded(test)]
173    async fn open_file_async_real() {
174        let dir = pseudo_directory! {
175            "file" => read_only("read_only"),
176        };
177        let dir = vfs::directory::serve_read_only(dir, ExecutionScope::new());
178        let file = open_file_async(&dir, "file", fio::Rights::READ_BYTES).unwrap();
179        fuchsia_fs::file::close(file).await.unwrap();
180    }
181
182    #[fasync::run_singlethreaded(test)]
183    async fn open_file_async_fake() {
184        let dir = pseudo_directory! {
185            "file" => read_only("read_only"),
186        };
187        let dir = vfs::directory::serve_read_only(dir, ExecutionScope::new());
188        let fake = open_file_async(&dir, "fake", fio::Rights::READ_BYTES).unwrap();
189        // The open error is not detected until the proxy is interacted with.
190        assert_matches!(fuchsia_fs::file::close(fake).await, Err(_));
191    }
192}