1use crate::mm::ProtectionFlags;
6use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
7use crate::task::{CurrentTask, Kernel, LockedAndTask};
8use crate::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
9use crate::vfs::{
10 DirectoryEntryType, DirentSink, FileHandle, FileObject, FsStr, FsString, LookupContext,
11 NamespaceNode, RenameFlags, SeekTarget, UnlinkKind,
12};
13use fidl::HandleBased;
14use fidl::endpoints::{ClientEnd, ServerEnd};
15use fidl_fuchsia_io as fio;
16use fuchsia_runtime::UtcInstant;
17use futures::future::BoxFuture;
18use itertools::Either;
19use starnix_logging::{log_error, track_stub};
20use starnix_sync::{Locked, Unlocked};
21use starnix_types::convert::IntoFidl as _;
22use starnix_uapi::auth::Credentials;
23use starnix_uapi::device_id::DeviceId;
24use starnix_uapi::errors::Errno;
25use starnix_uapi::file_mode::{AccessCheck, FileMode};
26use starnix_uapi::open_flags::OpenFlags;
27use starnix_uapi::vfs::ResolveFlags;
28use starnix_uapi::{errno, error, from_status_like_fdio, ino_t, off_t};
29use std::collections::HashMap;
30use std::sync::Arc;
31use std::sync::atomic::{AtomicU64, Ordering};
32use vfs::directory::mutable::connection::MutableConnection;
33use vfs::directory::{self};
34use vfs::{
35 ObjectRequestRef, ProtocolsExt, ToObjectRequest, attributes, execution_scope, file, path,
36};
37
38#[derive(Default)]
39struct FileServerStats {
40 serving: AtomicU64,
43
44 reads: AtomicU64,
46
47 read_bytes: AtomicU64,
49
50 writes: AtomicU64,
52
53 write_bytes: AtomicU64,
55}
56
57struct FileServerRegistry {
58 stats: starnix_sync::Mutex<HashMap<&'static str, Arc<FileServerStats>>>,
59}
60
61impl FileServerRegistry {
62 fn get(kernel: &Kernel) -> Arc<Self> {
63 let mut is_new = false;
64 let registry = kernel.expando.get_or_init(|| {
65 is_new = true;
66 Self { stats: starnix_sync::Mutex::new(HashMap::new()) }
67 });
68 if is_new {
69 let registry_weak = Arc::downgrade(®istry);
70 kernel.inspect_node.record_lazy_child("file_server", move || {
71 let inspector = fuchsia_inspect::Inspector::default();
72 if let Some(registry) = registry_weak.upgrade() {
73 let root = inspector.root();
74 for (tag, stats) in registry.stats.lock().iter() {
75 let node = root.create_child(*tag);
76 node.record_uint("serving", stats.serving.load(Ordering::Relaxed));
77 node.record_uint("reads", stats.reads.load(Ordering::Relaxed));
78 node.record_uint("read_bytes", stats.read_bytes.load(Ordering::Relaxed));
79 node.record_uint("writes", stats.writes.load(Ordering::Relaxed));
80 node.record_uint("write_bytes", stats.write_bytes.load(Ordering::Relaxed));
81 root.record(node);
82 }
83 }
84 Box::pin(async { Ok(inspector) })
85 });
86 }
87 registry
88 }
89
90 fn get_stats(&self, tag: &'static str) -> Arc<FileServerStats> {
91 self.stats.lock().entry(tag).or_insert_with(|| Arc::default()).clone()
92 }
93}
94
95pub fn serve_file_tagged(
96 current_task: &CurrentTask,
97 file: &FileObject,
98 credentials: Arc<Credentials>,
99 tag: &'static str,
100) -> Result<(ClientEnd<fio::NodeMarker>, execution_scope::ExecutionScope), Errno> {
101 let (client_end, server_end) = fidl::endpoints::create_endpoints::<fio::NodeMarker>();
102 let scope = serve_file_at_tagged(server_end, current_task, file, credentials, tag)?;
103 Ok((client_end, scope))
104}
105
106pub fn serve_file(
108 current_task: &CurrentTask,
109 file: &FileObject,
110 credentials: Arc<Credentials>,
111) -> Result<(ClientEnd<fio::NodeMarker>, execution_scope::ExecutionScope), Errno> {
112 serve_file_tagged(current_task, file, credentials, "default")
113}
114
115pub fn serve_file_at_tagged(
116 server_end: ServerEnd<fio::NodeMarker>,
117 current_task: &CurrentTask,
118 file: &FileObject,
119 credentials: Arc<Credentials>,
120 tag: &'static str,
121) -> Result<execution_scope::ExecutionScope, Errno> {
122 let kernel = current_task.kernel();
123 let stats = FileServerRegistry::get(&kernel).get_stats(tag);
124 let fidl_flags: fio::OpenFlags = (file.flags() & !OpenFlags::TRUNC).into_fidl();
127 let starnix_file = StarnixNodeConnection::new(
128 &kernel,
129 file.weak_handle.upgrade().unwrap(),
130 credentials,
131 stats.clone(),
132 );
133 let scope = execution_scope::ExecutionScope::new();
134 kernel.kthreads.spawn_future(
135 {
136 let scope = scope.clone();
137 move || async move {
138 stats.serving.fetch_add(1, Ordering::Relaxed);
139 if starnix_file.is_dir() {
140 fidl_flags.to_object_request(server_end).handle(|object_request| {
141 object_request.take().create_connection_sync::<MutableConnection<_>, _>(
142 scope.clone(),
143 starnix_file,
144 fidl_flags,
145 );
146 Ok(())
147 });
148 } else {
149 fidl_flags.to_object_request(server_end).handle(|object_request| {
150 object_request
151 .take()
152 .create_connection_sync::<file::RawIoConnection<_>, _>(
153 scope.clone(),
154 starnix_file,
155 fidl_flags,
156 );
157 Ok(())
158 });
159 }
160 scope.wait().await;
161 stats.serving.fetch_sub(1, Ordering::Relaxed);
162 }
163 },
164 "serve_file_at",
165 );
166 Ok(scope)
167}
168
169pub fn serve_file_at(
170 server_end: ServerEnd<fio::NodeMarker>,
171 current_task: &CurrentTask,
172 file: &FileObject,
173 credentials: Arc<Credentials>,
174) -> Result<execution_scope::ExecutionScope, Errno> {
175 serve_file_at_tagged(server_end, current_task, file, credentials, "default")
176}
177
178#[async_trait::async_trait(?Send)]
179trait Work: Send + 'static {
180 async fn run(
181 self: Box<Self>,
182 locked: &mut Locked<Unlocked>,
183 current_task: &CurrentTask,
184 file: &FileHandle,
185 );
186}
187
188struct EitherSender<R>(Either<futures::channel::oneshot::Sender<R>, std::sync::mpsc::Sender<R>>);
189
190impl<R> From<futures::channel::oneshot::Sender<R>> for EitherSender<R> {
191 fn from(v: futures::channel::oneshot::Sender<R>) -> Self {
192 Self(Either::Left(v))
193 }
194}
195
196impl<R> From<std::sync::mpsc::Sender<R>> for EitherSender<R> {
197 fn from(v: std::sync::mpsc::Sender<R>) -> Self {
198 Self(Either::Right(v))
199 }
200}
201
202impl<R> EitherSender<R> {
203 async fn send(self, r: R) {
204 match self.0 {
205 Either::Left(s) => {
206 let _ = s.send(r);
207 }
208 Either::Right(s) => {
209 let _ = s.send(r);
210 }
211 }
212 }
213}
214
215struct WorkWrapper<R, F>
216where
217 R: Send + 'static,
218 F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> R + Send + 'static,
219{
220 f: F,
221 sender: EitherSender<R>,
222}
223
224#[async_trait::async_trait(?Send)]
225impl<R, F> Work for WorkWrapper<R, F>
226where
227 R: Send + 'static,
228 F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> R + Send + 'static,
229{
230 async fn run(
231 self: Box<Self>,
232 locked: &mut Locked<Unlocked>,
233 current_task: &CurrentTask,
234 file: &FileHandle,
235 ) {
236 let f: F = self.f;
237 let r = f(locked, current_task, file).await;
238 self.sender.send(r).await;
239 }
240}
241
242async fn handle_file(
243 locked_and_task: LockedAndTask<'_>,
244 credentials: Arc<Credentials>,
245 file: FileHandle,
246 receiver: std::sync::mpsc::Receiver<Box<dyn Work>>,
247) {
248 locked_and_task
250 .current_task()
251 .override_creds_async(credentials.clone(), async || {
252 let file = match file.name.open(
254 &mut locked_and_task.unlocked(),
255 locked_and_task.current_task(),
256 file.flags(),
257 AccessCheck::skip(),
258 ) {
259 Ok(file) => file,
260 Err(e) => {
261 log_error!("Unable to reopen file: {e:?}");
262 return;
263 }
264 };
265 while let Ok(w) = receiver.recv() {
266 w.run(&mut locked_and_task.unlocked(), locked_and_task.current_task(), &file).await;
267 }
268 })
269 .await;
270}
271
272fn to_open_flags(flags: &impl ProtocolsExt) -> OpenFlags {
273 let rights = flags.rights().unwrap_or_default();
274 let mut open_flags = if rights.contains(fio::Operations::WRITE_BYTES) {
275 if rights.contains(fio::Operations::READ_BYTES) {
276 OpenFlags::RDWR
277 } else {
278 OpenFlags::WRONLY
279 }
280 } else {
281 OpenFlags::RDONLY
282 };
283
284 if flags.create_directory() {
285 open_flags |= OpenFlags::DIRECTORY;
286 }
287
288 match flags.creation_mode() {
289 vfs::CreationMode::Always => open_flags |= OpenFlags::CREAT | OpenFlags::EXCL,
290 vfs::CreationMode::AllowExisting => open_flags |= OpenFlags::CREAT,
291 vfs::CreationMode::UnnamedTemporary => open_flags |= OpenFlags::TMPFILE,
292 vfs::CreationMode::UnlinkableUnnamedTemporary => {
293 open_flags |= OpenFlags::TMPFILE | OpenFlags::EXCL
294 }
295 vfs::CreationMode::Never => {}
296 };
297
298 if flags.is_truncate() {
299 open_flags |= OpenFlags::TRUNC;
300 }
301
302 if flags.is_append() {
303 open_flags |= OpenFlags::APPEND;
304 }
305
306 open_flags
307}
308
309#[derive(Clone)]
322struct StarnixNodeConnection {
323 is_dir: bool,
324 credentials: Arc<Credentials>,
325 work_sender: std::sync::mpsc::Sender<Box<dyn Work>>,
326 stats: Arc<FileServerStats>,
327}
328
329fn lookup_parent(
330 locked: &mut Locked<Unlocked>,
331 current_task: &CurrentTask,
332 file: &FileObject,
333 path: path::Path,
334) -> Result<(NamespaceNode, FsString), Errno> {
335 let (node, name) = current_task.lookup_parent(
336 locked,
337 &mut LookupContext::default(),
338 &file.name,
339 path.as_str().into(),
340 )?;
341 Ok((node, name.to_owned()))
342}
343
344impl StarnixNodeConnection {
345 fn new(
346 kernel: &Kernel,
347 file: FileHandle,
348 credentials: Arc<Credentials>,
349 stats: Arc<FileServerStats>,
350 ) -> Arc<Self> {
351 let (work_sender, receiver) = std::sync::mpsc::channel();
352 let is_dir = file.node().is_dir();
353 let closure = {
354 let credentials = credentials.clone();
355 async move |locked_and_task: LockedAndTask<'_>| {
356 handle_file(locked_and_task, credentials, file, receiver).await;
357 }
358 };
359 let req = SpawnRequestBuilder::new().with_async_closure(closure).build();
360 kernel.kthreads.spawner().spawn_from_request(req);
361 Arc::new(Self { is_dir, credentials, work_sender, stats })
362 }
363
364 fn spawn_task<R, E, F>(&self, f: F) -> Result<R, Errno>
365 where
366 R: Send + 'static,
367 E: Send + 'static,
368 F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> Result<R, E>
369 + Send
370 + 'static,
371 Errno: From<E>,
372 {
373 let (sender, receiver) = std::sync::mpsc::channel();
374 self.work_sender
375 .send(Box::new(WorkWrapper { f, sender: sender.into() }))
376 .map_err(|_| errno!(EIO))?;
377 Ok(receiver.recv().map_err(|_| errno!(EIO))??)
378 }
379
380 async fn spawn_task_async<R, E, F>(&self, f: F) -> Result<R, Errno>
381 where
382 R: Send + 'static,
383 E: Send + 'static,
384 F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> Result<R, E>
385 + Send
386 + 'static,
387 Errno: From<E>,
388 {
389 let (sender, receiver) = futures::channel::oneshot::channel();
390 self.work_sender
391 .send(Box::new(WorkWrapper { f, sender: sender.into() }))
392 .map_err(|_| errno!(EIO))?;
393 Ok(receiver.await.map_err(|_| errno!(EIO))??)
394 }
395
396 fn is_dir(&self) -> bool {
397 self.is_dir
398 }
399
400 fn lookup_parent(&self, path: path::Path) -> Result<(NamespaceNode, FsString), Errno> {
401 self.spawn_task(async move |locked, current_task, file| {
402 lookup_parent(locked, current_task, file, path)
403 })
404 }
405
406 fn reopen(&self, flags: &impl ProtocolsExt) -> Result<Arc<Self>, Errno> {
409 let credentials = self.credentials.clone();
410 let flags = to_open_flags(flags);
411 let stats = self.stats.clone();
412 self.spawn_task(async move |locked, current_task, file| {
413 let file = file.name.open(locked, current_task, flags, AccessCheck::default())?;
414 Ok(StarnixNodeConnection::new(¤t_task.kernel(), file, credentials, stats))
415 })
416 }
417
418 fn directory_read_dirents<'a>(
420 &'a self,
421 pos: &'a directory::traversal_position::TraversalPosition,
422 sink: Box<dyn directory::dirents_sink::Sink>,
423 ) -> Result<
424 (
425 directory::traversal_position::TraversalPosition,
426 Box<dyn directory::dirents_sink::Sealed>,
427 ),
428 Errno,
429 > {
430 let pos = pos.clone();
431 self.spawn_task(async move |locked, current_task, file| {
432 struct DirentSinkAdapter<'a> {
433 sink: Option<directory::dirents_sink::AppendResult>,
434 offset: &'a mut off_t,
435 }
436 impl<'a> DirentSinkAdapter<'a> {
437 fn append(
438 &mut self,
439 entry: &directory::entry::EntryInfo,
440 name: &str,
441 ) -> Result<(), Errno> {
442 let sink = self.sink.take();
443 self.sink = match sink {
444 s @ Some(directory::dirents_sink::AppendResult::Sealed(_)) => {
445 self.sink = s;
446 return error!(ENOSPC);
447 }
448 Some(directory::dirents_sink::AppendResult::Ok(sink)) => {
449 Some(sink.append(entry, name))
450 }
451 None => return error!(ENOTSUP),
452 };
453 Ok(())
454 }
455 }
456 impl<'a> DirentSink for DirentSinkAdapter<'a> {
457 fn add(
458 &mut self,
459 inode_num: ino_t,
460 offset: off_t,
461 entry_type: DirectoryEntryType,
462 name: &FsStr,
463 ) -> Result<(), Errno> {
464 if name != ".." {
466 if let Some(dirent_type) =
468 fio::DirentType::from_primitive(entry_type.bits())
469 {
470 let entry_info =
471 directory::entry::EntryInfo::new(inode_num, dirent_type);
472 self.append(&entry_info, &String::from_utf8_lossy(name))?
473 }
474 }
475 *self.offset = offset;
476 Ok(())
477 }
478 fn offset(&self) -> off_t {
479 *self.offset
480 }
481 }
482 let offset = match pos {
483 directory::traversal_position::TraversalPosition::Start => 0,
484 directory::traversal_position::TraversalPosition::Index(v) => v as i64,
485 directory::traversal_position::TraversalPosition::End => {
486 return Ok((
487 directory::traversal_position::TraversalPosition::End,
488 sink.seal(),
489 ));
490 }
491 _ => return error!(EINVAL),
492 };
493 if file.offset.read() != offset {
494 file.seek(locked, current_task, SeekTarget::Set(offset))?;
495 }
496 let mut file_offset = file.offset.copy();
497 let sink_result = {
498 let mut dirent_sink = DirentSinkAdapter {
499 sink: Some(directory::dirents_sink::AppendResult::Ok(sink)),
500 offset: &mut *file_offset,
501 };
502 file.readdir(locked, current_task, &mut dirent_sink)?;
503 dirent_sink.sink
504 };
505 let ret = match sink_result {
506 Some(directory::dirents_sink::AppendResult::Sealed(seal)) => {
507 Ok((directory::traversal_position::TraversalPosition::End, seal))
508 }
509 Some(directory::dirents_sink::AppendResult::Ok(sink)) => Ok((
510 directory::traversal_position::TraversalPosition::Index(*file_offset as u64),
511 sink.seal(),
512 )),
513 None => error!(ENOTSUP),
514 };
515 file_offset.update();
516 ret
517 })
518 }
519
520 fn directory_entry_open(
522 self: Arc<Self>,
523 scope: execution_scope::ExecutionScope,
524 flags: impl ProtocolsExt,
525 path: path::Path,
526 object_request: ObjectRequestRef<'_>,
527 ) -> Result<(), zx::Status> {
528 if self.is_dir() {
529 if path.is_dot() {
530 let dir = self.reopen(&flags)?;
532 object_request
533 .take()
534 .create_connection_sync::<MutableConnection<_>, _>(scope, dir, flags);
535 return Ok(());
536 }
537
538 let starnix_file = self.spawn_task({
540 let credentials = self.credentials.clone();
541 let stats = self.stats.clone();
542 let create_directory = flags.creation_mode() != vfs::common::CreationMode::Never
543 && flags.create_directory();
544 let open_flags = to_open_flags(&flags);
545 async move |locked, current_task, file| {
546 let (node, name) = lookup_parent(locked, current_task, file, path)?;
547 let file = match current_task.open_namespace_node_at(
548 locked,
549 node.clone(),
550 name.as_ref(),
551 open_flags,
552 FileMode::ALLOW_ALL,
553 ResolveFlags::empty(),
554 AccessCheck::default(),
555 ) {
556 Err(e) if e == errno!(EISDIR) && create_directory => {
557 let mode = current_task
558 .fs()
559 .apply_umask(FileMode::from_bits(0o777) | FileMode::IFDIR);
560 let name = node.create_node(
561 locked,
562 ¤t_task,
563 name.as_ref(),
564 mode,
565 DeviceId::NONE,
566 )?;
567 name.open(
568 locked,
569 ¤t_task,
570 open_flags & !(OpenFlags::CREAT | OpenFlags::EXCL),
571 AccessCheck::skip(),
572 )?
573 }
574 f => f?,
575 };
576 Ok(StarnixNodeConnection::new(¤t_task.kernel(), file, credentials, stats))
577 }
578 })?;
579
580 return starnix_file.directory_entry_open(
581 scope,
582 flags,
583 path::Path::dot(),
584 object_request,
585 );
586 }
587
588 if !path.is_dot() {
590 return Err(zx::Status::NOT_DIR);
591 }
592 let file = self.reopen(&flags)?;
593 object_request
594 .take()
595 .create_connection_sync::<file::RawIoConnection<_>, _>(scope, file, flags);
596 Ok(())
597 }
598
599 fn get_attributes(
600 &self,
601 requested_attributes: fio::NodeAttributesQuery,
602 ) -> fio::NodeAttributes2 {
603 self.spawn_task(async move |_, _, file| {
604 let info = file.node().info();
605
606 #[allow(clippy::unnecessary_cast)]
608 let link_count = info.link_count as u64;
609
610 let (protocols, abilities) = if info.mode.contains(FileMode::IFDIR) {
611 (
612 fio::NodeProtocolKinds::DIRECTORY,
613 fio::Operations::GET_ATTRIBUTES
614 | fio::Operations::UPDATE_ATTRIBUTES
615 | fio::Operations::ENUMERATE
616 | fio::Operations::TRAVERSE
617 | fio::Operations::MODIFY_DIRECTORY,
618 )
619 } else {
620 (
621 fio::NodeProtocolKinds::FILE,
622 fio::Operations::GET_ATTRIBUTES
623 | fio::Operations::UPDATE_ATTRIBUTES
624 | fio::Operations::READ_BYTES
625 | fio::Operations::WRITE_BYTES,
626 )
627 };
628
629 Ok(attributes!(
630 requested_attributes,
631 Mutable {
632 creation_time: info.time_status_change.into_nanos() as u64,
633 modification_time: info.time_modify.into_nanos() as u64,
634 mode: info.mode.bits(),
635 uid: info.uid,
636 gid: info.gid,
637 rdev: info.rdev.bits(),
638 },
639 Immutable {
640 protocols: protocols,
641 abilities: abilities,
642 content_size: info.size as u64,
643 storage_size: info.storage_size() as u64,
644 link_count: link_count,
645 id: file.fs.dev_id.bits(),
646 }
647 ))
648 })
649 .expect("spawn_task")
650 }
651
652 fn update_attributes(&self, attributes: fio::MutableNodeAttributes) {
653 let _ = self.spawn_task(async move |_, _, file| {
654 file.node().update_info(|info| {
655 if let Some(time) = attributes.creation_time {
656 info.time_status_change = UtcInstant::from_nanos(time as i64);
657 }
658 if let Some(time) = attributes.modification_time {
659 info.time_modify = UtcInstant::from_nanos(time as i64);
660 }
661 if let Some(mode) = attributes.mode {
662 info.mode = FileMode::from_bits(mode);
663 }
664 if let Some(uid) = attributes.uid {
665 info.uid = uid;
666 }
667 if let Some(gid) = attributes.gid {
668 info.gid = gid;
669 }
670 if let Some(rdev) = attributes.rdev {
671 info.rdev = DeviceId::from_bits(rdev);
672 }
673 });
674 Ok(())
675 });
676 }
677}
678
679impl vfs::node::Node for StarnixNodeConnection {
680 async fn get_attributes(
681 &self,
682 requested_attributes: fio::NodeAttributesQuery,
683 ) -> Result<fio::NodeAttributes2, zx::Status> {
684 Ok(StarnixNodeConnection::get_attributes(self, requested_attributes))
685 }
686}
687
688impl directory::entry::GetEntryInfo for StarnixNodeConnection {
689 fn entry_info(&self) -> directory::entry::EntryInfo {
690 let dirent_type =
691 if self.is_dir() { fio::DirentType::Directory } else { fio::DirentType::File };
692 directory::entry::EntryInfo::new(0, dirent_type)
693 }
694}
695
696impl directory::entry_container::Directory for StarnixNodeConnection {
697 fn open(
698 self: Arc<Self>,
699 scope: execution_scope::ExecutionScope,
700 path: path::Path,
701 flags: fio::Flags,
702 object_request: ObjectRequestRef<'_>,
703 ) -> Result<(), zx::Status> {
704 self.directory_entry_open(scope, flags, path, object_request)
705 }
706
707 async fn read_dirents(
708 &self,
709 pos: &directory::traversal_position::TraversalPosition,
710 sink: Box<dyn directory::dirents_sink::Sink>,
711 ) -> Result<
712 (
713 directory::traversal_position::TraversalPosition,
714 Box<dyn directory::dirents_sink::Sealed>,
715 ),
716 zx::Status,
717 > {
718 StarnixNodeConnection::directory_read_dirents(self, pos, sink).map_err(Errno::into)
719 }
720 fn register_watcher(
721 self: Arc<Self>,
722 _scope: execution_scope::ExecutionScope,
723 _mask: fio::WatchMask,
724 _watcher: directory::entry_container::DirectoryWatcher,
725 ) -> Result<(), zx::Status> {
726 track_stub!(TODO("https://fxbug.dev/322875605"), "register directory watcher");
727 Ok(())
728 }
729 fn unregister_watcher(self: Arc<Self>, _key: usize) {}
730}
731
732impl directory::entry_container::MutableDirectory for StarnixNodeConnection {
733 async fn update_attributes(
734 &self,
735 attributes: fio::MutableNodeAttributes,
736 ) -> Result<(), zx::Status> {
737 StarnixNodeConnection::update_attributes(self, attributes);
738 Ok(())
739 }
740 async fn unlink(
741 self: Arc<Self>,
742 name: &str,
743 must_be_directory: bool,
744 ) -> Result<(), zx::Status> {
745 let name = FsString::from(name.to_owned());
746 self.spawn_task_async(async move |locked, current_task, file| {
747 let kind =
748 if must_be_directory { UnlinkKind::Directory } else { UnlinkKind::NonDirectory };
749 file.name.entry.unlink(
750 locked,
751 current_task,
752 &file.name.mount,
753 name.as_ref(),
754 kind,
755 false,
756 )
757 })
758 .await?;
759 Ok(())
760 }
761 async fn sync(&self) -> Result<(), zx::Status> {
762 Ok(())
763 }
764 fn rename(
765 self: Arc<Self>,
766 src_dir: Arc<dyn directory::entry_container::MutableDirectory>,
767 src_name: path::Path,
768 dst_name: path::Path,
769 ) -> BoxFuture<'static, Result<(), zx::Status>> {
770 let this = self.clone();
771 Box::pin(async move {
772 Ok(self
773 .spawn_task_async(async move |locked, current_task, file| {
774 let src_dir = src_dir
775 .into_any()
776 .downcast::<StarnixNodeConnection>()
777 .map_err(|_| errno!(EXDEV))?;
778 let (dst_node, dst_name) =
779 lookup_parent(locked, current_task, &file, dst_name)?;
780 let (src_node, src_name) = if Arc::ptr_eq(&src_dir, &this) {
781 lookup_parent(locked, current_task, &file, src_name)?
782 } else {
783 src_dir.lookup_parent(src_name)?
784 };
785 NamespaceNode::rename(
786 locked,
787 current_task,
788 &src_node,
789 src_name.as_ref(),
790 &dst_node,
791 dst_name.as_ref(),
792 RenameFlags::empty(),
793 )
794 })
795 .await?)
796 })
797 }
798}
799
800impl file::File for StarnixNodeConnection {
801 fn writable(&self) -> bool {
802 true
803 }
804 async fn open_file(&self, _optionss: &file::FileOptions) -> Result<(), zx::Status> {
805 Ok(())
806 }
807 async fn truncate(&self, length: u64) -> Result<(), zx::Status> {
808 Ok(self
809 .spawn_task_async(async move |locked, current_task, file| {
810 file.ftruncate(locked, current_task, length)
813 })
814 .await?)
815 }
816 async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, zx::Status> {
817 Ok(self
818 .spawn_task_async(async move |locked, current_task, file| {
819 (|| {
820 let mut prot_flags = ProtectionFlags::empty();
821 if flags.contains(fio::VmoFlags::READ) {
822 prot_flags |= ProtectionFlags::READ;
823 }
824 if flags.contains(fio::VmoFlags::WRITE) {
825 prot_flags |= ProtectionFlags::WRITE;
826 }
827 if flags.contains(fio::VmoFlags::EXECUTE) {
828 prot_flags |= ProtectionFlags::EXEC;
829 }
830 let memory = file.get_memory(locked, current_task, None, prot_flags)?;
831 let vmo = memory.as_vmo().ok_or(zx::Status::NOT_SUPPORTED)?;
832 if flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
833 let size = vmo.get_size()?;
834 vmo.create_child(zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE, 0, size)
835 } else {
836 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)
837 }
838 })()
839 .map_err(|e| from_status_like_fdio!(e))
840 })
841 .await?)
842 }
843
844 async fn get_size(&self) -> Result<u64, zx::Status> {
845 Ok(self
846 .spawn_task_async(async move |_, _, file| Ok(file.node().info().size as u64))
847 .await?)
848 }
849 async fn update_attributes(
850 &self,
851 attributes: fio::MutableNodeAttributes,
852 ) -> Result<(), zx::Status> {
853 StarnixNodeConnection::update_attributes(self, attributes);
854 Ok(())
855 }
856 async fn sync(&self, _mode: file::SyncMode) -> Result<(), zx::Status> {
857 Ok(())
858 }
859}
860
861impl file::RawFileIoConnection for StarnixNodeConnection {
862 async fn read(&self, count: u64) -> Result<Vec<u8>, zx::Status> {
863 self.stats.reads.fetch_add(1, Ordering::Relaxed);
864 let data: Vec<u8> = self
865 .spawn_task_async(async move |locked, current_task, file| {
866 let mut data = VecOutputBuffer::new(count as usize);
867 file.read(locked, current_task, &mut data)?;
868 Ok(data.into())
869 })
870 .await?;
871 self.stats.read_bytes.fetch_add(data.len() as u64, Ordering::Relaxed);
872 Ok(data)
873 }
874
875 async fn read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, zx::Status> {
876 self.stats.reads.fetch_add(1, Ordering::Relaxed);
877 let data: Vec<u8> = self
878 .spawn_task_async(async move |locked, current_task, file| {
879 let mut data = VecOutputBuffer::new(count as usize);
880 file.read_at(locked, current_task, offset as usize, &mut data)?;
881 Ok(data.into())
882 })
883 .await?;
884 self.stats.read_bytes.fetch_add(data.len() as u64, Ordering::Relaxed);
885 Ok(data)
886 }
887
888 async fn write(&self, content: &[u8]) -> Result<u64, zx::Status> {
889 self.stats.writes.fetch_add(1, Ordering::Relaxed);
890 let mut data = VecInputBuffer::new(content);
891 let written = self
892 .spawn_task_async(async move |locked, current_task, file| {
893 let written = file.write(locked, current_task, &mut data)?;
894 Ok(written as u64)
895 })
896 .await?;
897 self.stats.write_bytes.fetch_add(written, Ordering::Relaxed);
898 Ok(written)
899 }
900
901 async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, zx::Status> {
902 self.stats.writes.fetch_add(1, Ordering::Relaxed);
903 let mut data = VecInputBuffer::new(content);
904 let written = self
905 .spawn_task_async(async move |locked, current_task, file| {
906 let written = file.write_at(locked, current_task, offset as usize, &mut data)?;
907 Ok(written as u64)
908 })
909 .await?;
910 self.stats.write_bytes.fetch_add(written as u64, Ordering::Relaxed);
911 Ok(written)
912 }
913
914 async fn seek(&self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, zx::Status> {
915 let target = match origin {
916 fio::SeekOrigin::Start => SeekTarget::Set(offset),
917 fio::SeekOrigin::Current => SeekTarget::Cur(offset),
918 fio::SeekOrigin::End => SeekTarget::End(offset),
919 };
920 Ok(self.spawn_task(async move |locked, current_task, file| {
921 let seek_result = file.seek(locked, current_task, target)?;
922 Ok(seek_result as u64)
923 })?)
924 }
925
926 fn set_flags(&self, flags: fio::Flags) -> Result<(), zx::Status> {
927 const SETTABLE_FLAGS_MASK: OpenFlags = OpenFlags::APPEND;
934 let flags = if flags.contains(fio::Flags::FILE_APPEND) {
935 OpenFlags::APPEND
936 } else {
937 OpenFlags::empty()
938 };
939 Ok(self.spawn_task(async move |_, _, file| {
940 file.update_file_flags(flags, SETTABLE_FLAGS_MASK);
941 Ok(())
942 })?)
943 }
944}
945
946#[cfg(test)]
947mod tests {
948 use super::*;
949 use crate::fs::tmpfs::TmpFs;
950 use crate::testing::*;
951 use crate::vfs::{FsString, Namespace};
952 use starnix_uapi::auth::{Capabilities, Credentials};
953 use std::collections::HashSet;
954 use syncio::{Zxio, ZxioOpenOptions, zxio_node_attr_has_t};
955
956 fn assert_directory_content(zxio: &Zxio, content: &[&[u8]]) {
957 let expected = content.iter().map(|&x| FsString::from(x)).collect::<HashSet<_>>();
958 let mut iterator = zxio.create_dirent_iterator().expect("iterator");
959 iterator.rewind().expect("iterator");
960 let found =
961 iterator.map(|x| x.as_ref().expect("dirent").name.clone()).collect::<HashSet<_>>();
962 assert_eq!(found, expected);
963 }
964
965 #[::fuchsia::test]
966 async fn access_file_system() {
967 spawn_kernel_and_run(async |locked, current_task| {
968 let kernel = current_task.kernel();
969 let fs = TmpFs::new_fs(locked, &kernel);
970
971 let file =
972 &fs.root().open_anonymous(locked, current_task, OpenFlags::RDWR).expect("open");
973 let (root_handle, scope) =
974 serve_file(current_task, file, Credentials::root()).expect("serve");
975
976 let fs_dev_id = fs.dev_id;
979 std::thread::spawn(move || {
980 let root_zxio = Zxio::create(root_handle.into_handle()).expect("create");
981
982 assert_directory_content(&root_zxio, &[b"."]);
983 assert_directory_content(&root_zxio, &[b"."]);
985
986 let attrs = root_zxio
987 .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
988 .expect("attr_get");
989 assert_eq!(attrs.id, fs_dev_id.bits());
990
991 let mut attrs = syncio::zxio_node_attributes_t::default();
992 attrs.has.creation_time = true;
993 attrs.has.modification_time = true;
994 attrs.creation_time = 0;
995 attrs.modification_time = 42;
996 root_zxio.attr_set(&attrs).expect("attr_set");
997 let attrs = root_zxio
998 .attr_get(zxio_node_attr_has_t {
999 creation_time: true,
1000 modification_time: true,
1001 ..Default::default()
1002 })
1003 .expect("attr_get");
1004 assert_eq!(attrs.creation_time, 0);
1005 assert_eq!(attrs.modification_time, 42);
1006
1007 assert_eq!(
1008 root_zxio
1009 .open("foo", fio::PERM_READABLE | fio::PERM_WRITABLE, Default::default())
1010 .expect_err("open"),
1011 zx::Status::NOT_FOUND
1012 );
1013 let foo_zxio = root_zxio
1014 .open(
1015 "foo",
1016 fio::PERM_READABLE
1017 | fio::PERM_WRITABLE
1018 | fio::Flags::FLAG_MAYBE_CREATE
1019 | fio::Flags::PROTOCOL_FILE,
1020 Default::default(),
1021 )
1022 .expect("zxio_open");
1023 assert_directory_content(&root_zxio, &[b".", b"foo"]);
1024
1025 assert_eq!(foo_zxio.write(b"hello").expect("write"), 5);
1026 assert_eq!(foo_zxio.write_at(2, b"ch").expect("write_at"), 2);
1027 let mut buffer = [0; 7];
1028 assert_eq!(foo_zxio.read_at(2, &mut buffer).expect("read_at"), 3);
1029 assert_eq!(&buffer[..3], b"cho");
1030 assert_eq!(foo_zxio.seek(syncio::SeekOrigin::Start, 0).expect("seek"), 0);
1031 assert_eq!(foo_zxio.read(&mut buffer).expect("read"), 5);
1032 assert_eq!(&buffer[..5], b"hecho");
1033
1034 let attrs = foo_zxio
1035 .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
1036 .expect("attr_get");
1037 assert_eq!(attrs.id, fs_dev_id.bits());
1038
1039 let mut attrs = syncio::zxio_node_attributes_t::default();
1040 attrs.has.creation_time = true;
1041 attrs.has.modification_time = true;
1042 attrs.creation_time = 0;
1043 attrs.modification_time = 42;
1044 foo_zxio.attr_set(&attrs).expect("attr_set");
1045 let attrs = foo_zxio
1046 .attr_get(zxio_node_attr_has_t {
1047 creation_time: true,
1048 modification_time: true,
1049 ..Default::default()
1050 })
1051 .expect("attr_get");
1052 assert_eq!(attrs.creation_time, 0);
1053 assert_eq!(attrs.modification_time, 42);
1054
1055 assert_eq!(
1056 root_zxio
1057 .open(
1058 "bar/baz",
1059 fio::Flags::PROTOCOL_DIRECTORY
1060 | fio::Flags::FLAG_MAYBE_CREATE
1061 | fio::PERM_READABLE
1062 | fio::PERM_WRITABLE,
1063 Default::default(),
1064 )
1065 .expect_err("open"),
1066 zx::Status::NOT_FOUND
1067 );
1068
1069 let bar_zxio = root_zxio
1070 .open(
1071 "bar",
1072 fio::Flags::PROTOCOL_DIRECTORY
1073 | fio::Flags::FLAG_MAYBE_CREATE
1074 | fio::PERM_READABLE
1075 | fio::PERM_WRITABLE,
1076 Default::default(),
1077 )
1078 .expect("open");
1079 let baz_zxio = root_zxio
1080 .open(
1081 "bar/baz",
1082 fio::Flags::PROTOCOL_DIRECTORY
1083 | fio::Flags::FLAG_MAYBE_CREATE
1084 | fio::PERM_READABLE
1085 | fio::PERM_WRITABLE,
1086 Default::default(),
1087 )
1088 .expect("open");
1089 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1090 assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1091
1092 bar_zxio.rename("baz", &root_zxio, "quz").expect("rename");
1093 assert_directory_content(&bar_zxio, &[b"."]);
1094 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar", b"quz"]);
1095 assert_directory_content(&baz_zxio, &[b"."]);
1096 })
1097 .join()
1098 .expect("join");
1099 scope.shutdown();
1100 scope.wait().await;
1101 std::mem::drop(fs);
1103 })
1104 .await;
1105 }
1106
1107 #[::fuchsia::test]
1108 async fn serve_file_strips_trunc() {
1109 spawn_kernel_and_run(async |locked, current_task| {
1110 let kernel = current_task.kernel();
1111 let fs = TmpFs::new_fs(locked, &kernel);
1112 let ns = Namespace::new(fs);
1113 let root = ns.root();
1114
1115 let file_node = root
1116 .create_node(
1117 locked,
1118 current_task,
1119 b"test".into(),
1120 FileMode::IFREG | FileMode::ALLOW_ALL,
1121 DeviceId::NONE,
1122 )
1123 .expect("create_node");
1124
1125 let file = file_node
1126 .open(locked, current_task, OpenFlags::RDWR, AccessCheck::skip())
1127 .expect("open");
1128 file.write(locked, current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1129
1130 let file_to_serve = current_task
1132 .open_namespace_node_at(
1133 locked,
1134 root,
1135 b"test".into(),
1136 OpenFlags::RDWR | OpenFlags::TRUNC,
1137 FileMode::default(),
1138 ResolveFlags::default(),
1139 AccessCheck::skip(),
1140 )
1141 .expect("open O_TRUNC");
1142
1143 assert_eq!(
1145 file_to_serve.node().fetch_and_refresh_info(locked, current_task).unwrap().size,
1146 0
1147 );
1148
1149 file_to_serve
1151 .write(locked, current_task, &mut VecInputBuffer::new(b"world"))
1152 .expect("write world");
1153 assert_eq!(file_to_serve.node().info().size, 5);
1154
1155 let (client_end, scope) =
1156 serve_file(current_task, &file_to_serve, Credentials::root()).expect("serve");
1157
1158 fuchsia_async::unblock(|| {
1159 let zxio = Zxio::create(client_end.into_handle()).expect("create");
1160 let mut attr = syncio::zxio_node_attributes_t::default();
1161 attr.has.content_size = true;
1162 let attr = zxio.attr_get(attr.has).expect("attr_get");
1163 assert_eq!(attr.content_size, 5);
1165 })
1166 .await;
1167
1168 scope.shutdown();
1169 scope.wait().await;
1170 })
1171 .await;
1172 }
1173
1174 #[::fuchsia::test]
1175 async fn truncate_checks_fd_permissions() {
1176 spawn_kernel_and_run(async |locked, current_task| {
1177 let kernel = current_task.kernel();
1178 let fs = TmpFs::new_fs(locked, &kernel);
1179 let ns = Namespace::new(fs);
1180 let root = ns.root();
1181
1182 let file_node = root
1183 .create_node(
1184 locked,
1185 current_task,
1186 "test".into(),
1187 FileMode::IFREG | FileMode::IRWXU,
1188 DeviceId::NONE,
1189 )
1190 .expect("create_node");
1191
1192 let file = file_node
1193 .open(locked, current_task, OpenFlags::RDWR, AccessCheck::skip())
1194 .expect("open");
1195 file.write(locked, current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1196
1197 let (client_end, scope) = serve_file(
1199 current_task,
1200 &file,
1201 Arc::new(Credentials {
1202 fsuid: 2000,
1203 cap_effective: Capabilities::empty(),
1204 ..Credentials::clone(¤t_task.current_creds())
1205 }),
1206 )
1207 .expect("serve");
1208
1209 fuchsia_async::unblock(move || {
1210 let zxio = Zxio::create(client_end.into_handle()).expect("create");
1211 zxio.truncate(2).expect("truncate");
1214
1215 let mut attr = syncio::zxio_node_attributes_t::default();
1216 attr.has.content_size = true;
1217 let attr = zxio.attr_get(attr.has).expect("attr_get");
1218 assert_eq!(attr.content_size, 2);
1219 })
1220 .await;
1221
1222 scope.shutdown();
1223 scope.wait().await;
1224 })
1225 .await;
1226 }
1227
1228 #[::fuchsia::test]
1229 async fn open() {
1230 spawn_kernel_and_run(async |locked, current_task| {
1231 let kernel = current_task.kernel();
1232 let fs = TmpFs::new_fs(locked, &kernel);
1233
1234 let file = &fs
1235 .root()
1236 .open_anonymous(locked, current_task, OpenFlags::RDWR)
1237 .expect("open_anonymous failed");
1238 let (root_handle, scope) =
1239 serve_file(current_task, file, Credentials::root()).expect("serve_file failed");
1240
1241 std::thread::spawn(move || {
1242 let root_zxio =
1243 Zxio::create(root_handle.into_handle()).expect("zxio create failed");
1244
1245 assert_directory_content(&root_zxio, &[b"."]);
1246 assert_eq!(
1247 root_zxio
1248 .open(
1249 "foo",
1250 fio::PERM_READABLE | fio::PERM_WRITABLE,
1251 ZxioOpenOptions::default()
1252 )
1253 .expect_err("open3 passed unexpectedly"),
1254 zx::Status::NOT_FOUND
1255 );
1256 root_zxio
1257 .open(
1258 "foo",
1259 fio::Flags::PROTOCOL_FILE
1260 | fio::PERM_READABLE
1261 | fio::PERM_WRITABLE
1262 | fio::Flags::FLAG_MUST_CREATE,
1263 ZxioOpenOptions::default(),
1264 )
1265 .expect("open3 failed");
1266 assert_directory_content(&root_zxio, &[b".", b"foo"]);
1267
1268 assert_eq!(
1269 root_zxio
1270 .open(
1271 "bar/baz",
1272 fio::Flags::PROTOCOL_DIRECTORY
1273 | fio::PERM_READABLE
1274 | fio::PERM_WRITABLE
1275 | fio::Flags::FLAG_MUST_CREATE,
1276 ZxioOpenOptions::default()
1277 )
1278 .expect_err("open3 passed unexpectedly"),
1279 zx::Status::NOT_FOUND
1280 );
1281 let bar_zxio = root_zxio
1282 .open(
1283 "bar",
1284 fio::Flags::PROTOCOL_DIRECTORY
1285 | fio::PERM_READABLE
1286 | fio::PERM_WRITABLE
1287 | fio::Flags::FLAG_MUST_CREATE,
1288 ZxioOpenOptions::default(),
1289 )
1290 .expect("open3 failed");
1291 root_zxio
1292 .open(
1293 "bar/baz",
1294 fio::Flags::PROTOCOL_DIRECTORY
1295 | fio::PERM_READABLE
1296 | fio::PERM_WRITABLE
1297 | fio::Flags::FLAG_MUST_CREATE,
1298 ZxioOpenOptions::default(),
1299 )
1300 .expect("open3 failed");
1301 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1302 assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1303 })
1304 .join()
1305 .expect("join");
1306 scope.shutdown();
1307 scope.wait().await;
1308
1309 std::mem::drop(fs);
1311 })
1312 .await;
1313 }
1314
1315 #[::fuchsia::test]
1316 async fn use_credentials() {
1317 spawn_kernel_and_run(async |locked, current_task| {
1318 let kernel = current_task.kernel();
1319 let fs = TmpFs::new_fs(locked, &kernel);
1320
1321 let file = &fs
1322 .root()
1323 .open_anonymous(locked, current_task, OpenFlags::RDWR)
1324 .expect("open_anonymous failed");
1325 let ns = Namespace::new(fs);
1327 ns.root()
1328 .open_create_node(
1329 locked,
1330 current_task,
1331 "test".into(),
1332 FileMode::from_bits(0o600) | FileMode::IFREG,
1333 DeviceId::NONE,
1334 OpenFlags::empty(),
1335 )
1336 .expect("open_create_node failed");
1337
1338 let mut creds = Credentials::with_ids(0, 0);
1339 creds.fsuid = 1;
1340 creds.cap_effective = Capabilities::empty();
1341
1342 let (root_handle, scope) =
1343 serve_file(current_task, file, creds.into()).expect("serve_file failed");
1344
1345 std::thread::spawn(move || {
1346 let root_zxio =
1347 Zxio::create(root_handle.into_handle()).expect("zxio create failed");
1348
1349 assert_directory_content(&root_zxio, &[b".", b"test"]);
1350 assert_eq!(
1351 root_zxio
1352 .open(
1353 "test",
1354 fio::PERM_READABLE | fio::PERM_WRITABLE,
1355 ZxioOpenOptions::default()
1356 )
1357 .expect_err("open3 passed unexpectedly"),
1358 zx::Status::ACCESS_DENIED
1359 );
1360 })
1361 .join()
1362 .expect("join");
1363 scope.shutdown();
1364 scope.wait().await;
1365
1366 std::mem::drop(ns);
1368 })
1369 .await;
1370 }
1371}