1use crate::{ErofsError, ErofsFilesystem, FileNode, InodeDataLayout};
6use fidl_fuchsia_storage_block as fblock;
7use fuchsia_sync::Mutex;
8use mapping::{
9 DeviceExtent, Extent, Extents, MAPPING_VMO_SIZE, MappingCommand, PENDING_COMMANDS_CAPACITY,
10 RawMappingCommand,
11};
12use std::collections::HashMap;
13use vmo_fifo::SyncSender;
14
15pub struct VolumeMapper {
17 session: fblock::MapperSessionSynchronousProxy,
18 sender: Mutex<SyncSender<RawMappingCommand>>,
19 vmos_by_nid: Mutex<HashMap<u64, zx::Vmo>>,
20 file_offset: u64,
21}
22
23impl VolumeMapper {
24 pub fn new(
26 session: fblock::MapperSessionSynchronousProxy,
27 mapping_vmo: zx::Vmo,
28 file_offset: u64,
29 ) -> Result<Self, zx::Status> {
30 let sender =
31 SyncSender::<RawMappingCommand>::new(mapping_vmo, 8, PENDING_COMMANDS_CAPACITY)?;
32 Ok(Self {
33 session,
34 sender: Mutex::new(sender),
35 vmos_by_nid: Mutex::new(HashMap::new()),
36 file_offset,
37 })
38 }
39
40 pub fn open_with_session(
43 file_offset: u64,
44 open_fn: impl FnOnce(
45 zx::Vmo,
46 fidl::endpoints::ServerEnd<fblock::MapperSessionMarker>,
47 ) -> Result<(), zx::Status>,
48 ) -> Result<Self, zx::Status> {
49 let mapping_vmo = zx::Vmo::create(MAPPING_VMO_SIZE)?;
50 let mapping_vmo_dup = mapping_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
51 let (session_client, session_server) =
52 fidl::endpoints::create_endpoints::<fblock::MapperSessionMarker>();
53 open_fn(mapping_vmo_dup, session_server)?;
54 Self::new(session_client.into_sync_proxy(), mapping_vmo, file_offset)
55 }
56
57 pub fn get_or_create_vmo(
65 &self,
66 fs: &ErofsFilesystem,
67 node: &FileNode,
68 ) -> Result<zx::Vmo, zx::Status> {
69 let nid = node.nid();
70 if let Some(vmo) = self.vmos_by_nid.lock().get(&nid) {
71 return vmo.duplicate_handle(zx::Rights::SAME_RIGHTS);
72 }
73
74 let (vmo, key) = self.create_vmo_internal(fs, node)?;
75 let Some(key) = key else {
76 return Ok(vmo);
77 };
78 let mut vmos = self.vmos_by_nid.lock();
79 if let Some(existing) = vmos.get(&nid) {
80 self.close_file(key);
81 return existing.duplicate_handle(zx::Rights::SAME_RIGHTS);
82 }
83 let dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
84 vmos.insert(nid, vmo);
85 Ok(dup)
86 }
87
88 fn create_vmo_internal(
89 &self,
90 fs: &ErofsFilesystem,
91 node: &FileNode,
92 ) -> Result<(zx::Vmo, Option<u64>), zx::Status> {
93 let size = node.size();
94 let block_size = fs.block_size();
95 let read_only_rights = zx::Rights::VMO_DEFAULT - zx::Rights::WRITE;
96
97 if size == 0 {
98 let vmo = zx::Vmo::create(0)?.replace_handle(read_only_rights)?;
99 return Ok((vmo, None));
100 }
101
102 let extents = match node.data_layout() {
103 InodeDataLayout::FlatPlain => {
104 let offset = node
105 .blkaddr_offset(block_size, 0)
106 .map_err(|e| ErofsError::from(e).to_status())?;
107 let aligned_size = size.next_multiple_of(block_size);
108 vec![Extent::new(0..aligned_size, DeviceExtent::Block(self.file_offset + offset))]
109 }
110 InodeDataLayout::FlatInline => {
111 let full_blocks_len = (size / block_size) * block_size;
112 let tail_len = size - full_blocks_len;
113 let mut extents = Vec::with_capacity(2);
114 if full_blocks_len > 0 {
115 let offset = node
116 .blkaddr_offset(block_size, 0)
117 .map_err(|e| ErofsError::from(e).to_status())?;
118 extents.push(Extent::new(
119 0..full_blocks_len,
120 DeviceExtent::Block(self.file_offset + offset),
121 ));
122 }
123 if tail_len > 0 {
124 let inline_offset =
125 node.metadata_end_offset().map_err(|e| ErofsError::from(e).to_status())?;
126 extents.push(Extent::new(
127 full_blocks_len..(full_blocks_len + block_size),
128 DeviceExtent::InlineTail(self.file_offset + inline_offset),
129 ));
130 }
131 extents
132 }
133 InodeDataLayout::CompressedFull | InodeDataLayout::CompressedCompact => {
134 let vmo = zx::Vmo::create(size)?;
135 let mut buf = vec![0u8; size as usize];
136 fs.read_file_range(node, 0, &mut buf).map_err(ErofsError::to_status)?;
137 vmo.write(&buf, 0)?;
138 let vmo = vmo.replace_handle(read_only_rights)?;
139 return Ok((vmo, None));
140 }
141 };
142
143 let encoded_extents = Extents::try_new(&extents, self.file_offset).map_err(|error| {
144 log::error!(error:?; "Failed to build Extents for EROFS nid {}", node.nid());
145 zx::Status::IO_DATA_INTEGRITY
146 })?;
147 let extent_count = encoded_extents.len() as u32;
148 let allocation_size = extent_count as usize * 8;
149
150 let (key, vmo) = self
151 .session
152 .create_vmo(size, fblock::CreateVmoOptions::empty(), zx::MonotonicInstant::INFINITE)
153 .map_err(|_| zx::Status::PEER_CLOSED)
154 .and_then(|r| r.map_err(zx::Status::err_from_raw))?;
155
156 let commit_res = (|| -> Result<zx::Vmo, zx::Status> {
157 let mut sender = self.sender.lock();
158 let mut payload = sender.reserve_payload(allocation_size)?;
159 for (mut chunk, val) in
160 payload.data().chunks_mut(8).zip(Extents::encode_extents(&encoded_extents))
161 {
162 chunk.copy_from_slice(&val.to_le_bytes());
163 }
164 let cmd = MappingCommand::Mappings {
165 key,
166 offset: payload.offset(),
167 stored_size: size,
168 device_offset: self.file_offset,
169 metadata_count: 0,
170 extent_count,
171 encrypted: false,
172 };
173 payload.commit(cmd.into())?;
174 drop(sender);
175 vmo.replace_handle(read_only_rights)
176 })();
177
178 match commit_res {
179 Ok(vmo) => Ok((vmo, Some(key))),
180 Err(status) => {
181 self.close_file(key);
182 Err(status)
183 }
184 }
185 }
186
187 pub fn close_file(&self, key: u64) {
189 let _ = self.sender.lock().push(MappingCommand::CloseBlob { key }.into());
190 let _ = self.session.close_vmo(key);
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::readers::VecReader;
198 use crate::{Node, format};
199 use futures::TryStreamExt as _;
200 use std::sync::Arc;
201 use zerocopy::IntoBytes as _;
202 use zerocopy::byteorder::little_endian::{U16 as LEU16, U32 as LEU32, U64 as LEU64};
203
204 fn make_test_fs() -> ErofsFilesystem {
205 let block_size = 4096usize;
206 let num_blocks = 8;
207 let mut image = vec![0u8; num_blocks * block_size];
208
209 let sb = format::SuperBlock {
210 magic: LEU32::new(format::EROFS_MAGIC),
211 checksum: LEU32::new(0),
212 feature_compat: LEU32::new(0),
213 block_size_bits: 12,
214 sb_ext_slots: 0,
215 root_nid: LEU16::new(0),
216 inode_count: LEU64::new(5),
217 epoch: LEU64::new(0),
218 fixed_nsec: LEU32::new(0),
219 blocks: LEU32::new(num_blocks as u32),
220 meta_block_addr: LEU32::new(1),
221 xattr_block_addr: LEU32::new(0),
222 uuid: [0; 16],
223 volume_name: [0; 16],
224 feature_incompat: LEU32::new(0),
225 available_compr_algs: LEU16::new(0),
226 extra_devices: LEU32::new(0),
227 dirblkbits: 0,
228 reserved: [0; 37],
229 };
230 image[1024..1024 + 128].copy_from_slice(sb.as_bytes());
231
232 let root_inode = format::InodeCompact {
234 format: LEU16::new(0),
235 xattr_icount: LEU16::new(0),
236 mode: LEU16::new(0o040755),
237 link_count: LEU16::new(2),
238 size: LEU32::new(0),
239 reserved_1: [0; 4],
240 i_u: [0; 4],
241 ino: LEU32::new(1),
242 uid: LEU16::new(0),
243 gid: LEU16::new(0),
244 reserved_2: [0; 4],
245 };
246 image[4096..4096 + 32].copy_from_slice(root_inode.as_bytes());
247
248 let plain_inode = format::InodeCompact {
250 format: LEU16::new((InodeDataLayout::FlatPlain as u16) << 1),
251 xattr_icount: LEU16::new(0),
252 mode: LEU16::new(0o100644),
253 link_count: LEU16::new(1),
254 size: LEU32::new(5000),
255 reserved_1: [0; 4],
256 i_u: 2u32.to_le_bytes(),
257 ino: LEU32::new(2),
258 uid: LEU16::new(0),
259 gid: LEU16::new(0),
260 reserved_2: [0; 4],
261 };
262 image[4128..4128 + 32].copy_from_slice(plain_inode.as_bytes());
263
264 let inline_tail_only = format::InodeCompact {
266 format: LEU16::new((InodeDataLayout::FlatInline as u16) << 1),
267 xattr_icount: LEU16::new(0),
268 mode: LEU16::new(0o100644),
269 link_count: LEU16::new(1),
270 size: LEU32::new(500),
271 reserved_1: [0; 4],
272 i_u: 0u32.to_le_bytes(),
273 ino: LEU32::new(3),
274 uid: LEU16::new(0),
275 gid: LEU16::new(0),
276 reserved_2: [0; 4],
277 };
278 image[4160..4160 + 32].copy_from_slice(inline_tail_only.as_bytes());
279
280 let inline_multi = format::InodeCompact {
283 format: LEU16::new((InodeDataLayout::FlatInline as u16) << 1),
284 xattr_icount: LEU16::new(0),
285 mode: LEU16::new(0o100644),
286 link_count: LEU16::new(1),
287 size: LEU32::new(4096 + 300),
288 reserved_1: [0; 4],
289 i_u: 4u32.to_le_bytes(),
290 ino: LEU32::new(4),
291 uid: LEU16::new(0),
292 gid: LEU16::new(0),
293 reserved_2: [0; 4],
294 };
295 image[4736..4736 + 32].copy_from_slice(inline_multi.as_bytes());
296
297 let empty_inode = format::InodeCompact {
299 format: LEU16::new((InodeDataLayout::FlatPlain as u16) << 1),
300 xattr_icount: LEU16::new(0),
301 mode: LEU16::new(0o100644),
302 link_count: LEU16::new(1),
303 size: LEU32::new(0),
304 reserved_1: [0; 4],
305 i_u: 0u32.to_le_bytes(),
306 ino: LEU32::new(5),
307 uid: LEU16::new(0),
308 gid: LEU16::new(0),
309 reserved_2: [0; 4],
310 };
311 image[5376..5376 + 32].copy_from_slice(empty_inode.as_bytes());
312
313 ErofsFilesystem::new(Arc::new(VecReader::new(image))).expect("parse test fs")
314 }
315
316 #[fuchsia::test]
317 fn test_volume_mapper_extents_and_caching() {
318 let fs = make_test_fs();
319 let file_offset = 4096u64;
320 let created_vmos = Arc::new(Mutex::new(Vec::<(u64, u64)>::new()));
321 let created_vmos_clone = created_vmos.clone();
322
323 let (receiver_tx, receiver_rx) = std::sync::mpsc::channel();
324 let mapper =
325 VolumeMapper::open_with_session(file_offset, move |mapping_vmo, server_end| {
326 let receiver = vmo_fifo::Receiver::<RawMappingCommand>::new(
327 mapping_vmo,
328 PENDING_COMMANDS_CAPACITY,
329 )
330 .expect("create Receiver");
331 receiver_tx.send(receiver).unwrap();
332
333 std::thread::spawn(move || {
334 let mut executor = fuchsia_async::LocalExecutor::default();
335 executor.run_singlethreaded(async move {
336 let mut stream = server_end.into_stream();
337 let mut next_key = 1u64;
338 while let Ok(Some(req)) = stream.try_next().await {
339 match req {
340 fblock::MapperSessionRequest::CreateVmo {
341 size, responder, ..
342 } => {
343 let key = next_key;
344 next_key += 1;
345 created_vmos_clone.lock().push((key, size));
346 let vmo = zx::Vmo::create(size).unwrap();
347 let _ = responder.send(Ok((key, vmo)));
348 }
349 fblock::MapperSessionRequest::CloseVmo { .. } => {}
350 fblock::MapperSessionRequest::Close { responder } => {
351 let _ = responder.send(Ok(()));
352 break;
353 }
354 _ => unreachable!(),
355 }
356 }
357 });
358 });
359 Ok(())
360 })
361 .expect("open_with_session");
362
363 let receiver = receiver_rx.recv().unwrap();
364
365 let Node::File(empty_node) = fs.node(40).unwrap() else { panic!() };
368 let empty_vmo = mapper.get_or_create_vmo(&fs, &empty_node).unwrap();
369 assert_eq!(empty_vmo.get_size().unwrap(), 0);
370 assert!(!empty_vmo.basic_info().unwrap().rights.contains(zx::Rights::WRITE));
371 assert!(created_vmos.lock().is_empty());
372 assert!(!mapper.vmos_by_nid.lock().contains_key(&40));
373
374 let Node::File(plain_node) = fs.node(1).unwrap() else { panic!() };
376 let plain_vmo1 = mapper.get_or_create_vmo(&fs, &plain_node).unwrap();
377 let plain_vmo2 = mapper.get_or_create_vmo(&fs, &plain_node).unwrap();
378 assert_eq!(plain_vmo1.koid().unwrap(), plain_vmo2.koid().unwrap());
379 assert!(mapper.vmos_by_nid.lock().contains_key(&1));
380 assert!(!plain_vmo1.basic_info().unwrap().rights.contains(zx::Rights::WRITE));
381 assert_eq!(*created_vmos.lock(), vec![(1, 5000)]);
382
383 let msg = receiver.claim().unwrap();
384 let MappingCommand::Mappings {
385 key, offset, stored_size, device_offset, extent_count, ..
386 } = MappingCommand::try_from(*msg).unwrap()
387 else {
388 panic!("expected Mappings command");
389 };
390 assert_eq!(key, 1);
391 assert_eq!(stored_size, 5000);
392 assert_eq!(device_offset, file_offset);
393 let slice = msg.payload_slice(offset, extent_count * 8);
394 let mut words = vec![0u64; extent_count as usize];
395 for (i, word) in words.iter_mut().enumerate() {
396 let mut bytes = [0u8; 8];
397 slice.subslice(i * 8..(i + 1) * 8).copy_to_slice(&mut bytes);
398 *word = u64::from_le_bytes(bytes);
399 }
400 let decoded = Extents::from_encoded(words, device_offset).unwrap();
401 assert_eq!(
402 decoded,
403 Extents::try_new(
404 &[Extent::new(0..8192, DeviceExtent::Block(file_offset + 2 * 4096))],
405 device_offset
406 )
407 .unwrap()
408 );
409 msg.pop().unwrap();
410
411 let Node::File(inline_node) = fs.node(2).unwrap() else { panic!() };
413 let _inline_vmo = mapper.get_or_create_vmo(&fs, &inline_node).unwrap();
414 assert_eq!(*created_vmos.lock(), vec![(1, 5000), (2, 500)]);
415
416 let msg = receiver.claim().unwrap();
417 let MappingCommand::Mappings {
418 key, offset, stored_size, device_offset, extent_count, ..
419 } = MappingCommand::try_from(*msg).unwrap()
420 else {
421 panic!("expected Mappings command");
422 };
423 assert_eq!(key, 2);
424 assert_eq!(stored_size, 500);
425 let slice = msg.payload_slice(offset, extent_count * 8);
426 let mut words = vec![0u64; extent_count as usize];
427 for (i, word) in words.iter_mut().enumerate() {
428 let mut bytes = [0u8; 8];
429 slice.subslice(i * 8..(i + 1) * 8).copy_to_slice(&mut bytes);
430 *word = u64::from_le_bytes(bytes);
431 }
432 let decoded = Extents::from_encoded(words, device_offset).unwrap();
433 assert_eq!(
434 decoded,
435 Extents::try_new(
436 &[Extent::new(0..4096, DeviceExtent::InlineTail(file_offset + 4192))],
437 device_offset
438 )
439 .unwrap()
440 );
441 msg.pop().unwrap();
442
443 let Node::File(multi_node) = fs.node(20).unwrap() else { panic!() };
445 let _multi_vmo = mapper.get_or_create_vmo(&fs, &multi_node).unwrap();
446 assert_eq!(*created_vmos.lock(), vec![(1, 5000), (2, 500), (3, 4396)]);
447
448 let msg = receiver.claim().unwrap();
449 let MappingCommand::Mappings {
450 key, offset, stored_size, device_offset, extent_count, ..
451 } = MappingCommand::try_from(*msg).unwrap()
452 else {
453 panic!("expected Mappings command");
454 };
455 assert_eq!(key, 3);
456 assert_eq!(stored_size, 4396);
457 let slice = msg.payload_slice(offset, extent_count * 8);
458 let mut words = vec![0u64; extent_count as usize];
459 for (i, word) in words.iter_mut().enumerate() {
460 let mut bytes = [0u8; 8];
461 slice.subslice(i * 8..(i + 1) * 8).copy_to_slice(&mut bytes);
462 *word = u64::from_le_bytes(bytes);
463 }
464 let decoded = Extents::from_encoded(words, device_offset).unwrap();
465 assert_eq!(
466 decoded,
467 Extents::try_new(
468 &[
469 Extent::new(0..4096, DeviceExtent::Block(file_offset + 4 * 4096)),
470 Extent::new(4096..8192, DeviceExtent::InlineTail(file_offset + 4768)),
471 ],
472 device_offset
473 )
474 .unwrap()
475 );
476 msg.pop().unwrap();
477 }
478}