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