Skip to main content

vfs/directory/
connection.rs

1// Copyright 2019 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#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
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 = "NEXT")))]
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
30/// Return type for `BaseConnection::handle_request`.
31pub enum ConnectionState {
32    /// Connection is still alive.
33    Alive,
34    /// Connection have received Node::Close message and should be closed.
35    Closed,
36}
37
38/// Handles functionality shared between mutable and immutable FIDL connections to a directory.  A
39/// single directory may contain multiple connections.  Instances of the `BaseConnection`
40/// will also hold any state that is "per-connection".  Currently that would be the access flags
41/// and the seek position.
42pub(in crate::directory) struct BaseConnection<DirectoryType: Directory> {
43    /// Execution scope this connection and any async operations and connections it creates will
44    /// use.
45    pub(in crate::directory) scope: ExecutionScope,
46
47    pub(in crate::directory) directory: OpenNode<DirectoryType>,
48
49    /// Flags set on this connection when it was opened or cloned.
50    pub(in crate::directory) options: DirectoryOptions,
51
52    /// Seek position for this connection to the directory.  We just store the element that was
53    /// returned last by ReadDirents for this connection.  Next call will look for the next element
54    /// in alphabetical order and resume from there.
55    ///
56    /// An alternative is to use an intrusive tree to have a dual index in both names and IDs that
57    /// are assigned to the entries in insertion order.  Then we can store an ID instead of the
58    /// full entry name.  This is what the C++ version is doing currently.
59    ///
60    /// It should be possible to do the same intrusive dual-indexing using, for example,
61    ///
62    ///     https://docs.rs/intrusive-collections/0.7.6/intrusive_collections/
63    ///
64    /// but, as, I think, at least for the pseudo directories, this approach is fine, and it simple
65    /// enough.
66    seek: TraversalPosition,
67}
68
69impl<DirectoryType: Directory> BaseConnection<DirectoryType> {
70    /// Constructs an instance of `BaseConnection` - to be used by derived connections, when they
71    /// need to create a nested `BaseConnection` "sub-object".  But when implementing
72    /// `create_connection`, derived connections should use the [`create_connection`] call.
73    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    /// Handle a [`DirectoryRequest`].  This function is responsible for handing all the basic
82    /// directory operations.
83    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.into_raw(), &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.into_raw(), &attrs)
131                }
132                .trace(trace::trace_future_args!("storage", "Directory::GetAttr"))
133                .await?;
134            }
135            fio::DirectoryRequest::GetAttributes { query, responder } => {
136                async move {
137                    // TODO(https://fxbug.dev/346585458): Restrict or remove GET_ATTRIBUTES.
138                    let attrs = self.directory.get_attributes(query).await;
139                    responder.send(
140                        attrs
141                            .as_ref()
142                            .map(|attrs| (&attrs.mutable_attributes, &attrs.immutable_attributes))
143                            .map_err(|status| status.into_raw()),
144                    )
145                }
146                .trace(trace::trace_future_args!("storage", "Directory::GetAttributes"))
147                .await?;
148            }
149            fio::DirectoryRequest::UpdateAttributes { payload: _, responder } => {
150                trace::duration!("storage", "Directory::UpdateAttributes");
151                // TODO(https://fxbug.dev/324112547): Handle unimplemented io2 method.
152                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
153            }
154            fio::DirectoryRequest::ListExtendedAttributes { iterator, control_handle: _ } => {
155                self.handle_list_extended_attribute(iterator)
156                    .trace(trace::trace_future_args!(
157                        "storage",
158                        "Directory::ListExtendedAttributes"
159                    ))
160                    .await;
161            }
162            fio::DirectoryRequest::GetExtendedAttribute { name, responder } => {
163                async move {
164                    let res =
165                        self.handle_get_extended_attribute(name).await.map_err(Status::into_raw);
166                    responder.send(res)
167                }
168                .trace(trace::trace_future_args!("storage", "Directory::GetExtendedAttribute"))
169                .await?;
170            }
171            fio::DirectoryRequest::SetExtendedAttribute { name, value, mode, responder } => {
172                async move {
173                    let res = self
174                        .handle_set_extended_attribute(name, value, mode)
175                        .await
176                        .map_err(Status::into_raw);
177                    responder.send(res)
178                }
179                .trace(trace::trace_future_args!("storage", "Directory::SetExtendedAttribute"))
180                .await?;
181            }
182            fio::DirectoryRequest::RemoveExtendedAttribute { name, responder } => {
183                async move {
184                    let res =
185                        self.handle_remove_extended_attribute(name).await.map_err(Status::into_raw);
186                    responder.send(res)
187                }
188                .trace(trace::trace_future_args!("storage", "Directory::RemoveExtendedAttribute"))
189                .await?;
190            }
191            fio::DirectoryRequest::GetFlags { responder } => {
192                trace::duration!("storage", "Directory::GetFlags");
193                responder.send(Ok(fio::Flags::from(&self.options)))?;
194            }
195            fio::DirectoryRequest::SetFlags { flags: _, responder } => {
196                trace::duration!("storage", "Directory::SetFlags");
197                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
198            }
199            fio::DirectoryRequest::DeprecatedGetFlags { responder } => {
200                trace::duration!("storage", "Directory::DeprecatedGetFlags");
201                responder.send(Status::OK.into_raw(), self.options.to_io1())?;
202            }
203            fio::DirectoryRequest::DeprecatedSetFlags { flags: _, responder } => {
204                trace::duration!("storage", "Directory::DeprecatedSetFlags");
205                responder.send(Status::NOT_SUPPORTED.into_raw())?;
206            }
207            #[cfg(any(
208                fuchsia_api_level_at_least = "PLATFORM",
209                not(fuchsia_api_level_at_least = "NEXT")
210            ))]
211            fio::DirectoryRequest::DeprecatedOpen {
212                flags,
213                mode: _,
214                path,
215                object,
216                control_handle: _,
217            } => {
218                {
219                    trace::duration!("storage", "Directory::Open");
220                    self.handle_deprecated_open(flags, path, object);
221                }
222                // Since open typically spawns a task, yield to the executor now to give that task a
223                // chance to run before we try and process the next request for this directory.
224                yield_to_executor().await;
225            }
226            fio::DirectoryRequest::AdvisoryLock { request: _, responder } => {
227                trace::duration!("storage", "Directory::AdvisoryLock");
228                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
229            }
230            fio::DirectoryRequest::ReadDirents { max_bytes, responder } => {
231                async move {
232                    let (status, entries) = self.handle_read_dirents(max_bytes).await;
233                    responder.send(status.into_raw(), entries.as_slice())
234                }
235                .trace(trace::trace_future_args!("storage", "Directory::ReadDirents"))
236                .await?;
237            }
238            fio::DirectoryRequest::Rewind { responder } => {
239                trace::duration!("storage", "Directory::Rewind");
240                self.seek = Default::default();
241                responder.send(Status::OK.into_raw())?;
242            }
243            fio::DirectoryRequest::Link { src, dst_parent_token, dst, responder } => {
244                async move {
245                    let status: Status = self.handle_link(&src, dst_parent_token, dst).await.into();
246                    responder.send(status.into_raw())
247                }
248                .trace(trace::trace_future_args!("storage", "Directory::Link"))
249                .await?;
250            }
251            fio::DirectoryRequest::Watch { mask, options, watcher, responder } => {
252                trace::duration!("storage", "Directory::Watch");
253                let status = if options != 0 {
254                    Status::INVALID_ARGS
255                } else {
256                    self.handle_watch(mask, watcher.into()).into()
257                };
258                responder.send(status.into_raw())?;
259            }
260            fio::DirectoryRequest::Query { responder } => {
261                trace::duration!("storage", "Directory::Query");
262                let () = responder.send(fio::DirectoryMarker::PROTOCOL_NAME.as_bytes())?;
263            }
264            fio::DirectoryRequest::QueryFilesystem { responder } => {
265                trace::duration!("storage", "Directory::QueryFilesystem");
266                match self.directory.query_filesystem() {
267                    Err(status) => responder.send(status.into_raw(), None)?,
268                    Ok(info) => responder.send(0, Some(&info))?,
269                }
270            }
271            fio::DirectoryRequest::Unlink { name: _, options: _, responder } => {
272                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
273            }
274            fio::DirectoryRequest::GetToken { responder } => {
275                responder.send(Status::NOT_SUPPORTED.into_raw(), None)?;
276            }
277            fio::DirectoryRequest::Rename { src: _, dst_parent_token: _, dst: _, responder } => {
278                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
279            }
280            #[cfg(fuchsia_api_level_at_least = "28")]
281            fio::DirectoryRequest::DeprecatedSetAttr { flags: _, attributes: _, responder } => {
282                responder.send(Status::NOT_SUPPORTED.into_raw())?;
283            }
284            #[cfg(not(fuchsia_api_level_at_least = "28"))]
285            fio::DirectoryRequest::SetAttr { flags: _, attributes: _, responder } => {
286                responder.send(Status::NOT_SUPPORTED.into_raw())?;
287            }
288            fio::DirectoryRequest::Sync { responder } => {
289                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
290            }
291            fio::DirectoryRequest::CreateSymlink { responder, .. } => {
292                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
293            }
294            fio::DirectoryRequest::Open { path, mut flags, options, object, control_handle: _ } => {
295                {
296                    // Remove POSIX flags when the respective rights are not available.
297                    if !self.options.rights.contains(fio::INHERITED_WRITE_PERMISSIONS) {
298                        flags &= !fio::Flags::PERM_INHERIT_WRITE;
299                    }
300                    if !self.options.rights.contains(fio::Rights::EXECUTE) {
301                        flags &= !fio::Flags::PERM_INHERIT_EXECUTE;
302                    }
303
304                    ObjectRequest::new(flags, &options, object)
305                        .handle_async(async |req| self.handle_open(path, flags, req).await)
306                        .trace(trace::trace_future_args!("storage", "Directory::Open3"))
307                        .await;
308                }
309                // Since open typically spawns a task, yield to the executor now to give that task a
310                // chance to run before we try and process the next request for this directory.
311                yield_to_executor().await;
312            }
313            fio::DirectoryRequest::_UnknownMethod { .. } => (),
314        }
315        Ok(ConnectionState::Alive)
316    }
317
318    fn handle_clone(&mut self, object: flex_client::Channel) {
319        let flags = fio::Flags::from(&self.options);
320        ObjectRequest::new(flags, &Default::default(), object)
321            .handle(|req| self.directory.clone().open(self.scope.clone(), Path::dot(), flags, req));
322    }
323
324    #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
325    fn handle_deprecated_open(
326        &self,
327        mut flags: fio::OpenFlags,
328        path: String,
329        server_end: ServerEnd<fio::NodeMarker>,
330    ) {
331        let describe = flags.intersects(fio::OpenFlags::DESCRIBE);
332
333        let path = match Path::validate_and_split(path) {
334            Ok(path) => path,
335            Err(status) => {
336                send_on_open_with_error(describe, server_end, status);
337                return;
338            }
339        };
340
341        if path.is_dir() {
342            flags |= fio::OpenFlags::DIRECTORY;
343        }
344
345        let flags = match check_child_connection_flags(self.options.to_io1(), flags) {
346            Ok(updated) => updated,
347            Err(status) => {
348                send_on_open_with_error(describe, server_end, status);
349                return;
350            }
351        };
352        if path.is_dot() {
353            if flags.intersects(fio::OpenFlags::NOT_DIRECTORY) {
354                send_on_open_with_error(describe, server_end, Status::INVALID_ARGS);
355                return;
356            }
357            if flags.intersects(fio::OpenFlags::CREATE_IF_ABSENT) {
358                send_on_open_with_error(describe, server_end, Status::ALREADY_EXISTS);
359                return;
360            }
361        }
362
363        // It is up to the open method to handle OPEN_FLAG_DESCRIBE from this point on.
364        let directory = self.directory.clone();
365        directory.deprecated_open(self.scope.clone(), flags, path, server_end);
366    }
367
368    async fn handle_open(
369        &self,
370        path: String,
371        flags: fio::Flags,
372        object_request: ObjectRequestRef<'_>,
373    ) -> Result<(), Status> {
374        let path = Path::validate_and_split(path)?;
375
376        // Child connection must have stricter or same rights as the parent connection.
377        if let Some(rights) = flags.rights() {
378            if rights.intersects(!self.options.rights) {
379                return Err(Status::ACCESS_DENIED);
380            }
381        }
382
383        // If requesting attributes, check permission.
384        if !object_request.attributes().is_empty()
385            && !self.options.rights.contains(fio::Operations::GET_ATTRIBUTES)
386        {
387            return Err(Status::ACCESS_DENIED);
388        }
389
390        match flags.creation_mode() {
391            CreationMode::Never => {
392                if object_request.create_attributes().is_some() {
393                    return Err(Status::INVALID_ARGS);
394                }
395            }
396            CreationMode::UnnamedTemporary | CreationMode::UnlinkableUnnamedTemporary => {
397                // We only support creating unnamed temporary files.
398                if !flags.intersects(fio::Flags::PROTOCOL_FILE) {
399                    return Err(Status::NOT_SUPPORTED);
400                }
401                // The parent connection must be able to modify directories if creating an object.
402                if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
403                    return Err(Status::ACCESS_DENIED);
404                }
405                // The ability to create an unnamed temporary file is dependent on the filesystem.
406                // We won't know if the directory the path eventually leads to supports the creation
407                // of unnamed temporary files until we have fully traversed the path. The way that
408                // Rust VFS is set up is such that the filesystem is responsible for traversing the
409                // path, so it is the filesystem's responsibility to report if it does not support
410                // this feature.
411            }
412            CreationMode::AllowExisting | CreationMode::Always => {
413                // The parent connection must be able to modify directories if creating an object.
414                if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
415                    return Err(Status::ACCESS_DENIED);
416                }
417
418                let protocol_flags = flags & fio::MASK_KNOWN_PROTOCOLS;
419                // If creating an object, exactly one protocol must be specified (the flags must be
420                // a power of two and non-zero).
421                if protocol_flags.is_empty()
422                    || (protocol_flags.bits() & (protocol_flags.bits() - 1)) != 0
423                {
424                    return Err(Status::INVALID_ARGS);
425                }
426                // Only a directory or file object can be created.
427                if !protocol_flags
428                    .intersects(fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::PROTOCOL_FILE)
429                {
430                    return Err(Status::NOT_SUPPORTED);
431                }
432            }
433        }
434
435        if path.is_dot() && flags.creation_mode() == CreationMode::Always {
436            return Err(Status::ALREADY_EXISTS);
437        }
438
439        self.directory.clone().open_async(self.scope.clone(), path, flags, object_request).await
440    }
441
442    async fn handle_read_dirents(&mut self, max_bytes: u64) -> (Status, Vec<u8>) {
443        async {
444            let (new_pos, sealed) =
445                self.directory.read_dirents(&self.seek, read_dirents::Sink::new(max_bytes)).await?;
446            self.seek = new_pos;
447            let read_dirents::Done { buf, status } = *sealed
448                .open()
449                .downcast::<read_dirents::Done>()
450                .map_err(|_: Box<dyn std::any::Any>| {
451                    #[cfg(debug)]
452                    panic!(
453                        "`read_dirents()` returned a `dirents_sink::Sealed`
454                        instance that is not an instance of the \
455                        `read_dirents::Done`. This is a bug in the \
456                        `read_dirents()` implementation."
457                    );
458                    Status::NOT_SUPPORTED
459                })?;
460            Ok((status, buf))
461        }
462        .await
463        .unwrap_or_else(|status| (status, Vec::new()))
464    }
465
466    async fn handle_link(
467        &self,
468        source_name: &str,
469        target_parent_token: flex_client::NullableHandle,
470        target_name: String,
471    ) -> Result<(), Status> {
472        if source_name.contains('/') || target_name.contains('/') {
473            return Err(Status::INVALID_ARGS);
474        }
475
476        // To avoid rights escalation, we must make sure that the connection to the source directory
477        // has the maximal set of file rights.  We do not check for EXECUTE because mutable
478        // filesystems that support link don't currently support EXECUTE rights.
479        if !self.options.rights.contains(fio::RW_STAR_DIR) {
480            return Err(Status::BAD_HANDLE);
481        }
482
483        let (target_parent, target_rights) = self
484            .scope
485            .token_registry()
486            .get_owner_and_rights(target_parent_token)?
487            .ok_or(Err(Status::NOT_FOUND))?;
488
489        if !target_rights.contains(fio::Rights::MODIFY_DIRECTORY) {
490            return Err(Status::BAD_HANDLE);
491        }
492
493        target_parent.link(target_name, self.directory.clone().into_any(), source_name).await
494    }
495
496    fn handle_watch(
497        &mut self,
498        mask: fio::WatchMask,
499        watcher: DirectoryWatcher,
500    ) -> Result<(), Status> {
501        let directory = self.directory.clone();
502        directory.register_watcher(self.scope.clone(), mask, watcher)
503    }
504
505    async fn handle_list_extended_attribute(
506        &self,
507        iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
508    ) {
509        if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
510            let _ = iterator.close_with_epitaph(Status::BAD_HANDLE);
511            return;
512        }
513        let attributes = match self.directory.list_extended_attributes().await {
514            Ok(attributes) => attributes,
515            Err(status) => {
516                #[cfg(any(test, feature = "use_log"))]
517                log::error!(status:?; "list extended attributes failed");
518                #[allow(clippy::unnecessary_lazy_evaluations)]
519                iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
520                    #[cfg(any(test, feature = "use_log"))]
521                    log::error!(_error:?; "failed to send epitaph")
522                });
523                return;
524            }
525        };
526        self.scope.spawn(extended_attributes_sender(iterator, attributes));
527    }
528
529    async fn handle_get_extended_attribute(
530        &self,
531        name: Vec<u8>,
532    ) -> Result<fio::ExtendedAttributeValue, Status> {
533        if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
534            return Err(Status::BAD_HANDLE);
535        }
536        let value = self.directory.get_extended_attribute(name).await?;
537        encode_extended_attribute_value(value)
538    }
539
540    async fn handle_set_extended_attribute(
541        &self,
542        name: Vec<u8>,
543        value: fio::ExtendedAttributeValue,
544        mode: fio::SetExtendedAttributeMode,
545    ) -> Result<(), Status> {
546        if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
547            return Err(Status::BAD_HANDLE);
548        }
549        if name.contains(&0) {
550            return Err(Status::INVALID_ARGS);
551        }
552        let val = decode_extended_attribute_value(value)?;
553        self.directory.set_extended_attribute(name, val, mode).await
554    }
555
556    async fn handle_remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
557        if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
558            return Err(Status::BAD_HANDLE);
559        }
560        self.directory.remove_extended_attribute(name).await
561    }
562}
563
564impl<DirectoryType: Directory> Representation for BaseConnection<DirectoryType> {
565    type Protocol = fio::DirectoryMarker;
566
567    async fn get_representation(
568        &self,
569        requested_attributes: fio::NodeAttributesQuery,
570    ) -> Result<fio::Representation, Status> {
571        Ok(fio::Representation::Directory(fio::DirectoryInfo {
572            attributes: if requested_attributes.is_empty() {
573                None
574            } else {
575                Some(self.directory.get_attributes(requested_attributes).await?)
576            },
577            ..Default::default()
578        }))
579    }
580
581    #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
582    async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
583        Ok(fio::NodeInfoDeprecated::Directory(fio::DirectoryObject))
584    }
585}
586
587#[cfg(test)]
588mod tests {
589    use super::*;
590    use crate::directory::immutable::Simple;
591    use assert_matches::assert_matches;
592    use flex_fuchsia_io as fio;
593
594    #[cfg(not(feature = "fdomain"))]
595    use fuchsia_fs::directory;
596    #[cfg(feature = "fdomain")]
597    use fuchsia_fs_fdomain::directory;
598
599    fn test_scope() -> crate::execution_scope::ExecutionScope {
600        #[cfg(feature = "fdomain")]
601        let client = flex_local::local_client_empty();
602        #[cfg(feature = "fdomain")]
603        return crate::execution_scope::ExecutionScope::new(client);
604        #[cfg(not(feature = "fdomain"))]
605        return crate::execution_scope::ExecutionScope::new();
606    }
607
608    #[fuchsia::test]
609    async fn test_open_not_found() {
610        let dir = Simple::new();
611        let scope = test_scope();
612        let dir_proxy = crate::directory::serve(dir, scope.clone(), fio::PERM_READABLE);
613
614        // Try to open a file that doesn't exist.
615        let node_proxy =
616            directory::open_async::<fio::NodeMarker>(&dir_proxy, "foo", fio::PERM_READABLE)
617                .unwrap();
618
619        // The channel is closed with a NOT_FOUND epitaph.
620        assert_matches!(
621            node_proxy.query().await,
622            Err(fidl::Error::ClientChannelClosed {
623                status: Status::NOT_FOUND,
624                protocol_name: "fuchsia.io.Node",
625                ..
626            })
627        );
628    }
629
630    #[fuchsia::test]
631    async fn test_open_with_send_representation_not_found() {
632        let dir = Simple::new();
633        let scope = test_scope();
634        let dir_proxy = crate::directory::serve(dir, scope.clone(), fio::PERM_READABLE);
635
636        // Try to open a file that doesn't exist.
637        let node_proxy = directory::open_async::<fio::NodeMarker>(
638            &dir_proxy,
639            "foo",
640            fio::PERM_READABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
641        )
642        .unwrap();
643
644        // The channel is closed with a NOT_FOUND epitaph.
645        assert_matches!(
646            node_proxy.query().await,
647            Err(fidl::Error::ClientChannelClosed {
648                status: Status::NOT_FOUND,
649                protocol_name: "fuchsia.io.Node",
650                ..
651            })
652        );
653    }
654}