shared_memory_extended/
lib.rs

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