[io][conformance] Return NodeInfo::Vmofile for VMO files in rust vfs

This fixes a small bug in the Rust VFS's Describe method and OnOpen
event for VMO files. It now correctly returns NodeInfo::Vmofile for
these, instead of NodeInfo::File, which it was previously doing.

Bug: 68968
Change-Id: I5a8a086be0c693a22e6eaccd614ba4ecb5286873
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/483004
Reviewed-by: Chris Suter <csuter@google.com>
Commit-Queue: Cameron Silvestrini <csilvestrini@google.com>
diff --git a/src/lib/storage/vfs/rust/src/directory/mutable/simple/tests.rs b/src/lib/storage/vfs/rust/src/directory/mutable/simple/tests.rs
index 924f650..7780bdf 100644
--- a/src/lib/storage/vfs/rust/src/directory/mutable/simple/tests.rs
+++ b/src/lib/storage/vfs/rust/src/directory/mutable/simple/tests.rs
@@ -17,6 +17,7 @@
     assert_unlink, assert_unlink_err, assert_watch, assert_watcher_one_message_watched_events,
     assert_write, open_as_directory_assert_err, open_as_file_assert_err,
     open_get_directory_proxy_assert_ok, open_get_file_proxy_assert_ok, open_get_proxy_assert,
+    open_get_vmo_file_proxy_assert_ok,
 };
 
 use crate::{
@@ -543,9 +544,9 @@
             // `open_as_file_assert_content!` as the later will immediately close the connection,
             // and if no connections exist the VMO is recycled and the assertion is invoked to
             // check if the VMO content matches the final expected state.
-            let file = open_get_file_proxy_assert_ok!(&proxy, flags, "file");
+            let file = open_get_vmo_file_proxy_assert_ok!(&proxy, flags, "file");
 
-            open_as_file_assert_content!(&proxy, flags, "file", "Initial");
+            open_as_vmo_file_assert_content!(&proxy, flags, "file", "Initial");
 
             let root_token = assert_get_token!(&proxy);
             assert_link!(&proxy, "file", root_token, "same-file");
@@ -562,11 +563,11 @@
                 assert_read_dirents!(proxy, 1000, expected.into_vec());
             }
 
-            let same_file = open_get_file_proxy_assert_ok!(&proxy, flags, "same-file");
+            let same_file = open_get_vmo_file_proxy_assert_ok!(&proxy, flags, "same-file");
             assert_write!(file, "------- twice");
             assert_write!(same_file, "Updated");
 
-            open_as_file_assert_content!(&proxy, flags, "file", "Updated twice");
+            open_as_vmo_file_assert_content!(&proxy, flags, "file", "Updated twice");
 
             drop(watcher_client);
             assert_close!(file);
@@ -612,9 +613,9 @@
             // `open_as_file_assert_content!` as the later will immediately close the connection,
             // and if no connections exist the VMO is recycled and the assertion is invoked to
             // check if the VMO content matches the final expected state.
-            let passwd = open_get_file_proxy_assert_ok!(&etc, flags, "passwd");
+            let passwd = open_get_vmo_file_proxy_assert_ok!(&etc, flags, "passwd");
 
-            open_as_file_assert_content!(&etc, flags, "passwd", "secret");
+            open_as_vmo_file_assert_content!(&etc, flags, "passwd", "secret");
 
             let tmp_token = assert_get_token!(&tmp);
             assert_link!(&etc, "passwd", tmp_token, "linked-passwd");
@@ -638,11 +639,11 @@
                 assert_read_dirents!(tmp, 1000, expected.into_vec());
             }
 
-            let linked_passwd = open_get_file_proxy_assert_ok!(&tmp, flags, "linked-passwd");
+            let linked_passwd = open_get_vmo_file_proxy_assert_ok!(&tmp, flags, "linked-passwd");
             assert_write!(passwd, "-------- twice");
             assert_write!(linked_passwd, "modified");
 
