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