1use flex_fuchsia_io as fio;
8use futures::prelude::*;
9use thiserror::Error;
10
11#[cfg(target_os = "fuchsia")]
12#[cfg(not(feature = "fdomain"))]
13pub use fuchsia::*;
14
15#[cfg(target_os = "fuchsia")]
16#[cfg(not(feature = "fdomain"))]
17mod fuchsia {
18 use super::*;
19
20 pub fn open_in_namespace(path: &str, flags: fio::Flags) -> Result<fio::NodeProxy, OpenError> {
31 let (node, request) = fidl::endpoints::create_proxy();
32 open_channel_in_namespace(path, flags, request)?;
33 Ok(node)
34 }
35
36 pub fn open_channel_in_namespace(
47 path: &str,
48 flags: fio::Flags,
49 request: fidl::endpoints::ServerEnd<fio::NodeMarker>,
50 ) -> Result<(), OpenError> {
51 let namespace = fdio::Namespace::installed().map_err(OpenError::Namespace)?;
52 namespace.open(path, flags, request.into_channel()).map_err(OpenError::Namespace)
53 }
54}
55
56#[derive(Debug, Clone, Error)]
58#[allow(missing_docs)]
59pub enum OpenError {
60 #[error("while making a fidl proxy: {0}")]
61 CreateProxy(#[source] flex_client::Error),
62
63 #[error("while opening from namespace: {0}")]
64 Namespace(#[source] zx_status::Status),
65
66 #[error("while sending open request: {0}")]
67 SendOpenRequest(#[source] fidl::Error),
68
69 #[error("node event stream closed prematurely")]
70 OnOpenEventStreamClosed,
71
72 #[error("while reading OnOpen event: {0}")]
73 OnOpenDecode(#[source] fidl::Error),
74
75 #[error("open failed with status: {0}")]
76 OpenError(#[source] zx_status::Status),
77
78 #[error("remote responded with success but provided no node info")]
79 MissingOnOpenInfo,
80
81 #[error("expected node to be a {expected:?}, but got a {actual:?}")]
82 UnexpectedNodeKind { expected: Kind, actual: Kind },
83
84 #[error("received unknown event (ordinal = {ordinal})")]
85 UnknownEvent { ordinal: u64 },
86}
87
88impl OpenError {
89 pub fn is_not_found_error(&self) -> bool {
91 matches!(
92 self,
93 OpenError::OpenError(zx_status::Status::NOT_FOUND)
94 | OpenError::Namespace(zx_status::Status::NOT_FOUND)
95 )
96 }
97}
98
99#[derive(Debug, Clone, Error)]
101#[allow(missing_docs)]
102pub enum CloneError {
103 #[error("while making a fidl proxy: {0}")]
104 CreateProxy(#[source] fidl::Error),
105
106 #[error("while sending clone request: {0}")]
107 SendCloneRequest(#[source] fidl::Error),
108}
109
110#[derive(Debug, Clone, Error)]
112#[allow(missing_docs)]
113pub enum CloseError {
114 #[error("while sending close request: {0}")]
115 SendCloseRequest(#[source] fidl::Error),
116
117 #[error("close failed with status: {0}")]
118 CloseError(#[source] zx_status::Status),
119}
120
121#[derive(Debug, Clone, Error)]
123#[allow(missing_docs)]
124pub enum RenameError {
125 #[error("while sending rename request")]
126 SendRenameRequest(#[source] fidl::Error),
127
128 #[error("while sending get_token request")]
129 SendGetTokenRequest(#[source] fidl::Error),
130
131 #[error("rename failed with status")]
132 RenameError(#[source] zx_status::Status),
133
134 #[error("while opening subdirectory")]
135 OpenError(#[from] OpenError),
136
137 #[error("get_token failed with status")]
138 GetTokenError(#[source] zx_status::Status),
139
140 #[error("no handle from get token")]
141 NoHandleError,
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
146#[allow(missing_docs)]
147pub enum Kind {
148 Service,
149 File,
150 Directory,
151 Symlink,
152 Unknown,
153}
154
155impl Kind {
156 pub(crate) fn kind_of(info: &fio::NodeInfoDeprecated) -> Kind {
157 match info {
158 fio::NodeInfoDeprecated::Service(_) => Kind::Service,
159 fio::NodeInfoDeprecated::File(_) => Kind::File,
160 fio::NodeInfoDeprecated::Directory(_) => Kind::Directory,
161 fio::NodeInfoDeprecated::Symlink(_) => Kind::Symlink,
162 }
163 }
164
165 fn expect_file(info: fio::NodeInfoDeprecated) -> Result<(), Kind> {
166 match info {
167 fio::NodeInfoDeprecated::File(fio::FileObject { .. }) => Ok(()),
168 other => Err(Kind::kind_of(&other)),
169 }
170 }
171
172 fn expect_directory(info: fio::NodeInfoDeprecated) -> Result<(), Kind> {
173 match info {
174 fio::NodeInfoDeprecated::Directory(fio::DirectoryObject) => Ok(()),
175 other => Err(Kind::kind_of(&other)),
176 }
177 }
178
179 pub(crate) fn kind_of2(representation: &fio::Representation) -> Kind {
180 match representation {
181 fio::Representation::Directory(_) => Kind::Directory,
182 fio::Representation::File(_) => Kind::File,
183 fio::Representation::Symlink(_) => Kind::Symlink,
184 _ => Kind::Unknown,
185 }
186 }
187
188 fn expect_file2(representation: &fio::Representation) -> Result<(), Kind> {
189 match representation {
190 fio::Representation::File(fio::FileInfo { .. }) => Ok(()),
191 other => Err(Kind::kind_of2(other)),
192 }
193 }
194
195 fn expect_directory2(representation: &fio::Representation) -> Result<(), Kind> {
196 match representation {
197 fio::Representation::Directory(_) => Ok(()),
198 other => Err(Kind::kind_of2(other)),
199 }
200 }
201}
202
203pub async fn close(node: fio::NodeProxy) -> Result<(), CloseError> {
205 let result = node.close().await.map_err(CloseError::SendCloseRequest)?;
206 result.map_err(|s| CloseError::CloseError(zx_status::Status::from_raw(s)))
207}
208
209pub(crate) async fn verify_node_describe_event(
212 node: fio::NodeProxy,
213) -> Result<fio::NodeProxy, OpenError> {
214 match take_on_open_event(&node).await? {
215 fio::NodeEvent::OnOpen_ { s: status, info } => {
216 let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
217 info.ok_or(OpenError::MissingOnOpenInfo)?;
218 }
219 fio::NodeEvent::OnRepresentation { .. } => {}
220 fio::NodeEvent::_UnknownEvent { ordinal, .. } => {
221 return Err(OpenError::UnknownEvent { ordinal })
222 }
223 }
224
225 Ok(node)
226}
227
228pub(crate) async fn verify_directory_describe_event(
231 node: fio::DirectoryProxy,
232) -> Result<fio::DirectoryProxy, OpenError> {
233 match take_on_open_event(&node).await? {
234 fio::DirectoryEvent::OnOpen_ { s: status, info } => {
235 let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
236 let info = info.ok_or(OpenError::MissingOnOpenInfo)?;
237 let () = Kind::expect_directory(*info).map_err(|actual| {
238 OpenError::UnexpectedNodeKind { expected: Kind::Directory, actual }
239 })?;
240 }
241 fio::DirectoryEvent::OnRepresentation { payload } => {
242 let () = Kind::expect_directory2(&payload).map_err(|actual| {
243 OpenError::UnexpectedNodeKind { expected: Kind::Directory, actual }
244 })?;
245 }
246 fio::DirectoryEvent::_UnknownEvent { ordinal, .. } => {
247 return Err(OpenError::UnknownEvent { ordinal })
248 }
249 }
250
251 Ok(node)
252}
253
254pub(crate) async fn verify_file_describe_event(
257 node: fio::FileProxy,
258) -> Result<fio::FileProxy, OpenError> {
259 match take_on_open_event(&node).await? {
260 fio::FileEvent::OnOpen_ { s: status, info } => {
261 let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
262 let info = info.ok_or(OpenError::MissingOnOpenInfo)?;
263 let () = Kind::expect_file(*info)
264 .map_err(|actual| OpenError::UnexpectedNodeKind { expected: Kind::File, actual })?;
265 }
266 fio::FileEvent::OnRepresentation { payload } => {
267 let () = Kind::expect_file2(&payload)
268 .map_err(|actual| OpenError::UnexpectedNodeKind { expected: Kind::File, actual })?;
269 }
270 fio::FileEvent::_UnknownEvent { ordinal, .. } => {
271 return Err(OpenError::UnknownEvent { ordinal })
272 }
273 }
274
275 Ok(node)
276}
277
278pub(crate) trait OnOpenEventProducer {
279 type Event;
280 type Stream: futures::Stream<Item = Result<Self::Event, fidl::Error>> + Unpin;
281 fn take_event_stream(&self) -> Self::Stream;
282}
283
284macro_rules! impl_on_open_event_producer {
285 ($proxy:ty, $event:ty, $stream:ty) => {
286 impl OnOpenEventProducer for $proxy {
287 type Event = $event;
288 type Stream = $stream;
289 fn take_event_stream(&self) -> Self::Stream {
290 self.take_event_stream()
291 }
292 }
293 };
294}
295
296impl_on_open_event_producer!(fio::NodeProxy, fio::NodeEvent, fio::NodeEventStream);
297impl_on_open_event_producer!(fio::FileProxy, fio::FileEvent, fio::FileEventStream);
298impl_on_open_event_producer!(fio::DirectoryProxy, fio::DirectoryEvent, fio::DirectoryEventStream);
299
300pub(crate) async fn take_on_open_event<T>(node: &T) -> Result<T::Event, OpenError>
301where
302 T: OnOpenEventProducer,
303{
304 node.take_event_stream().next().await.ok_or(OpenError::OnOpenEventStreamClosed)?.map_err(|e| {
305 if let fidl::Error::ClientChannelClosed { status, .. } = e {
306 OpenError::OpenError(status)
307 } else {
308 OpenError::OnOpenDecode(e)
309 }
310 })
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use assert_matches::assert_matches;
317 use fuchsia_async as fasync;
318
319 #[fasync::run_singlethreaded(test)]
322 async fn open_in_namespace_opens_real_node() {
323 let file_node = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
324 let protocol = file_node.query().await.unwrap();
325 assert_eq!(protocol, fio::FILE_PROTOCOL_NAME.as_bytes());
326
327 let dir_node = open_in_namespace("/pkg/data", fio::PERM_READABLE).unwrap();
328 let protocol = dir_node.query().await.unwrap();
329 assert_eq!(protocol, fio::DIRECTORY_PROTOCOL_NAME.as_bytes());
330 }
331
332 #[fasync::run_singlethreaded(test)]
333 async fn open_in_namespace_opens_fake_node_under_of_root_namespace_entry() {
334 let notfound = open_in_namespace("/pkg/fake", fio::PERM_READABLE).unwrap();
335 assert_matches!(close(notfound).await, Err(_));
337 }
338
339 #[fasync::run_singlethreaded(test)]
340 async fn open_in_namespace_rejects_fake_root_namespace_entry() {
341 assert_matches!(
342 open_in_namespace("/fake", fio::PERM_READABLE),
343 Err(OpenError::Namespace(zx_status::Status::NOT_FOUND))
344 );
345 }
346}