-            open_as_file_assert_content!(&etc, flags, "passwd", "modified twice");
+            open_as_vmo_file_assert_content!(&etc, flags, "passwd", "modified twice");
 
             drop(tmp_watcher_client);
             assert_close!(passwd);
@@ -740,9 +741,9 @@
             // `open_as_file_assert_content!` as the later will immediately close the connection,
             // and if no connections exist the VMO is recycled and the assertion is invoked to
             // check if the VMO content matches the final expected state.
-            let passwd = open_get_file_proxy_assert_ok!(&etc, flags, "passwd");
+            let passwd = open_get_vmo_file_proxy_assert_ok!(&etc, flags, "passwd");
 
-            open_as_file_assert_content!(&etc, flags, "passwd", "secret");
+            open_as_vmo_file_assert_content!(&etc, flags, "passwd", "secret");
 
             let tmp_token = assert_get_token!(&tmp);
             assert_link!(&etc, "passwd", tmp_token, "linked-passwd");
@@ -766,11 +767,11 @@
                 assert_read_dirents!(tmp, 1000, expected.into_vec());
             }
 
-            let linked_passwd = open_get_file_proxy_assert_ok!(&tmp, flags, "linked-passwd");
+            let linked_passwd = open_get_vmo_file_proxy_assert_ok!(&tmp, flags, "linked-passwd");
             assert_write!(passwd, "-------- twice");
             assert_write!(linked_passwd, "modified");
 
-            open_as_file_assert_content!(&etc, flags, "passwd", "modified twice");
+            open_as_vmo_file_assert_content!(&etc, flags, "passwd", "modified twice");
 
             drop(tmp_watcher_client);
             assert_close!(passwd);
diff --git a/src/lib/storage/vfs/rust/src/directory/test_utils.rs b/src/lib/storage/vfs/rust/src/directory/test_utils.rs
index c2ff7f2..d0aa0fd 100644
--- a/src/lib/storage/vfs/rust/src/directory/test_utils.rs
+++ b/src/lib/storage/vfs/rust/src/directory/test_utils.rs
@@ -120,6 +120,17 @@
     }};
 }
 
