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