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,
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<L>(
54 locked: &mut Locked<L>,
55 kernel: &Kernel,
56 base: &fio::DirectorySynchronousProxy,
57 mut options: FileSystemOptions,
58 rights: fio::Flags,
59 ) -> Result<FileSystemHandle, Error>
60 where
61 L: LockEqualOrBefore<FileOpsCore>,
62 {
63 let (root, server_end) = fidl::endpoints::create_sync_proxy::<fio::DirectoryMarker>();
64 let path =
65 std::str::from_utf8(&options.source).map_err(|_| anyhow!("Source path is not utf8"))?;
66 base.open(path, rights, &Default::default(), server_end.into_channel())
67 .map_err(|e| anyhow!("Failed to open root: {}", e))?;
68
69 let metadata = {
70 let (file, server_end) = fidl::endpoints::create_endpoints::<fio::FileMarker>();
71 root.open(
72 "metadata.v1",
73 fio::PERM_READABLE,
74 &Default::default(),
75 server_end.into_channel(),
76 )
77 .source_context("open metadata file")?;
78 let mut file: std::fs::File = fdio::create_fd(file.into_channel().into_handle())
79 .source_context("create fd from metadata file (wrong mount path?)")?
80 .into();
81 let mut buf = Vec::new();
82 file.read_to_end(&mut buf).source_context("read metadata file")?;
83 Metadata::deserialize(&buf).source_context("deserialize metadata file")?
84 };
85
86 ensure!(
88 metadata.get(ext4_metadata::ROOT_INODE_NUM).is_some(),
89 "Root node does not exist in remote bundle"
90 );
91
92 if !rights.contains(fio::PERM_WRITABLE) {
93 options.flags |= MountFlags::RDONLY;
94 }
95
96 let fs = FileSystem::new(
97 locked,
98 kernel,
99 CacheMode::Cached(CacheConfig { capacity: REMOTE_BUNDLE_NODE_LRU_CAPACITY }),
100 RemoteBundle { metadata, root, rights },
101 options,
102 )?;
103 fs.create_root(ext4_metadata::ROOT_INODE_NUM, DirectoryObject);
104 Ok(fs)
105 }
106
107 fn from_fs(fs: &FileSystem) -> &RemoteBundle {
110 fs.downcast_ops::<RemoteBundle>().unwrap()
111 }
112
113 fn get_node(&self, inode_num: u64) -> &Node {
117 self.metadata.get(inode_num).unwrap()
118 }
119
120 fn get_xattr(&self, node: &FsNode, name: &FsStr) -> Result<ValueOrSize<FsString>, Errno> {
121 let value = &self
122 .get_node(node.ino)
123 .extended_attributes
124 .get(&**name)
125 .ok_or_else(|| errno!(ENODATA))?[..];
126 Ok(FsString::from(value).into())
127 }
128
129 fn list_xattrs(&self, node: &FsNode) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
130 Ok(self
131 .get_node(node.ino)
132 .extended_attributes
133 .keys()
134 .map(|k| FsString::from(&k[..]))
135 .collect::<Vec<_>>()
136 .into())
137 }
138}
139
140impl FileSystemOps for RemoteBundle {
141 fn statfs(
142 &self,
143 _locked: &mut Locked<FileOpsCore>,
144 _fs: &FileSystem,
145 _current_task: &CurrentTask,
146 ) -> Result<statfs, Errno> {
147 const REMOTE_BUNDLE_FS_MAGIC: u32 = u32::from_be_bytes(*b"bndl");
148 Ok(default_statfs(REMOTE_BUNDLE_FS_MAGIC))
149 }
150 fn name(&self) -> &'static FsStr {
151 "remote_bundle".into()
152 }
153}
154
155struct File {
156 inner: Mutex<Inner>,
157}
158
159enum Inner {
160 NeedsVmo(fio::FileSynchronousProxy),
161 Memory(Arc<MemoryObject>),
162}
163
164impl Inner {
165 fn get_memory(&mut self) -> Result<Arc<MemoryObject>, Errno> {
166 if let Inner::NeedsVmo(file) = &*self {
167 let memory = Arc::new(MemoryObject::from(
168 file.get_backing_memory(fio::VmoFlags::READ, zx::MonotonicInstant::INFINITE)
169 .map_err(|err| errno!(EIO, format!("Error {err} on GetBackingMemory")))?
170 .map_err(|s| from_status_like_fdio!(zx::Status::from_raw(s)))?,
171 ));
172 *self = Inner::Memory(memory);
173 }
174 let Inner::Memory(memory) = &*self else { unreachable!() };
175 Ok(memory.clone())
176 }
177}
178
179impl FsNodeOps for File {
180 fs_node_impl_not_dir!();
181
182 fn create_file_ops(
183 &self,
184 _locked: &mut Locked<FileOpsCore>,
185 _node: &FsNode,
186 _current_task: &CurrentTask,
187 _flags: OpenFlags,
188 ) -> Result<Box<dyn FileOps>, Errno> {
189 let memory = self.inner.lock().get_memory()?;
190 let size = usize::try_from(memory.get_content_size()).unwrap();
191 Ok(Box::new(MemoryFile { memory, size }))
192 }
193
194 fn fetch_and_refresh_info<'a>(
195 &self,
196 _locked: &mut Locked<FileOpsCore>,
197 _node: &FsNode,
198 _current_task: &CurrentTask,
199 info: &'a RwLock<FsNodeInfo>,
200 ) -> Result<RwLockReadGuard<'a, FsNodeInfo>, Errno> {
201 let memory = self.inner.lock().get_memory()?;
202 let content_size = memory.get_content_size();
203 let attrs = zxio_node_attributes_t {
204 content_size: content_size,
205 storage_size: content_size,
207 link_count: 1,
208 has: zxio_node_attr_has_t {
209 content_size: true,
210 storage_size: true,
211 link_count: true,
212 ..Default::default()
213 },
214 ..Default::default()
215 };
216 let mut info = info.write();
217 update_info_from_attrs(&mut info, &attrs);
218 Ok(RwLockWriteGuard::downgrade(info))
219 }
220
221 fn get_xattr(
222 &self,
223 _locked: &mut Locked<FileOpsCore>,
224 node: &FsNode,
225 _current_task: &CurrentTask,
226 name: &FsStr,
227 _size: usize,
228 ) -> Result<ValueOrSize<FsString>, Errno> {
229 let fs = node.fs();
230 let bundle = RemoteBundle::from_fs(&fs);
231 bundle.get_xattr(node, name)
232 }
233
234 fn list_xattrs(
235 &self,
236 _locked: &mut Locked<FileOpsCore>,
237 node: &FsNode,
238 _current_task: &CurrentTask,
239 _size: usize,
240 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
241 let fs = node.fs();
242 let bundle = RemoteBundle::from_fs(&fs);
243 bundle.list_xattrs(node)
244 }
245}
246
247struct MemoryFile {
258 memory: Arc<MemoryObject>,
259 size: usize,
260}
261
262impl FileOps for MemoryFile {
263 fileops_impl_seekable!();
264 fileops_impl_noop_sync!();
265
266 fn read(
267 &self,
268 _locked: &mut Locked<FileOpsCore>,
269 _file: &FileObject,
270 _current_task: &CurrentTask,
271 mut offset: usize,
272 data: &mut dyn OutputBuffer,
273 ) -> Result<usize, Errno> {
274 data.write_each(&mut |buf| {
275 let buflen = buf.len();
276 let buf = &mut buf[..std::cmp::min(self.size.saturating_sub(offset), buflen)];
277 if !buf.is_empty() {
278 self.memory
279 .read_uninit(buf, offset as u64)
280 .map_err(|status| from_status_like_fdio!(status))?;
281 offset += buf.len();
282 }
283 Ok(buf.len())
284 })
285 }
286
287 fn write(
288 &self,
289 _locked: &mut Locked<FileOpsCore>,
290 _file: &FileObject,
291 _current_task: &CurrentTask,
292 _offset: usize,
293 _data: &mut dyn InputBuffer,
294 ) -> Result<usize, Errno> {
295 error!(EPERM)
296 }
297
298 fn get_memory(
299 &self,
300 _locked: &mut Locked<FileOpsCore>,
301 _file: &FileObject,
302 _current_task: &CurrentTask,
303 _length: Option<usize>,
304 prot: ProtectionFlags,
305 ) -> Result<Arc<MemoryObject>, Errno> {
306 Ok(if prot.contains(ProtectionFlags::EXEC) {
307 Arc::new(
308 self.memory
309 .duplicate_handle(zx::Rights::SAME_RIGHTS)
310 .map_err(impossible_error)?
311 .replace_as_executable(&VMEX_RESOURCE)
312 .map_err(impossible_error)?,
313 )
314 } else {
315 self.memory.clone()
316 })
317 }
318
319 fn wait_async(
320 &self,
321 _locked: &mut Locked<FileOpsCore>,
322 _file: &FileObject,
323 _current_task: &CurrentTask,
324 _waiter: &Waiter,
325 _events: FdEvents,
326 _handler: EventHandler,
327 ) -> Option<WaitCanceler> {
328 None
329 }
330
331 fn query_events(
332 &self,
333 _locked: &mut Locked<FileOpsCore>,
334 _file: &FileObject,
335 _current_task: &CurrentTask,
336 ) -> Result<FdEvents, Errno> {
337 Ok(FdEvents::POLLIN)
338 }
339}
340
341struct DirectoryObject;
342
343impl FileOps for DirectoryObject {
344 fileops_impl_directory!();
345 fileops_impl_noop_sync!();
346
347 fn seek(
348 &self,
349 _locked: &mut Locked<FileOpsCore>,
350 _file: &FileObject,
351 _current_task: &CurrentTask,
352 current_offset: off_t,
353 target: SeekTarget,
354 ) -> Result<off_t, Errno> {
355 default_seek(current_offset, target, || error!(EINVAL))
356 }
357
358 fn readdir(
359 &self,
360 _locked: &mut Locked<FileOpsCore>,
361 file: &FileObject,
362 _current_task: &CurrentTask,
363 sink: &mut dyn DirentSink,
364 ) -> Result<(), Errno> {
365 emit_dotdot(file, sink)?;
366
367 let bundle = RemoteBundle::from_fs(&file.fs);
368 let child_iter = bundle
369 .get_node(file.node().ino)
370 .directory()
371 .ok_or_else(|| errno!(EIO))?
372 .children
373 .iter();
374
375 for (name, inode_num) in child_iter.skip(sink.offset() as usize - 2) {
376 let node = bundle.metadata.get(*inode_num).ok_or_else(|| errno!(EIO))?;
377 sink.add(
378 *inode_num,
379 sink.offset() + 1,
380 DirectoryEntryType::from_mode(FileMode::from_bits(node.mode.into())),
381 name.as_str().into(),
382 )?;
383 }
384
385 Ok(())
386 }
387}
388
389impl FsNodeOps for DirectoryObject {
390 fs_node_impl_dir_readonly!();
391
392 fn create_file_ops(
393 &self,
394 _locked: &mut Locked<FileOpsCore>,
395 _node: &FsNode,
396 _current_task: &CurrentTask,
397 _flags: OpenFlags,
398 ) -> Result<Box<dyn FileOps>, Errno> {
399 Ok(Box::new(DirectoryObject))
400 }
401
402 fn lookup(
403 &self,
404 _locked: &mut Locked<FileOpsCore>,
405 node: &FsNode,
406 _current_task: &CurrentTask,
407 name: &FsStr,
408 ) -> Result<FsNodeHandle, Errno> {
409 let name = std::str::from_utf8(name).map_err(|_| {
410 log_warn!("bad utf8 in pathname! remote filesystems can't handle this");
411 errno!(EINVAL)
412 })?;
413
414 let fs = node.fs();
415 let bundle = RemoteBundle::from_fs(&fs);
416 let metadata = &bundle.metadata;
417 let ino = metadata
418 .lookup(node.ino, name)
419 .map_err(|e| errno!(ENOENT, format!("Error: {e:?} opening {name}")))?;
420 let metadata_node = metadata.get(ino).ok_or_else(|| errno!(EIO))?;
421 let info = to_fs_node_info(metadata_node);
422
423 match metadata_node.info() {
424 NodeInfo::Symlink(_) => Ok(fs.create_node(ino, SymlinkObject, info)),
425 NodeInfo::Directory(_) => Ok(fs.create_node(ino, DirectoryObject, info)),
426 NodeInfo::File(_) => {
427 let (file, server_end) = fidl::endpoints::create_sync_proxy::<fio::FileMarker>();
428 bundle
429 .root
430 .open(
431 &format!("{ino}"),
432 bundle.rights,
433 &Default::default(),
434 server_end.into_channel(),
435 )
436 .map_err(|_| errno!(EIO))?;
437 Ok(fs.create_node(ino, File { inner: Mutex::new(Inner::NeedsVmo(file)) }, info))
438 }
439 }
440 }
441
442 fn get_xattr(
443 &self,
444 _locked: &mut Locked<FileOpsCore>,
445 node: &FsNode,
446 _current_task: &CurrentTask,
447 name: &FsStr,
448 _size: usize,
449 ) -> Result<ValueOrSize<FsString>, Errno> {
450 let fs = node.fs();
451 let bundle = RemoteBundle::from_fs(&fs);
452 bundle.get_xattr(node, name)
453 }
454
455 fn list_xattrs(
456 &self,
457 _locked: &mut Locked<FileOpsCore>,
458 node: &FsNode,
459 _current_task: &CurrentTask,
460 _size: usize,
461 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
462 let fs = node.fs();
463 let bundle = RemoteBundle::from_fs(&fs);
464 bundle.list_xattrs(node)
465 }
466}
467
468struct SymlinkObject;
469
470impl FsNodeOps for SymlinkObject {
471 fs_node_impl_symlink!();
472
473 fn readlink(
474 &self,
475 _locked: &mut Locked<FileOpsCore>,
476 node: &FsNode,
477 _current_task: &CurrentTask,
478 ) -> Result<SymlinkTarget, Errno> {
479 let fs = node.fs();
480 let bundle = RemoteBundle::from_fs(&fs);
481 let target = bundle.get_node(node.ino).symlink().ok_or_else(|| errno!(EIO))?.target.clone();
482 Ok(SymlinkTarget::Path(target.as_str().into()))
483 }
484
485 fn get_xattr(
486 &self,
487 _locked: &mut Locked<FileOpsCore>,
488 node: &FsNode,
489 _current_task: &CurrentTask,
490 name: &FsStr,
491 _size: usize,
492 ) -> Result<ValueOrSize<FsString>, Errno> {
493 let fs = node.fs();
494 let bundle = RemoteBundle::from_fs(&fs);
495 bundle.get_xattr(node, name)
496 }
497
498 fn list_xattrs(
499 &self,
500 _locked: &mut Locked<FileOpsCore>,
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 starnix_uapi::errors::Errno;
535 use starnix_uapi::file_mode::{AccessCheck, FileMode};
536 use starnix_uapi::open_flags::OpenFlags;
537 use starnix_uapi::{ino_t, off_t};
538 use std::collections::{HashMap, HashSet};
539 use {fidl_fuchsia_io as fio, zx};
540
541 #[::fuchsia::test]
542 async fn test_read_image() {
543 spawn_kernel_and_run_with_pkgfs(async |locked, current_task| {
544 let kernel = current_task.kernel();
545 let rights = fio::PERM_READABLE | fio::PERM_EXECUTABLE;
546 let (server, client) = zx::Channel::create();
547 fdio::open("/pkg", rights, server).expect("failed to open /pkg");
548 let fs = RemoteBundle::new_fs(
549 locked,
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(locked, ¤t_task, &mut context, "foo".into())
562 .expect("lookup failed");
563
564 let test_file = test_dir
565 .lookup_child(locked, ¤t_task, &mut context, "file".into())
566 .expect("lookup failed")
567 .open(locked, ¤t_task, OpenFlags::RDONLY, AccessCheck::default())
568 .expect("open failed");
569
570 let mut buffer = VecOutputBuffer::new(64);
571 assert_eq!(test_file.read(locked, ¤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(locked, ¤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(locked, ¤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(locked, ¤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(locked, ¤t_task, &mut context, "symlink".into())
611 .expect("lookup failed");
612
613 if let SymlinkTarget::Path(target) =
614 test_symlink.readlink(locked, ¤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(locked, ¤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(locked, ¤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}