1#![recursion_limit = "256"]
21
22use erofs::readers::VmoReader;
23use erofs::{
24 DirectoryEntry as ErofsDirEntry, DirectoryNode, ErofsFilesystem, FileNode, FileType, Node,
25 NodeInner, SymlinkNode, VolumeMapper,
26};
27use fidl_fuchsia_erofs::{ErofsMarker, ErofsServeRequest};
28use fidl_fuchsia_io as fio;
29use fidl_fuchsia_storage_mapping::FileMapperMarker;
30use fuchsia_component::client::connect_to_protocol_sync;
31use fuchsia_runtime::UtcInstant;
32use starnix_core::fs::fuchsia::RemoteFs;
33use starnix_core::mm::ProtectionFlags;
34use starnix_core::mm::memory::MemoryObject;
35use starnix_core::task::CurrentTask;
36use starnix_core::vfs::fs_args::MountParams;
37use starnix_core::vfs::{
38 CacheMode, DEFAULT_BYTES_PER_BLOCK, DirEntry, DirectoryEntryType, DirentSink, FileObject,
39 FileOps, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions, FsNode, FsNodeFlags,
40 FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString, MemoryRegularFile, SeekTarget,
41 SymlinkTarget, ValueOrSize, default_seek, emit_dotdot, fileops_impl_directory,
42 fileops_impl_noop_sync, fs_node_impl_dir_readonly, fs_node_impl_not_dir, fs_node_impl_symlink,
43};
44use starnix_logging::{impossible_error, log_error, log_info, log_warn};
45use starnix_types::vfs::default_statfs;
46use starnix_uapi::errors::Errno;
47use starnix_uapi::file_mode::FileMode;
48use starnix_uapi::mount_flags::FileSystemFlags;
49use starnix_uapi::open_flags::OpenFlags;
50use starnix_uapi::{
51 EROFS_SUPER_MAGIC_V1, errno, error, from_status_like_fdio, ino_t, off_t, statfs,
52};
53use std::sync::atomic::{AtomicBool, Ordering};
54use std::sync::{Arc, OnceLock};
55use zx::{MonotonicInstant, VmoChildOptions};
56
57static FILE_MAPPER_AVAILABLE: AtomicBool = AtomicBool::new(true);
65
66const DOT_DOTDOT_OFFSET: off_t = 2;
69
70const READDIR_BATCH_SIZE: usize = 16;
72
73struct ErofsFileSystem {
75 fs: Arc<ErofsFilesystem>,
77 mapper: VolumeMapper,
79}
80
81impl FileSystemOps for ErofsFileSystem {
82 fn name(&self) -> &'static FsStr {
83 "erofs".into()
84 }
85
86 fn uses_external_node_ids(&self) -> bool {
87 true
88 }
89
90 fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
91 let mut stat = default_statfs(EROFS_SUPER_MAGIC_V1);
92 let block_size = self.fs.block_size();
93 stat.f_bsize = block_size as i64;
94 stat.f_frsize = block_size as i64;
95 stat.f_blocks = (self.fs.total_bytes() / block_size).try_into().unwrap_or(i64::MAX);
97 stat.f_files = self.fs.total_inodes().try_into().unwrap_or(i64::MAX);
98 Ok(stat)
99 }
100
101 fn reconfigure(
102 &self,
103 fs: &FileSystem,
104 _current_task: &CurrentTask,
105 new_flags: FileSystemFlags,
106 _params: &MountParams,
107 ) -> Result<(), Errno> {
108 if !new_flags.contains(FileSystemFlags::RDONLY) {
109 return error!(EROFS);
110 }
111 fs.options.flags.store(new_flags, Ordering::Relaxed);
112 Ok(())
113 }
114}
115
116fn fs_node_info(node: &NodeInner, block_size: u64) -> FsNodeInfo {
118 let time = UtcInstant::from_nanos(node.mtime_ns() as i64);
121 let storage_size = node.storage_size(block_size);
122 FsNodeInfo {
123 mode: FileMode::from_bits(node.mode().into()),
124 link_count: node.link_count() as usize,
125 uid: node.uid(),
126 gid: node.gid(),
127 size: node.size() as usize,
128 blksize: DEFAULT_BYTES_PER_BLOCK,
130 blocks: storage_size.div_ceil(DEFAULT_BYTES_PER_BLOCK as u64) as usize,
131 time_status_change: time,
132 time_access: time,
133 time_modify: time,
134 ..Default::default()
135 }
136}
137
138fn get_node_xattr(
141 node: &FsNode,
142 erofs_node: &NodeInner,
143 name: &FsStr,
144) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
145 let fs = node.fs();
146 let fs_ops = fs.downcast_ops::<ErofsFileSystem>().unwrap();
147 match fs_ops.fs.get_xattr(erofs_node, name.as_ref()).map_err(|e| errno!(EIO, e))? {
148 Some(val) => Ok(val.into_boxed_slice().into()),
149 None => error!(ENODATA),
150 }
151}
152
153fn list_node_xattrs(
155 node: &FsNode,
156 erofs_node: &NodeInner,
157) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
158 let fs = node.fs();
159 let fs_ops = fs.downcast_ops::<ErofsFileSystem>().unwrap();
160 let names = fs_ops.fs.list_xattrs(erofs_node).map_err(|e| errno!(EIO, e))?;
161 Ok(names.into_iter().map(FsString::from).collect::<Vec<_>>().into())
162}
163
164struct ErofsDirectory {
166 node: DirectoryNode,
167}
168
169impl FsNodeOps for ErofsDirectory {
170 fs_node_impl_dir_readonly!();
171
172 fn create_file_ops(
173 &self,
174 _node: &FsNode,
175 _current_task: &CurrentTask,
176 _flags: OpenFlags,
177 ) -> Result<Box<dyn FileOps>, Errno> {
178 Ok(Box::new(ErofsDirFileObject { node: self.node.clone() }))
179 }
180
181 fn lookup(
182 &self,
183 entry: &DirEntry,
184 _current_task: &CurrentTask,
185 name: &FsStr,
186 ) -> Result<FsNodeHandle, Errno> {
187 let name_str = std::str::from_utf8(name.as_ref()).map_err(|_| errno!(ENOENT))?;
190 let fs = entry.node.fs();
191 let fs_ops = fs.downcast_ops::<ErofsFileSystem>().unwrap();
192 let child_node = fs_ops
193 .fs
194 .lookup(&self.node, name_str)
195 .map_err(|e| errno!(EIO, e))?
196 .ok_or_else(|| errno!(ENOENT, name))?;
197 let nid = child_node.nid() as ino_t;
200 fs.get_or_create_node(nid, || {
201 let info = fs_node_info(&child_node, fs_ops.fs.block_size());
202 let ops: Box<dyn FsNodeOps> = match child_node {
203 Node::Directory(dir) => Box::new(ErofsDirectory { node: dir }),
204 Node::File(file) => Box::new(ErofsFile { node: file, memory: OnceLock::new() }),
205 Node::Symlink(symlink) => {
206 Box::new(ErofsSymlink { node: symlink, target: OnceLock::new() })
207 }
208 };
209 Ok(FsNode::new_uncached(nid, ops, &fs, info, FsNodeFlags::empty()))
210 })
211 }
212
213 fn get_xattr(
214 &self,
215 node: &FsNode,
216 _current_task: &CurrentTask,
217 name: &FsStr,
218 _max_size: usize,
219 ) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
220 get_node_xattr(node, &self.node, name)
221 }
222
223 fn list_xattrs(
224 &self,
225 node: &FsNode,
226 _current_task: &CurrentTask,
227 _max_size: usize,
228 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
229 list_node_xattrs(node, &self.node)
230 }
231}
232
233struct ErofsDirFileObject {
235 node: DirectoryNode,
236}
237
238impl FileOps for ErofsDirFileObject {
239 fileops_impl_directory!();
240 fileops_impl_noop_sync!();
241
242 fn seek(
243 &self,
244 _file: &FileObject,
245 _current_task: &CurrentTask,
246 current_offset: off_t,
247 target: SeekTarget,
248 ) -> Result<off_t, Errno> {
249 default_seek(current_offset, target, || error!(EINVAL))
250 }
251
252 fn readdir(
253 &self,
254 file: &FileObject,
255 _current_task: &CurrentTask,
256 sink: &mut dyn DirentSink,
257 ) -> Result<(), Errno> {
258 emit_dotdot(file, sink)?;
262
263 let fs = file.node().fs();
264 let fs_ops = fs.downcast_ops::<ErofsFileSystem>().unwrap();
265 let mut entry_offset = (sink.offset() - DOT_DOTDOT_OFFSET) as usize;
266 let mut buffer = vec![ErofsDirEntry::default(); READDIR_BATCH_SIZE];
267
268 loop {
269 let filled = fs_ops
270 .fs
271 .read_directory(&self.node, entry_offset, &mut buffer)
272 .map_err(|e| errno!(EIO, e))?;
273 for i in 0..filled {
274 let entry = &buffer[i];
275 let next_entry_offset = entry_offset + i + 1;
276 if entry.name == "." || entry.name == ".." {
280 continue;
281 }
282 let entry_type = match entry.file_type {
283 FileType::RegFile => DirectoryEntryType::REG,
284 FileType::Dir => DirectoryEntryType::DIR,
285 FileType::ChrDev => DirectoryEntryType::CHR,
286 FileType::BlkDev => DirectoryEntryType::BLK,
287 FileType::Fifo => DirectoryEntryType::FIFO,
288 FileType::Sock => DirectoryEntryType::SOCK,
289 FileType::Symlink => DirectoryEntryType::LNK,
290 FileType::Unknown => DirectoryEntryType::UNKNOWN,
291 };
292 sink.add(
293 entry.nid as ino_t,
294 next_entry_offset as off_t + DOT_DOTDOT_OFFSET,
295 entry_type,
296 entry.name.as_bytes().into(),
297 )?;
298 }
299 if filled < buffer.len() {
300 break;
301 }
302 entry_offset += filled;
303 }
304 Ok(())
305 }
306}
307
308struct ErofsFile {
310 node: FileNode,
311 memory: OnceLock<Arc<MemoryObject>>,
314}
315
316impl FsNodeOps for ErofsFile {
317 fs_node_impl_not_dir!();
318
319 fn create_file_ops(
320 &self,
321 node: &FsNode,
322 _current_task: &CurrentTask,
323 flags: OpenFlags,
324 ) -> Result<Box<dyn FileOps>, Errno> {
325 if flags.can_write() {
326 return error!(EROFS);
327 }
328 let memory = if let Some(memory) = self.memory.get() {
329 memory.clone()
330 } else {
331 let fs = node.fs();
332 let fs_ops = fs.downcast_ops::<ErofsFileSystem>().unwrap();
333 let vmo = fs_ops
334 .mapper
335 .get_or_create_vmo(&fs_ops.fs, &self.node)
336 .map_err(|status| from_status_like_fdio!(status))?;
337 let memory = Arc::new(MemoryObject::from(vmo).with_zx_name(b"starnix:erofs"));
338 let _ = self.memory.set(memory);
340 self.memory.get().unwrap().clone()
341 };
342 Ok(Box::new(MemoryRegularFile::new(memory)))
343 }
344
345 fn get_xattr(
346 &self,
347 node: &FsNode,
348 _current_task: &CurrentTask,
349 name: &FsStr,
350 _max_size: usize,
351 ) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
352 get_node_xattr(node, &self.node, name)
353 }
354
355 fn list_xattrs(
356 &self,
357 node: &FsNode,
358 _current_task: &CurrentTask,
359 _max_size: usize,
360 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
361 list_node_xattrs(node, &self.node)
362 }
363}
364
365struct ErofsSymlink {
367 node: SymlinkNode,
368 target: OnceLock<FsString>,
370}
371
372impl FsNodeOps for ErofsSymlink {
373 fs_node_impl_symlink!();
374
375 fn readlink(&self, node: &FsNode, _current_task: &CurrentTask) -> Result<SymlinkTarget, Errno> {
376 if let Some(target) = self.target.get() {
377 return Ok(SymlinkTarget::Path(target.clone()));
378 }
379 let fs = node.fs();
380 let fs_ops = fs.downcast_ops::<ErofsFileSystem>().unwrap();
381 let target: FsString =
382 fs_ops.fs.read_symlink(&self.node).map_err(|e| errno!(EIO, e))?.into();
383 let _ = self.target.set(target.clone());
384 Ok(SymlinkTarget::Path(target))
385 }
386
387 fn get_xattr(
388 &self,
389 node: &FsNode,
390 _current_task: &CurrentTask,
391 name: &FsStr,
392 _max_size: usize,
393 ) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
394 get_node_xattr(node, &self.node, name)
395 }
396
397 fn list_xattrs(
398 &self,
399 node: &FsNode,
400 _current_task: &CurrentTask,
401 _max_size: usize,
402 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
403 list_node_xattrs(node, &self.node)
404 }
405}
406
407pub fn new_fs(
411 current_task: &CurrentTask,
412 options: FileSystemOptions,
413) -> Result<FileSystemHandle, Errno> {
414 let kernel = current_task.kernel();
415
416 if !options.flags.load(Ordering::Relaxed).contains(FileSystemFlags::RDONLY) {
418 return Err(errno!(EROFS));
419 }
420
421 let fsoffset = options.params.get_as::<u64>(b"fsoffset")?;
422 let page_size = zx::system_get_page_size() as u64;
423
424 let source_device = current_task.open_file(options.source.as_ref(), OpenFlags::RDONLY)?;
425 let memory = source_device.get_memory(current_task, None, ProtectionFlags::READ)?;
426 let backing_vmo = memory
427 .as_vmo()
428 .ok_or_else(|| errno!(EINVAL))?
429 .duplicate_handle(zx::Rights::SAME_RIGHTS)
430 .map_err(impossible_error)?;
431
432 let backing_vmo = match fsoffset {
436 None => {
437 log_info!("mounting erofs image source={:?}", options.source);
438 backing_vmo
439 }
440 Some(fsoffset) if fsoffset > 0 => {
441 if fsoffset % page_size != 0 {
442 log_warn!("fsoffset {} is not page-aligned (page size {})", fsoffset, page_size);
443 return Err(errno!(EINVAL));
444 }
445 let vmo_size = backing_vmo.get_size().map_err(impossible_error)?;
446 let clone_len =
447 vmo_size.checked_sub(fsoffset).filter(|&len| len > 0).ok_or_else(|| {
448 log_warn!("fsoffset {} exceeds or equals vmo size {}", fsoffset, vmo_size);
449 errno!(EINVAL)
450 })?;
451 log_info!("mounting erofs image with fsoffset={}, len={}", fsoffset, clone_len);
452 backing_vmo.create_child(VmoChildOptions::SLICE, fsoffset, clone_len).map_err(|e| {
453 log_error!("Failed to create child VMO slice for fsoffset {}: {:?}", fsoffset, e);
454 errno!(EINVAL)
455 })?
456 }
457 Some(_) => {
458 log_info!("mounting erofs image with fsoffset of zero");
459 backing_vmo
460 }
461 };
462
463 if FILE_MAPPER_AVAILABLE.load(Ordering::Relaxed) {
467 if let Ok(file_token) = source_device
468 .get_file_token(current_task)
469 .and_then(|t| t.add_offset(fsoffset.unwrap_or(0)))
470 .ok()
471 .filter(|t| t.offset % page_size == 0)
472 .ok_or(())
473 {
474 if let Ok(file_mapper) = connect_to_protocol_sync::<FileMapperMarker>() {
475 let token = file_token.token;
476 let file_offset = file_token.offset;
477 let keep_alive = file_token.keep_alive;
478 let mapper_res =
479 VolumeMapper::open_with_session(file_offset, |mapping_vmo, session_server| {
480 file_mapper
481 .open_session(
482 token,
483 mapping_vmo,
484 session_server,
485 MonotonicInstant::INFINITE,
486 )
487 .map_err(|_| {
488 FILE_MAPPER_AVAILABLE.store(false, Ordering::Relaxed);
489 zx::Status::PEER_CLOSED
490 })?
491 .map_err(zx::Status::err_from_raw)
492 });
493 drop(keep_alive);
497 if let Ok(mapper) = mapper_res {
498 let reader = Arc::new(
499 VmoReader::new(Arc::new(backing_vmo)).map_err(|e| errno!(EIO, e))?,
500 );
501 let erofs_fs =
502 Arc::new(ErofsFilesystem::new(reader).map_err(|e| errno!(EINVAL, e))?);
503 let root_node = erofs_fs.root_node();
504 let root_nid = root_node.nid() as ino_t;
505 let root_info = fs_node_info(&root_node, erofs_fs.block_size());
506 let fs_ops = ErofsFileSystem { fs: erofs_fs, mapper };
507 let fs = FileSystem::new(
508 kernel,
509 CacheMode::Cached(kernel.fs_cache_config()),
510 fs_ops,
511 options,
512 )?;
513 fs.create_root_with_info(
514 root_nid,
515 ErofsDirectory { node: root_node },
516 root_info,
517 );
518 return Ok(fs);
519 }
520 } else {
521 FILE_MAPPER_AVAILABLE.store(false, Ordering::Relaxed);
522 }
523 }
524 }
525
526 let erofs_provider = connect_to_protocol_sync::<ErofsMarker>().map_err(|_| errno!(ENOENT))?;
529
530 let (root_client, root_server) = fidl::endpoints::create_endpoints();
531 erofs_provider
532 .serve(
533 ErofsServeRequest {
534 backing_vmo: Some(backing_vmo),
535 root: Some(root_server),
536 ..Default::default()
537 },
538 MonotonicInstant::INFINITE,
539 )
540 .map_err(|e| {
541 log_error!("FIDL error on Erofs.Serve: {e:?}");
542 errno!(EIO)
543 })?
544 .map_err(|e| {
545 log_error!("Erofs.Serve failed: {e:?}");
546 from_status_like_fdio!(zx::Status::err_from_raw(e))
547 })?;
548
549 let (remotefs, root_node, info, node_id) =
550 RemoteFs::new(root_client.into_channel(), fio::PERM_READABLE, "erofs")?;
551
552 let fs =
553 FileSystem::new(kernel, CacheMode::Cached(kernel.fs_cache_config()), remotefs, options)?;
554
555 fs.create_root_with_info(node_id, root_node, info);
556
557 Ok(fs)
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563 use fidl_fuchsia_storage_block as fblock;
564 use futures::TryStreamExt as _;
565 use starnix_core::testing::spawn_kernel_and_run;
566 use starnix_core::vfs::buffers::VecOutputBuffer;
567 use starnix_core::vfs::{LookupContext, Namespace, NamespaceNode, SymlinkMode};
568 use std::sync::atomic::AtomicUsize;
569
570 struct TestDirentSink {
571 offset: off_t,
572 max_entries: Option<usize>,
573 entries: Vec<(String, DirectoryEntryType)>,
574 }
575
576 impl TestDirentSink {
577 fn new() -> Self {
578 Self { offset: 0, max_entries: None, entries: Vec::new() }
579 }
580
581 fn with_limit(offset: off_t, max_entries: usize) -> Self {
582 Self { offset, max_entries: Some(max_entries), entries: Vec::new() }
583 }
584 }
585
586 impl DirentSink for TestDirentSink {
587 fn add(
588 &mut self,
589 _inode_num: ino_t,
590 offset: off_t,
591 entry_type: DirectoryEntryType,
592 name: &FsStr,
593 ) -> Result<(), Errno> {
594 if let Some(max) = self.max_entries {
595 if self.entries.len() >= max {
596 return error!(ENOSPC);
597 }
598 }
599 self.offset = offset;
600 self.entries.push((String::from_utf8_lossy(name.as_ref()).into_owned(), entry_type));
601 Ok(())
602 }
603
604 fn offset(&self) -> off_t {
605 self.offset
606 }
607 }
608
609 fn mount_test_erofs(
610 current_task: &CurrentTask,
611 create_vmo_count: Arc<AtomicUsize>,
612 fail_create_vmo: bool,
613 ) -> (FileSystemHandle, Arc<Namespace>) {
614 mount_test_erofs_from_path(
615 current_task,
616 "/pkg/data/simple.erofs",
617 create_vmo_count,
618 fail_create_vmo,
619 )
620 }
621
622 fn mount_test_erofs_from_path(
623 current_task: &CurrentTask,
624 image_path: &str,
625 create_vmo_count: Arc<AtomicUsize>,
626 fail_create_vmo: bool,
627 ) -> (FileSystemHandle, Arc<Namespace>) {
628 let erofs_bytes = std::fs::read(image_path).expect("read erofs image");
629 let expected_file1 = std::fs::read("/pkg/data/simple/file1").expect("read file1");
630 let expected_photosynthesis =
631 std::fs::read("/pkg/data/simple/photosynthesis").expect("read photosynthesis");
632
633 let mapper = VolumeMapper::open_with_session(4096, move |_mapping_vmo, server_end| {
634 std::thread::spawn(move || {
635 let mut executor = fuchsia_async::LocalExecutor::default();
636 executor.run_singlethreaded(async move {
637 let mut stream = server_end.into_stream();
638 let mut next_key = 1u64;
639 while let Ok(Some(req)) = stream.try_next().await {
640 match req {
641 fblock::MapperSessionRequest::CreateVmo { size, responder, .. } => {
642 create_vmo_count.fetch_add(1, Ordering::Relaxed);
643 if fail_create_vmo {
644 let _ = responder.send(Err(zx::Status::IO.into_raw()));
645 continue;
646 }
647 let key = next_key;
648 next_key += 1;
649 let vmo = zx::Vmo::create(size).unwrap();
650 if size == expected_file1.len() as u64 {
651 vmo.write(&expected_file1, 0).unwrap();
652 } else if size == expected_photosynthesis.len() as u64 {
653 vmo.write(&expected_photosynthesis, 0).unwrap();
654 }
655 let _ = responder.send(Ok((key, vmo)));
656 }
657 fblock::MapperSessionRequest::CloseVmo { .. } => {}
658 fblock::MapperSessionRequest::Close { responder } => {
659 let _ = responder.send(Ok(()));
660 break;
661 }
662 _ => unreachable!(),
663 }
664 }
665 });
666 });
667 Ok(())
668 })
669 .expect("open_with_session");
670
671 let vmo = zx::Vmo::create(erofs_bytes.len() as u64).unwrap();
672 vmo.write(&erofs_bytes, 0).unwrap();
673 let reader = Arc::new(VmoReader::new(Arc::new(vmo)).unwrap());
674 let erofs_fs = Arc::new(ErofsFilesystem::new(reader).unwrap());
675
676 let kernel = current_task.kernel();
677 let root_node = erofs_fs.root_node();
678 let root_nid = root_node.nid() as ino_t;
679 let root_info = fs_node_info(&root_node, erofs_fs.block_size());
680 let fs_ops = ErofsFileSystem { fs: erofs_fs, mapper };
681 let fs = FileSystem::new(
682 kernel,
683 CacheMode::Cached(kernel.fs_cache_config()),
684 fs_ops,
685 FileSystemOptions::default(),
686 )
687 .expect("FileSystem::new");
688 fs.create_root_with_info(root_nid, ErofsDirectory { node: root_node }, root_info);
689 let ns = Namespace::new(fs.clone());
690 (fs, ns)
691 }
692
693 fn lookup_node(
694 current_task: &CurrentTask,
695 parent: &NamespaceNode,
696 name: &[u8],
697 ) -> NamespaceNode {
698 let mut context = LookupContext::new(SymlinkMode::NoFollow);
699 parent.lookup_child(current_task, &mut context, name.into()).expect("lookup_child")
700 }
701
702 #[::fuchsia::test]
703 async fn test_statfs_and_node_metadata() {
704 spawn_kernel_and_run(async move |current_task| {
705 let expected_file1 = std::fs::read("/pkg/data/simple/file1").expect("read file1");
706 let (fs, ns) = mount_test_erofs(current_task, Arc::new(AtomicUsize::new(0)), false);
707
708 let stat = fs.statfs(current_task).expect("statfs");
709 assert_eq!(stat.f_type, EROFS_SUPER_MAGIC_V1 as i64);
710 assert_eq!(stat.f_bsize, 4096);
711 assert!(stat.f_blocks > 0);
712 assert!(stat.f_files > 0);
713
714 assert_eq!(
716 fs.reconfigure(current_task, FileSystemFlags::empty(), &MountParams::default()),
717 error!(EROFS)
718 );
719 assert!(
720 fs.reconfigure(current_task, FileSystemFlags::RDONLY, &MountParams::default())
721 .is_ok()
722 );
723
724 assert_eq!(
726 new_fs(current_task, FileSystemOptions::default()).map(|_| ()),
727 error!(EROFS)
728 );
729
730 let img_file = current_task
732 .open_file_at(
733 starnix_core::vfs::FdNumber::AT_FDCWD,
734 "/image.bin".into(),
735 OpenFlags::CREAT | OpenFlags::RDWR,
736 FileMode::from_bits(0o644),
737 starnix_uapi::vfs::ResolveFlags::empty(),
738 )
739 .expect("create /image.bin");
740 img_file
741 .node()
742 .truncate(current_task, ¤t_task.fs().root().mount, 8192)
743 .expect("truncate /image.bin");
744 for bad_param in [b"fsoffset=100".as_slice(), b"fsoffset=8192", b"fsoffset=16384"] {
745 let options = FileSystemOptions {
746 source: "/image.bin".into(),
747 flags: FileSystemFlags::RDONLY.into(),
748 params: MountParams::parse(bad_param.into()).expect("parse MountParams"),
749 };
750 assert_eq!(new_fs(current_task, options).map(|_| ()), error!(EINVAL));
751 }
752
753 let root = ns.root();
754 {
755 let root_info = root.entry.node.info();
756 assert!(root_info.mode.is_dir());
757 assert!(root_info.link_count >= 2);
758 }
759
760 let file1_entry = lookup_node(current_task, &root, b"file1");
761 {
762 let file1_info = file1_entry.entry.node.info();
763 assert!(file1_info.mode.is_reg());
764 assert_eq!(file1_info.size, expected_file1.len());
765 assert_eq!(file1_info.blksize, DEFAULT_BYTES_PER_BLOCK);
766 assert_eq!(
767 file1_info.blocks,
768 expected_file1.len().div_ceil(DEFAULT_BYTES_PER_BLOCK)
769 );
770 }
771
772 let symlink_entry = lookup_node(current_task, &root, b"symlink_to_file1");
773 assert!(symlink_entry.entry.node.info().mode.is_lnk());
774 let SymlinkTarget::Path(target) =
775 symlink_entry.readlink(current_task).expect("readlink")
776 else {
777 panic!("expected SymlinkTarget::Path");
778 };
779 assert_eq!(target, "file1");
780 })
781 .await;
782 }
783
784 #[::fuchsia::test]
785 async fn test_directory_readdir_pagination_and_seek() {
786 spawn_kernel_and_run(async move |current_task| {
787 let (_fs, ns) = mount_test_erofs(current_task, Arc::new(AtomicUsize::new(0)), false);
788 let root = ns.root();
789
790 let root_dir = root.open(current_task, OpenFlags::RDONLY).expect("open root");
792 let mut sink = TestDirentSink::new();
793 root_dir.readdir(current_task, &mut sink).expect("readdir root");
794 let names: Vec<&str> = sink.entries.iter().map(|(n, _)| n.as_str()).collect();
795 assert_eq!(
796 names,
797 vec![
798 ".",
799 "..",
800 "file1",
801 "large_dir",
802 "mixed_compression",
803 "photosynthesis",
804 "quantum",
805 "symlink_to_file1",
806 ]
807 );
808
809 assert_eq!(root_dir.seek(current_task, SeekTarget::Set(0)).unwrap(), 0);
811 assert_eq!(root_dir.seek(current_task, SeekTarget::Cur(3)).unwrap(), 3);
812 assert_eq!(root_dir.seek(current_task, SeekTarget::Set(-1)), error!(EINVAL));
813
814 let large_dir_node = lookup_node(current_task, &root, b"large_dir");
816 let large_dir =
817 large_dir_node.open(current_task, OpenFlags::RDONLY).expect("open large_dir");
818 let mut full_sink = TestDirentSink::new();
819 large_dir.readdir(current_task, &mut full_sink).expect("readdir large_dir");
820 assert!(
821 full_sink.entries.len() > 32,
822 "expected > 32 entries in large_dir, got {}",
823 full_sink.entries.len()
824 );
825 assert_eq!(full_sink.entries[0], (".".to_string(), DirectoryEntryType::DIR));
826 assert_eq!(full_sink.entries[1], ("..".to_string(), DirectoryEntryType::DIR));
827 for (name, kind) in &full_sink.entries[2..] {
828 assert!(name.starts_with("file_number_"), "unexpected entry: {name}");
829 assert_eq!(*kind, DirectoryEntryType::REG);
830 }
831
832 let mut paged_entries = Vec::new();
834 let mut offset = 0;
835 loop {
836 let mut page_sink = TestDirentSink::with_limit(offset, 5);
837 let res = large_dir.readdir(current_task, &mut page_sink);
838 let count = page_sink.entries.len();
839 offset = page_sink.offset();
840 paged_entries.extend(page_sink.entries);
841 if res.is_ok() {
842 break;
843 }
844 assert_eq!(res, error!(ENOSPC));
845 assert_eq!(count, 5);
846 }
847 assert_eq!(paged_entries, full_sink.entries);
848 })
849 .await;
850 }
851
852 #[::fuchsia::test]
853 async fn test_file_read_get_memory_and_caching() {
854 spawn_kernel_and_run(async move |current_task| {
855 let expected_file1 = std::fs::read("/pkg/data/simple/file1").expect("read file1");
856 let expected_photosynthesis =
857 std::fs::read("/pkg/data/simple/photosynthesis").expect("read photosynthesis");
858 let create_vmo_count = Arc::new(AtomicUsize::new(0));
859 let (_fs, ns) = mount_test_erofs(current_task, create_vmo_count.clone(), false);
860 let root = ns.root();
861
862 let file1_entry = lookup_node(current_task, &root, b"file1");
864 assert_eq!(create_vmo_count.load(Ordering::Relaxed), 0);
865
866 assert_eq!(file1_entry.open(current_task, OpenFlags::RDWR).unwrap_err(), errno!(EROFS));
868 assert_eq!(create_vmo_count.load(Ordering::Relaxed), 0);
869
870 let file1 = file1_entry.open(current_task, OpenFlags::RDONLY).expect("open file1");
872 assert_eq!(create_vmo_count.load(Ordering::Relaxed), 1);
873
874 let _file1_again =
876 file1_entry.open(current_task, OpenFlags::RDONLY).expect("re-open file1");
877 assert_eq!(create_vmo_count.load(Ordering::Relaxed), 1);
878
879 let mut buf = VecOutputBuffer::new(expected_file1.len() + 64);
880 let n = file1.read(current_task, &mut buf).expect("read file1");
881 assert_eq!(n, expected_file1.len());
882 assert_eq!(buf.data(), expected_file1.as_slice());
883
884 let mem_ro =
886 file1.get_memory(current_task, None, ProtectionFlags::READ).expect("get_memory RO");
887 assert_eq!(mem_ro.read_to_vec(0, expected_file1.len() as u64).unwrap(), expected_file1);
888 let mem_exec = file1
889 .get_memory(current_task, None, ProtectionFlags::READ | ProtectionFlags::EXEC)
890 .expect("get_memory EXEC");
891 assert!(mem_exec.get_rights().contains(zx::Rights::EXECUTE));
892 assert_eq!(
893 file1.get_memory(current_task, None, ProtectionFlags::WRITE).unwrap_err(),
894 errno!(EACCES)
895 );
896
897 let photo_file = lookup_node(current_task, &root, b"photosynthesis")
899 .open(current_task, OpenFlags::RDONLY)
900 .expect("open photosynthesis");
901 assert_eq!(create_vmo_count.load(Ordering::Relaxed), 2);
902 let mut buf = VecOutputBuffer::new(expected_photosynthesis.len() + 64);
903 let n = photo_file.read(current_task, &mut buf).expect("read photosynthesis");
904 assert_eq!(n, expected_photosynthesis.len());
905 assert_eq!(buf.data(), expected_photosynthesis.as_slice());
906
907 let expected_mixed = std::fs::read("/pkg/data/simple/mixed_compression")
911 .expect("read mixed_compression");
912 let lz4_vmo_count = Arc::new(AtomicUsize::new(0));
913 let (_lz4_fs, lz4_ns) = mount_test_erofs_from_path(
914 current_task,
915 "/pkg/data/simple_lz4.erofs",
916 lz4_vmo_count.clone(),
917 false,
918 );
919 let mixed_file = lookup_node(current_task, &lz4_ns.root(), b"mixed_compression")
920 .open(current_task, OpenFlags::RDONLY)
921 .expect("open mixed_compression");
922 assert_eq!(lz4_vmo_count.load(Ordering::Relaxed), 0);
923 let mut buf = VecOutputBuffer::new(expected_mixed.len() + 64);
924 let n = mixed_file.read(current_task, &mut buf).expect("read mixed_compression");
925 assert_eq!(n, expected_mixed.len());
926 assert_eq!(buf.data(), expected_mixed.as_slice());
927 })
928 .await;
929 }
930
931 #[::fuchsia::test]
932 async fn test_xattrs_and_error_paths() {
933 spawn_kernel_and_run(async move |current_task| {
934 let (_fs, ns) = mount_test_erofs(current_task, Arc::new(AtomicUsize::new(0)), false);
935 let root = ns.root();
936
937 let mut context = LookupContext::new(SymlinkMode::NoFollow);
939 assert_eq!(
940 root.lookup_child(current_task, &mut context, "does_not_exist".into()).unwrap_err(),
941 errno!(ENOENT)
942 );
943 let mut context = LookupContext::new(SymlinkMode::NoFollow);
944 assert_eq!(
945 root.lookup_child(current_task, &mut context, b"\xff\xfe".into()).unwrap_err(),
946 errno!(ENOENT)
947 );
948
949 let file1_node = &lookup_node(current_task, &root, b"file1").entry.node;
951 let ValueOrSize::Value(flavor) = file1_node
952 .ops()
953 .get_xattr(file1_node, current_task, "user.flavor".into(), usize::MAX)
954 .expect("get_xattr user.flavor")
955 else {
956 panic!("expected Value");
957 };
958 assert_eq!(&*flavor, b"vanilla");
959 assert_eq!(
960 file1_node
961 .ops()
962 .get_xattr(file1_node, current_task, "user.missing".into(), usize::MAX)
963 .unwrap_err(),
964 errno!(ENODATA)
965 );
966
967 let ValueOrSize::Value(mut xattr_list) = file1_node
968 .ops()
969 .list_xattrs(file1_node, current_task, usize::MAX)
970 .expect("list_xattrs")
971 else {
972 panic!("expected Value");
973 };
974 xattr_list.sort();
975 assert_eq!(
976 xattr_list,
977 vec![
978 FsString::from("security.selinux"),
979 FsString::from("user.flavor"),
980 FsString::from("user.security"),
981 FsString::from("user.shared"),
982 ]
983 );
984
985 let root_node = &root.entry.node;
987 assert!(root_node.ops().list_xattrs(root_node, current_task, usize::MAX).is_ok());
988 assert_eq!(
989 root_node
990 .ops()
991 .get_xattr(root_node, current_task, "user.missing".into(), usize::MAX)
992 .unwrap_err(),
993 errno!(ENODATA)
994 );
995
996 let symlink_node = &lookup_node(current_task, &root, b"symlink_to_file1").entry.node;
997 assert!(symlink_node.ops().list_xattrs(symlink_node, current_task, usize::MAX).is_ok());
998 assert_eq!(
999 symlink_node
1000 .ops()
1001 .get_xattr(symlink_node, current_task, "user.missing".into(), usize::MAX)
1002 .unwrap_err(),
1003 errno!(ENODATA)
1004 );
1005
1006 let (_bad_fs, bad_ns) =
1008 mount_test_erofs(current_task, Arc::new(AtomicUsize::new(0)), true);
1009 let bad_file1 = lookup_node(current_task, &bad_ns.root(), b"file1");
1010 assert_eq!(bad_file1.open(current_task, OpenFlags::RDONLY).unwrap_err(), errno!(EIO));
1011 })
1012 .await;
1013 }
1014}