From dff9210e5e26f7db5380f24b7a00fc9cbc41adef Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Fri, 29 Mar 2019 17:41:37 +0200 Subject: [PATCH] implement KvmVec struct KvmVec provides a common abstraction for all the KVM structures that ressemble arrays. Signed-off-by: Serban Iorga --- src/ioctls/common/kvm_vec.rs | 467 +++++++++++++++++++++++++++++++++++ src/ioctls/common/mod.rs | 8 + src/ioctls/mod.rs | 4 + 3 files changed, 479 insertions(+) create mode 100644 src/ioctls/common/kvm_vec.rs create mode 100644 src/ioctls/common/mod.rs diff --git a/src/ioctls/common/kvm_vec.rs b/src/ioctls/common/kvm_vec.rs new file mode 100644 index 00000000..a8665cf4 --- /dev/null +++ b/src/ioctls/common/kvm_vec.rs @@ -0,0 +1,467 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Portions Copyright 2017 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the THIRD-PARTY file. + +use kvm_bindings::__IncompleteArrayField; +use std::mem; +use std::mem::size_of; + +/// Errors associated with the KvmVec struct. +#[derive(Debug, Clone)] +pub enum Error { + /// The max size has been exceeded + SizeLimitExceeded, +} + +// Trait for accessing some properties of certain KVM structures that resemble an array. +// +// The kvm API has many structs that resemble the following `T` structure: +// +// ``` +// #[repr(C)] +// struct T { +// len: u32, +// some_data: u32, +// entries: __IncompleteArrayField, +// } +// ``` +#[allow(clippy::len_without_is_empty)] +pub trait KvmArray { + type Entry: PartialEq; + + /// Get the array length + /// + fn len(&self) -> usize; + + /// Get the array length as mut + /// + fn set_len(&mut self, len: usize); + + /// Get max array length + /// + fn max_len() -> usize; + + /// Get the array entries + /// + fn entries(&self) -> &__IncompleteArrayField; + + /// Get the array entries as mut + /// + fn entries_mut(&mut self) -> &mut __IncompleteArrayField; +} + +/// An adapter that helps in treating a KvmArray similarly to an actual `Vec`. +/// +pub struct KvmVec { + // this variable holds the KvmArray structure. We use a `Vec` To make the allocation + // large enough while still being aligned for `T`. Only the first element of `Vec` will + // actually be used as a `T`. The remaining memory in the `Vec` is for `entries`, which + // must be contiguous. Since the entries are of type `KvmArray::Entry` we must be careful to convert the + // desired capacity of the `KvmVec` from `KvmArray::Entry` to `T` when reserving or releasing memory. + mem_allocator: Vec, + // the number of elements of type `KvmArray::Entry` currently in the vec + len: usize, + // the capacity of the `KvmVec` measured in elements of type `KvmArray::Entry` + capacity: usize, +} + +impl KvmVec { + /// Get the capacity required by mem_allocator in order to hold the provided number of `KvmArray::Entry` + /// + fn kvm_vec_len_to_mem_allocator_len(kvm_vec_len: usize) -> usize { + let kvm_vec_size_in_bytes = size_of::() + kvm_vec_len * size_of::(); + (kvm_vec_size_in_bytes + size_of::() - 1) / size_of::() + } + + /// Get the number of elements of type `KvmArray::Entry` that fit in a mem_allocator of provided len + /// + fn mem_allocator_len_to_kvm_vec_len(mem_allocator_len: usize) -> usize { + let array_size_in_bytes = (mem_allocator_len - 1) * size_of::(); + array_size_in_bytes / size_of::() + } + + /// Constructs a new KvmVec that contains `num_elements` empty elements of type `KvmArray::Entry` + /// + /// # Arguments + /// + /// * `num_elements` - The number of empty elements of type `KvmArray::Entry` in the initial `KvmVec` + /// + pub fn new(num_elements: usize) -> KvmVec { + let required_mem_allocator_capacity = + KvmVec::::kvm_vec_len_to_mem_allocator_len(num_elements); + + let mut mem_allocator = Vec::with_capacity(required_mem_allocator_capacity); + for _ in 0..required_mem_allocator_capacity { + mem_allocator.push(T::default()) + } + mem_allocator[0].set_len(num_elements); + + KvmVec { + mem_allocator, + len: num_elements, + capacity: num_elements, + } + } + + /// Get a reference to the actual KVM structure instance. + /// + pub fn as_kvm_struct(&self) -> &T { + &self.mem_allocator[0] + } + + /// Get a mut reference to the actual KVM structure instance. + /// + pub fn as_mut_kvm_struct(&mut self) -> &mut T { + &mut self.mem_allocator[0] + } + + /// Get a pointer to the KVM struct so it can be passed to the kernel. + /// + pub fn as_ptr(&self) -> *const T { + self.as_kvm_struct() + } + + /// Get a mutable pointer to the KVM struct so it can be passed to the kernel. + /// + pub fn as_mut_ptr(&mut self) -> *mut T { + self.as_mut_kvm_struct() + } + + /// Get a mut `Vec` that contains all the elements. + /// It is important to call `mem::forget` after using this vector. Otherwise rust will destroy it. + /// + fn as_vec(&mut self) -> Vec { + unsafe { + let entries_ptr = self.as_mut_kvm_struct().entries_mut().as_mut_ptr(); + // This is safe since self.len and self.capacity should be correct + Vec::from_raw_parts(entries_ptr, self.len, self.capacity as usize) + } + } + + /// Get the mutable elements slice so they can be modified before passing to the VCPU. + /// + pub fn as_entries_slice(&self) -> &[T::Entry] { + let len = self.as_kvm_struct().len(); + unsafe { self.as_kvm_struct().entries().as_slice(len as usize) } + } + + /// Get the mutable elements slice so they can be modified before passing to the VCPU. + /// + pub fn as_mut_entries_slice(&mut self) -> &mut [T::Entry] { + let len = self.as_kvm_struct().len(); + unsafe { + self.as_mut_kvm_struct() + .entries_mut() + .as_mut_slice(len as usize) + } + } + + /// Reserves capacity for at least `additional` more `KvmArray::Entry` elements. + /// If the capacity is already reserved, this method doesn't do anything + /// + fn reserve(&mut self, additional: usize) { + let desired_capacity = self.len + additional; + if desired_capacity <= self.capacity { + return; + } + + let current_mem_allocator_len = self.mem_allocator.len(); + let required_mem_allocator_len = + KvmVec::::kvm_vec_len_to_mem_allocator_len(desired_capacity); + let additional_mem_allocator_len = required_mem_allocator_len - current_mem_allocator_len; + + self.mem_allocator.reserve(additional_mem_allocator_len); + self.capacity = + KvmVec::::mem_allocator_len_to_kvm_vec_len(self.mem_allocator.capacity()); + } + + /// Updates the length of `self` to the specified value. + /// Also updates the length of the `T::Entry` structure and of `self.mem_allocator` accordingly. + /// + fn update_len(&mut self, len: usize) { + self.len = len; + self.as_mut_kvm_struct().set_len(len); + + /// We need to set the len of the mem_allocator to be the number of T elements needed to fit + /// an array of `len` elements of type `T::Entry`. This way, when we call + /// `self.mem_allocator.shrink_to_fit()` only the unnecessary memory will be released. + let required_mem_allocator_len = KvmVec::::kvm_vec_len_to_mem_allocator_len(len); + unsafe { + self.mem_allocator.set_len(required_mem_allocator_len); + } + } + + /// Appends an element to the end of the collection and updates `len`. + /// + /// # Arguments + /// + /// * `entry` - The element that will be appended to the end of the collection. + /// + /// # Error: When len is already equal to max possible len it returns Error::SizeLimitExceeded + /// + pub fn push(&mut self, entry: T::Entry) -> Result<(), Error> { + let desired_len = self.len + 1; + if desired_len > T::max_len() { + return Err(Error::SizeLimitExceeded); + } + + self.reserve(1); + + let mut entries = self.as_vec(); + entries.push(entry); + self.update_len(desired_len); + + mem::forget(entries); + + Ok(()) + } + + /// Retains only the elements specified by the predicate. + /// + /// # Arguments + /// + /// * `f` - The function used to evaluate whether an entry will be kept or not. When `f` returns `true` the entry is kept. + /// + pub fn retain

