Skip to main content

starnix_core/vfs/
file_server.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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    /// The number of objects currently being served.  This will not count multiple connections to
41    /// the same object, or, for a directory, connections to any of its children.
42    serving: AtomicU64,
43
44    /// The total number of reads performed for files served.
45    reads: AtomicU64,
46
47    /// The total number of bytes read for files served.
48    read_bytes: AtomicU64,
49
50    /// The total number of writes performed for files served.
51    writes: AtomicU64,
52
53    /// The total number of writes written for files served.
54    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(&registry);
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
106/// Returns a handle implementing a fuchsia.io.Node delegating to the given `file`.
107pub 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    // The TRUNC flag needs to be stripped as otherwise the VFS library will try and truncate
125    // the file when it creates the connection.
126    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    // Run with the correct credentials
239    current_task
240        .override_creds_async(credentials.clone(), async || {
241            // Reopen file object to not share state with the given FileObject.
242            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/// A representation of `file` for the rust vfs.
294///
295/// This struct implements the following trait from the rust vfs library:
296/// - directory::entry_container::Directory
297/// - directory::entry_container::MutableDirectory
298/// - file::File
299/// - file::RawFileIoConnection
300///
301/// Each method is delegated back to the starnix vfs, using `task` as the current task. Blocking
302/// methods are run from the kernel dynamic thread spawner so that the async dispatched do not
303/// block on these.
304/// All vfs operations should be done using `credentials`.
305#[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    /// Reopen the current `StarnixNodeConnection` with the given `OpenFlags`. The new file will not share
383    /// state. It is equivalent to opening the same file, not dup'ing the file descriptor.
384    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(&current_task.kernel(), file, credentials, stats))
391        })
392    }
393
394    /// Implementation of `vfs::directory::entry_container::Directory::directory_read_dirents`.
395    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                    // We must take `self.sink` here because the Fuchsia VFS `Sink::append` method
423                    // (called on the inner sink below) consumes the sink by value.
424                    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                                // The sink is sealed and the entry did not fit.
434                                //
435                                // This function is called by `readdir` to iterate through directory
436                                // entries and stops when all entries have been read or if this
437                                // returns an error. By returning `ENOSPC` here, the `readdir`
438                                // loop will halt before it advances to the next entry. This ensures
439                                // that the entry that failed to fit is not skipped, and will be
440                                // the first one processed when the client resumes reading.
441                                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                    // Ignore ..
459                    if name != ".." {
460                        // Ignore entries with unknown types.
461                        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                        // We caught ENOSPC. We must distinguish between:
500                        // 1. ENOSPC sent when sink is sealed: This is expected when the buffer
501                        //    fills up. We ignore the error and return the partial results.
502                        // 2. A genuine filesystem error (sink is NOT sealed): This is a real
503                        //    failure, so we must propagate it.
504                        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    /// Implementation of `vfs::directory::entry::DirectoryEntry::open`.
528    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                // Reopen the current directory.
538                let dir = self.reopen(&flags)?;
539                object_request
540                    .take()
541                    .create_connection_sync::<MutableConnection<_>, _>(scope, dir, flags);
542                return Ok(());
543            }
544
545            // Open a path under the current directory.
546            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                                &current_task,
567                                name.as_ref(),
568                                mode,
569                                DeviceId::NONE,
570                            )?;
571                            name.open(
572                                &current_task,
573                                OpenAccessCheck::skip(
574                                    open_flags & !(OpenFlags::CREAT | OpenFlags::EXCL),
575                                ),
576                            )?
577                        }
578                        f => f?,
579                    };
580                    Ok(StarnixNodeConnection::new(&current_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        // Reopen the current file.
593        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            // This cast is necessary depending on the architecture.
611            #[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                // `ftruncate` checks fewer permissions than `file.name.truncate`, which is what we
812                // want.
813                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        // Called on the connection via `fcntl(FSETFL, ...)`. fuchsia.io only supports `O_APPEND`
927        // right now, and does not have equivalents for the following flags:
928        //  - `O_ASYNC`
929        //  - `O_DIRECT`
930        //  - `O_NOATIME` (only allowed if caller's EUID is same as the file's UID)
931        //  - `O_NONBLOCK`
932        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            // Capture information from the filesystem in the main thread. The filesystem must not be
975            // transferred to the other thread.
976            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                // Check that one can reiterate from the start.
982                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            // This ensures fs cannot be captures in the thread.
1100            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            // Reopen with O_TRUNC.
1127            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            // Ensure it IS truncated by the open.
1138            assert_eq!(file_to_serve.node().fetch_and_refresh_info(current_task).unwrap().size, 0);
1139
1140            // Write something so we can check if it gets truncated again.
1141            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                // If O_TRUNC was not stripped, the size would be 0 here.
1155                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            // Serve the file as different user.
1187            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(&current_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                // truncate should succeed because the FD is open for writing, even though the file
1201                // is being served with a different user.
1202                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            // This ensures fs cannot be captured in the thread.
1299            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            // Create a file as root.
1315            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            // This ensures fs cannot be captured in the thread.
1355            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            // Create 500 files. Through trial and error, this number was found to exceed the sink's
1374            // buffer capacity.
1375            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            // This ensures fs cannot be captured in the thread.
1423            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}