| // Copyright 2017 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. |
| |
| //! Type-safe bindings for Zircon vmo objects. |
| |
| use crate::ok; |
| use crate::{object_get_info, object_get_property, object_set_property, ObjectQuery, Topic}; |
| use crate::{AsHandleRef, Bti, Handle, HandleBased, HandleRef, Koid, Resource, Rights, Status}; |
| use crate::{Property, PropertyQuery}; |
| use bitflags::bitflags; |
| use fuchsia_zircon_sys as sys; |
| use std::{mem::MaybeUninit, ptr}; |
| use zerocopy::FromBytes; |
| |
| /// An object representing a Zircon |
| /// [virtual memory object](https://fuchsia.dev/fuchsia-src/concepts/objects/vm_object.md). |
| /// |
| /// As essentially a subtype of `Handle`, it can be freely interconverted. |
| #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] |
| #[repr(transparent)] |
| pub struct Vmo(Handle); |
| impl_handle_based!(Vmo); |
| |
| #[repr(C)] |
| #[derive(Debug, Copy, Clone, Eq, PartialEq)] |
| pub struct VmoInfo { |
| pub koid: Koid, |
| pub size_bytes: u64, |
| pub parent_koid: Koid, |
| pub num_children: usize, |
| pub num_mappings: usize, |
| pub share_count: usize, |
| pub flags: VmoInfoFlags, |
| pub committed_bytes: u64, |
| pub handle_rights: Rights, |
| pub cache_policy: CachePolicy, |
| pub metadata_bytes: u64, |
| pub committed_change_events: u64, |
| pub populated_bytes: u64, |
| } |
| |
| impl Default for VmoInfo { |
| fn default() -> VmoInfo { |
| Self::from(sys::zx_info_vmo_t::default()) |
| } |
| } |
| |
| impl From<sys::zx_info_vmo_t> for VmoInfo { |
| fn from(info: sys::zx_info_vmo_t) -> VmoInfo { |
| VmoInfo { |
| koid: Koid::from_raw(info.koid), |
| size_bytes: info.size_bytes, |
| parent_koid: Koid::from_raw(info.parent_koid), |
| num_children: info.num_children, |
| num_mappings: info.num_mappings, |
| share_count: info.share_count, |
| flags: VmoInfoFlags::from_bits_truncate(info.flags), |
| committed_bytes: info.committed_bytes, |
| handle_rights: Rights::from_bits_truncate(info.handle_rights), |
| cache_policy: CachePolicy::from(info.cache_policy), |
| metadata_bytes: info.metadata_bytes, |
| committed_change_events: info.committed_change_events, |
| populated_bytes: info.populated_bytes, |
| } |
| } |
| } |
| |
| struct VmoInfoQuery; |
| unsafe impl ObjectQuery for VmoInfoQuery { |
| const TOPIC: Topic = Topic::VMO; |
| type InfoTy = sys::zx_info_vmo_t; |
| } |
| |
| impl Vmo { |
| /// Create a virtual memory object. |
| /// |
| /// Wraps the |
| /// `zx_vmo_create` |
| /// syscall. See the |
| /// [Shared Memory: Virtual Memory Objects (VMOs)](https://fuchsia.dev/fuchsia-src/concepts/kernel/concepts#shared_memory_virtual_memory_objects_vmos) |
| /// for more information. |
| pub fn create(size: u64) -> Result<Vmo, Status> { |
| Vmo::create_with_opts(VmoOptions::from_bits_truncate(0), size) |
| } |
| |
| /// Create a virtual memory object with options. |
| /// |
| /// Wraps the |
| /// `zx_vmo_create` |
| /// syscall, allowing options to be passed. |
| pub fn create_with_opts(opts: VmoOptions, size: u64) -> Result<Vmo, Status> { |
| let mut handle = 0; |
| let status = unsafe { sys::zx_vmo_create(size, opts.bits(), &mut handle) }; |
| ok(status)?; |
| unsafe { Ok(Vmo::from(Handle::from_raw(handle))) } |
| } |
| |
| /// Create a physically contiguous virtual memory object. |
| /// |
| /// Wraps the |
| /// [`zx_vmo_create_contiguous`](https://fuchsia.dev/fuchsia-src/reference/syscalls/vmo_create_contiguous) syscall. |
| pub fn create_contiguous(bti: &Bti, size: usize, alignment_log2: u32) -> Result<Vmo, Status> { |
| let mut vmo_handle = sys::zx_handle_t::default(); |
| let status = unsafe { |
| // SAFETY: regular system call with no unsafe parameters. |
| sys::zx_vmo_create_contiguous(bti.raw_handle(), size, alignment_log2, &mut vmo_handle) |
| }; |
| ok(status)?; |
| unsafe { |
| // SAFETY: The syscall docs claim that upon success, vmo_handle will be a valid |
| // handle to a virtual memory object. |
| Ok(Vmo::from(Handle::from_raw(vmo_handle))) |
| } |
| } |
| |
| /// Read from a virtual memory object. |
| /// |
| /// Wraps the `zx_vmo_read` syscall. |
| pub fn read(&self, data: &mut [u8], offset: u64) -> Result<(), Status> { |
| unsafe { |
| let status = sys::zx_vmo_read(self.raw_handle(), data.as_mut_ptr(), offset, data.len()); |
| ok(status) |
| } |
| } |
| |
| /// Same as read, but reads into memory that might not be initialized, returning an initialized |
| /// slice of bytes on success. |
| pub fn read_uninit<'a>( |
| &self, |
| data: &'a mut [MaybeUninit<u8>], |
| offset: u64, |
| ) -> Result<&'a mut [u8], Status> { |
| // SAFETY: This system call requires that the pointer and length we pass are valid to write |
| // to, which we guarantee here by getting the pointer and length from a valid slice. |
| unsafe { |
| let status = sys::zx_vmo_read( |
| self.raw_handle(), |
| // TODO(https://fxbug.dev/42079723) use MaybeUninit::slice_as_mut_ptr when stable |
| data.as_mut_ptr() as *mut u8, |
| offset, |
| data.len(), |
| ); |
| ok(status)?; |
| } |
| // TODO(https://fxbug.dev/42079723) use MaybeUninit::slice_assume_init_mut when stable |
| Ok( |
| // SAFETY: We're converting &mut [MaybeUninit<u8>] back to &mut [u8], which is only |
| // valid to do if all elements of `data` have actually been initialized. Here we |
| // have to trust that the kernel didn't lie when it said it wrote to the entire |
| // buffer, but as long as that assumption is valid them it's safe to assume this |
| // slice is init. |
| unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr() as *mut u8, data.len()) }, |
| ) |
| } |
| |
| /// Same as read, but returns a Vec. |
| pub fn read_to_vec(&self, offset: u64, length: u64) -> Result<Vec<u8>, Status> { |
| let len = length.try_into().map_err(|_| Status::INVALID_ARGS)?; |
| let mut buffer = Vec::with_capacity(len); |
| self.read_uninit(buffer.spare_capacity_mut(), offset)?; |
| unsafe { |
| // SAFETY: since read_uninit succeeded we know that we can consider the buffer |
| // initialized. |
| buffer.set_len(len); |
| } |
| Ok(buffer) |
| } |
| |
| /// Same as read, but returns an array. |
| pub fn read_to_array<T: FromBytes, const N: usize>( |
| &self, |
| offset: u64, |
| ) -> Result<[T; N], Status> { |
| // TODO(https://fxbug.dev/42079731): replace with MaybeUninit::uninit_array. |
| let array: MaybeUninit<[MaybeUninit<T>; N]> = MaybeUninit::uninit(); |
| // SAFETY: We are converting from an uninitialized array to an array |
| // of uninitialized elements which is the same. See |
| // https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element. |
| let mut array = unsafe { array.assume_init() }; |
| |
| // SAFETY: T is FromBytes, which means that any bit pattern is valid. Interpreting |
| // [MaybeUninit<T>] as [MaybeUninit<u8>] is safe because T's alignment requirements |
| // are larger than u8. |
| // |
| // TODO(https://fxbug.dev/42079727): Use MaybeUninit::slice_as_bytes_mut once stable. |
| let buffer = unsafe { |
| std::slice::from_raw_parts_mut( |
| array.as_mut_ptr() as *mut MaybeUninit<u8>, |
| N * std::mem::size_of::<T>(), |
| ) |
| }; |
| |
| self.read_uninit(buffer, offset)?; |
| // SAFETY: This is safe because we have initialized all the elements in |
| // the array (since `read_uninit` returned successfully). |
| // |
| // TODO(https://fxbug.dev/42079725): replace with MaybeUninit::array_assume_init. |
| let buffer = array.map(|a| unsafe { a.assume_init() }); |
| Ok(buffer) |
| } |
| |
| /// Same as read, but returns a `T`. |
| pub fn read_to_object<T: FromBytes>(&self, offset: u64) -> Result<T, Status> { |
| let mut object = MaybeUninit::uninit(); |
| // SAFETY: T is FromBytes, which means that any bit pattern is valid. Interpreting |
| // MaybeUninit<T> as [MaybeUninit<u8>] is safe because T's alignment requirements |
| // are larger than, or equal to, u8's. |
| // |
| // TODO(https://fxbug.dev/42079727): Use MaybeUninit::as_bytes_mut once stable. |
| let buffer = unsafe { |
| std::slice::from_raw_parts_mut( |
| object.as_mut_ptr() as *mut MaybeUninit<u8>, |
| std::mem::size_of::<T>(), |
| ) |
| }; |
| self.read_uninit(buffer, offset)?; |
| |
| // SAFETY: The call to `read_uninit` succeeded so we know that `object` |
| // has been initialized. |
| let object = unsafe { object.assume_init() }; |
| Ok(object) |
| } |
| |
| /// Write to a virtual memory object. |
| /// |
| /// Wraps the `zx_vmo_write` syscall. |
| pub fn write(&self, data: &[u8], offset: u64) -> Result<(), Status> { |
| unsafe { |
| let status = sys::zx_vmo_write(self.raw_handle(), data.as_ptr(), offset, data.len()); |
| ok(status) |
| } |
| } |
| |
| /// Efficiently transfers data from one VMO to another. |
| pub fn transfer_data( |
| &self, |
| options: TransferDataOptions, |
| offset: u64, |
| length: u64, |
| src_vmo: &Vmo, |
| src_offset: u64, |
| ) -> Result<(), Status> { |
| let status = unsafe { |
| sys::zx_vmo_transfer_data( |
| self.raw_handle(), |
| options.bits(), |
| offset, |
| length, |
| src_vmo.raw_handle(), |
| src_offset, |
| ) |
| }; |
| ok(status) |
| } |
| |
| /// Get the size of a virtual memory object. |
| /// |
| /// Wraps the `zx_vmo_get_size` syscall. |
| pub fn get_size(&self) -> Result<u64, Status> { |
| let mut size = 0; |
| let status = unsafe { sys::zx_vmo_get_size(self.raw_handle(), &mut size) }; |
| ok(status).map(|()| size) |
| } |
| |
| /// Attempt to change the size of a virtual memory object. |
| /// |
| /// Wraps the `zx_vmo_set_size` syscall. |
| pub fn set_size(&self, size: u64) -> Result<(), Status> { |
| let status = unsafe { sys::zx_vmo_set_size(self.raw_handle(), size) }; |
| ok(status) |
| } |
| |
| /// Attempt to change the cache policy of a virtual memory object. |
| /// |
| /// Wraps the `zx_vmo_set_cache_policy` syscall. |
| pub fn set_cache_policy(&self, cache_policy: CachePolicy) -> Result<(), Status> { |
| let status = |
| unsafe { sys::zx_vmo_set_cache_policy(self.raw_handle(), cache_policy as u32) }; |
| ok(status) |
| } |
| |
| /// Perform an operation on a range of a virtual memory object. |
| /// |
| /// Wraps the |
| /// [zx_vmo_op_range](https://fuchsia.dev/fuchsia-src/reference/syscalls/vmo_op_range.md) |
| /// syscall. |
| pub fn op_range(&self, op: VmoOp, offset: u64, size: u64) -> Result<(), Status> { |
| let status = unsafe { |
| sys::zx_vmo_op_range(self.raw_handle(), op.into_raw(), offset, size, ptr::null_mut(), 0) |
| }; |
| ok(status) |
| } |
| |
| /// Wraps the [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md) |
| /// syscall for the ZX_INFO_VMO topic. |
| pub fn info(&self) -> Result<VmoInfo, Status> { |
| let mut info = sys::zx_info_vmo_t::default(); |
| object_get_info::<VmoInfoQuery>(self.as_handle_ref(), std::slice::from_mut(&mut info)) |
| .map(|_| VmoInfo::from(info)) |
| } |
| |
| /// Create a new virtual memory object that clones a range of this one. |
| /// |
| /// Wraps the |
| /// [zx_vmo_create_child](https://fuchsia.dev/fuchsia-src/reference/syscalls/vmo_create_child.md) |
| /// syscall. |
| pub fn create_child( |
| &self, |
| opts: VmoChildOptions, |
| offset: u64, |
| size: u64, |
| ) -> Result<Vmo, Status> { |
| let mut out = 0; |
| let status = unsafe { |
| sys::zx_vmo_create_child(self.raw_handle(), opts.bits(), offset, size, &mut out) |
| }; |
| ok(status)?; |
| unsafe { Ok(Vmo::from(Handle::from_raw(out))) } |
| } |
| |
| /// Replace a VMO, adding execute rights. |
| /// |
| /// Wraps the |
| /// [zx_vmo_replace_as_executable](https://fuchsia.dev/fuchsia-src/reference/syscalls/vmo_replace_as_executable.md) |
| /// syscall. |
| pub fn replace_as_executable(self, vmex: &Resource) -> Result<Vmo, Status> { |
| let mut out = 0; |
| let status = unsafe { |
| sys::zx_vmo_replace_as_executable(self.raw_handle(), vmex.raw_handle(), &mut out) |
| }; |
| // zx_vmo_replace_as_executable always invalidates the passed in handle |
| // so we need to forget 'self' without executing its drop which will attempt |
| // to close the now-invalid handle value. |
| std::mem::forget(self); |
| ok(status)?; |
| unsafe { Ok(Vmo::from(Handle::from_raw(out))) } |
| } |
| } |
| |
| bitflags! { |
| /// Options that may be used when creating a `Vmo`. |
| #[repr(transparent)] |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct VmoOptions: u32 { |
| const RESIZABLE = sys::ZX_VMO_RESIZABLE; |
| const TRAP_DIRTY = sys::ZX_VMO_TRAP_DIRTY; |
| } |
| } |
| |
| bitflags! { |
| /// Flags that may be set when receiving info on a `Vmo`. |
| #[repr(transparent)] |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct VmoInfoFlags: u32 { |
| const RESIZABLE = sys::ZX_INFO_VMO_RESIZABLE; |
| const IS_COW_CLONE = sys::ZX_INFO_VMO_IS_COW_CLONE; |
| const PAGER_BACKED = sys::ZX_INFO_VMO_PAGER_BACKED; |
| const CONTIGUOUS = sys::ZX_INFO_VMO_CONTIGUOUS; |
| } |
| } |
| |
| bitflags! { |
| /// Options that may be used when creating a `Vmo` child. |
| #[repr(transparent)] |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct VmoChildOptions: u32 { |
| const SNAPSHOT = sys::ZX_VMO_CHILD_SNAPSHOT; |
| const SNAPSHOT_AT_LEAST_ON_WRITE = sys::ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE; |
| const RESIZABLE = sys::ZX_VMO_CHILD_RESIZABLE; |
| const SLICE = sys::ZX_VMO_CHILD_SLICE; |
| const NO_WRITE = sys::ZX_VMO_CHILD_NO_WRITE; |
| const REFERENCE = sys::ZX_VMO_CHILD_REFERENCE; |
| } |
| } |
| |
| bitflags! { |
| /// Options that may be used when transferring data between VMOs. |
| #[repr(transparent)] |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct TransferDataOptions: u32 { |
| } |
| } |
| |
| /// VM Object opcodes |
| #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] |
| #[repr(transparent)] |
| pub struct VmoOp(u32); |
| impl VmoOp { |
| pub fn from_raw(raw: u32) -> VmoOp { |
| VmoOp(raw) |
| } |
| pub fn into_raw(self) -> u32 { |
| self.0 |
| } |
| } |
| |
| // VM Object Cache Policies. |
| #[derive(Debug, Copy, Clone, Eq, PartialEq)] |
| #[repr(u32)] |
| pub enum CachePolicy { |
| Cached = sys::ZX_CACHE_POLICY_CACHED, |
| UnCached = sys::ZX_CACHE_POLICY_UNCACHED, |
| UnCachedDevice = sys::ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| WriteCombining = sys::ZX_CACHE_POLICY_WRITE_COMBINING, |
| Unknown = u32::MAX, |
| } |
| |
| impl From<u32> for CachePolicy { |
| fn from(v: u32) -> Self { |
| match v { |
| sys::ZX_CACHE_POLICY_CACHED => CachePolicy::Cached, |
| sys::ZX_CACHE_POLICY_UNCACHED => CachePolicy::UnCached, |
| sys::ZX_CACHE_POLICY_UNCACHED_DEVICE => CachePolicy::UnCachedDevice, |
| sys::ZX_CACHE_POLICY_WRITE_COMBINING => CachePolicy::WriteCombining, |
| _ => CachePolicy::Unknown, |
| } |
| } |
| } |
| |
| assoc_values!(VmoOp, [ |
| COMMIT = sys::ZX_VMO_OP_COMMIT; |
| DECOMMIT = sys::ZX_VMO_OP_DECOMMIT; |
| LOCK = sys::ZX_VMO_OP_LOCK; |
| UNLOCK = sys::ZX_VMO_OP_UNLOCK; |
| CACHE_SYNC = sys::ZX_VMO_OP_CACHE_SYNC; |
| CACHE_INVALIDATE = sys::ZX_VMO_OP_CACHE_INVALIDATE; |
| CACHE_CLEAN = sys::ZX_VMO_OP_CACHE_CLEAN; |
| CACHE_CLEAN_INVALIDATE = sys::ZX_VMO_OP_CACHE_CLEAN_INVALIDATE; |
| ZERO = sys::ZX_VMO_OP_ZERO; |
| TRY_LOCK = sys::ZX_VMO_OP_TRY_LOCK; |
| DONT_NEED = sys::ZX_VMO_OP_DONT_NEED; |
| ALWAYS_NEED = sys::ZX_VMO_OP_ALWAYS_NEED; |
| ]); |
| |
| unsafe_handle_properties!(object: Vmo, |
| props: [ |
| {query_ty: VMO_CONTENT_SIZE, tag: VmoContentSizeTag, prop_ty: u64, get:get_content_size, set: set_content_size}, |
| ] |
| ); |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::{Iommu, IommuDescDummy, ObjectType}; |
| use fidl_fuchsia_boot as fboot; |
| use fidl_fuchsia_kernel as fkernel; |
| use fuchsia_component::client::connect_channel_to_protocol; |
| use test_case::test_case; |
| use zerocopy::FromZeroes; |
| |
| #[test] |
| fn vmo_create_contiguous_invalid_handle() { |
| let status = Vmo::create_contiguous(&Bti::from(Handle::invalid()), 4096, 0); |
| assert_eq!(status, Err(Status::BAD_HANDLE)); |
| } |
| |
| #[test] |
| fn vmo_create_contiguous() { |
| use fuchsia_zircon::{Channel, HandleBased, Time}; |
| let (client_end, server_end) = Channel::create(); |
| connect_channel_to_protocol::<fboot::RootResourceMarker>(server_end).unwrap(); |
| let service = fboot::RootResourceSynchronousProxy::new(client_end); |
| let resource = service.get(Time::INFINITE).expect("couldn't get root resource"); |
| // This test and fuchsia-zircon are different crates, so we need |
| // to use from_raw to convert between the fuchsia_zircon handle and this test handle. |
| // See https://fxbug.dev/42173139 for details. |
| let resource = unsafe { Resource::from(Handle::from_raw(resource.into_raw())) }; |
| let iommu = Iommu::create_dummy(&resource, IommuDescDummy::default()).unwrap(); |
| let bti = Bti::create(&iommu, 0).unwrap(); |
| |
| let vmo = Vmo::create_contiguous(&bti, 8192, 0).unwrap(); |
| let info = vmo.as_handle_ref().basic_info().unwrap(); |
| assert_eq!(info.object_type, ObjectType::VMO); |
| |
| let vmo_info = vmo.info().unwrap(); |
| assert!(vmo_info.flags.contains(VmoInfoFlags::CONTIGUOUS)); |
| } |
| |
| #[test] |
| fn vmo_get_size() { |
| let size = 16 * 1024 * 1024; |
| let vmo = Vmo::create(size).unwrap(); |
| assert_eq!(size, vmo.get_size().unwrap()); |
| } |
| |
| #[test] |
| fn vmo_set_size() { |
| // Use a multiple of page size to match VMOs page aligned size |
| let start_size = 4096; |
| let vmo = Vmo::create_with_opts(VmoOptions::RESIZABLE, start_size).unwrap(); |
| assert_eq!(start_size, vmo.get_size().unwrap()); |
| |
| // Change the size and make sure the new size is reported |
| let new_size = 8192; |
| assert!(vmo.set_size(new_size).is_ok()); |
| assert_eq!(new_size, vmo.get_size().unwrap()); |
| } |
| |
| #[test] |
| fn vmo_get_info_default() { |
| let size = 4096; |
| let vmo = Vmo::create(size).unwrap(); |
| let info = vmo.info().unwrap(); |
| assert!(!info.flags.contains(VmoInfoFlags::PAGER_BACKED)); |
| } |
| |
| #[test] |
| fn vmo_get_child_info() { |
| let size = 4096; |
| let vmo = Vmo::create(size).unwrap(); |
| let info = vmo.info().unwrap(); |
| assert!(!info.flags.contains(VmoInfoFlags::IS_COW_CLONE)); |
| |
| let child = vmo.create_child(VmoChildOptions::SNAPSHOT, 0, 512).unwrap(); |
| let info = child.info().unwrap(); |
| assert!(info.flags.contains(VmoInfoFlags::IS_COW_CLONE)); |
| |
| let child = vmo.create_child(VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE, 0, 512).unwrap(); |
| let info = child.info().unwrap(); |
| assert!(info.flags.contains(VmoInfoFlags::IS_COW_CLONE)); |
| |
| let child = vmo.create_child(VmoChildOptions::SLICE, 0, 512).unwrap(); |
| let info = child.info().unwrap(); |
| assert!(!info.flags.contains(VmoInfoFlags::IS_COW_CLONE)); |
| } |
| |
| #[test] |
| fn vmo_set_size_fails_on_non_resizable() { |
| let size = 4096; |
| let vmo = Vmo::create(size).unwrap(); |
| assert_eq!(size, vmo.get_size().unwrap()); |
| |
| let new_size = 8192; |
| assert_eq!(Err(Status::UNAVAILABLE), vmo.set_size(new_size)); |
| assert_eq!(size, vmo.get_size().unwrap()); |
| } |
| |
| #[test_case(0)] |
| #[test_case(1)] |
| fn vmo_read_to_array(read_offset: usize) { |
| const ACTUAL_SIZE: usize = 5; |
| const ACTUAL: [u8; ACTUAL_SIZE] = [1, 2, 3, 4, 5]; |
| let vmo = Vmo::create(ACTUAL.len() as u64).unwrap(); |
| vmo.write(&ACTUAL, 0).unwrap(); |
| let read_len = ACTUAL_SIZE - read_offset; |
| assert_eq!( |
| &vmo.read_to_array::<u8, ACTUAL_SIZE>(read_offset as u64).unwrap()[..read_len], |
| &ACTUAL[read_offset..] |
| ); |
| } |
| |
| #[test_case(0)] |
| #[test_case(1)] |
| fn vmo_read_to_vec(read_offset: usize) { |
| const ACTUAL_SIZE: usize = 4; |
| const ACTUAL: [u8; ACTUAL_SIZE] = [6, 7, 8, 9]; |
| let vmo = Vmo::create(ACTUAL.len() as u64).unwrap(); |
| vmo.write(&ACTUAL, 0).unwrap(); |
| let read_len = ACTUAL_SIZE - read_offset; |
| assert_eq!( |
| &vmo.read_to_vec(read_offset as u64, read_len as u64).unwrap(), |
| &ACTUAL[read_offset..] |
| ); |
| } |
| |
| #[test_case(0)] |
| #[test_case(1)] |
| fn vmo_read_to_object(read_offset: usize) { |
| #[repr(C)] |
| #[derive(Debug, Eq, FromBytes, FromZeroes, PartialEq)] |
| struct Object { |
| a: u8, |
| b: u8, |
| } |
| |
| const ACTUAL_SIZE: usize = std::mem::size_of::<Object>(); |
| const ACTUAL: [u8; ACTUAL_SIZE + 1] = [10, 11, 12]; |
| let vmo = Vmo::create(ACTUAL.len() as u64).unwrap(); |
| vmo.write(&ACTUAL, 0).unwrap(); |
| assert_eq!( |
| vmo.read_to_object::<Object>(read_offset as u64).unwrap(), |
| Object { a: ACTUAL[read_offset], b: ACTUAL[1 + read_offset] } |
| ); |
| } |
| |
| #[test] |
| fn vmo_read_write() { |
| let mut vec1 = vec![0; 16]; |
| let vmo = Vmo::create(4096 as u64).unwrap(); |
| assert!(vmo.write(b"abcdef", 0).is_ok()); |
| assert!(vmo.read(&mut vec1, 0).is_ok()); |
| assert_eq!(b"abcdef", &vec1[0..6]); |
| assert!(vmo.write(b"123", 2).is_ok()); |
| assert!(vmo.read(&mut vec1, 0).is_ok()); |
| assert_eq!(b"ab123f", &vec1[0..6]); |
| |
| // Read one byte into the vmo. |
| assert!(vmo.read(&mut vec1, 1).is_ok()); |
| assert_eq!(b"b123f", &vec1[0..5]); |
| |
| assert_eq!(&vmo.read_to_vec(0, 6).expect("read_to_vec failed"), b"ab123f"); |
| } |
| |
| #[test] |
| fn vmo_child_snapshot() { |
| let size = 4096 * 2; |
| let vmo = Vmo::create(size).unwrap(); |
| |
| vmo.write(&[1; 4096], 0).unwrap(); |
| vmo.write(&[2; 4096], 4096).unwrap(); |
| |
| let child = vmo.create_child(VmoChildOptions::SNAPSHOT, 0, size).unwrap(); |
| |
| child.write(&[3; 4096], 0).unwrap(); |
| |
| vmo.write(&[4; 4096], 0).unwrap(); |
| vmo.write(&[5; 4096], 4096).unwrap(); |
| |
| let mut page = [0; 4096]; |
| |
| // SNAPSHOT child observes no further changes to parent VMO. |
| child.read(&mut page[..], 0).unwrap(); |
| assert_eq!(&page[..], &[3; 4096][..]); |
| child.read(&mut page[..], 4096).unwrap(); |
| assert_eq!(&page[..], &[2; 4096][..]); |
| } |
| |
| #[test] |
| fn vmo_child_snapshot_at_least_on_write() { |
| let size = 4096 * 2; |
| let vmo = Vmo::create(size).unwrap(); |
| |
| vmo.write(&[1; 4096], 0).unwrap(); |
| vmo.write(&[2; 4096], 4096).unwrap(); |
| |
| let child = vmo.create_child(VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE, 0, size).unwrap(); |
| |
| child.write(&[3; 4096], 0).unwrap(); |
| |
| vmo.write(&[4; 4096], 0).unwrap(); |
| vmo.write(&[5; 4096], 4096).unwrap(); |
| |
| let mut page = [0; 4096]; |
| |
| // SNAPSHOT_AT_LEAST_ON_WRITE child may observe changes to pages it has not yet written to, |
| // but such behavior is not guaranteed. |
| child.read(&mut page[..], 0).unwrap(); |
| assert_eq!(&page[..], &[3; 4096][..]); |
| child.read(&mut page[..], 4096).unwrap(); |
| assert!( |
| &page[..] == &[2; 4096][..] || &page[..] == &[5; 4096][..], |
| "expected page of 2 or 5, got {:?}", |
| &page[..] |
| ); |
| } |
| |
| #[test] |
| fn vmo_child_no_write() { |
| let size = 4096; |
| let vmo = Vmo::create(size).unwrap(); |
| vmo.write(&[1; 4096], 0).unwrap(); |
| |
| let child = |
| vmo.create_child(VmoChildOptions::SLICE | VmoChildOptions::NO_WRITE, 0, size).unwrap(); |
| assert_eq!(child.write(&[3; 4096], 0), Err(Status::ACCESS_DENIED)); |
| } |
| |
| #[test] |
| fn vmo_op_range_unsupported() { |
| let vmo = Vmo::create(12).unwrap(); |
| assert_eq!(vmo.op_range(VmoOp::LOCK, 0, 1), Err(Status::NOT_SUPPORTED)); |
| assert_eq!(vmo.op_range(VmoOp::UNLOCK, 0, 1), Err(Status::NOT_SUPPORTED)); |
| } |
| |
| #[test] |
| fn vmo_cache() { |
| let vmo = Vmo::create(12).unwrap(); |
| |
| // Cache operations should all succeed. |
| assert_eq!(vmo.op_range(VmoOp::CACHE_SYNC, 0, 12), Ok(())); |
| assert_eq!(vmo.op_range(VmoOp::CACHE_INVALIDATE, 0, 12), Ok(())); |
| assert_eq!(vmo.op_range(VmoOp::CACHE_CLEAN, 0, 12), Ok(())); |
| assert_eq!(vmo.op_range(VmoOp::CACHE_CLEAN_INVALIDATE, 0, 12), Ok(())); |
| } |
| |
| #[test] |
| fn vmo_create_child() { |
| let original = Vmo::create(16).unwrap(); |
| assert!(original.write(b"one", 0).is_ok()); |
| |
| // Clone the VMO, and make sure it contains what we expect. |
| let clone = |
| original.create_child(VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE, 0, 16).unwrap(); |
| let mut read_buffer = vec![0; 16]; |
| assert!(clone.read(&mut read_buffer, 0).is_ok()); |
| assert_eq!(&read_buffer[0..3], b"one"); |
| |
| // Writing to the original will not affect the clone. |
| assert!(original.write(b"two", 0).is_ok()); |
| assert!(original.read(&mut read_buffer, 0).is_ok()); |
| assert_eq!(&read_buffer[0..3], b"two"); |
| assert!(clone.read(&mut read_buffer, 0).is_ok()); |
| assert_eq!(&read_buffer[0..3], b"one"); |
| |
| // However, writing to the clone will not affect the original. |
| assert!(clone.write(b"three", 0).is_ok()); |
| assert!(original.read(&mut read_buffer, 0).is_ok()); |
| assert_eq!(&read_buffer[0..3], b"two"); |
| assert!(clone.read(&mut read_buffer, 0).is_ok()); |
| assert_eq!(&read_buffer[0..5], b"three"); |
| } |
| |
| #[test] |
| fn vmo_replace_as_executeable() { |
| use fuchsia_zircon::{Channel, HandleBased, Time}; |
| |
| let vmo = Vmo::create(16).unwrap(); |
| |
| let info = vmo.as_handle_ref().basic_info().unwrap(); |
| assert!(!info.rights.contains(Rights::EXECUTE)); |
| |
| let (client_end, server_end) = Channel::create(); |
| connect_channel_to_protocol::<fkernel::VmexResourceMarker>(server_end).unwrap(); |
| let service = fkernel::VmexResourceSynchronousProxy::new(client_end); |
| let resource = service.get(Time::INFINITE).expect("couldn't get vmex resource"); |
| let resource = unsafe { crate::Resource::from(Handle::from_raw(resource.into_raw())) }; |
| |
| let exec_vmo = vmo.replace_as_executable(&resource).unwrap(); |
| let info = exec_vmo.as_handle_ref().basic_info().unwrap(); |
| assert!(info.rights.contains(Rights::EXECUTE)); |
| } |
| |
| #[test] |
| fn vmo_content_size() { |
| let start_size = 1024; |
| let vmo = Vmo::create_with_opts(VmoOptions::RESIZABLE, start_size).unwrap(); |
| assert_eq!(vmo.get_content_size().unwrap(), start_size); |
| vmo.set_content_size(&0).unwrap(); |
| assert_eq!(vmo.get_content_size().unwrap(), 0); |
| |
| // write should not change content size. |
| let content = b"abcdef"; |
| assert!(vmo.write(content, 0).is_ok()); |
| assert_eq!(vmo.get_content_size().unwrap(), 0); |
| } |
| |
| #[test] |
| fn vmo_zero() { |
| let vmo = Vmo::create(16).unwrap(); |
| let content = b"0123456789abcdef"; |
| assert!(vmo.write(content, 0).is_ok()); |
| let mut buf = vec![0u8; 16]; |
| assert!(vmo.read(&mut buf[..], 0).is_ok()); |
| assert_eq!(&buf[..], content); |
| |
| assert!(vmo.op_range(VmoOp::ZERO, 0, 16).is_ok()); |
| assert!(vmo.read(&mut buf[..], 0).is_ok()); |
| assert_eq!(&buf[..], &[0u8; 16]); |
| } |
| } |