1use crate::mm::ProtectionFlags;
6use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
7use crate::task::{CurrentTask, Kernel};
8use crate::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
9use crate::vfs::{
10 DirectoryEntryType, DirectoryMode, DirentSink, FileHandle, FileObject, FsStr, FsString,
11 LookupContext, NamespaceNode, OpenAccessCheck, 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};
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::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: Default::default() }
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(self: Box<Self>, current_task: &CurrentTask, file: &FileHandle);
181}
182
183struct EitherSender<R>(Either<futures::channel::oneshot::Sender<R>, std::sync::mpsc::Sender<R>>);
184
185impl<R> From<futures::channel::oneshot::Sender<R>> for EitherSender<R> {
186 fn from(v: futures::channel::oneshot::Sender<R>) -> Self {
187 Self(Either::Left(v))
188 }
189}
190
191impl<R> From<std::sync::mpsc::Sender<R>> for EitherSender<R> {
192 fn from(v: std::sync::mpsc::Sender<R>) -> Self {
193 Self(Either::Right(v))
194 }
195}
196
197impl<R> EitherSender<R> {
198 async fn send(self, r: R) {
199 match self.0 {
200 Either::Left(s) => {
201 let _ = s.send(r);
202 }
203 Either::Right(s) => {
204 let _ = s.send(r);
205 }
206 }
207 }
208}
209
210struct WorkWrapper<R, F>
211where
212 R: Send + 'static,
213 F: AsyncFnOnce(&CurrentTask, &FileHandle) -> R + Send + 'static,
214{
215 f: F,
216 sender: EitherSender<R>,
217}
218
219#[async_trait::async_trait(?Send)]
220impl<R, F> Work for WorkWrapper<R, F>
221where
222 R: Send + 'static,
223 F: AsyncFnOnce(&CurrentTask, &FileHandle) -> R + Send + 'static,
224{
225 async fn run(self: Box<Self>, current_task: &CurrentTask, file: &FileHandle) {
226 let f: F = self.f;
227 let r = f(current_task, file).await;
228 self.sender.send(r).await;
229 }
230}
231
232async fn handle_file(
233 current_task: &CurrentTask,
234 credentials: Arc<Credentials>,
235 file: FileHandle,
236 mut receiver: futures::channel::mpsc::UnboundedReceiver<Box<dyn Work>>,
237) {
238 current_task
240 .override_creds_async(credentials.clone(), async || {
241 let file = match file.name.open(current_task, OpenAccessCheck::skip(file.flags())) {
243 Ok(file) => file,
244 Err(e) => {
245 log_error!("Unable to reopen file: {e:?}");
246 return;
247 }
248 };
249 while let Some(w) = receiver.next().await {
250 w.run(current_task, &file).await;
251 }
252 })
253 .await;
254}
255
256fn to_open_flags(flags: &impl ProtocolsExt) -> OpenFlags {
257 let rights = flags.rights().unwrap_or_default();
258 let mut open_flags = if rights.contains(fio::Operations::WRITE_BYTES) {
259 if rights.contains(fio::Operations::READ_BYTES) {
260 OpenFlags::RDWR
261 } else {
262 OpenFlags::WRONLY
263 }
264 } else {
265 OpenFlags::RDONLY
266 };
267
268 if flags.create_directory() {
269 open_flags |= OpenFlags::DIRECTORY;
270 }
271
272 match flags.creation_mode() {
273 vfs::CreationMode::Always => open_flags |= OpenFlags::CREAT | OpenFlags::EXCL,
274 vfs::CreationMode::AllowExisting => open_flags |= OpenFlags::CREAT,
275 vfs::CreationMode::UnnamedTemporary => open_flags |= OpenFlags::TMPFILE,
276 vfs::CreationMode::UnlinkableUnnamedTemporary => {
277 open_flags |= OpenFlags::TMPFILE | OpenFlags::EXCL
278 }
279 vfs::CreationMode::Never => {}
280 };
281
282 if flags.is_truncate() {
283 open_flags |= OpenFlags::TRUNC;
284 }
285
286 if flags.is_append() {
287 open_flags |= OpenFlags::APPEND;
288 }
289
290 open_flags
291}
292
293#[derive(Clone)]
306struct StarnixNodeConnection {
307 is_dir: bool,
308 credentials: Arc<Credentials>,
309 work_sender: futures::channel::mpsc::UnboundedSender<Box<dyn Work>>,
310 stats: Arc<FileServerStats>,
311}
312
313fn lookup_parent(
314 current_task: &CurrentTask,
315 file: &FileObject,
316 path: path::Path,
317) -> Result<(NamespaceNode, FsString), Errno> {
318 let (node, name) = current_task.lookup_parent(
319 &mut LookupContext::default(),
320 &file.name,
321 path.as_str().into(),
322 )?;
323 Ok((node, name.to_owned()))
324}
325
326impl StarnixNodeConnection {
327 fn new(
328 kernel: &Kernel,
329 file: FileHandle,
330 credentials: Arc<Credentials>,
331 stats: Arc<FileServerStats>,
332 ) -> Arc<Self> {
333 let (work_sender, receiver) = futures::channel::mpsc::unbounded();
334 let is_dir = file.node().is_dir();
335 let closure = {
336 let credentials = credentials.clone();
337 async move |current_task: &CurrentTask| {
338 handle_file(current_task, credentials, file, receiver).await;
339 }
340 };
341 let req = SpawnRequestBuilder::new().with_async_closure(closure).build();
342 kernel.kthreads.spawner().spawn_from_request(req);
343 Arc::new(Self { is_dir, credentials, work_sender, stats })
344 }
345
346 fn spawn_task<R, E, F>(&self, f: F) -> Result<R, Errno>
347 where
348 R: Send + 'static,
349 E: Send + 'static,
350 F: AsyncFnOnce(&CurrentTask, &FileHandle) -> Result<R, E> + Send + 'static,
351 Errno: From<E>,
352 {
353 let (sender, receiver) = std::sync::mpsc::channel();
354 self.work_sender
355 .unbounded_send(Box::new(WorkWrapper { f, sender: sender.into() }))
356 .map_err(|_| errno!(EIO))?;
357 Ok(receiver.recv().map_err(|_| errno!(EIO))??)
358 }
359
360 async fn spawn_task_async<R, E, F>(&self, f: F) -> Result<R, Errno>
361 where
362 R: Send + 'static,
363 E: Send + 'static,
364 F: AsyncFnOnce(&CurrentTask, &FileHandle) -> Result<R, E> + Send + 'static,
365 Errno: From<E>,
366 {
367 let (sender, receiver) = futures::channel::oneshot::channel();
368 self.work_sender
369 .unbounded_send(Box::new(WorkWrapper { f, sender: sender.into() }))
370 .map_err(|_| errno!(EIO))?;
371 Ok(receiver.await.map_err(|_| errno!(EIO))??)
372 }
373
374 fn is_dir(&self) -> bool {
375 self.is_dir
376 }
377
378 fn lookup_parent(&self, path: path::Path) -> Result<(NamespaceNode, FsString), Errno> {
379 self.spawn_task(async move |current_task, file| lookup_parent(current_task, file, path))
380 }
381
382 fn reopen(&self, flags: &impl ProtocolsExt) -> Result<Arc<Self>, Errno> {
385 let credentials = self.credentials.clone();
386 let flags = to_open_flags(flags);
387 let stats = self.stats.clone();
388 self.spawn_task(async move |current_task, file| {
389 let file = file.name.open(current_task, flags)?;
390 Ok(StarnixNodeConnection::new(¤t_task.kernel(), file, credentials, stats))
391 })
392 }
393
394 fn directory_read_dirents<'a>(
396 &'a self,
397 pos: &'a directory::traversal_position::TraversalPosition,
398 sink: Box<dyn directory::dirents_sink::Sink>,
399 ) -> Result<
400 (
401 directory::traversal_position::TraversalPosition,
402 Box<dyn directory::dirents_sink::Sealed>,
403 ),
404 Errno,
405 > {
406 let pos = pos.clone();
407 self.spawn_task(async move |current_task, file| {
408 struct DirentSinkAdapter<'a> {
409 sink: Option<directory::dirents_sink::AppendResult>,
410 offset: &'a mut off_t,
411 }
412 impl<'a> DirentSinkAdapter<'a> {
413 fn is_sealed(&self) -> bool {
414 matches!(self.sink, Some(directory::dirents_sink::AppendResult::Sealed(_)))
415 }
416
417 fn append(
418 &mut self,
419 entry: &directory::entry::EntryInfo,
420 name: &str,
421 ) -> Result<(), Errno> {
422 let sink = self.sink.take();
425 match sink {
426 s @ Some(directory::dirents_sink::AppendResult::Sealed(_)) => {
427 self.sink = s;
428 error!(ENOSPC)
429 }
430 Some(directory::dirents_sink::AppendResult::Ok(sink)) => {
431 self.sink = Some(sink.append(entry, name));
432 if self.is_sealed() {
433 error!(ENOSPC)
442 } else {
443 Ok(())
444 }
445 }
446 None => error!(ENOTSUP),
447 }
448 }
449 }
450 impl<'a> DirentSink for DirentSinkAdapter<'a> {
451 fn add(
452 &mut self,
453 inode_num: ino_t,
454 offset: off_t,
455 entry_type: DirectoryEntryType,
456 name: &FsStr,
457 ) -> Result<(), Errno> {
458 if name != ".." {
460 if let Some(dirent_type) =
462 fio::DirentType::from_primitive(entry_type.bits())
463 {
464 let entry_info =
465 directory::entry::EntryInfo::new(inode_num, dirent_type);
466 self.append(&entry_info, &String::from_utf8_lossy(name))?
467 }
468 }
469 *self.offset = offset;
470 Ok(())
471 }
472 fn offset(&self) -> off_t {
473 *self.offset
474 }
475 }
476 let offset = match pos {
477 directory::traversal_position::TraversalPosition::Start => 0,
478 directory::traversal_position::TraversalPosition::Index(v) => v as i64,
479 directory::traversal_position::TraversalPosition::End => {
480 return Ok((
481 directory::traversal_position::TraversalPosition::End,
482 sink.seal(),
483 ));
484 }
485 _ => return error!(EINVAL),
486 };
487 if file.offset.read() != offset {
488 file.seek(current_task, SeekTarget::Set(offset))?;
489 }
490 let mut file_offset = file.offset.copy();
491 let sink_result = {
492 let mut dirent_sink = DirentSinkAdapter {
493 sink: Some(directory::dirents_sink::AppendResult::Ok(sink)),
494 offset: &mut *file_offset,
495 };
496 match file.readdir(current_task, &mut dirent_sink) {
497 Ok(()) => {}
498 Err(err) if err == ENOSPC => {
499 if !dirent_sink.is_sealed() {
505 return Err(err);
506 }
507 }
508 Err(err) => return Err(err),
509 }
510 dirent_sink.sink
511 };
512 let ret = match sink_result {
513 Some(directory::dirents_sink::AppendResult::Sealed(seal)) => Ok((
514 directory::traversal_position::TraversalPosition::Index(*file_offset as u64),
515 seal,
516 )),
517 Some(directory::dirents_sink::AppendResult::Ok(sink)) => {
518 Ok((directory::traversal_position::TraversalPosition::End, sink.seal()))
519 }
520 None => error!(ENOTSUP),
521 };
522 file_offset.update();
523 ret
524 })
525 }
526
527 fn directory_entry_open(
529 self: Arc<Self>,
530 scope: execution_scope::ExecutionScope,
531 flags: impl ProtocolsExt,
532 path: path::Path,
533 object_request: ObjectRequestRef<'_>,
534 ) -> Result<(), zx::Status> {
535 if self.is_dir() {
536 if path.is_dot() {
537 let dir = self.reopen(&flags)?;
539 object_request
540 .take()
541 .create_connection_sync::<MutableConnection<_>, _>(scope, dir, flags);
542 return Ok(());
543 }
544
545 let starnix_file = self.spawn_task({
547 let credentials = self.credentials.clone();
548 let stats = self.stats.clone();
549 let create_directory = flags.creation_mode() != vfs::common::CreationMode::Never
550 && flags.create_directory();
551 let open_flags = to_open_flags(&flags);
552 async move |current_task, file| {
553 let (node, name) = lookup_parent(current_task, file, path)?;
554 let file = match current_task.open_namespace_node_at(
555 node.clone(),
556 name.as_ref(),
557 open_flags,
558 FileMode::ALLOW_ALL,
559 ResolveFlags::empty(),
560 ) {
561 Err(e) if e == errno!(EISDIR) && create_directory => {
562 let mode = current_task
563 .fs()
564 .apply_umask(FileMode::from_bits(0o777) | FileMode::IFDIR);
565 let name = node.create_node(
566 ¤t_task,
567 name.as_ref(),
568 mode,
569 DeviceId::NONE,
570 )?;
571 name.open(
572 ¤t_task,
573 OpenAccessCheck::skip(
574 open_flags & !(OpenFlags::CREAT | OpenFlags::EXCL),
575 ),
576 )?
577 }
578 f => f?,
579 };
580 Ok(StarnixNodeConnection::new(¤t_task.kernel(), file, credentials, stats))
581 }
582 })?;
583
584 return starnix_file.directory_entry_open(
585 scope,
586 flags,
587 path::Path::dot(),
588 object_request,
589 );
590 }
591
592 if !path.is_dot() {
594 return Err(zx::Status::NOT_DIR);
595 }
596 let file = self.reopen(&flags)?;
597 object_request
598 .take()
599 .create_connection_sync::<file::RawIoConnection<_>, _>(scope, file, flags);
600 Ok(())
601 }
602
603 fn get_attributes(
604 &self,
605 requested_attributes: fio::NodeAttributesQuery,
606 ) -> fio::NodeAttributes2 {
607 self.spawn_task(async move |_, file| {
608 let info = file.node().info();
609
610 #[allow(clippy::unnecessary_cast)]
612 let link_count = info.link_count as u64;
613
614 let (protocols, abilities) = if info.mode.contains(FileMode::IFDIR) {
615 (
616 fio::NodeProtocolKinds::DIRECTORY,
617 fio::Operations::GET_ATTRIBUTES
618 | fio::Operations::UPDATE_ATTRIBUTES
619 | fio::Operations::ENUMERATE
620 | fio::Operations::TRAVERSE
621 | fio::Operations::MODIFY_DIRECTORY,
622 )
623 } else {
624 (
625 fio::NodeProtocolKinds::FILE,
626 fio::Operations::GET_ATTRIBUTES
627 | fio::Operations::UPDATE_ATTRIBUTES
628 | fio::Operations::READ_BYTES
629 | fio::Operations::WRITE_BYTES,
630 )
631 };
632
633 Ok(attributes!(
634 requested_attributes,
635 Mutable {
636 creation_time: info.time_status_change.into_nanos() as u64,
637 modification_time: info.time_modify.into_nanos() as u64,
638 mode: info.mode.bits(),
639 uid: info.uid,
640 gid: info.gid,
641 rdev: info.rdev.bits(),
642 },
643 Immutable {
644 protocols: protocols,
645 abilities: abilities,
646 content_size: info.size as u64,
647 storage_size: info.storage_size() as u64,
648 link_count: link_count,
649 id: file.fs.dev_id.bits(),
650 }
651 ))
652 })
653 .expect("spawn_task")
654 }
655
656 fn update_attributes(&self, attributes: fio::MutableNodeAttributes) {
657 let _ = self.spawn_task(async move |_, file| {
658 file.node().update_info(|info| {
659 if let Some(time) = attributes.creation_time {
660 info.time_status_change = UtcInstant::from_nanos(time as i64);
661 }
662 if let Some(time) = attributes.modification_time {
663 info.time_modify = UtcInstant::from_nanos(time as i64);
664 }
665 if let Some(mode) = attributes.mode {
666 info.mode = FileMode::from_bits(mode);
667 }
668 if let Some(uid) = attributes.uid {
669 info.uid = uid;
670 }
671 if let Some(gid) = attributes.gid {
672 info.gid = gid;
673 }
674 if let Some(rdev) = attributes.rdev {
675 info.rdev = DeviceId::from_bits(rdev);
676 }
677 });
678 Ok(())
679 });
680 }
681}
682
683impl vfs::node::Node for StarnixNodeConnection {
684 async fn get_attributes(
685 &self,
686 requested_attributes: fio::NodeAttributesQuery,
687 ) -> Result<fio::NodeAttributes2, zx::Status> {
688 Ok(StarnixNodeConnection::get_attributes(self, requested_attributes))
689 }
690}
691
692impl directory::entry::GetEntryInfo for StarnixNodeConnection {
693 fn entry_info(&self) -> directory::entry::EntryInfo {
694 let dirent_type =
695 if self.is_dir() { fio::DirentType::Directory } else { fio::DirentType::File };
696 directory::entry::EntryInfo::new(0, dirent_type)
697 }
698}
699
700impl directory::entry_container::Directory for StarnixNodeConnection {
701 fn open(
702 self: Arc<Self>,
703 scope: execution_scope::ExecutionScope,
704 path: path::Path,
705 flags: fio::Flags,
706 object_request: ObjectRequestRef<'_>,
707 ) -> Result<(), zx::Status> {
708 self.directory_entry_open(scope, flags, path, object_request)
709 }
710
711 async fn read_dirents(
712 &self,
713 pos: &directory::traversal_position::TraversalPosition,
714 sink: Box<dyn directory::dirents_sink::Sink>,
715 ) -> Result<
716 (
717 directory::traversal_position::TraversalPosition,
718 Box<dyn directory::dirents_sink::Sealed>,
719 ),
720 zx::Status,
721 > {
722 StarnixNodeConnection::directory_read_dirents(self, pos, sink).map_err(Errno::into)
723 }
724 fn register_watcher(
725 self: Arc<Self>,
726 _scope: execution_scope::ExecutionScope,
727 _mask: fio::WatchMask,
728 _watcher: directory::entry_container::DirectoryWatcher,
729 ) -> Result<(), zx::Status> {
730 track_stub!(TODO("https://fxbug.dev/322875605"), "register directory watcher");
731 Ok(())
732 }
733 fn unregister_watcher(self: Arc<Self>, _key: usize) {}
734}
735
736impl directory::entry_container::MutableDirectory for StarnixNodeConnection {
737 async fn update_attributes(
738 &self,
739 attributes: fio::MutableNodeAttributes,
740 ) -> Result<(), zx::Status> {
741 StarnixNodeConnection::update_attributes(self, attributes);
742 Ok(())
743 }
744 async fn unlink(
745 self: Arc<Self>,
746 name: &str,
747 must_be_directory: bool,
748 ) -> Result<(), zx::Status> {
749 let name = FsString::from(name.to_owned());
750 self.spawn_task_async(async move |current_task, file| {
751 let kind =
752 if must_be_directory { UnlinkKind::Directory } else { UnlinkKind::NonDirectory };
753 file.name.entry.unlink(
754 current_task,
755 &file.name.mount,
756 name.as_ref(),
757 kind,
758 DirectoryMode::AllowAny,
759 )
760 })
761 .await?;
762 Ok(())
763 }
764 async fn sync(&self) -> Result<(), zx::Status> {
765 Ok(())
766 }
767 fn rename(
768 self: Arc<Self>,
769 src_dir: Arc<dyn directory::entry_container::MutableDirectory>,
770 src_name: path::Path,
771 dst_name: path::Path,
772 ) -> BoxFuture<'static, Result<(), zx::Status>> {
773 let this = self.clone();
774 Box::pin(async move {
775 Ok(self
776 .spawn_task_async(async move |current_task, file| {
777 let src_dir = src_dir
778 .into_any()
779 .downcast::<StarnixNodeConnection>()
780 .map_err(|_| errno!(EXDEV))?;
781 let (dst_node, dst_name) = lookup_parent(current_task, &file, dst_name)?;
782 let (src_node, src_name) = if Arc::ptr_eq(&src_dir, &this) {
783 lookup_parent(current_task, &file, src_name)?
784 } else {
785 src_dir.lookup_parent(src_name)?
786 };
787 NamespaceNode::rename(
788 current_task,
789 &src_node,
790 src_name.as_ref(),
791 &dst_node,
792 dst_name.as_ref(),
793 RenameFlags::empty(),
794 )
795 })
796 .await?)
797 })
798 }
799}
800
801impl file::File for StarnixNodeConnection {
802 fn writable(&self) -> bool {
803 true
804 }
805 async fn open_file(&self, _optionss: &file::FileOptions) -> Result<(), zx::Status> {
806 Ok(())
807 }
808 async fn truncate(&self, length: u64) -> Result<(), zx::Status> {
809 Ok(self
810 .spawn_task_async(async move |current_task, file| {
811 file.ftruncate(current_task, length)
814 })
815 .await?)
816 }
817 async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, zx::Status> {
818 Ok(self
819 .spawn_task_async(async move |current_task, file| {
820 (|| {
821 let mut prot_flags = ProtectionFlags::empty();
822 if flags.contains(fio::VmoFlags::READ) {
823 prot_flags |= ProtectionFlags::READ;
824 }
825 if flags.contains(fio::VmoFlags::WRITE) {
826 prot_flags |= ProtectionFlags::WRITE;
827 }
828 if flags.contains(fio::VmoFlags::EXECUTE) {
829 prot_flags |= ProtectionFlags::EXEC;
830 }
831 let memory = file.get_memory(current_task, None, prot_flags)?;
832 let vmo = memory.as_vmo().ok_or(zx::Status::NOT_SUPPORTED)?;
833 if flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
834 let size = vmo.get_size()?;
835 vmo.create_child(zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE, 0, size)
836 } else {
837 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)
838 }
839 })()
840 .map_err(|e| from_status_like_fdio!(e))
841 })
842 .await?)
843 }
844
845 async fn get_size(&self) -> Result<u64, zx::Status> {
846 Ok(self.spawn_task_async(async move |_, file| Ok(file.node().info().size as u64)).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 |current_task, file| {
865 let mut data = VecOutputBuffer::new(count as usize);
866 file.read(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 |current_task, file| {
878 let mut data = VecOutputBuffer::new(count as usize);
879 file.read_at(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 |current_task, file| {
892 let written = file.write(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 |current_task, file| {
905 let written = file.write_at(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 |current_task, file| {
920 let seek_result = file.seek(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 |current_task| {
967 let kernel = current_task.kernel();
968 let fs = TmpFs::new_fs(&kernel);
969
970 let file = &fs.root().open_anonymous(current_task, OpenFlags::RDWR).expect("open");
971 let (root_handle, scope) =
972 serve_file(current_task, file, Credentials::root()).expect("serve");
973
974 let fs_dev_id = fs.dev_id;
977 std::thread::spawn(move || {
978 let root_zxio = Zxio::create(root_handle.into_channel().into()).expect("create");
979
980 assert_directory_content(&root_zxio, &[b"."]);
981 assert_directory_content(&root_zxio, &[b"."]);
983
984 let attrs = root_zxio
985 .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
986 .expect("attr_get");
987 assert_eq!(attrs.id, fs_dev_id.bits());
988
989 let mut attrs = syncio::zxio_node_attributes_t::default();
990 attrs.has.creation_time = true;
991 attrs.has.modification_time = true;
992 attrs.creation_time = 0;
993 attrs.modification_time = 42;
994 root_zxio.attr_set(&attrs).expect("attr_set");
995 let attrs = root_zxio
996 .attr_get(zxio_node_attr_has_t {
997 creation_time: true,
998 modification_time: true,
999 ..Default::default()
1000 })
1001 .expect("attr_get");
1002 assert_eq!(attrs.creation_time, 0);
1003 assert_eq!(attrs.modification_time, 42);
1004
1005 assert_eq!(
1006 root_zxio
1007 .open("foo", fio::PERM_READABLE | fio::PERM_WRITABLE, Default::default())
1008 .expect_err("open"),
1009 zx::Status::NOT_FOUND
1010 );
1011 let foo_zxio = root_zxio
1012 .open(
1013 "foo",
1014 fio::PERM_READABLE
1015 | fio::PERM_WRITABLE
1016 | fio::Flags::FLAG_MAYBE_CREATE
1017 | fio::Flags::PROTOCOL_FILE,
1018 Default::default(),
1019 )
1020 .expect("zxio_open");
1021 assert_directory_content(&root_zxio, &[b".", b"foo"]);
1022
1023 assert_eq!(foo_zxio.write(b"hello").expect("write"), 5);
1024 assert_eq!(foo_zxio.write_at(2, b"ch").expect("write_at"), 2);
1025 let mut buffer = [0; 7];
1026 assert_eq!(foo_zxio.read_at(2, &mut buffer).expect("read_at"), 3);
1027 assert_eq!(&buffer[..3], b"cho");
1028 assert_eq!(foo_zxio.seek(syncio::SeekOrigin::Start, 0).expect("seek"), 0);
1029 assert_eq!(foo_zxio.read(&mut buffer).expect("read"), 5);
1030 assert_eq!(&buffer[..5], b"hecho");
1031
1032 let attrs = foo_zxio
1033 .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
1034 .expect("attr_get");
1035 assert_eq!(attrs.id, fs_dev_id.bits());
1036
1037 let mut attrs = syncio::zxio_node_attributes_t::default();
1038 attrs.has.creation_time = true;
1039 attrs.has.modification_time = true;
1040 attrs.creation_time = 0;
1041 attrs.modification_time = 42;
1042 foo_zxio.attr_set(&attrs).expect("attr_set");
1043 let attrs = foo_zxio
1044 .attr_get(zxio_node_attr_has_t {
1045 creation_time: true,
1046 modification_time: true,
1047 ..Default::default()
1048 })
1049 .expect("attr_get");
1050 assert_eq!(attrs.creation_time, 0);
1051 assert_eq!(attrs.modification_time, 42);
1052
1053 assert_eq!(
1054 root_zxio
1055 .open(
1056 "bar/baz",
1057 fio::Flags::PROTOCOL_DIRECTORY
1058 | fio::Flags::FLAG_MAYBE_CREATE
1059 | fio::PERM_READABLE
1060 | fio::PERM_WRITABLE,
1061 Default::default(),
1062 )
1063 .expect_err("open"),
1064 zx::Status::NOT_FOUND
1065 );
1066
1067 let bar_zxio = root_zxio
1068 .open(
1069 "bar",
1070 fio::Flags::PROTOCOL_DIRECTORY
1071 | fio::Flags::FLAG_MAYBE_CREATE
1072 | fio::PERM_READABLE
1073 | fio::PERM_WRITABLE,
1074 Default::default(),
1075 )
1076 .expect("open");
1077 let baz_zxio = root_zxio
1078 .open(
1079 "bar/baz",
1080 fio::Flags::PROTOCOL_DIRECTORY
1081 | fio::Flags::FLAG_MAYBE_CREATE
1082 | fio::PERM_READABLE
1083 | fio::PERM_WRITABLE,
1084 Default::default(),
1085 )
1086 .expect("open");
1087 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1088 assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1089
1090 bar_zxio.rename("baz", &root_zxio, "quz").expect("rename");
1091 assert_directory_content(&bar_zxio, &[b"."]);
1092 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar", b"quz"]);
1093 assert_directory_content(&baz_zxio, &[b"."]);
1094 })
1095 .join()
1096 .expect("join");
1097 scope.shutdown();
1098 scope.wait().await;
1099 std::mem::drop(fs);
1101 })
1102 .await;
1103 }
1104
1105 #[::fuchsia::test]
1106 async fn serve_file_strips_trunc() {
1107 spawn_kernel_and_run(async |current_task| {
1108 let kernel = current_task.kernel();
1109 let fs = TmpFs::new_fs(&kernel);
1110 let ns = Namespace::new(fs);
1111 let root = ns.root();
1112
1113 let file_node = root
1114 .create_node(
1115 current_task,
1116 b"test".into(),
1117 FileMode::IFREG | FileMode::ALLOW_ALL,
1118 DeviceId::NONE,
1119 )
1120 .expect("create_node");
1121
1122 let file =
1123 file_node.open(current_task, OpenAccessCheck::skip(OpenFlags::RDWR)).expect("open");
1124 file.write(current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1125
1126 let file_to_serve = current_task
1128 .open_namespace_node_at(
1129 root,
1130 b"test".into(),
1131 OpenAccessCheck::skip(OpenFlags::RDWR | OpenFlags::TRUNC),
1132 FileMode::default(),
1133 ResolveFlags::default(),
1134 )
1135 .expect("open O_TRUNC");
1136
1137 assert_eq!(file_to_serve.node().fetch_and_refresh_info(current_task).unwrap().size, 0);
1139
1140 file_to_serve
1142 .write(current_task, &mut VecInputBuffer::new(b"world"))
1143 .expect("write world");
1144 assert_eq!(file_to_serve.node().info().size, 5);
1145
1146 let (client_end, scope) =
1147 serve_file(current_task, &file_to_serve, Credentials::root()).expect("serve");
1148
1149 fuchsia_async::unblock(|| {
1150 let zxio = Zxio::create(client_end.into_channel().into()).expect("create");
1151 let mut attr = syncio::zxio_node_attributes_t::default();
1152 attr.has.content_size = true;
1153 let attr = zxio.attr_get(attr.has).expect("attr_get");
1154 assert_eq!(attr.content_size, 5);
1156 })
1157 .await;
1158
1159 scope.shutdown();
1160 scope.wait().await;
1161 })
1162 .await;
1163 }
1164
1165 #[::fuchsia::test]
1166 async fn truncate_checks_fd_permissions() {
1167 spawn_kernel_and_run(async |current_task| {
1168 let kernel = current_task.kernel();
1169 let fs = TmpFs::new_fs(&kernel);
1170 let ns = Namespace::new(fs);
1171 let root = ns.root();
1172
1173 let file_node = root
1174 .create_node(
1175 current_task,
1176 "test".into(),
1177 FileMode::IFREG | FileMode::IRWXU,
1178 DeviceId::NONE,
1179 )
1180 .expect("create_node");
1181
1182 let file =
1183 file_node.open(current_task, OpenAccessCheck::skip(OpenFlags::RDWR)).expect("open");
1184 file.write(current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1185
1186 let (client_end, scope) = serve_file(
1188 current_task,
1189 &file,
1190 Arc::new(Credentials {
1191 fsuid: 2000,
1192 cap_effective: Capabilities::empty(),
1193 ..Credentials::clone(¤t_task.current_creds())
1194 }),
1195 )
1196 .expect("serve");
1197
1198 fuchsia_async::unblock(move || {
1199 let zxio = Zxio::create(client_end.into_channel().into()).expect("create");
1200 zxio.truncate(2).expect("truncate");
1203
1204 let mut attr = syncio::zxio_node_attributes_t::default();
1205 attr.has.content_size = true;
1206 let attr = zxio.attr_get(attr.has).expect("attr_get");
1207 assert_eq!(attr.content_size, 2);
1208 })
1209 .await;
1210
1211 scope.shutdown();
1212 scope.wait().await;
1213 })
1214 .await;
1215 }
1216
1217 #[::fuchsia::test]
1218 async fn open() {
1219 spawn_kernel_and_run(async |current_task| {
1220 let kernel = current_task.kernel();
1221 let fs = TmpFs::new_fs(&kernel);
1222
1223 let file = &fs
1224 .root()
1225 .open_anonymous(current_task, OpenFlags::RDWR)
1226 .expect("open_anonymous failed");
1227 let (root_handle, scope) =
1228 serve_file(current_task, file, Credentials::root()).expect("serve_file failed");
1229
1230 std::thread::spawn(move || {
1231 let root_zxio =
1232 Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1233
1234 assert_directory_content(&root_zxio, &[b"."]);
1235 assert_eq!(
1236 root_zxio
1237 .open(
1238 "foo",
1239 fio::PERM_READABLE | fio::PERM_WRITABLE,
1240 ZxioOpenOptions::default()
1241 )
1242 .expect_err("open3 passed unexpectedly"),
1243 zx::Status::NOT_FOUND
1244 );
1245 root_zxio
1246 .open(
1247 "foo",
1248 fio::Flags::PROTOCOL_FILE
1249 | fio::PERM_READABLE
1250 | fio::PERM_WRITABLE
1251 | fio::Flags::FLAG_MUST_CREATE,
1252 ZxioOpenOptions::default(),
1253 )
1254 .expect("open3 failed");
1255 assert_directory_content(&root_zxio, &[b".", b"foo"]);
1256
1257 assert_eq!(
1258 root_zxio
1259 .open(
1260 "bar/baz",
1261 fio::Flags::PROTOCOL_DIRECTORY
1262 | fio::PERM_READABLE
1263 | fio::PERM_WRITABLE
1264 | fio::Flags::FLAG_MUST_CREATE,
1265 ZxioOpenOptions::default()
1266 )
1267 .expect_err("open3 passed unexpectedly"),
1268 zx::Status::NOT_FOUND
1269 );
1270 let bar_zxio = root_zxio
1271 .open(
1272 "bar",
1273 fio::Flags::PROTOCOL_DIRECTORY
1274 | fio::PERM_READABLE
1275 | fio::PERM_WRITABLE
1276 | fio::Flags::FLAG_MUST_CREATE,
1277 ZxioOpenOptions::default(),
1278 )
1279 .expect("open3 failed");
1280 root_zxio
1281 .open(
1282 "bar/baz",
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 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1291 assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1292 })
1293 .join()
1294 .expect("join");
1295 scope.shutdown();
1296 scope.wait().await;
1297
1298 std::mem::drop(fs);
1300 })
1301 .await;
1302 }
1303
1304 #[::fuchsia::test]
1305 async fn use_credentials() {
1306 spawn_kernel_and_run(async |current_task| {
1307 let kernel = current_task.kernel();
1308 let fs = TmpFs::new_fs(&kernel);
1309
1310 let file = &fs
1311 .root()
1312 .open_anonymous(current_task, OpenFlags::RDWR)
1313 .expect("open_anonymous failed");
1314 let ns = Namespace::new(fs);
1316 ns.root()
1317 .open_create_node(
1318 current_task,
1319 "test".into(),
1320 FileMode::from_bits(0o600) | FileMode::IFREG,
1321 DeviceId::NONE,
1322 OpenFlags::empty(),
1323 )
1324 .expect("open_create_node failed");
1325
1326 let mut creds = Credentials::with_ids(0, 0);
1327 creds.fsuid = 1;
1328 creds.cap_effective = Capabilities::empty();
1329
1330 let (root_handle, scope) =
1331 serve_file(current_task, file, creds.into()).expect("serve_file failed");
1332
1333 std::thread::spawn(move || {
1334 let root_zxio =
1335 Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1336
1337 assert_directory_content(&root_zxio, &[b".", b"test"]);
1338 assert_eq!(
1339 root_zxio
1340 .open(
1341 "test",
1342 fio::PERM_READABLE | fio::PERM_WRITABLE,
1343 ZxioOpenOptions::default()
1344 )
1345 .expect_err("open3 passed unexpectedly"),
1346 zx::Status::ACCESS_DENIED
1347 );
1348 })
1349 .join()
1350 .expect("join");
1351 scope.shutdown();
1352 scope.wait().await;
1353
1354 std::mem::drop(ns);
1356 })
1357 .await;
1358 }
1359
1360 #[::fuchsia::test]
1361 async fn large_directory_listing() {
1362 spawn_kernel_and_run(async |current_task| {
1363 let kernel = current_task.kernel();
1364 let fs = TmpFs::new_fs(&kernel);
1365
1366 let file = &fs
1367 .root()
1368 .open_anonymous(current_task, OpenFlags::RDWR)
1369 .expect("open_anonymous failed");
1370
1371 let ns = Namespace::new(fs);
1372 let mut expected_files = vec![b".".to_vec()];
1373 for i in 0..500 {
1376 let name = format!("file_{:03}", i);
1377 ns.root()
1378 .open_create_node(
1379 current_task,
1380 name.as_str().into(),
1381 FileMode::from_bits(0o600) | FileMode::IFREG,
1382 DeviceId::NONE,
1383 OpenFlags::empty(),
1384 )
1385 .expect("open_create_node failed");
1386 expected_files.push(name.into_bytes());
1387 }
1388
1389 let (root_handle, scope) =
1390 serve_file(current_task, file, Credentials::root()).expect("serve_file failed");
1391
1392 std::thread::spawn(move || {
1393 let root_zxio =
1394 Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1395
1396 let expected = expected_files
1397 .iter()
1398 .map(|x| FsString::from(x.clone()))
1399 .collect::<HashSet<_>>();
1400 let mut iterator =
1401 root_zxio.create_dirent_iterator().expect("create_dirent_iterator failed");
1402 iterator.rewind().expect("rewind failed");
1403
1404 let mut found = HashSet::new();
1405 for res in iterator {
1406 match res {
1407 Ok(dirent) => {
1408 found.insert(dirent.name.clone());
1409 }
1410 Err(status) => {
1411 panic!("Iterator returned error: {:?}", status);
1412 }
1413 }
1414 }
1415 assert_eq!(found, expected);
1416 })
1417 .join()
1418 .expect("thread join failed");
1419 scope.shutdown();
1420 scope.wait().await;
1421
1422 std::mem::drop(ns);
1424 })
1425 .await;
1426 }
1427
1428 #[::fuchsia::test]
1429 async fn readdir_propagates_genuine_nospc_error() {
1430 spawn_kernel_and_run(async |current_task| {
1431 use crate::vfs::{
1432 DirEntry, FileOps, FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps,
1433 fs_node_impl_dir_readonly,
1434 };
1435 use crate::{
1436 fileops_impl_directory, fileops_impl_noop_sync, fileops_impl_unbounded_seek,
1437 };
1438
1439 struct FaultyDirectory;
1440 impl FileOps for FaultyDirectory {
1441 fileops_impl_directory!();
1442 fileops_impl_noop_sync!();
1443 fileops_impl_unbounded_seek!();
1444
1445 fn readdir(
1446 &self,
1447 _file: &FileObject,
1448 _current_task: &CurrentTask,
1449 _sink: &mut dyn DirentSink,
1450 ) -> Result<(), Errno> {
1451 error!(ENOSPC)
1452 }
1453 }
1454
1455 struct FaultyDirectoryNode;
1456 impl FsNodeOps for FaultyDirectoryNode {
1457 fs_node_impl_dir_readonly!();
1458
1459 fn create_file_ops(
1460 &self,
1461 _node: &FsNode,
1462 _current_task: &CurrentTask,
1463 _flags: OpenFlags,
1464 ) -> Result<Box<dyn FileOps>, Errno> {
1465 Ok(Box::new(FaultyDirectory))
1466 }
1467
1468 fn lookup(
1469 &self,
1470 _node: &FsNode,
1471 _current_task: &CurrentTask,
1472 _name: &FsStr,
1473 ) -> Result<FsNodeHandle, Errno> {
1474 error!(ENOENT)
1475 }
1476 }
1477
1478 let kernel = current_task.kernel();
1479 let fs = TmpFs::new_fs(&kernel);
1480
1481 let ino = fs.allocate_ino();
1482 let info = FsNodeInfo::new(
1483 FileMode::from_bits(0o777) | FileMode::IFDIR,
1484 current_task.current_fscred(),
1485 );
1486 let node = fs.create_node(ino, FaultyDirectoryNode, info);
1487 let dir_entry = DirEntry::new(node, None, "faulty_dir".into());
1488 let name = NamespaceNode::new_anonymous(dir_entry);
1489 let file = FileObject::new(
1490 current_task,
1491 Box::new(FaultyDirectory),
1492 name,
1493 OpenFlags::DIRECTORY | OpenFlags::RDONLY,
1494 )
1495 .expect("FileObject::new failed");
1496
1497 let (root_handle, scope) =
1498 serve_file(current_task, &file, Credentials::root()).expect("serve_file failed");
1499
1500 std::thread::spawn(move || {
1501 let root_zxio =
1502 Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1503
1504 let mut iterator =
1505 root_zxio.create_dirent_iterator().expect("create_dirent_iterator failed");
1506 iterator.rewind().expect("rewind failed");
1507
1508 let mut got_error = false;
1509 for res in iterator {
1510 if let Err(status) = res {
1511 assert_eq!(status, zx::Status::NO_SPACE);
1512 got_error = true;
1513 break;
1514 }
1515 }
1516 assert!(got_error, "Expected iterator to fail with NO_SPACE");
1517 })
1518 .join()
1519 .expect("thread join failed");
1520 scope.shutdown();
1521 scope.wait().await;
1522 })
1523 .await;
1524 }
1525}