| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::{ |
| mm::ProtectionFlags, |
| task::{CurrentTask, Kernel}, |
| vfs::{ |
| buffers::{VecInputBuffer, VecOutputBuffer}, |
| DirectoryEntryType, DirentSink, FileHandle, FileObject, FsStr, LookupContext, |
| NamespaceNode, RenameFlags, SeekTarget, UnlinkKind, |
| }, |
| }; |
| use async_trait::async_trait; |
| use fidl::{ |
| endpoints::{ClientEnd, ServerEnd}, |
| HandleBased, |
| }; |
| use fidl_fuchsia_io as fio; |
| use fuchsia_zircon as zx; |
| use starnix_logging::track_stub; |
| use starnix_sync::{DeviceOpen, FileOpsCore, LockBefore, Locked}; |
| use starnix_uapi::{ |
| device_type::DeviceType, errno, error, errors::Errno, file_mode::FileMode, ino_t, off_t, |
| open_flags::OpenFlags, vfs::ResolveFlags, |
| }; |
| use std::{ |
| ops::{Deref, DerefMut}, |
| sync::{Arc, Weak}, |
| }; |
| use vfs::{ |
| attributes, |
| directory::{self, entry_container::Directory}, |
| execution_scope, file, path, ToObjectRequest, |
| }; |
| |
| /// Returns a handle implementing a fuchsia.io.Node delegating to the given `file`. |
| pub fn serve_file<L>( |
| locked: &mut Locked<'_, L>, |
| current_task: &CurrentTask, |
| file: &FileObject, |
| ) -> Result<(ClientEnd<fio::NodeMarker>, execution_scope::ExecutionScope), Errno> |
| where |
| L: LockBefore<FileOpsCore>, |
| L: LockBefore<DeviceOpen>, |
| { |
| let (client_end, server_end) = fidl::endpoints::create_endpoints::<fio::NodeMarker>(); |
| let scope = serve_file_at(locked, server_end, current_task, file)?; |
| Ok((client_end, scope)) |
| } |
| |
| pub fn serve_file_at<L>( |
| locked: &mut Locked<'_, L>, |
| server_end: ServerEnd<fio::NodeMarker>, |
| current_task: &CurrentTask, |
| file: &FileObject, |
| ) -> Result<execution_scope::ExecutionScope, Errno> |
| where |
| L: LockBefore<FileOpsCore>, |
| L: LockBefore<DeviceOpen>, |
| { |
| // Reopen file object to not share state with the given FileObject. |
| let file = file.name.open(locked, current_task, file.flags(), false)?; |
| let open_flags = file.flags(); |
| let kernel = current_task.kernel(); |
| let starnix_file = StarnixNodeConnection::new(Arc::downgrade(kernel), file); |
| let scope = execution_scope::ExecutionScope::new(); |
| // TODO(security): Switching to the `system_task` here loses track of the credentials from |
| // `current_task`. Do we need to retain these credentials? |
| kernel.kthreads.spawn_future({ |
| let scope = scope.clone(); |
| async move { |
| starnix_file.open(scope.clone(), open_flags.into(), path::Path::dot(), server_end); |
| scope.wait().await; |
| } |
| }); |
| Ok(scope) |
| } |
| |
| /// A representation of `file` for the rust vfs. |
| /// |
| /// This struct implements the following trait from the rust vfs library: |
| /// - directory::entry_container::Directory |
| /// - directory::entry_container::MutableDirectory |
| /// - file::File |
| /// - file::RawFileIoConnection |
| /// - directory::entry::DirectoryEntry |
| /// |
| /// Each method is delegated back to the starnix vfs, using `task` as the current task. Blocking |
| /// methods are run from the kernel dynamic thread spawner so that the async dispatched do not |
| /// block on these. |
| struct StarnixNodeConnection { |
| kernel: Weak<Kernel>, |
| file: FileHandle, |
| } |
| |
| struct SystemTaskRef { |
| kernel: Arc<Kernel>, |
| } |
| |
| impl Deref for SystemTaskRef { |
| type Target = CurrentTask; |
| |
| fn deref(&self) -> &CurrentTask { |
| self.kernel.kthreads.system_task() |
| } |
| } |
| |
| impl StarnixNodeConnection { |
| fn new(kernel: Weak<Kernel>, file: FileHandle) -> Arc<Self> { |
| Arc::new(StarnixNodeConnection { kernel, file }) |
| } |
| |
| fn kernel(&self) -> Result<Arc<Kernel>, Errno> { |
| self.kernel.upgrade().ok_or_else(|| errno!(ESRCH)) |
| } |
| async fn task(&self) -> Result<impl Deref<Target = CurrentTask>, Errno> { |
| Ok(SystemTaskRef { kernel: self.kernel()? }) |
| } |
| |
| fn is_dir(&self) -> bool { |
| self.file.node().is_dir() |
| } |
| |
| /// Reopen the current `StarnixNodeConnection` with the given `OpenFlags`. The new file will not share |
| /// state. It is equivalent to opening the same file, not dup'ing the file descriptor. |
| fn reopen<L>( |
| &self, |
| locked: &mut Locked<'_, L>, |
| current_task: &CurrentTask, |
| flags: fio::OpenFlags, |
| ) -> Result<Arc<Self>, Errno> |
| where |
| L: LockBefore<FileOpsCore>, |
| L: LockBefore<DeviceOpen>, |
| { |
| let file = self.file.name.open(locked, current_task, flags.into(), true)?; |
| Ok(StarnixNodeConnection::new(self.kernel.clone(), file)) |
| } |
| |
| /// Implementation of `vfs::directory::entry_container::Directory::directory_read_dirents`. |
| async fn directory_read_dirents<'a>( |
| &'a self, |
| pos: &'a directory::traversal_position::TraversalPosition, |
| sink: Box<dyn directory::dirents_sink::Sink>, |
| ) -> Result< |
| ( |
| directory::traversal_position::TraversalPosition, |
| Box<dyn directory::dirents_sink::Sealed>, |
| ), |
| Errno, |
| > { |
| let current_task = self.task().await?; |
| struct DirentSinkAdapter<'a> { |
| sink: Option<directory::dirents_sink::AppendResult>, |
| offset: &'a mut off_t, |
| } |
| impl<'a> DirentSinkAdapter<'a> { |
| fn append( |
| &mut self, |
| entry: &directory::entry::EntryInfo, |
| name: &str, |
| ) -> Result<(), Errno> { |
| let sink = self.sink.take(); |
| self.sink = match sink { |
| s @ Some(directory::dirents_sink::AppendResult::Sealed(_)) => { |
| self.sink = s; |
| return error!(ENOSPC); |
| } |
| Some(directory::dirents_sink::AppendResult::Ok(sink)) => { |
| Some(sink.append(entry, name)) |
| } |
| None => return error!(ENOTSUP), |
| }; |
| Ok(()) |
| } |
| } |
| impl<'a> DirentSink for DirentSinkAdapter<'a> { |
| fn add( |
| &mut self, |
| inode_num: ino_t, |
| offset: off_t, |
| entry_type: DirectoryEntryType, |
| name: &FsStr, |
| ) -> Result<(), Errno> { |
| // Ignore .. |
| if name != ".." { |
| // Ignore entries with unknown types. |
| if let Some(dirent_type) = fio::DirentType::from_primitive(entry_type.bits()) { |
| let entry_info = directory::entry::EntryInfo::new(inode_num, dirent_type); |
| self.append(&entry_info, &String::from_utf8_lossy(name))? |
| } |
| } |
| *self.offset = offset; |
| Ok(()) |
| } |
| fn offset(&self) -> off_t { |
| *self.offset |
| } |
| } |
| let offset = match pos { |
| directory::traversal_position::TraversalPosition::Start => 0, |
| directory::traversal_position::TraversalPosition::Name(_) => return error!(EINVAL), |
| directory::traversal_position::TraversalPosition::Index(v) => *v as i64, |
| directory::traversal_position::TraversalPosition::End => { |
| return Ok((directory::traversal_position::TraversalPosition::End, sink.seal())); |
| } |
| }; |
| if *self.file.offset.lock() != offset { |
| self.file.seek(¤t_task, SeekTarget::Set(offset))?; |
| } |
| let mut file_offset = self.file.offset.lock(); |
| let mut dirent_sink = DirentSinkAdapter { |
| sink: Some(directory::dirents_sink::AppendResult::Ok(sink)), |
| offset: &mut file_offset, |
| }; |
| let kernel = self.kernel().unwrap().clone(); |
| self.file.readdir( |
| kernel.kthreads.unlocked_for_async().deref_mut(), |
| ¤t_task, |
| &mut dirent_sink, |
| )?; |
| match dirent_sink.sink { |
| Some(directory::dirents_sink::AppendResult::Sealed(seal)) => { |
| Ok((directory::traversal_position::TraversalPosition::End, seal)) |
| } |
| Some(directory::dirents_sink::AppendResult::Ok(sink)) => Ok(( |
| directory::traversal_position::TraversalPosition::Index(*file_offset as u64), |
| sink.seal(), |
| )), |
| None => error!(ENOTSUP), |
| } |
| } |
| |
| async fn lookup_parent<'a>( |
| &self, |
| path: &'a FsStr, |
| ) -> Result<(NamespaceNode, &'a FsStr), Errno> { |
| self.task().await?.lookup_parent(&mut LookupContext::default(), &self.file.name, path) |
| } |
| |
| /// Implementation of `vfs::directory::entry::DirectoryEntry::open`. |
| async fn directory_entry_open( |
| self: Arc<Self>, |
| scope: execution_scope::ExecutionScope, |
| flags: fio::OpenFlags, |
| path: path::Path, |
| server_end: &mut ServerEnd<fio::NodeMarker>, |
| ) -> Result<(), Errno> { |
| let current_task = self.task().await?; |
| let kernel = self.kernel().unwrap().clone(); |
| if self.is_dir() { |
| if path.is_dot() { |
| // Reopen the current directory. |
| let dir = self.reopen( |
| kernel.kthreads.unlocked_for_async().deref_mut(), |
| ¤t_task, |
| flags, |
| )?; |
| flags |
| .to_object_request(std::mem::replace(server_end, zx::Handle::invalid().into())) |
| .handle(|object_request| { |
| object_request.spawn_connection( |
| scope, |
| dir, |
| flags, |
| directory::mutable::connection::MutableConnection::create, |
| ) |
| }); |
| return Ok(()); |
| } |
| |
| // Open a path under the current directory. |
| let path = path.into_string(); |
| let (node, name) = self.lookup_parent(path.as_bytes().into()).await?; |
| let must_create_directory = |
| flags.contains(fio::OpenFlags::DIRECTORY | fio::OpenFlags::CREATE); |
| let mut locked = kernel.kthreads.unlocked_for_async(); |
| let file = match current_task.open_namespace_node_at( |
| &mut locked, |
| node.clone(), |
| name, |
| flags.into(), |
| FileMode::ALLOW_ALL, |
| ResolveFlags::empty(), |
| ) { |
| Err(e) if e == errno!(EISDIR) && must_create_directory => { |
| let mode = |
| current_task.fs().apply_umask(FileMode::from_bits(0o777) | FileMode::IFDIR); |
| let name = |
| node.create_node(&mut locked, ¤t_task, name, mode, DeviceType::NONE)?; |
| let flags = |
| flags & !(fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT); |
| name.open(&mut locked, ¤t_task, flags.into(), false)? |
| } |
| f => f?, |
| }; |
| std::mem::drop(locked); |
| |
| let starnix_file = StarnixNodeConnection::new(self.kernel.clone(), file); |
| starnix_file.open( |
| scope, |
| flags, |
| path::Path::dot(), |
| std::mem::replace(server_end, zx::Handle::invalid().into()), |
| ); |
| return Ok(()); |
| } |
| |
| // Reopen the current file. |
| if !path.is_dot() { |
| return error!(EINVAL); |
| } |
| let file = |
| self.reopen(kernel.kthreads.unlocked_for_async().deref_mut(), ¤t_task, flags)?; |
| flags |
| .to_object_request(std::mem::replace(server_end, zx::Handle::invalid().into())) |
| .handle(|object_request| { |
| object_request.spawn_connection(scope, file, flags, file::RawIoConnection::create) |
| }); |
| Ok(()) |
| } |
| |
| /// Implementation of `vfs::directory::entry_container::Directory::get_attrs` and |
| /// `vfs::File::get_attrs`. |
| fn get_attrs(&self) -> fio::NodeAttributes { |
| let info = self.file.node().info(); |
| |
| // This cast is necessary depending on the architecture. |
| #[allow(clippy::unnecessary_cast)] |
| let link_count = info.link_count as u64; |
| |
| fio::NodeAttributes { |
| mode: info.mode.bits(), |
| id: self.file.fs.dev_id.bits(), |
| content_size: info.size as u64, |
| storage_size: info.storage_size() as u64, |
| link_count, |
| creation_time: info.time_status_change.into_nanos() as u64, |
| modification_time: info.time_modify.into_nanos() as u64, |
| } |
| } |
| |
| fn get_attributes( |
| &self, |
| requested_attributes: fio::NodeAttributesQuery, |
| ) -> fio::NodeAttributes2 { |
| let info = self.file.node().info(); |
| |
| // This cast is necessary depending on the architecture. |
| #[allow(clippy::unnecessary_cast)] |
| let link_count = info.link_count as u64; |
| |
| let (protocols, abilities) = if info.mode.contains(FileMode::IFDIR) { |
| ( |
| fio::NodeProtocolKinds::DIRECTORY, |
| fio::Operations::GET_ATTRIBUTES |
| | fio::Operations::UPDATE_ATTRIBUTES |
| | fio::Operations::ENUMERATE |
| | fio::Operations::TRAVERSE |
| | fio::Operations::MODIFY_DIRECTORY, |
| ) |
| } else { |
| ( |
| fio::NodeProtocolKinds::FILE, |
| fio::Operations::GET_ATTRIBUTES |
| | fio::Operations::UPDATE_ATTRIBUTES |
| | fio::Operations::READ_BYTES |
| | fio::Operations::WRITE_BYTES, |
| ) |
| }; |
| |
| attributes!( |
| requested_attributes, |
| Mutable { |
| creation_time: info.time_status_change.into_nanos() as u64, |
| modification_time: info.time_modify.into_nanos() as u64, |
| mode: info.mode.bits(), |
| uid: info.uid, |
| gid: info.gid, |
| rdev: info.rdev.bits(), |
| }, |
| Immutable { |
| protocols: protocols, |
| abilities: abilities, |
| content_size: info.size as u64, |
| storage_size: info.storage_size() as u64, |
| link_count: link_count, |
| id: self.file.fs.dev_id.bits(), |
| } |
| ) |
| } |
| |
| /// Implementation of `vfs::directory::entry_container::MutableDirectory::set_attrs` and |
| /// `vfs::File::set_attrs`. |
| fn set_attrs(&self, flags: fio::NodeAttributeFlags, attributes: fio::NodeAttributes) { |
| self.file.node().update_info(|info| { |
| if flags.contains(fio::NodeAttributeFlags::CREATION_TIME) { |
| info.time_status_change = zx::Time::from_nanos(attributes.creation_time as i64); |
| } |
| if flags.contains(fio::NodeAttributeFlags::MODIFICATION_TIME) { |
| info.time_modify = zx::Time::from_nanos(attributes.modification_time as i64); |
| } |
| }); |
| } |
| |
| fn update_attributes(&self, attributes: fio::MutableNodeAttributes) { |
| self.file.node().update_info(|info| { |
| if let Some(time) = attributes.creation_time { |
| info.time_status_change = zx::Time::from_nanos(time as i64); |
| } |
| if let Some(time) = attributes.modification_time { |
| info.time_modify = zx::Time::from_nanos(time as i64); |
| } |
| if let Some(mode) = attributes.mode { |
| info.mode = FileMode::from_bits(mode); |
| } |
| if let Some(uid) = attributes.uid { |
| info.uid = uid; |
| } |
| if let Some(gid) = attributes.gid { |
| info.gid = gid; |
| } |
| if let Some(rdev) = attributes.rdev { |
| info.rdev = DeviceType::from_bits(rdev); |
| } |
| }); |
| } |
| } |
| |
| #[async_trait] |
| impl vfs::node::Node for StarnixNodeConnection { |
| async fn get_attrs(&self) -> Result<fio::NodeAttributes, zx::Status> { |
| Ok(StarnixNodeConnection::get_attrs(self)) |
| } |
| |
| async fn get_attributes( |
| &self, |
| requested_attributes: fio::NodeAttributesQuery, |
| ) -> Result<fio::NodeAttributes2, zx::Status> { |
| Ok(StarnixNodeConnection::get_attributes(self, requested_attributes)) |
| } |
| } |
| |
| #[async_trait] |
| impl directory::entry_container::Directory for StarnixNodeConnection { |
| fn open( |
| self: Arc<Self>, |
| scope: execution_scope::ExecutionScope, |
| flags: fio::OpenFlags, |
| path: path::Path, |
| mut server_end: ServerEnd<fio::NodeMarker>, |
| ) { |
| scope.spawn({ |
| let scope = scope.clone(); |
| async move { |
| if let Err(errno) = |
| self.directory_entry_open(scope, flags, path, &mut server_end).await |
| { |
| vfs::common::send_on_open_with_error( |
| flags.contains(fio::OpenFlags::DESCRIBE), |
| server_end, |
| errno.into(), |
| ); |
| } |
| } |
| }) |
| } |
| |
| async fn read_dirents<'a>( |
| &'a self, |
| pos: &'a directory::traversal_position::TraversalPosition, |
| sink: Box<dyn directory::dirents_sink::Sink>, |
| ) -> Result< |
| ( |
| directory::traversal_position::TraversalPosition, |
| Box<dyn directory::dirents_sink::Sealed>, |
| ), |
| zx::Status, |
| > { |
| StarnixNodeConnection::directory_read_dirents(self, pos, sink).await.map_err(Errno::into) |
| } |
| fn register_watcher( |
| self: Arc<Self>, |
| _scope: execution_scope::ExecutionScope, |
| _mask: fio::WatchMask, |
| _watcher: directory::entry_container::DirectoryWatcher, |
| ) -> Result<(), zx::Status> { |
| track_stub!(TODO("https://fxbug.dev/322875605"), "register directory watcher"); |
| Ok(()) |
| } |
| fn unregister_watcher(self: Arc<Self>, _key: usize) {} |
| } |
| |
| #[async_trait] |
| impl directory::entry_container::MutableDirectory for StarnixNodeConnection { |
| async fn set_attrs( |
| &self, |
| flags: fio::NodeAttributeFlags, |
| attributes: fio::NodeAttributes, |
| ) -> Result<(), zx::Status> { |
| StarnixNodeConnection::set_attrs(self, flags, attributes); |
| Ok(()) |
| } |
| async fn update_attributes( |
| &self, |
| attributes: fio::MutableNodeAttributes, |
| ) -> Result<(), zx::Status> { |
| StarnixNodeConnection::update_attributes(self, attributes); |
| Ok(()) |
| } |
| async fn unlink( |
| self: Arc<Self>, |
| name: &str, |
| must_be_directory: bool, |
| ) -> Result<(), zx::Status> { |
| let kind = if must_be_directory { UnlinkKind::Directory } else { UnlinkKind::NonDirectory }; |
| let current_task = self.task().await?; |
| self.file.name.entry.unlink( |
| self.kernel().unwrap().kthreads.unlocked_for_async().deref_mut(), |
| ¤t_task, |
| &self.file.name.mount, |
| name.into(), |
| kind, |
| false, |
| )?; |
| Ok(()) |
| } |
| async fn sync(&self) -> Result<(), zx::Status> { |
| Ok(()) |
| } |
| async fn rename( |
| self: Arc<Self>, |
| src_dir: Arc<dyn directory::entry_container::MutableDirectory>, |
| src_name: path::Path, |
| dst_name: path::Path, |
| ) -> Result<(), zx::Status> { |
| let src_name = src_name.into_string(); |
| let dst_name = dst_name.into_string(); |
| let src_dir = |
| src_dir.into_any().downcast::<StarnixNodeConnection>().map_err(|_| errno!(EXDEV))?; |
| let (src_node, src_name) = src_dir.lookup_parent(src_name.as_str().into()).await?; |
| let (dst_node, dst_name) = self.lookup_parent(dst_name.as_str().into()).await?; |
| NamespaceNode::rename( |
| &*self.task().await?, |
| &src_node, |
| src_name, |
| &dst_node, |
| dst_name, |
| RenameFlags::empty(), |
| )?; |
| Ok(()) |
| } |
| } |
| |
| impl file::File for StarnixNodeConnection { |
| fn writable(&self) -> bool { |
| true |
| } |
| async fn open_file(&self, _optionss: &file::FileOptions) -> Result<(), zx::Status> { |
| Ok(()) |
| } |
| async fn truncate(&self, length: u64) -> Result<(), zx::Status> { |
| let current_task = self.task().await?; |
| self.file.name.truncate( |
| self.kernel().unwrap().kthreads.unlocked_for_async().deref_mut(), |
| ¤t_task, |
| length, |
| )?; |
| Ok(()) |
| } |
| async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, zx::Status> { |
| let mut prot_flags = ProtectionFlags::empty(); |
| if flags.contains(fio::VmoFlags::READ) { |
| prot_flags |= ProtectionFlags::READ; |
| } |
| if flags.contains(fio::VmoFlags::WRITE) { |
| prot_flags |= ProtectionFlags::WRITE; |
| } |
| if flags.contains(fio::VmoFlags::EXECUTE) { |
| prot_flags |= ProtectionFlags::EXEC; |
| } |
| let vmo = self.file.get_vmo(&*self.task().await?, None, prot_flags)?; |
| if flags.contains(fio::VmoFlags::PRIVATE_CLONE) { |
| let size = vmo.get_size()?; |
| vmo.create_child(zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE, 0, size) |
| } else { |
| vmo.duplicate_handle(zx::Rights::SAME_RIGHTS) |
| } |
| } |
| |
| async fn get_size(&self) -> Result<u64, zx::Status> { |
| Ok(self.file.node().info().size as u64) |
| } |
| async fn set_attrs( |
| &self, |
| flags: fio::NodeAttributeFlags, |
| attributes: fio::NodeAttributes, |
| ) -> Result<(), zx::Status> { |
| StarnixNodeConnection::set_attrs(self, flags, attributes); |
| Ok(()) |
| } |
| async fn update_attributes( |
| &self, |
| attributes: fio::MutableNodeAttributes, |
| ) -> Result<(), zx::Status> { |
| StarnixNodeConnection::update_attributes(self, attributes); |
| Ok(()) |
| } |
| async fn sync(&self, _mode: file::SyncMode) -> Result<(), zx::Status> { |
| Ok(()) |
| } |
| } |
| |
| impl file::RawFileIoConnection for StarnixNodeConnection { |
| async fn read(&self, count: u64) -> Result<Vec<u8>, zx::Status> { |
| let file = self.file.clone(); |
| let kernel = self.kernel()?; |
| Ok(kernel |
| .kthreads |
| .spawner() |
| .spawn_and_get_result(move |locked, current_task| -> Result<Vec<u8>, Errno> { |
| let mut data = VecOutputBuffer::new(count as usize); |
| file.read(locked, current_task, &mut data)?; |
| Ok(data.into()) |
| }) |
| .await??) |
| } |
| |
| async fn read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, zx::Status> { |
| let file = self.file.clone(); |
| let kernel = self.kernel()?; |
| Ok(kernel |
| .kthreads |
| .spawner() |
| .spawn_and_get_result(move |locked, current_task| -> Result<Vec<u8>, Errno> { |
| let mut data = VecOutputBuffer::new(count as usize); |
| file.read_at(locked, current_task, offset as usize, &mut data)?; |
| Ok(data.into()) |
| }) |
| .await??) |
| } |
| |
| async fn write(&self, content: &[u8]) -> Result<u64, zx::Status> { |
| let file = self.file.clone(); |
| let kernel = self.kernel()?; |
| let mut data = VecInputBuffer::new(content); |
| let written = kernel |
| .kthreads |
| .spawner() |
| .spawn_and_get_result(move |locked, current_task| -> Result<usize, Errno> { |
| file.write(locked, current_task, &mut data) |
| }) |
| .await??; |
| Ok(written as u64) |
| } |
| |
| async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, zx::Status> { |
| let file = self.file.clone(); |
| let kernel = self.kernel()?; |
| let mut data = VecInputBuffer::new(content); |
| let written = kernel |
| .kthreads |
| .spawner() |
| .spawn_and_get_result(move |locked, current_task| -> Result<usize, Errno> { |
| file.write_at(locked, current_task, offset as usize, &mut data) |
| }) |
| .await??; |
| Ok(written as u64) |
| } |
| |
| async fn seek(&self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, zx::Status> { |
| let target = match origin { |
| fio::SeekOrigin::Start => SeekTarget::Set(offset), |
| fio::SeekOrigin::Current => SeekTarget::Cur(offset), |
| fio::SeekOrigin::End => SeekTarget::End(offset), |
| }; |
| Ok(self.file.seek(&*self.task().await?, target)? as u64) |
| } |
| |
| fn update_flags(&self, flags: fio::OpenFlags) -> zx::Status { |
| let flags = OpenFlags::from(flags); |
| |
| // `fcntl(F_SETFL)` also supports setting `O_NOATIME`, but it's allowed |
| // only when the calling process has the same EUID as the UID of the |
| // file. We don't know caller's EUID here, so we cannot check if |
| // O_NOATIME should be allowed. It's not a problem here because |
| // `fidl::fuchsia::io::OpenFlags` doesn't have an equivalent of |
| // `O_NOATIME`. |
| assert!(!flags.contains(OpenFlags::NOATIME)); |
| |
| let settable_flags = OpenFlags::APPEND | OpenFlags::DIRECT | OpenFlags::NONBLOCK; |
| self.file.update_file_flags(flags, settable_flags); |
| |
| zx::Status::OK |
| } |
| } |
| |
| impl vfs::node::IsDirectory for StarnixNodeConnection { |
| fn is_directory(&self) -> bool { |
| self.is_dir() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::{fs::tmpfs::TmpFs, testing::*, vfs::FsString}; |
| use fuchsia_async as fasync; |
| use std::collections::HashSet; |
| use syncio::{zxio_node_attr_has_t, Zxio}; |
| |
| fn assert_directory_content(zxio: &Zxio, content: &[&[u8]]) { |
| let expected = content.iter().map(|&x| FsString::from(x)).collect::<HashSet<_>>(); |
| let mut iterator = zxio.create_dirent_iterator().expect("iterator"); |
| iterator.rewind().expect("iterator"); |
| let found = |
| iterator.map(|x| x.as_ref().expect("dirent").name.clone()).collect::<HashSet<_>>(); |
| assert_eq!(found, expected); |
| } |
| |
| #[::fuchsia::test] |
| async fn access_file_system() { |
| let (kernel, current_task, mut locked) = create_kernel_task_and_unlocked(); |
| let fs = TmpFs::new_fs(&kernel); |
| |
| let file = |
| &fs.root().open_anonymous(&mut locked, ¤t_task, OpenFlags::RDWR).expect("open"); |
| let (root_handle, scope) = serve_file(&mut locked, ¤t_task, file).expect("serve"); |
| |
| // Capture information from the filesystem in the main thread. The filesystem must not be |
| // transferred to the other thread. |
| let fs_dev_id = fs.dev_id; |
| fasync::unblock(move || { |
| let root_zxio = Zxio::create(root_handle.into_handle()).expect("create"); |
| |
| assert_directory_content(&root_zxio, &[b"."]); |
| // Check that one can reiterate from the start. |
| assert_directory_content(&root_zxio, &[b"."]); |
| |
| let attrs = root_zxio |
| .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() }) |
| .expect("attr_get"); |
| assert_eq!(attrs.id, fs_dev_id.bits()); |
| |
| let mut attrs = syncio::zxio_node_attributes_t::default(); |
| attrs.has.creation_time = true; |
| attrs.has.modification_time = true; |
| attrs.creation_time = 0; |
| attrs.modification_time = 42; |
| root_zxio.attr_set(&attrs).expect("attr_set"); |
| let attrs = root_zxio |
| .attr_get(zxio_node_attr_has_t { |
| creation_time: true, |
| modification_time: true, |
| ..Default::default() |
| }) |
| .expect("attr_get"); |
| assert_eq!(attrs.creation_time, 0); |
| assert_eq!(attrs.modification_time, 42); |
| |
| assert_eq!( |
| root_zxio |
| .open(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE, "foo") |
| .expect_err("open"), |
| zx::Status::NOT_FOUND |
| ); |
| let foo_zxio = root_zxio |
| .open( |
| fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::CREATE, |
| "foo", |
| ) |
| .expect("zxio_open"); |
| assert_directory_content(&root_zxio, &[b".", b"foo"]); |
| |
| assert_eq!(foo_zxio.write(b"hello").expect("write"), 5); |
| assert_eq!(foo_zxio.write_at(2, b"ch").expect("write_at"), 2); |
| let mut buffer = [0; 7]; |
| assert_eq!(foo_zxio.read_at(2, &mut buffer).expect("read_at"), 3); |
| assert_eq!(&buffer[..3], b"cho"); |
| assert_eq!(foo_zxio.seek(syncio::SeekOrigin::Start, 0).expect("seek"), 0); |
| assert_eq!(foo_zxio.read(&mut buffer).expect("read"), 5); |
| assert_eq!(&buffer[..5], b"hecho"); |
| |
| let attrs = foo_zxio |
| .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() }) |
| .expect("attr_get"); |
| assert_eq!(attrs.id, fs_dev_id.bits()); |
| |
| let mut attrs = syncio::zxio_node_attributes_t::default(); |
| attrs.has.creation_time = true; |
| attrs.has.modification_time = true; |
| attrs.creation_time = 0; |
| attrs.modification_time = 42; |
| foo_zxio.attr_set(&attrs).expect("attr_set"); |
| let attrs = foo_zxio |
| .attr_get(zxio_node_attr_has_t { |
| creation_time: true, |
| modification_time: true, |
| ..Default::default() |
| }) |
| .expect("attr_get"); |
| assert_eq!(attrs.creation_time, 0); |
| assert_eq!(attrs.modification_time, 42); |
| |
| assert_eq!( |
| root_zxio |
| .open( |
| fio::OpenFlags::DIRECTORY |
| | fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE, |
| "bar/baz" |
| ) |
| .expect_err("open"), |
| zx::Status::NOT_FOUND |
| ); |
| |
| let bar_zxio = root_zxio |
| .open( |
| fio::OpenFlags::DIRECTORY |
| | fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE, |
| "bar", |
| ) |
| .expect("open"); |
| let baz_zxio = root_zxio |
| .open( |
| fio::OpenFlags::DIRECTORY |
| | fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE, |
| "bar/baz", |
| ) |
| .expect("open"); |
| assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]); |
| assert_directory_content(&bar_zxio, &[b".", b"baz"]); |
| |
| bar_zxio.rename("baz", &root_zxio, "quz").expect("rename"); |
| assert_directory_content(&bar_zxio, &[b"."]); |
| assert_directory_content(&root_zxio, &[b".", b"foo", b"bar", b"quz"]); |
| assert_directory_content(&baz_zxio, &[b"."]); |
| }) |
| .await; |
| scope.shutdown(); |
| // This ensures fs cannot be captures in the thread. |
| std::mem::drop(fs); |
| } |
| } |