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