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 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
157 pub(crate) fn kind_of(info: &fio::NodeInfoDeprecated) -> Kind {
158 match info {
159 fio::NodeInfoDeprecated::Service(_) => Kind::Service,
160 fio::NodeInfoDeprecated::File(_) => Kind::File,
161 fio::NodeInfoDeprecated::Directory(_) => Kind::Directory,
162 fio::NodeInfoDeprecated::Symlink(_) => Kind::Symlink,
163 }
164 }
165
166 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
167 fn expect_file(info: fio::NodeInfoDeprecated) -> Result<(), Kind> {
168 match info {
169 fio::NodeInfoDeprecated::File(fio::FileObject { .. }) => Ok(()),
170 other => Err(Kind::kind_of(&other)),
171 }
172 }
173
174 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
175 fn expect_directory(info: fio::NodeInfoDeprecated) -> Result<(), Kind> {
176 match info {
177 fio::NodeInfoDeprecated::Directory(fio::DirectoryObject) => Ok(()),
178 other => Err(Kind::kind_of(&other)),
179 }
180 }
181
182 pub(crate) fn kind_of2(representation: &fio::Representation) -> Kind {
183 match representation {
184 fio::Representation::Directory(_) => Kind::Directory,
185 fio::Representation::File(_) => Kind::File,
186 fio::Representation::Symlink(_) => Kind::Symlink,
187 _ => Kind::Unknown,
188 }
189 }
190
191 fn expect_file2(representation: &fio::Representation) -> Result<(), Kind> {
192 match representation {
193 fio::Representation::File(fio::FileInfo { .. }) => Ok(()),
194 other => Err(Kind::kind_of2(other)),
195 }
196 }
197
198 fn expect_directory2(representation: &fio::Representation) -> Result<(), Kind> {
199 match representation {
200 fio::Representation::Directory(_) => Ok(()),
201 other => Err(Kind::kind_of2(other)),
202 }
203 }
204}
205
206pub async fn close(node: fio::NodeProxy) -> Result<(), CloseError> {
208 let result = node.close().await.map_err(CloseError::SendCloseRequest)?;
209 result.map_err(|s| CloseError::CloseError(zx_status::Status::from_raw(s)))
210}
211
212pub(crate) async fn verify_node_describe_event(
215 node: fio::NodeProxy,
216) -> Result<fio::NodeProxy, OpenError> {
217 match take_on_open_event(&node).await? {
218 #[cfg(any(
219 fuchsia_api_level_at_least = "PLATFORM",
220 not(fuchsia_api_level_at_least = "NEXT")
221 ))]
222 fio::NodeEvent::OnOpen_ { s: status, info } => {
223 let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
224 info.ok_or(OpenError::MissingOnOpenInfo)?;
225 }
226 fio::NodeEvent::OnRepresentation { .. } => {}
227 fio::NodeEvent::_UnknownEvent { ordinal, .. } => {
228 return Err(OpenError::UnknownEvent { ordinal });
229 }
230 }
231
232 Ok(node)
233}
234
235pub(crate) async fn verify_directory_describe_event(
238 node: fio::DirectoryProxy,
239) -> Result<fio::DirectoryProxy, OpenError> {
240 match take_on_open_event(&node).await? {
241 #[cfg(any(
242 fuchsia_api_level_at_least = "PLATFORM",
243 not(fuchsia_api_level_at_least = "NEXT")
244 ))]
245 fio::DirectoryEvent::OnOpen_ { s: status, info } => {
246 let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
247 let info = info.ok_or(OpenError::MissingOnOpenInfo)?;
248 let () = Kind::expect_directory(*info).map_err(|actual| {
249 OpenError::UnexpectedNodeKind { expected: Kind::Directory, actual }
250 })?;
251 }
252 fio::DirectoryEvent::OnRepresentation { payload } => {
253 let () = Kind::expect_directory2(&payload).map_err(|actual| {
254 OpenError::UnexpectedNodeKind { expected: Kind::Directory, actual }
255 })?;
256 }
257 fio::DirectoryEvent::_UnknownEvent { ordinal, .. } => {
258 return Err(OpenError::UnknownEvent { ordinal });
259 }
260 }
261
262 Ok(node)
263}
264
265pub(crate) async fn verify_file_describe_event(
268 node: fio::FileProxy,
269) -> Result<fio::FileProxy, OpenError> {
270 match take_on_open_event(&node).await? {
271 #[cfg(any(
272 fuchsia_api_level_at_least = "PLATFORM",
273 not(fuchsia_api_level_at_least = "NEXT")
274 ))]
275 fio::FileEvent::OnOpen_ { s: status, info } => {
276 let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
277 let info = info.ok_or(OpenError::MissingOnOpenInfo)?;
278 let () = Kind::expect_file(*info)
279 .map_err(|actual| OpenError::UnexpectedNodeKind { expected: Kind::File, actual })?;
280 }
281 fio::FileEvent::OnRepresentation { payload } => {
282 let () = Kind::expect_file2(&payload)
283 .map_err(|actual| OpenError::UnexpectedNodeKind { expected: Kind::File, actual })?;
284 }
285 fio::FileEvent::_UnknownEvent { ordinal, .. } => {
286 return Err(OpenError::UnknownEvent { ordinal });
287 }
288 }
289
290 Ok(node)
291}
292
293pub(crate) trait OnOpenEventProducer {
294 type Event;
295 type Stream: futures::Stream<Item = Result<Self::Event, fidl::Error>> + Unpin;
296 fn take_event_stream(&self) -> Self::Stream;
297}
298
299macro_rules! impl_on_open_event_producer {
300 ($proxy:ty, $event:ty, $stream:ty) => {
301 impl OnOpenEventProducer for $proxy {
302 type Event = $event;
303 type Stream = $stream;
304 fn take_event_stream(&self) -> Self::Stream {
305 self.take_event_stream()
306 }
307 }
308 };
309}
310
311impl_on_open_event_producer!(fio::NodeProxy, fio::NodeEvent, fio::NodeEventStream);
312impl_on_open_event_producer!(fio::FileProxy, fio::FileEvent, fio::FileEventStream);
313impl_on_open_event_producer!(fio::DirectoryProxy, fio::DirectoryEvent, fio::DirectoryEventStream);
314
315pub(crate) async fn take_on_open_event<T>(node: &T) -> Result<T::Event, OpenError>
316where
317 T: OnOpenEventProducer,
318{
319 node.take_event_stream().next().await.ok_or(OpenError::OnOpenEventStreamClosed)?.map_err(|e| {
320 if let fidl::Error::ClientChannelClosed { status, .. } = e {
321 OpenError::OpenError(status)
322 } else {
323 OpenError::OnOpenDecode(e)
324 }
325 })
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use assert_matches::assert_matches;
332 use fidl::endpoints::DiscoverableProtocolMarker as _;
333 use fuchsia_async as fasync;
334
335 #[fasync::run_singlethreaded(test)]
338 async fn open_in_namespace_opens_real_node() {
339 let file_node = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
340 let protocol = file_node.query().await.unwrap();
341 assert_eq!(protocol, fio::FileMarker::PROTOCOL_NAME.as_bytes());
342
343 let dir_node = open_in_namespace("/pkg/data", fio::PERM_READABLE).unwrap();
344 let protocol = dir_node.query().await.unwrap();
345 assert_eq!(protocol, fio::DirectoryMarker::PROTOCOL_NAME.as_bytes());
346 }
347
348 #[fasync::run_singlethreaded(test)]
349 async fn open_in_namespace_opens_fake_node_under_of_root_namespace_entry() {
350 let notfound = open_in_namespace("/pkg/fake", fio::PERM_READABLE).unwrap();
351 assert_matches!(close(notfound).await, Err(_));
353 }
354
355 #[fasync::run_singlethreaded(test)]
356 async fn open_in_namespace_rejects_fake_root_namespace_entry() {
357 assert_matches!(
358 open_in_namespace("/fake", fio::PERM_READABLE),
359 Err(OpenError::Namespace(zx_status::Status::NOT_FOUND))
360 );
361 }
362}