iceoryx2_bb_posix/
ipc_capable.rs

1// Copyright (c) 2024 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
13use iceoryx2_bb_concurrency::atomic::AtomicBool;
14use iceoryx2_bb_concurrency::atomic::Ordering;
15use iceoryx2_bb_concurrency::cell::UnsafeCell;
16
17pub(crate) mod internal {
18    use core::fmt::Debug;
19
20    use super::*;
21
22    #[derive(Debug, Clone, Copy, Eq, PartialEq)]
23    pub enum Capability {
24        InterProcess,
25        ProcessLocal,
26    }
27
28    /// Stores an OS handle to some resource that is also inter-process capable, like a unnamed
29    /// semaphore or mutex handle.
30    pub struct HandleStorage<T> {
31        handle: UnsafeCell<T>,
32        is_inter_process_capable: AtomicBool,
33        is_initialized: AtomicBool,
34    }
35
36    unsafe impl<T> Send for HandleStorage<T> {}
37    unsafe impl<T> Sync for HandleStorage<T> {}
38
39    impl<T> Debug for HandleStorage<T> {
40        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41            write!(
42                f,
43                "HandleStorage<{}> {{ is_interprocess_capable: {}, is_initialized: {} }}",
44                core::any::type_name::<T>(),
45                self.is_inter_process_capable.load(Ordering::Relaxed),
46                self.is_initialized.load(Ordering::Relaxed),
47            )
48        }
49    }
50
51    impl<T> Drop for HandleStorage<T> {
52        fn drop(&mut self) {
53            self.is_initialized.store(false, Ordering::Relaxed);
54        }
55    }
56
57    impl<T> HandleStorage<T> {
58        /// Creates a new HandleStorage with a predefined handle that must be not initialized.
59        pub fn new(handle: T) -> Self {
60            Self {
61                handle: UnsafeCell::new(handle),
62                is_initialized: AtomicBool::new(false),
63                is_inter_process_capable: AtomicBool::new(false),
64            }
65        }
66
67        /// Returns true if the [`Handle`] is initialized, otherwise false.
68        pub fn is_initialized(&self) -> bool {
69            self.is_initialized.load(Ordering::Relaxed)
70        }
71
72        /// Initializes the handle via a provided initializer callback. If the initializer returns
73        /// true the underlying handle is marked as initialized otherwise it is still uninitialized.
74        ///
75        /// # Safety
76        ///  * The handle must be uninitialized
77        ///  * Must not be shared with other threads before calling [`IpcCapable::initialize()`]
78        ///
79        pub unsafe fn initialize<E, F: FnOnce(*mut T) -> Result<Capability, E>>(
80            &self,
81            initializer: F,
82        ) -> Result<(), E> {
83            debug_assert!(
84                !self.is_initialized.load(Ordering::Relaxed),
85                "The handle must be uninitialized before it can be initialized."
86            );
87
88            self.is_inter_process_capable.store(
89                initializer(self.handle.get())? == Capability::InterProcess,
90                Ordering::Relaxed,
91            );
92
93            // does not need to sync any memory since the construct is not allowed to
94            // be shared with any other thread before it is initialized
95            // -> Ordering::Relaxed
96            self.is_initialized.store(true, Ordering::Relaxed);
97
98            Ok(())
99        }
100
101        pub fn is_inter_process_capable(&self) -> bool {
102            self.is_inter_process_capable.load(Ordering::Relaxed)
103        }
104
105        /// Releases the underlying resources of the handle via the cleanup callback.
106        ///
107        /// # Safety
108        ///  * The handle must be initialized
109        ///  * Must not be used concurrently. Only one thread - the one that calls
110        ///    [`IpcCapable::cleanup()`] - is allowed to operate on the [`IpcCapable`].
111        ///
112        pub unsafe fn cleanup<F: FnOnce(&mut T)>(&self, cleanup: F) {
113            debug_assert!(
114                self.is_initialized.load(Ordering::Relaxed),
115                "The handle must be initialized before it can be cleaned up."
116            );
117
118            cleanup(self.get());
119
120            // does not need to sync any memory since the construct is not allowed to
121            // be shared with any other thread before it is initialized
122            // -> Ordering::Relaxed
123            self.is_initialized.store(false, Ordering::Relaxed);
124        }
125
126        /// Returns a mutable reference to the underlying handle.
127        ///
128        /// # Safety
129        ///  * The handle must be initialized
130        ///
131        #[allow(clippy::mut_from_ref)]
132        pub unsafe fn get(&self) -> &mut T {
133            debug_assert!(
134                self.is_initialized.load(Ordering::Relaxed),
135                "The handle must be initialized before it can be acquired."
136            );
137
138            unsafe { &mut *self.handle.get() }
139        }
140    }
141
142    pub trait IpcConstructible<'a, T: Handle> {
143        fn new(handle: &'a T) -> Self;
144    }
145}
146
147#[derive(Debug, PartialEq, Eq, Clone, Copy)]
148pub enum HandleState {
149    Initialized,
150    Uninitialized,
151}
152
153/// Represents a handle that is in general inter-process capable.
154pub trait Handle: Send + Sync {
155    fn new() -> Self;
156    fn is_inter_process_capable(&self) -> bool;
157    fn is_initialized(&self) -> bool;
158}
159
160/// Represents struct that can be configured for inter-process use.
161pub trait IpcCapable<'a, T: Handle>: internal::IpcConstructible<'a, T> + Sized {
162    /// Returns true if the object is interprocess capable, otherwise false
163    fn is_interprocess_capable(&self) -> bool;
164
165    /// Creates an IPC Capable object from its handle.
166    ///
167    /// # Safety
168    ///   * The handle must be initialized
169    ///   * The handle must be ipc capable, see [`Handle::is_inter_process_capable()`].
170    ///   * The handle must not be cleaned up while it is used by the object
171    ///
172    unsafe fn from_ipc_handle(handle: &'a T) -> Self {
173        debug_assert!(
174            handle.is_initialized(),
175            "The handle must be initialized before it can be used to construct an object."
176        );
177
178        debug_assert!(
179            handle.is_inter_process_capable(),
180            "The handle must be interprocess capable to be used for constructing an ipc capable object."
181        );
182
183        Self::new(handle)
184    }
185}