1#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
6use crate::common::send_on_open_with_error;
7use crate::common::{
8 decode_extended_attribute_value, encode_extended_attribute_value, extended_attributes_sender,
9};
10#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
11use crate::directory::common::check_child_connection_flags;
12use crate::directory::entry_container::{Directory, DirectoryWatcher};
13use crate::directory::traversal_position::TraversalPosition;
14use crate::directory::{DirectoryOptions, read_dirents};
15use crate::execution_scope::{ExecutionScope, yield_to_executor};
16use crate::node::OpenNode;
17use crate::object_request::Representation;
18use crate::path::Path;
19use flex_client::fidl::DiscoverableProtocolMarker as _;
20
21use anyhow::Error;
22use flex_client::fidl::ServerEnd;
23use flex_fuchsia_io as fio;
24use storage_trace::{self as trace, TraceFutureExt};
25use zx_status::Status;
26
27use crate::common::CreationMode;
28use crate::{ObjectRequest, ObjectRequestRef, ProtocolsExt};
29
30pub enum ConnectionState {
32 Alive,
34 Closed,
36}
37
38pub(in crate::directory) struct BaseConnection<DirectoryType: Directory> {
43 pub(in crate::directory) scope: ExecutionScope,
46
47 pub(in crate::directory) directory: OpenNode<DirectoryType>,
48
49 pub(in crate::directory) options: DirectoryOptions,
51
52 seek: TraversalPosition,
67}
68
69impl<DirectoryType: Directory> BaseConnection<DirectoryType> {
70 pub(in crate::directory) fn new(
74 scope: ExecutionScope,
75 directory: OpenNode<DirectoryType>,
76 options: DirectoryOptions,
77 ) -> Self {
78 BaseConnection { scope, directory, options, seek: Default::default() }
79 }
80
81 pub(in crate::directory) async fn handle_request(
84 &mut self,
85 request: fio::DirectoryRequest,
86 ) -> Result<ConnectionState, Error> {
87 match request {
88 #[cfg(any(
89 fuchsia_api_level_at_least = "PLATFORM",
90 not(fuchsia_api_level_at_least = "29")
91 ))]
92 fio::DirectoryRequest::DeprecatedClone { flags, object, control_handle: _ } => {
93 trace::duration!("storage", "Directory::DeprecatedClone");
94 crate::common::send_on_open_with_error(
95 flags.contains(fio::OpenFlags::DESCRIBE),
96 object,
97 Status::NOT_SUPPORTED,
98 );
99 }
100 fio::DirectoryRequest::Clone { request, control_handle: _ } => {
101 trace::duration!("storage", "Directory::Clone");
102 self.handle_clone(request.into_channel());
103 }
104 fio::DirectoryRequest::Close { responder } => {
105 trace::duration!("storage", "Directory::Close");
106 responder.send(Ok(()))?;
107 return Ok(ConnectionState::Closed);
108 }
109 #[cfg(fuchsia_api_level_at_least = "28")]
110 fio::DirectoryRequest::DeprecatedGetAttr { responder } => {
111 async move {
112 let (status, attrs) = crate::common::io2_to_io1_attrs(
113 self.directory.as_ref(),
114 self.options.rights,
115 )
116 .await;
117 responder.send(status, &attrs)
118 }
119 .trace(trace::trace_future_args!("storage", "Directory::GetAttr"))
120 .await?;
121 }
122 #[cfg(not(fuchsia_api_level_at_least = "28"))]
123 fio::DirectoryRequest::GetAttr { responder } => {
124 async move {
125 let (status, attrs) = crate::common::io2_to_io1_attrs(
126 self.directory.as_ref(),
127 self.options.rights,
128 )
129 .await;
130 responder.send(status, &attrs)
131 }
132 .trace(trace::trace_future_args!("storage", "Directory::GetAttr"))
133 .await?;
134 }
135 fio::DirectoryRequest::GetAttributes { query, responder } => {
136 async move {
137 match self.handle_get_attributes(query).await {
138 Ok(attrs) => responder
139 .send(Ok((&attrs.mutable_attributes, &attrs.immutable_attributes))),
140 Err(status) => responder.send(Err(status.into_raw())),
141 }
142 }
143 .trace(trace::trace_future_args!("storage", "Directory::GetAttributes"))
144 .await?;
145 }
146 fio::DirectoryRequest::UpdateAttributes { payload: _, responder } => {
147 trace::duration!("storage", "Directory::UpdateAttributes");
148 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
150 }
151 fio::DirectoryRequest::ListExtendedAttributes { iterator, control_handle: _ } => {
152 self.handle_list_extended_attribute(iterator)
153 .trace(trace::trace_future_args!(
154 "storage",
155 "Directory::ListExtendedAttributes"
156 ))
157 .await;
158 }
159 fio::DirectoryRequest::GetExtendedAttribute { name, responder } => {
160 async move {
161 let res =
162 self.handle_get_extended_attribute(name).await.map_err(Status::into_raw);
163 responder.send(res)
164 }
165 .trace(trace::trace_future_args!("storage", "Directory::GetExtendedAttribute"))
166 .await?;
167 }
168 fio::DirectoryRequest::SetExtendedAttribute { name, value, mode, responder } => {
169 async move {
170 let res = self
171 .handle_set_extended_attribute(name, value, mode)
172 .await
173 .map_err(Status::into_raw);
174 responder.send(res)
175 }
176 .trace(trace::trace_future_args!("storage", "Directory::SetExtendedAttribute"))
177 .await?;
178 }
179 fio::DirectoryRequest::RemoveExtendedAttribute { name, responder } => {
180 async move {
181 let res =
182 self.handle_remove_extended_attribute(name).await.map_err(Status::into_raw);
183 responder.send(res)
184 }
185 .trace(trace::trace_future_args!("storage", "Directory::RemoveExtendedAttribute"))
186 .await?;
187 }
188 fio::DirectoryRequest::GetFlags { responder } => {
189 trace::duration!("storage", "Directory::GetFlags");
190 responder.send(Ok(fio::Flags::from(&self.options)))?;
191 }
192 fio::DirectoryRequest::SetFlags { flags: _, responder } => {
193 trace::duration!("storage", "Directory::SetFlags");
194 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
195 }
196 fio::DirectoryRequest::DeprecatedGetFlags { responder } => {
197 trace::duration!("storage", "Directory::DeprecatedGetFlags");
198 responder.send(zx_status::sys::ZX_OK, self.options.to_io1())?;
199 }
200 fio::DirectoryRequest::DeprecatedSetFlags { flags: _, responder } => {
201 trace::duration!("storage", "Directory::DeprecatedSetFlags");
202 responder.send(Status::NOT_SUPPORTED.into_raw())?;
203 }
204 #[cfg(any(
205 fuchsia_api_level_at_least = "PLATFORM",
206 not(fuchsia_api_level_at_least = "32")
207 ))]
208 fio::DirectoryRequest::DeprecatedOpen {
209 flags,
210 mode: _,
211 path,
212 object,
213 control_handle: _,
214 } => {
215 {
216 trace::duration!("storage", "Directory::Open");
217 self.handle_deprecated_open(flags, path, object);
218 }
219 yield_to_executor().await;
222 }
223 fio::DirectoryRequest::AdvisoryLock { request: _, responder } => {
224 trace::duration!("storage", "Directory::AdvisoryLock");
225 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
226 }
227 fio::DirectoryRequest::ReadDirents { max_bytes, responder } => {
228 async move {
229 let (status, entries) = self.handle_read_dirents(max_bytes).await;
230 responder.send(Status::result_into_raw(status), entries.as_slice())
231 }
232 .trace(trace::trace_future_args!("storage", "Directory::ReadDirents"))
233 .await?;
234 }
235 fio::DirectoryRequest::Rewind { responder } => {
236 trace::duration!("storage", "Directory::Rewind");
237 let status = if !self.options.rights.intersects(fio::Operations::ENUMERATE) {
238 Status::ACCESS_DENIED.into_raw()
239 } else {
240 self.seek = Default::default();
241 zx_status::sys::ZX_OK
242 };
243 responder.send(status)?;
244 }
245 fio::DirectoryRequest::Link { src, dst_parent_token, dst, responder } => {
246 async move {
247 responder.send(Status::result_into_raw(
248 self.handle_link(&src, dst_parent_token, dst).await,
249 ))
250 }
251 .trace(trace::trace_future_args!("storage", "Directory::Link"))
252 .await?;
253 }
254 fio::DirectoryRequest::Watch { mask, options, watcher, responder } => {
255 trace::duration!("storage", "Directory::Watch");
256 let raw_status = if options != 0 {
257 Status::INVALID_ARGS.into_raw()
258 } else {
259 Status::result_into_raw(self.handle_watch(mask, watcher.into()))
260 };
261 responder.send(raw_status)?;
262 }
263 fio::DirectoryRequest::Query { responder } => {
264 trace::duration!("storage", "Directory::Query");
265 let () = responder.send(fio::DirectoryMarker::PROTOCOL_NAME.as_bytes())?;
266 }
267 fio::DirectoryRequest::QueryFilesystem { responder } => {
268 trace::duration!("storage", "Directory::QueryFilesystem");
269 match self.directory.query_filesystem() {
270 Err(status) => responder.send(status.into_raw(), None)?,
271 Ok(info) => responder.send(zx_status::sys::ZX_OK, Some(&info))?,
272 }
273 }
274 fio::DirectoryRequest::Unlink { name: _, options: _, responder } => {
275 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
276 }
277 #[cfg(fuchsia_api_level_at_least = "NEXT")]
278 fio::DirectoryRequest::DeprecatedGetToken { responder } => {
279 responder.send(Status::NOT_SUPPORTED.into_raw(), None)?;
280 }
281 #[cfg(fuchsia_api_level_at_least = "NEXT")]
282 fio::DirectoryRequest::GetToken { responder } => {
283 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
284 }
285 #[cfg(not(fuchsia_api_level_at_least = "NEXT"))]
286 fio::DirectoryRequest::GetToken { responder } => {
287 responder.send(Status::NOT_SUPPORTED.into_raw(), None)?;
288 }
289 fio::DirectoryRequest::Rename { src: _, dst_parent_token: _, dst: _, responder } => {
290 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
291 }
292 #[cfg(fuchsia_api_level_at_least = "28")]
293 fio::DirectoryRequest::DeprecatedSetAttr { flags: _, attributes: _, responder } => {
294 responder.send(Status::NOT_SUPPORTED.into_raw())?;
295 }
296 #[cfg(not(fuchsia_api_level_at_least = "28"))]
297 fio::DirectoryRequest::SetAttr { flags: _, attributes: _, responder } => {
298 responder.send(Status::NOT_SUPPORTED.into_raw())?;
299 }
300 fio::DirectoryRequest::Sync { responder } => {
301 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
302 }
303 fio::DirectoryRequest::CreateSymlink { responder, .. } => {
304 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
305 }
306 fio::DirectoryRequest::Open { path, mut flags, options, object, control_handle: _ } => {
307 {
308 if !self.options.rights.contains(fio::INHERITED_WRITE_PERMISSIONS) {
310 flags &= !fio::Flags::PERM_INHERIT_WRITE;
311 }
312 if !self.options.rights.contains(fio::Rights::EXECUTE) {
313 flags &= !fio::Flags::PERM_INHERIT_EXECUTE;
314 }
315
316 ObjectRequest::new(flags, &options, object)
317 .handle_async(async |req| self.handle_open(path, flags, req).await)
318 .trace(trace::trace_future_args!("storage", "Directory::Open3"))
319 .await;
320 }
321 yield_to_executor().await;
324 }
325 fio::DirectoryRequest::_UnknownMethod { .. } => (),
326 }
327 Ok(ConnectionState::Alive)
328 }
329 async fn handle_get_attributes(
330 &self,
331 query: fio::NodeAttributesQuery,
332 ) -> Result<fio::NodeAttributes2, Status> {
333 if !self.options.rights.intersects(fio::Operations::GET_ATTRIBUTES) {
334 return Err(Status::ACCESS_DENIED);
335 }
336 self.directory.get_attributes(query).await
337 }
338
339 fn handle_clone(&mut self, object: flex_client::Channel) {
340 let flags = fio::Flags::from(&self.options);
341 ObjectRequest::new(flags, &Default::default(), object)
342 .handle(|req| self.directory.clone().open(self.scope.clone(), Path::dot(), flags, req));
343 }
344
345 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
346 fn handle_deprecated_open(
347 &self,
348 mut flags: fio::OpenFlags,
349 path: String,
350 server_end: ServerEnd<fio::NodeMarker>,
351 ) {
352 let describe = flags.intersects(fio::OpenFlags::DESCRIBE);
353
354 let path = match Path::validate_and_split(path) {
355 Ok(path) => path,
356 Err(status) => {
357 send_on_open_with_error(describe, server_end, status);
358 return;
359 }
360 };
361
362 if !path.is_dot() && !self.options.rights.contains(fio::Operations::TRAVERSE) {
363 send_on_open_with_error(describe, server_end, Status::ACCESS_DENIED);
364 return;
365 }
366
367 if path.is_dir() {
368 flags |= fio::OpenFlags::DIRECTORY;
369 }
370
371 let flags = match check_child_connection_flags(self.options.to_io1(), flags) {
372 Ok(updated) => updated,
373 Err(status) => {
374 send_on_open_with_error(describe, server_end, status);
375 return;
376 }
377 };
378 if path.is_dot() {
379 if flags.intersects(fio::OpenFlags::NOT_DIRECTORY) {
380 send_on_open_with_error(describe, server_end, Status::INVALID_ARGS);
381 return;
382 }
383 if flags.intersects(fio::OpenFlags::CREATE_IF_ABSENT) {
384 send_on_open_with_error(describe, server_end, Status::ALREADY_EXISTS);
385 return;
386 }
387 }
388
389 let directory = self.directory.clone();
391 directory.deprecated_open(self.scope.clone(), flags, path, server_end);
392 }
393
394 async fn handle_open(
395 &self,
396 path: String,
397 flags: fio::Flags,
398 object_request: ObjectRequestRef<'_>,
399 ) -> Result<(), Status> {
400 let path = Path::validate_and_split(path)?;
401
402 if !path.is_dot() && !self.options.rights.contains(fio::Operations::TRAVERSE) {
403 return Err(Status::ACCESS_DENIED);
404 }
405
406 if let Some(rights) = flags.rights() {
408 if rights.intersects(!self.options.rights) {
409 return Err(Status::ACCESS_DENIED);
410 }
411 }
412
413 if flags.intersects(fio::Flags::PROTOCOL_SERVICE)
416 && !self.options.rights.contains(fio::Operations::CONNECT)
417 {
418 return Err(Status::ACCESS_DENIED);
419 }
420
421 if !object_request.attributes().is_empty()
423 && !self.options.rights.contains(fio::Operations::GET_ATTRIBUTES)
424 {
425 return Err(Status::ACCESS_DENIED);
426 }
427
428 match flags.creation_mode() {
429 CreationMode::Never => {
430 if object_request.create_attributes().is_some() {
431 return Err(Status::INVALID_ARGS);
432 }
433 }
434 CreationMode::UnnamedTemporary | CreationMode::UnlinkableUnnamedTemporary => {
435 if !flags.intersects(fio::Flags::PROTOCOL_FILE) {
437 return Err(Status::NOT_SUPPORTED);
438 }
439 if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
441 return Err(Status::ACCESS_DENIED);
442 }
443 }
450 CreationMode::AllowExisting | CreationMode::Always => {
451 if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
453 return Err(Status::ACCESS_DENIED);
454 }
455
456 let protocol_flags = flags & fio::MASK_KNOWN_PROTOCOLS;
457 if protocol_flags.is_empty()
460 || (protocol_flags.bits() & (protocol_flags.bits() - 1)) != 0
461 {
462 return Err(Status::INVALID_ARGS);
463 }
464 if !protocol_flags
466 .intersects(fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::PROTOCOL_FILE)
467 {
468 return Err(Status::NOT_SUPPORTED);
469 }
470 }
471 }
472
473 if path.is_dot() && flags.creation_mode() == CreationMode::Always {
474 return Err(Status::ALREADY_EXISTS);
475 }
476
477 self.directory.clone().open_async(self.scope.clone(), path, flags, object_request).await
478 }
479
480 async fn handle_read_dirents(&mut self, max_bytes: u64) -> (Result<(), Status>, Vec<u8>) {
481 if !self.options.rights.intersects(fio::Operations::ENUMERATE) {
482 return (Err(Status::ACCESS_DENIED), Vec::new());
483 }
484 let (new_pos, sealed) =
485 match self.directory.read_dirents(&self.seek, read_dirents::Sink::new(max_bytes)).await
486 {
487 Ok(res) => res,
488 Err(e) => return (Err(e), Vec::new()),
489 };
490 self.seek = new_pos;
491 match sealed.open().downcast::<read_dirents::Done>() {
492 Ok(done) => (done.status, done.buf),
493 Err(_) => {
494 debug_assert!(
495 false,
496 "`read_dirents()` returned a `dirents_sink::Sealed` \
497 instance that is not an instance of the \
498 `read_dirents::Done`. This is a bug in the \
499 `read_dirents()` implementation."
500 );
501 (Err(Status::NOT_SUPPORTED), Vec::new())
502 }
503 }
504 }
505
506 async fn handle_link(
507 &self,
508 source_name: &str,
509 target_parent_token: flex_client::NullableHandle,
510 target_name: String,
511 ) -> Result<(), Status> {
512 if source_name.contains('/') || target_name.contains('/') {
513 return Err(Status::INVALID_ARGS);
514 }
515
516 if !self.options.rights.contains(fio::RW_STAR_DIR) {
520 return Err(Status::BAD_HANDLE);
521 }
522
523 let (target_parent, target_rights) = self
524 .scope
525 .token_registry()
526 .get_owner_and_rights(target_parent_token)?
527 .ok_or(Status::NOT_FOUND)?;
528
529 if !target_rights.contains(fio::Rights::MODIFY_DIRECTORY) {
530 return Err(Status::BAD_HANDLE);
531 }
532
533 let target_parent = target_parent.into_mutable_directory().ok_or(Status::INVALID_ARGS)?;
534
535 target_parent.link(target_name, self.directory.clone().into_any(), source_name).await
536 }
537
538 fn handle_watch(
539 &mut self,
540 mask: fio::WatchMask,
541 watcher: DirectoryWatcher,
542 ) -> Result<(), Status> {
543 if !self.options.rights.intersects(fio::Operations::ENUMERATE) {
544 return Err(Status::ACCESS_DENIED);
545 }
546 let directory = self.directory.clone();
547 directory.register_watcher(self.scope.clone(), mask, watcher)
548 }
549
550 async fn handle_list_extended_attribute(
551 &self,
552 iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
553 ) {
554 if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
555 let _ = iterator.close_with_epitaph(Status::BAD_HANDLE);
556 return;
557 }
558 let attributes = match self.directory.list_extended_attributes().await {
559 Ok(attributes) => attributes,
560 Err(status) => {
561 #[cfg(any(test, feature = "use_log"))]
562 log::error!(status:?; "list extended attributes failed");
563 #[allow(clippy::unnecessary_lazy_evaluations)]
564 iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
565 #[cfg(any(test, feature = "use_log"))]
566 log::error!(_error:?; "failed to send epitaph")
567 });
568 return;
569 }
570 };
571 self.scope.spawn(extended_attributes_sender(iterator, attributes));
572 }
573
574 async fn handle_get_extended_attribute(
575 &self,
576 name: Vec<u8>,
577 ) -> Result<fio::ExtendedAttributeValue, Status> {
578 if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
579 return Err(Status::BAD_HANDLE);
580 }
581 let value = self.directory.get_extended_attribute(name).await?;
582 encode_extended_attribute_value(value)
583 }
584
585 async fn handle_set_extended_attribute(
586 &self,
587 name: Vec<u8>,
588 value: fio::ExtendedAttributeValue,
589 mode: fio::SetExtendedAttributeMode,
590 ) -> Result<(), Status> {
591 if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
592 return Err(Status::BAD_HANDLE);
593 }
594 if name.contains(&0) {
595 return Err(Status::INVALID_ARGS);
596 }
597 let val = decode_extended_attribute_value(value)?;
598 self.directory.set_extended_attribute(name, val, mode).await
599 }
600
601 async fn handle_remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
602 if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
603 return Err(Status::BAD_HANDLE);
604 }
605 self.directory.remove_extended_attribute(name).await
606 }
607}
608
609impl<DirectoryType: Directory> Representation for BaseConnection<DirectoryType> {
610 type Protocol = fio::DirectoryMarker;
611
612 async fn get_representation(
613 &self,
614 requested_attributes: fio::NodeAttributesQuery,
615 ) -> Result<fio::Representation, Status> {
616 Ok(fio::Representation::Directory(fio::DirectoryInfo {
617 attributes: if requested_attributes.is_empty() {
618 None
619 } else {
620 Some(self.directory.get_attributes(requested_attributes).await?)
621 },
622 ..Default::default()
623 }))
624 }
625
626 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
627 async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
628 Ok(fio::NodeInfoDeprecated::Directory(fio::DirectoryObject))
629 }
630}
631
632#[cfg(test)]
633mod tests {
634 use super::*;
635 use crate::directory::immutable::Simple;
636 use assert_matches::assert_matches;
637 use flex_fuchsia_io as fio;
638
639 #[cfg(not(feature = "fdomain"))]
640 use fuchsia_fs::directory;
641 #[cfg(feature = "fdomain")]
642 use fuchsia_fs_fdomain::directory;
643
644 fn test_scope() -> crate::execution_scope::ExecutionScope {
645 #[cfg(feature = "fdomain")]
646 let client = flex_local::local_client_empty();
647 #[cfg(feature = "fdomain")]
648 return crate::execution_scope::ExecutionScope::new(client);
649 #[cfg(not(feature = "fdomain"))]
650 return crate::execution_scope::ExecutionScope::new();
651 }
652
653 #[fuchsia::test]
654 async fn test_open_not_found() {
655 let dir = Simple::new();
656 let scope = test_scope();
657 let dir_proxy = crate::directory::serve(dir, scope.clone(), fio::PERM_READABLE);
658
659 let node_proxy =
661 directory::open_async::<fio::NodeMarker>(&dir_proxy, "foo", fio::PERM_READABLE)
662 .unwrap();
663
664 assert_matches!(
666 node_proxy.query().await,
667 Err(fidl::Error::ClientChannelClosed {
668 epitaph,
669 protocol_name: "fuchsia.io.Node",
670 ..
671 }) if epitaph == Status::NOT_FOUND
672 );
673 }
674
675 #[fuchsia::test]
676 async fn test_open_with_send_representation_not_found() {
677 let dir = Simple::new();
678 let scope = test_scope();
679 let dir_proxy = crate::directory::serve(dir, scope.clone(), fio::PERM_READABLE);
680
681 let node_proxy = directory::open_async::<fio::NodeMarker>(
683 &dir_proxy,
684 "foo",
685 fio::PERM_READABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
686 )
687 .unwrap();
688
689 assert_matches!(
691 node_proxy.query().await,
692 Err(fidl::Error::ClientChannelClosed {
693 epitaph,
694 protocol_name: "fuchsia.io.Node",
695 ..
696 }) if epitaph == Status::NOT_FOUND
697 );
698 }
699}