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