component_debug/
dirs.rs

1// Copyright 2023 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//! Convenience functions for accessing directories of a component instance
6//! and opening protocols that exist in them.
7
8use fidl::endpoints::{create_proxy, ProtocolMarker};
9use moniker::Moniker;
10use thiserror::Error;
11use {fidl_fuchsia_io as fio, fidl_fuchsia_sys2 as fsys};
12
13/// Errors that can be returned from opening a component instance directory.
14#[derive(Debug, Error)]
15pub enum OpenError {
16    #[error("instance {0} could not be found")]
17    InstanceNotFound(Moniker),
18    #[error("opening {0} requires {1} to be resolved")]
19    InstanceNotResolved(OpenDirType, Moniker),
20    #[error("opening {0} requires {1} to be running")]
21    InstanceNotRunning(OpenDirType, Moniker),
22    #[error("component manager's open request on the directory returned a FIDL error")]
23    OpenFidlError,
24    #[error("{0} does not have a {1}")]
25    NoSuchDir(Moniker, OpenDirType),
26    #[error("component manager could not parse moniker: {0}")]
27    BadMoniker(Moniker),
28    #[error("component manager could not parse dir type: {0}")]
29    BadDirType(OpenDirType),
30    #[error("component manager could not parse path: {0}")]
31    BadPath(String),
32    #[error("component manager responded with an unknown error code")]
33    UnknownError,
34    #[error(transparent)]
35    Fidl(#[from] fidl::Error),
36}
37
38/// The directories of a component instance that can be opened.
39#[derive(Clone, Debug)]
40pub enum OpenDirType {
41    /// Served by the component's program. Rights unknown.
42    Outgoing,
43    /// Served by the component's runner. Rights unknown.
44    Runtime,
45    /// Served by the component's resolver. Rights unknown.
46    Package,
47    /// Served by component manager. Directory has RW rights.
48    Exposed,
49    /// Served by component manager. Directory has RW rights.
50    Namespace,
51}
52
53impl std::fmt::Display for OpenDirType {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            Self::Outgoing => write!(f, "outgoing directory"),
57            Self::Runtime => write!(f, "runtime directory"),
58            Self::Package => write!(f, "package directory"),
59            Self::Exposed => write!(f, "exposed directory"),
60            Self::Namespace => write!(f, "namespace directory"),
61        }
62    }
63}
64
65impl Into<fsys::OpenDirType> for OpenDirType {
66    fn into(self) -> fsys::OpenDirType {
67        match self {
68            Self::Outgoing => fsys::OpenDirType::OutgoingDir,
69            Self::Runtime => fsys::OpenDirType::RuntimeDir,
70            Self::Package => fsys::OpenDirType::PackageDir,
71            Self::Exposed => fsys::OpenDirType::ExposedDir,
72            Self::Namespace => fsys::OpenDirType::NamespaceDir,
73        }
74    }
75}
76
77impl From<fsys::OpenDirType> for OpenDirType {
78    fn from(fidl_type: fsys::OpenDirType) -> Self {
79        match fidl_type {
80            fsys::OpenDirType::OutgoingDir => Self::Outgoing,
81            fsys::OpenDirType::RuntimeDir => Self::Runtime,
82            fsys::OpenDirType::PackageDir => Self::Package,
83            fsys::OpenDirType::ExposedDir => Self::Exposed,
84            fsys::OpenDirType::NamespaceDir => Self::Namespace,
85            fsys::OpenDirTypeUnknown!() => panic!("This should not be constructed"),
86        }
87    }
88}
89
90/// Opens a protocol in a component instance directory, assuming it is located at the root.
91pub async fn connect_to_instance_protocol_at_dir_root<P: ProtocolMarker>(
92    moniker: &Moniker,
93    dir_type: OpenDirType,
94    realm: &fsys::RealmQueryProxy,
95) -> Result<P::Proxy, OpenError> {
96    connect_to_instance_protocol_at_path::<P>(moniker, dir_type, P::DEBUG_NAME, realm).await
97}
98
99/// Opens a protocol in a component instance directory at the given |path|.
100pub async fn connect_to_instance_protocol_at_path<P: ProtocolMarker>(
101    moniker: &Moniker,
102    dir_type: OpenDirType,
103    path: &str,
104    realm: &fsys::RealmQueryProxy,
105) -> Result<P::Proxy, OpenError> {
106    let (proxy, server_end) = create_proxy::<P>();
107    let server_end = server_end.into_channel();
108    open_in_instance_dir(
109        &moniker,
110        dir_type,
111        fio::OpenFlags::empty(),
112        fio::ModeType::empty(),
113        path,
114        server_end,
115        realm,
116    )
117    .await?;
118    Ok(proxy)
119}
120
121/// Opens the root of a component instance directory with read rights.
122pub async fn open_instance_dir_root_readable(
123    moniker: &Moniker,
124    dir_type: OpenDirType,
125    realm: &fsys::RealmQueryProxy,
126) -> Result<fio::DirectoryProxy, OpenError> {
127    open_instance_subdir_readable(moniker, dir_type, ".", realm).await
128}
129
130/// Opens the subdirectory of a component instance directory with read rights.
131pub async fn open_instance_subdir_readable(
132    moniker: &Moniker,
133    dir_type: OpenDirType,
134    path: &str,
135    realm: &fsys::RealmQueryProxy,
136) -> Result<fio::DirectoryProxy, OpenError> {
137    let (root_dir, server_end) = create_proxy::<fio::DirectoryMarker>();
138    let server_end = server_end.into_channel();
139    open_in_instance_dir(
140        moniker,
141        dir_type,
142        fio::OpenFlags::RIGHT_READABLE,
143        fio::ModeType::empty(),
144        path,
145        server_end,
146        realm,
147    )
148    .await?;
149    Ok(root_dir)
150}
151
152/// Opens an object in a component instance directory with the given |flags|, |mode| and |path|.
153/// Component manager will make the corresponding `fuchsia.io.Directory/Open` call on
154/// the directory.
155pub async fn open_in_instance_dir(
156    moniker: &Moniker,
157    dir_type: OpenDirType,
158    flags: fio::OpenFlags,
159    mode: fio::ModeType,
160    path: &str,
161    object: fidl::Channel,
162    realm: &fsys::RealmQueryProxy,
163) -> Result<(), OpenError> {
164    let moniker_str = moniker.to_string();
165    realm
166        .deprecated_open(&moniker_str, dir_type.clone().into(), flags, mode, path, object.into())
167        .await?
168        .map_err(|e| match e {
169            fsys::OpenError::InstanceNotFound => OpenError::InstanceNotFound(moniker.clone()),
170            fsys::OpenError::InstanceNotResolved => {
171                OpenError::InstanceNotResolved(dir_type, moniker.clone())
172            }
173            fsys::OpenError::InstanceNotRunning => {
174                OpenError::InstanceNotRunning(dir_type, moniker.clone())
175            }
176            fsys::OpenError::NoSuchDir => OpenError::NoSuchDir(moniker.clone(), dir_type),
177            fsys::OpenError::BadDirType => OpenError::BadDirType(dir_type),
178            fsys::OpenError::BadPath => OpenError::BadPath(path.to_string()),
179            fsys::OpenError::BadMoniker => OpenError::BadMoniker(moniker.clone()),
180            fsys::OpenError::FidlError => OpenError::OpenFidlError,
181            _ => OpenError::UnknownError,
182        })
183}
184
185#[cfg(test)]
186mod tests {
187    use fidl::endpoints::spawn_stream_handler;
188    use moniker::Moniker;
189    use {fidl_fuchsia_io as fio, fidl_fuchsia_sys2 as fsys};
190
191    use super::{
192        connect_to_instance_protocol_at_path, open_instance_dir_root_readable,
193        open_instance_subdir_readable, OpenDirType,
194    };
195
196    #[fuchsia::test]
197    async fn test_connect_to_instance_protocol_at_path() {
198        // Ensure that connect_to_instance_protocol_at_path() passes the correct arguments to RealmQuery.
199        let realm = spawn_stream_handler(move |realm_request| async move {
200            match realm_request {
201                fsys::RealmQueryRequest::DeprecatedOpen {
202                    moniker,
203                    dir_type,
204                    flags,
205                    mode: _,
206                    path,
207                    object: _,
208                    responder,
209                } => {
210                    assert_eq!(moniker, "moniker");
211                    assert_eq!(dir_type, fsys::OpenDirType::NamespaceDir);
212                    assert_eq!(flags, fio::OpenFlags::empty());
213                    assert_eq!(path, "/path");
214                    responder.send(Ok(())).unwrap();
215                }
216                _ => unreachable!(),
217            };
218        });
219
220        connect_to_instance_protocol_at_path::<fsys::RealmQueryMarker>(
221            &Moniker::parse_str("moniker").unwrap(),
222            OpenDirType::Namespace,
223            "/path",
224            &realm,
225        )
226        .await
227        .unwrap();
228    }
229
230    #[fuchsia::test]
231    async fn test_open_instance_subdir_readable() {
232        // Ensure that open_instance_subdir_readable() passes the correct arguments to RealmQuery.
233        let realm = spawn_stream_handler(move |realm_request| async move {
234            match realm_request {
235                fsys::RealmQueryRequest::DeprecatedOpen {
236                    moniker,
237                    dir_type,
238                    flags,
239                    mode: _,
240                    path,
241                    object: _,
242                    responder,
243                } => {
244                    assert_eq!(moniker, "moniker");
245                    assert_eq!(dir_type, fsys::OpenDirType::NamespaceDir);
246                    assert_eq!(flags, fio::OpenFlags::RIGHT_READABLE);
247                    assert_eq!(path, ".");
248                    responder.send(Ok(())).unwrap();
249                }
250                _ => unreachable!(),
251            };
252        });
253
254        open_instance_subdir_readable(
255            &Moniker::parse_str("moniker").unwrap(),
256            OpenDirType::Namespace,
257            ".",
258            &realm,
259        )
260        .await
261        .unwrap();
262
263        open_instance_dir_root_readable(
264            &Moniker::parse_str("moniker").unwrap(),
265            OpenDirType::Namespace,
266            &realm,
267        )
268        .await
269        .unwrap();
270    }
271}