Skip to main content

starnix_core/vfs/
file_server.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::mm::ProtectionFlags;
6use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
7use crate::task::{CurrentTask, Kernel, LockedAndTask};
8use crate::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
9use crate::vfs::{
10    DirectoryEntryType, DirentSink, FileHandle, FileObject, FsStr, FsString, LookupContext,
11    NamespaceNode, RenameFlags, SeekTarget, UnlinkKind,
12};
13use fidl::endpoints::{ClientEnd, ServerEnd};
14use fidl_fuchsia_io as fio;
15use fuchsia_runtime::UtcInstant;
16use futures::future::BoxFuture;
17use itertools::Either;
18use starnix_logging::{log_error, track_stub};
19use starnix_sync::{Locked, Unlocked};
20use starnix_types::convert::IntoFidl as _;
21use starnix_uapi::auth::Credentials;
22use starnix_uapi::device_id::DeviceId;
23use starnix_uapi::errors::Errno;
24use starnix_uapi::file_mode::{AccessCheck, FileMode};
25use starnix_uapi::open_flags::OpenFlags;
26use starnix_uapi::vfs::ResolveFlags;
27use starnix_uapi::{errno, error, from_status_like_fdio, ino_t, off_t};
28use std::collections::HashMap;
29use std::sync::Arc;
30use std::sync::atomic::{AtomicU64, Ordering};
31use vfs::directory::mutable::connection::MutableConnection;
32use vfs::directory::{self};
33use vfs::{
34    ObjectRequestRef, ProtocolsExt, ToObjectRequest, attributes, execution_scope, file, path,
35};
36
37#[derive(Default)]
38struct FileServerStats {
39    /// The number of objects currently being served.  This will not count multiple connections to
40    /// the same object, or, for a directory, connections to any of its children.
41    serving: AtomicU64,
42
43    /// The total number of reads performed for files served.
44    reads: AtomicU64,
45
46    /// The total number of bytes read for files served.
47    read_bytes: AtomicU64,
48
49    /// The total number of writes performed for files served.
50    writes: AtomicU64,
51
52    /// The total number of writes written for files served.
53    write_bytes: AtomicU64,
54}
55
56struct FileServerRegistry {
57    stats: starnix_sync::Mutex<HashMap<&'static str, Arc<FileServerStats>>>,
58}
59
60impl FileServerRegistry {
61    fn get(kernel: &Kernel) -> Arc<Self> {
62        let mut is_new = false;
63        let registry = kernel.expando.get_or_init(|| {
64            is_new = true;
65            Self { stats: starnix_sync::Mutex::new(HashMap::new()) }
66        });
67        if is_new {
68            let registry_weak = Arc::downgrade(&registry);
69            kernel.inspect_node.record_lazy_child("file_server", move || {
70                let inspector = fuchsia_inspect::Inspector::default();
71                if let Some(registry) = registry_weak.upgrade() {
72                    let root = inspector.root();
73                    for (tag, stats) in registry.stats.lock().iter() {
74                        let node = root.create_child(*tag);
75                        node.record_uint("serving", stats.serving.load(Ordering::Relaxed));
76                        node.record_uint("reads", stats.reads.load(Ordering::Relaxed));
77                        node.record_uint("read_bytes", stats.read_bytes.load(Ordering::Relaxed));
78                        node.record_uint("writes", stats.writes.load(Ordering::Relaxed));
79                        node.record_uint("write_bytes", stats.write_bytes.load(Ordering::Relaxed));
80                        root.record(node);
81                    }
82                }
83                Box::pin(async { Ok(inspector) })
84            });
85        }
86        registry
87    }
88
89    fn get_stats(&self, tag: &'static str) -> Arc<FileServerStats> {
90        self.stats.lock().entry(tag).or_insert_with(|| Arc::default()).clone()
91    }
92}
93
94pub fn serve_file_tagged(
95    current_task: &CurrentTask,
96    file: &FileObject,
97    credentials: Arc<Credentials>,
98    tag: &'static str,
99) -> Result<(ClientEnd<fio::NodeMarker>, execution_scope::ExecutionScope), Errno> {
100    let (client_end, server_end) = fidl::endpoints::create_endpoints::<fio::NodeMarker>();
101    let scope = serve_file_at_tagged(server_end, current_task, file, credentials, tag)?;
102    Ok((client_end, scope))
103}
104
105/// Returns a handle implementing a fuchsia.io.Node delegating to the given `file`.
106pub fn serve_file(
107    current_task: &CurrentTask,
108    file: &FileObject,
109    credentials: Arc<Credentials>,
110) -> Result<(ClientEnd<fio::NodeMarker>, execution_scope::ExecutionScope), Errno> {
111    serve_file_tagged(current_task, file, credentials, "default")
112}
113
114pub fn serve_file_at_tagged(
115    server_end: ServerEnd<fio::NodeMarker>,
116    current_task: &CurrentTask,
117    file: &FileObject,
118    credentials: Arc<Credentials>,
119    tag: &'static str,
120) -> Result<execution_scope::ExecutionScope, Errno> {
121    let kernel = current_task.kernel();
122    let stats = FileServerRegistry::get(&kernel).get_stats(tag);
123    // The TRUNC flag needs to be stripped as otherwise the VFS library will try and truncate
124    // the file when it creates the connection.
125    let fidl_flags: fio::OpenFlags = (file.flags() & !OpenFlags::TRUNC).into_fidl();
126    let starnix_file = StarnixNodeConnection::new(
127        &kernel,
128        file.weak_handle.upgrade().unwrap(),
129        credentials,
130        stats.clone(),
131    );
132    let scope = execution_scope::ExecutionScope::new();
133    kernel.kthreads.spawn_future(
134        {
135            let scope = scope.clone();
136            move || async move {
137                stats.serving.fetch_add(1, Ordering::Relaxed);
138                if starnix_file.is_dir() {
139                    fidl_flags.to_object_request(server_end).handle(|object_request| {
140                        object_request.take().create_connection_sync::<MutableConnection<_>, _>(
141                            scope.clone(),
142                            starnix_file,
143                            fidl_flags,
144                        );
145                        Ok(())
146                    });
147                } else {
148                    fidl_flags.to_object_request(server_end).handle(|object_request| {
149                        object_request
150                            .take()
151                            .create_connection_sync::<file::RawIoConnection<_>, _>(
152                                scope.clone(),
153                                starnix_file,
154                                fidl_flags,
155                            );
156                        Ok(())
157                    });
158                }
159                scope.wait().await;
160                stats.serving.fetch_sub(1, Ordering::Relaxed);
161            }
162        },
163        "serve_file_at",
164    );
165    Ok(scope)
166}
167
168pub fn serve_file_at(
169    server_end: ServerEnd<fio::NodeMarker>,
170    current_task: &CurrentTask,
171    file: &FileObject,
172    credentials: Arc<Credentials>,
173) -> Result<execution_scope::ExecutionScope, Errno> {
174    serve_file_at_tagged(server_end, current_task, file, credentials, "default")
175}
176
177#[async_trait::async_trait(?Send)]
178trait Work: Send + 'static {
179    async fn run(
180        self: Box<Self>,
181        locked: &mut Locked<Unlocked>,
182        current_task: &CurrentTask,
183        file: &FileHandle,
184    );
185}
186
187struct EitherSender<R>(Either<futures::channel::oneshot::Sender<R>, std::sync::mpsc::Sender<R>>);
188
189impl<R> From<futures::channel::oneshot::Sender<R>> for EitherSender<R> {
190    fn from(v: futures::channel::oneshot::Sender<R>) -> Self {
191        Self(Either::Left(v))
192    }
193}
194
195impl<R> From<std::sync::mpsc::Sender<R>> for EitherSender<R> {
196    fn from(v: std::sync::mpsc::Sender<R>) -> Self {
197        Self(Either::Right(v))
198    }
199}
200
201impl<R> EitherSender<R> {
202    async fn send(self, r: R) {
203        match self.0 {
204            Either::Left(s) => {
205                let _ = s.send(r);
206            }
207            Either::Right(s) => {
208                let _ = s.send(r);
209            }
210        }
211    }
212}
213
214struct WorkWrapper<R, F>
215where
216    R: Send + 'static,
217    F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> R + Send + 'static,
218{
219    f: F,
220    sender: EitherSender<R>,
221}
222
223#[async_trait::async_trait(?Send)]
224impl<R, F> Work for WorkWrapper<R, F>
225where
226    R: Send + 'static,
227    F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> R + Send + 'static,
228{
229    async fn run(
230        self: Box<Self>,
231        locked: &mut Locked<Unlocked>,
232        current_task: &CurrentTask,
233        file: &FileHandle,
234    ) {
235        let f: F = self.f;
236        let r = f(locked, current_task, file).await;
237        self.sender.send(r).await;
238    }
239}
240
241async fn handle_file(
242    locked_and_task: LockedAndTask<'_>,
243    credentials: Arc<Credentials>,
244    file: FileHandle,
245    receiver: std::sync::mpsc::Receiver<Box<dyn Work>>,
246) {
247    // Run with the correct credentials
248    locked_and_task
249        .current_task()
250        .override_creds_async(credentials.clone(), async || {
251            // Reopen file object to not share state with the given FileObject.
252            let file = match file.name.open(
253                &mut locked_and_task.unlocked(),
254                locked_and_task.current_task(),
255                file.flags(),
256                AccessCheck::skip(),
257            ) {
258                Ok(file) => file,
259                Err(e) => {
260                    log_error!("Unable to reopen file: {e:?}");
261                    return;
262                }
263            };
264            while let Ok(w) = receiver.recv() {
265                w.run(&mut locked_and_task.unlocked(), locked_and_task.current_task(), &file).await;
266            }
267        })
268        .await;
269}
270
271fn to_open_flags(flags: &impl ProtocolsExt) -> OpenFlags {
272    let rights = flags.rights().unwrap_or_default();
273    let mut open_flags = if rights.contains(fio::Operations::WRITE_BYTES) {
274        if rights.contains(fio::Operations::READ_BYTES) {
275            OpenFlags::RDWR
276        } else {
277            OpenFlags::WRONLY
278        }
279    } else {
280        OpenFlags::RDONLY
281    };
282
283    if flags.create_directory() {
284        open_flags |= OpenFlags::DIRECTORY;
285    }
286
287    match flags.creation_mode() {
288        vfs::CreationMode::Always => open_flags |= OpenFlags::CREAT | OpenFlags::EXCL,
289        vfs::CreationMode::AllowExisting => open_flags |= OpenFlags::CREAT,
290        vfs::CreationMode::UnnamedTemporary => open_flags |= OpenFlags::TMPFILE,
291        vfs::CreationMode::UnlinkableUnnamedTemporary => {
292            open_flags |= OpenFlags::TMPFILE | OpenFlags::EXCL
293        }
294        vfs::CreationMode::Never => {}
295    };
296
297    if flags.is_truncate() {
298        open_flags |= OpenFlags::TRUNC;
299    }
300
301    if flags.is_append() {
302        open_flags |= OpenFlags::APPEND;
303    }
304
305    open_flags
306}
307
308/// A representation of `file` for the rust vfs.
309///
310/// This struct implements the following trait from the rust vfs library:
311/// - directory::entry_container::Directory
312/// - directory::entry_container::MutableDirectory
313/// - file::File
314/// - file::RawFileIoConnection
315///
316/// Each method is delegated back to the starnix vfs, using `task` as the current task. Blocking
317/// methods are run from the kernel dynamic thread spawner so that the async dispatched do not
318/// block on these.
319/// All vfs operations should be done using `credentials`.
320#[derive(Clone)]
321struct StarnixNodeConnection {
322    is_dir: bool,
323    credentials: Arc<Credentials>,
324    work_sender: std::sync::mpsc::Sender<Box<dyn Work>>,
325    stats: Arc<FileServerStats>,
326}
327
328fn lookup_parent(
329    locked: &mut Locked<Unlocked>,
330    current_task: &CurrentTask,
331    file: &FileObject,
332    path: path::Path,
333) -> Result<(NamespaceNode, FsString), Errno> {
334    let (node, name) = current_task.lookup_parent(
335        locked,
336        &mut LookupContext::default(),
337        &file.name,
338        path.as_str().into(),
339    )?;
340    Ok((node, name.to_owned()))
341}
342
343impl StarnixNodeConnection {
344    fn new(
345        kernel: &Kernel,
346        file: FileHandle,
347        credentials: Arc<Credentials>,
348        stats: Arc<FileServerStats>,
349    ) -> Arc<Self> {
350        let (work_sender, receiver) = std::sync::mpsc::channel();
351        let is_dir = file.node().is_dir();
352        let closure = {
353            let credentials = credentials.clone();
354            async move |locked_and_task: LockedAndTask<'_>| {
355                handle_file(locked_and_task, credentials, file, receiver).await;
356            }
357        };
358        let req = SpawnRequestBuilder::new().with_async_closure(closure).build();
359        kernel.kthreads.spawner().spawn_from_request(req);
360        Arc::new(Self { is_dir, credentials, work_sender, stats })
361    }
362
363    fn spawn_task<R, E, F>(&self, f: F) -> Result<R, Errno>
364    where
365        R: Send + 'static,
366        E: Send + 'static,
367        F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> Result<R, E>
368            + Send
369            + 'static,
370        Errno: From<E>,
371    {
372        let (sender, receiver) = std::sync::mpsc::channel();
373        self.work_sender
374            .send(Box::new(WorkWrapper { f, sender: sender.into() }))
375            .map_err(|_| errno!(EIO))?;
376        Ok(receiver.recv().map_err(|_| errno!(EIO))??)
377    }
378
379    async fn spawn_task_async<R, E, F>(&self, f: F) -> Result<R, Errno>
380    where
381        R: Send + 'static,
382        E: Send + 'static,
383        F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> Result<R, E>
384            + Send
385            + 'static,
386        Errno: From<E>,
387    {
388        let (sender, receiver) = futures::channel::oneshot::channel();
389        self.work_sender
390            .send(Box::new(WorkWrapper { f, sender: sender.into() }))
391            .map_err(|_| errno!(EIO))?;
392        Ok(receiver.await.map_err(|_| errno!(EIO))??)
393    }
394
395    fn is_dir(&self) -> bool {
396        self.is_dir
397    }
398
399    fn lookup_parent(&self, path: path::Path) -> Result<(NamespaceNode, FsString), Errno> {
400        self.spawn_task(async move |locked, current_task, file| {
401            lookup_parent(locked, current_task, file, path)
402        })
403    }
404
405    /// Reopen the current `StarnixNodeConnection` with the given `OpenFlags`. The new file will not share
406    /// state. It is equivalent to opening the same file, not dup'ing the file descriptor.
407    fn reopen(&self, flags: &impl ProtocolsExt) -> Result<Arc<Self>, Errno> {
408        let credentials = self.credentials.clone();
409        let flags = to_open_flags(flags);
410        let stats = self.stats.clone();
411        self.spawn_task(async move |locked, current_task, file| {
412            let file = file.name.open(locked, current_task, flags, AccessCheck::default())?;
413            Ok(StarnixNodeConnection::new(&current_task.kernel(), file, credentials, stats))
414        })
415    }
416
417    /// Implementation of `vfs::directory::entry_container::Directory::directory_read_dirents`.
418    fn directory_read_dirents<'a>(
419        &'a self,
420        pos: &'a directory::traversal_position::TraversalPosition,
421        sink: Box<dyn directory::dirents_sink::Sink>,
422    ) -> Result<
423        (
424            directory::traversal_position::TraversalPosition,
425            Box<dyn directory::dirents_sink::Sealed>,
426        ),
427        Errno,
428    > {
429        let pos = pos.clone();
430        self.spawn_task(async move |locked, current_task, file| {
431            struct DirentSinkAdapter<'a> {
432                sink: Option<directory::dirents_sink::AppendResult>,
433                offset: &'a mut off_t,
434            }
435            impl<'a> DirentSinkAdapter<'a> {
436                fn append(
437                    &mut self,
438                    entry: &directory::entry::EntryInfo,
439                    name: &str,
440                ) -> Result<(), Errno> {
441                    let sink = self.sink.take();
442                    self.sink = match sink {
443                        s @ Some(directory::dirents_sink::AppendResult::Sealed(_)) => {
444                            self.sink = s;
445                            return error!(ENOSPC);
446                        }
447                        Some(directory::dirents_sink::AppendResult::Ok(sink)) => {
448                            Some(sink.append(entry, name))
449                        }
450                        None => return error!(ENOTSUP),
451                    };
452                    Ok(())
453                }
454            }
455            impl<'a> DirentSink for DirentSinkAdapter<'a> {
456                fn add(
457                    &mut self,
458                    inode_num: ino_t,
459                    offset: off_t,
460                    entry_type: DirectoryEntryType,
461                    name: &FsStr,
462                ) -> Result<(), Errno> {
463                    // Ignore ..
464                    if name != ".." {
465                        // Ignore entries with unknown types.
466                        if let Some(dirent_type) =
467                            fio::DirentType::from_primitive(entry_type.bits())
468                        {
469                            let entry_info =
470                                directory::entry::EntryInfo::new(inode_num, dirent_type);
471                            self.append(&entry_info, &String::from_utf8_lossy(name))?
472                        }
473                    }
474                    *self.offset = offset;
475                    Ok(())
476                }
477                fn offset(&self) -> off_t {
478                    *self.offset
479                }
480            }
481            let offset = match pos {
482                directory::traversal_position::TraversalPosition::Start => 0,
483                directory::traversal_position::TraversalPosition::Index(v) => v as i64,
484                directory::traversal_position::TraversalPosition::End => {
485                    return Ok((
486                        directory::traversal_position::TraversalPosition::End,
487                        sink.seal(),
488                    ));
489                }
490                _ => return error!(EINVAL),
491            };
492            if file.offset.read() != offset {
493                file.seek(locked, current_task, SeekTarget::Set(offset))?;
494            }
495            let mut file_offset = file.offset.copy();
496            let sink_result = {
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                dirent_sink.sink
503            };
504            let ret = match sink_result {
505                Some(directory::dirents_sink::AppendResult::Sealed(seal)) => {
506                    Ok((directory::traversal_position::TraversalPosition::End, seal))
507                }
508                Some(directory::dirents_sink::AppendResult::Ok(sink)) => Ok((
509                    directory::traversal_position::TraversalPosition::Index(*file_offset as u64),
510                    sink.seal(),
511                )),
512                None => error!(ENOTSUP),
513            };
514            file_offset.update();
515            ret
516        })
517    }
518
519    /// Implementation of `vfs::directory::entry::DirectoryEntry::open`.
520    fn directory_entry_open(
521        self: Arc<Self>,
522        scope: execution_scope::ExecutionScope,
523        flags: impl ProtocolsExt,
524        path: path::Path,
525        object_request: ObjectRequestRef<'_>,
526    ) -> Result<(), zx::Status> {
527        if self.is_dir() {
528            if path.is_dot() {
529                // Reopen the current directory.
530                let dir = self.reopen(&flags)?;
531                object_request
532                    .take()
533                    .create_connection_sync::<MutableConnection<_>, _>(scope, dir, flags);
534                return Ok(());
535            }
536
537            // Open a path under the current directory.
538            let starnix_file = self.spawn_task({
539                let credentials = self.credentials.clone();
540                let stats = self.stats.clone();
541                let create_directory = flags.creation_mode() != vfs::common::CreationMode::Never
542                    && flags.create_directory();
543                let open_flags = to_open_flags(&flags);
544                async move |locked, current_task, file| {
545                    let (node, name) = lookup_parent(locked, current_task, file, path)?;
546                    let file = match current_task.open_namespace_node_at(
547                        locked,
548                        node.clone(),
549                        name.as_ref(),
550                        open_flags,
551                        FileMode::ALLOW_ALL,
552                        ResolveFlags::empty(),
553                        AccessCheck::default(),
554                    ) {
555                        Err(e) if e == errno!(EISDIR) && create_directory => {
556                            let mode = current_task
557                                .fs()
558                                .apply_umask(FileMode::from_bits(0o777) | FileMode::IFDIR);
559                            let name = node.create_node(
560                                locked,
561                                &current_task,
562                                name.as_ref(),
563                                mode,
564                                DeviceId::NONE,
565                            )?;
566                            name.open(
567                                locked,
568                                &current_task,
569                                open_flags & !(OpenFlags::CREAT | OpenFlags::EXCL),
570                                AccessCheck::skip(),
571                            )?
572                        }
573                        f => f?,
574                    };
575                    Ok(StarnixNodeConnection::new(&current_task.kernel(), file, credentials, stats))
576                }
577            })?;
578
579            return starnix_file.directory_entry_open(
580                scope,
581                flags,
582                path::Path::dot(),
583                object_request,
584            );
585        }
586
587        // Reopen the current file.
588        if !path.is_dot() {
589            return Err(zx::Status::NOT_DIR);
590        }
591        let file = self.reopen(&flags)?;
592        object_request
593            .take()
594            .create_connection_sync::<file::RawIoConnection<_>, _>(scope, file, flags);
595        Ok(())
596    }
597
598    fn get_attributes(
599        &self,
600        requested_attributes: fio::NodeAttributesQuery,
601    ) -> fio::NodeAttributes2 {
602        self.spawn_task(async move |_, _, file| {
603            let info = file.node().info();
604
605            // This cast is necessary depending on the architecture.
606            #[allow(clippy::unnecessary_cast)]
607            let link_count = info.link_count as u64;
608
609            let (protocols, abilities) = if info.mode.contains(FileMode::IFDIR) {
610                (
611                    fio::NodeProtocolKinds::DIRECTORY,
612                    fio::Operations::GET_ATTRIBUTES
613                        | fio::Operations::UPDATE_ATTRIBUTES
614                        | fio::Operations::ENUMERATE
615                        | fio::Operations::TRAVERSE
616                        | fio::Operations::MODIFY_DIRECTORY,
617                )
618            } else {
619                (
620                    fio::NodeProtocolKinds::FILE,
621                    fio::Operations::GET_ATTRIBUTES
622                        | fio::Operations::UPDATE_ATTRIBUTES
623                        | fio::Operations::READ_BYTES
624                        | fio::Operations::WRITE_BYTES,
625                )
626            };
627
628            Ok(attributes!(
629                requested_attributes,
630                Mutable {
631                    creation_time: info.time_status_change.into_nanos() as u64,
632                    modification_time: info.time_modify.into_nanos() as u64,
633                    mode: info.mode.bits(),
634                    uid: info.uid,
635                    gid: info.gid,
636                    rdev: info.rdev.bits(),
637                },
638                Immutable {
639                    protocols: protocols,
640                    abilities: abilities,
641                    content_size: info.size as u64,
642                    storage_size: info.storage_size() as u64,
643                    link_count: link_count,
644                    id: file.fs.dev_id.bits(),
645                }
646            ))
647        })
648        .expect("spawn_task")
649    }
650
651    fn update_attributes(&self, attributes: fio::MutableNodeAttributes) {
652        let _ = self.spawn_task(async move |_, _, file| {
653            file.node().update_info(|info| {
654                if let Some(time) = attributes.creation_time {
655                    info.time_status_change = UtcInstant::from_nanos(time as i64);
656                }
657                if let Some(time) = attributes.modification_time {
658                    info.time_modify = UtcInstant::from_nanos(time as i64);
659                }
660                if let Some(mode) = attributes.mode {
661                    info.mode = FileMode::from_bits(mode);
662                }
663                if let Some(uid) = attributes.uid {
664                    info.uid = uid;
665                }
666                if let Some(gid) = attributes.gid {
667                    info.gid = gid;
668                }
669                if let Some(rdev) = attributes.rdev {
670                    info.rdev = DeviceId::from_bits(rdev);
671                }
672            });
673            Ok(())
674        });
675    }
676}
677
678impl vfs::node::Node for StarnixNodeConnection {
679    async fn get_attributes(
680        &self,
681        requested_attributes: fio::NodeAttributesQuery,
682    ) -> Result<fio::NodeAttributes2, zx::Status> {
683        Ok(StarnixNodeConnection::get_attributes(self, requested_attributes))
684    }
685}
686
687impl directory::entry::GetEntryInfo for StarnixNodeConnection {
688    fn entry_info(&self) -> directory::entry::EntryInfo {
689        let dirent_type =
690            if self.is_dir() { fio::DirentType::Directory } else { fio::DirentType::File };
691        directory::entry::EntryInfo::new(0, dirent_type)
692    }
693}
694
695impl directory::entry_container::Directory for StarnixNodeConnection {
696    fn open(
697        self: Arc<Self>,
698        scope: execution_scope::ExecutionScope,
699        path: path::Path,
700        flags: fio::Flags,
701        object_request: ObjectRequestRef<'_>,
702    ) -> Result<(), zx::Status> {
703        self.directory_entry_open(scope, flags, path, object_request)
704    }
705
706    async fn read_dirents(
707        &self,
708        pos: &directory::traversal_position::TraversalPosition,
709        sink: Box<dyn directory::dirents_sink::Sink>,
710    ) -> Result<
711        (
712            directory::traversal_position::TraversalPosition,
713            Box<dyn directory::dirents_sink::Sealed>,
714        ),
715        zx::Status,
716    > {
717        StarnixNodeConnection::directory_read_dirents(self, pos, sink).map_err(Errno::into)
718    }
719    fn register_watcher(
720        self: Arc<Self>,
721        _scope: execution_scope::ExecutionScope,
722        _mask: fio::WatchMask,
723        _watcher: directory::entry_container::DirectoryWatcher,
724    ) -> Result<(), zx::Status> {
725        track_stub!(TODO("https://fxbug.dev/322875605"), "register directory watcher");
726        Ok(())
727    }
728    fn unregister_watcher(self: Arc<Self>, _key: usize) {}
729}
730
731impl directory::entry_container::MutableDirectory for StarnixNodeConnection {
732    async fn update_attributes(
733        &self,
734        attributes: fio::MutableNodeAttributes,
735    ) -> Result<(), zx::Status> {
736        StarnixNodeConnection::update_attributes(self, attributes);
737        Ok(())
738    }
739    async fn unlink(
740        self: Arc<Self>,
741        name: &str,
742        must_be_directory: bool,
743    ) -> Result<(), zx::Status> {
744        let name = FsString::from(name.to_owned());
745        self.spawn_task_async(async move |locked, current_task, file| {
746            let kind =
747                if must_be_directory { UnlinkKind::Directory } else { UnlinkKind::NonDirectory };
748            file.name.entry.unlink(
749                locked,
750                current_task,
751                &file.name.mount,
752                name.as_ref(),
753                kind,
754                false,
755            )
756        })
757        .await?;
758        Ok(())
759    }
760    async fn sync(&self) -> Result<(), zx::Status> {
761        Ok(())
762    }
763    fn rename(
764        self: Arc<Self>,
765        src_dir: Arc<dyn directory::entry_container::MutableDirectory>,
766        src_name: path::Path,
767        dst_name: path::Path,
768    ) -> BoxFuture<'static, Result<(), zx::Status>> {
769        let this = self.clone();
770        Box::pin(async move {
771            Ok(self
772                .spawn_task_async(async move |locked, current_task, file| {
773                    let src_dir = src_dir
774                        .into_any()
775                        .downcast::<StarnixNodeConnection>()
776                        .map_err(|_| errno!(EXDEV))?;
777                    let (dst_node, dst_name) =
778                        lookup_parent(locked, current_task, &file, dst_name)?;
779                    let (src_node, src_name) = if Arc::ptr_eq(&src_dir, &this) {
780                        lookup_parent(locked, current_task, &file, src_name)?
781                    } else {
782                        src_dir.lookup_parent(src_name)?
783                    };
784                    NamespaceNode::rename(
785                        locked,
786                        current_task,
787                        &src_node,
788                        src_name.as_ref(),
789                        &dst_node,
790                        dst_name.as_ref(),
791                        RenameFlags::empty(),
792                    )
793                })
794                .await?)
795        })
796    }
797}
798
799impl file::File for StarnixNodeConnection {
800    fn writable(&self) -> bool {
801        true
802    }
803    async fn open_file(&self, _optionss: &file::FileOptions) -> Result<(), zx::Status> {
804        Ok(())
805    }
806    async fn truncate(&self, length: u64) -> Result<(), zx::Status> {
807        Ok(self
808            .spawn_task_async(async move |locked, current_task, file| {
809                // `ftruncate` checks fewer permissions than `file.name.truncate`, which is what we
810                // want.
811                file.ftruncate(locked, current_task, length)
812            })
813            .await?)
814    }
815    async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, zx::Status> {
816        Ok(self
817            .spawn_task_async(async move |locked, current_task, file| {
818                (|| {
819                    let mut prot_flags = ProtectionFlags::empty();
820                    if flags.contains(fio::VmoFlags::READ) {
821                        prot_flags |= ProtectionFlags::READ;
822                    }
823                    if flags.contains(fio::VmoFlags::WRITE) {
824                        prot_flags |= ProtectionFlags::WRITE;
825                    }
826                    if flags.contains(fio::VmoFlags::EXECUTE) {
827                        prot_flags |= ProtectionFlags::EXEC;
828                    }
829                    let memory = file.get_memory(locked, current_task, None, prot_flags)?;
830                    let vmo = memory.as_vmo().ok_or(zx::Status::NOT_SUPPORTED)?;
831                    if flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
832                        let size = vmo.get_size()?;
833                        vmo.create_child(zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE, 0, size)
834                    } else {
835                        vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)
836                    }
837                })()
838                .map_err(|e| from_status_like_fdio!(e))
839            })
840            .await?)
841    }
842
843    async fn get_size(&self) -> Result<u64, zx::Status> {
844        Ok(self
845            .spawn_task_async(async move |_, _, file| Ok(file.node().info().size as u64))
846            .await?)
847    }
848    async fn update_attributes(
849        &self,
850        attributes: fio::MutableNodeAttributes,
851    ) -> Result<(), zx::Status> {
852        StarnixNodeConnection::update_attributes(self, attributes);
853        Ok(())
854    }
855    async fn sync(&self, _mode: file::SyncMode) -> Result<(), zx::Status> {
856        Ok(())
857    }
858}
859
860impl file::RawFileIoConnection for StarnixNodeConnection {
861    async fn read(&self, count: u64) -> Result<Vec<u8>, zx::Status> {
862        self.stats.reads.fetch_add(1, Ordering::Relaxed);
863        let data: Vec<u8> = self
864            .spawn_task_async(async move |locked, current_task, file| {
865                let mut data = VecOutputBuffer::new(count as usize);
866                file.read(locked, current_task, &mut data)?;
867                Ok(data.into())
868            })
869            .await?;
870        self.stats.read_bytes.fetch_add(data.len() as u64, Ordering::Relaxed);
871        Ok(data)
872    }
873
874    async fn read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, zx::Status> {
875        self.stats.reads.fetch_add(1, Ordering::Relaxed);
876        let data: Vec<u8> = self
877            .spawn_task_async(async move |locked, current_task, file| {
878                let mut data = VecOutputBuffer::new(count as usize);
879                file.read_at(locked, current_task, offset as usize, &mut data)?;
880                Ok(data.into())
881            })
882            .await?;
883        self.stats.read_bytes.fetch_add(data.len() as u64, Ordering::Relaxed);
884        Ok(data)
885    }
886
887    async fn write(&self, content: &[u8]) -> Result<u64, zx::Status> {
888        self.stats.writes.fetch_add(1, Ordering::Relaxed);
889        let mut data = VecInputBuffer::new(content);
890        let written = self
891            .spawn_task_async(async move |locked, current_task, file| {
892                let written = file.write(locked, current_task, &mut data)?;
893                Ok(written as u64)
894            })
895            .await?;
896        self.stats.write_bytes.fetch_add(written, Ordering::Relaxed);
897        Ok(written)
898    }
899
900    async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, zx::Status> {
901        self.stats.writes.fetch_add(1, Ordering::Relaxed);
902        let mut data = VecInputBuffer::new(content);
903        let written = self
904            .spawn_task_async(async move |locked, current_task, file| {
905                let written = file.write_at(locked, current_task, offset as usize, &mut data)?;
906                Ok(written as u64)
907            })
908            .await?;
909        self.stats.write_bytes.fetch_add(written as u64, Ordering::Relaxed);
910        Ok(written)
911    }
912
913    async fn seek(&self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, zx::Status> {
914        let target = match origin {
915            fio::SeekOrigin::Start => SeekTarget::Set(offset),
916            fio::SeekOrigin::Current => SeekTarget::Cur(offset),
917            fio::SeekOrigin::End => SeekTarget::End(offset),
918        };
919        Ok(self.spawn_task(async move |locked, current_task, file| {
920            let seek_result = file.seek(locked, current_task, target)?;
921            Ok(seek_result as u64)
922        })?)
923    }
924
925    fn set_flags(&self, flags: fio::Flags) -> Result<(), zx::Status> {
926        // Called on the connection via `fcntl(FSETFL, ...)`. fuchsia.io only supports `O_APPEND`
927        // right now, and does not have equivalents for the following flags:
928        //  - `O_ASYNC`
929        //  - `O_DIRECT`
930        //  - `O_NOATIME` (only allowed if caller's EUID is same as the file's UID)
931        //  - `O_NONBLOCK`
932        const SETTABLE_FLAGS_MASK: OpenFlags = OpenFlags::APPEND;
933        let flags = if flags.contains(fio::Flags::FILE_APPEND) {
934            OpenFlags::APPEND
935        } else {
936            OpenFlags::empty()
937        };
938        Ok(self.spawn_task(async move |_, _, file| {
939            file.update_file_flags(flags, SETTABLE_FLAGS_MASK);
940            Ok(())
941        })?)
942    }
943}
944
945#[cfg(test)]
946mod tests {
947    use super::*;
948    use crate::fs::tmpfs::TmpFs;
949    use crate::testing::*;
950    use crate::vfs::{FsString, Namespace};
951    use starnix_uapi::auth::{Capabilities, Credentials};
952    use std::collections::HashSet;
953    use syncio::{Zxio, ZxioOpenOptions, zxio_node_attr_has_t};
954
955    fn assert_directory_content(zxio: &Zxio, content: &[&[u8]]) {
956        let expected = content.iter().map(|&x| FsString::from(x)).collect::<HashSet<_>>();
957        let mut iterator = zxio.create_dirent_iterator().expect("iterator");
958        iterator.rewind().expect("iterator");
959        let found =
960            iterator.map(|x| x.as_ref().expect("dirent").name.clone()).collect::<HashSet<_>>();
961        assert_eq!(found, expected);
962    }
963
964    #[::fuchsia::test]
965    async fn access_file_system() {
966        spawn_kernel_and_run(async |locked, current_task| {
967            let kernel = current_task.kernel();
968            let fs = TmpFs::new_fs(locked, &kernel);
969
970            let file =
971                &fs.root().open_anonymous(locked, current_task, OpenFlags::RDWR).expect("open");
972            let (root_handle, scope) =
973                serve_file(current_task, file, Credentials::root()).expect("serve");
974
975            // Capture information from the filesystem in the main thread. The filesystem must not be
976            // transferred to the other thread.
977            let fs_dev_id = fs.dev_id;
978            std::thread::spawn(move || {
979                let root_zxio = Zxio::create(root_handle.into_channel().into()).expect("create");
980
981                assert_directory_content(&root_zxio, &[b"."]);
982                // Check that one can reiterate from the start.
983                assert_directory_content(&root_zxio, &[b"."]);
984
985                let attrs = root_zxio
986                    .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
987                    .expect("attr_get");
988                assert_eq!(attrs.id, fs_dev_id.bits());
989
990                let mut attrs = syncio::zxio_node_attributes_t::default();
991                attrs.has.creation_time = true;
992                attrs.has.modification_time = true;
993                attrs.creation_time = 0;
994                attrs.modification_time = 42;
995                root_zxio.attr_set(&attrs).expect("attr_set");
996                let attrs = root_zxio
997                    .attr_get(zxio_node_attr_has_t {
998                        creation_time: true,
999                        modification_time: true,
1000                        ..Default::default()
1001                    })
1002                    .expect("attr_get");
1003                assert_eq!(attrs.creation_time, 0);
1004                assert_eq!(attrs.modification_time, 42);
1005
1006                assert_eq!(
1007                    root_zxio
1008                        .open("foo", fio::PERM_READABLE | fio::PERM_WRITABLE, Default::default())
1009                        .expect_err("open"),
1010                    zx::Status::NOT_FOUND
1011                );
1012                let foo_zxio = root_zxio
1013                    .open(
1014                        "foo",
1015                        fio::PERM_READABLE
1016                            | fio::PERM_WRITABLE
1017                            | fio::Flags::FLAG_MAYBE_CREATE
1018                            | fio::Flags::PROTOCOL_FILE,
1019                        Default::default(),
1020                    )
1021                    .expect("zxio_open");
1022                assert_directory_content(&root_zxio, &[b".", b"foo"]);
1023
1024                assert_eq!(foo_zxio.write(b"hello").expect("write"), 5);
1025                assert_eq!(foo_zxio.write_at(2, b"ch").expect("write_at"), 2);
1026                let mut buffer = [0; 7];
1027                assert_eq!(foo_zxio.read_at(2, &mut buffer).expect("read_at"), 3);
1028                assert_eq!(&buffer[..3], b"cho");
1029                assert_eq!(foo_zxio.seek(syncio::SeekOrigin::Start, 0).expect("seek"), 0);
1030                assert_eq!(foo_zxio.read(&mut buffer).expect("read"), 5);
1031                assert_eq!(&buffer[..5], b"hecho");
1032
1033                let attrs = foo_zxio
1034                    .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
1035                    .expect("attr_get");
1036                assert_eq!(attrs.id, fs_dev_id.bits());
1037
1038                let mut attrs = syncio::zxio_node_attributes_t::default();
1039                attrs.has.creation_time = true;
1040                attrs.has.modification_time = true;
1041                attrs.creation_time = 0;
1042                attrs.modification_time = 42;
1043                foo_zxio.attr_set(&attrs).expect("attr_set");
1044                let attrs = foo_zxio
1045                    .attr_get(zxio_node_attr_has_t {
1046                        creation_time: true,
1047                        modification_time: true,
1048                        ..Default::default()
1049                    })
1050                    .expect("attr_get");
1051                assert_eq!(attrs.creation_time, 0);
1052                assert_eq!(attrs.modification_time, 42);
1053
1054                assert_eq!(
1055                    root_zxio
1056                        .open(
1057                            "bar/baz",
1058                            fio::Flags::PROTOCOL_DIRECTORY
1059                                | fio::Flags::FLAG_MAYBE_CREATE
1060                                | fio::PERM_READABLE
1061                                | fio::PERM_WRITABLE,
1062                            Default::default(),
1063                        )
1064                        .expect_err("open"),
1065                    zx::Status::NOT_FOUND
1066                );
1067
1068                let bar_zxio = root_zxio
1069                    .open(
1070                        "bar",
1071                        fio::Flags::PROTOCOL_DIRECTORY
1072                            | fio::Flags::FLAG_MAYBE_CREATE
1073                            | fio::PERM_READABLE
1074                            | fio::PERM_WRITABLE,
1075                        Default::default(),
1076                    )
1077                    .expect("open");
1078                let baz_zxio = root_zxio
1079                    .open(
1080                        "bar/baz",
1081                        fio::Flags::PROTOCOL_DIRECTORY
1082                            | fio::Flags::FLAG_MAYBE_CREATE
1083                            | fio::PERM_READABLE
1084                            | fio::PERM_WRITABLE,
1085                        Default::default(),
1086                    )
1087                    .expect("open");
1088                assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1089                assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1090
1091                bar_zxio.rename("baz", &root_zxio, "quz").expect("rename");
1092                assert_directory_content(&bar_zxio, &[b"."]);
1093                assert_directory_content(&root_zxio, &[b".", b"foo", b"bar", b"quz"]);
1094                assert_directory_content(&baz_zxio, &[b"."]);
1095            })
1096            .join()
1097            .expect("join");
1098            scope.shutdown();
1099            scope.wait().await;
1100            // This ensures fs cannot be captures in the thread.
1101            std::mem::drop(fs);
1102        })
1103        .await;
1104    }
1105
1106    #[::fuchsia::test]
1107    async fn serve_file_strips_trunc() {
1108        spawn_kernel_and_run(async |locked, current_task| {
1109            let kernel = current_task.kernel();
1110            let fs = TmpFs::new_fs(locked, &kernel);
1111            let ns = Namespace::new(fs);
1112            let root = ns.root();
1113
1114            let file_node = root
1115                .create_node(
1116                    locked,
1117                    current_task,
1118                    b"test".into(),
1119                    FileMode::IFREG | FileMode::ALLOW_ALL,
1120                    DeviceId::NONE,
1121                )
1122                .expect("create_node");
1123
1124            let file = file_node
1125                .open(locked, current_task, OpenFlags::RDWR, AccessCheck::skip())
1126                .expect("open");
1127            file.write(locked, current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1128
1129            // Reopen with O_TRUNC.
1130            let file_to_serve = current_task
1131                .open_namespace_node_at(
1132                    locked,
1133                    root,
1134                    b"test".into(),
1135                    OpenFlags::RDWR | OpenFlags::TRUNC,
1136                    FileMode::default(),
1137                    ResolveFlags::default(),
1138                    AccessCheck::skip(),
1139                )
1140                .expect("open O_TRUNC");
1141
1142            // Ensure it IS truncated by the open.
1143            assert_eq!(
1144                file_to_serve.node().fetch_and_refresh_info(locked, current_task).unwrap().size,
1145                0
1146            );
1147
1148            // Write something so we can check if it gets truncated again.
1149            file_to_serve
1150                .write(locked, current_task, &mut VecInputBuffer::new(b"world"))
1151                .expect("write world");
1152            assert_eq!(file_to_serve.node().info().size, 5);
1153
1154            let (client_end, scope) =
1155                serve_file(current_task, &file_to_serve, Credentials::root()).expect("serve");
1156
1157            fuchsia_async::unblock(|| {
1158                let zxio = Zxio::create(client_end.into_channel().into()).expect("create");
1159                let mut attr = syncio::zxio_node_attributes_t::default();
1160                attr.has.content_size = true;
1161                let attr = zxio.attr_get(attr.has).expect("attr_get");
1162                // If O_TRUNC was not stripped, the size would be 0 here.
1163                assert_eq!(attr.content_size, 5);
1164            })
1165            .await;
1166
1167            scope.shutdown();
1168            scope.wait().await;
1169        })
1170        .await;
1171    }
1172
1173    #[::fuchsia::test]
1174    async fn truncate_checks_fd_permissions() {
1175        spawn_kernel_and_run(async |locked, current_task| {
1176            let kernel = current_task.kernel();
1177            let fs = TmpFs::new_fs(locked, &kernel);
1178            let ns = Namespace::new(fs);
1179            let root = ns.root();
1180
1181            let file_node = root
1182                .create_node(
1183                    locked,
1184                    current_task,
1185                    "test".into(),
1186                    FileMode::IFREG | FileMode::IRWXU,
1187                    DeviceId::NONE,
1188                )
1189                .expect("create_node");
1190
1191            let file = file_node
1192                .open(locked, current_task, OpenFlags::RDWR, AccessCheck::skip())
1193                .expect("open");
1194            file.write(locked, current_task, &mut VecInputBuffer::new(b"hello")).expect("write");
1195
1196            // Serve the file as different user.
1197            let (client_end, scope) = serve_file(
1198                current_task,
1199                &file,
1200                Arc::new(Credentials {
1201                    fsuid: 2000,
1202                    cap_effective: Capabilities::empty(),
1203                    ..Credentials::clone(&current_task.current_creds())
1204                }),
1205            )
1206            .expect("serve");
1207
1208            fuchsia_async::unblock(move || {
1209                let zxio = Zxio::create(client_end.into_channel().into()).expect("create");
1210                // truncate should succeed because the FD is open for writing, even though the file
1211                // is being served with a different user.
1212                zxio.truncate(2).expect("truncate");
1213
1214                let mut attr = syncio::zxio_node_attributes_t::default();
1215                attr.has.content_size = true;
1216                let attr = zxio.attr_get(attr.has).expect("attr_get");
1217                assert_eq!(attr.content_size, 2);
1218            })
1219            .await;
1220
1221            scope.shutdown();
1222            scope.wait().await;
1223        })
1224        .await;
1225    }
1226
1227    #[::fuchsia::test]
1228    async fn open() {
1229        spawn_kernel_and_run(async |locked, current_task| {
1230            let kernel = current_task.kernel();
1231            let fs = TmpFs::new_fs(locked, &kernel);
1232
1233            let file = &fs
1234                .root()
1235                .open_anonymous(locked, current_task, OpenFlags::RDWR)
1236                .expect("open_anonymous failed");
1237            let (root_handle, scope) =
1238                serve_file(current_task, file, Credentials::root()).expect("serve_file failed");
1239
1240            std::thread::spawn(move || {
1241                let root_zxio =
1242                    Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1243
1244                assert_directory_content(&root_zxio, &[b"."]);
1245                assert_eq!(
1246                    root_zxio
1247                        .open(
1248                            "foo",
1249                            fio::PERM_READABLE | fio::PERM_WRITABLE,
1250                            ZxioOpenOptions::default()
1251                        )
1252                        .expect_err("open3 passed unexpectedly"),
1253                    zx::Status::NOT_FOUND
1254                );
1255                root_zxio
1256                    .open(
1257                        "foo",
1258                        fio::Flags::PROTOCOL_FILE
1259                            | fio::PERM_READABLE
1260                            | fio::PERM_WRITABLE
1261                            | fio::Flags::FLAG_MUST_CREATE,
1262                        ZxioOpenOptions::default(),
1263                    )
1264                    .expect("open3 failed");
1265                assert_directory_content(&root_zxio, &[b".", b"foo"]);
1266
1267                assert_eq!(
1268                    root_zxio
1269                        .open(
1270                            "bar/baz",
1271                            fio::Flags::PROTOCOL_DIRECTORY
1272                                | fio::PERM_READABLE
1273                                | fio::PERM_WRITABLE
1274                                | fio::Flags::FLAG_MUST_CREATE,
1275                            ZxioOpenOptions::default()
1276                        )
1277                        .expect_err("open3 passed unexpectedly"),
1278                    zx::Status::NOT_FOUND
1279                );
1280                let bar_zxio = root_zxio
1281                    .open(
1282                        "bar",
1283                        fio::Flags::PROTOCOL_DIRECTORY
1284                            | fio::PERM_READABLE
1285                            | fio::PERM_WRITABLE
1286                            | fio::Flags::FLAG_MUST_CREATE,
1287                        ZxioOpenOptions::default(),
1288                    )
1289                    .expect("open3 failed");
1290                root_zxio
1291                    .open(
1292                        "bar/baz",
1293                        fio::Flags::PROTOCOL_DIRECTORY
1294                            | fio::PERM_READABLE
1295                            | fio::PERM_WRITABLE
1296                            | fio::Flags::FLAG_MUST_CREATE,
1297                        ZxioOpenOptions::default(),
1298                    )
1299                    .expect("open3 failed");
1300                assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1301                assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1302            })
1303            .join()
1304            .expect("join");
1305            scope.shutdown();
1306            scope.wait().await;
1307
1308            // This ensures fs cannot be captured in the thread.
1309            std::mem::drop(fs);
1310        })
1311        .await;
1312    }
1313
1314    #[::fuchsia::test]
1315    async fn use_credentials() {
1316        spawn_kernel_and_run(async |locked, current_task| {
1317            let kernel = current_task.kernel();
1318            let fs = TmpFs::new_fs(locked, &kernel);
1319
1320            let file = &fs
1321                .root()
1322                .open_anonymous(locked, current_task, OpenFlags::RDWR)
1323                .expect("open_anonymous failed");
1324            // Create a file as root.
1325            let ns = Namespace::new(fs);
1326            ns.root()
1327                .open_create_node(
1328                    locked,
1329                    current_task,
1330                    "test".into(),
1331                    FileMode::from_bits(0o600) | FileMode::IFREG,
1332                    DeviceId::NONE,
1333                    OpenFlags::empty(),
1334                )
1335                .expect("open_create_node failed");
1336
1337            let mut creds = Credentials::with_ids(0, 0);
1338            creds.fsuid = 1;
1339            creds.cap_effective = Capabilities::empty();
1340
1341            let (root_handle, scope) =
1342                serve_file(current_task, file, creds.into()).expect("serve_file failed");
1343
1344            std::thread::spawn(move || {
1345                let root_zxio =
1346                    Zxio::create(root_handle.into_channel().into()).expect("zxio create failed");
1347
1348                assert_directory_content(&root_zxio, &[b".", b"test"]);
1349                assert_eq!(
1350                    root_zxio
1351                        .open(
1352                            "test",
1353                            fio::PERM_READABLE | fio::PERM_WRITABLE,
1354                            ZxioOpenOptions::default()
1355                        )
1356                        .expect_err("open3 passed unexpectedly"),
1357                    zx::Status::ACCESS_DENIED
1358                );
1359            })
1360            .join()
1361            .expect("join");
1362            scope.shutdown();
1363            scope.wait().await;
1364
1365            // This ensures fs cannot be captured in the thread.
1366            std::mem::drop(ns);
1367        })
1368        .await;
1369    }
1370}