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            proxy.open(path, flags, &fio::Options::default(), server_end.into())?;
52            #[cfg(not(fuchsia_api_level_at_least = "27"))]
53            proxy.open3(path, flags, &fio::Options::default(), server_end.into())?;
54            std::mem::forget(proxy.into_channel());
55        }
56        Ok(())
57    }
58}
59
60/// A trait for types that can vend out a [`Directory`] reference.
61///
62/// A new trait is needed because both `DirectoryProxy` and `AsRef` are external types.
63/// As a result, implementing `AsRef<&dyn Directory>` for `DirectoryProxy` is not allowed
64/// under coherence rules.
65pub trait AsRefDirectory {
66    /// Get a [`Directory`] reference.
67    fn as_ref_directory(&self) -> &dyn Directory;
68}
69
70impl AsRefDirectory for fio::DirectoryProxy {
71    fn as_ref_directory(&self) -> &dyn Directory {
72        self
73    }
74}
75
76impl AsRefDirectory for fio::DirectorySynchronousProxy {
77    fn as_ref_directory(&self) -> &dyn Directory {
78        self
79    }
80}
81
82impl AsRefDirectory for ClientEnd<fio::DirectoryMarker> {
83    fn as_ref_directory(&self) -> &dyn Directory {
84        self
85    }
86}
87
88impl<T: Directory> AsRefDirectory for Box<T> {
89    fn as_ref_directory(&self) -> &dyn Directory {
90        &**self
91    }
92}
93
94impl<T: Directory> AsRefDirectory for Arc<T> {
95    fn as_ref_directory(&self) -> &dyn Directory {
96        &**self
97    }
98}
99
100impl<T: Directory> AsRefDirectory for &T {
101    fn as_ref_directory(&self) -> &dyn Directory {
102        *self
103    }
104}
105
106/// Opens the given `path` from the given `parent` directory as a [`DirectoryProxy`] asynchronously.
107pub fn open_directory_async(
108    parent: &impl AsRefDirectory,
109    path: &str,
110    rights: fio::Rights,
111) -> Result<fio::DirectoryProxy, Error> {
112    let (dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
113
114    let flags = fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::from_bits_truncate(rights.bits());
115    let () = parent
116        .as_ref_directory()
117        .open(path, flags, server_end.into_channel())
118        .context("opening directory without describe")?;
119
120    Ok(dir)
121}
122
123/// Opens the given `path` from the given `parent` directory as a [`FileProxy`] asynchronously.
124pub fn open_file_async(
125    parent: &impl AsRefDirectory,
126    path: &str,
127    rights: fio::Rights,
128) -> Result<fio::FileProxy, Error> {
129    let (file, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
130
131    let flags = fio::Flags::PROTOCOL_FILE | fio::Flags::from_bits_truncate(rights.bits());
132    let () = parent
133        .as_ref_directory()
134        .open(path, flags, server_end.into_channel())
135        .context("opening file without describe")?;
136
137    Ok(file)
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use assert_matches::assert_matches;
144    use fuchsia_async as fasync;
145    use vfs::directory::immutable::simple;
146    use vfs::file::vmo::read_only;
147    use vfs::pseudo_directory;
148
149    #[fasync::run_singlethreaded(test)]
150    async fn open_directory_async_real() {
151        let dir = pseudo_directory! {
152            "dir" => simple(),
153        };
154        let dir = vfs::directory::serve_read_only(dir);
155        let dir = open_directory_async(&dir, "dir", fio::Rights::empty()).unwrap();
156        fuchsia_fs::directory::close(dir).await.unwrap();
157    }
158
159    #[fasync::run_singlethreaded(test)]
160    async fn open_directory_async_fake() {
161        let dir = pseudo_directory! {
162            "dir" => simple(),
163        };
164        let dir = vfs::directory::serve_read_only(dir);
165        let dir = open_directory_async(&dir, "fake", fio::Rights::empty()).unwrap();
166        // The open error is not detected until the proxy is interacted with.
167        assert_matches!(fuchsia_fs::directory::close(dir).await, Err(_));
168    }
169
170    #[fasync::run_singlethreaded(test)]
171    async fn open_file_async_real() {
172        let dir = pseudo_directory! {
173            "file" => read_only("read_only"),
174        };
175        let dir = vfs::directory::serve_read_only(dir);
176        let file = open_file_async(&dir, "file", fio::Rights::READ_BYTES).unwrap();
177        fuchsia_fs::file::close(file).await.unwrap();
178    }
179
180    #[fasync::run_singlethreaded(test)]
181    async fn open_file_async_fake() {
182        let dir = pseudo_directory! {
183            "file" => read_only("read_only"),
184        };
185        let dir = vfs::directory::serve_read_only(dir);
186        let fake = open_file_async(&dir, "fake", fio::Rights::READ_BYTES).unwrap();
187        // The open error is not detected until the proxy is interacted with.
188        assert_matches!(fuchsia_fs::file::close(fake).await, Err(_));
189    }
190}