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