1use crate::fs::fuchsia::update_info_from_attrs;
6use crate::mm::memory::MemoryObject;
7use crate::mm::{ProtectionFlags, VMEX_RESOURCE};
8use crate::task::{CurrentTask, EventHandler, Kernel, WaitCanceler, Waiter};
9use crate::vfs::fs_args::MountParams;
10use crate::vfs::{
11 CacheConfig, CacheMode, DEFAULT_BYTES_PER_BLOCK, DirEntry, DirectoryEntryType, DirentSink,
12 FileObject, FileOps, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions, FileToken,
13 FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString, InputBuffer, OutputBuffer,
14 SeekTarget, StatxFlags, SymlinkTarget, ValueOrSize, default_seek, emit_dotdot,
15 fileops_impl_directory, fileops_impl_noop_sync, fileops_impl_seekable,
16 fs_node_impl_dir_readonly, fs_node_impl_not_dir, fs_node_impl_symlink,
17};
18use anyhow::{Error, anyhow};
19use ext4_metadata::{Metadata, Node, NodeInfo};
20use fidl_fuchsia_io as fio;
21use once_cell::sync::OnceCell;
22use starnix_logging::{impossible_error, log_warn, track_stub};
23use starnix_sync::{
24 DynamicLockDepRwLock, LockDepMutex, LockDepReadGuard, LockDepWriteGuard, RemoteBundleInnerLock,
25};
26use starnix_types::vfs::default_statfs;
27use starnix_uapi::auth::FsCred;
28use starnix_uapi::errors::{Errno, SourceContext};
29use starnix_uapi::file_mode::FileMode;
30use starnix_uapi::mount_flags::FileSystemFlags;
31use starnix_uapi::open_flags::OpenFlags;
32use starnix_uapi::vfs::FdEvents;
33use starnix_uapi::{errno, error, from_status_like_fdio, off_t, statfs};
34use std::io::Read;
35use std::sync::Arc;
36use std::sync::atomic::Ordering;
37use syncio::{zxio_node_attr_has_t, zxio_node_attributes_t};
38
39const REMOTE_BUNDLE_NODE_LRU_CAPACITY: usize = 1024;
40
41pub struct RemoteBundle {
46 metadata: Metadata,
47 root: fio::DirectorySynchronousProxy,
48 rights: fio::Flags,
49}
50
51impl RemoteBundle {
52 pub fn new_fs(
54 current_task: &CurrentTask,
55 mut options: FileSystemOptions,
56 ) -> Result<FileSystemHandle, Errno> {
57 let kernel = current_task.kernel();
58 let requested_path = std::str::from_utf8(&options.source)
59 .map_err(|_| errno!(EINVAL, "source path is not utf8"))?;
60 let (root_proxy, subdir) =
61 kernel.open_ns_dir(requested_path, fio::Flags::PROTOCOL_DIRECTORY)?;
62 options.source = subdir.into();
63 Self::new_fs_in_base(
64 current_task.kernel(),
65 &root_proxy,
66 options,
67 fio::PERM_READABLE | fio::PERM_EXECUTABLE,
68 )
69 .map_err(|e| errno!(EIO, format!("failed to mount remote bundle: {e}")))
70 }
71
72 pub fn new_fs_in_base(
74 kernel: &Kernel,
75 base: &fio::DirectorySynchronousProxy,
76 options: FileSystemOptions,
77 rights: fio::Flags,
78 ) -> Result<FileSystemHandle, Error> {
79 let (root, server_end) = fidl::endpoints::create_sync_proxy::<fio::DirectoryMarker>();
80 let path =
81 std::str::from_utf8(&options.source).map_err(|_| anyhow!("Source path is not utf8"))?;
82 base.open(path, rights, &Default::default(), server_end.into_channel())
83 .map_err(|e| anyhow!("Failed to open root: {}", e))?;
84
85 let metadata = {
86 let (file, server_end) = fidl::endpoints::create_endpoints::<fio::FileMarker>();
87 root.open(
88 "metadata.v1",
89 fio::PERM_READABLE,
90 &Default::default(),
91 server_end.into_channel(),
92 )
93 .source_context("open metadata file")?;
94 let mut file: std::fs::File = fdio::create_fd(file.into_channel().into_handle())
95 .source_context("create fd from metadata file (wrong mount path?)")?
96 .into();
97 let mut buf = Vec::new();
98 file.read_to_end(&mut buf).source_context("read metadata file")?;
99 Metadata::deserialize(&buf).source_context("deserialize metadata file")?
100 };
101
102 let root_node = metadata
104 .get(ext4_metadata::ROOT_INODE_NUM)
105 .ok_or_else(|| anyhow!("Root node does not exist in remote bundle"))?;
106 let root_info = to_fs_node_info(root_node);
107
108 if !rights.contains(fio::PERM_WRITABLE) {
109 options.flags.fetch_or(FileSystemFlags::RDONLY, Ordering::Relaxed);
110 }
111
112 let fs = FileSystem::new(
113 kernel,
114 CacheMode::Cached(CacheConfig { capacity: REMOTE_BUNDLE_NODE_LRU_CAPACITY }),
115 RemoteBundle { metadata, root, rights },
116 options,
117 )?;
118 fs.create_root_with_info(ext4_metadata::ROOT_INODE_NUM, DirectoryObject, root_info);
119 Ok(fs)
120 }
121
122 fn from_fs(fs: &FileSystem) -> &RemoteBundle {
125 fs.downcast_ops::<RemoteBundle>().unwrap()
126 }
127
128 fn get_node(&self, inode_num: u64) -> &Node {
132 self.metadata.get(inode_num).unwrap()
133 }
134
135 fn get_xattr(&self, node: &FsNode, name: &FsStr) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
136 let value = &self
137 .get_node(node.ino)
138 .extended_attributes
139 .get(&**name)
140 .ok_or_else(|| errno!(ENODATA))?[..];
141 Ok(Box::<[u8]>::from(&**value).into())
142 }
143
144 fn list_xattrs(&self, node: &FsNode) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
145 Ok(self
146 .get_node(node.ino)
147 .extended_attributes
148 .keys()
149 .map(|k| FsString::from(&k[..]))
150 .collect::<Vec<_>>()
151 .into())
152 }
153}
154
155impl FileSystemOps for RemoteBundle {
156 fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
157 const REMOTE_BUNDLE_FS_MAGIC: u32 = u32::from_be_bytes(*b"bndl");
158 let mut stats = default_statfs(REMOTE_BUNDLE_FS_MAGIC);
159 track_stub!(TODO("https://fxbug.dev/540853536"), "RemoteBundle statfs block count");
163 stats.f_blocks = 100000;
164 Ok(stats)
165 }
166 fn name(&self) -> &'static FsStr {
167 "remote_bundle".into()
168 }
169 fn reconfigure(
170 &self,
171 fs: &FileSystem,
172 _current_task: &CurrentTask,
173 flags: FileSystemFlags,
174 params: &MountParams,
175 ) -> Result<(), Errno> {
176 if !params.is_empty() {
177 track_stub!(TODO("https://fxbug.dev/322875506"), "MS_REMOUNT: Updating data");
178 }
179 fs.options.flags.store(flags | FileSystemFlags::RDONLY, Ordering::Relaxed);
180 Ok(())
181 }
182}
183
184struct File {
185 inner: LockDepMutex<Inner, RemoteBundleInnerLock>,
186 exec_memory: OnceCell<Arc<MemoryObject>>,
187}
188
189enum Inner {
190 NeedsVmo(fio::FileSynchronousProxy),
191 Memory(Arc<MemoryObject>),
192}
193
194impl Inner {
195 fn get_memory(&mut self) -> Result<Arc<MemoryObject>, Errno> {
196 if let Inner::NeedsVmo(file) = &*self {
197 let memory = Arc::new(MemoryObject::from(
198 file.get_backing_memory(fio::VmoFlags::READ, zx::MonotonicInstant::INFINITE)
199 .map_err(|err| errno!(EIO, format!("Error {err} on GetBackingMemory")))?
200 .map_err(|s| from_status_like_fdio!(zx::Status::err_from_raw(s)))?,
201 ));
202 *self = Inner::Memory(memory);
203 }
204 let Inner::Memory(memory) = &*self else { unreachable!() };
205 Ok(memory.clone())
206 }
207}
208
209impl FsNodeOps for File {
210 fs_node_impl_not_dir!();
211
212 fn create_file_ops(
213 &self,
214 _node: &FsNode,
215 _current_task: &CurrentTask,
216 _flags: OpenFlags,
217 ) -> Result<Box<dyn FileOps>, Errno> {
218 let memory = self.inner.lock().get_memory()?;
219 let size = usize::try_from(memory.get_content_size()).unwrap();
220 Ok(Box::new(MemoryFile { memory, size }))
221 }
222
223 fn fetch_and_refresh_info<'a>(
224 &self,
225 _node: &FsNode,
226 _current_task: &CurrentTask,
227 info: &'a DynamicLockDepRwLock<FsNodeInfo>,
228 flags: StatxFlags,
229 ) -> Result<LockDepReadGuard<'a, FsNodeInfo>, Errno> {
230 if flags.contains(StatxFlags::AT_STATX_DONT_SYNC) {
231 return Ok(info.read());
232 }
233 let memory = self.inner.lock().get_memory()?;
234 let content_size = memory.get_content_size();
235 let attrs = zxio_node_attributes_t {
236 content_size: content_size,
237 storage_size: content_size,
239 link_count: 1,
240 has: zxio_node_attr_has_t {
241 content_size: true,
242 storage_size: true,
243 link_count: true,
244 ..Default::default()
245 },
246 ..Default::default()
247 };
248 let mut info = info.write();
249 update_info_from_attrs(&mut info, &attrs);
250 Ok(LockDepWriteGuard::downgrade(info))
251 }
252
253 fn get_xattr(
254 &self,
255 node: &FsNode,
256 _current_task: &CurrentTask,
257 name: &FsStr,
258 _size: usize,
259 ) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
260 let fs = node.fs();
261 let bundle = RemoteBundle::from_fs(&fs);
262 bundle.get_xattr(node, name)
263 }
264
265 fn list_xattrs(
266 &self,
267 node: &FsNode,
268 _current_task: &CurrentTask,
269 _size: usize,
270 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
271 let fs = node.fs();
272 let bundle = RemoteBundle::from_fs(&fs);
273 bundle.list_xattrs(node)
274 }
275}
276
277struct MemoryFile {
288 memory: Arc<MemoryObject>,
289 size: usize,
290}
291
292impl FileOps for MemoryFile {
293 fileops_impl_seekable!();
294 fileops_impl_noop_sync!();
295
296 fn read(
297 &self,
298 _file: &FileObject,
299 _current_task: &CurrentTask,
300 mut offset: usize,
301 data: &mut dyn OutputBuffer,
302 ) -> Result<usize, Errno> {
303 data.write_each(&mut |buf| {
304 let buflen = buf.len();
305 let buf = &mut buf[..std::cmp::min(self.size.saturating_sub(offset), buflen)];
306 if !buf.is_empty() {
307 self.memory
308 .read_uninit(buf, offset as u64)
309 .map_err(|status| from_status_like_fdio!(status))?;
310 offset += buf.len();
311 }
312 Ok(buf.len())
313 })
314 }
315
316 fn write(
317 &self,
318 _file: &FileObject,
319 _current_task: &CurrentTask,
320 _offset: usize,
321 _data: &mut dyn InputBuffer,
322 ) -> Result<usize, Errno> {
323 error!(EPERM)
324 }
325
326 fn get_memory(
327 &self,
328 file: &FileObject,
329 _current_task: &CurrentTask,
330 _length: Option<usize>,
331 prot: ProtectionFlags,
332 ) -> Result<Arc<MemoryObject>, Errno> {
333 if prot.contains(ProtectionFlags::EXEC) {
334 let file_node = file.node().downcast_ops::<File>().unwrap();
335 file_node
336 .exec_memory
337 .get_or_try_init(|| {
338 self.memory
339 .duplicate_handle(zx::Rights::SAME_RIGHTS)
340 .and_then(|m| m.replace_as_executable(&VMEX_RESOURCE))
341 .map(Arc::new)
342 .map_err(impossible_error)
343 })
344 .cloned()
345 } else {
346 Ok(self.memory.clone())
347 }
348 }
349
350 fn get_file_token(
351 &self,
352 file: &FileObject,
353 _current_task: &CurrentTask,
354 ) -> Result<FileToken, Errno> {
355 let fs = file.node().fs();
356 let bundle = RemoteBundle::from_fs(&fs);
357 let ino = file.node().ino;
358 let (proxy, server_end) = fidl::endpoints::create_sync_proxy::<fio::FileMarker>();
359 bundle
360 .root
361 .open(&format!("{ino}"), bundle.rights, &Default::default(), server_end.into_channel())
362 .map_err(|_| errno!(EIO))?;
363 let token = proxy
364 .get_token(zx::MonotonicInstant::INFINITE)
365 .map_err(|err| errno!(EIO, format!("Error {err} on GetToken")))?
366 .map_err(|s| from_status_like_fdio!(zx::Status::err_from_raw(s)))?;
367 Ok(FileToken { token, offset: 0, keep_alive: proxy })
368 }
369
370 fn wait_async(
371 &self,
372 _file: &FileObject,
373 _current_task: &CurrentTask,
374 _waiter: &Waiter,
375 _events: FdEvents,
376 _handler: EventHandler,
377 ) -> Option<WaitCanceler> {
378 None
379 }
380
381 fn query_events(
382 &self,
383 _file: &FileObject,
384 _current_task: &CurrentTask,
385 ) -> Result<FdEvents, Errno> {
386 Ok(FdEvents::POLLIN)
387 }
388}
389
390struct DirectoryObject;
391
392impl FileOps for DirectoryObject {
393 fileops_impl_directory!();
394 fileops_impl_noop_sync!();
395
396 fn seek(
397 &self,
398 _file: &FileObject,
399 _current_task: &CurrentTask,
400 current_offset: off_t,
401 target: SeekTarget,
402 ) -> Result<off_t, Errno> {
403 default_seek(current_offset, target, || error!(EINVAL))
404 }
405
406 fn readdir(
407 &self,
408 file: &FileObject,
409 _current_task: &CurrentTask,
410 sink: &mut dyn DirentSink,
411 ) -> Result<(), Errno> {
412 emit_dotdot(file, sink)?;
413
414 let bundle = RemoteBundle::from_fs(&file.fs);
415 let child_iter = bundle
416 .get_node(file.node().ino)
417 .directory()
418 .ok_or_else(|| errno!(EIO))?
419 .children
420 .iter();
421
422 for (name, inode_num) in child_iter.skip(sink.offset() as usize - 2) {
423 let node = bundle.metadata.get(*inode_num).ok_or_else(|| errno!(EIO))?;
424 sink.add(
425 *inode_num,
426 sink.offset() + 1,
427 DirectoryEntryType::from_mode(FileMode::from_bits(node.mode.into())),
428 name.as_str().into(),
429 )?;
430 }
431
432 Ok(())
433 }
434}
435
436impl FsNodeOps for DirectoryObject {
437 fs_node_impl_dir_readonly!();
438
439 fn create_file_ops(
440 &self,
441 _node: &FsNode,
442 _current_task: &CurrentTask,
443 _flags: OpenFlags,
444 ) -> Result<Box<dyn FileOps>, Errno> {
445 Ok(Box::new(DirectoryObject))
446 }
447
448 fn lookup(
449 &self,
450 entry: &DirEntry,
451 _current_task: &CurrentTask,
452 name: &FsStr,
453 ) -> Result<FsNodeHandle, Errno> {
454 let name = std::str::from_utf8(name).map_err(|_| {
455 log_warn!("bad utf8 in pathname! remote filesystems can't handle this");
456 errno!(EINVAL)
457 })?;
458
459 let fs = entry.node.fs();
460 let bundle = RemoteBundle::from_fs(&fs);
461 let metadata = &bundle.metadata;
462 let ino = metadata
463 .lookup(entry.node.ino, name)
464 .map_err(|e| errno!(ENOENT, format!("Error: {e:?} opening {name}")))?;
465 let metadata_node = metadata.get(ino).ok_or_else(|| errno!(EIO))?;
466 let info = to_fs_node_info(metadata_node);
467
468 match metadata_node.info() {
469 NodeInfo::Symlink(_) => Ok(fs.create_node(ino, SymlinkObject, info)),
470 NodeInfo::Directory(_) => Ok(fs.create_node(ino, DirectoryObject, info)),
471 NodeInfo::File(_) => {
472 let (file, server_end) = fidl::endpoints::create_sync_proxy::<fio::FileMarker>();
473 bundle
474 .root
475 .open(
476 &format!("{ino}"),
477 bundle.rights,
478 &Default::default(),
479 server_end.into_channel(),
480 )
481 .map_err(|_| errno!(EIO))?;
482 Ok(fs.create_node(
483 ino,
484 File { inner: Inner::NeedsVmo(file).into(), exec_memory: OnceCell::new() },
485 info,
486 ))
487 }
488 }
489 }
490
491 fn get_xattr(
492 &self,
493 node: &FsNode,
494 _current_task: &CurrentTask,
495 name: &FsStr,
496 _size: usize,
497 ) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
498 let fs = node.fs();
499 let bundle = RemoteBundle::from_fs(&fs);
500 bundle.get_xattr(node, name)
501 }
502
503 fn list_xattrs(
504 &self,
505 node: &FsNode,
506 _current_task: &CurrentTask,
507 _size: usize,
508 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
509 let fs = node.fs();
510 let bundle = RemoteBundle::from_fs(&fs);
511 bundle.list_xattrs(node)
512 }
513}
514
515struct SymlinkObject;
516
517impl FsNodeOps for SymlinkObject {
518 fs_node_impl_symlink!();
519
520 fn readlink(&self, node: &FsNode, _current_task: &CurrentTask) -> Result<SymlinkTarget, Errno> {
521 let fs = node.fs();
522 let bundle = RemoteBundle::from_fs(&fs);
523 let target = bundle.get_node(node.ino).symlink().ok_or_else(|| errno!(EIO))?.target.clone();
524 Ok(SymlinkTarget::Path(target.as_str().into()))
525 }
526
527 fn get_xattr(
528 &self,
529 node: &FsNode,
530 _current_task: &CurrentTask,
531 name: &FsStr,
532 _size: usize,
533 ) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
534 let fs = node.fs();
535 let bundle = RemoteBundle::from_fs(&fs);
536 bundle.get_xattr(node, name)
537 }
538
539 fn list_xattrs(
540 &self,
541 node: &FsNode,
542 _current_task: &CurrentTask,
543 _size: usize,
544 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
545 let fs = node.fs();
546 let bundle = RemoteBundle::from_fs(&fs);
547 bundle.list_xattrs(node)
548 }
549}
550
551fn to_fs_node_info(metadata_node: &ext4_metadata::Node) -> FsNodeInfo {
552 let mode = FileMode::from_bits(metadata_node.mode.into());
553 let owner = FsCred { uid: metadata_node.uid.into(), gid: metadata_node.gid.into() };
554 let mut info = FsNodeInfo::new(mode, owner);
555 info.size = 1;
559 info.blocks = 1;
560 info.blksize = DEFAULT_BYTES_PER_BLOCK;
561 info.link_count = 1;
562 info
563}
564
565#[cfg(test)]
566mod test {
567 use crate::fs::fuchsia::RemoteBundle;
568 use crate::mm::ProtectionFlags;
569 use crate::testing::spawn_kernel_and_run_with_pkgfs;
570 use crate::vfs::buffers::VecOutputBuffer;
571 use crate::vfs::{
572 DirectoryEntryType, DirentSink, FileSystemOptions, FsStr, LookupContext, Namespace,
573 SymlinkMode, SymlinkTarget,
574 };
575 use fidl_fuchsia_io as fio;
576 use starnix_uapi::errors::Errno;
577 use starnix_uapi::file_mode::FileMode;
578 use starnix_uapi::open_flags::OpenFlags;
579 use starnix_uapi::{ino_t, off_t};
580 use std::collections::{HashMap, HashSet};
581 use std::sync::Arc;
582 use zx;
583
584 #[::fuchsia::test]
585 async fn test_read_image() {
586 spawn_kernel_and_run_with_pkgfs(async |current_task| {
587 let kernel = current_task.kernel();
588 let rights = fio::PERM_READABLE | fio::PERM_EXECUTABLE;
589 let (server, client) = zx::Channel::create();
590 fdio::open("/pkg", rights, server).expect("failed to open /pkg");
591 let fs = RemoteBundle::new_fs_in_base(
592 &kernel,
593 &fio::DirectorySynchronousProxy::new(client),
594 FileSystemOptions { source: "data/test-image".into(), ..Default::default() },
595 rights,
596 )
597 .expect("new_fs failed");
598 let ns = Namespace::new(fs);
599 let root = ns.root();
600 {
601 let info = root.entry.node.info();
602 assert_eq!(info.mode, FileMode::from_bits(0o40755));
603 assert_eq!(info.uid, 49152); assert_eq!(info.gid, 0); }
606 let mut context = LookupContext::new(SymlinkMode::NoFollow);
607
608 let test_dir = root
609 .lookup_child(¤t_task, &mut context, "foo".into())
610 .expect("lookup failed");
611
612 let test_file_node = test_dir
613 .lookup_child(¤t_task, &mut context, "file".into())
614 .expect("lookup failed");
615 let test_file =
616 test_file_node.open(¤t_task, OpenFlags::RDONLY).expect("open failed");
617
618 let ro_mem1 = test_file
619 .get_memory(¤t_task, None, ProtectionFlags::READ)
620 .expect("get_memory READ failed");
621 let rx_mem1 = test_file
622 .get_memory(¤t_task, None, ProtectionFlags::READ | ProtectionFlags::EXEC)
623 .expect("get_memory READ|EXEC failed");
624 assert!(rx_mem1.get_rights().contains(zx::Rights::EXECUTE));
625
626 let test_file2 =
627 test_file_node.open(¤t_task, OpenFlags::RDONLY).expect("open 2 failed");
628 let ro_mem2 = test_file2
629 .get_memory(¤t_task, None, ProtectionFlags::READ)
630 .expect("get_memory READ 2 failed");
631 let rx_mem2 = test_file2
632 .get_memory(¤t_task, None, ProtectionFlags::READ | ProtectionFlags::EXEC)
633 .expect("get_memory READ|EXEC 2 failed");
634 assert!(Arc::ptr_eq(&ro_mem1, &ro_mem2));
635 assert!(Arc::ptr_eq(&rx_mem1, &rx_mem2));
636
637 let mut buffer = VecOutputBuffer::new(64);
638 assert_eq!(test_file.read(¤t_task, &mut buffer).expect("read failed"), 6);
639 let buffer: Vec<u8> = buffer.into();
640 assert_eq!(&buffer[..6], b"hello\n");
641
642 let file_token =
643 test_file.get_file_token(¤t_task).expect("get_file_token failed");
644 assert_eq!(file_token.offset, 0);
645
646 assert_eq!(
647 &*test_file
648 .node()
649 .get_xattr(¤t_task, &test_dir.mount, "user.a".into(), usize::MAX)
650 .expect("get_xattr failed")
651 .unwrap(),
652 b"apple"
653 );
654 assert_eq!(
655 &*test_file
656 .node()
657 .get_xattr(¤t_task, &test_dir.mount, "user.b".into(), usize::MAX)
658 .expect("get_xattr failed")
659 .unwrap(),
660 b"ball"
661 );
662 assert_eq!(
663 test_file
664 .node()
665 .list_xattrs(¤t_task, usize::MAX)
666 .expect("list_xattr failed")
667 .unwrap()
668 .into_iter()
669 .collect::<HashSet<_>>(),
670 ["user.a".into(), "user.b".into()].into_iter().collect::<HashSet<_>>(),
671 );
672
673 {
674 let info = test_file.node().info();
675 assert_eq!(info.mode, FileMode::from_bits(0o100640));
676 assert_eq!(info.uid, 49152); assert_eq!(info.gid, 24403); }
679
680 let test_symlink = test_dir
681 .lookup_child(¤t_task, &mut context, "symlink".into())
682 .expect("lookup failed");
683
684 if let SymlinkTarget::Path(target) =
685 test_symlink.readlink(¤t_task).expect("readlink failed")
686 {
687 assert_eq!(&target, "file");
688 } else {
689 panic!("unexpected symlink type");
690 }
691
692 let opened_dir = test_dir.open(¤t_task, OpenFlags::RDONLY).expect("open failed");
693
694 struct Sink {
695 offset: off_t,
696 entries: HashMap<Vec<u8>, (ino_t, DirectoryEntryType)>,
697 }
698
699 impl DirentSink for Sink {
700 fn add(
701 &mut self,
702 inode_num: ino_t,
703 offset: off_t,
704 entry_type: DirectoryEntryType,
705 name: &FsStr,
706 ) -> Result<(), Errno> {
707 assert_eq!(offset, self.offset + 1);
708 self.entries.insert(name.to_vec(), (inode_num, entry_type));
709 self.offset = offset;
710 Ok(())
711 }
712
713 fn offset(&self) -> off_t {
714 self.offset
715 }
716 }
717
718 let mut sink = Sink { offset: 0, entries: HashMap::new() };
719 opened_dir.readdir(¤t_task, &mut sink).expect("readdir failed");
720
721 assert_eq!(
722 sink.entries,
723 [
724 (b".".into(), (test_dir.entry.node.ino, DirectoryEntryType::DIR)),
725 (b"..".into(), (root.entry.node.ino, DirectoryEntryType::DIR)),
726 (b"file".into(), (test_file.node().ino, DirectoryEntryType::REG)),
727 (b"symlink".into(), (test_symlink.entry.node.ino, DirectoryEntryType::LNK))
728 ]
729 .into()
730 );
731 })
732 .await;
733 }
734}