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, LockedAndTask};
8use crate::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
9use crate::vfs::{
10    DirectoryEntryType, DirentSink, FileHandle, FileObject, FsStr, FsString, LookupContext,
11    NamespaceNode, RenameFlags, SeekTarget, UnlinkKind,
12};
13use fidl::endpoints::{ClientEnd, ServerEnd};
14use fidl_fuchsia_io as fio;
15use fuchsia_runtime::UtcInstant;
16use futures::StreamExt;
17use futures::future::BoxFuture;
18use itertools::Either;
19use starnix_logging::{log_error, track_stub};
20use starnix_sync::{FileServerStatsLock, LockDepMutex, Locked, Unlocked};
21use starnix_types::convert::IntoFidl as _;
22use starnix_uapi::auth::Credentials;
23use starnix_uapi::device_id::DeviceId;
24use starnix_uapi::errors::{ENOSPC, Errno};
25use starnix_uapi::file_mode::{AccessCheck, FileMode};
26use starnix_uapi::open_flags::OpenFlags;
27use starnix_uapi::vfs::ResolveFlags;
28use starnix_uapi::{errno, error, from_status_like_fdio, ino_t, off_t};
29use std::collections::HashMap;
30use std::sync::Arc;
31use std::sync::atomic::{AtomicU64, Ordering};
32use vfs::directory::mutable::connection::MutableConnection;
33use vfs::directory::{self};
34use vfs::{
35    ObjectRequestRef, ProtocolsExt, ToObjectRequest, attributes, execution_scope, file, path,
36};
37
38#[derive(Default)]
39struct FileServerStats {
40    /// 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: LockDepMutex::new(HashMap::new()) }
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(
181        self: Box<Self>,
182        locked: &mut Locked<Unlocked>,
183        current_task: &CurrentTask,
184        file: &FileHandle,
185    );
186}
187
188struct EitherSender<R>(Either<futures::channel::oneshot::Sender<R>, std::sync::mpsc::Sender<R>>);
189
190impl<R> From<futures::channel::oneshot::Sender<R>> for EitherSender<R> {
191    fn from(v: futures::channel::oneshot::Sender<R>) -> Self {
192        Self(Either::Left(v))
193    }
194}
195
196impl<R> From<std::sync::mpsc::Sender<R>> for EitherSender<R> {
197    fn from(v: std::sync::mpsc::Sender<R>) -> Self {
198        Self(Either::Right(v))
199    }
200}
201
202impl<R> EitherSender<R> {
203    async fn send(self, r: R) {
204        match self.0 {
205            Either::Left(s) => {
206                let _ = s.send(r);
207            }
208            Either::Right(s) => {
209                let _ = s.send(r);
210            }
211        }
212    }
213}
214
215struct WorkWrapper<R, F>
216where
217    R: Send + 'static,
218    F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> R + Send + 'static,
219{
220    f: F,
221    sender: EitherSender<R>,
222}
223
224#[async_trait::async_trait(?Send)]
225impl<R, F> Work for WorkWrapper<R, F>
226where
227    R: Send + 'static,
228    F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> R + Send + 'static,
229{
230    async fn run(
231        self: Box<Self>,
232        locked: &mut Locked<Unlocked>,
233        current_task: &CurrentTask,
234        file: &FileHandle,
235    ) {
236        let f: F = self.f;
237        let r = f(locked, current_task, file).await;
238        self.sender.send(r).await;
239    }
240}
241
242async fn handle_file(
243    locked_and_task: LockedAndTask<'_>,
244    credentials: Arc<Credentials>,
245    file: FileHandle,
246    mut receiver: futures::channel::mpsc::UnboundedReceiver<Box<dyn Work>>,
247) {
248    // Run with the correct credentials
249    locked_and_task
250        .current_task()
251        .override_creds_async(credentials.clone(), async || {
252            // Reopen file object to not share state with the given FileObject.
253            let file = match file.name.open(
254                &mut locked_and_task.unlocked(),
255                locked_and_task.current_task(),
256                file.flags(),
257                AccessCheck::skip(),
258            ) {
259                Ok(file) => file,
260                Err(e) => {
261                    log_error!("Unable to reopen file: {e:?}");
262                    return;
263                }
264            };
265            while let Some(w) = receiver.next().await {
266                w.run(&mut locked_and_task.unlocked(), locked_and_task.current_task(), &file).await;
267            }
268        })
269        .await;
270}
271
272fn to_open_flags(flags: &impl ProtocolsExt) -> OpenFlags {
273    let rights = flags.rights().unwrap_or_default();
274    let mut open_flags = if rights.contains(fio::Operations::WRITE_BYTES) {
275        if rights.contains(fio::Operations::READ_BYTES) {
276            OpenFlags::RDWR
277        } else {
278            OpenFlags::WRONLY
279        }
280    } else {
281        OpenFlags::RDONLY
282    };
283
284    if flags.create_directory() {
285        open_flags |= OpenFlags::DIRECTORY;
286    }
287
288    match flags.creation_mode() {
289        vfs::CreationMode::Always => open_flags |= OpenFlags::CREAT | OpenFlags::EXCL,
290        vfs::CreationMode::AllowExisting => open_flags |= OpenFlags::CREAT,
291        vfs::CreationMode::UnnamedTemporary => open_flags |= OpenFlags::TMPFILE,
292        vfs::CreationMode::UnlinkableUnnamedTemporary => {
293            open_flags |= OpenFlags::TMPFILE | OpenFlags::EXCL
294        }
295        vfs::CreationMode::Never => {}
296    };
297
298    if flags.is_truncate() {
299        open_flags |= OpenFlags::TRUNC;
300    }
301
302    if flags.is_append() {
303        open_flags |= OpenFlags::APPEND;
304    }
305
306    open_flags
307}
308
309/// A representation of `file` for the rust vfs.
310///
311/// This struct implements the following trait from the rust vfs library:
312/// - directory::entry_container::Directory
313/// - directory::entry_container::MutableDirectory
314/// - file::File
315/// - file::RawFileIoConnection
316///
317/// Each method is delegated back to the starnix vfs, using `task` as the current task. Blocking
318/// methods are run from the kernel dynamic thread spawner so that the async dispatched do not
319/// block on these.
320/// All vfs operations should be done using `credentials`.
321#[derive(Clone)]
322struct StarnixNodeConnection {
323    is_dir: bool,
324    credentials: Arc<Credentials>,
325    work_sender: futures::channel::mpsc::UnboundedSender<Box<dyn Work>>,
326    stats: Arc<FileServerStats>,
327}
328
329fn lookup_parent(
330    locked: &mut Locked<Unlocked>,
331    current_task: &CurrentTask,
332    file: &FileObject,
333    path: path::Path,
334) -> Result<(NamespaceNode, FsString), Errno> {
335    let (node, name) = current_task.lookup_parent(
336        locked,
337        &mut LookupContext::default(),
338        &file.name,
339        path.as_str().into(),
340    )?;
341    Ok((node, name.to_owned()))
342}
343
344impl StarnixNodeConnection {
345    fn new(
346        kernel: &Kernel,
347        file: FileHandle,
348        credentials: Arc<Credentials>,
349        stats: Arc<FileServerStats>,
350    ) -> Arc<Self> {
351        let (work_sender, receiver) = futures::channel::mpsc::unbounded();
352        let is_dir = file.node().is_dir();
353        let closure = {
354            let credentials = credentials.clone();
355            async move |locked_and_task: LockedAndTask<'_>| {
356                handle_file(locked_and_task, credentials, file, receiver).await;
357            }
358        };
359        let req = SpawnRequestBuilder::new().with_async_closure(closure).build();
360        kernel.kthreads.spawner().spawn_from_request(req);
361        Arc::new(Self { is_dir, credentials, work_sender, stats })
362    }
363
364    fn spawn_task<R, E, F>(&self, f: F) -> Result<R, Errno>
365    where
366        R: Send + 'static,
367        E: Send + 'static,
368        F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> Result<R, E>
369            + Send
370            + 'static,
371        Errno: From<E>,
372    {
373        let (sender, receiver) = std::sync::mpsc::channel();
374        self.work_sender
375            .unbounded_send(Box::new(WorkWrapper { f, sender: sender.into() }))
376            .map_err(|_| errno!(EIO))?;
377        Ok(receiver.recv().map_err(|_| errno!(EIO))??)
378    }
379
380    async fn spawn_task_async<R, E, F>(&self, f: F) -> Result<R, Errno>
381    where
382        R: Send + 'static,
383        E: Send + 'static,
384        F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> Result<R, E>
385            + Send
386            + 'static,
387        Errno: From<E>,
388    {
389        let (sender, receiver) = futures::channel::oneshot::channel();
390        self.work_sender
391            .unbounded_send(Box::new(WorkWrapper { f, sender: sender.into() }))
392            .map_err(|_| errno!(EIO))?;
393        Ok(receiver.await.map_err(|_| errno!(EIO))??)
394    }
395
396    fn is_dir(&self) -> bool {
397        self.is_dir
398    }
399
400    fn lookup_parent(&self, path: path::Path) -> Result<(NamespaceNode, FsString), Errno> {
401        self.spawn_task(async move |locked, current_task, file| {
402            lookup_parent(locked, current_task, file, path)
403        })
404    }
405
406    /// Reopen the current `StarnixNodeConnection` with the given `OpenFlags`. The new file will not share
407    /// state. It is equivalent to opening the same file, not dup'ing the file descriptor.
408    fn reopen(&self, flags: &impl ProtocolsExt) -> Result<Arc<Self>, Errno> {
409        let credentials = self.credentials.clone();
410        let flags = to_open_flags(flags);
411        let stats = self.stats.clone();
412        self.spawn_task(async move |locked, current_task, file| {
413            let file = file.name.open(locked, current_task, flags, AccessCheck::default())?;
414            Ok(StarnixNodeConnection::new(&current_task.kernel(), file, credentials, stats))
415        })
416    }
417
418    /// Implementation of `vfs::directory::entry_container::Directory::directory_read_dirents`.
419    fn directory_read_dirents<'a>(
420        &'a self,
421        pos: &'a directory::traversal_position::TraversalPosition,
422        sink: Box<dyn directory::dirents_sink::Sink>,
423    ) -> Result<
424        (
425            directory::traversal_position::TraversalPosition,
426            Box<dyn directory::dirents_sink::Sealed>,
427        ),
428        Errno,
429    > {
430        let pos = pos.clone();
431        self.spawn_task(async move |locked, current_task, file| {
432            struct DirentSinkAdapter<'a> {
433                sink: Option<directory::dirents_sink::AppendResult>,
434                offset: &'a mut off_t,
435            }
436            impl<'a> DirentSinkAdapter<'a> {
437                fn is_sealed(&self) -> bool {
438                    matches!(self.sink, Some(directory::dirents_sink::AppendResult::Sealed(_)))
439                }
440
441                fn append(
442                    &mut self,
443                    entry: &directory::entry::EntryInfo,
444                    name: &str,
445                ) -> Result<(), Errno> {
446                    // We must take `self.sink` here because the Fuchsia VFS `Sink::append` method
447                    // (called on the inner sink below) consumes the sink by value.
448                    let sink = self.sink.take();
449                    match sink {
450                        s @ Some(directory::dirents_sink::AppendResult::Sealed(_)) => {
451                            self.sink = s;
452                            error!(ENOSPC)
453                        }
454                        Some(directory::dirents_sink::AppendResult::Ok(sink)) => {
455                            self.sink = Some(sink.append(entry, name));
456                            if self.is_sealed() {
457                                // The sink is sealed and the entry did not fit.
458                                //
459                                // This function is called by `readdir` to iterate through directory
460                                // entries and stops when all entries have been read or if this
461                                // returns an error. By returning `ENOSPC` here, the `readdir`
462                                // loop will halt before it advances to the next entry. This ensures
463                                // that the entry that failed to fit is not skipped, and will be
464                                // the first one processed when the client resumes reading.
465                                error!(ENOSPC)
466                            } else {
467                                Ok(())
468                            }
469                        }
470                        None => error!(ENOTSUP),
471                    }
472                }
473            }
474            impl<'a> DirentSink for DirentSinkAdapter<'a> {
475                fn add(
476                    &mut self,
477                    inode_num: ino_t,
478                    offset: off_t,
479                    entry_type: DirectoryEntryType,
480                    name: &FsStr,
481                ) -> Result<(), Errno> {
482                    // Ignore ..
483                    if name != ".." {
484                        // Ignore entries with unknown types.
485                        if let Some(dirent_type) =
486                            fio::DirentType::from_primitive(entry_type.bits())
487                        {
488                            let entry_info =
489                                directory::entry::EntryInfo::new(inode_num, dirent_type);
490                            self.append(&entry_info, &String::from_utf8_lossy(name))?
491                        }
492                    }
493                    *self.offset = offset;
494                    Ok(())
495                }
496                fn offset(&self) -> off_t {
497                    *self.offset
498                }
499            }
500            let offset = match pos {
501                directory::traversal_position::TraversalPosition::Start => 0,
502                directory::traversal_position::TraversalPosition::Index(v) => v as i64,
503                directory::traversal_position::TraversalPosition::End => {
504                    return Ok((
505                        directory::traversal_position::TraversalPosition::End,
506                        sink.seal(),
507                    ));
508                }
509                _ => return error!(EINVAL),
510            };
511            if file.offset.read() != offset {
512                file.seek(locked, current_task, SeekTarget::Set(offset))?;
513            }
514            let mut file_offset = file.offset.copy();
515            let sink_result = {
516                let mut dirent_sink = DirentSinkAdapter {
517                    sink: Some(directory::dirents_sink::AppendResult::Ok(sink)),
518                    offset: &mut *file_offset,
519                };
520                match file.readdir(locked, current_task, &mut dirent_sink) {
521                    Ok(()) => {}
522                    Err(err) if err == ENOSPC => {
523                        // We caught ENOSPC. We must distinguish between:
524                        // 1. ENOSPC sent when sink is sealed: This is expected when the buffer
525                        //    fills up. We ignore the error and return the partial results.
526                        // 2. A genuine filesystem error (sink is NOT sealed): This is a real
527                        //    failure, so we must propagate it.
528                        if !dirent_sink.is_sealed() {
529                            return Err(err);
530                        }
531                    }
532                    Err(err) => return Err(err),
533                }
534                dirent_sink.sink
535            };
536            let ret = match sink_result {
537                Some(directory::dirents_sink::AppendResult::Sealed(seal)) => Ok((
538                    directory::traversal_position::TraversalPosition::Index(*file_offset as u64),
539                    seal,
540                )),
541                Some(directory::dirents_sink::AppendResult::Ok(sink)) => {
542                    Ok((directory::traversal_position::TraversalPosition::End, sink.seal()))
543                }
544                None => error!(ENOTSUP),
545            };
546            file_offset.update();
547            ret
548        })
549    }
550
551    /// Implementation of `vfs::directory::entry::DirectoryEntry::open`.
552    fn directory_entry_open(
553        self: Arc<Self>,
554        scope: execution_scope::ExecutionScope,
555        flags: impl ProtocolsExt,
556        path: path::Path,
557        object_request: ObjectRequestRef<'_>,
558    ) -> Result<(), zx::Status> {
559        if self.is_dir() {
560            if path.is_dot() {
561                // Reopen the current directory.
562                let dir = self.reopen(&flags)?;
563                object_request
564                    .take()
565                    .create_connection_sync::<MutableConnection<_>, _>(scope, dir, flags);
566                return Ok(());
567            }
568
569            // Open a path under the current directory.
570            let starnix_file = self.spawn_task({
571                let credentials = self.credentials.clone();
572                let stats = self.stats.clone();
573                let create_directory = flags.creation_mode() != vfs::common::CreationMode::Never
574                    && flags.create_directory();
575                let open_flags = to_open_flags(&flags);
576                async move |locked, current_task, file| {
577                    let (node, name) = lookup_parent(locked, current_task, file, path)?;
578                    let file = match current_task.open_namespace_node_at(
579                        locked,
580                        node.clone(),
581                        name.as_ref(),
582                        open_flags,
583                        FileMode::ALLOW_ALL,
584                        ResolveFlags::empty(),
585                        AccessCheck::default(),
586                    ) {
587                        Err(e) if e == errno!(EISDIR) && create_directory => {
588                            let mode = current_task
589                                .fs()
590                                .apply_umask(FileMode::from_bits(0o777) | FileMode::IFDIR);
591                            let name = node.create_node(
592                                locked,
593                                &current_task,
594                                name.as_ref(),
595                                mode,
596                                DeviceId::NONE,
597                            )?;
598                            name.open(
599                                locked,
600                                &current_task,
601                                open_flags & !(OpenFlags::CREAT | OpenFlags::EXCL),
602                                AccessCheck::skip(),
603                            )?
604                        }
605                        f => f?,
606                    };
607                    Ok(StarnixNodeConnection::new(&current_task.kernel(), file, credentials, stats))
608                }
609            })?;
610
611            return starnix_file.directory_entry_open(
612                scope,
613                flags,
614                path::Path::dot(),
615                object_request,
616            );
617        }
618
619        // Reopen the current file.
620        if !path.is_dot() {
621            return Err(zx::Status::NOT_DIR);
622        }
623        let file = self.reopen(&flags)?;
624        object_request
625            .take()
626            .create_connection_sync::<file::RawIoConnection<_>, _>(scope, file, flags);
627        Ok(())
628    }
629
630    fn get_attributes(
631        &self,
632        requested_attributes: fio::NodeAttributesQuery,
633    ) -> fio::NodeAttributes2 {
634        self.spawn_task(async move |_, _, file| {
635            let info = file.node().info();
636
637            // This cast is necessary depending on the architecture.
638            #[allow(clippy::unnecessary_cast)]
639            let link_count = info.link_count as u64;
640
641            let (protocols, abilities) = if info.mode.contains(FileMode::IFDIR) {
642                (
643                    fio::NodeProtocolKinds::DIRECTORY,
644                    fio::Operations::GET_ATTRIBUTES
645                        | fio::Operations::UPDATE_ATTRIBUTES
646                        | fio::Operations::ENUMERATE
647                        | fio::Operations::TRAVERSE
648                        | fio::Operations::MODIFY_DIRECTORY,
649                )
650            } else {
651                (
652                    fio::NodeProtocolKinds::FILE,
653                    fio::Operations::GET_ATTRIBUTES
654                        | fio::Operations::UPDATE_ATTRIBUTES
655                        | fio::Operations::READ_BYTES
656                        | fio::Operations::WRITE_BYTES,
657                )
658            };
659
660            Ok(attributes!(
661                requested_attributes,
662                Mutable {
663                    creation_time: info.time_status_change.into_nanos() as u64,
664                    modification_time: info.time_modify.into_nanos() as u64,
665                    mode: info.mode.bits(),
666                    uid: info.uid,
667                    gid: info.gid,
668                    rdev: info.rdev.bits(),
669                },
670                Immutable {
671                    protocols: protocols,
672                    abilities: abilities,
673                    content_size: info.size as u64,
674                    storage_size: info.storage_size() as u64,
675                    link_count: link_count,
676                    id: file.fs.dev_id.bits(),
677                }
678            ))
679        })
680        .expect("spawn_task")
681    }
682
683    fn update_attributes(&self, attributes: fio::MutableNodeAttributes) {
684        let _ = self.spawn_task(async move |_, _, file| {
685            file.node().update_info(|info| {
686                if let Some(time) = attributes.creation_time {
687                    info.time_status_change = UtcInstant::from_nanos(time as i64);
688                }
689                if let Some(time) = attributes.modification_time {
690                    info.time_modify = UtcInstant::from_nanos(time as i64);
691                }
692                if let Some(mode) = attributes.mode {
693                    info.mode = FileMode::from_bits(mode);
694                }
695                if let Some(uid) = attributes.uid {
696                    info.uid = uid;
697                }
698                if let Some(gid) = attributes.gid {
699                    info.gid = gid;
700                }
701                if let Some(rdev) = attributes.rdev {
702                    info.rdev = DeviceId::from_bits(rdev);
703                }
704            });
705            Ok(())
706        });
707    }
708}
709
710impl vfs::node::Node for StarnixNodeConnection {
711    async fn get_attributes(
712        &self,
713        requested_attributes: fio::NodeAttributesQuery,
714    ) -> Result<fio::NodeAttributes2, zx::Status> {
715        Ok(StarnixNodeConnection::get_attributes(self, requested_attributes))
716    }
717}
718
719impl directory::entry::GetEntryInfo for StarnixNodeConnection {
720    fn entry_info(&self) -> directory::entry::EntryInfo {
721        let dirent_type =
722            if self.is_dir() { fio::DirentType::Directory } else { fio::DirentType::File };
723        directory::entry::EntryInfo::new(0, dirent_type)
724    }
725}
726
727impl directory::entry_container::Directory for StarnixNodeConnection {
728    fn open(
729        self: Arc<Self>,
730        scope: execution_scope::ExecutionScope,
731        path: path::Path,
732        flags: fio::Flags,
733        object_request: ObjectRequestRef<'_>,
734    ) -> Result<(), zx::Status> {
735        self.directory_entry_open(scope, flags, path, object_request)
736    }
737
738    async fn read_dirents(
739        &self,
740        pos: &directory::traversal_position::TraversalPosition,
741        sink: Box<dyn directory::dirents_sink::Sink>,
742    ) -> Result<
743        (
744            directory::traversal_position::TraversalPosition,
745            Box<dyn directory::dirents_sink::Sealed>,
746        ),
747        zx::Status,
748    > {
749        StarnixNodeConnection::directory_read_dirents(self, pos, sink).map_err(Errno::into)
750    }
751    fn register_watcher(
752        self: Arc<Self>,
753        _scope: execution_scope::ExecutionScope,
754        _mask: fio::WatchMask,
755        _watcher: directory::entry_container::DirectoryWatcher,
756    ) -> Result<(), zx::Status> {
757        track_stub!(TODO("https://fxbug.dev/322875605"), "register directory watcher");
758        Ok(())
759    }
760    fn unregister_watcher(self: Arc<Self>, _key: usize) {}
761}
762
763impl directory::entry_container::MutableDirectory for StarnixNodeConnection {
764    async fn update_attributes(
765        &self,
766        attributes: fio::MutableNodeAttributes,
767    ) -> Result<(), zx::Status> {
768        StarnixNodeConnection::update_attributes(self, attributes);
769        Ok(())
770    }
771    async fn unlink(
772        self: Arc<Self>,
773        name: &str,
774        must_be_directory: bool,
775    ) -> Result<(), zx::Status> {
776        let name = FsString::from(name.to_owned());
777        self.spawn_task_async(async move |locked, current_task, file| {
778            let kind =
779                if must_be_directory { UnlinkKind::Directory } else { UnlinkKind::NonDirectory };
780            file.name.entry.unlink(
781                locked,
782                current_task,
783                &file.name.mount,
784                name.as_ref(),
785                kind,
786                false,
787            )
788        })
789        .await?;
790        Ok(())
791    }
792    async fn sync(&self) -> Result<(), zx::Status> {
793        Ok(())
794    }
795    fn rename(
796        self: Arc<Self>,
797        src_dir: Arc<dyn directory::entry_container::MutableDirectory>,
798        src_name: path::Path,
799        dst_name: path::Path,
800    ) -> BoxFuture<'static, Result<(), zx::Status>> {
801        let this = self.clone();
802        Box::pin(async move {
803            Ok(self
804                .spawn_task_async(async move |locked, current_task, file| {
805                    let src_dir = src_dir
806                        .into_any()
807                        .downcast::<StarnixNodeConnection>()
808                        .map_err(|_| errno!(EXDEV))?;
809                    let (dst_node, dst_name) =
810                        lookup_parent(locked, current_task, &file, dst_name)?;
811                    let (src_node, src_name) = if Arc::ptr_eq(&src_dir, &this) {
812                        lookup_parent(locked, current_task, &file, src_name)?
813                    } else {
814                        src_dir.lookup_parent(src_name)?
815                    };
816                    NamespaceNode::rename(
817                        locked,
818                        current_task,
819                        &src_node,
820                        src_name.as_ref(),
821                        &dst_node,
822                        dst_name.as_ref(),
823                        RenameFlags::empty(),
824                    )
825                })
826                .await?)
827        })
828    }
829}
830
831impl file::File for StarnixNodeConnection {
832    fn writable(&self) -> bool {
833        true
834    }
835    async fn open_file(&self, _optionss: &file::FileOptions) -> Result<(), zx::Status> {
836        Ok(())
837    }
838    async fn truncate(&self, length: u64) -> Result<(), zx::Status> {
839        Ok(self
840            .spawn_task_async(async move |locked, current_task, file| {
841                // `ftruncate` checks fewer permissions than `file.name.truncate`, which is what we
842                // want.
843                file.ftruncate(locked, current_task, length)
844            })
845            .await?)
846    }
847    async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, zx::Status> {
848        Ok(self
849            .spawn_task_async(async move |locked, current_task, file| {
850                (|| {
851                    let mut prot_flags = ProtectionFlags::empty();
852                    if flags.contains(fio::VmoFlags::READ) {
853                        prot_flags |= ProtectionFlags::READ;
854                    }
855                    if flags.contains(fio::VmoFlags::WRITE) {
856                        prot_flags |= ProtectionFlags::WRITE;
857                    }
858                    if flags.contains(fio::VmoFlags::EXECUTE) {
859                        prot_flags |= ProtectionFlags::EXEC;
860                    }
861                    let memory = file.get_memory(locked, current_task, None, prot_flags)?;
862                    let vmo = memory.as_vmo().ok_or(zx::Status::NOT_SUPPORTED)?;
863                    if flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
864                        let size = vmo.get_size()?;
865                        vmo.create_child(zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE, 0, size)
866                    } else {
867                        vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)
868                    }
869                })()
870                .map_err(|e| from_status_like_fdio!(e))
871            })
872            .await?)
873    }
874
875    async fn get_size(&self) -> Result<u64, zx::Status> {
876        Ok(self
877            .spawn_task_async(async move |_, _, file| Ok(file.node().info().size as u64))
878            .await?)
879    }
880    async fn update_attributes(
881        &self,
882        attributes: fio::MutableNodeAttributes,
883    ) -> Result<(), zx::Status> {
884        StarnixNodeConnection::update_attributes(self, attributes);
885        Ok(())
886    }
887    async fn sync(&self, _mode: file::SyncMode) -> Result<(), zx::Status> {
888        Ok(())
889    }
890}
891
892impl file::RawFileIoConnection for StarnixNodeConnection {
893    async fn read(&self, count: u64) -> Result<Vec<u8>, zx::Status> {
894        self.stats.reads.fetch_add(1, Ordering::Relaxed);
895        let data: Vec<u8> = self
896            .spawn_task_async(async move |locked, current_task, file| {
897                let mut data = VecOutputBuffer::new(count as usize);
898                file.read(locked, current_task, &mut data)?;
899                Ok(data.into())
900            })
901            .await?;
902        self.stats.read_bytes.fetch_add(data.len() as u64, Ordering::Relaxed);
903        Ok(data)
904    }
905
906    async fn read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, zx::Status> {
907        self.stats.reads.fetch_add(1, Ordering::Relaxed);
908        let data: Vec<u8> = self
909            .spawn_task_async(async move |locked, current_task, file| {
910                let mut data = VecOutputBuffer::new(count as usize);
911                file.read_at(locked, current_task, offset as usize, &mut data)?;
912                Ok(data.into())
913            })
914            .await?;
915        self.stats.read_bytes.fetch_add(data.len() as u64, Ordering::Relaxed);
916        Ok(data)
917    }
918
919    async fn write(&self, content: &[u8]) -> Result<u64, zx::Status> {
920        self.stats.writes.fetch_add(1, Ordering::Relaxed);
921        let mut data = VecInputBuffer::new(content);
922        let written = self
923            .spawn_task_async(async move |locked, current_task, file| {
924                let written = file.write(locked, current_task, &mut data)?;
925                Ok(written as u64)
926            })
927            .await?;
928        self.stats.write_bytes.fetch_add(written, Ordering::Relaxed);
929        Ok(written)
930    }
931
932    async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, zx::Status> {
933        self.stats.writes.fetch_add(1, Ordering::Relaxed);
934        let mut data = VecInputBuffer::new(content);
935        let written = self
936            .spawn_task_async(async move |locked, current_task, file| {
937                let written = file.write_at(locked, current_task, offset as usize, &mut data)?;
938                Ok(written as u64)
939            })
940            .await?;
941        self.stats.write_bytes.fetch_add(written as u64, Ordering::Relaxed);
942        Ok(written)
943    }
944
945    async fn seek(&self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, zx::Status> {
946        let target = match origin {
947            fio::SeekOrigin::Start => SeekTarget::Set(offset),
948            fio::SeekOrigin::Current => SeekTarget::Cur(offset),
949            fio::SeekOrigin::End => SeekTarget::End(offset),
950        };
951        Ok(self.spawn_task(async move |locked, current_task, file| {
952            let seek_result = file.seek(locked, current_task, target)?;
953            Ok(seek_result as u64)
954        })?)
955    }
956
957    fn set_flags(&self, flags: fio::Flags) -> Result<(), zx::Status> {
958        // Called on the connection via `fcntl(FSETFL, ...)`. fuchsia.io only supports `O_APPEND`
959        // right now, and does not have equivalents for the following flags:
960        //  - `O_ASYNC`
961        //  - `O_DIRECT`
962        //  - `O_NOATIME` (only allowed if caller's EUID is same as the file's UID)
963        //  - `O_NONBLOCK`
964        const SETTABLE_FLAGS_MASK: OpenFlags = OpenFlags::APPEND;
965        let flags = if flags.contains(fio::Flags::FILE_APPEND) {
966            OpenFlags::APPEND
967        } else {
968            OpenFlags::empty()
969        };
970        Ok(self.spawn_task(async move |_, _, file| {
971            file.update_file_flags(flags, SETTABLE_FLAGS_MASK);
972            Ok(())
973        })?)
974    }
975}
976
977#[cfg(test)]
978mod tests {
979    use super::*;
980    use crate::fs::tmpfs::TmpFs;
981    use crate::testing::*;
982    use crate::vfs::{FsString, Namespace};
983    use starnix_uapi::auth::{Capabilities, Credentials};
984    use std::collections::HashSet;
985    use syncio::{Zxio, ZxioOpenOptions, zxio_node_attr_has_t};
986
987    fn assert_directory_content(zxio: &Zxio, content: &[&[u8]]) {
988        let expected = content.iter().map(|&x| FsString::from(x)).collect::<HashSet<_>>();
989        let mut iterator = zxio.create_dirent_iterator().expect("iterator");
990        iterator.rewind().expect("iterator");
991        let found =
992            iterator.map(|x| x.as_ref().expect("dirent").name.clone()).collect::<HashSet<_>>();
993        assert_eq!(found, expected);
994    }
995
996    #[::fuchsia::test]
997    async fn access_file_system() {
998        spawn_kernel_and_run(async |locked, current_task| {
999            let kernel = current_task.kernel();
1000            let fs = TmpFs::new_fs(locked, &kernel);
1001
1002            let file =
1003                &fs.root().open_anonymous(locked, current_task, OpenFlags::RDWR).expect("open");
1004            let (root_handle, scope) =
1005                serve_file(current_task, file, Credentials::root()).expect("serve");
1006
1007            // Capture information from the filesystem in the main thread. The filesystem must not be
1008            // transferred to the other thread.
1009            let fs_dev_id = fs.dev_id;
1010            std::thread::spawn(move || {
1011                let root_zxio = Zxio::create(root_handle.into_channel().into()).expect("create");
1012
1013                assert_directory_content(&root_zxio, &[b"."]);
1014                // Check that one can reiterate from the start.
1015                assert_directory_content(&root_zxio, &[b"."]);
1016
1017                let attrs = root_zxio
1018                    .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
1019                    .expect("attr_get");
1020                assert_eq!(attrs.id, fs_dev_id.bits());
1021
1022                let mut attrs = syncio::zxio_node_attributes_t::default();
1023                attrs.has.creation_time = true;
1024                attrs.has.modification_time = true;
1025                attrs.creation_time = 0;
1026                attrs.modification_time = 42;
1027                root_zxio.attr_set(&attrs).expect("attr_set");
1028                let attrs = root_zxio
1029                    .attr_get(zxio_node_attr_has_t {
1030                        creation_time: true,
1031                        modification_time: true,
1032                        ..Default::default()
1033                    })
1034                    .expect("attr_get");
1035                assert_eq!(attrs.creation_time, 0);
1036                assert_eq!(attrs.modification_time, 42);
1037
1038                assert_eq!(
1039                    root_zxio
1040                        .open("foo", fio::PERM_READABLE | fio::PERM_WRITABLE, Default::default())
1041                        .expect_err("open"),
1042                    zx::Status::NOT_FOUND
1043                );
1044                let foo_zxio = root_zxio
1045                    .open(
1046                        "foo",
1047                        fio::PERM_READABLE
1048                            | fio::PERM_WRITABLE
1049                            | fio::Flags::FLAG_MAYBE_CREATE
1050                            | fio::Flags::PROTOCOL_FILE,
1051                        Default::default(),
1052                    )
1053                    .expect("zxio_open");
1054                assert_directory_content(&root_zxio, &[b".", b"foo"]);
1055
1056                assert_eq!(foo_zxio.write(b"hello").expect("write"), 5);
1057                assert_eq!(foo_zxio.write_at(2, b"ch").expect("write_at"), 2);
1058                let mut buffer = [0; 7];
1059                assert_eq!(foo_zxio.read_at(2, &mut buffer).expect("read_at"), 3);
1060                assert_eq!(&buffer[..3], b"cho");
1061                assert_eq!(foo_zxio.seek(syncio::SeekOrigin::Start, 0).expect("seek"), 0);
1062                assert_eq!(foo_zxio.read(&mut buffer).expect("read"), 5);
1063                assert_eq!(&buffer[..5], b"hecho");
1064
1065                let attrs = foo_zxio
1066                    .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
1067                    .expect("attr_get");
1068                assert_eq!(attrs.id, fs_dev_id.bits());
1069
1070                let mut attrs = syncio::zxio_node_attributes_t::default();
1071                attrs.has.creation_time = true;
1072                attrs.has.modification_time = true;
1073                attrs.creation_time = 0;
1074                attrs.modification_time = 42;
1075                foo_zxio.attr_set(&attrs).expect("attr_set");
1076                let attrs = foo_zxio
1077                    .attr_get(zxio_node_attr_has_t {
1078                        creation_time: true,
1079                        modification_time: true,
1080                        ..Default::default()
1081                    })
1082                    .expect("attr_get");
1083                assert_eq!(attrs.creation_time, 0);
1084                assert_eq!(attrs.modification_time, 42);
1085
1086                assert_eq!(
1087                    root_zxio
1088                        .open(
1089                            "bar/baz",
1090                            fio::Flags::PROTOCOL_DIRECTORY
1091                                | fio::Flags::FLAG_MAYBE_CREATE
1092                                | fio::PERM_READABLE
1093                                | fio::PERM_WRITABLE,
1094                            Default::default(),
1095                        )
1096                        .expect_err("open"),
1097                    zx::Status::NOT_FOUND
1098                );
1099
1100                let bar_zxio = root_zxio
1101                    .open(
1102                        "bar",
1103                        fio::Flags::PROTOCOL_DIRECTORY
1104                            | fio::Flags::FLAG_MAYBE_CREATE
1105                            | fio::PERM_READABLE
1106                            | fio::PERM_WRITABLE,
1107                        Default::default(),
1108                    )
1109                    .expect("open");
1110                let baz_zxio = root_zxio
1111                    .open(
1112                        "bar/baz",
1113                        fio::Flags::PROTOCOL_DIRECTORY
1114                            | fio::Flags::FLAG_MAYBE_CREATE
1115                            | fio::PERM_READABLE
1116                            | fio::PERM_WRITABLE,
1117                        Default::default(),
1118                    )
1119                    .expect("open");
1120                assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1121                assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1122
1123                bar_zxio.rename("baz", &root_zxio, "quz").expect("rename");
1124                assert_directory_content(&bar_zxio, &[b"."]);
1125                assert_directory_content(&root_zxio, &[b".", b"foo", b"bar", b"quz"]);
1126                assert_directory_content(&baz_zxio, &[b"."]);
1127            })
1128            .join()
1129            .expect("join");
1130            scope.shutdown();
1131            scope.wait().await;
1132            // This ensures fs cannot be captures in the thread.
1133            std::mem::drop(fs);
1134        })
1135        .await;
1136    }
1137
1138    #[::fuchsia::test]
1139    async fn serve_file_strips_trunc() {
1140        spawn_kernel_and_run(async |locked, current_task| {
1141            let kernel = current_task.kernel();
1142            let fs = TmpFs::new_fs(locked, &kernel);
1143            let ns = Namespace::new(fs);
1144            let root = ns.root();
1145
1146            let file_node = root
1147                .create_node(
1148                    locked,
1149                    current_task,
1150                    b"test".into(),
1151                    FileMode::IFREG | FileMode::ALLOW_ALL,
1152                    DeviceId::NONE,
1153                )
1154                .expect("create_node");
1155
1156            let file = file_node
1157                .open(locked, current_task, OpenFlags::RDWR, AccessCheck::skip())
1158                .expect("open");
1159            file.write(locked, current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1160
1161            // Reopen with O_TRUNC.
1162            let file_to_serve = current_task
1163                .open_namespace_node_at(
1164                    locked,
1165                    root,
1166                    b"test".into(),
1167                    OpenFlags::RDWR | OpenFlags::TRUNC,
1168                    FileMode::default(),
1169                    ResolveFlags::default(),
1170                    AccessCheck::skip(),
1171                )
1172                .expect("open O_TRUNC");
1173
1174            // Ensure it IS truncated by the open.
1175            assert_eq!(
1176                file_to_serve.node().fetch_and_refresh_info(locked, current_task).unwrap().size,
1177                0
1178            );
1179
1180            // Write something so we can check if it gets truncated again.
1181            file_to_serve
1182                .write(locked, current_task, &mut VecInputBuffer::new(b"world"))
1183                .expect("write world");
1184            assert_eq!(file_to_serve.node().info().size, 5);
1185
1186            let (client_end, scope) =
1187                serve_file(current_task, &file_to_serve, Credentials::root()).expect("serve");
1188
1189            fuchsia_async::unblock(|| {
1190                let zxio = Zxio::create(client_end.into_channel().into()).expect("create");
1191                let mut attr = syncio::zxio_node_attributes_t::default();
1192                attr.has.content_size = true;
1193                let attr = zxio.attr_get(attr.has).expect("attr_get");
1194                // If O_TRUNC was not stripped, the size would be 0 here.
1195                assert_eq!(attr.content_size, 5);
1196            })
1197            .await;
1198
1199            scope.shutdown();
1200            scope.wait().await;
1201        })
1202        .await;
1203    }
1204
1205    #[::fuchsia::test]
1206    async fn truncate_checks_fd_permissions() {
1207        spawn_kernel_and_run(async |locked, current_task| {
1208            let kernel = current_task.kernel();
1209            let fs = TmpFs::new_fs(locked, &kernel);
1210            let ns = Namespace::new(fs);
1211            let root = ns.root();
1212
1213            let file_node = root
1214                .create_node(
1215                    locked,
1216                    current_task,
1217                    "test".into(),
1218                    FileMode::IFREG | FileMode::IRWXU,
1219                    DeviceId::NONE,
1220                )
1221                .expect("create_node");
1222
1223            let file = file_node
1224                .open(locked, current_task, OpenFlags::RDWR, AccessCheck::skip())
1225                .expect("open");
1226            file.write(locked, current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1227
1228            // Serve the file as different user.
1229            let (client_end, scope) = serve_file(
1230                current_task,
1231                &file,
1232                Arc::new(Credentials {
1233                    fsuid: 2000,
1234                    cap_effective: Capabilities::empty(),
1235                    ..Credentials::clone(&current_task.current_creds())
1236                }),
1237            )
1238            .expect("serve");
1239
1240            fuchsia_async::unblock(move || {
1241                let zxio = Zxio::create(client_end.into_channel().into()).expect("create");
1242                // truncate should succeed because the FD is open for writing, even though the file
1243                // is being served with a different user.
1244                zxio.truncate(2).expect("truncate");
1245
1246                let mut attr = syncio::zxio_node_attributes_t::default();
1247                attr.has.content_size = true;
1248                let attr = zxio.attr_get(attr.has).expect("attr_get");
1249                assert_eq!(attr.content_size, 2);
1250            })
1251            .await;
1252
1253            scope.shutdown();
1254            scope.wait().await;
1255        })
1256        .await;
1257    }
1258
1259    #[::fuchsia::test]
1260    async fn open() {
1261        spawn_kernel_and_run(async |locked, current_task| {
1262            let kernel = current_task.kernel();
1263            let fs = TmpFs::new_fs(locked, &kernel);
1264
1265            let file = &fs
1266                .root()
1267                .open_anonymous(locked, current_task, OpenFlags::RDWR)
1268                .expect("open_anonymous failed");
1269            let (root_handle, scope) =
1270                serve_file(current_task, file, Credentials::root()).expect("serve_file failed");
1271
1272            std::thread::spawn(move || {
1273                let root_zxio =
1274                    Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1275
1276                assert_directory_content(&root_zxio, &[b"."]);
1277                assert_eq!(
1278                    root_zxio
1279                        .open(
1280                            "foo",
1281                            fio::PERM_READABLE | fio::PERM_WRITABLE,
1282                            ZxioOpenOptions::default()
1283                        )
1284                        .expect_err("open3 passed unexpectedly"),
1285                    zx::Status::NOT_FOUND
1286                );
1287                root_zxio
1288                    .open(
1289                        "foo",
1290                        fio::Flags::PROTOCOL_FILE
1291                            | fio::PERM_READABLE
1292                            | fio::PERM_WRITABLE
1293                            | fio::Flags::FLAG_MUST_CREATE,
1294                        ZxioOpenOptions::default(),
1295                    )
1296                    .expect("open3 failed");
1297                assert_directory_content(&root_zxio, &[b".", b"foo"]);
1298
1299                assert_eq!(
1300                    root_zxio
1301                        .open(
1302                            "bar/baz",
1303                            fio::Flags::PROTOCOL_DIRECTORY
1304                                | fio::PERM_READABLE
1305                                | fio::PERM_WRITABLE
1306                                | fio::Flags::FLAG_MUST_CREATE,
1307                            ZxioOpenOptions::default()
1308                        )
1309                        .expect_err("open3 passed unexpectedly"),
1310                    zx::Status::NOT_FOUND
1311                );
1312                let bar_zxio = root_zxio
1313                    .open(
1314                        "bar",
1315                        fio::Flags::PROTOCOL_DIRECTORY
1316                            | fio::PERM_READABLE
1317                            | fio::PERM_WRITABLE
1318                            | fio::Flags::FLAG_MUST_CREATE,
1319                        ZxioOpenOptions::default(),
1320                    )
1321                    .expect("open3 failed");
1322                root_zxio
1323                    .open(
1324                        "bar/baz",
1325                        fio::Flags::PROTOCOL_DIRECTORY
1326                            | fio::PERM_READABLE
1327                            | fio::PERM_WRITABLE
1328                            | fio::Flags::FLAG_MUST_CREATE,
1329                        ZxioOpenOptions::default(),
1330                    )
1331                    .expect("open3 failed");
1332                assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1333                assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1334            })
1335            .join()
1336            .expect("join");
1337            scope.shutdown();
1338            scope.wait().await;
1339
1340            // This ensures fs cannot be captured in the thread.
1341            std::mem::drop(fs);
1342        })
1343        .await;
1344    }
1345
1346    #[::fuchsia::test]
1347    async fn use_credentials() {
1348        spawn_kernel_and_run(async |locked, current_task| {
1349            let kernel = current_task.kernel();
1350            let fs = TmpFs::new_fs(locked, &kernel);
1351
1352            let file = &fs
1353                .root()
1354                .open_anonymous(locked, current_task, OpenFlags::RDWR)
1355                .expect("open_anonymous failed");
1356            // Create a file as root.
1357            let ns = Namespace::new(fs);
1358            ns.root()
1359                .open_create_node(
1360                    locked,
1361                    current_task,
1362                    "test".into(),
1363                    FileMode::from_bits(0o600) | FileMode::IFREG,
1364                    DeviceId::NONE,
1365                    OpenFlags::empty(),
1366                )
1367                .expect("open_create_node failed");
1368
1369            let mut creds = Credentials::with_ids(0, 0);
1370            creds.fsuid = 1;
1371            creds.cap_effective = Capabilities::empty();
1372
1373            let (root_handle, scope) =
1374                serve_file(current_task, file, creds.into()).expect("serve_file failed");
1375
1376            std::thread::spawn(move || {
1377                let root_zxio =
1378                    Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1379
1380                assert_directory_content(&root_zxio, &[b".", b"test"]);
1381                assert_eq!(
1382                    root_zxio
1383                        .open(
1384                            "test",
1385                            fio::PERM_READABLE | fio::PERM_WRITABLE,
1386                            ZxioOpenOptions::default()
1387                        )
1388                        .expect_err("open3 passed unexpectedly"),
1389                    zx::Status::ACCESS_DENIED
1390                );
1391            })
1392            .join()
1393            .expect("join");
1394            scope.shutdown();
1395            scope.wait().await;
1396
1397            // This ensures fs cannot be captured in the thread.
1398            std::mem::drop(ns);
1399        })
1400        .await;
1401    }
1402
1403    #[::fuchsia::test]
1404    async fn large_directory_listing() {
1405        spawn_kernel_and_run(async |locked, current_task| {
1406            let kernel = current_task.kernel();
1407            let fs = TmpFs::new_fs(locked, &kernel);
1408
1409            let file = &fs
1410                .root()
1411                .open_anonymous(locked, current_task, OpenFlags::RDWR)
1412                .expect("open_anonymous failed");
1413
1414            let ns = Namespace::new(fs);
1415            let mut expected_files = vec![b".".to_vec()];
1416            // Create 500 files. Through trial and error, this number was found to exceed the sink's
1417            // buffer capacity.
1418            for i in 0..500 {
1419                let name = format!("file_{:03}", i);
1420                ns.root()
1421                    .open_create_node(
1422                        locked,
1423                        current_task,
1424                        name.as_str().into(),
1425                        FileMode::from_bits(0o600) | FileMode::IFREG,
1426                        DeviceId::NONE,
1427                        OpenFlags::empty(),
1428                    )
1429                    .expect("open_create_node failed");
1430                expected_files.push(name.into_bytes());
1431            }
1432
1433            let (root_handle, scope) =
1434                serve_file(current_task, file, Credentials::root()).expect("serve_file failed");
1435
1436            std::thread::spawn(move || {
1437                let root_zxio =
1438                    Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1439
1440                let expected = expected_files
1441                    .iter()
1442                    .map(|x| FsString::from(x.clone()))
1443                    .collect::<HashSet<_>>();
1444                let mut iterator =
1445                    root_zxio.create_dirent_iterator().expect("create_dirent_iterator failed");
1446                iterator.rewind().expect("rewind failed");
1447
1448                let mut found = HashSet::new();
1449                for res in iterator {
1450                    match res {
1451                        Ok(dirent) => {
1452                            found.insert(dirent.name.clone());
1453                        }
1454                        Err(status) => {
1455                            panic!("Iterator returned error: {:?}", status);
1456                        }
1457                    }
1458                }
1459                assert_eq!(found, expected);
1460            })
1461            .join()
1462            .expect("thread join failed");
1463            scope.shutdown();
1464            scope.wait().await;
1465
1466            // This ensures fs cannot be captured in the thread.
1467            std::mem::drop(ns);
1468        })
1469        .await;
1470    }
1471
1472    #[::fuchsia::test]
1473    async fn readdir_propagates_genuine_nospc_error() {
1474        spawn_kernel_and_run(async |locked, current_task| {
1475            use crate::vfs::{
1476                DirEntry, FileOps, FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps,
1477                fs_node_impl_dir_readonly,
1478            };
1479            use crate::{
1480                fileops_impl_directory, fileops_impl_noop_sync, fileops_impl_unbounded_seek,
1481            };
1482            use starnix_sync::FileOpsCore;
1483
1484            struct FaultyDirectory;
1485            impl FileOps for FaultyDirectory {
1486                fileops_impl_directory!();
1487                fileops_impl_noop_sync!();
1488                fileops_impl_unbounded_seek!();
1489
1490                fn readdir(
1491                    &self,
1492                    _locked: &mut Locked<FileOpsCore>,
1493                    _file: &FileObject,
1494                    _current_task: &CurrentTask,
1495                    _sink: &mut dyn DirentSink,
1496                ) -> Result<(), Errno> {
1497                    error!(ENOSPC)
1498                }
1499            }
1500
1501            struct FaultyDirectoryNode;
1502            impl FsNodeOps for FaultyDirectoryNode {
1503                fs_node_impl_dir_readonly!();
1504
1505                fn create_file_ops(
1506                    &self,
1507                    _locked: &mut Locked<FileOpsCore>,
1508                    _node: &FsNode,
1509                    _current_task: &CurrentTask,
1510                    _flags: OpenFlags,
1511                ) -> Result<Box<dyn FileOps>, Errno> {
1512                    Ok(Box::new(FaultyDirectory))
1513                }
1514
1515                fn lookup(
1516                    &self,
1517                    _locked: &mut Locked<FileOpsCore>,
1518                    _node: &FsNode,
1519                    _current_task: &CurrentTask,
1520                    _name: &FsStr,
1521                ) -> Result<FsNodeHandle, Errno> {
1522                    error!(ENOENT)
1523                }
1524            }
1525
1526            let kernel = current_task.kernel();
1527            let fs = TmpFs::new_fs(locked, &kernel);
1528
1529            let ino = fs.allocate_ino();
1530            let info = FsNodeInfo::new(
1531                FileMode::from_bits(0o777) | FileMode::IFDIR,
1532                current_task.current_fscred(),
1533            );
1534            let node = fs.create_node(ino, FaultyDirectoryNode, info);
1535            let dir_entry = DirEntry::new(node, None, "faulty_dir".into());
1536            let name = NamespaceNode::new_anonymous(dir_entry);
1537            let file = FileObject::new(
1538                locked,
1539                current_task,
1540                Box::new(FaultyDirectory),
1541                name,
1542                OpenFlags::DIRECTORY | OpenFlags::RDONLY,
1543            )
1544            .expect("FileObject::new failed");
1545
1546            let (root_handle, scope) =
1547                serve_file(current_task, &file, Credentials::root()).expect("serve_file failed");
1548
1549            std::thread::spawn(move || {
1550                let root_zxio =
1551                    Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1552
1553                let mut iterator =
1554                    root_zxio.create_dirent_iterator().expect("create_dirent_iterator failed");
1555                iterator.rewind().expect("rewind failed");
1556
1557                let mut got_error = false;
1558                for res in iterator {
1559                    if let Err(status) = res {
1560                        assert_eq!(status, zx::Status::NO_SPACE);
1561                        got_error = true;
1562                        break;
1563                    }
1564                }
1565                assert!(got_error, "Expected iterator to fail with NO_SPACE");
1566            })
1567            .join()
1568            .expect("thread join failed");
1569            scope.shutdown();
1570            scope.wait().await;
1571        })
1572        .await;
1573    }
1574}