iceoryx2_bb_posix/
file_lock.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//! A FileLock can be created around any object which implements the [`FileDescriptorBased`] trait.
14//! Either one can exclusively lock the file for writing or many can lock it for reading.
15//!
16//! # Example
17//!
18//! ```no_run
19//! # extern crate iceoryx2_loggers;
20//!
21//! use iceoryx2_bb_posix::file::*;
22//! use iceoryx2_bb_posix::file_lock::*;
23//! use iceoryx2_bb_system_types::file_path::FilePath;
24//! use iceoryx2_bb_container::semantic_string::SemanticString;
25//!
26//! let file_name = FilePath::new(b"/tmp/file_lock_demo1").unwrap();
27//! let file = FileBuilder::new(&file_name)
28//!                              .creation_mode(CreationMode::PurgeAndCreate)
29//!                              .permission(Permission::OWNER_ALL)
30//!                              .create()
31//!                              .expect("failed to create file");
32//!
33//! let handle = ReadWriteMutexHandle::new();
34//! let fileWithLock = FileLockBuilder::new().create(file, &handle).expect("failed to create lock");
35//!
36//! fileWithLock.write_lock().unwrap().write(b"Hello world!");
37//! let mut content = String::new();
38//! fileWithLock.read_lock().unwrap().read_to_string(&mut content);
39//! ```
40
41pub use crate::read_write_mutex::*;
42
43use crate::file_descriptor::FileDescriptor;
44use crate::file_descriptor::FileDescriptorBased;
45use crate::process::{Process, ProcessId};
46use core::fmt::Debug;
47use core::{ops::Deref, ops::DerefMut};
48use iceoryx2_bb_concurrency::atomic::AtomicI64;
49use iceoryx2_bb_concurrency::atomic::Ordering;
50use iceoryx2_bb_elementary::enum_gen;
51use iceoryx2_log::fail;
52use iceoryx2_pal_posix::posix::errno::Errno;
53use iceoryx2_pal_posix::posix::MemZeroedStruct;
54use iceoryx2_pal_posix::*;
55
56use crate::clock::NanosleepError;
57
58enum_gen! { FileWriterGetLockError
59  mapping:
60    FileTryLockError,
61    NanosleepError,
62    ReadWriteMutexWriteLockError
63}
64
65enum_gen! { FileReaderGetLockError
66  mapping:
67    FileTryLockError,
68    NanosleepError,
69    ReadWriteMutexReadLockError
70}
71
72enum_gen! { FileTryLockError
73  entry:
74    Interrupt,
75    ExceedsMaximumNumberOfLockedRegionsInSystem,
76    InvalidFileDescriptorOrWrongOpenMode,
77    DeadlockConditionDetected,
78    UnknownError(i32)
79}
80
81enum_gen! { FileWriterTryLockError
82  mapping:
83    FileTryLockError,
84    ReadWriteMutexWriteLockError
85}
86
87enum_gen! { FileReaderTryLockError
88  mapping:
89    FileTryLockError,
90    ReadWriteMutexReadLockError
91}
92
93enum_gen! { FileUnlockError
94  entry:
95    Interrupt,
96    InvalidFileDescriptorOrWrongOpenMode,
97    IsNotLocked,
98    UnknownError(i32)
99}
100
101enum_gen! { FileLockStateError
102  entry:
103    InvalidFileDescriptor,
104    Interrupt,
105    UnknownError(i32)
106
107  mapping:
108    ReadWriteMutexReadLockError
109}
110
111enum_gen! {
112    /// The FileLockError enum is a generalization when one doesn't require the fine-grained error
113    /// handling enums. One can forward FileLockError as more generic return value when a method
114    /// returns a FileLock***Error.
115    /// On a higher level it is again convertable to [`crate::Error`]
116    FileLockError
117  generalization:
118    UnableToAcquireLock <= FileWriterGetLockError; FileReaderGetLockError; FileTryLockError; FileWriterTryLockError; FileReaderTryLockError; FileUnlockError; FileLockStateError
119}
120
121/// A guard which is acquired when the file could be successfully locked for writing with
122/// [`FileLock::write_lock()`] or [`FileLock::write_try_lock()`].
123/// It provides read and write access to the underlying file and unlocks it as soon as it goes out
124/// of scope.
125#[derive(Debug)]
126pub struct FileLockWriteGuard<'handle, 'b, T: FileDescriptorBased + Debug> {
127    file_lock: &'handle FileLock<'b, T>,
128    guard: MutexWriteGuard<'handle, T>,
129}
130
131unsafe impl<T: Send + FileDescriptorBased + Debug> Send for FileLockWriteGuard<'_, '_, T> {}
132unsafe impl<T: Send + Sync + FileDescriptorBased + Debug> Sync for FileLockWriteGuard<'_, '_, T> {}
133
134impl<T: FileDescriptorBased + Debug> Deref for FileLockWriteGuard<'_, '_, T> {
135    type Target = T;
136
137    fn deref(&self) -> &Self::Target {
138        &self.guard
139    }
140}
141
142impl<T: FileDescriptorBased + Debug> DerefMut for FileLockWriteGuard<'_, '_, T> {
143    fn deref_mut(&mut self) -> &mut Self::Target {
144        &mut self.guard
145    }
146}
147
148impl<T: FileDescriptorBased + Debug> Drop for FileLockWriteGuard<'_, '_, T> {
149    fn drop(&mut self) {
150        self.file_lock.release(self.guard.file_descriptor()).ok();
151    }
152}
153
154/// A guard which is acquired when the file could be successfully locked for reading with
155/// [`FileLock::read_lock()`] or [`FileLock::read_try_lock()`].
156/// It provides read access to the underlying file and unlocks it as soon as it goes out
157/// of scope.
158#[derive(Debug)]
159pub struct FileLockReadGuard<'handle, 'b, T: FileDescriptorBased + Debug> {
160    file_lock: &'handle FileLock<'b, T>,
161    guard: MutexReadGuard<'handle, T>,
162}
163
164unsafe impl<T: Send + FileDescriptorBased + Debug> Send for FileLockReadGuard<'_, '_, T> {}
165unsafe impl<T: Send + Sync + FileDescriptorBased + Debug> Sync for FileLockReadGuard<'_, '_, T> {}
166
167impl<T: FileDescriptorBased + Debug> Deref for FileLockReadGuard<'_, '_, T> {
168    type Target = T;
169
170    fn deref(&self) -> &Self::Target {
171        &self.guard
172    }
173}
174
175impl<T: FileDescriptorBased + Debug> Drop for FileLockReadGuard<'_, '_, T> {
176    fn drop(&mut self) {
177        self.file_lock.release(self.guard.file_descriptor()).ok();
178    }
179}
180
181/// The builder to create a [`FileLock`].
182///
183/// One has to create an object first which implements the [`FileDescriptorBased`] trait.
184///
185#[derive(Debug, Default)]
186pub struct FileLockBuilder {}
187
188impl FileLockBuilder {
189    pub fn new() -> Self {
190        Self::default()
191    }
192
193    pub fn create<T: FileDescriptorBased + Debug>(
194        self,
195        value: T,
196        handle: &ReadWriteMutexHandle<T>,
197    ) -> Result<FileLock<'_, T>, ReadWriteMutexCreationError> {
198        FileLock::new(value, self, handle)
199    }
200}
201
202/// A FileLock can be created around any object which implements the [`FileDescriptorBased`] trait.
203/// Either one can exclusively lock the file for writing or many can lock it for reading.
204///
205/// **Attention!** The FileLock is and advisary lock and not secure! If all participants follow the
206/// rules it provides threadsafe access but if one accesses the underlying file directly one can
207/// experience race-conditions.
208///
209/// **Attention!** Only the same instance of FileLock blocks inside the same process. If one for
210/// instance opens the file twice in the same process the read/write locking will not work anymore.
211/// But it works between processes. If two different processes are opening the same file
212/// read/writer locks will block the processes.
213#[derive(Debug)]
214pub struct FileLock<'a, T: FileDescriptorBased + Debug> {
215    file: ReadWriteMutex<'a, 'a, T>,
216    lock_state: AtomicI64,
217}
218
219unsafe impl<T: Send + FileDescriptorBased + Debug> Send for FileLock<'_, T> {}
220unsafe impl<T: Send + Sync + FileDescriptorBased + Debug> Sync for FileLock<'_, T> {}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223#[repr(i16)]
224pub enum LockType {
225    Read = posix::F_RDLCK as i16,
226    Write = posix::F_WRLCK as i16,
227    Unlock = posix::F_UNLCK as i16,
228}
229
230/// Describes the current state of the [`FileLock`]. If no one holds the lock then
231/// [`LockType::Unlock`] is set, otherwise [`LockType::Read`] or [`LockType::Write`] and the
232/// process id of the owner of the lock.
233#[derive(Debug)]
234pub struct LockState {
235    lock_type: LockType,
236    pid: ProcessId,
237}
238
239impl LockState {
240    pub fn lock_type(&self) -> LockType {
241        self.lock_type
242    }
243
244    pub fn pid_of_owner(&self) -> ProcessId {
245        self.pid
246    }
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250enum InternalMode {
251    Blocking,
252    NonBlocking,
253}
254
255impl<'a, T: FileDescriptorBased + Debug> FileLock<'a, T> {
256    fn new(
257        value: T,
258        config: FileLockBuilder,
259        handle: &'a ReadWriteMutexHandle<T>,
260    ) -> Result<Self, ReadWriteMutexCreationError> {
261        Ok(Self {
262            file: fail!(from config, when ReadWriteMutexBuilder::new()
263                .is_interprocess_capable(false)
264                .create(value, handle),
265                "Failed to create ReadWriteMutex for FileLock."),
266            lock_state: AtomicI64::new(0),
267        })
268    }
269
270    /// Blocking until the write lock of the underlying file is acquired. Returns a [`FileLockWriteGuard`]
271    /// which provides read and write access to the underlying file and releases the lock as soon
272    /// as it goes out of scope.
273    /// A write-lock can be acquired when no reader and no writer locks are acquired by any
274    /// other participant.
275    pub fn write_lock(&self) -> Result<FileLockWriteGuard<'_, '_, T>, FileWriterGetLockError> {
276        let guard = fail!(from self, when self.file.write_blocking_lock(),
277            "Failed to acquire writer mutex lock in write_lock");
278        self.internal_lock(
279            LockType::Write,
280            InternalMode::Blocking,
281            guard.file_descriptor(),
282        )?;
283
284        Ok(FileLockWriteGuard {
285            file_lock: self,
286            guard,
287        })
288    }
289
290    /// Tries to acquire the write lock in a non-blocking way. If the lock could be acquired it returns a [`FileLockWriteGuard`]
291    /// which provides read and write access to the underlying file and releases the lock as soon
292    /// as it goes out of scope. Otherwise it returns [`None`]
293    /// A write-lock can be acquired when no reader and no writer locks are acquired by any
294    /// other participant.
295    pub fn write_try_lock(
296        &self,
297    ) -> Result<Option<FileLockWriteGuard<'_, '_, T>>, FileWriterTryLockError> {
298        let guard = fail!(from self, when self.file.write_try_lock(),
299                     "Failed while trying to acquire writer mutex lock in write_try_lock");
300
301        if guard.is_none() {
302            return Ok(None);
303        }
304
305        match self.internal_lock(
306            LockType::Write,
307            InternalMode::NonBlocking,
308            guard.as_ref().unwrap().file_descriptor(),
309        )? {
310            true => Ok(Some(FileLockWriteGuard {
311                file_lock: self,
312                guard: guard.unwrap(),
313            })),
314            false => Ok(None),
315        }
316    }
317
318    /// Blocking until the read lock of the underlying file is acquired. Returns a
319    /// [`FileLockReadGuard`] which provides read access to the underlying file and releases the
320    /// lock as soon as it goes out of scope.
321    /// A read-lock can be acquired when no write lock is acquired by any other participant.
322    pub fn read_lock(&self) -> Result<FileLockReadGuard<'_, '_, T>, FileReaderGetLockError> {
323        let guard = fail!(from self, when self.file.read_blocking_lock(),
324                         "Failed to acquire reader mutex lock in read_lock");
325
326        self.internal_lock(
327            LockType::Read,
328            InternalMode::Blocking,
329            guard.file_descriptor(),
330        )?;
331
332        Ok(FileLockReadGuard {
333            file_lock: self,
334            guard,
335        })
336    }
337
338    /// Tries to acquire a read lock of the underlying file. If the lock could be acquired it returns a
339    /// [`FileLockReadGuard`] which provides read access to the underlying file and releases the
340    /// lock as soon as it goes out of scope. Otherwise it returns [`None`].
341    /// A read-lock can be acquired when no write lock is acquired by any other participant.
342    pub fn read_try_lock(
343        &self,
344    ) -> Result<Option<FileLockReadGuard<'_, '_, T>>, FileReaderTryLockError> {
345        let guard = fail!(from self, when self.file.read_try_lock(),
346                            "Failed while trying to acquire reader mutex lock in read_try_lock");
347
348        if guard.is_none() {
349            return Ok(None);
350        }
351
352        match self.internal_lock(
353            LockType::Read,
354            InternalMode::NonBlocking,
355            guard.as_ref().unwrap().file_descriptor(),
356        )? {
357            true => Ok(Some(FileLockReadGuard {
358                file_lock: self,
359                guard: guard.unwrap(),
360            })),
361            false => Ok(None),
362        }
363    }
364
365    /// Returns the current [`LockState`] of the [`FileLock`].
366    pub fn get_lock_state(&self) -> Result<LockState, FileLockStateError> {
367        match 0.cmp(&self.lock_state.load(Ordering::Relaxed)) {
368            core::cmp::Ordering::Less => {
369                return Ok(LockState {
370                    lock_type: LockType::Read,
371                    pid: Process::from_self().id(),
372                })
373            }
374            core::cmp::Ordering::Greater => {
375                return Ok(LockState {
376                    lock_type: LockType::Write,
377                    pid: Process::from_self().id(),
378                })
379            }
380            core::cmp::Ordering::Equal => (),
381        }
382
383        let msg = "Unable to acquire current file lock state";
384        let mut current_lock_state = posix::flock::new_zeroed();
385        current_lock_state.l_type = posix::F_WRLCK as _;
386
387        let fd_guard = fail!(from self, when self.file.read_blocking_lock(),
388            "{} due to an internal failure in while acquiring the mutex.", msg);
389
390        match unsafe {
391            posix::fcntl(
392                fd_guard.file_descriptor().native_handle(),
393                posix::F_GETLK,
394                &mut current_lock_state,
395            )
396        } != -1
397        {
398            true => Ok(LockState {
399                lock_type: match current_lock_state.l_type as i32 {
400                    posix::F_WRLCK => LockType::Write,
401                    posix::F_RDLCK => LockType::Read,
402                    _ => LockType::Unlock,
403                },
404                pid: ProcessId::new(current_lock_state.l_pid),
405            }),
406            false => handle_errno!(FileLockStateError, from self,
407                Errno::EBADF => (InvalidFileDescriptor, "{} since the file-descriptor is invalid or not opened in the correct mode.", msg),
408                Errno::EINTR => (Interrupt, "{} since an interrupt signal was received.", msg),
409                v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
410            ),
411        }
412    }
413
414    fn release(&self, file_descriptor: &FileDescriptor) -> Result<(), FileUnlockError> {
415        let mut new_lock_state = posix::flock::new_zeroed();
416        new_lock_state.l_type = LockType::Unlock as _;
417        new_lock_state.l_whence = posix::SEEK_SET as _;
418
419        let msg = "Unable to release file-lock";
420        if unsafe {
421            posix::fcntl(
422                file_descriptor.native_handle(),
423                posix::F_SETLK,
424                &mut new_lock_state,
425            )
426        } != -1
427        {
428            self.set_lock_state(LockType::Unlock);
429            return Ok(());
430        }
431
432        handle_errno!(FileUnlockError, from self,
433            Errno::EBADF => (InvalidFileDescriptorOrWrongOpenMode, "{} since the file-descriptor is invalid or not opened in the correct mode.", msg),
434            Errno::EINTR => (Interrupt, "{} since an interrupt signal was received.", msg),
435            v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
436        );
437    }
438
439    fn internal_lock(
440        &self,
441        lock_type: LockType,
442        mode: InternalMode,
443        file_descriptor: &FileDescriptor,
444    ) -> Result<bool, FileTryLockError> {
445        let mut new_lock_state = posix::flock::new_zeroed();
446        new_lock_state.l_type = lock_type as _;
447        new_lock_state.l_whence = posix::SEEK_SET as _;
448
449        if unsafe {
450            posix::fcntl(
451                file_descriptor.native_handle(),
452                if mode == InternalMode::NonBlocking {
453                    posix::F_SETLK
454                } else {
455                    posix::F_SETLKW
456                },
457                &mut new_lock_state,
458            )
459        } != -1
460        {
461            self.set_lock_state(lock_type);
462            return Ok(true);
463        }
464
465        let msg = match lock_type {
466            LockType::Read => "Unable to acquire read file-lock",
467            _ => "Unable to acquire write file-lock",
468        };
469
470        handle_errno!(FileTryLockError, from self,
471            success Errno::EACCES => false;
472            success Errno::EAGAIN => false,
473            Errno::EBADF => (InvalidFileDescriptorOrWrongOpenMode, "{} since the file-descriptor is invalid or not opened in the correct mode.", msg),
474            Errno::EINTR => (Interrupt, "{} since an interrupt signal was received.", msg),
475            Errno::ENOLCK => (ExceedsMaximumNumberOfLockedRegionsInSystem, "{} since it would exceed the maximum supported number of locked regions in the system..", msg),
476            Errno::EDEADLK => (DeadlockConditionDetected, "{} since a deadlock condition was detected.", msg),
477            v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
478        );
479    }
480
481    fn set_lock_state(&self, value: LockType) {
482        let current_value = self.lock_state.load(Ordering::Relaxed);
483        let adjustment = match value {
484            LockType::Read => 1,
485            LockType::Write => -1,
486            LockType::Unlock => {
487                if current_value > 0 {
488                    -1
489                } else {
490                    1
491                }
492            }
493        };
494
495        self.lock_state.fetch_add(adjustment, Ordering::Relaxed);
496    }
497}