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