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::StreamExt;
17use futures::future::BoxFuture;
18use itertools::Either;
19use starnix_logging::{log_error, track_stub};
20use starnix_sync::{FileServerStatsLock, LockDepMutex, Locked, Unlocked};
21use starnix_types::convert::IntoFidl as _;
22use starnix_uapi::auth::Credentials;
23use starnix_uapi::device_id::DeviceId;
24use starnix_uapi::errors::{ENOSPC, 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: LockDepMutex<HashMap<&'static str, Arc<FileServerStats>>, FileServerStatsLock>,
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: LockDepMutex::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 mut receiver: futures::channel::mpsc::UnboundedReceiver<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 Some(w) = receiver.next().await {
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: futures::channel::mpsc::UnboundedSender<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) = futures::channel::mpsc::unbounded();
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 .unbounded_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 .unbounded_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 is_sealed(&self) -> bool {
438 matches!(self.sink, Some(directory::dirents_sink::AppendResult::Sealed(_)))
439 }
440
441 fn append(
442 &mut self,
443 entry: &directory::entry::EntryInfo,
444 name: &str,
445 ) -> Result<(), Errno> {
446 let sink = self.sink.take();
449 match sink {
450 s @ Some(directory::dirents_sink::AppendResult::Sealed(_)) => {
451 self.sink = s;
452 error!(ENOSPC)
453 }
454 Some(directory::dirents_sink::AppendResult::Ok(sink)) => {
455 self.sink = Some(sink.append(entry, name));
456 if self.is_sealed() {
457 error!(ENOSPC)
466 } else {
467 Ok(())
468 }
469 }
470 None => error!(ENOTSUP),
471 }
472 }
473 }
474 impl<'a> DirentSink for DirentSinkAdapter<'a> {
475 fn add(
476 &mut self,
477 inode_num: ino_t,
478 offset: off_t,
479 entry_type: DirectoryEntryType,
480 name: &FsStr,
481 ) -> Result<(), Errno> {
482 if name != ".." {
484 if let Some(dirent_type) =
486 fio::DirentType::from_primitive(entry_type.bits())
487 {
488 let entry_info =
489 directory::entry::EntryInfo::new(inode_num, dirent_type);
490 self.append(&entry_info, &String::from_utf8_lossy(name))?
491 }
492 }
493 *self.offset = offset;
494 Ok(())
495 }
496 fn offset(&self) -> off_t {
497 *self.offset
498 }
499 }
500 let offset = match pos {
501 directory::traversal_position::TraversalPosition::Start => 0,
502 directory::traversal_position::TraversalPosition::Index(v) => v as i64,
503 directory::traversal_position::TraversalPosition::End => {
504 return Ok((
505 directory::traversal_position::TraversalPosition::End,
506 sink.seal(),
507 ));
508 }
509 _ => return error!(EINVAL),
510 };
511 if file.offset.read() != offset {
512 file.seek(locked, current_task, SeekTarget::Set(offset))?;
513 }
514 let mut file_offset = file.offset.copy();
515 let sink_result = {
516 let mut dirent_sink = DirentSinkAdapter {
517 sink: Some(directory::dirents_sink::AppendResult::Ok(sink)),
518 offset: &mut *file_offset,
519 };
520 match file.readdir(locked, current_task, &mut dirent_sink) {
521 Ok(()) => {}
522 Err(err) if err == ENOSPC => {
523 if !dirent_sink.is_sealed() {
529 return Err(err);
530 }
531 }
532 Err(err) => return Err(err),
533 }
534 dirent_sink.sink
535 };
536 let ret = match sink_result {
537 Some(directory::dirents_sink::AppendResult::Sealed(seal)) => Ok((
538 directory::traversal_position::TraversalPosition::Index(*file_offset as u64),
539 seal,
540 )),
541 Some(directory::dirents_sink::AppendResult::Ok(sink)) => {
542 Ok((directory::traversal_position::TraversalPosition::End, sink.seal()))
543 }
544 None => error!(ENOTSUP),
545 };
546 file_offset.update();
547 ret
548 })
549 }
550
551 fn directory_entry_open(
553 self: Arc<Self>,
554 scope: execution_scope::ExecutionScope,
555 flags: impl ProtocolsExt,
556 path: path::Path,
557 object_request: ObjectRequestRef<'_>,
558 ) -> Result<(), zx::Status> {
559 if self.is_dir() {
560 if path.is_dot() {
561 let dir = self.reopen(&flags)?;
563 object_request
564 .take()
565 .create_connection_sync::<MutableConnection<_>, _>(scope, dir, flags);
566 return Ok(());
567 }
568
569 let starnix_file = self.spawn_task({
571 let credentials = self.credentials.clone();
572 let stats = self.stats.clone();
573 let create_directory = flags.creation_mode() != vfs::common::CreationMode::Never
574 && flags.create_directory();
575 let open_flags = to_open_flags(&flags);
576 async move |locked, current_task, file| {
577 let (node, name) = lookup_parent(locked, current_task, file, path)?;
578 let file = match current_task.open_namespace_node_at(
579 locked,
580 node.clone(),
581 name.as_ref(),
582 open_flags,
583 FileMode::ALLOW_ALL,
584 ResolveFlags::empty(),
585 AccessCheck::default(),
586 ) {
587 Err(e) if e == errno!(EISDIR) && create_directory => {
588 let mode = current_task
589 .fs()
590 .apply_umask(FileMode::from_bits(0o777) | FileMode::IFDIR);
591 let name = node.create_node(
592 locked,
593 ¤t_task,
594 name.as_ref(),
595 mode,
596 DeviceId::NONE,
597 )?;
598 name.open(
599 locked,
600 ¤t_task,
601 open_flags & !(OpenFlags::CREAT | OpenFlags::EXCL),
602 AccessCheck::skip(),
603 )?
604 }
605 f => f?,
606 };
607 Ok(StarnixNodeConnection::new(¤t_task.kernel(), file, credentials, stats))
608 }
609 })?;
610
611 return starnix_file.directory_entry_open(
612 scope,
613 flags,
614 path::Path::dot(),
615 object_request,
616 );
617 }
618
619 if !path.is_dot() {
621 return Err(zx::Status::NOT_DIR);
622 }
623 let file = self.reopen(&flags)?;
624 object_request
625 .take()
626 .create_connection_sync::<file::RawIoConnection<_>, _>(scope, file, flags);
627 Ok(())
628 }
629
630 fn get_attributes(
631 &self,
632 requested_attributes: fio::NodeAttributesQuery,
633 ) -> fio::NodeAttributes2 {
634 self.spawn_task(async move |_, _, file| {
635 let info = file.node().info();
636
637 #[allow(clippy::unnecessary_cast)]
639 let link_count = info.link_count as u64;
640
641 let (protocols, abilities) = if info.mode.contains(FileMode::IFDIR) {
642 (
643 fio::NodeProtocolKinds::DIRECTORY,
644 fio::Operations::GET_ATTRIBUTES
645 | fio::Operations::UPDATE_ATTRIBUTES
646 | fio::Operations::ENUMERATE
647 | fio::Operations::TRAVERSE
648 | fio::Operations::MODIFY_DIRECTORY,
649 )
650 } else {
651 (
652 fio::NodeProtocolKinds::FILE,
653 fio::Operations::GET_ATTRIBUTES
654 | fio::Operations::UPDATE_ATTRIBUTES
655 | fio::Operations::READ_BYTES
656 | fio::Operations::WRITE_BYTES,
657 )
658 };
659
660 Ok(attributes!(
661 requested_attributes,
662 Mutable {
663 creation_time: info.time_status_change.into_nanos() as u64,
664 modification_time: info.time_modify.into_nanos() as u64,
665 mode: info.mode.bits(),
666 uid: info.uid,
667 gid: info.gid,
668 rdev: info.rdev.bits(),
669 },
670 Immutable {
671 protocols: protocols,
672 abilities: abilities,
673 content_size: info.size as u64,
674 storage_size: info.storage_size() as u64,
675 link_count: link_count,
676 id: file.fs.dev_id.bits(),
677 }
678 ))
679 })
680 .expect("spawn_task")
681 }
682
683 fn update_attributes(&self, attributes: fio::MutableNodeAttributes) {
684 let _ = self.spawn_task(async move |_, _, file| {
685 file.node().update_info(|info| {
686 if let Some(time) = attributes.creation_time {
687 info.time_status_change = UtcInstant::from_nanos(time as i64);
688 }
689 if let Some(time) = attributes.modification_time {
690 info.time_modify = UtcInstant::from_nanos(time as i64);
691 }
692 if let Some(mode) = attributes.mode {
693 info.mode = FileMode::from_bits(mode);
694 }
695 if let Some(uid) = attributes.uid {
696 info.uid = uid;
697 }
698 if let Some(gid) = attributes.gid {
699 info.gid = gid;
700 }
701 if let Some(rdev) = attributes.rdev {
702 info.rdev = DeviceId::from_bits(rdev);
703 }
704 });
705 Ok(())
706 });
707 }
708}
709
710impl vfs::node::Node for StarnixNodeConnection {
711 async fn get_attributes(
712 &self,
713 requested_attributes: fio::NodeAttributesQuery,
714 ) -> Result<fio::NodeAttributes2, zx::Status> {
715 Ok(StarnixNodeConnection::get_attributes(self, requested_attributes))
716 }
717}
718
719impl directory::entry::GetEntryInfo for StarnixNodeConnection {
720 fn entry_info(&self) -> directory::entry::EntryInfo {
721 let dirent_type =
722 if self.is_dir() { fio::DirentType::Directory } else { fio::DirentType::File };
723 directory::entry::EntryInfo::new(0, dirent_type)
724 }
725}
726
727impl directory::entry_container::Directory for StarnixNodeConnection {
728 fn open(
729 self: Arc<Self>,
730 scope: execution_scope::ExecutionScope,
731 path: path::Path,
732 flags: fio::Flags,
733 object_request: ObjectRequestRef<'_>,
734 ) -> Result<(), zx::Status> {
735 self.directory_entry_open(scope, flags, path, object_request)
736 }
737
738 async fn read_dirents(
739 &self,
740 pos: &directory::traversal_position::TraversalPosition,
741 sink: Box<dyn directory::dirents_sink::Sink>,
742 ) -> Result<
743 (
744 directory::traversal_position::TraversalPosition,
745 Box<dyn directory::dirents_sink::Sealed>,
746 ),
747 zx::Status,
748 > {
749 StarnixNodeConnection::directory_read_dirents(self, pos, sink).map_err(Errno::into)
750 }
751 fn register_watcher(
752 self: Arc<Self>,
753 _scope: execution_scope::ExecutionScope,
754 _mask: fio::WatchMask,
755 _watcher: directory::entry_container::DirectoryWatcher,
756 ) -> Result<(), zx::Status> {
757 track_stub!(TODO("https://fxbug.dev/322875605"), "register directory watcher");
758 Ok(())
759 }
760 fn unregister_watcher(self: Arc<Self>, _key: usize) {}
761}
762
763impl directory::entry_container::MutableDirectory for StarnixNodeConnection {
764 async fn update_attributes(
765 &self,
766 attributes: fio::MutableNodeAttributes,
767 ) -> Result<(), zx::Status> {
768 StarnixNodeConnection::update_attributes(self, attributes);
769 Ok(())
770 }
771 async fn unlink(
772 self: Arc<Self>,
773 name: &str,
774 must_be_directory: bool,
775 ) -> Result<(), zx::Status> {
776 let name = FsString::from(name.to_owned());
777 self.spawn_task_async(async move |locked, current_task, file| {
778 let kind =
779 if must_be_directory { UnlinkKind::Directory } else { UnlinkKind::NonDirectory };
780 file.name.entry.unlink(
781 locked,
782 current_task,
783 &file.name.mount,
784 name.as_ref(),
785 kind,
786 false,
787 )
788 })
789 .await?;
790 Ok(())
791 }
792 async fn sync(&self) -> Result<(), zx::Status> {
793 Ok(())
794 }
795 fn rename(
796 self: Arc<Self>,
797 src_dir: Arc<dyn directory::entry_container::MutableDirectory>,
798 src_name: path::Path,
799 dst_name: path::Path,
800 ) -> BoxFuture<'static, Result<(), zx::Status>> {
801 let this = self.clone();
802 Box::pin(async move {
803 Ok(self
804 .spawn_task_async(async move |locked, current_task, file| {
805 let src_dir = src_dir
806 .into_any()
807 .downcast::<StarnixNodeConnection>()
808 .map_err(|_| errno!(EXDEV))?;
809 let (dst_node, dst_name) =
810 lookup_parent(locked, current_task, &file, dst_name)?;
811 let (src_node, src_name) = if Arc::ptr_eq(&src_dir, &this) {
812 lookup_parent(locked, current_task, &file, src_name)?
813 } else {
814 src_dir.lookup_parent(src_name)?
815 };
816 NamespaceNode::rename(
817 locked,
818 current_task,
819 &src_node,
820 src_name.as_ref(),
821 &dst_node,
822 dst_name.as_ref(),
823 RenameFlags::empty(),
824 )
825 })
826 .await?)
827 })
828 }
829}
830
831impl file::File for StarnixNodeConnection {
832 fn writable(&self) -> bool {
833 true
834 }
835 async fn open_file(&self, _optionss: &file::FileOptions) -> Result<(), zx::Status> {
836 Ok(())
837 }
838 async fn truncate(&self, length: u64) -> Result<(), zx::Status> {
839 Ok(self
840 .spawn_task_async(async move |locked, current_task, file| {
841 file.ftruncate(locked, current_task, length)
844 })
845 .await?)
846 }
847 async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, zx::Status> {
848 Ok(self
849 .spawn_task_async(async move |locked, current_task, file| {
850 (|| {
851 let mut prot_flags = ProtectionFlags::empty();
852 if flags.contains(fio::VmoFlags::READ) {
853 prot_flags |= ProtectionFlags::READ;
854 }
855 if flags.contains(fio::VmoFlags::WRITE) {
856 prot_flags |= ProtectionFlags::WRITE;
857 }
858 if flags.contains(fio::VmoFlags::EXECUTE) {
859 prot_flags |= ProtectionFlags::EXEC;
860 }
861 let memory = file.get_memory(locked, current_task, None, prot_flags)?;
862 let vmo = memory.as_vmo().ok_or(zx::Status::NOT_SUPPORTED)?;
863 if flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
864 let size = vmo.get_size()?;
865 vmo.create_child(zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE, 0, size)
866 } else {
867 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)
868 }
869 })()
870 .map_err(|e| from_status_like_fdio!(e))
871 })
872 .await?)
873 }
874
875 async fn get_size(&self) -> Result<u64, zx::Status> {
876 Ok(self
877 .spawn_task_async(async move |_, _, file| Ok(file.node().info().size as u64))
878 .await?)
879 }
880 async fn update_attributes(
881 &self,
882 attributes: fio::MutableNodeAttributes,
883 ) -> Result<(), zx::Status> {
884 StarnixNodeConnection::update_attributes(self, attributes);
885 Ok(())
886 }
887 async fn sync(&self, _mode: file::SyncMode) -> Result<(), zx::Status> {
888 Ok(())
889 }
890}
891
892impl file::RawFileIoConnection for StarnixNodeConnection {
893 async fn read(&self, count: u64) -> Result<Vec<u8>, zx::Status> {
894 self.stats.reads.fetch_add(1, Ordering::Relaxed);
895 let data: Vec<u8> = self
896 .spawn_task_async(async move |locked, current_task, file| {
897 let mut data = VecOutputBuffer::new(count as usize);
898 file.read(locked, current_task, &mut data)?;
899 Ok(data.into())
900 })
901 .await?;
902 self.stats.read_bytes.fetch_add(data.len() as u64, Ordering::Relaxed);
903 Ok(data)
904 }
905
906 async fn read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, zx::Status> {
907 self.stats.reads.fetch_add(1, Ordering::Relaxed);
908 let data: Vec<u8> = self
909 .spawn_task_async(async move |locked, current_task, file| {
910 let mut data = VecOutputBuffer::new(count as usize);
911 file.read_at(locked, current_task, offset as usize, &mut data)?;
912 Ok(data.into())
913 })
914 .await?;
915 self.stats.read_bytes.fetch_add(data.len() as u64, Ordering::Relaxed);
916 Ok(data)
917 }
918
919 async fn write(&self, content: &[u8]) -> Result<u64, zx::Status> {
920 self.stats.writes.fetch_add(1, Ordering::Relaxed);
921 let mut data = VecInputBuffer::new(content);
922 let written = self
923 .spawn_task_async(async move |locked, current_task, file| {
924 let written = file.write(locked, current_task, &mut data)?;
925 Ok(written as u64)
926 })
927 .await?;
928 self.stats.write_bytes.fetch_add(written, Ordering::Relaxed);
929 Ok(written)
930 }
931
932 async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, zx::Status> {
933 self.stats.writes.fetch_add(1, Ordering::Relaxed);
934 let mut data = VecInputBuffer::new(content);
935 let written = self
936 .spawn_task_async(async move |locked, current_task, file| {
937 let written = file.write_at(locked, current_task, offset as usize, &mut data)?;
938 Ok(written as u64)
939 })
940 .await?;
941 self.stats.write_bytes.fetch_add(written as u64, Ordering::Relaxed);
942 Ok(written)
943 }
944
945 async fn seek(&self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, zx::Status> {
946 let target = match origin {
947 fio::SeekOrigin::Start => SeekTarget::Set(offset),
948 fio::SeekOrigin::Current => SeekTarget::Cur(offset),
949 fio::SeekOrigin::End => SeekTarget::End(offset),
950 };
951 Ok(self.spawn_task(async move |locked, current_task, file| {
952 let seek_result = file.seek(locked, current_task, target)?;
953 Ok(seek_result as u64)
954 })?)
955 }
956
957 fn set_flags(&self, flags: fio::Flags) -> Result<(), zx::Status> {
958 const SETTABLE_FLAGS_MASK: OpenFlags = OpenFlags::APPEND;
965 let flags = if flags.contains(fio::Flags::FILE_APPEND) {
966 OpenFlags::APPEND
967 } else {
968 OpenFlags::empty()
969 };
970 Ok(self.spawn_task(async move |_, _, file| {
971 file.update_file_flags(flags, SETTABLE_FLAGS_MASK);
972 Ok(())
973 })?)
974 }
975}
976
977#[cfg(test)]
978mod tests {
979 use super::*;
980 use crate::fs::tmpfs::TmpFs;
981 use crate::testing::*;
982 use crate::vfs::{FsString, Namespace};
983 use starnix_uapi::auth::{Capabilities, Credentials};
984 use std::collections::HashSet;
985 use syncio::{Zxio, ZxioOpenOptions, zxio_node_attr_has_t};
986
987 fn assert_directory_content(zxio: &Zxio, content: &[&[u8]]) {
988 let expected = content.iter().map(|&x| FsString::from(x)).collect::<HashSet<_>>();
989 let mut iterator = zxio.create_dirent_iterator().expect("iterator");
990 iterator.rewind().expect("iterator");
991 let found =
992 iterator.map(|x| x.as_ref().expect("dirent").name.clone()).collect::<HashSet<_>>();
993 assert_eq!(found, expected);
994 }
995
996 #[::fuchsia::test]
997 async fn access_file_system() {
998 spawn_kernel_and_run(async |locked, current_task| {
999 let kernel = current_task.kernel();
1000 let fs = TmpFs::new_fs(locked, &kernel);
1001
1002 let file =
1003 &fs.root().open_anonymous(locked, current_task, OpenFlags::RDWR).expect("open");
1004 let (root_handle, scope) =
1005 serve_file(current_task, file, Credentials::root()).expect("serve");
1006
1007 let fs_dev_id = fs.dev_id;
1010 std::thread::spawn(move || {
1011 let root_zxio = Zxio::create(root_handle.into_channel().into()).expect("create");
1012
1013 assert_directory_content(&root_zxio, &[b"."]);
1014 assert_directory_content(&root_zxio, &[b"."]);
1016
1017 let attrs = root_zxio
1018 .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
1019 .expect("attr_get");
1020 assert_eq!(attrs.id, fs_dev_id.bits());
1021
1022 let mut attrs = syncio::zxio_node_attributes_t::default();
1023 attrs.has.creation_time = true;
1024 attrs.has.modification_time = true;
1025 attrs.creation_time = 0;
1026 attrs.modification_time = 42;
1027 root_zxio.attr_set(&attrs).expect("attr_set");
1028 let attrs = root_zxio
1029 .attr_get(zxio_node_attr_has_t {
1030 creation_time: true,
1031 modification_time: true,
1032 ..Default::default()
1033 })
1034 .expect("attr_get");
1035 assert_eq!(attrs.creation_time, 0);
1036 assert_eq!(attrs.modification_time, 42);
1037
1038 assert_eq!(
1039 root_zxio
1040 .open("foo", fio::PERM_READABLE | fio::PERM_WRITABLE, Default::default())
1041 .expect_err("open"),
1042 zx::Status::NOT_FOUND
1043 );
1044 let foo_zxio = root_zxio
1045 .open(
1046 "foo",
1047 fio::PERM_READABLE
1048 | fio::PERM_WRITABLE
1049 | fio::Flags::FLAG_MAYBE_CREATE
1050 | fio::Flags::PROTOCOL_FILE,
1051 Default::default(),
1052 )
1053 .expect("zxio_open");
1054 assert_directory_content(&root_zxio, &[b".", b"foo"]);
1055
1056 assert_eq!(foo_zxio.write(b"hello").expect("write"), 5);
1057 assert_eq!(foo_zxio.write_at(2, b"ch").expect("write_at"), 2);
1058 let mut buffer = [0; 7];
1059 assert_eq!(foo_zxio.read_at(2, &mut buffer).expect("read_at"), 3);
1060 assert_eq!(&buffer[..3], b"cho");
1061 assert_eq!(foo_zxio.seek(syncio::SeekOrigin::Start, 0).expect("seek"), 0);
1062 assert_eq!(foo_zxio.read(&mut buffer).expect("read"), 5);
1063 assert_eq!(&buffer[..5], b"hecho");
1064
1065 let attrs = foo_zxio
1066 .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
1067 .expect("attr_get");
1068 assert_eq!(attrs.id, fs_dev_id.bits());
1069
1070 let mut attrs = syncio::zxio_node_attributes_t::default();
1071 attrs.has.creation_time = true;
1072 attrs.has.modification_time = true;
1073 attrs.creation_time = 0;
1074 attrs.modification_time = 42;
1075 foo_zxio.attr_set(&attrs).expect("attr_set");
1076 let attrs = foo_zxio
1077 .attr_get(zxio_node_attr_has_t {
1078 creation_time: true,
1079 modification_time: true,
1080 ..Default::default()
1081 })
1082 .expect("attr_get");
1083 assert_eq!(attrs.creation_time, 0);
1084 assert_eq!(attrs.modification_time, 42);
1085
1086 assert_eq!(
1087 root_zxio
1088 .open(
1089 "bar/baz",
1090 fio::Flags::PROTOCOL_DIRECTORY
1091 | fio::Flags::FLAG_MAYBE_CREATE
1092 | fio::PERM_READABLE
1093 | fio::PERM_WRITABLE,
1094 Default::default(),
1095 )
1096 .expect_err("open"),
1097 zx::Status::NOT_FOUND
1098 );
1099
1100 let bar_zxio = root_zxio
1101 .open(
1102 "bar",
1103 fio::Flags::PROTOCOL_DIRECTORY
1104 | fio::Flags::FLAG_MAYBE_CREATE
1105 | fio::PERM_READABLE
1106 | fio::PERM_WRITABLE,
1107 Default::default(),
1108 )
1109 .expect("open");
1110 let baz_zxio = root_zxio
1111 .open(
1112 "bar/baz",
1113 fio::Flags::PROTOCOL_DIRECTORY
1114 | fio::Flags::FLAG_MAYBE_CREATE
1115 | fio::PERM_READABLE
1116 | fio::PERM_WRITABLE,
1117 Default::default(),
1118 )
1119 .expect("open");
1120 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1121 assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1122
1123 bar_zxio.rename("baz", &root_zxio, "quz").expect("rename");
1124 assert_directory_content(&bar_zxio, &[b"."]);
1125 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar", b"quz"]);
1126 assert_directory_content(&baz_zxio, &[b"."]);
1127 })
1128 .join()
1129 .expect("join");
1130 scope.shutdown();
1131 scope.wait().await;
1132 std::mem::drop(fs);
1134 })
1135 .await;
1136 }
1137
1138 #[::fuchsia::test]
1139 async fn serve_file_strips_trunc() {
1140 spawn_kernel_and_run(async |locked, current_task| {
1141 let kernel = current_task.kernel();
1142 let fs = TmpFs::new_fs(locked, &kernel);
1143 let ns = Namespace::new(fs);
1144 let root = ns.root();
1145
1146 let file_node = root
1147 .create_node(
1148 locked,
1149 current_task,
1150 b"test".into(),
1151 FileMode::IFREG | FileMode::ALLOW_ALL,
1152 DeviceId::NONE,
1153 )
1154 .expect("create_node");
1155
1156 let file = file_node
1157 .open(locked, current_task, OpenFlags::RDWR, AccessCheck::skip())
1158 .expect("open");
1159 file.write(locked, current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1160
1161 let file_to_serve = current_task
1163 .open_namespace_node_at(
1164 locked,
1165 root,
1166 b"test".into(),
1167 OpenFlags::RDWR | OpenFlags::TRUNC,
1168 FileMode::default(),
1169 ResolveFlags::default(),
1170 AccessCheck::skip(),
1171 )
1172 .expect("open O_TRUNC");
1173
1174 assert_eq!(
1176 file_to_serve.node().fetch_and_refresh_info(locked, current_task).unwrap().size,
1177 0
1178 );
1179
1180 file_to_serve
1182 .write(locked, current_task, &mut VecInputBuffer::new(b"world"))
1183 .expect("write world");
1184 assert_eq!(file_to_serve.node().info().size, 5);
1185
1186 let (client_end, scope) =
1187 serve_file(current_task, &file_to_serve, Credentials::root()).expect("serve");
1188
1189 fuchsia_async::unblock(|| {
1190 let zxio = Zxio::create(client_end.into_channel().into()).expect("create");
1191 let mut attr = syncio::zxio_node_attributes_t::default();
1192 attr.has.content_size = true;
1193 let attr = zxio.attr_get(attr.has).expect("attr_get");
1194 assert_eq!(attr.content_size, 5);
1196 })
1197 .await;
1198
1199 scope.shutdown();
1200 scope.wait().await;
1201 })
1202 .await;
1203 }
1204
1205 #[::fuchsia::test]
1206 async fn truncate_checks_fd_permissions() {
1207 spawn_kernel_and_run(async |locked, current_task| {
1208 let kernel = current_task.kernel();
1209 let fs = TmpFs::new_fs(locked, &kernel);
1210 let ns = Namespace::new(fs);
1211 let root = ns.root();
1212
1213 let file_node = root
1214 .create_node(
1215 locked,
1216 current_task,
1217 "test".into(),
1218 FileMode::IFREG | FileMode::IRWXU,
1219 DeviceId::NONE,
1220 )
1221 .expect("create_node");
1222
1223 let file = file_node
1224 .open(locked, current_task, OpenFlags::RDWR, AccessCheck::skip())
1225 .expect("open");
1226 file.write(locked, current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1227
1228 let (client_end, scope) = serve_file(
1230 current_task,
1231 &file,
1232 Arc::new(Credentials {
1233 fsuid: 2000,
1234 cap_effective: Capabilities::empty(),
1235 ..Credentials::clone(¤t_task.current_creds())
1236 }),
1237 )
1238 .expect("serve");
1239
1240 fuchsia_async::unblock(move || {
1241 let zxio = Zxio::create(client_end.into_channel().into()).expect("create");
1242 zxio.truncate(2).expect("truncate");
1245
1246 let mut attr = syncio::zxio_node_attributes_t::default();
1247 attr.has.content_size = true;
1248 let attr = zxio.attr_get(attr.has).expect("attr_get");
1249 assert_eq!(attr.content_size, 2);
1250 })
1251 .await;
1252
1253 scope.shutdown();
1254 scope.wait().await;
1255 })
1256 .await;
1257 }
1258
1259 #[::fuchsia::test]
1260 async fn open() {
1261 spawn_kernel_and_run(async |locked, current_task| {
1262 let kernel = current_task.kernel();
1263 let fs = TmpFs::new_fs(locked, &kernel);
1264
1265 let file = &fs
1266 .root()
1267 .open_anonymous(locked, current_task, OpenFlags::RDWR)
1268 .expect("open_anonymous failed");
1269 let (root_handle, scope) =
1270 serve_file(current_task, file, Credentials::root()).expect("serve_file failed");
1271
1272 std::thread::spawn(move || {
1273 let root_zxio =
1274 Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1275
1276 assert_directory_content(&root_zxio, &[b"."]);
1277 assert_eq!(
1278 root_zxio
1279 .open(
1280 "foo",
1281 fio::PERM_READABLE | fio::PERM_WRITABLE,
1282 ZxioOpenOptions::default()
1283 )
1284 .expect_err("open3 passed unexpectedly"),
1285 zx::Status::NOT_FOUND
1286 );
1287 root_zxio
1288 .open(
1289 "foo",
1290 fio::Flags::PROTOCOL_FILE
1291 | fio::PERM_READABLE
1292 | fio::PERM_WRITABLE
1293 | fio::Flags::FLAG_MUST_CREATE,
1294 ZxioOpenOptions::default(),
1295 )
1296 .expect("open3 failed");
1297 assert_directory_content(&root_zxio, &[b".", b"foo"]);
1298
1299 assert_eq!(
1300 root_zxio
1301 .open(
1302 "bar/baz",
1303 fio::Flags::PROTOCOL_DIRECTORY
1304 | fio::PERM_READABLE
1305 | fio::PERM_WRITABLE
1306 | fio::Flags::FLAG_MUST_CREATE,
1307 ZxioOpenOptions::default()
1308 )
1309 .expect_err("open3 passed unexpectedly"),
1310 zx::Status::NOT_FOUND
1311 );
1312 let bar_zxio = root_zxio
1313 .open(
1314 "bar",
1315 fio::Flags::PROTOCOL_DIRECTORY
1316 | fio::PERM_READABLE
1317 | fio::PERM_WRITABLE
1318 | fio::Flags::FLAG_MUST_CREATE,
1319 ZxioOpenOptions::default(),
1320 )
1321 .expect("open3 failed");
1322 root_zxio
1323 .open(
1324 "bar/baz",
1325 fio::Flags::PROTOCOL_DIRECTORY
1326 | fio::PERM_READABLE
1327 | fio::PERM_WRITABLE
1328 | fio::Flags::FLAG_MUST_CREATE,
1329 ZxioOpenOptions::default(),
1330 )
1331 .expect("open3 failed");
1332 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1333 assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1334 })
1335 .join()
1336 .expect("join");
1337 scope.shutdown();
1338 scope.wait().await;
1339
1340 std::mem::drop(fs);
1342 })
1343 .await;
1344 }
1345
1346 #[::fuchsia::test]
1347 async fn use_credentials() {
1348 spawn_kernel_and_run(async |locked, current_task| {
1349 let kernel = current_task.kernel();
1350 let fs = TmpFs::new_fs(locked, &kernel);
1351
1352 let file = &fs
1353 .root()
1354 .open_anonymous(locked, current_task, OpenFlags::RDWR)
1355 .expect("open_anonymous failed");
1356 let ns = Namespace::new(fs);
1358 ns.root()
1359 .open_create_node(
1360 locked,
1361 current_task,
1362 "test".into(),
1363 FileMode::from_bits(0o600) | FileMode::IFREG,
1364 DeviceId::NONE,
1365 OpenFlags::empty(),
1366 )
1367 .expect("open_create_node failed");
1368
1369 let mut creds = Credentials::with_ids(0, 0);
1370 creds.fsuid = 1;
1371 creds.cap_effective = Capabilities::empty();
1372
1373 let (root_handle, scope) =
1374 serve_file(current_task, file, creds.into()).expect("serve_file failed");
1375
1376 std::thread::spawn(move || {
1377 let root_zxio =
1378 Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1379
1380 assert_directory_content(&root_zxio, &[b".", b"test"]);
1381 assert_eq!(
1382 root_zxio
1383 .open(
1384 "test",
1385 fio::PERM_READABLE | fio::PERM_WRITABLE,
1386 ZxioOpenOptions::default()
1387 )
1388 .expect_err("open3 passed unexpectedly"),
1389 zx::Status::ACCESS_DENIED
1390 );
1391 })
1392 .join()
1393 .expect("join");
1394 scope.shutdown();
1395 scope.wait().await;
1396
1397 std::mem::drop(ns);
1399 })
1400 .await;
1401 }
1402
1403 #[::fuchsia::test]
1404 async fn large_directory_listing() {
1405 spawn_kernel_and_run(async |locked, current_task| {
1406 let kernel = current_task.kernel();
1407 let fs = TmpFs::new_fs(locked, &kernel);
1408
1409 let file = &fs
1410 .root()
1411 .open_anonymous(locked, current_task, OpenFlags::RDWR)
1412 .expect("open_anonymous failed");
1413
1414 let ns = Namespace::new(fs);
1415 let mut expected_files = vec![b".".to_vec()];
1416 for i in 0..500 {
1419 let name = format!("file_{:03}", i);
1420 ns.root()
1421 .open_create_node(
1422 locked,
1423 current_task,
1424 name.as_str().into(),
1425 FileMode::from_bits(0o600) | FileMode::IFREG,
1426 DeviceId::NONE,
1427 OpenFlags::empty(),
1428 )
1429 .expect("open_create_node failed");
1430 expected_files.push(name.into_bytes());
1431 }
1432
1433 let (root_handle, scope) =
1434 serve_file(current_task, file, Credentials::root()).expect("serve_file failed");
1435
1436 std::thread::spawn(move || {
1437 let root_zxio =
1438 Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1439
1440 let expected = expected_files
1441 .iter()
1442 .map(|x| FsString::from(x.clone()))
1443 .collect::<HashSet<_>>();
1444 let mut iterator =
1445 root_zxio.create_dirent_iterator().expect("create_dirent_iterator failed");
1446 iterator.rewind().expect("rewind failed");
1447
1448 let mut found = HashSet::new();
1449 for res in iterator {
1450 match res {
1451 Ok(dirent) => {
1452 found.insert(dirent.name.clone());
1453 }
1454 Err(status) => {
1455 panic!("Iterator returned error: {:?}", status);
1456 }
1457 }
1458 }
1459 assert_eq!(found, expected);
1460 })
1461 .join()
1462 .expect("thread join failed");
1463 scope.shutdown();
1464 scope.wait().await;
1465
1466 std::mem::drop(ns);
1468 })
1469 .await;
1470 }
1471
1472 #[::fuchsia::test]
1473 async fn readdir_propagates_genuine_nospc_error() {
1474 spawn_kernel_and_run(async |locked, current_task| {
1475 use crate::vfs::{
1476 DirEntry, FileOps, FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps,
1477 fs_node_impl_dir_readonly,
1478 };
1479 use crate::{
1480 fileops_impl_directory, fileops_impl_noop_sync, fileops_impl_unbounded_seek,
1481 };
1482 use starnix_sync::FileOpsCore;
1483
1484 struct FaultyDirectory;
1485 impl FileOps for FaultyDirectory {
1486 fileops_impl_directory!();
1487 fileops_impl_noop_sync!();
1488 fileops_impl_unbounded_seek!();
1489
1490 fn readdir(
1491 &self,
1492 _locked: &mut Locked<FileOpsCore>,
1493 _file: &FileObject,
1494 _current_task: &CurrentTask,
1495 _sink: &mut dyn DirentSink,
1496 ) -> Result<(), Errno> {
1497 error!(ENOSPC)
1498 }
1499 }
1500
1501 struct FaultyDirectoryNode;
1502 impl FsNodeOps for FaultyDirectoryNode {
1503 fs_node_impl_dir_readonly!();
1504
1505 fn create_file_ops(
1506 &self,
1507 _locked: &mut Locked<FileOpsCore>,
1508 _node: &FsNode,
1509 _current_task: &CurrentTask,
1510 _flags: OpenFlags,
1511 ) -> Result<Box<dyn FileOps>, Errno> {
1512 Ok(Box::new(FaultyDirectory))
1513 }
1514
1515 fn lookup(
1516 &self,
1517 _locked: &mut Locked<FileOpsCore>,
1518 _node: &FsNode,
1519 _current_task: &CurrentTask,
1520 _name: &FsStr,
1521 ) -> Result<FsNodeHandle, Errno> {
1522 error!(ENOENT)
1523 }
1524 }
1525
1526 let kernel = current_task.kernel();
1527 let fs = TmpFs::new_fs(locked, &kernel);
1528
1529 let ino = fs.allocate_ino();
1530 let info = FsNodeInfo::new(
1531 FileMode::from_bits(0o777) | FileMode::IFDIR,
1532 current_task.current_fscred(),
1533 );
1534 let node = fs.create_node(ino, FaultyDirectoryNode, info);
1535 let dir_entry = DirEntry::new(node, None, "faulty_dir".into());
1536 let name = NamespaceNode::new_anonymous(dir_entry);
1537 let file = FileObject::new(
1538 locked,
1539 current_task,
1540 Box::new(FaultyDirectory),
1541 name,
1542 OpenFlags::DIRECTORY | OpenFlags::RDONLY,
1543 )
1544 .expect("FileObject::new failed");
1545
1546 let (root_handle, scope) =
1547 serve_file(current_task, &file, Credentials::root()).expect("serve_file failed");
1548
1549 std::thread::spawn(move || {
1550 let root_zxio =
1551 Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1552
1553 let mut iterator =
1554 root_zxio.create_dirent_iterator().expect("create_dirent_iterator failed");
1555 iterator.rewind().expect("rewind failed");
1556
1557 let mut got_error = false;
1558 for res in iterator {
1559 if let Err(status) = res {
1560 assert_eq!(status, zx::Status::NO_SPACE);
1561 got_error = true;
1562 break;
1563 }
1564 }
1565 assert!(got_error, "Expected iterator to fail with NO_SPACE");
1566 })
1567 .join()
1568 .expect("thread join failed");
1569 scope.shutdown();
1570 scope.wait().await;
1571 })
1572 .await;
1573 }
1574}