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