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