(&mut self, f: P) + where + P: FnMut(&T::Entry) -> bool, + { + let mut entries = self.as_vec(); + entries.retain(f); + + self.update_len(entries.len()); + self.mem_allocator.shrink_to_fit(); + self.capacity = + KvmVec::::mem_allocator_len_to_kvm_vec_len(self.mem_allocator.capacity()); + + mem::forget(entries); + } +} + +impl PartialEq for KvmVec { + fn eq(&self, other: &KvmVec) -> bool { + self.len == other.len && self.as_entries_slice() == other.as_entries_slice() + } +} + +impl Clone for KvmVec { + fn clone(&self) -> Self { + let mut clone = KvmVec::::new(self.len); + + let num_bytes = self.mem_allocator.len() * size_of::(); + let src_byte_slice = + unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u8, num_bytes) }; + let dst_byte_slice = + unsafe { std::slice::from_raw_parts_mut(clone.as_mut_ptr() as *mut u8, num_bytes) }; + dst_byte_slice.copy_from_slice(src_byte_slice); + + clone + } +} + +#[cfg(test)] +mod tests { + use super::*; + use kvm_bindings::*; + + const MAX_LEN: usize = 100; + + #[repr(C)] + #[derive(Default)] + struct MockKvmArray { + pub len: __u32, + pub padding: __u32, + pub entries: __IncompleteArrayField<__u32>, + } + + impl KvmArray for MockKvmArray { + type Entry = u32; + + fn len(&self) -> usize { + self.len as usize + } + + fn set_len(&mut self, len: usize) { + self.len = len as u32 + } + + fn max_len() -> usize { + MAX_LEN + } + + fn entries(&self) -> &__IncompleteArrayField { + &self.entries + } + + fn entries_mut(&mut self) -> &mut __IncompleteArrayField { + &mut self.entries + } + } + + type MockKvmVec = KvmVec; + + const ENTRIES_OFFSET: usize = 2; + + const KVM_VEC_LEN_TO_MEM_ALLOCATOR_LEN: &'static [(usize, usize)] = &[ + (0, 1), + (1, 2), + (2, 2), + (3, 3), + (4, 3), + (5, 4), + (10, 6), + (50, 26), + (100, 51), + ]; + + const MEM_ALLOCATOR_LEN_TO_KVM_VEC_LEN: &'static [(usize, usize)] = &[ + // (0, 1), + (1, 0), + (2, 2), + (3, 4), + (4, 6), + (5, 8), + (10, 18), + (50, 98), + (100, 198), + ]; + + #[test] + fn test_kvm_vec_len_to_mem_allocator_len() { + for pair in KVM_VEC_LEN_TO_MEM_ALLOCATOR_LEN { + let kvm_vec_len = pair.0; + let mem_allocator_len = pair.1; + assert_eq!( + mem_allocator_len, + MockKvmVec::kvm_vec_len_to_mem_allocator_len(kvm_vec_len) + ); + } + } + + #[test] + fn test_mem_allocator_len_to_kvm_vec_len() { + for pair in MEM_ALLOCATOR_LEN_TO_KVM_VEC_LEN { + let mem_allocator_len = pair.0; + let kvm_vec_len = pair.1; + assert_eq!( + kvm_vec_len, + MockKvmVec::mem_allocator_len_to_kvm_vec_len(mem_allocator_len) + ); + } + } + + #[test] + fn test_new() { + let num_entries = 10; + + let kvm_vec = MockKvmVec::new(num_entries); + assert_eq!(num_entries, kvm_vec.capacity); + + let u32_slice = unsafe { + std::slice::from_raw_parts(kvm_vec.as_ptr() as *const u32, num_entries + ENTRIES_OFFSET) + }; + assert_eq!(num_entries, u32_slice[0] as usize); + for entry in u32_slice[1..].iter() { + assert_eq!(*entry, 0); + } + } + + #[test] + fn test_entries_slice() { + let num_entries = 10; + let mut kvm_vec = MockKvmVec::new(num_entries); + + let expected_slice = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + + { + let mut mut_entries_slice = kvm_vec.as_mut_entries_slice(); + mut_entries_slice.copy_from_slice(expected_slice); + } + + let u32_slice = unsafe { + std::slice::from_raw_parts(kvm_vec.as_ptr() as *const u32, num_entries + ENTRIES_OFFSET) + }; + assert_eq!(expected_slice, &u32_slice[ENTRIES_OFFSET..]); + assert_eq!(expected_slice, kvm_vec.as_entries_slice()); + } + + #[test] + fn test_reserve() { + let mut kvm_vec = MockKvmVec::new(0); + + // test that the right capacity is reserved + for pair in KVM_VEC_LEN_TO_MEM_ALLOCATOR_LEN { + let num_elements = pair.0; + let required_mem_allocator_len = pair.1; + + let kvm_vec_capacity = kvm_vec.capacity; + kvm_vec.reserve(num_elements); + + assert!(kvm_vec.mem_allocator.capacity() >= required_mem_allocator_len); + assert_eq!(0, kvm_vec.len); + assert!(kvm_vec.capacity >= num_elements); + } + + // test that when the capacity is already reserved, the method doesn't do anything + let current_capacity = kvm_vec.capacity; + kvm_vec.reserve(current_capacity - 1); + assert_eq!(current_capacity, kvm_vec.capacity); + } + + #[test] + fn test_push() { + let mut kvm_vec = MockKvmVec::new(0); + + for i in 0..MAX_LEN { + assert!(kvm_vec.push(i as u32).is_ok()); + assert_eq!(kvm_vec.as_entries_slice()[i], i as u32); + } + + assert!(kvm_vec.push(0).is_err()); + } + + #[test] + fn test_retain() { + let mut kvm_vec = MockKvmVec::new(0); + + for i in 0..MAX_LEN { + assert!(kvm_vec.push(i as u32).is_ok()); + } + + kvm_vec.retain(|entry| entry % 2 == 0); + + for entry in kvm_vec.as_entries_slice().iter() { + assert_eq!(0, entry % 2); + } + } + + #[test] + fn test_partial_eq() { + let mut kvm_vec_1 = MockKvmVec::new(0); + let mut kvm_vec_2 = MockKvmVec::new(0); + let mut kvm_vec_3 = MockKvmVec::new(0); + + for i in 0..MAX_LEN { + assert!(kvm_vec_1.push(i as u32).is_ok()); + assert!(kvm_vec_2.push(i as u32).is_ok()); + assert!(kvm_vec_3.push(0).is_ok()); + } + + assert!(kvm_vec_1 == kvm_vec_2); + assert!(kvm_vec_1 != kvm_vec_3); + } + + #[test] + fn test_clone() { + let mut kvm_vec = MockKvmVec::new(0); + + for i in 0..MAX_LEN { + assert!(kvm_vec.push(i as u32).is_ok()); + } + + assert!(kvm_vec == kvm_vec.clone()); + } +} diff --git a/src/ioctls/common/mod.rs b/src/ioctls/common/mod.rs new file mode 100644 index 00000000..9e89be8c --- /dev/null +++ b/src/ioctls/common/mod.rs @@ -0,0 +1,8 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Portions Copyright 2017 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the THIRD-PARTY file. + +pub mod kvm_vec; diff --git a/src/ioctls/mod.rs b/src/ioctls/mod.rs index d5a1cf3e..5864ba70 100644 --- a/src/ioctls/mod.rs +++ b/src/ioctls/mod.rs @@ -15,6 +15,8 @@ use kvm_bindings::kvm_run; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] use kvm_bindings::{kvm_cpuid2, kvm_cpuid_entry2}; +/// Helper for dealing with KVM api structures +mod common; /// Wrappers over KVM device ioctls. pub mod device; /// Wrappers over KVM system ioctls. @@ -24,6 +26,8 @@ pub mod vcpu; /// Wrappers over KVM Virtual Machine ioctls. pub mod vm; +use self::common::kvm_vec::*; + /// A specialized `Result` type for KVM ioctls. /// /// This typedef is generally used to avoid writing out io::Error directly and