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