+/// Opens the specified path as a VMO file and checks its content.  Also see all the `assert_*`
+/// macros in `../test_utils.rs`.
+#[macro_export]
+macro_rules! open_as_vmo_file_assert_content {
+    ($proxy:expr, $flags:expr, $path:expr, $expected_content:expr) => {{
+        let file = open_get_vmo_file_proxy_assert_ok!($proxy, $flags, $path);
+        assert_read!(file, $expected_content);
+        assert_close!(file);
+    }};
+}
+
 #[macro_export]
 macro_rules! assert_watch {
     ($proxy:expr, $mask:expr) => {{
diff --git a/src/lib/storage/vfs/rust/src/file/vmo/asynchronous/tests.rs b/src/lib/storage/vfs/rust/src/file/vmo/asynchronous/tests.rs
index 4986ff7..791c00e 100644
--- a/src/lib/storage/vfs/rust/src/file/vmo/asynchronous/tests.rs
+++ b/src/lib/storage/vfs/rust/src/file/vmo/asynchronous/tests.rs
@@ -13,7 +13,7 @@
     assert_read_fidl_err_closed, assert_seek, assert_seek_err, assert_truncate,
     assert_truncate_err, assert_vmo_content, assert_write, assert_write_at, assert_write_at_err,
     assert_write_err, assert_write_fidl_err_closed, clone_as_file_assert_err,
-    clone_get_file_proxy_assert_ok, clone_get_proxy_assert, report_invalid_vmo_content,
+    clone_get_proxy_assert, clone_get_vmo_file_proxy_assert_ok, report_invalid_vmo_content,
 };
 
 use crate::{
@@ -33,7 +33,7 @@
 use {
     fidl::endpoints::create_proxy,
     fidl_fuchsia_io::{
-        FileEvent, FileMarker, FileObject, NodeAttributes, NodeInfo, INO_UNKNOWN, MODE_TYPE_FILE,
+        FileEvent, FileMarker, NodeAttributes, NodeInfo, Vmofile, INO_UNKNOWN, MODE_TYPE_FILE,
         OPEN_FLAG_DESCRIBE, OPEN_FLAG_NODE_REFERENCE, OPEN_FLAG_POSIX, OPEN_FLAG_TRUNCATE,
         OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE, VMO_FLAG_EXACT, VMO_FLAG_PRIVATE, VMO_FLAG_READ,
         VMO_FLAG_WRITE,
@@ -111,10 +111,8 @@
 
         assert_event!(proxy, FileEvent::OnOpen_ { s, info }, {
             assert_eq!(s, ZX_OK);
-            assert_eq!(
-                info,
-                Some(Box::new(NodeInfo::File(FileObject { event: None, stream: None })))
-            );
+            let info = *info.expect("Empty NodeInfo");
+            assert!(matches!(info, NodeInfo::Vmofile(Vmofile { vmo: _, offset: 0, length: 14 })));
         });
     });
 }
@@ -256,10 +254,11 @@
 
             assert_event!(proxy, FileEvent::OnOpen_ { s, info }, {
                 assert_eq!(s, ZX_OK);
-                assert_eq!(
+                let info = *info.expect("Empty NodeInfo");
+                assert!(matches!(
                     info,
-                    Some(Box::new(NodeInfo::File(FileObject { event: None, stream: None })))
-                );
+                    NodeInfo::Vmofile(Vmofile { vmo: _, offset: 0, length: 10 })
+                ));
             });
 
             assert_read!(proxy, "Have value");
@@ -744,7 +743,7 @@
             assert_seek!(first_proxy, 0, Start);
             assert_write!(first_proxy, "As updated");
 
-            let second_proxy = clone_get_file_proxy_assert_ok!(
+            let second_proxy = clone_get_vmo_file_proxy_assert_ok!(
                 &first_proxy,
                 OPEN_RIGHT_READABLE | OPEN_FLAG_DESCRIBE
             );
@@ -771,7 +770,7 @@
             assert_seek!(first_proxy, 0, Start);
             assert_write!(first_proxy, "As updated");
 
-            let second_proxy = clone_get_file_proxy_assert_ok!(
+            let second_proxy = clone_get_vmo_file_proxy_assert_ok!(
                 &first_proxy,
                 CLONE_FLAG_SAME_RIGHTS | OPEN_FLAG_DESCRIBE
             );
@@ -917,7 +916,8 @@
                 assert_write_fidl_err_closed!(second_proxy, "Write attempt");
 
                 // We now try without OPEN_RIGHT_READABLE, as we might still be able to Seek.
-                let third_proxy = clone_get_file_proxy_assert_ok!(&first_proxy, OPEN_FLAG_DESCRIBE);
+                let third_proxy =
+                    clone_get_vmo_file_proxy_assert_ok!(&first_proxy, OPEN_FLAG_DESCRIBE);
 
                 assert_seek_err!(third_proxy, 0, Current, Status::BAD_HANDLE, 0);
 
diff --git a/src/lib/storage/vfs/rust/src/file/vmo/connection/io1.rs b/src/lib/storage/vfs/rust/src/file/vmo/connection/io1.rs
index b2fefaa..e56c3b4 100644
--- a/src/lib/storage/vfs/rust/src/file/vmo/connection/io1.rs
+++ b/src/lib/storage/vfs/rust/src/file/vmo/connection/io1.rs
@@ -21,8 +21,8 @@
     anyhow::Error,
     fidl::endpoints::{RequestStream, ServerEnd},
     fidl_fuchsia_io::{
-        FileObject, FileRequest, FileRequestStream, NodeAttributes, NodeInfo, NodeMarker,
-        SeekOrigin, INO_UNKNOWN, MODE_TYPE_FILE, OPEN_FLAG_DESCRIBE, OPEN_FLAG_NODE_REFERENCE,
+        FileRequest, FileRequestStream, NodeAttributes, NodeInfo, NodeMarker, SeekOrigin, Vmofile,
+        INO_UNKNOWN, MODE_TYPE_FILE, OPEN_FLAG_DESCRIBE, OPEN_FLAG_NODE_REFERENCE,
         OPEN_FLAG_TRUNCATE, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE, VMO_FLAG_EXACT,
         VMO_FLAG_EXEC, VMO_FLAG_PRIVATE, VMO_FLAG_READ, VMO_FLAG_WRITE,
     },
@@ -199,15 +199,7 @@
             }
         };
 
-        if flags & OPEN_FLAG_DESCRIBE != 0 {
-            let mut info = NodeInfo::File(FileObject { event: None, stream: None });
-            match control_handle.send_on_open_(Status::OK.into_raw(), Some(&mut info)) {
-                Ok(()) => (),
-                Err(_) => return,
-            }
-        }
-
-        let handle_requests = FileConnection {
+        let mut connection = FileConnection {
             scope: scope.clone(),
             file,
             requests,
@@ -215,9 +207,26 @@
             readable,
             writable,
             seek: 0,
+        };
+
+        if flags & OPEN_FLAG_DESCRIBE != 0 {
+            match connection.get_node_info().await {
+                Ok(mut info) => {
+                    let send_result =
+                        control_handle.send_on_open_(Status::OK.into_raw(), Some(&mut info));
+                    if send_result.is_err() {
+                        return;
+                    }
+                }
+                Err(status) => {
+                    debug_assert!(status != Status::OK);
+                    control_handle.shutdown_with_epitaph(status);
+                    return;
+                }
+            }
         }
-        .handle_requests();
-        handle_requests.await;
+
+        connection.handle_requests().await;
     }
 
     async fn ensure_vmo<'state_guard>(
@@ -366,6 +375,19 @@
         }
     }
 
