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