iceoryx2_bb_posix/
shared_memory.rs

1// Copyright (c) 2023 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13//! Provides access to a POSIX [`SharedMemory`]Object used to share memory between processes.
14//!
15//! # Important
16//!
17//! When constructing objects into the memory one MUST ensure that the memory representation is
18//! identical in every process. Therefore, it is important to add `#[repr(C)]` to the struct. If
19//! this struct is a composite every member must have `#[repr(C)]` enabled.
20//!
21//! # Examples
22//!
23//! ## Create non-existing shared memory.
24//!
25//! ```
26//! # extern crate iceoryx2_loggers;
27//!
28//! use iceoryx2_bb_posix::shared_memory::*;
29//! use iceoryx2_bb_system_types::file_name::FileName;
30//! use iceoryx2_bb_container::semantic_string::*;
31//!
32//! let name = FileName::new(b"someShmName").unwrap();
33//! let mut shm = SharedMemoryBuilder::new(&name)
34//!                     .is_memory_locked(false)
35//!           // the SharedMemoryCreationBuilder is used from here on
36//!                     .creation_mode(CreationMode::PurgeAndCreate)
37//!                     .size(1024)
38//!                     .permission(Permission::OWNER_ALL)
39//!                     .zero_memory(true)
40//!                     .create()
41//!                     .expect("failed to create shared memory");
42//!
43//! println!("shm name: {}", shm.name());
44//! println!("shm addr: {:?}", shm.base_address());
45//! println!("shm size: {}", shm.size());
46//!
47//! // set the first byte of the shared memory
48//! shm.as_mut_slice()[0] = 0xFF;
49//! ```
50//!
51//! ## Open existing shared memory.
52//!
53//! ```no_run
54//! # extern crate iceoryx2_loggers;
55//!
56//! use iceoryx2_bb_posix::shared_memory::*;
57//! use iceoryx2_bb_system_types::file_name::FileName;
58//! use iceoryx2_bb_container::semantic_string::*;
59//!
60//! let name = FileName::new(b"someShmName").unwrap();
61//! let shm = SharedMemoryBuilder::new(&name)
62//!                     .is_memory_locked(false)
63//!                     .open_existing(AccessMode::Read)
64//!                     .expect("failed to open shared memory");
65//!
66//! // print the first byte of the shared memory
67//! println!("first byte: {}", shm.as_slice()[0]);
68//! ```
69
70use core::ptr::NonNull;
71
72use alloc::vec;
73use alloc::vec::Vec;
74
75use iceoryx2_bb_concurrency::atomic::AtomicBool;
76use iceoryx2_bb_concurrency::atomic::Ordering;
77use iceoryx2_bb_container::semantic_string::*;
78use iceoryx2_bb_elementary::enum_gen;
79use iceoryx2_bb_system_types::file_name::*;
80use iceoryx2_bb_system_types::file_path::*;
81use iceoryx2_bb_system_types::path::*;
82use iceoryx2_log::{error, fail, fatal_panic, trace};
83use iceoryx2_pal_configuration::PATH_SEPARATOR;
84use iceoryx2_pal_posix::posix::errno::Errno;
85use iceoryx2_pal_posix::posix::POSIX_SUPPORT_ADVANCED_SIGNAL_HANDLING;
86use iceoryx2_pal_posix::posix::POSIX_SUPPORT_PERSISTENT_SHARED_MEMORY;
87use iceoryx2_pal_posix::*;
88
89pub use crate::access_mode::AccessMode;
90pub use crate::creation_mode::CreationMode;
91use crate::file::{FileStatError, FileTruncateError};
92use crate::file_descriptor::*;
93use crate::memory_lock::{MemoryLock, MemoryLockCreationError};
94use crate::memory_mapping::{
95    MappingBehavior, MemoryMapping, MemoryMappingBuilder, MemoryMappingCreationError,
96};
97pub use crate::permission::Permission;
98use crate::signal::SignalHandler;
99use crate::system_configuration::Limit;
100
101enum_gen! { SharedMemoryCreationError
102  entry:
103    SizeDoesNotFit,
104    InsufficientMemory,
105    InsufficientMemoryToBeMemoryLocked,
106    UnsupportedSizeOfZero,
107    InsufficientPermissions,
108    MappedRegionLimitReached,
109    PerProcessFileHandleLimitReached,
110    SystemWideFileHandleLimitReached,
111    NameTooLong,
112    InvalidName,
113    AlreadyExist,
114    DoesNotExist,
115    UnknownError(i32)
116  mapping:
117    FileTruncateError,
118    FileStatError,
119    MemoryLockCreationError,
120    SharedMemoryRemoveError,
121    MemoryMappingCreationError
122}
123
124enum_gen! { SharedMemoryRemoveError
125  entry:
126    InsufficientPermissions,
127    UnknownError(i32)
128}
129
130/// The builder for the [`SharedMemory`].
131#[derive(Debug)]
132pub struct SharedMemoryBuilder {
133    name: FileName,
134    size: usize,
135    is_memory_locked: bool,
136    has_ownership: bool,
137    permission: Permission,
138    creation_mode: Option<CreationMode>,
139    zero_memory: bool,
140    access_mode: AccessMode,
141    mapping_offset: isize,
142    enforce_base_address: Option<u64>,
143}
144
145impl SharedMemoryBuilder {
146    pub fn new(name: &FileName) -> Self {
147        SharedMemoryBuilder {
148            name: *name,
149            size: 0,
150            is_memory_locked: false,
151            permission: Permission::OWNER_ALL,
152            access_mode: AccessMode::None,
153            has_ownership: true,
154            creation_mode: None,
155            zero_memory: true,
156            mapping_offset: 0,
157            enforce_base_address: None,
158        }
159    }
160
161    /// Defines the mapping offset when the shared memory object is mapped into
162    /// the process space.
163    pub fn mapping_offset(mut self, value: isize) -> Self {
164        self.mapping_offset = value;
165        self
166    }
167
168    /// Locks the shared memory into the heap. If this is enabled swapping of the
169    /// created shared memory segment is no longer possible.
170    pub fn is_memory_locked(mut self, value: bool) -> Self {
171        self.is_memory_locked = value;
172        self
173    }
174
175    /// Sets a base address for the shared memory which is enforced. When the shared memory
176    /// could not mapped at the provided address the creation fails.
177    pub fn enforce_base_address(mut self, value: u64) -> Self {
178        self.enforce_base_address = Some(value);
179        self
180    }
181
182    /// Opens an already existing shared memory.
183    pub fn open_existing(
184        mut self,
185        access_mode: AccessMode,
186    ) -> Result<SharedMemory, SharedMemoryCreationError> {
187        self.access_mode = access_mode;
188        Self::open(self)
189    }
190
191    fn create_memory_mapping(
192        file_descriptor: FileDescriptor,
193        config: &SharedMemoryBuilder,
194    ) -> Result<MemoryMapping, SharedMemoryCreationError> {
195        match MemoryMappingBuilder::from_file_descriptor(file_descriptor)
196            .mapping_behavior(MappingBehavior::Shared)
197            .initial_mapping_permission(config.access_mode.into())
198            .mapping_address_hint(config.enforce_base_address.unwrap_or(0) as usize)
199            .enforce_mapping_address_hint(config.enforce_base_address.is_some())
200            .offset(config.mapping_offset)
201            .size(config.size)
202            .create()
203        {
204            Ok(mapping) => Ok(mapping),
205            Err(e) => {
206                fail!(from config, with e.into(),
207                        "Failed to create shared memory since the memory mapping failed ({e:?}).");
208            }
209        }
210    }
211
212    fn open(mut self) -> Result<SharedMemory, SharedMemoryCreationError> {
213        let msg = "Unable to open shared memory";
214        let fd = SharedMemory::shm_open(&self.name, &self)?;
215
216        let actual_shm_size = fail!(from self, when fd.metadata(),
217                "{} since a failure occurred while acquiring the file attributes.", msg)
218        .size();
219        self.size = actual_shm_size as usize;
220
221        let memory_mapping = Self::create_memory_mapping(fd, &self)?;
222
223        let shm = SharedMemory {
224            name: self.name,
225            has_ownership: AtomicBool::new(false),
226            memory_lock: None,
227            memory_mapping,
228            mapping_offset: self.mapping_offset,
229        };
230
231        trace!(from shm, "open");
232        Ok(shm)
233    }
234
235    /// Creates a new shared memory segment.
236    pub fn creation_mode(mut self, creation_mode: CreationMode) -> SharedMemoryCreationBuilder {
237        self.access_mode = AccessMode::ReadWrite;
238        self.creation_mode = Some(creation_mode);
239        SharedMemoryCreationBuilder { config: self }
240    }
241}
242
243#[derive(Debug)]
244pub struct SharedMemoryCreationBuilder {
245    config: SharedMemoryBuilder,
246}
247
248impl SharedMemoryCreationBuilder {
249    /// Sets the permissions of the new shared memory
250    pub fn permission(mut self, value: Permission) -> Self {
251        self.config.permission = value;
252        self
253    }
254
255    /// Zero the memory of the shared memory. It can serve to purposes.
256    /// * Ensure that the memory is clean before using it.
257    /// * Ensure that enough memory is actually available. On some operating systems the memory is
258    ///   only virtually allocated and when it is later required but there is not enough memory
259    ///   left the application fails.
260    pub fn zero_memory(mut self, value: bool) -> Self {
261        self.config.zero_memory = value;
262        self
263    }
264
265    /// The size of the shared memory.
266    pub fn size(mut self, size: usize) -> Self {
267        self.config.size = size;
268        self
269    }
270
271    /// Defines if a newly created [`SharedMemory`] owns the underlying resources. If they are not
272    /// owned they will not be cleaned up and can be opened later but they need to be explicitly
273    /// removed.
274    pub fn has_ownership(mut self, value: bool) -> Self {
275        self.config.has_ownership = value;
276        self
277    }
278
279    /// Creates the shared memory segment.
280    pub fn create(mut self) -> Result<SharedMemory, SharedMemoryCreationError> {
281        let msg = "Unable to create shared memory";
282
283        if self.config.size == 0 {
284            fail!(from self.config, with SharedMemoryCreationError::UnsupportedSizeOfZero,
285                "{msg} since a size of 0 is not supported for a shared memory object.");
286        }
287
288        let shm_created;
289        let mut fd = match self
290            .config
291            .creation_mode
292            .expect("CreationMode must be set on creation")
293        {
294            CreationMode::CreateExclusive => {
295                shm_created = true;
296                SharedMemory::shm_create(&self.config.name, &self.config)?
297            }
298            CreationMode::PurgeAndCreate => {
299                shm_created = true;
300                fail!(from self.config, when SharedMemory::shm_unlink(&self.config.name),
301                    "Failed to remove already existing shared memory.");
302                SharedMemory::shm_create(&self.config.name, &self.config)?
303            }
304            CreationMode::OpenOrCreate => {
305                match SharedMemory::shm_open(&self.config.name, &self.config) {
306                    Ok(fd) => {
307                        shm_created = false;
308                        self.config.has_ownership = false;
309                        fd
310                    }
311                    Err(SharedMemoryCreationError::DoesNotExist) => {
312                        shm_created = true;
313                        match SharedMemory::shm_create(&self.config.name, &self.config) {
314                            Ok(fd) => fd,
315                            Err(SharedMemoryCreationError::AlreadyExist) => {
316                                SharedMemory::shm_open(&self.config.name, &self.config)?
317                            }
318                            Err(e) => return Err(e),
319                        }
320                    }
321                    Err(v) => return Err(v),
322                }
323            }
324        };
325
326        if !shm_created {
327            let actual_shm_size = fail!(from self.config, when fd.metadata(),
328                    "{} since a failure occurred while acquiring the file attributes.", msg)
329            .size();
330            if self.config.size > actual_shm_size as usize {
331                fail!(from self.config, with SharedMemoryCreationError::SizeDoesNotFit,
332                    "{} since the actual size {} is not equal to the configured size {}.", msg, actual_shm_size, self.config.size);
333            }
334
335            self.config.size = actual_shm_size as _;
336            let memory_mapping = SharedMemoryBuilder::create_memory_mapping(fd, &self.config)?;
337
338            let shm = SharedMemory {
339                name: self.config.name,
340                has_ownership: AtomicBool::new(self.config.has_ownership),
341                memory_lock: None,
342                memory_mapping,
343                mapping_offset: self.config.mapping_offset,
344            };
345
346            trace!(from shm, "open");
347            return Ok(shm);
348        }
349
350        fail!(from self.config, when fd.truncate(self.config.size), "{} since the shared memory truncation failed.", msg);
351
352        let actual_shm_size = fail!(from self.config, when fd.metadata(),
353                "{} since a failure occurred while acquiring the file attributes.", msg)
354        .size();
355        if (actual_shm_size as usize) < self.config.size {
356            fail!(from self.config, with SharedMemoryCreationError::SizeDoesNotFit,
357                "{} since the actual size {} is less than to the configured size {}.", msg, actual_shm_size, self.config.size);
358        }
359
360        self.config.size = actual_shm_size as _;
361        let memory_mapping = SharedMemoryBuilder::create_memory_mapping(fd, &self.config)?;
362
363        let mut shm = SharedMemory {
364            name: self.config.name,
365            has_ownership: AtomicBool::new(self.config.has_ownership),
366            memory_lock: None,
367            memory_mapping,
368            mapping_offset: self.config.mapping_offset,
369        };
370
371        if self.config.is_memory_locked {
372            shm.memory_lock = Some(
373                fail!(from self.config, when unsafe { MemoryLock::new(shm.memory_mapping.base_address().cast(), shm.memory_mapping.size()) },
374                        "{} since the memory lock failed.", msg),
375            )
376        }
377
378        if self.config.zero_memory {
379            if POSIX_SUPPORT_ADVANCED_SIGNAL_HANDLING {
380                let memset_call = || unsafe {
381                    posix::memset(
382                        shm.memory_mapping.base_address_mut().cast(),
383                        0,
384                        self.config.size,
385                    );
386                };
387                match SignalHandler::call_and_fetch(memset_call) {
388                    None => (),
389                    Some(v) => {
390                        fail!(from self.config, with SharedMemoryCreationError::InsufficientMemory,
391                            "{} since a signal {} was raised while zeroing the memory. Is enough memory available on the system?", msg, v);
392                    }
393                }
394            } else {
395                unsafe {
396                    posix::memset(
397                        shm.memory_mapping.base_address_mut().cast(),
398                        0,
399                        self.config.size,
400                    )
401                };
402            }
403        }
404
405        trace!(from shm, "create");
406        Ok(shm)
407    }
408}
409
410/// A POSIX shared memory object which is build by the [`SharedMemoryBuilder`].
411#[derive(Debug)]
412pub struct SharedMemory {
413    name: FileName,
414    has_ownership: AtomicBool,
415    memory_mapping: MemoryMapping,
416    memory_lock: Option<MemoryLock>,
417    mapping_offset: isize,
418}
419
420impl Drop for SharedMemory {
421    fn drop(&mut self) {
422        if self.has_ownership() {
423            match self.set_permission(Permission::OWNER_ALL) {
424                Ok(()) => match Self::shm_unlink(&self.name) {
425                    Ok(_) => {
426                        trace!(from self, "delete");
427                    }
428                    Err(_) => {
429                        error!(from self, "Failed to cleanup shared memory.");
430                    }
431                },
432                Err(e) => {
433                    error!(from self, "Failed to cleanup shared memory since the permissions could not be adjusted ({:?}).", e);
434                }
435            }
436        }
437    }
438}
439
440impl SharedMemory {
441    /// Returns true if the shared memory exists and is accessible, otherwise false.
442    pub fn does_exist(name: &FileName) -> bool {
443        let file_path =
444            FilePath::from_path_and_file(&Path::new(&[PATH_SEPARATOR; 1]).unwrap(), name).unwrap();
445        FileDescriptor::new(unsafe {
446            posix::shm_open(
447                file_path.as_c_str(),
448                AccessMode::Read.as_oflag(),
449                Permission::none().as_mode(),
450            )
451        })
452        .is_some()
453    }
454
455    /// Returns the mapping offset used when the shared memory object was mapped into process space
456    pub fn mapping_offset(&self) -> isize {
457        self.mapping_offset
458    }
459
460    /// Returns if the posix implementation supports persistent shared memory, meaning that when every
461    /// shared memory handle got out of scope the underlying OS resource remains.
462    pub fn does_support_persistency() -> bool {
463        POSIX_SUPPORT_PERSISTENT_SHARED_MEMORY
464    }
465
466    /// Returns true if the shared memory object has the ownership of the underlying posix shared
467    /// memory. Ownership implies hereby that the posix shared memory is removed as soon as this
468    /// object goes out of scope.
469    pub fn has_ownership(&self) -> bool {
470        self.has_ownership.load(Ordering::Relaxed)
471    }
472
473    /// Releases the ownership of the underlying posix shared memory. If the object goes out of
474    /// scope the shared memory is no longer removed.
475    pub fn release_ownership(&self) {
476        self.has_ownership.store(false, Ordering::Relaxed)
477    }
478
479    /// Acquires the ownership of the underlying posix shared memory. If the object goes out of
480    /// scope the shared memory will be removed.
481    pub fn acquire_ownership(&self) {
482        self.has_ownership.store(true, Ordering::Relaxed)
483    }
484
485    /// Removes a shared memory file.
486    pub fn remove(name: &FileName) -> Result<bool, SharedMemoryRemoveError> {
487        match Self::shm_unlink(name) {
488            Ok(true) => {
489                trace!(from "SharedMemory::remove", "\"{}\"", name);
490                Ok(true)
491            }
492            Ok(false) => Ok(false),
493            Err(v) => Err(v),
494        }
495    }
496
497    /// Returns a list of all shared memory objects
498    pub fn list() -> Vec<FileName> {
499        let mut result = vec![];
500
501        let raw_shm_names = unsafe { posix::shm_list() };
502        for name in &raw_shm_names {
503            if let Ok(f) = unsafe { FileName::from_c_str(name.as_ptr() as *mut _) } {
504                result.push(f)
505            }
506        }
507
508        result
509    }
510
511    /// returns the name of the shared memory
512    pub fn name(&self) -> &FileName {
513        &self.name
514    }
515
516    /// returns the base address of the shared memory. The base address is always aligned to the
517    /// page size, this implies that it is aligned with every possible type.
518    /// No further alignment required!
519    pub fn base_address(&self) -> NonNull<u8> {
520        match NonNull::new(self.memory_mapping.base_address().cast_mut()) {
521            Some(v) => v,
522            None => {
523                fatal_panic!(from self,
524                    "This should never happen! A valid shared memory object should never contain a base address with null value.");
525            }
526        }
527    }
528
529    /// returns the size of the shared memory
530    pub fn size(&self) -> usize {
531        self.memory_mapping.size()
532    }
533
534    /// returns a slice to the memory
535    pub fn as_slice(&self) -> &[u8] {
536        self.memory_mapping.as_slice()
537    }
538
539    /// returns a mutable slice to the memory
540    pub fn as_mut_slice(&mut self) -> &mut [u8] {
541        self.memory_mapping.as_mut_slice()
542    }
543
544    fn shm_create(
545        name: &FileName,
546        config: &SharedMemoryBuilder,
547    ) -> Result<FileDescriptor, SharedMemoryCreationError> {
548        let file_path =
549            FilePath::from_path_and_file(&Path::new(&[PATH_SEPARATOR; 1]).unwrap(), name).unwrap();
550        let fd = FileDescriptor::new(unsafe {
551            posix::shm_open(
552                file_path.as_c_str(),
553                CreationMode::CreateExclusive.as_oflag() | config.access_mode.as_oflag(),
554                config.permission.as_mode(),
555            )
556        });
557
558        if let Some(v) = fd {
559            return Ok(v);
560        }
561
562        let msg = "Unable to create shared memory";
563        handle_errno!(SharedMemoryCreationError, from config,
564            Errno::EACCES => (InsufficientPermissions, "{} due to insufficient permissions.", msg),
565            Errno::EINVAL => (InvalidName, "{} since the provided name \"{}\" is invalid.", msg, name),
566            Errno::EEXIST => (AlreadyExist, "{} since it already exists.", msg),
567            Errno::EMFILE => (PerProcessFileHandleLimitReached, "{} since the per-process file handle limit was reached.", msg),
568            Errno::ENFILE => (SystemWideFileHandleLimitReached, "{} since the system-wide file handle limit was reached.", msg),
569            Errno::ENAMETOOLONG => (NameTooLong, "{} since the name exceeds the maximum supported length of {}.", msg, Limit::MaxFileNameLength.value() ),
570            v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
571        );
572    }
573
574    fn shm_open(
575        name: &FileName,
576        config: &SharedMemoryBuilder,
577    ) -> Result<FileDescriptor, SharedMemoryCreationError> {
578        let file_path =
579            FilePath::from_path_and_file(&Path::new(&[PATH_SEPARATOR; 1]).unwrap(), name).unwrap();
580        let fd = FileDescriptor::new(unsafe {
581            posix::shm_open(
582                file_path.as_c_str(),
583                config.access_mode.as_oflag(),
584                Permission::none().as_mode(),
585            )
586        });
587
588        if let Some(v) = fd {
589            return Ok(v);
590        }
591
592        let msg = "Unable to open shared memory";
593        handle_errno!(SharedMemoryCreationError, from config,
594            Errno::ENOENT => (DoesNotExist, "{} since the shared memory does not exist.", msg),
595            Errno::EACCES => (InsufficientPermissions, "{} due to insufficient permissions.", msg),
596            Errno::EINVAL => (InvalidName, "{} since the provided name \"{}\" is invalid.", msg, name),
597            Errno::EMFILE => (PerProcessFileHandleLimitReached, "{} since the per-process file handle limit was reached.", msg),
598            Errno::ENFILE => (SystemWideFileHandleLimitReached, "{} since the system-wide file handle limit was reached.", msg),
599            Errno::ENAMETOOLONG => (NameTooLong, "{} since the name exceeds the maximum supported length of {}.", msg, Limit::MaxFileNameLength.value() ),
600            v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
601        );
602    }
603
604    fn shm_unlink(name: &FileName) -> Result<bool, SharedMemoryRemoveError> {
605        let file_path =
606            FilePath::from_path_and_file(&Path::new(&[PATH_SEPARATOR; 1]).unwrap(), name).unwrap();
607        if unsafe { posix::shm_unlink(file_path.as_c_str()) } == 0 {
608            return Ok(true);
609        }
610
611        let msg = "Unable to remove shared memory device file";
612        let origin = "SharedMemory::unlink()";
613        match posix::Errno::get() {
614            posix::Errno::EACCES => {
615                fail!(from origin, with SharedMemoryRemoveError::InsufficientPermissions,
616                    "{} \"{}\" due to insufficient permissions.", msg, name);
617            }
618            posix::Errno::ENOENT => Ok(false),
619            v => {
620                fail!(from origin, with SharedMemoryRemoveError::UnknownError(v as i32),
621                    "{} \"{}\" since an unknown error occurred ({}).", msg, name, v);
622            }
623        }
624    }
625}
626
627impl FileDescriptorBased for SharedMemory {
628    fn file_descriptor(&self) -> &FileDescriptor {
629        self.memory_mapping
630            .file_descriptor()
631            .as_ref()
632            .expect("Memory mapping is based on a file descriptor")
633    }
634}
635
636impl FileDescriptorManagement for SharedMemory {}