+    /// Returns `NodeInfo` for the VMO file.
+    async fn get_node_info(&mut self) -> Result<NodeInfo, Status> {
+        let vmofile = update_initialized_state! {
+            match &*self.file.state().await;
+            error: "get_node_info" => Err(Status::INTERNAL);
+            { vmo, size, .. } => {
+                let vmo = vmo.duplicate_handle(Rights::SAME_RIGHTS).unwrap();
+                Ok(Vmofile {vmo, offset: 0, length: *size})
+            }
+        }?;
+        Ok(NodeInfo::Vmofile(vmofile))
+    }
+
     /// Handle a [`FileRequest`]. This function is responsible for handing all the file operations
     /// that operate on the connection-specific buffer.
     async fn handle_request(&mut self, req: FileRequest) -> Result<ConnectionState, Error> {
@@ -381,10 +403,13 @@
                 let _ = self.handle_close(|status| responder.send(status.into_raw())).await;
                 return Ok(ConnectionState::Closed);
             }
-            FileRequest::Describe { responder } => {
-                let mut info = NodeInfo::File(FileObject { event: None, stream: None });
-                responder.send(&mut info)?;
-            }
+            FileRequest::Describe { responder } => match self.get_node_info().await {
+                Ok(mut info) => responder.send(&mut info)?,
+                Err(status) => {
+                    debug_assert!(status != Status::OK);
+                    responder.control_handle().shutdown_with_epitaph(status);
+                }
+            },
             FileRequest::Sync { responder } => {
                 // VMOs are always in sync.
                 responder.send(ZX_OK)?;
diff --git a/src/lib/storage/vfs/rust/src/test_utils/assertions.rs b/src/lib/storage/vfs/rust/src/test_utils/assertions.rs
index 4c447cc..d119ac3 100644
--- a/src/lib/storage/vfs/rust/src/test_utils/assertions.rs
+++ b/src/lib/storage/vfs/rust/src/test_utils/assertions.rs
@@ -10,10 +10,10 @@
         crate::directory::test_utils::DirentsSameInodeBuilder,
         fidl_fuchsia_io::{
             DirectoryEvent, DirectoryMarker, DirectoryObject, FileEvent, FileMarker, FileObject,
-            NodeInfo, SeekOrigin, Service, DIRENT_TYPE_BLOCK_DEVICE, DIRENT_TYPE_DIRECTORY,
-            DIRENT_TYPE_FILE, DIRENT_TYPE_SERVICE, DIRENT_TYPE_SOCKET, DIRENT_TYPE_UNKNOWN,
-            INO_UNKNOWN, OPEN_FLAG_DESCRIBE, OPEN_RIGHT_READABLE, WATCH_EVENT_ADDED,
-            WATCH_EVENT_EXISTING, WATCH_EVENT_IDLE, WATCH_EVENT_REMOVED,
+            NodeInfo, SeekOrigin, Service, Vmofile, DIRENT_TYPE_BLOCK_DEVICE,
+            DIRENT_TYPE_DIRECTORY, DIRENT_TYPE_FILE, DIRENT_TYPE_SERVICE, DIRENT_TYPE_SOCKET,
+            DIRENT_TYPE_UNKNOWN, INO_UNKNOWN, OPEN_FLAG_DESCRIBE, OPEN_RIGHT_READABLE,
+            WATCH_EVENT_ADDED, WATCH_EVENT_EXISTING, WATCH_EVENT_IDLE, WATCH_EVENT_REMOVED,
         },
         fuchsia_zircon::{MessageBuf, Status},
         futures::stream::StreamExt,
@@ -370,6 +370,25 @@
 
 // See comment at the top of the file for why this is a macro.
 #[macro_export]
+macro_rules! open_get_vmo_file_proxy_assert_ok {
+    ($proxy:expr, $flags:expr, $path:expr) => {{
+        use $crate::test_utils::assertions::reexport::{
+            FileEvent, FileMarker, NodeInfo, Status, Vmofile,
+        };
+
+        open_get_proxy_assert!($proxy, $flags, $path, FileMarker, FileEvent::OnOpen_ { s, info }, {
+            assert_eq!(Status::from_raw(s), Status::OK);
+            let info = *info.expect("Empty NodeInfo");
+            assert!(
+                matches!(info, NodeInfo::Vmofile(Vmofile { .. })),
+                format!("Expected Vmofile but got {:?}", info)
+            );
+        })
+    }};
+}
+
+// See comment at the top of the file for why this is a macro.
+#[macro_export]
 macro_rules! open_as_file_assert_err {
     ($proxy:expr, $flags:expr, $path:expr, $expected_status:expr) => {{
         use $crate::test_utils::assertions::reexport::{FileEvent, FileMarker, Status};
@@ -461,6 +480,25 @@
 
 // See comment at the top of the file for why this is a macro.
 #[macro_export]
+macro_rules! clone_get_vmo_file_proxy_assert_ok {
+    ($proxy:expr, $flags:expr) => {{
+        use $crate::test_utils::assertions::reexport::{
+            FileEvent, FileMarker, NodeInfo, Status, Vmofile,
+        };
+
+        clone_get_proxy_assert!($proxy, $flags, FileMarker, FileEvent::OnOpen_ { s, info }, {
+            assert_eq!(Status::from_raw(s), Status::OK);
+            let info = *info.expect("Empty NodeInfo");
+            assert!(
+                matches!(info, NodeInfo::Vmofile(Vmofile { .. })),
+                format!("Expected Vmofile but got {:?}", info)
+            );
+        })
+    }};
+}
+
+// See comment at the top of the file for why this is a macro.
+#[macro_export]
 macro_rules! clone_as_file_assert_err {
     ($proxy:expr, $flags:expr, $expected_status:expr) => {{
         use $crate::test_utils::assertions::reexport::{FileEvent, FileMarker, Status};