1use anyhow::{Error, anyhow};
6use fidl_fuchsia_io as fio;
7use log::error;
8use std::sync::{Arc, OnceLock};
9use vfs::directory::entry::{EntryInfo, GetEntryInfo};
10use vfs::file::{File, FileOptions, GetVmo, SyncMode};
11use vfs::immutable_attributes;
12
13const BLOCK_SIZE: u64 = 8192;
15static VMEX_RESOURCE: OnceLock<zx::Resource> = OnceLock::new();
16
17pub fn init_vmex_resource(vmex: zx::Resource) -> Result<(), Error> {
20 VMEX_RESOURCE.set(vmex).map_err(|_| anyhow!(zx::Status::ALREADY_BOUND))
21}
22
23pub struct VmoBlob {
26 vmo: zx::Vmo,
27 root_hash: [u8; 32],
28}
29
30impl VmoBlob {
31 pub fn new(vmo: zx::Vmo, root_hash: [u8; 32]) -> Arc<Self> {
32 Arc::new(Self { vmo, root_hash })
33 }
34
35 pub fn root_hash(&self) -> &[u8; 32] {
36 &self.root_hash
37 }
38}
39
40impl GetVmo for VmoBlob {
41 fn get_vmo(&self) -> &zx::Vmo {
42 &self.vmo
43 }
44}
45
46impl GetEntryInfo for VmoBlob {
47 fn entry_info(&self) -> EntryInfo {
48 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)
49 }
50}
51
52impl vfs::node::Node for VmoBlob {
53 async fn get_attributes(
54 &self,
55 requested_attributes: fio::NodeAttributesQuery,
56 ) -> Result<fio::NodeAttributes2, zx::Status> {
57 let content_size = self.get_size().await?;
58 Ok(immutable_attributes!(
59 requested_attributes,
60 Immutable {
61 protocols: fio::NodeProtocolKinds::FILE,
62 abilities: fio::Operations::GET_ATTRIBUTES
63 | fio::Operations::READ_BYTES
64 | fio::Operations::EXECUTE,
65 content_size: content_size,
66 storage_size: content_size.div_ceil(BLOCK_SIZE) * BLOCK_SIZE,
68 root_hash: self.root_hash.to_vec(),
69 }
70 ))
71 }
72}
73
74impl File for VmoBlob {
76 fn executable(&self) -> bool {
77 true
78 }
79
80 async fn open_file(&self, _options: &FileOptions) -> Result<(), zx::Status> {
81 Ok(())
82 }
83
84 async fn truncate(&self, _length: u64) -> Result<(), zx::Status> {
85 Err(zx::Status::ACCESS_DENIED)
86 }
87
88 async fn get_size(&self) -> Result<u64, zx::Status> {
89 self.vmo.get_content_size()
90 }
91
92 async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, zx::Status> {
93 if flags.contains(fio::VmoFlags::SHARED_BUFFER) {
95 error!("get_backing_memory does not support exact sharing mode!");
96 return Err(zx::Status::NOT_SUPPORTED);
97 }
98 if flags.contains(fio::VmoFlags::WRITE) && !flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
102 error!("get_buffer only supports VmoFlags::WRITE with VmoFlags::PRIVATE_CLONE!");
103 return Err(zx::Status::NOT_SUPPORTED);
104 }
105
106 let use_reference = !flags.contains(fio::VmoFlags::WRITE);
109 let child_options = if use_reference {
110 zx::VmoChildOptions::REFERENCE | zx::VmoChildOptions::NO_WRITE
111 } else {
112 zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE
113 };
114 let child_size = if use_reference { 0 } else { self.vmo.get_content_size()? };
115 let mut child_vmo = self.vmo.create_child(child_options, 0, child_size)?;
116
117 if flags.contains(fio::VmoFlags::EXECUTE) {
118 child_vmo = child_vmo
120 .replace_as_executable(VMEX_RESOURCE.get().ok_or(zx::Status::NOT_SUPPORTED)?)?;
121 }
122
123 Ok(child_vmo)
124 }
125
126 async fn update_attributes(
127 &self,
128 _attributes: fio::MutableNodeAttributes,
129 ) -> Result<(), zx::Status> {
130 Err(zx::Status::NOT_SUPPORTED)
131 }
132
133 async fn sync(&self, _mode: SyncMode) -> Result<(), zx::Status> {
134 Ok(())
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use vfs::node::Node;
142
143 fn create_test_blob(content: &[u8]) -> Arc<VmoBlob> {
144 let vmo = zx::Vmo::create(content.len().max(4096) as u64).unwrap();
145 vmo.set_content_size(&(content.len() as u64)).unwrap();
146 vmo.write(content, 0).unwrap();
147 VmoBlob::new(vmo, [0xabu8; 32])
148 }
149
150 #[fuchsia::test]
151 async fn test_vmo_blob_node_and_file_traits() {
152 let blob = create_test_blob(b"hello vmo blob");
153
154 assert_eq!(blob.get_vmo().get_content_size().unwrap(), 14);
155 assert_eq!(blob.entry_info(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File));
156 assert!(blob.executable());
157 assert_eq!(blob.open_file(&FileOptions::default()).await, Ok(()));
158 assert_eq!(blob.truncate(0).await, Err(zx::Status::ACCESS_DENIED));
159 assert_eq!(blob.get_size().await, Ok(14));
160 assert_eq!(
161 blob.update_attributes(fio::MutableNodeAttributes::default()).await,
162 Err(zx::Status::NOT_SUPPORTED)
163 );
164 assert_eq!(blob.sync(SyncMode::Normal).await, Ok(()));
165
166 let attrs = blob.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap();
167 assert_eq!(attrs.immutable_attributes.protocols, Some(fio::NodeProtocolKinds::FILE));
168 assert_eq!(attrs.immutable_attributes.content_size, Some(14));
169 assert_eq!(attrs.immutable_attributes.storage_size, Some(BLOCK_SIZE));
170 assert_eq!(attrs.immutable_attributes.root_hash, Some(vec![0xabu8; 32]));
171 }
172
173 #[fuchsia::test]
174 async fn test_get_backing_memory_and_vmex() {
175 let blob = create_test_blob(b"backing memory test");
176
177 assert_eq!(
179 blob.get_backing_memory(fio::VmoFlags::SHARED_BUFFER).await.unwrap_err(),
180 zx::Status::NOT_SUPPORTED
181 );
182
183 assert_eq!(
185 blob.get_backing_memory(fio::VmoFlags::WRITE).await.unwrap_err(),
186 zx::Status::NOT_SUPPORTED
187 );
188
189 let ro_vmo = blob.get_backing_memory(fio::VmoFlags::READ).await.unwrap();
191 let mut buf = [0u8; 19];
192 ro_vmo.read(&mut buf, 0).unwrap();
193 assert_eq!(&buf, b"backing memory test");
194
195 let rw_vmo = blob
197 .get_backing_memory(
198 fio::VmoFlags::READ | fio::VmoFlags::WRITE | fio::VmoFlags::PRIVATE_CLONE,
199 )
200 .await
201 .unwrap();
202 rw_vmo.write(b"MODIFIED", 0).unwrap();
203
204 assert!(
206 blob.get_backing_memory(fio::VmoFlags::READ | fio::VmoFlags::EXECUTE).await.is_err()
207 );
208
209 let _ = init_vmex_resource(zx::Resource::from(zx::NullableHandle::invalid()));
211 assert!(init_vmex_resource(zx::Resource::from(zx::NullableHandle::invalid())).is_err());
212 }
213}