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