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 = "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
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                    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                // TODO(https://fxbug.dev/324112547): Handle unimplemented io2 method.
149                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(Status::OK.into_raw(), 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                // Since open typically spawns a task, yield to the executor now to give that task a
220                // chance to run before we try and process the next request for this directory.
221                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.into_raw(), 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                self.seek = Default::default();
238                responder.send(Status::OK.into_raw())?;
239            }
240            fio::DirectoryRequest::Link { src, dst_parent_token, dst, responder } => {
241                async move {
242                    let status: Status = self.handle_link(&src, dst_parent_token, dst).await.into();
243                    responder.send(status.into_raw())
244                }
245                .trace(trace::trace_future_args!("storage", "Directory::Link"))
246                .await?;
247            }
248            fio::DirectoryRequest::Watch { mask, options, watcher, responder } => {
249                trace::duration!("storage", "Directory::Watch");
250                let status = if options != 0 {
251                    Status::INVALID_ARGS
252                } else {
253                    self.handle_watch(mask, watcher.into()).into()
254                };
255                responder.send(status.into_raw())?;
256            }
257            fio::DirectoryRequest::Query { responder } => {
258                trace::duration!("storage", "Directory::Query");
259                let () = responder.send(fio::DirectoryMarker::PROTOCOL_NAME.as_bytes())?;
260            }
261            fio::DirectoryRequest::QueryFilesystem { responder } => {
262                trace::duration!("storage", "Directory::QueryFilesystem");
263                match self.directory.query_filesystem() {
264                    Err(status) => responder.send(status.into_raw(), None)?,
265                    Ok(info) => responder.send(0, Some(&info))?,
266                }
267            }
268            fio::DirectoryRequest::Unlink { name: _, options: _, responder } => {
269                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
270            }
271            fio::DirectoryRequest::GetToken { responder } => {
272                responder.send(Status::NOT_SUPPORTED.into_raw(), None)?;
273            }
274            fio::DirectoryRequest::Rename { src: _, dst_parent_token: _, dst: _, responder } => {
275                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
276            }
277            #[cfg(fuchsia_api_level_at_least = "28")]
278            fio::DirectoryRequest::DeprecatedSetAttr { flags: _, attributes: _, responder } => {
279                responder.send(Status::NOT_SUPPORTED.into_raw())?;
280            }
281            #[cfg(not(fuchsia_api_level_at_least = "28"))]
282            fio::DirectoryRequest::SetAttr { flags: _, attributes: _, responder } => {
283                responder.send(Status::NOT_SUPPORTED.into_raw())?;
284            }
285            fio::DirectoryRequest::Sync { responder } => {
286                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
287            }
288            fio::DirectoryRequest::CreateSymlink { responder, .. } => {
289                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
290            }
291            fio::DirectoryRequest::Open { path, mut flags, options, object, control_handle: _ } => {
292                {
293                    // Remove POSIX flags when the respective rights are not available.
294                    if !self.options.rights.contains(fio::INHERITED_WRITE_PERMISSIONS) {
295                        flags &= !fio::Flags::PERM_INHERIT_WRITE;
296                    }
297                    if !self.options.rights.contains(fio::Rights::EXECUTE) {
298                        flags &= !fio::Flags::PERM_INHERIT_EXECUTE;
299                    }
300
301                    ObjectRequest::new(flags, &options, object)
302                        .handle_async(async |req| self.handle_open(path, flags, req).await)
303                        .trace(trace::trace_future_args!("storage", "Directory::Open3"))
304                        .await;
305                }
306                // Since open typically spawns a task, yield to the executor now to give that task a
307                // chance to run before we try and process the next request for this directory.
308                yield_to_executor().await;
309            }
310            fio::DirectoryRequest::_UnknownMethod { .. } => (),
311        }
312        Ok(ConnectionState::Alive)
313    }
314    async fn handle_get_attributes(
315        &self,
316        query: fio::NodeAttributesQuery,
317    ) -> Result<fio::NodeAttributes2, Status> {
318        if !self.options.rights.intersects(fio::Operations::GET_ATTRIBUTES) {
319            return Err(Status::ACCESS_DENIED);
320        }
321        self.directory.get_attributes(query).await
322    }
323
324    fn handle_clone(&mut self, object: flex_client::Channel) {
325        let flags = fio::Flags::from(&self.options);
326        ObjectRequest::new(flags, &Default::default(), object)
327            .handle(|req| self.directory.clone().open(self.scope.clone(), Path::dot(), flags, req));
328    }
329
330    #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
331    fn handle_deprecated_open(
332        &self,
333        mut flags: fio::OpenFlags,
334        path: String,
335        server_end: ServerEnd<fio::NodeMarker>,
336    ) {
337        let describe = flags.intersects(fio::OpenFlags::DESCRIBE);
338
339        let path = match Path::validate_and_split(path) {
340            Ok(path) => path,
341            Err(status) => {
342                send_on_open_with_error(describe, server_end, status);
343                return;
344            }
345        };
346
347        if !path.is_dot() && !self.options.rights.contains(fio::Operations::TRAVERSE) {
348            send_on_open_with_error(describe, server_end, Status::ACCESS_DENIED);
349            return;
350        }
351
352        if path.is_dir() {
353            flags |= fio::OpenFlags::DIRECTORY;
354        }
355
356        let flags = match check_child_connection_flags(self.options.to_io1(), flags) {
357            Ok(updated) => updated,
358            Err(status) => {
359                send_on_open_with_error(describe, server_end, status);
360                return;
361            }
362        };
363        if path.is_dot() {
364            if flags.intersects(fio::OpenFlags::NOT_DIRECTORY) {
365                send_on_open_with_error(describe, server_end, Status::INVALID_ARGS);
366                return;
367            }
368            if flags.intersects(fio::OpenFlags::CREATE_IF_ABSENT) {
369                send_on_open_with_error(describe, server_end, Status::ALREADY_EXISTS);
370                return;
371            }
372        }
373
374        // It is up to the open method to handle OPEN_FLAG_DESCRIBE from this point on.
375        let directory = self.directory.clone();
376        directory.deprecated_open(self.scope.clone(), flags, path, server_end);
377    }
378
379    async fn handle_open(
380        &self,
381        path: String,
382        flags: fio::Flags,
383        object_request: ObjectRequestRef<'_>,
384    ) -> Result<(), Status> {
385        let path = Path::validate_and_split(path)?;
386
387        if !path.is_dot() && !self.options.rights.contains(fio::Operations::TRAVERSE) {
388            return Err(Status::ACCESS_DENIED);
389        }
390
391        // Child connection must have stricter or same rights as the parent connection.
392        if let Some(rights) = flags.rights() {
393            if rights.intersects(!self.options.rights) {
394                return Err(Status::ACCESS_DENIED);
395            }
396        }
397
398        // If requesting attributes, check permission.
399        if !object_request.attributes().is_empty()
400            && !self.options.rights.contains(fio::Operations::GET_ATTRIBUTES)
401        {
402            return Err(Status::ACCESS_DENIED);
403        }
404
405        match flags.creation_mode() {
406            CreationMode::Never => {
407                if object_request.create_attributes().is_some() {
408                    return Err(Status::INVALID_ARGS);
409                }
410            }
411            CreationMode::UnnamedTemporary | CreationMode::UnlinkableUnnamedTemporary => {
412                // We only support creating unnamed temporary files.
413                if !flags.intersects(fio::Flags::PROTOCOL_FILE) {
414                    return Err(Status::NOT_SUPPORTED);
415                }
416                // The parent connection must be able to modify directories if creating an object.
417                if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
418                    return Err(Status::ACCESS_DENIED);
419                }
420                // The ability to create an unnamed temporary file is dependent on the filesystem.
421                // We won't know if the directory the path eventually leads to supports the creation
422                // of unnamed temporary files until we have fully traversed the path. The way that
423                // Rust VFS is set up is such that the filesystem is responsible for traversing the
424                // path, so it is the filesystem's responsibility to report if it does not support
425                // this feature.
426            }
427            CreationMode::AllowExisting | CreationMode::Always => {
428                // The parent connection must be able to modify directories if creating an object.
429                if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
430                    return Err(Status::ACCESS_DENIED);
431                }
432
433                let protocol_flags = flags & fio::MASK_KNOWN_PROTOCOLS;
434                // If creating an object, exactly one protocol must be specified (the flags must be
435                // a power of two and non-zero).
436                if protocol_flags.is_empty()
437                    || (protocol_flags.bits() & (protocol_flags.bits() - 1)) != 0
438                {
439                    return Err(Status::INVALID_ARGS);
440                }
441                // Only a directory or file object can be created.
442                if !protocol_flags
443                    .intersects(fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::PROTOCOL_FILE)
444                {
445                    return Err(Status::NOT_SUPPORTED);
446                }
447            }
448        }
449
450        if path.is_dot() && flags.creation_mode() == CreationMode::Always {
451            return Err(Status::ALREADY_EXISTS);
452        }
453
454        self.directory.clone().open_async(self.scope.clone(), path, flags, object_request).await
455    }
456
457    async fn handle_read_dirents(&mut self, max_bytes: u64) -> (Status, Vec<u8>) {
458        async {
459            let (new_pos, sealed) =
460                self.directory.read_dirents(&self.seek, read_dirents::Sink::new(max_bytes)).await?;
461            self.seek = new_pos;
462            let read_dirents::Done { buf, status } = *sealed
463                .open()
464                .downcast::<read_dirents::Done>()
465                .map_err(|_: Box<dyn std::any::Any>| {
466                    #[cfg(debug)]
467                    panic!(
468                        "`read_dirents()` returned a `dirents_sink::Sealed`
469                        instance that is not an instance of the \
470                        `read_dirents::Done`. This is a bug in the \
471                        `read_dirents()` implementation."
472                    );
473                    Status::NOT_SUPPORTED
474                })?;
475            Ok((status, buf))
476        }
477        .await
478        .unwrap_or_else(|status| (status, Vec::new()))
479    }
480
481    async fn handle_link(
482        &self,
483        source_name: &str,
484        target_parent_token: flex_client::NullableHandle,
485        target_name: String,
486    ) -> Result<(), Status> {
487        if source_name.contains('/') || target_name.contains('/') {
488            return Err(Status::INVALID_ARGS);
489        }
490
491        // To avoid rights escalation, we must make sure that the connection to the source directory
492        // has the maximal set of file rights.  We do not check for EXECUTE because mutable
493        // filesystems that support link don't currently support EXECUTE rights.
494        if !self.options.rights.contains(fio::RW_STAR_DIR) {
495            return Err(Status::BAD_HANDLE);
496        }
497
498        let (target_parent, target_rights) = self
499            .scope
500            .token_registry()
501            .get_owner_and_rights(target_parent_token)?
502            .ok_or(Err(Status::NOT_FOUND))?;
503
504        if !target_rights.contains(fio::Rights::MODIFY_DIRECTORY) {
505            return Err(Status::BAD_HANDLE);
506        }
507
508        target_parent.link(target_name, self.directory.clone().into_any(), source_name).await
509    }
510
511    fn handle_watch(
512        &mut self,
513        mask: fio::WatchMask,
514        watcher: DirectoryWatcher,
515    ) -> Result<(), Status> {
516        let directory = self.directory.clone();
517        directory.register_watcher(self.scope.clone(), mask, watcher)
518    }
519
520    async fn handle_list_extended_attribute(
521        &self,
522        iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
523    ) {
524        if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
525            let _ = iterator.close_with_epitaph(Status::BAD_HANDLE);
526            return;
527        }
528        let attributes = match self.directory.list_extended_attributes().await {
529            Ok(attributes) => attributes,
530            Err(status) => {
531                #[cfg(any(test, feature = "use_log"))]
532                log::error!(status:?; "list extended attributes failed");
533                #[allow(clippy::unnecessary_lazy_evaluations)]
534                iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
535                    #[cfg(any(test, feature = "use_log"))]
536                    log::error!(_error:?; "failed to send epitaph")
537                });
538                return;
539            }
540        };
541        self.scope.spawn(extended_attributes_sender(iterator, attributes));
542    }
543
544    async fn handle_get_extended_attribute(
545        &self,
546        name: Vec<u8>,
547    ) -> Result<fio::ExtendedAttributeValue, Status> {
548        if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
549            return Err(Status::BAD_HANDLE);
550        }
551        let value = self.directory.get_extended_attribute(name).await?;
552        encode_extended_attribute_value(value)
553    }
554
555    async fn handle_set_extended_attribute(
556        &self,
557        name: Vec<u8>,
558        value: fio::ExtendedAttributeValue,
559        mode: fio::SetExtendedAttributeMode,
560    ) -> Result<(), Status> {
561        if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
562            return Err(Status::BAD_HANDLE);
563        }
564        if name.contains(&0) {
565            return Err(Status::INVALID_ARGS);
566        }
567        let val = decode_extended_attribute_value(value)?;
568        self.directory.set_extended_attribute(name, val, mode).await
569    }
570
571    async fn handle_remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
572        if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
573            return Err(Status::BAD_HANDLE);
574        }
575        self.directory.remove_extended_attribute(name).await
576    }
577}
578
579impl<DirectoryType: Directory> Representation for BaseConnection<DirectoryType> {
580    type Protocol = fio::DirectoryMarker;
581
582    async fn get_representation(
583        &self,
584        requested_attributes: fio::NodeAttributesQuery,
585    ) -> Result<fio::Representation, Status> {
586        Ok(fio::Representation::Directory(fio::DirectoryInfo {
587            attributes: if requested_attributes.is_empty() {
588                None
589            } else {
590                Some(self.directory.get_attributes(requested_attributes).await?)
591            },
592            ..Default::default()
593        }))
594    }
595
596    #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
597    async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
598        Ok(fio::NodeInfoDeprecated::Directory(fio::DirectoryObject))
599    }
600}
601
602#[cfg(test)]
603mod tests {
604    use super::*;
605    use crate::directory::immutable::Simple;
606    use assert_matches::assert_matches;
607    use flex_fuchsia_io as fio;
608
609    #[cfg(not(feature = "fdomain"))]
610    use fuchsia_fs::directory;
611    #[cfg(feature = "fdomain")]
612    use fuchsia_fs_fdomain::directory;
613
614    fn test_scope() -> crate::execution_scope::ExecutionScope {
615        #[cfg(feature = "fdomain")]
616        let client = flex_local::local_client_empty();
617        #[cfg(feature = "fdomain")]
618        return crate::execution_scope::ExecutionScope::new(client);
619        #[cfg(not(feature = "fdomain"))]
620        return crate::execution_scope::ExecutionScope::new();
621    }
622
623    #[fuchsia::test]
624    async fn test_open_not_found() {
625        let dir = Simple::new();
626        let scope = test_scope();
627        let dir_proxy = crate::directory::serve(dir, scope.clone(), fio::PERM_READABLE);
628
629        // Try to open a file that doesn't exist.
630        let node_proxy =
631            directory::open_async::<fio::NodeMarker>(&dir_proxy, "foo", fio::PERM_READABLE)
632                .unwrap();
633
634        // The channel is closed with a NOT_FOUND epitaph.
635        assert_matches!(
636            node_proxy.query().await,
637            Err(fidl::Error::ClientChannelClosed {
638                epitaph,
639                protocol_name: "fuchsia.io.Node",
640                ..
641            }) if epitaph == Status::NOT_FOUND
642        );
643    }
644
645    #[fuchsia::test]
646    async fn test_open_with_send_representation_not_found() {
647        let dir = Simple::new();
648        let scope = test_scope();
649        let dir_proxy = crate::directory::serve(dir, scope.clone(), fio::PERM_READABLE);
650
651        // Try to open a file that doesn't exist.
652        let node_proxy = directory::open_async::<fio::NodeMarker>(
653            &dir_proxy,
654            "foo",
655            fio::PERM_READABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
656        )
657        .unwrap();
658
659        // The channel is closed with a NOT_FOUND epitaph.
660        assert_matches!(
661            node_proxy.query().await,
662            Err(fidl::Error::ClientChannelClosed {
663                epitaph,
664                protocol_name: "fuchsia.io.Node",
665                ..
666            }) if epitaph == Status::NOT_FOUND
667        );
668    }
669}