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