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::MountFlags;
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 |= MountFlags::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
177struct File {
178 inner: Mutex<Inner>,
179}
180
181enum Inner {
182 NeedsVmo(fio::FileSynchronousProxy),
183 Memory(Arc<MemoryObject>),
184}
185
186impl Inner {
187 fn get_memory(&mut self) -> Result<Arc<MemoryObject>, Errno> {
188 if let Inner::NeedsVmo(file) = &*self {
189 let memory = Arc::new(MemoryObject::from(
190 file.get_backing_memory(fio::VmoFlags::READ, zx::MonotonicInstant::INFINITE)
191 .map_err(|err| errno!(EIO, format!("Error {err} on GetBackingMemory")))?
192 .map_err(|s| from_status_like_fdio!(zx::Status::from_raw(s)))?,
193 ));
194 *self = Inner::Memory(memory);
195 }
196 let Inner::Memory(memory) = &*self else { unreachable!() };
197 Ok(memory.clone())
198 }
199}
200
201impl FsNodeOps for File {
202 fs_node_impl_not_dir!();
203
204 fn create_file_ops(
205 &self,
206 _locked: &mut Locked<FileOpsCore>,
207 _node: &FsNode,
208 _current_task: &CurrentTask,
209 _flags: OpenFlags,
210 ) -> Result<Box<dyn FileOps>, Errno> {
211 let memory = self.inner.lock().get_memory()?;
212 let size = usize::try_from(memory.get_content_size()).unwrap();
213 Ok(Box::new(MemoryFile { memory, size }))
214 }
215
216 fn fetch_and_refresh_info<'a>(
217 &self,
218 _locked: &mut Locked<FileOpsCore>,
219 _node: &FsNode,
220 _current_task: &CurrentTask,
221 info: &'a RwLock<FsNodeInfo>,
222 ) -> Result<RwLockReadGuard<'a, FsNodeInfo>, Errno> {
223 let memory = self.inner.lock().get_memory()?;
224 let content_size = memory.get_content_size();
225 let attrs = zxio_node_attributes_t {
226 content_size: content_size,
227 storage_size: content_size,
229 link_count: 1,
230 has: zxio_node_attr_has_t {
231 content_size: true,
232 storage_size: true,
233 link_count: true,
234 ..Default::default()
235 },
236 ..Default::default()
237 };
238 let mut info = info.write();
239 update_info_from_attrs(&mut info, &attrs);
240 Ok(RwLockWriteGuard::downgrade(info))
241 }
242
243 fn get_xattr(
244 &self,
245 _locked: &mut Locked<FileOpsCore>,
246 node: &FsNode,
247 _current_task: &CurrentTask,
248 name: &FsStr,
249 _size: usize,
250 ) -> Result<ValueOrSize<FsString>, Errno> {
251 let fs = node.fs();
252 let bundle = RemoteBundle::from_fs(&fs);
253 bundle.get_xattr(node, name)
254 }
255
256 fn list_xattrs(
257 &self,
258 _locked: &mut Locked<FileOpsCore>,
259 node: &FsNode,
260 _current_task: &CurrentTask,
261 _size: usize,
262 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
263 let fs = node.fs();
264 let bundle = RemoteBundle::from_fs(&fs);
265 bundle.list_xattrs(node)
266 }
267}
268
269struct MemoryFile {
280 memory: Arc<MemoryObject>,
281 size: usize,
282}
283
284impl FileOps for MemoryFile {
285 fileops_impl_seekable!();
286 fileops_impl_noop_sync!();
287
288 fn read(
289 &self,
290 _locked: &mut Locked<FileOpsCore>,
291 _file: &FileObject,
292 _current_task: &CurrentTask,
293 mut offset: usize,
294 data: &mut dyn OutputBuffer,
295 ) -> Result<usize, Errno> {
296 data.write_each(&mut |buf| {
297 let buflen = buf.len();
298 let buf = &mut buf[..std::cmp::min(self.size.saturating_sub(offset), buflen)];
299 if !buf.is_empty() {
300 self.memory
301 .read_uninit(buf, offset as u64)
302 .map_err(|status| from_status_like_fdio!(status))?;
303 offset += buf.len();
304 }
305 Ok(buf.len())
306 })
307 }
308
309 fn write(
310 &self,
311 _locked: &mut Locked<FileOpsCore>,
312 _file: &FileObject,
313 _current_task: &CurrentTask,
314 _offset: usize,
315 _data: &mut dyn InputBuffer,
316 ) -> Result<usize, Errno> {
317 error!(EPERM)
318 }
319
320 fn get_memory(
321 &self,
322 _locked: &mut Locked<FileOpsCore>,
323 _file: &FileObject,
324 _current_task: &CurrentTask,
325 _length: Option<usize>,
326 prot: ProtectionFlags,
327 ) -> Result<Arc<MemoryObject>, Errno> {
328 Ok(if prot.contains(ProtectionFlags::EXEC) {
329 Arc::new(
330 self.memory
331 .duplicate_handle(zx::Rights::SAME_RIGHTS)
332 .map_err(impossible_error)?
333 .replace_as_executable(&VMEX_RESOURCE)
334 .map_err(impossible_error)?,
335 )
336 } else {
337 self.memory.clone()
338 })
339 }
340
341 fn wait_async(
342 &self,
343 _locked: &mut Locked<FileOpsCore>,
344 _file: &FileObject,
345 _current_task: &CurrentTask,
346 _waiter: &Waiter,
347 _events: FdEvents,
348 _handler: EventHandler,
349 ) -> Option<WaitCanceler> {
350 None
351 }
352
353 fn query_events(
354 &self,
355 _locked: &mut Locked<FileOpsCore>,
356 _file: &FileObject,
357 _current_task: &CurrentTask,
358 ) -> Result<FdEvents, Errno> {
359 Ok(FdEvents::POLLIN)
360 }
361}
362
363struct DirectoryObject;
364
365impl FileOps for DirectoryObject {
366 fileops_impl_directory!();
367 fileops_impl_noop_sync!();
368
369 fn seek(
370 &self,
371 _locked: &mut Locked<FileOpsCore>,
372 _file: &FileObject,
373 _current_task: &CurrentTask,
374 current_offset: off_t,
375 target: SeekTarget,
376 ) -> Result<off_t, Errno> {
377 default_seek(current_offset, target, || error!(EINVAL))
378 }
379
380 fn readdir(
381 &self,
382 _locked: &mut Locked<FileOpsCore>,
383 file: &FileObject,
384 _current_task: &CurrentTask,
385 sink: &mut dyn DirentSink,
386 ) -> Result<(), Errno> {
387 emit_dotdot(file, sink)?;
388
389 let bundle = RemoteBundle::from_fs(&file.fs);
390 let child_iter = bundle
391 .get_node(file.node().ino)
392 .directory()
393 .ok_or_else(|| errno!(EIO))?
394 .children
395 .iter();
396
397 for (name, inode_num) in child_iter.skip(sink.offset() as usize - 2) {
398 let node = bundle.metadata.get(*inode_num).ok_or_else(|| errno!(EIO))?;
399 sink.add(
400 *inode_num,
401 sink.offset() + 1,
402 DirectoryEntryType::from_mode(FileMode::from_bits(node.mode.into())),
403 name.as_str().into(),
404 )?;
405 }
406
407 Ok(())
408 }
409}
410
411impl FsNodeOps for DirectoryObject {
412 fs_node_impl_dir_readonly!();
413
414 fn create_file_ops(
415 &self,
416 _locked: &mut Locked<FileOpsCore>,
417 _node: &FsNode,
418 _current_task: &CurrentTask,
419 _flags: OpenFlags,
420 ) -> Result<Box<dyn FileOps>, Errno> {
421 Ok(Box::new(DirectoryObject))
422 }
423
424 fn lookup(
425 &self,
426 _locked: &mut Locked<FileOpsCore>,
427 node: &FsNode,
428 _current_task: &CurrentTask,
429 name: &FsStr,
430 ) -> Result<FsNodeHandle, Errno> {
431 let name = std::str::from_utf8(name).map_err(|_| {
432 log_warn!("bad utf8 in pathname! remote filesystems can't handle this");
433 errno!(EINVAL)
434 })?;
435
436 let fs = node.fs();
437 let bundle = RemoteBundle::from_fs(&fs);
438 let metadata = &bundle.metadata;
439 let ino = metadata
440 .lookup(node.ino, name)
441 .map_err(|e| errno!(ENOENT, format!("Error: {e:?} opening {name}")))?;
442 let metadata_node = metadata.get(ino).ok_or_else(|| errno!(EIO))?;
443 let info = to_fs_node_info(metadata_node);
444
445 match metadata_node.info() {
446 NodeInfo::Symlink(_) => Ok(fs.create_node(ino, SymlinkObject, info)),
447 NodeInfo::Directory(_) => Ok(fs.create_node(ino, DirectoryObject, info)),
448 NodeInfo::File(_) => {
449 let (file, server_end) = fidl::endpoints::create_sync_proxy::<fio::FileMarker>();
450 bundle
451 .root
452 .open(
453 &format!("{ino}"),
454 bundle.rights,
455 &Default::default(),
456 server_end.into_channel(),
457 )
458 .map_err(|_| errno!(EIO))?;
459 Ok(fs.create_node(ino, File { inner: Mutex::new(Inner::NeedsVmo(file)) }, info))
460 }
461 }
462 }
463
464 fn get_xattr(
465 &self,
466 _locked: &mut Locked<FileOpsCore>,
467 node: &FsNode,
468 _current_task: &CurrentTask,
469 name: &FsStr,
470 _size: usize,
471 ) -> Result<ValueOrSize<FsString>, Errno> {
472 let fs = node.fs();
473 let bundle = RemoteBundle::from_fs(&fs);
474 bundle.get_xattr(node, name)
475 }
476
477 fn list_xattrs(
478 &self,
479 _locked: &mut Locked<FileOpsCore>,
480 node: &FsNode,
481 _current_task: &CurrentTask,
482 _size: usize,
483 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
484 let fs = node.fs();
485 let bundle = RemoteBundle::from_fs(&fs);
486 bundle.list_xattrs(node)
487 }
488}
489
490struct SymlinkObject;
491
492impl FsNodeOps for SymlinkObject {
493 fs_node_impl_symlink!();
494
495 fn readlink(
496 &self,
497 _locked: &mut Locked<FileOpsCore>,
498 node: &FsNode,
499 _current_task: &CurrentTask,
500 ) -> Result<SymlinkTarget, Errno> {
501 let fs = node.fs();
502 let bundle = RemoteBundle::from_fs(&fs);
503 let target = bundle.get_node(node.ino).symlink().ok_or_else(|| errno!(EIO))?.target.clone();
504 Ok(SymlinkTarget::Path(target.as_str().into()))
505 }
506
507 fn get_xattr(
508 &self,
509 _locked: &mut Locked<FileOpsCore>,
510 node: &FsNode,
511 _current_task: &CurrentTask,
512 name: &FsStr,
513 _size: usize,
514 ) -> Result<ValueOrSize<FsString>, Errno> {
515 let fs = node.fs();
516 let bundle = RemoteBundle::from_fs(&fs);
517 bundle.get_xattr(node, name)
518 }
519
520 fn list_xattrs(
521 &self,
522 _locked: &mut Locked<FileOpsCore>,
523 node: &FsNode,
524 _current_task: &CurrentTask,
525 _size: usize,
526 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
527 let fs = node.fs();
528 let bundle = RemoteBundle::from_fs(&fs);
529 bundle.list_xattrs(node)
530 }
531}
532
533fn to_fs_node_info(metadata_node: &ext4_metadata::Node) -> FsNodeInfo {
534 let mode = FileMode::from_bits(metadata_node.mode.into());
535 let owner = FsCred { uid: metadata_node.uid.into(), gid: metadata_node.gid.into() };
536 let mut info = FsNodeInfo::new(mode, owner);
537 info.size = 1;
541 info.blocks = 1;
542 info.blksize = DEFAULT_BYTES_PER_BLOCK;
543 info.link_count = 1;
544 info
545}
546
547#[cfg(test)]
548mod test {
549 use crate::fs::fuchsia::RemoteBundle;
550 use crate::testing::spawn_kernel_and_run_with_pkgfs;
551 use crate::vfs::buffers::VecOutputBuffer;
552 use crate::vfs::{
553 DirectoryEntryType, DirentSink, FileSystemOptions, FsStr, LookupContext, Namespace,
554 SymlinkMode, SymlinkTarget,
555 };
556 use starnix_uapi::errors::Errno;
557 use starnix_uapi::file_mode::{AccessCheck, FileMode};
558 use starnix_uapi::open_flags::OpenFlags;
559 use starnix_uapi::{ino_t, off_t};
560 use std::collections::{HashMap, HashSet};
561 use {fidl_fuchsia_io as fio, zx};
562
563 #[::fuchsia::test]
564 async fn test_read_image() {
565 spawn_kernel_and_run_with_pkgfs(async |locked, current_task| {
566 let kernel = current_task.kernel();
567 let rights = fio::PERM_READABLE | fio::PERM_EXECUTABLE;
568 let (server, client) = zx::Channel::create();
569 fdio::open("/pkg", rights, server).expect("failed to open /pkg");
570 let fs = RemoteBundle::new_fs_in_base(
571 locked,
572 &kernel,
573 &fio::DirectorySynchronousProxy::new(client),
574 FileSystemOptions { source: "data/test-image".into(), ..Default::default() },
575 rights,
576 )
577 .expect("new_fs failed");
578 let ns = Namespace::new(fs);
579 let root = ns.root();
580 let mut context = LookupContext::default().with(SymlinkMode::NoFollow);
581
582 let test_dir = root
583 .lookup_child(locked, ¤t_task, &mut context, "foo".into())
584 .expect("lookup failed");
585
586 let test_file = test_dir
587 .lookup_child(locked, ¤t_task, &mut context, "file".into())
588 .expect("lookup failed")
589 .open(locked, ¤t_task, OpenFlags::RDONLY, AccessCheck::default())
590 .expect("open failed");
591
592 let mut buffer = VecOutputBuffer::new(64);
593 assert_eq!(test_file.read(locked, ¤t_task, &mut buffer).expect("read failed"), 6);
594 let buffer: Vec<u8> = buffer.into();
595 assert_eq!(&buffer[..6], b"hello\n");
596
597 assert_eq!(
598 &test_file
599 .node()
600 .get_xattr(locked, ¤t_task, &test_dir.mount, "user.a".into(), usize::MAX)
601 .expect("get_xattr failed")
602 .unwrap(),
603 "apple"
604 );
605 assert_eq!(
606 &test_file
607 .node()
608 .get_xattr(locked, ¤t_task, &test_dir.mount, "user.b".into(), usize::MAX)
609 .expect("get_xattr failed")
610 .unwrap(),
611 "ball"
612 );
613 assert_eq!(
614 test_file
615 .node()
616 .list_xattrs(locked, ¤t_task, usize::MAX)
617 .expect("list_xattr failed")
618 .unwrap()
619 .into_iter()
620 .collect::<HashSet<_>>(),
621 ["user.a".into(), "user.b".into()].into_iter().collect::<HashSet<_>>(),
622 );
623
624 {
625 let info = test_file.node().info();
626 assert_eq!(info.mode, FileMode::from_bits(0o100640));
627 assert_eq!(info.uid, 49152); assert_eq!(info.gid, 24403); }
630
631 let test_symlink = test_dir
632 .lookup_child(locked, ¤t_task, &mut context, "symlink".into())
633 .expect("lookup failed");
634
635 if let SymlinkTarget::Path(target) =
636 test_symlink.readlink(locked, ¤t_task).expect("readlink failed")
637 {
638 assert_eq!(&target, "file");
639 } else {
640 panic!("unexpected symlink type");
641 }
642
643 let opened_dir = test_dir
644 .open(locked, ¤t_task, OpenFlags::RDONLY, AccessCheck::default())
645 .expect("open failed");
646
647 struct Sink {
648 offset: off_t,
649 entries: HashMap<Vec<u8>, (ino_t, DirectoryEntryType)>,
650 }
651
652 impl DirentSink for Sink {
653 fn add(
654 &mut self,
655 inode_num: ino_t,
656 offset: off_t,
657 entry_type: DirectoryEntryType,
658 name: &FsStr,
659 ) -> Result<(), Errno> {
660 assert_eq!(offset, self.offset + 1);
661 self.entries.insert(name.to_vec(), (inode_num, entry_type));
662 self.offset = offset;
663 Ok(())
664 }
665
666 fn offset(&self) -> off_t {
667 self.offset
668 }
669 }
670
671 let mut sink = Sink { offset: 0, entries: HashMap::new() };
672 opened_dir.readdir(locked, ¤t_task, &mut sink).expect("readdir failed");
673
674 assert_eq!(
675 sink.entries,
676 [
677 (b".".into(), (test_dir.entry.node.ino, DirectoryEntryType::DIR)),
678 (b"..".into(), (root.entry.node.ino, DirectoryEntryType::DIR)),
679 (b"file".into(), (test_file.node().ino, DirectoryEntryType::REG)),
680 (b"symlink".into(), (test_symlink.entry.node.ino, DirectoryEntryType::LNK))
681 ]
682 .into()
683 );
684 })
685 .await;
686 }
687}