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::HandleBased;
14use fidl::endpoints::{ClientEnd, ServerEnd};
15use fidl_fuchsia_io as fio;
16use fuchsia_runtime::UtcInstant;
17use futures::future::BoxFuture;
18use itertools::Either;
19use starnix_logging::{log_error, track_stub};
20use starnix_sync::{Locked, Unlocked};
21use starnix_types::convert::IntoFidl as _;
22use starnix_uapi::auth::Credentials;
23use starnix_uapi::device_id::DeviceId;
24use starnix_uapi::errors::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: starnix_sync::Mutex<HashMap<&'static str, Arc<FileServerStats>>>,
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: starnix_sync::Mutex::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    receiver: std::sync::mpsc::Receiver<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 Ok(w) = receiver.recv() {
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: std::sync::mpsc::Sender<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) = std::sync::mpsc::channel();
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            .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            .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 append(
438                    &mut self,
439                    entry: &directory::entry::EntryInfo,
440                    name: &str,
441                ) -> Result<(), Errno> {
442                    let sink = self.sink.take();
443                    self.sink = match sink {
444                        s @ Some(directory::dirents_sink::AppendResult::Sealed(_)) => {
445                            self.sink = s;
446                            return error!(ENOSPC);
447                        }
448                        Some(directory::dirents_sink::AppendResult::Ok(sink)) => {
449                            Some(sink.append(entry, name))
450                        }
451                        None => return error!(ENOTSUP),
452                    };
453                    Ok(())
454                }
455            }
456            impl<'a> DirentSink for DirentSinkAdapter<'a> {
457                fn add(
458                    &mut self,
459                    inode_num: ino_t,
460                    offset: off_t,
461                    entry_type: DirectoryEntryType,
462                    name: &FsStr,
463                ) -> Result<(), Errno> {
464                    // Ignore ..
465                    if name != ".." {
466                        // Ignore entries with unknown types.
467                        if let Some(dirent_type) =
468                            fio::DirentType::from_primitive(entry_type.bits())
469                        {
470                            let entry_info =
471                                directory::entry::EntryInfo::new(inode_num, dirent_type);
472                            self.append(&entry_info, &String::from_utf8_lossy(name))?
473                        }
474                    }
475                    *self.offset = offset;
476                    Ok(())
477                }
478                fn offset(&self) -> off_t {
479                    *self.offset
480                }
481            }
482            let offset = match pos {
483                directory::traversal_position::TraversalPosition::Start => 0,
484                directory::traversal_position::TraversalPosition::Index(v) => v as i64,
485                directory::traversal_position::TraversalPosition::End => {
486                    return Ok((
487                        directory::traversal_position::TraversalPosition::End,
488                        sink.seal(),
489                    ));
490                }
491                _ => return error!(EINVAL),
492            };
493            if file.offset.read() != offset {
494                file.seek(locked, current_task, SeekTarget::Set(offset))?;
495            }
496            let mut file_offset = file.offset.copy();
497            let sink_result = {
498                let mut dirent_sink = DirentSinkAdapter {
499                    sink: Some(directory::dirents_sink::AppendResult::Ok(sink)),
500                    offset: &mut *file_offset,
501                };
502                file.readdir(locked, current_task, &mut dirent_sink)?;
503                dirent_sink.sink
504            };
505            let ret = match sink_result {
506                Some(directory::dirents_sink::AppendResult::Sealed(seal)) => {
507                    Ok((directory::traversal_position::TraversalPosition::End, seal))
508                }
509                Some(directory::dirents_sink::AppendResult::Ok(sink)) => Ok((
510                    directory::traversal_position::TraversalPosition::Index(*file_offset as u64),
511                    sink.seal(),
512                )),
513                None => error!(ENOTSUP),
514            };
515            file_offset.update();
516            ret
517        })
518    }
519
520    /// Implementation of `vfs::directory::entry::DirectoryEntry::open`.
521    fn directory_entry_open(
522        self: Arc<Self>,
523        scope: execution_scope::ExecutionScope,
524        flags: impl ProtocolsExt,
525        path: path::Path,
526        object_request: ObjectRequestRef<'_>,
527    ) -> Result<(), zx::Status> {
528        if self.is_dir() {
529            if path.is_dot() {
530                // Reopen the current directory.
531                let dir = self.reopen(&flags)?;
532                object_request
533                    .take()
534                    .create_connection_sync::<MutableConnection<_>, _>(scope, dir, flags);
535                return Ok(());
536            }
537
538            // Open a path under the current directory.
539            let starnix_file = self.spawn_task({
540                let credentials = self.credentials.clone();
541                let stats = self.stats.clone();
542                let create_directory = flags.creation_mode() != vfs::common::CreationMode::Never
543                    && flags.create_directory();
544                let open_flags = to_open_flags(&flags);
545                async move |locked, current_task, file| {
546                    let (node, name) = lookup_parent(locked, current_task, file, path)?;
547                    let file = match current_task.open_namespace_node_at(
548                        locked,
549                        node.clone(),
550                        name.as_ref(),
551                        open_flags,
552                        FileMode::ALLOW_ALL,
553                        ResolveFlags::empty(),
554                        AccessCheck::default(),
555                    ) {
556                        Err(e) if e == errno!(EISDIR) && create_directory => {
557                            let mode = current_task
558                                .fs()
559                                .apply_umask(FileMode::from_bits(0o777) | FileMode::IFDIR);
560                            let name = node.create_node(
561                                locked,
562                                &current_task,
563                                name.as_ref(),
564                                mode,
565                                DeviceId::NONE,
566                            )?;
567                            name.open(
568                                locked,
569                                &current_task,
570                                open_flags & !(OpenFlags::CREAT | OpenFlags::EXCL),
571                                AccessCheck::skip(),
572                            )?
573                        }
574                        f => f?,
575                    };
576                    Ok(StarnixNodeConnection::new(&current_task.kernel(), file, credentials, stats))
577                }
578            })?;
579
580            return starnix_file.directory_entry_open(
581                scope,
582                flags,
583                path::Path::dot(),
584                object_request,
585            );
586        }
587
588        // Reopen the current file.
589        if !path.is_dot() {
590            return Err(zx::Status::NOT_DIR);
591        }
592        let file = self.reopen(&flags)?;
593        object_request
594            .take()
595            .create_connection_sync::<file::RawIoConnection<_>, _>(scope, file, flags);
596        Ok(())
597    }
598
599    fn get_attributes(
600        &self,
601        requested_attributes: fio::NodeAttributesQuery,
602    ) -> fio::NodeAttributes2 {
603        self.spawn_task(async move |_, _, file| {
604            let info = file.node().info();
605
606            // This cast is necessary depending on the architecture.
607            #[allow(clippy::unnecessary_cast)]
608            let link_count = info.link_count as u64;
609
610            let (protocols, abilities) = if info.mode.contains(FileMode::IFDIR) {
611                (
612                    fio::NodeProtocolKinds::DIRECTORY,
613                    fio::Operations::GET_ATTRIBUTES
614                        | fio::Operations::UPDATE_ATTRIBUTES
615                        | fio::Operations::ENUMERATE
616                        | fio::Operations::TRAVERSE
617                        | fio::Operations::MODIFY_DIRECTORY,
618                )
619            } else {
620                (
621                    fio::NodeProtocolKinds::FILE,
622                    fio::Operations::GET_ATTRIBUTES
623                        | fio::Operations::UPDATE_ATTRIBUTES
624                        | fio::Operations::READ_BYTES
625                        | fio::Operations::WRITE_BYTES,
626                )
627            };
628
629            Ok(attributes!(
630                requested_attributes,
631                Mutable {
632                    creation_time: info.time_status_change.into_nanos() as u64,
633                    modification_time: info.time_modify.into_nanos() as u64,
634                    mode: info.mode.bits(),
635                    uid: info.uid,
636                    gid: info.gid,
637                    rdev: info.rdev.bits(),
638                },
639                Immutable {
640                    protocols: protocols,
641                    abilities: abilities,
642                    content_size: info.size as u64,
643                    storage_size: info.storage_size() as u64,
644                    link_count: link_count,
645                    id: file.fs.dev_id.bits(),
646                }
647            ))
648        })
649        .expect("spawn_task")
650    }
651
652    fn update_attributes(&self, attributes: fio::MutableNodeAttributes) {
653        let _ = self.spawn_task(async move |_, _, file| {
654            file.node().update_info(|info| {
655                if let Some(time) = attributes.creation_time {
656                    info.time_status_change = UtcInstant::from_nanos(time as i64);
657                }
658                if let Some(time) = attributes.modification_time {
659                    info.time_modify = UtcInstant::from_nanos(time as i64);
660                }
661                if let Some(mode) = attributes.mode {
662                    info.mode = FileMode::from_bits(mode);
663                }
664                if let Some(uid) = attributes.uid {
665                    info.uid = uid;
666                }
667                if let Some(gid) = attributes.gid {
668                    info.gid = gid;
669                }
670                if let Some(rdev) = attributes.rdev {
671                    info.rdev = DeviceId::from_bits(rdev);
672                }
673            });
674            Ok(())
675        });
676    }
677}
678
679impl vfs::node::Node for StarnixNodeConnection {
680    async fn get_attributes(
681        &self,
682        requested_attributes: fio::NodeAttributesQuery,
683    ) -> Result<fio::NodeAttributes2, zx::Status> {
684        Ok(StarnixNodeConnection::get_attributes(self, requested_attributes))
685    }
686}
687
688impl directory::entry::GetEntryInfo for StarnixNodeConnection {
689    fn entry_info(&self) -> directory::entry::EntryInfo {
690        let dirent_type =
691            if self.is_dir() { fio::DirentType::Directory } else { fio::DirentType::File };
692        directory::entry::EntryInfo::new(0, dirent_type)
693    }
694}
695
696impl directory::entry_container::Directory for StarnixNodeConnection {
697    fn open(
698        self: Arc<Self>,
699        scope: execution_scope::ExecutionScope,
700        path: path::Path,
701        flags: fio::Flags,
702        object_request: ObjectRequestRef<'_>,
703    ) -> Result<(), zx::Status> {
704        self.directory_entry_open(scope, flags, path, object_request)
705    }
706
707    async fn read_dirents(
708        &self,
709        pos: &directory::traversal_position::TraversalPosition,
710        sink: Box<dyn directory::dirents_sink::Sink>,
711    ) -> Result<
712        (
713            directory::traversal_position::TraversalPosition,
714            Box<dyn directory::dirents_sink::Sealed>,
715        ),
716        zx::Status,
717    > {
718        StarnixNodeConnection::directory_read_dirents(self, pos, sink).map_err(Errno::into)
719    }
720    fn register_watcher(
721        self: Arc<Self>,
722        _scope: execution_scope::ExecutionScope,
723        _mask: fio::WatchMask,
724        _watcher: directory::entry_container::DirectoryWatcher,
725    ) -> Result<(), zx::Status> {
726        track_stub!(TODO("https://fxbug.dev/322875605"), "register directory watcher");
727        Ok(())
728    }
729    fn unregister_watcher(self: Arc<Self>, _key: usize) {}
730}
731
732impl directory::entry_container::MutableDirectory for StarnixNodeConnection {
733    async fn update_attributes(
734        &self,
735        attributes: fio::MutableNodeAttributes,
736    ) -> Result<(), zx::Status> {
737        StarnixNodeConnection::update_attributes(self, attributes);
738        Ok(())
739    }
740    async fn unlink(
741        self: Arc<Self>,
742        name: &str,
743        must_be_directory: bool,
744    ) -> Result<(), zx::Status> {
745        let name = FsString::from(name.to_owned());
746        self.spawn_task_async(async move |locked, current_task, file| {
747            let kind =
748                if must_be_directory { UnlinkKind::Directory } else { UnlinkKind::NonDirectory };
749            file.name.entry.unlink(
750                locked,
751                current_task,
752                &file.name.mount,
753                name.as_ref(),
754                kind,
755                false,
756            )
757        })
758        .await?;
759        Ok(())
760    }
761    async fn sync(&self) -> Result<(), zx::Status> {
762        Ok(())
763    }
764    fn rename(
765        self: Arc<Self>,
766        src_dir: Arc<dyn directory::entry_container::MutableDirectory>,
767        src_name: path::Path,
768        dst_name: path::Path,
769    ) -> BoxFuture<'static, Result<(), zx::Status>> {
770        let this = self.clone();
771        Box::pin(async move {
772            Ok(self
773                .spawn_task_async(async move |locked, current_task, file| {
774                    let src_dir = src_dir
775                        .into_any()
776                        .downcast::<StarnixNodeConnection>()
777                        .map_err(|_| errno!(EXDEV))?;
778                    let (dst_node, dst_name) =
779                        lookup_parent(locked, current_task, &file, dst_name)?;
780                    let (src_node, src_name) = if Arc::ptr_eq(&src_dir, &this) {
781                        lookup_parent(locked, current_task, &file, src_name)?
782                    } else {
783                        src_dir.lookup_parent(src_name)?
784                    };
785                    NamespaceNode::rename(
786                        locked,
787                        current_task,
788                        &src_node,
789                        src_name.as_ref(),
790                        &dst_node,
791                        dst_name.as_ref(),
792                        RenameFlags::empty(),
793                    )
794                })
795                .await?)
796        })
797    }
798}
799
800impl file::File for StarnixNodeConnection {
801    fn writable(&self) -> bool {
802        true
803    }
804    async fn open_file(&self, _optionss: &file::FileOptions) -> Result<(), zx::Status> {
805        Ok(())
806    }
807    async fn truncate(&self, length: u64) -> Result<(), zx::Status> {
808        Ok(self
809            .spawn_task_async(async move |locked, current_task, file| {
810                // `ftruncate` checks fewer permissions than `file.name.truncate`, which is what we
811                // want.
812                file.ftruncate(locked, current_task, length)
813            })
814            .await?)
815    }
816    async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, zx::Status> {
817        Ok(self
818            .spawn_task_async(async move |locked, current_task, file| {
819                (|| {
820                    let mut prot_flags = ProtectionFlags::empty();
821                    if flags.contains(fio::VmoFlags::READ) {
822                        prot_flags |= ProtectionFlags::READ;
823                    }
824                    if flags.contains(fio::VmoFlags::WRITE) {
825                        prot_flags |= ProtectionFlags::WRITE;
826                    }
827                    if flags.contains(fio::VmoFlags::EXECUTE) {
828                        prot_flags |= ProtectionFlags::EXEC;
829                    }
830                    let memory = file.get_memory(locked, current_task, None, prot_flags)?;
831                    let vmo = memory.as_vmo().ok_or(zx::Status::NOT_SUPPORTED)?;
832                    if flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
833                        let size = vmo.get_size()?;
834                        vmo.create_child(zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE, 0, size)
835                    } else {
836                        vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)
837                    }
838                })()
839                .map_err(|e| from_status_like_fdio!(e))
840            })
841            .await?)
842    }
843
844    async fn get_size(&self) -> Result<u64, zx::Status> {
845        Ok(self
846            .spawn_task_async(async move |_, _, file| Ok(file.node().info().size as u64))
847            .await?)
848    }
849    async fn update_attributes(
850        &self,
851        attributes: fio::MutableNodeAttributes,
852    ) -> Result<(), zx::Status> {
853        StarnixNodeConnection::update_attributes(self, attributes);
854        Ok(())
855    }
856    async fn sync(&self, _mode: file::SyncMode) -> Result<(), zx::Status> {
857        Ok(())
858    }
859}
860
861impl file::RawFileIoConnection for StarnixNodeConnection {
862    async fn read(&self, count: u64) -> Result<Vec<u8>, zx::Status> {
863        self.stats.reads.fetch_add(1, Ordering::Relaxed);
864        let data: Vec<u8> = self
865            .spawn_task_async(async move |locked, current_task, file| {
866                let mut data = VecOutputBuffer::new(count as usize);
867                file.read(locked, current_task, &mut data)?;
868                Ok(data.into())
869            })
870            .await?;
871        self.stats.read_bytes.fetch_add(data.len() as u64, Ordering::Relaxed);
872        Ok(data)
873    }
874
875    async fn read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, zx::Status> {
876        self.stats.reads.fetch_add(1, Ordering::Relaxed);
877        let data: Vec<u8> = self
878            .spawn_task_async(async move |locked, current_task, file| {
879                let mut data = VecOutputBuffer::new(count as usize);
880                file.read_at(locked, current_task, offset as usize, &mut data)?;
881                Ok(data.into())
882            })
883            .await?;
884        self.stats.read_bytes.fetch_add(data.len() as u64, Ordering::Relaxed);
885        Ok(data)
886    }
887
888    async fn write(&self, content: &[u8]) -> Result<u64, zx::Status> {
889        self.stats.writes.fetch_add(1, Ordering::Relaxed);
890        let mut data = VecInputBuffer::new(content);
891        let written = self
892            .spawn_task_async(async move |locked, current_task, file| {
893                let written = file.write(locked, current_task, &mut data)?;
894                Ok(written as u64)
895            })
896            .await?;
897        self.stats.write_bytes.fetch_add(written, Ordering::Relaxed);
898        Ok(written)
899    }
900
901    async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, zx::Status> {
902        self.stats.writes.fetch_add(1, Ordering::Relaxed);
903        let mut data = VecInputBuffer::new(content);
904        let written = self
905            .spawn_task_async(async move |locked, current_task, file| {
906                let written = file.write_at(locked, current_task, offset as usize, &mut data)?;
907                Ok(written as u64)
908            })
909            .await?;
910        self.stats.write_bytes.fetch_add(written as u64, Ordering::Relaxed);
911        Ok(written)
912    }
913
914    async fn seek(&self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, zx::Status> {
915        let target = match origin {
916            fio::SeekOrigin::Start => SeekTarget::Set(offset),
917            fio::SeekOrigin::Current => SeekTarget::Cur(offset),
918            fio::SeekOrigin::End => SeekTarget::End(offset),
919        };
920        Ok(self.spawn_task(async move |locked, current_task, file| {
921            let seek_result = file.seek(locked, current_task, target)?;
922            Ok(seek_result as u64)
923        })?)
924    }
925
926    fn set_flags(&self, flags: fio::Flags) -> Result<(), zx::Status> {
927        // Called on the connection via `fcntl(FSETFL, ...)`. fuchsia.io only supports `O_APPEND`
928        // right now, and does not have equivalents for the following flags:
929        //  - `O_ASYNC`
930        //  - `O_DIRECT`
931        //  - `O_NOATIME` (only allowed if caller's EUID is same as the file's UID)
932        //  - `O_NONBLOCK`
933        const SETTABLE_FLAGS_MASK: OpenFlags = OpenFlags::APPEND;
934        let flags = if flags.contains(fio::Flags::FILE_APPEND) {
935            OpenFlags::APPEND
936        } else {
937            OpenFlags::empty()
938        };
939        Ok(self.spawn_task(async move |_, _, file| {
940            file.update_file_flags(flags, SETTABLE_FLAGS_MASK);
941            Ok(())
942        })?)
943    }
944}
945
946#[cfg(test)]
947mod tests {
948    use super::*;
949    use crate::fs::tmpfs::TmpFs;
950    use crate::testing::*;
951    use crate::vfs::{FsString, Namespace};
952    use starnix_uapi::auth::{Capabilities, Credentials};
953    use std::collections::HashSet;
954    use syncio::{Zxio, ZxioOpenOptions, zxio_node_attr_has_t};
955
956    fn assert_directory_content(zxio: &Zxio, content: &[&[u8]]) {
957        let expected = content.iter().map(|&x| FsString::from(x)).collect::<HashSet<_>>();
958        let mut iterator = zxio.create_dirent_iterator().expect("iterator");
959        iterator.rewind().expect("iterator");
960        let found =
961            iterator.map(|x| x.as_ref().expect("dirent").name.clone()).collect::<HashSet<_>>();
962        assert_eq!(found, expected);
963    }
964
965    #[::fuchsia::test]
966    async fn access_file_system() {
967        spawn_kernel_and_run(async |locked, current_task| {
968            let kernel = current_task.kernel();
969            let fs = TmpFs::new_fs(locked, &kernel);
970
971            let file =
972                &fs.root().open_anonymous(locked, current_task, OpenFlags::RDWR).expect("open");
973            let (root_handle, scope) =
974                serve_file(current_task, file, Credentials::root()).expect("serve");
975
976            // Capture information from the filesystem in the main thread. The filesystem must not be
977            // transferred to the other thread.
978            let fs_dev_id = fs.dev_id;
979            std::thread::spawn(move || {
980                let root_zxio = Zxio::create(root_handle.into_handle()).expect("create");
981
982                assert_directory_content(&root_zxio, &[b"."]);
983                // Check that one can reiterate from the start.
984                assert_directory_content(&root_zxio, &[b"."]);
985
986                let attrs = root_zxio
987                    .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
988                    .expect("attr_get");
989                assert_eq!(attrs.id, fs_dev_id.bits());
990
991                let mut attrs = syncio::zxio_node_attributes_t::default();
992                attrs.has.creation_time = true;
993                attrs.has.modification_time = true;
994                attrs.creation_time = 0;
995                attrs.modification_time = 42;
996                root_zxio.attr_set(&attrs).expect("attr_set");
997                let attrs = root_zxio
998                    .attr_get(zxio_node_attr_has_t {
999                        creation_time: true,
1000                        modification_time: true,
1001                        ..Default::default()
1002                    })
1003                    .expect("attr_get");
1004                assert_eq!(attrs.creation_time, 0);
1005                assert_eq!(attrs.modification_time, 42);
1006
1007                assert_eq!(
1008                    root_zxio
1009                        .open("foo", fio::PERM_READABLE | fio::PERM_WRITABLE, Default::default())
1010                        .expect_err("open"),
1011                    zx::Status::NOT_FOUND
1012                );
1013                let foo_zxio = root_zxio
1014                    .open(
1015                        "foo",
1016                        fio::PERM_READABLE
1017                            | fio::PERM_WRITABLE
1018                            | fio::Flags::FLAG_MAYBE_CREATE
1019                            | fio::Flags::PROTOCOL_FILE,
1020                        Default::default(),
1021                    )
1022                    .expect("zxio_open");
1023                assert_directory_content(&root_zxio, &[b".", b"foo"]);
1024
1025                assert_eq!(foo_zxio.write(b"hello").expect("write"), 5);
1026                assert_eq!(foo_zxio.write_at(2, b"ch").expect("write_at"), 2);
1027                let mut buffer = [0; 7];
1028                assert_eq!(foo_zxio.read_at(2, &mut buffer).expect("read_at"), 3);
1029                assert_eq!(&buffer[..3], b"cho");
1030                assert_eq!(foo_zxio.seek(syncio::SeekOrigin::Start, 0).expect("seek"), 0);
1031                assert_eq!(foo_zxio.read(&mut buffer).expect("read"), 5);
1032                assert_eq!(&buffer[..5], b"hecho");
1033
1034                let attrs = foo_zxio
1035                    .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
1036                    .expect("attr_get");
1037                assert_eq!(attrs.id, fs_dev_id.bits());
1038
1039                let mut attrs = syncio::zxio_node_attributes_t::default();
1040                attrs.has.creation_time = true;
1041                attrs.has.modification_time = true;
1042                attrs.creation_time = 0;
1043                attrs.modification_time = 42;
1044                foo_zxio.attr_set(&attrs).expect("attr_set");
1045                let attrs = foo_zxio
1046                    .attr_get(zxio_node_attr_has_t {
1047                        creation_time: true,
1048                        modification_time: true,
1049                        ..Default::default()
1050                    })
1051                    .expect("attr_get");
1052                assert_eq!(attrs.creation_time, 0);
1053                assert_eq!(attrs.modification_time, 42);
1054
1055                assert_eq!(
1056                    root_zxio
1057                        .open(
1058                            "bar/baz",
1059                            fio::Flags::PROTOCOL_DIRECTORY
1060                                | fio::Flags::FLAG_MAYBE_CREATE
1061                                | fio::PERM_READABLE
1062                                | fio::PERM_WRITABLE,
1063                            Default::default(),
1064                        )
1065                        .expect_err("open"),
1066                    zx::Status::NOT_FOUND
1067                );
1068
1069                let bar_zxio = root_zxio
1070                    .open(
1071                        "bar",
1072                        fio::Flags::PROTOCOL_DIRECTORY
1073                            | fio::Flags::FLAG_MAYBE_CREATE
1074                            | fio::PERM_READABLE
1075                            | fio::PERM_WRITABLE,
1076                        Default::default(),
1077                    )
1078                    .expect("open");
1079                let baz_zxio = root_zxio
1080                    .open(
1081                        "bar/baz",
1082                        fio::Flags::PROTOCOL_DIRECTORY
1083                            | fio::Flags::FLAG_MAYBE_CREATE
1084                            | fio::PERM_READABLE
1085                            | fio::PERM_WRITABLE,
1086                        Default::default(),
1087                    )
1088                    .expect("open");
1089                assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1090                assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1091
1092                bar_zxio.rename("baz", &root_zxio, "quz").expect("rename");
1093                assert_directory_content(&bar_zxio, &[b"."]);
1094                assert_directory_content(&root_zxio, &[b".", b"foo", b"bar", b"quz"]);
1095                assert_directory_content(&baz_zxio, &[b"."]);
1096            })
1097            .join()
1098            .expect("join");
1099            scope.shutdown();
1100            scope.wait().await;
1101            // This ensures fs cannot be captures in the thread.
1102            std::mem::drop(fs);
1103        })
1104        .await;
1105    }
1106
1107    #[::fuchsia::test]
1108    async fn serve_file_strips_trunc() {
1109        spawn_kernel_and_run(async |locked, current_task| {
1110            let kernel = current_task.kernel();
1111            let fs = TmpFs::new_fs(locked, &kernel);
1112            let ns = Namespace::new(fs);
1113            let root = ns.root();
1114
1115            let file_node = root
1116                .create_node(
1117                    locked,
1118                    current_task,
1119                    b"test".into(),
1120                    FileMode::IFREG | FileMode::ALLOW_ALL,
1121                    DeviceId::NONE,
1122                )
1123                .expect("create_node");
1124
1125            let file = file_node
1126                .open(locked, current_task, OpenFlags::RDWR, AccessCheck::skip())
1127                .expect("open");
1128            file.write(locked, current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1129
1130            // Reopen with O_TRUNC.
1131            let file_to_serve = current_task
1132                .open_namespace_node_at(
1133                    locked,
1134                    root,
1135                    b"test".into(),
1136                    OpenFlags::RDWR | OpenFlags::TRUNC,
1137                    FileMode::default(),
1138                    ResolveFlags::default(),
1139                    AccessCheck::skip(),
1140                )
1141                .expect("open O_TRUNC");
1142
1143            // Ensure it IS truncated by the open.
1144            assert_eq!(
1145                file_to_serve.node().fetch_and_refresh_info(locked, current_task).unwrap().size,
1146                0
1147            );
1148
1149            // Write something so we can check if it gets truncated again.
1150            file_to_serve
1151                .write(locked, current_task, &mut VecInputBuffer::new(b"world"))
1152                .expect("write world");
1153            assert_eq!(file_to_serve.node().info().size, 5);
1154
1155            let (client_end, scope) =
1156                serve_file(current_task, &file_to_serve, Credentials::root()).expect("serve");
1157
1158            fuchsia_async::unblock(|| {
1159                let zxio = Zxio::create(client_end.into_handle()).expect("create");
1160                let mut attr = syncio::zxio_node_attributes_t::default();
1161                attr.has.content_size = true;
1162                let attr = zxio.attr_get(attr.has).expect("attr_get");
1163                // If O_TRUNC was not stripped, the size would be 0 here.
1164                assert_eq!(attr.content_size, 5);
1165            })
1166            .await;
1167
1168            scope.shutdown();
1169            scope.wait().await;
1170        })
1171        .await;
1172    }
1173
1174    #[::fuchsia::test]
1175    async fn truncate_checks_fd_permissions() {
1176        spawn_kernel_and_run(async |locked, current_task| {
1177            let kernel = current_task.kernel();
1178            let fs = TmpFs::new_fs(locked, &kernel);
1179            let ns = Namespace::new(fs);
1180            let root = ns.root();
1181
1182            let file_node = root
1183                .create_node(
1184                    locked,
1185                    current_task,
1186                    "test".into(),
1187                    FileMode::IFREG | FileMode::IRWXU,
1188                    DeviceId::NONE,
1189                )
1190                .expect("create_node");
1191
1192            let file = file_node
1193                .open(locked, current_task, OpenFlags::RDWR, AccessCheck::skip())
1194                .expect("open");
1195            file.write(locked, current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1196
1197            // Serve the file as different user.
1198            let (client_end, scope) = serve_file(
1199                current_task,
1200                &file,
1201                Arc::new(Credentials {
1202                    fsuid: 2000,
1203                    cap_effective: Capabilities::empty(),
1204                    ..Credentials::clone(&current_task.current_creds())
1205                }),
1206            )
1207            .expect("serve");
1208
1209            fuchsia_async::unblock(move || {
1210                let zxio = Zxio::create(client_end.into_handle()).expect("create");
1211                // truncate should succeed because the FD is open for writing, even though the file
1212                // is being served with a different user.
1213                zxio.truncate(2).expect("truncate");
1214
1215                let mut attr = syncio::zxio_node_attributes_t::default();
1216                attr.has.content_size = true;
1217                let attr = zxio.attr_get(attr.has).expect("attr_get");
1218                assert_eq!(attr.content_size, 2);
1219            })
1220            .await;
1221
1222            scope.shutdown();
1223            scope.wait().await;
1224        })
1225        .await;
1226    }
1227
1228    #[::fuchsia::test]
1229    async fn open() {
1230        spawn_kernel_and_run(async |locked, current_task| {
1231            let kernel = current_task.kernel();
1232            let fs = TmpFs::new_fs(locked, &kernel);
1233
1234            let file = &fs
1235                .root()
1236                .open_anonymous(locked, current_task, OpenFlags::RDWR)
1237                .expect("open_anonymous failed");
1238            let (root_handle, scope) =
1239                serve_file(current_task, file, Credentials::root()).expect("serve_file failed");
1240
1241            std::thread::spawn(move || {
1242                let root_zxio =
1243                    Zxio::create(root_handle.into_handle()).expect("zxio create failed");
1244
1245                assert_directory_content(&root_zxio, &[b"."]);
1246                assert_eq!(
1247                    root_zxio
1248                        .open(
1249                            "foo",
1250                            fio::PERM_READABLE | fio::PERM_WRITABLE,
1251                            ZxioOpenOptions::default()
1252                        )
1253                        .expect_err("open3 passed unexpectedly"),
1254                    zx::Status::NOT_FOUND
1255                );
1256                root_zxio
1257                    .open(
1258                        "foo",
1259                        fio::Flags::PROTOCOL_FILE
1260                            | fio::PERM_READABLE
1261                            | fio::PERM_WRITABLE
1262                            | fio::Flags::FLAG_MUST_CREATE,
1263                        ZxioOpenOptions::default(),
1264                    )
1265                    .expect("open3 failed");
1266                assert_directory_content(&root_zxio, &[b".", b"foo"]);
1267
1268                assert_eq!(
1269                    root_zxio
1270                        .open(
1271                            "bar/baz",
1272                            fio::Flags::PROTOCOL_DIRECTORY
1273                                | fio::PERM_READABLE
1274                                | fio::PERM_WRITABLE
1275                                | fio::Flags::FLAG_MUST_CREATE,
1276                            ZxioOpenOptions::default()
1277                        )
1278                        .expect_err("open3 passed unexpectedly"),
1279                    zx::Status::NOT_FOUND
1280                );
1281                let bar_zxio = root_zxio
1282                    .open(
1283                        "bar",
1284                        fio::Flags::PROTOCOL_DIRECTORY
1285                            | fio::PERM_READABLE
1286                            | fio::PERM_WRITABLE
1287                            | fio::Flags::FLAG_MUST_CREATE,
1288                        ZxioOpenOptions::default(),
1289                    )
1290                    .expect("open3 failed");
1291                root_zxio
1292                    .open(
1293                        "bar/baz",
1294                        fio::Flags::PROTOCOL_DIRECTORY
1295                            | fio::PERM_READABLE
1296                            | fio::PERM_WRITABLE
1297                            | fio::Flags::FLAG_MUST_CREATE,
1298                        ZxioOpenOptions::default(),
1299                    )
1300                    .expect("open3 failed");
1301                assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1302                assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1303            })
1304            .join()
1305            .expect("join");
1306            scope.shutdown();
1307            scope.wait().await;
1308
1309            // This ensures fs cannot be captured in the thread.
1310            std::mem::drop(fs);
1311        })
1312        .await;
1313    }
1314
1315    #[::fuchsia::test]
1316    async fn use_credentials() {
1317        spawn_kernel_and_run(async |locked, current_task| {
1318            let kernel = current_task.kernel();
1319            let fs = TmpFs::new_fs(locked, &kernel);
1320
1321            let file = &fs
1322                .root()
1323                .open_anonymous(locked, current_task, OpenFlags::RDWR)
1324                .expect("open_anonymous failed");
1325            // Create a file as root.
1326            let ns = Namespace::new(fs);
1327            ns.root()
1328                .open_create_node(
1329                    locked,
1330                    current_task,
1331                    "test".into(),
1332                    FileMode::from_bits(0o600) | FileMode::IFREG,
1333                    DeviceId::NONE,
1334                    OpenFlags::empty(),
1335                )
1336                .expect("open_create_node failed");
1337
1338            let mut creds = Credentials::with_ids(0, 0);
1339            creds.fsuid = 1;
1340            creds.cap_effective = Capabilities::empty();
1341
1342            let (root_handle, scope) =
1343                serve_file(current_task, file, creds.into()).expect("serve_file failed");
1344
1345            std::thread::spawn(move || {
1346                let root_zxio =
1347                    Zxio::create(root_handle.into_handle()).expect("zxio create failed");
1348
1349                assert_directory_content(&root_zxio, &[b".", b"test"]);
1350                assert_eq!(
1351                    root_zxio
1352                        .open(
1353                            "test",
1354                            fio::PERM_READABLE | fio::PERM_WRITABLE,
1355                            ZxioOpenOptions::default()
1356                        )
1357                        .expect_err("open3 passed unexpectedly"),
1358                    zx::Status::ACCESS_DENIED
1359                );
1360            })
1361            .join()
1362            .expect("join");
1363            scope.shutdown();
1364            scope.wait().await;
1365
1366            // This ensures fs cannot be captured in the thread.
1367            std::mem::drop(ns);
1368        })
1369        .await;
1370    }
1371}