From fd3425db4731e8db64d1dab632892544de6b12d4 Mon Sep 17 00:00:00 2001 From: Zach Reizner Date: Thu, 6 Jul 2017 17:02:26 -0700 Subject: [PATCH] sys_util: add shared memory module This CL adds the SharedMemory structure for managing memory attached to a file descriptor, which can be sent to external processes. TEST=cargo test BUG=chromium:738638 Change-Id: I2b6a10e0e7275367ddb578831a73de6956d231bb Reviewed-on: https://chromium-review.googlesource.com/562509 Commit-Ready: Zach Reizner Tested-by: Zach Reizner Reviewed-by: Dylan Reid --- sys_util/src/lib.rs | 2 + sys_util/src/shm.rs | 169 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 sys_util/src/shm.rs diff --git a/sys_util/src/lib.rs b/sys_util/src/lib.rs index 65a5843ea7..e73f13367e 100644 --- a/sys_util/src/lib.rs +++ b/sys_util/src/lib.rs @@ -11,6 +11,7 @@ extern crate syscall_defines; #[macro_use] pub mod handle_eintr; mod mmap; +mod shm; mod eventfd; mod errno; mod guest_address; @@ -24,6 +25,7 @@ mod signal; pub mod syslog; pub use mmap::*; +pub use shm::*; pub use eventfd::*; pub use errno::{Error, Result}; use errno::errno_result; diff --git a/sys_util/src/shm.rs b/sys_util/src/shm.rs new file mode 100644 index 0000000000..5fee8dfe03 --- /dev/null +++ b/sys_util/src/shm.rs @@ -0,0 +1,169 @@ +// 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 LICENSE file. + +use std::ffi::CStr; +use std::fs::File; +use std::io::{Seek, SeekFrom}; +use std::os::unix::io::{AsRawFd, IntoRawFd, FromRawFd, RawFd}; + +use libc::{off64_t, c_int, c_uint, c_char, syscall, ftruncate64}; + +use syscall_defines::linux::LinuxSyscall::SYS_memfd_create; + +use {Result, errno_result}; + +/// A shared memory file descriptor and its size. +pub struct SharedMemory { + fd: File, + size: u64, +} + +// from +const MFD_CLOEXEC: c_uint = 0x0001; + +unsafe fn memfd_create(name: *const c_char, flags: c_uint) -> c_int { + syscall(SYS_memfd_create as i64, name as i64, flags as i64) as c_int +} + +impl SharedMemory { + /// Creates a new shared memory file descriptor with zero size. + /// + /// If a name is given, it will appear in `/proc/self/fd/` for the purposes of + /// debugging. The name does not need to be unique. + /// + /// The file descriptor is opened with the close on exec flag. + pub fn new(name: Option<&CStr>) -> Result { + let shm_name = name.map(|n| n.as_ptr()) + .unwrap_or(b"/crosvm_shm\0".as_ptr() as *const i8); + // The following are safe because we give a valid C string and check the + // results of the memfd_create call. + let fd = unsafe { memfd_create(shm_name, MFD_CLOEXEC) }; + if fd < 0 { + return errno_result(); + } + + let file = unsafe { File::from_raw_fd(fd) }; + + Ok(SharedMemory { fd: file, size: 0 }) + } + + /// Constructs a `SharedMemory` instance from a file descriptor that represents shared memory. + /// + /// The size of the resulting shared memory will be determined using `File::seek`. If the given + /// file's size can not be determined this way, this will return an error. + pub fn from_raw_fd(fd: T) -> Result { + // Safe because the IntoRawFd trait indicates fd has unique ownership. + let mut file = unsafe { File::from_raw_fd(fd.into_raw_fd()) }; + let file_size = file.seek(SeekFrom::End(0))?; + Ok(SharedMemory { + fd: file, + size: file_size as u64, + }) + } + + /// Gets the size in bytes of the shared memory. + /// + /// The size returned here does not reflect changes by other interfaces or users of the shared + /// memory file descriptor.. + pub fn size(&self) -> u64 { + self.size + } + + /// Sets the size in bytes of the shared memory. + /// + /// Note that if some process has already mapped this shared memory and the new size is smaller, + /// that process may get signaled with SIGBUS if they access any page past the new size. + pub fn set_size(&mut self, size: u64) -> Result<()> { + let ret = unsafe { ftruncate64(self.fd.as_raw_fd(), size as off64_t) }; + if ret < 0 { + return errno_result(); + } + self.size = size; + Ok(()) + } +} + +impl AsRawFd for SharedMemory { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::ffi::CString; + use std::fs::read_link; + use std::io::repeat; + + use data_model::VolatileMemory; + + use MemoryMapping; + + #[test] + fn new() { + let shm = SharedMemory::new(None).expect("failed to create shared memory"); + assert_eq!(shm.size(), 0); + } + + #[test] + fn new_sized() { + let mut shm = SharedMemory::new(None).expect("failed to create shared memory"); + shm.set_size(1024) + .expect("failed to set shared memory size"); + assert_eq!(shm.size(), 1024); + } + + #[test] + fn new_huge() { + let mut shm = SharedMemory::new(None).expect("failed to create shared memory"); + shm.set_size(0x7fff_ffff_ffff_ffff) + .expect("failed to set shared memory size"); + assert_eq!(shm.size(), 0x7fff_ffff_ffff_ffff); + } + + #[test] + fn new_too_huge() { + let mut shm = SharedMemory::new(None).expect("failed to create shared memory"); + shm.set_size(0x8000_0000_0000_0000).unwrap_err(); + assert_eq!(shm.size(), 0); + } + + #[test] + fn new_named() { + let name = "very unique name"; + let cname = CString::new(name).unwrap(); + let shm = SharedMemory::new(Some(&cname)).expect("failed to create shared memory"); + let fd_path = format!("/proc/self/fd/{}", shm.as_raw_fd()); + let link_name = + read_link(fd_path).expect("failed to read link of shared memory /proc/self/fd entry"); + assert!(link_name.to_str().unwrap().contains(name)); + } + + #[test] + fn mmap_page() { + let mut shm = SharedMemory::new(None).expect("failed to create shared memory"); + shm.set_size(4096) + .expect("failed to set shared memory size"); + + let mmap1 = + MemoryMapping::from_fd(&shm, shm.size() as usize).expect("failed to map shared memory"); + let mmap2 = + MemoryMapping::from_fd(&shm, shm.size() as usize).expect("failed to map shared memory"); + + assert_ne!(mmap1.get_slice(0, 1).unwrap().as_ptr(), + mmap2.get_slice(0, 1).unwrap().as_ptr()); + + mmap1 + .get_slice(0, 4096) + .expect("failed to get mmap slice") + .read_from(&mut repeat(0x45)) + .expect("failed to fill mmap slice"); + + for i in 0..4096 { + assert_eq!(mmap2.get_ref::(i).unwrap().load(), 0x45u8); + } + } +}