1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
//! A thin wrapper around shared memory system calls
//!
//! For help on how to get started, take a look at the [examples](https://github.com/elast0ny/shared_memory-rs/tree/master/examples) !
use std::fs::{File, OpenOptions};
use std::io::{ErrorKind, Read, Write};
use std::fs::remove_file;
use std::path::{Path, PathBuf};
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "logging")] {
pub use log;
} else {
#[allow(unused_macros)]
mod log {
macro_rules! trace (($($tt:tt)*) => {{}});
macro_rules! debug (($($tt:tt)*) => {{}});
macro_rules! info (($($tt:tt)*) => {{}});
macro_rules! warn (($($tt:tt)*) => {{}});
macro_rules! error (($($tt:tt)*) => {{}});
pub(crate) use {debug, trace};
}
}
}
use crate::log::*;
mod error;
pub use error::*;
//Load up the proper OS implementation
cfg_if! {
if #[cfg(target_os="windows")] {
mod windows;
use windows as os_impl;
} else if #[cfg(any(target_os="freebsd", target_os="linux", target_os="macos"))] {
mod unix;
use crate::unix as os_impl;
} else {
compile_error!("shared_memory isnt implemented for this platform...");
}
}
#[derive(Clone, Default)]
/// Struct used to configure different parameters before creating a shared memory mapping
pub struct ShmemConf {
owner: bool,
os_id: Option<String>,
overwrite_flink: bool,
flink_path: Option<PathBuf>,
size: usize,
ext: os_impl::ShmemConfExt,
}
impl Drop for ShmemConf {
fn drop(&mut self) {
// Delete the flink if we are the owner of the mapping
if self.owner {
if let Some(flink_path) = self.flink_path.as_ref() {
debug!("Deleting file link {}", flink_path.to_string_lossy());
let _ = remove_file(flink_path);
}
}
}
}
impl ShmemConf {
/// Create a new default shmem config
pub fn new() -> Self {
ShmemConf::default()
}
/// Provide a specific os identifier for the mapping
///
/// When not specified, a randomly generated identifier will be used
pub fn os_id<S: AsRef<str>>(mut self, os_id: S) -> Self {
self.os_id = Some(String::from(os_id.as_ref()));
self
}
/// Overwrites file links if it already exist when calling `create()`
pub fn force_create_flink(mut self) -> Self {
self.overwrite_flink = true;
self
}
/// Create the shared memory mapping with a file link
///
/// This creates a file on disk that contains the unique os_id for the mapping.
/// This can be useful when application want to rely on filesystems to share mappings
pub fn flink<S: AsRef<Path>>(mut self, path: S) -> Self {
self.flink_path = Some(PathBuf::from(path.as_ref()));
self
}
/// Sets the size of the mapping that will be used in `create()`
pub fn size(mut self, size: usize) -> Self {
self.size = size;
self
}
/// Create a new mapping using the current configuration
pub fn create(mut self) -> Result<Shmem, ShmemError> {
if self.size == 0 {
return Err(ShmemError::MapSizeZero);
}
if let Some(ref flink_path) = self.flink_path {
if !self.overwrite_flink && flink_path.is_file() {
return Err(ShmemError::LinkExists);
}
}
// Create the mapping
let mapping = match self.os_id {
None => {
// Generate random ID until one works
loop {
let cur_id = format!("/shmem_{:X}", rand::random::<u64>());
match os_impl::create_mapping(&cur_id, self.size) {
Err(ShmemError::MappingIdExists) => continue,
Ok(m) => break m,
Err(e) => {
return Err(e);
}
};
}
}
Some(ref specific_id) => os_impl::create_mapping(specific_id, self.size)?,
};
debug!("Created shared memory mapping '{}'", mapping.unique_id);
// Create flink
if let Some(ref flink_path) = self.flink_path {
debug!("Creating file link that points to mapping");
let mut open_options: OpenOptions = OpenOptions::new();
open_options.write(true);
if self.overwrite_flink {
open_options.create(true).truncate(true);
} else {
open_options.create_new(true);
}
match open_options.open(flink_path) {
Ok(mut f) => {
// write the shmem uid asap
if let Err(e) = f.write(mapping.unique_id.as_bytes()) {
let _ = std::fs::remove_file(flink_path);
return Err(ShmemError::LinkWriteFailed(e));
}
}
Err(e) if e.kind() == ErrorKind::AlreadyExists => {
return Err(ShmemError::LinkExists)
}
Err(e) => return Err(ShmemError::LinkCreateFailed(e)),
}
debug!(
"Created file link '{}' with id '{}'",
flink_path.to_string_lossy(),
mapping.unique_id
);
}
self.owner = true;
self.size = mapping.map_size;
Ok(Shmem {
config: self,
mapping,
})
}
/// Opens an existing mapping using the current configuration
pub fn open(mut self) -> Result<Shmem, ShmemError> {
// Must at least have a flink or an os_id
if self.flink_path.is_none() && self.os_id.is_none() {
debug!("Open called with no file link or unique id...");
return Err(ShmemError::NoLinkOrOsId);
}
let mut flink_uid = String::new();
let mut retry = 0;
loop {
let unique_id = if let Some(ref unique_id) = self.os_id {
retry = 5;
unique_id.as_str()
} else {
let flink_path = self.flink_path.as_ref().unwrap();
debug!(
"Open shared memory from file link {}",
flink_path.to_string_lossy()
);
let mut f = match File::open(flink_path) {
Ok(f) => f,
Err(e) => return Err(ShmemError::LinkOpenFailed(e)),
};
flink_uid.clear();
if let Err(e) = f.read_to_string(&mut flink_uid) {
return Err(ShmemError::LinkReadFailed(e));
}
flink_uid.as_str()
};
match os_impl::open_mapping(unique_id, self.size, &self.ext) {
Ok(m) => {
self.size = m.map_size;
self.owner = false;
return Ok(Shmem {
config: self,
mapping: m,
});
}
// If we got this failing os_id from the flink, try again in case the shmem owner didnt write the full
// unique_id to the file
Err(ShmemError::MapOpenFailed(_)) if self.os_id.is_none() && retry < 5 => {
retry += 1;
std::thread::sleep(std::time::Duration::from_millis(50));
}
Err(e) => return Err(e),
}
}
}
}
/// Structure used to extract information from an existing shared memory mapping
pub struct Shmem {
config: ShmemConf,
mapping: os_impl::MapData,
}
#[allow(clippy::len_without_is_empty)]
impl Shmem {
/// Returns whether we created the mapping or not
pub fn is_owner(&self) -> bool {
self.config.owner
}
/// Allows for gaining/releasing ownership of the mapping
///
/// Warning : You must ensure at least one process owns the mapping in order to ensure proper cleanup code is ran
pub fn set_owner(&mut self, is_owner: bool) -> bool {
self.mapping.set_owner(is_owner);
let prev_val = self.config.owner;
self.config.owner = is_owner;
prev_val
}
/// Returns the OS unique identifier for the mapping
pub fn get_os_id(&self) -> &str {
self.mapping.unique_id.as_str()
}
/// Returns the flink path if present
pub fn get_flink_path(&self) -> Option<&PathBuf> {
self.config.flink_path.as_ref()
}
/// Returns the total size of the mapping
pub fn len(&self) -> usize {
self.mapping.map_size
}
/// Returns a raw pointer to the mapping
pub fn as_ptr(&self) -> *mut u8 {
self.mapping.as_mut_ptr()
}
/// Returns mapping as a byte slice
/// # Safety
/// This function is unsafe because it is impossible to ensure the range of bytes is immutable
pub unsafe fn as_slice(&self) -> &[u8] {
std::slice::from_raw_parts(self.as_ptr(), self.len())
}
/// Returns mapping as a mutable byte slice
/// # Safety
/// This function is unsafe because it is impossible to ensure the returned mutable refence is unique/exclusive
pub unsafe fn as_slice_mut(&mut self) -> &mut [u8] {
std::slice::from_raw_parts_mut(self.as_ptr(), self.len())
}
}