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