iceoryx2_bb_posix/
process_state.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
13//! Process monitoring via holding a file lock of a specific file. If the process crashes the
14//! lock will be released by the operating system and another process can detect the crash. If the
15//! process shutdowns correctly the file is removed and another process detects the clean shutdown.
16//!
17//! # Example
18//!
19//! ## Application (That Shall Be Monitored)
20//!
21//! ```
22//! # extern crate iceoryx2_loggers;
23//!
24//! use iceoryx2_bb_posix::process_state::*;
25//!
26//! let process_state_path = FilePath::new(b"process_state_file").unwrap();
27//!
28//! // monitoring is enabled as soon as the guard object is created
29//! let guard = match ProcessGuardBuilder::new().create(&process_state_path) {
30//!     Ok(guard) => guard,
31//!     Err(ProcessGuardCreateError::AlreadyExists) => {
32//!         // process is dead and we have to cleanup all resources
33//!         match ProcessCleaner::new(&process_state_path) {
34//!             Ok(cleaner) => {
35//!                 // we own the stale resources and have to remove them
36//!                 // as soon as the guard goes out of scope the process state file is removed
37//!                 drop(cleaner);
38//!
39//!                 match ProcessGuardBuilder::new().create(&process_state_path) {
40//!                     Ok(guard) => guard,
41//!                     Err(_) => {
42//!                         panic!("Perform here some error handling");
43//!                     }
44//!                 }
45//!             }
46//!             Err(ProcessCleanerCreateError::OwnedByAnotherProcess) => {
47//!                 // cool, someone else has instantiated it and is already cleaning up all resources
48//!                 // wait a bit and try again
49//!                 std::thread::sleep(core::time::Duration::from_millis(500));
50//!                 match ProcessGuardBuilder::new().create(&process_state_path) {
51//!                     Ok(guard) => guard,
52//!                     Err(_) => {
53//!                         panic!("Perform here some error handling");
54//!                     }
55//!                 }
56//!             }
57//!             Err(_) => {
58//!                 panic!("Perform here some error handling");
59//!             }
60//!         }
61//!     }
62//!     Err(_) => {
63//!         panic!("Perform here some error handling");
64//!     }
65//! };
66//!
67//! // normal application code
68//!
69//! // stop monitoring
70//! drop(guard);
71//! ```
72//!
73//! ## Watchdog (Process That Monitors The State Of Other Processes)
74//!
75//! ```
76//! # extern crate iceoryx2_loggers;
77//!
78//! use iceoryx2_bb_posix::process_state::*;
79//!
80//! let process_state_path = FilePath::new(b"process_state_file").unwrap();
81//!
82//! let mut monitor = ProcessMonitor::new(&process_state_path).expect("");
83//!
84//! match monitor.state().expect("") {
85//!     // Process is alive and well
86//!     ProcessState::Alive => (),
87//!
88//!     // The process state file is created, this state should persist only a very small
89//!     // fraction of time
90//!     ProcessState::Starting => (),
91//!
92//!     // Process died, we have to inform other interested parties and maybe cleanup some
93//!     // resources
94//!     ProcessState::Dead => {
95//!         // monitored process terminated abnormally, perform cleanup
96//!
97//!         match ProcessCleaner::new(&process_state_path) {
98//!             Ok(guard) => {
99//!                 // we own the old resources of the abnormally terminated process
100//!                 // this is the place where we remove them
101//!             }
102//!             Err(ProcessCleanerCreateError::OwnedByAnotherProcess) => {
103//!                 // Some other process is cleaning up all resources
104//!             }
105//!             Err(e) => {
106//!                 // custom error handling
107//!             },
108//!         };
109//!     },
110//!     ProcessState::CleaningUp => (),
111//!     // The monitored process does not exist, maybe it did not yet start or already performed
112//!     // a clean shutdown.
113//!     ProcessState::DoesNotExist => (),
114//! }
115//! ```
116//!
117//! ## Cleanup (Process That Removes Stale Resources)
118//!
119//! ```
120//! # extern crate iceoryx2_loggers;
121//!
122//! use iceoryx2_bb_posix::process_state::*;
123//!
124//! let process_state_path = FilePath::new(b"process_state_file").unwrap();
125//!
126//! match ProcessCleaner::new(&process_state_path) {
127//!     Ok(_guard) => {
128//!         // we own the stale resources and have to remove them
129//!         // as soon as the _guard goes out of scope the process state file is removed
130//!     }
131//!     Err(ProcessCleanerCreateError::OwnedByAnotherProcess) => {
132//!         // cool, someone else has instantiated it and is already cleaning up all resources
133//!     }
134//!     Err(_) => (),
135//! }
136//! ```
137
138use alloc::format;
139use core::fmt::Debug;
140
141pub use iceoryx2_bb_container::semantic_string::SemanticString;
142pub use iceoryx2_bb_system_types::file_path::FilePath;
143
144use iceoryx2_bb_concurrency::cell::Cell;
145use iceoryx2_bb_container::semantic_string::SemanticStringError;
146use iceoryx2_bb_elementary::enum_gen;
147use iceoryx2_log::{fail, trace};
148use iceoryx2_pal_posix::posix::{self, Errno, MemZeroedStruct};
149
150use crate::{
151    access_mode::AccessMode,
152    directory::{Directory, DirectoryAccessError, DirectoryCreateError},
153    file::{File, FileBuilder, FileCreationError, FileOpenError, FileRemoveError},
154    file_descriptor::{FileDescriptorBased, FileDescriptorManagement},
155    file_lock::LockType,
156    permission::Permission,
157    unix_datagram_socket::CreationMode,
158};
159
160/// Defines the current state of a process.
161#[derive(Debug, Clone, Copy, Eq, PartialEq)]
162pub enum ProcessState {
163    Alive,
164    Dead,
165    DoesNotExist,
166    Starting,
167    CleaningUp,
168}
169
170/// Defines all errors that can occur when a new [`ProcessGuard`] is created.
171#[derive(Debug, Clone, Copy, Eq, PartialEq)]
172pub enum ProcessGuardCreateError {
173    InsufficientPermissions,
174    IsDirectory,
175    InvalidDirectory,
176    AlreadyExists,
177    NoSpaceLeft,
178    ReadOnlyFilesystem,
179    ContractViolation,
180    Interrupt,
181    InvalidCleanerPathName,
182    UnknownError(i32),
183}
184
185#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
186enum ProcessGuardLockError {
187    OwnedByAnotherProcess,
188    Interrupt,
189    UnknownError(i32),
190}
191
192enum_gen! {
193/// Defines all errors that can occur when a stale [`ProcessGuard`] is removed.
194    ProcessGuardRemoveError
195  entry:
196    InsufficientPermissions,
197    Interrupt,
198    OwnedByAnotherProcess,
199    InvalidCleanerPathName,
200    UnknownError(i32)
201  mapping:
202    FileRemoveError
203}
204
205/// Defines all errors that can occur when a new [`ProcessMonitor`] is created.
206#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
207pub enum ProcessMonitorCreateError {
208    InsufficientPermissions,
209    Interrupt,
210    IsDirectory,
211    InvalidCleanerPathName,
212    UnknownError,
213}
214
215enum_gen! {
216/// Defines all errors that can occur in [`ProcessMonitor::state()`].
217    ProcessMonitorStateError
218  entry:
219    CorruptedState,
220    Interrupt,
221    UnknownError(i32)
222
223  mapping:
224    ProcessMonitorCreateError
225}
226
227/// Defines all errors that can occur when a new [`ProcessCleaner`] is created.
228#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
229pub enum ProcessCleanerCreateError {
230    ProcessIsStillAlive,
231    OwnedByAnotherProcess,
232    Interrupt,
233    FailedToAcquireLockState,
234    UnableToOpenStateFile,
235    UnableToOpenCleanerFile,
236    InvalidCleanerPathName,
237    DoesNotExist,
238    UnknownError,
239}
240
241/// The builder of the [`ProcessGuard`]
242/// ```
243/// # extern crate iceoryx2_loggers;
244///
245/// use iceoryx2_bb_posix::process_state::*;
246///
247/// let process_state_path = FilePath::new(b"process_state_file").unwrap();
248///
249/// // start monitoring from this point on
250/// let guard = ProcessGuardBuilder::new().create(&process_state_path).expect("");
251///
252/// // normal application code
253///
254/// // stop monitoring
255/// drop(guard);
256/// ```
257#[derive(Debug)]
258pub struct ProcessGuardBuilder {
259    directory_permissions: Permission,
260    guard_permissions: Permission,
261}
262
263impl Default for ProcessGuardBuilder {
264    fn default() -> Self {
265        Self::new()
266    }
267}
268
269impl ProcessGuardBuilder {
270    /// Creates a new instance
271    pub fn new() -> Self {
272        Self {
273            directory_permissions: Permission::OWNER_ALL
274                | Permission::GROUP_READ
275                | Permission::GROUP_EXEC
276                | Permission::OTHERS_READ
277                | Permission::OTHERS_EXEC,
278            guard_permissions: Permission::OWNER_ALL,
279        }
280    }
281
282    /// Defines the [`Directory`] [`Permission`]s of the [`Directory`] that
283    /// will be created when the [`Directory`] from the provided [`FilePath`]
284    /// does not exist.
285    pub fn directory_permissions(mut self, value: Permission) -> Self {
286        self.directory_permissions = value;
287        self
288    }
289
290    /// Defines the [`Permission`]s of the [`ProcessGuard`].
291    pub fn guard_permissions(mut self, value: Permission) -> Self {
292        self.guard_permissions = value;
293        self
294    }
295
296    /// Creates a new [`ProcessGuard`]. As soon as it is created successfully another process can
297    /// monitor the state of the process. One cannot create multiple [`ProcessGuard`]s that use the
298    /// same `path`. But one can create multiple [`ProcessGuard`]s that are using different
299    /// `path`s.
300    ///
301    /// ```
302    /// # extern crate iceoryx2_loggers;
303    ///
304    /// use iceoryx2_bb_posix::process_state::*;
305    ///
306    /// let process_state_path = FilePath::new(b"process_state_file").unwrap();
307    ///
308    /// // start monitoring from this point on
309    /// let guard = ProcessGuardBuilder::new().create(&process_state_path).expect("");
310    /// ```
311    pub fn create(self, path: &FilePath) -> Result<ProcessGuard, ProcessGuardCreateError> {
312        let origin = "ProcessGuard::new()";
313        let msg = format!("Unable to create new ProcessGuard with the file \"{path}\"");
314
315        let owner_lock_path = match generate_owner_lock_path(path) {
316            Ok(f) => f,
317            Err(e) => {
318                fail!(from origin, with ProcessGuardCreateError::InvalidCleanerPathName,
319                "{} since the corresponding owner_lock path name would be invalid ({:?}).", msg, e);
320            }
321        };
322
323        fail!(from origin, when Self::create_directory(path, self.directory_permissions),
324            "{} since the directory \"{}\" of the process guard could not be created", msg, path);
325
326        let owner_lock_file = fail!(from origin, when Self::create_file(&owner_lock_path, self.guard_permissions),
327                                    "{} since the owner_lock file \"{}\" could not be created.", msg, owner_lock_path);
328        let mut file = fail!(from origin, when Self::create_file(path, INIT_PERMISSION),
329                                "{} since the state file \"{}\" could not be created.", msg, path);
330
331        match Self::lock_state_file(&file) {
332            Ok(()) => (),
333            Err(lock_error) => match lock_error {
334                ProcessGuardLockError::Interrupt => {
335                    fail!(from origin, with ProcessGuardCreateError::Interrupt,
336                            "{} since an interrupt signal was received while locking the file.", msg);
337                }
338                ProcessGuardLockError::OwnedByAnotherProcess => {
339                    fail!(from origin, with ProcessGuardCreateError::ContractViolation,
340                            "{} since the another process holds the lock of a process state that is in initialization.", msg);
341                }
342                ProcessGuardLockError::UnknownError(v) => {
343                    fail!(from origin, with ProcessGuardCreateError::UnknownError(v),
344                            "{} since an unknown failure occurred while locking the file ({:?}).", msg, v);
345                }
346            },
347        };
348
349        match file.set_permission(self.guard_permissions) {
350            Ok(_) => {
351                trace!(from "ProcessGuard::new()", "create process state \"{}\" for monitoring", path);
352                Ok(ProcessGuard {
353                    file,
354                    owner_lock_file,
355                })
356            }
357            Err(v) => {
358                fail!(from origin, with ProcessGuardCreateError::UnknownError(0),
359                    "{} since the final permissions could not be applied due to an internal failure ({:?}).", msg, v);
360            }
361        }
362    }
363
364    fn create_directory(
365        path: &FilePath,
366        permissions: Permission,
367    ) -> Result<(), ProcessGuardCreateError> {
368        let origin = "ProcessGuard::create_directory()";
369        let msg = format!(
370            "Unable to create directory \"{}\" for new ProcessGuard state with the file \"{}\"",
371            path.path(),
372            path
373        );
374
375        let dir_path = path.path();
376
377        if dir_path.is_empty() {
378            return Ok(());
379        }
380
381        match Directory::does_exist(&dir_path) {
382            Ok(true) => Ok(()),
383            Ok(false) => match Directory::create(&dir_path, permissions) {
384                Ok(_) | Err(DirectoryCreateError::DirectoryAlreadyExists) => Ok(()),
385                Err(DirectoryCreateError::InsufficientPermissions) => {
386                    fail!(from origin, with ProcessGuardCreateError::InsufficientPermissions,
387                    "{} since the directory {} could not be created due to insufficient permissions.",
388                    msg, dir_path);
389                }
390                Err(DirectoryCreateError::ReadOnlyFilesystem) => {
391                    fail!(from origin, with ProcessGuardCreateError::ReadOnlyFilesystem,
392                    "{} since the directory {} could not be created since it is located on an read-only file system.",
393                    msg, dir_path);
394                }
395                Err(DirectoryCreateError::NoSpaceLeft) => {
396                    fail!(from origin, with ProcessGuardCreateError::NoSpaceLeft,
397                    "{} since the directory {} could not be created since there is no space left.",
398                    msg, dir_path);
399                }
400                Err(v) => {
401                    fail!(from origin, with ProcessGuardCreateError::NoSpaceLeft,
402                    "{} since the directory {} could not be created due to an unknown failure ({:?}).",
403                    msg, dir_path, v);
404                }
405            },
406            Err(DirectoryAccessError::InsufficientPermissions) => {
407                fail!(from origin, with ProcessGuardCreateError::InsufficientPermissions,
408                    "{} since the directory {} could not be accessed due to insufficient permissions.",
409                    msg, dir_path);
410            }
411            Err(DirectoryAccessError::PathPrefixIsNotADirectory) => {
412                fail!(from origin, with ProcessGuardCreateError::InvalidDirectory,
413                    "{} since the directory {} is actually not a valid directory.", msg, dir_path);
414            }
415            Err(v) => {
416                fail!(from origin, with ProcessGuardCreateError::UnknownError(0),
417                    "{} since an unknown failure occurred ({:?}) while checking if directory {} exists.",
418                    msg, v, dir_path);
419            }
420        }
421    }
422
423    fn create_file(
424        path: &FilePath,
425        permission: Permission,
426    ) -> Result<File, ProcessGuardCreateError> {
427        let origin = "ProcessGuard::file()";
428        let msg = format!("Unable to create new ProcessGuard state file \"{path}\"");
429
430        match FileBuilder::new(path)
431            .has_ownership(true)
432            .creation_mode(CreationMode::CreateExclusive)
433            .permission(permission)
434            .create()
435        {
436            Ok(f) => Ok(f),
437            Err(FileCreationError::InsufficientPermissions) => {
438                fail!(from origin, with ProcessGuardCreateError::InsufficientPermissions,
439                    "{} due to insufficient permissions.", msg);
440            }
441            Err(FileCreationError::FileAlreadyExists) => {
442                fail!(from origin, with ProcessGuardCreateError::AlreadyExists,
443                    "{} since the underlying file already exists.", msg);
444            }
445            Err(FileCreationError::IsDirectory) => {
446                fail!(from origin, with ProcessGuardCreateError::IsDirectory,
447                    "{} since the path is a directory.", msg);
448            }
449            Err(FileCreationError::NoSpaceLeft) => {
450                fail!(from origin, with ProcessGuardCreateError::NoSpaceLeft,
451                    "{} since there is no space left on the device.", msg);
452            }
453            Err(FileCreationError::FilesytemIsReadOnly) => {
454                fail!(from origin, with ProcessGuardCreateError::ReadOnlyFilesystem,
455                    "{} since the file system is read only.", msg);
456            }
457            Err(v) => {
458                fail!(from origin, with ProcessGuardCreateError::UnknownError(0),
459                    "{} due to an internal failure ({:?}).", msg, v);
460            }
461        }
462    }
463
464    fn lock_state_file(file: &File) -> Result<(), ProcessGuardLockError> {
465        let msg = format!("Unable to lock process state file {file:?}");
466        let mut new_lock_state = posix::flock::new_zeroed();
467        new_lock_state.l_type = LockType::Write as _;
468        new_lock_state.l_whence = posix::SEEK_SET as _;
469
470        if unsafe {
471            posix::fcntl(
472                file.file_descriptor().native_handle(),
473                posix::F_SETLK,
474                &mut new_lock_state,
475            )
476        } != -1
477        {
478            return Ok(());
479        }
480
481        handle_errno!(ProcessGuardLockError, from "ProcessState::lock_state_file()",
482            Errno::EACCES => (OwnedByAnotherProcess, "{} since the lock is owned by another process.", msg),
483            Errno::EAGAIN => (OwnedByAnotherProcess, "{} since the lock is owned by another process.", msg),
484            Errno::EINTR => (Interrupt, "{} since an interrupt signal was received.", msg),
485            v => (UnknownError(v as i32), "{} due to an unknown failure (errno code: {}).", msg, v)
486        );
487    }
488}
489
490/// A guard for a process that makes the process monitorable by a [`ProcessMonitor`] as long as it
491/// is in scope. When it goes out of scope the process is no longer monitorable.
492///
493/// ```
494/// # extern crate iceoryx2_loggers;
495/// use iceoryx2_bb_posix::process_state::*;
496///
497/// let process_state_path = FilePath::new(b"process_state_file").unwrap();
498///
499/// // start monitoring from this point on
500/// let guard = ProcessGuardBuilder::new()
501///     .create(&process_state_path).expect("");
502///
503/// // normal application code
504///
505/// // stop monitoring
506/// drop(guard);
507/// ```
508#[derive(Debug)]
509pub struct ProcessGuard {
510    file: File,
511    owner_lock_file: File,
512}
513
514const INIT_PERMISSION: Permission = Permission::OWNER_WRITE;
515const OWNER_LOCK_SUFFIX: &[u8] = b"_owner_lock";
516
517fn generate_owner_lock_path(path: &FilePath) -> Result<FilePath, SemanticStringError> {
518    let mut owner_lock_path = *path;
519    owner_lock_path.push_bytes(OWNER_LOCK_SUFFIX)?;
520    Ok(owner_lock_path)
521}
522
523impl ProcessGuard {
524    /// Removes by force an existing [`ProcessGuard`]. This is useful when stale resources of
525    /// a dead process need to be cleaned up.
526    ///
527    /// # Safety
528    ///
529    ///  - Users must ensure that no [`Process`](crate::process::Process) currently has
530    ///    an instance of [`ProcessGuard`], [`ProcessCleaner`] and [`ProcessMonitor`] that
531    ///    will be removed.
532    pub unsafe fn remove(file: &FilePath) -> Result<bool, FileRemoveError> {
533        let msg = "Unable to remove process guard resources";
534        let origin = "ProcessGuard::remove()";
535        let owner_lock_path = match generate_owner_lock_path(file) {
536            Ok(v) => v,
537            Err(e) => {
538                fail!(from origin,
539                    with FileRemoveError::MaxSupportedPathLengthExceeded,
540                    "{msg} since the owner lock path exceeds the maximum supported path length ({e:?})."
541                );
542            }
543        };
544
545        let mut result = match File::remove(file) {
546            Ok(v) => v,
547            Err(e) => {
548                fail!(from origin, with e,
549                "{msg} since the underlying file \"{file}\" could not be removed.");
550            }
551        };
552
553        result &= match File::remove(&owner_lock_path) {
554            Ok(v) => v,
555            Err(e) => {
556                fail!(from origin, with e,
557                "{msg} since the underlying owner lock file \"{owner_lock_path}\" could not be removed.");
558            }
559        };
560
561        Ok(result)
562    }
563
564    /// Returns the [`FilePath`] under which the underlying file is stored.
565    pub fn path(&self) -> &FilePath {
566        match self.file.path() {
567            Some(path) => path,
568            None => {
569                unreachable!()
570            }
571        }
572    }
573
574    pub(crate) fn staged_death(self) {
575        self.file.release_ownership();
576        self.owner_lock_file.release_ownership();
577    }
578}
579
580/// Monitor processes that have created a [`ProcessGuard`]. If the process dies, shutdowns or is
581/// alive the monitor will detect it.
582///
583/// # Example
584///
585/// ```
586/// # extern crate iceoryx2_loggers;
587///
588/// use iceoryx2_bb_posix::process_state::*;
589///
590/// let process_state_path = FilePath::new(b"process_state_file").unwrap();
591///
592/// let mut monitor = ProcessMonitor::new(&process_state_path).unwrap();
593///
594/// match monitor.state().expect("") {
595///     // Process is alive and well
596///     ProcessState::Alive => (),
597///
598///     // The process state file is created, this should state should persist only a very small
599///     // fraction of time
600///     ProcessState::Starting => (),
601///
602///     // Process died, we have to inform other interested parties and maybe cleanup some
603///     // resources
604///     ProcessState::Dead => {
605///         // monitored process crashed, perform cleanup
606///
607///         match ProcessCleaner::new(&process_state_path) {
608///             Ok(guard) => {
609///                 // we own the old resources of the abnormally terminated process
610///                 // this is the place where we remove them
611///             }
612///             Err(ProcessCleanerCreateError::OwnedByAnotherProcess) => {
613///                 // Some other process is cleaning up all resources
614///             }
615///             Err(e) => {
616///                 // custom error handling
617///             },
618///         };
619///     },
620///
621///     // Process dies and another process is performing the cleanup currently
622///     ProcessState::CleaningUp => (),
623///
624///     // The monitored process does not exist, maybe it did not yet start or already performed
625///     // a clean shutdown.
626///     ProcessState::DoesNotExist => (),
627/// }
628/// ```
629pub struct ProcessMonitor {
630    file: Cell<Option<File>>,
631    path: FilePath,
632    owner_lock_path: FilePath,
633}
634
635impl Debug for ProcessMonitor {
636    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
637        write!(
638            f,
639            "ProcessMonitor {{ file = {:?}, path = {:?}}}",
640            unsafe { &*self.file.as_ptr() },
641            self.path
642        )
643    }
644}
645
646impl ProcessMonitor {
647    /// Creates a new [`ProcessMonitor`] that can obtain the state of the process that will be
648    /// monitored.
649    ///
650    /// # Example
651    ///
652    /// ```
653    /// # extern crate iceoryx2_loggers;
654    ///
655    /// use iceoryx2_bb_posix::process_state::*;
656    ///
657    /// let process_state_path = FilePath::new(b"process_state_file").unwrap();
658    ///
659    /// let mut monitor = ProcessMonitor::new(&process_state_path).expect("");
660    /// ```
661    pub fn new(path: &FilePath) -> Result<Self, ProcessMonitorCreateError> {
662        let msg = format!("Unable to open process monitor \"{path}\"");
663        let origin = "ProcessMonitor::new()";
664        let owner_lock_path = match generate_owner_lock_path(path) {
665            Ok(f) => f,
666            Err(e) => {
667                fail!(from origin, with ProcessMonitorCreateError::InvalidCleanerPathName,
668                "{} since the corresponding owner_lock path name would be invalid ({:?}).", msg, e);
669            }
670        };
671
672        let new_self = Self {
673            file: Cell::new(None),
674            path: *path,
675            owner_lock_path,
676        };
677
678        new_self.file.set(Self::open_file(&new_self.path)?);
679        Ok(new_self)
680    }
681
682    /// Returns the path of the underlying file of the [`ProcessGuard`].
683    pub fn path(&self) -> &FilePath {
684        &self.path
685    }
686
687    /// Returns the current state of the process that is monitored.
688    ///
689    /// # Example
690    ///
691    /// ```
692    /// # extern crate iceoryx2_loggers;
693    ///
694    /// use iceoryx2_bb_posix::process_state::*;
695    ///
696    /// let process_state_path = FilePath::new(b"process_state_file").unwrap();
697    ///
698    /// let mut monitor = ProcessMonitor::new(&process_state_path).expect("");
699    ///
700    /// match monitor.state().expect("") {
701    ///     // Process is alive and well
702    ///     ProcessState::Alive => (),
703    ///
704    ///     // The process state file is created, this should state should persist only a very small
705    ///     // fraction of time
706    ///     ProcessState::Starting => (),
707    ///
708    ///     // Process died, we have to inform other interested parties and maybe cleanup some
709    ///     // resources
710    ///     ProcessState::Dead => (),
711    ///
712    ///     // The monitored process does not exist, maybe it did not yet start or already performed
713    ///     // a clean shutdown.
714    ///     ProcessState::DoesNotExist => (),
715    ///
716    ///     // The monitored process crashed and another process acquired the [`ProcessCleaner`]
717    ///     // to remove its remaining resources
718    ///     ProcessState::CleaningUp => (),
719    /// }
720    /// ```
721    pub fn state(&self) -> Result<ProcessState, ProcessMonitorStateError> {
722        let msg = "Unable to acquire ProcessState";
723        match unsafe { &*self.file.as_ptr() } {
724            Some(_) => self.read_state_from_file(),
725            None => match File::does_exist(&self.path) {
726                Ok(true) => {
727                    self.file.set(Self::open_file(&self.path)?);
728                    self.read_state_from_file()
729                }
730                Ok(false) => Ok(ProcessState::DoesNotExist),
731                Err(v) => {
732                    fail!(from self, with ProcessMonitorStateError::UnknownError(0),
733                        "{} since an unknown failure occurred while checking if the file exists ({:?}).", msg, v);
734                }
735            },
736        }
737    }
738
739    fn get_lock_state(file: &File) -> Result<i64, ProcessMonitorStateError> {
740        let msg = format!("Unable to acquire lock on file {file:?}");
741        let mut current_state = posix::flock::new_zeroed();
742        current_state.l_type = LockType::Write as _;
743
744        if unsafe {
745            posix::fcntl(
746                file.file_descriptor().native_handle(),
747                posix::F_GETLK,
748                &mut current_state,
749            )
750        } == -1
751        {
752            handle_errno!(ProcessMonitorStateError, from "ProcessMonitor::get_lock_state()",
753                Errno::EINTR => (Interrupt, "{} since an interrupt signal was received.", msg),
754                v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
755            )
756        }
757
758        Ok(current_state.l_type as _)
759    }
760
761    fn read_state_from_file(&self) -> Result<ProcessState, ProcessMonitorStateError> {
762        let file = match unsafe { &*self.file.as_ptr() } {
763            Some(ref f) => f,
764            None => return Ok(ProcessState::Starting),
765        };
766
767        let msg = format!("Unable to read state from file {file:?}");
768        let lock_state = fail!(from self, when Self::get_lock_state(file),
769                            "{} since the lock state of the state file could not be acquired.", msg);
770
771        match lock_state as _ {
772            posix::F_WRLCK => Ok(ProcessState::Alive),
773            _ => match File::does_exist(&self.path) {
774                Ok(true) => match file.permission() {
775                    Ok(INIT_PERMISSION) => Ok(ProcessState::Starting),
776                    Err(_) | Ok(_) => {
777                        self.file.set(None);
778                        match Self::open_file(&self.owner_lock_path)? {
779                            Some(f) => {
780                                let lock_state = fail!(from self, when Self::get_lock_state(&f),
781                                                "{} since the lock state of the owner_lock file could not be acquired.", msg);
782                                if lock_state == posix::F_WRLCK as _ {
783                                    return Ok(ProcessState::CleaningUp);
784                                }
785
786                                Ok(ProcessState::Dead)
787                            }
788                            None => match File::does_exist(&self.path) {
789                                Ok(true) => {
790                                    fail!(from self, with ProcessMonitorStateError::CorruptedState,
791                                    "{} since the corresponding owner_lock file \"{}\" does not exist. This indicates a corrupted state.",
792                                    msg, self.owner_lock_path);
793                                }
794                                Ok(false) => {
795                                    self.file.set(None);
796                                    Ok(ProcessState::DoesNotExist)
797                                }
798                                Err(v) => {
799                                    fail!(from self, with ProcessMonitorStateError::UnknownError(0),
800                                        "{} since an unknown failure occurred while checking if the process state file exists ({:?}).", msg, v);
801                                }
802                            },
803                        }
804                    }
805                },
806                Ok(false) => {
807                    self.file.set(None);
808                    Ok(ProcessState::DoesNotExist)
809                }
810                Err(v) => {
811                    fail!(from self, with ProcessMonitorStateError::UnknownError(0),
812                            "{} since an unknown failure occurred while checking if the process state file exists ({:?}).", msg, v);
813                }
814            },
815        }
816    }
817
818    fn open_file(path: &FilePath) -> Result<Option<File>, ProcessMonitorCreateError> {
819        let origin = "ProcessMonitor::new()";
820        let msg = format!("Unable to open ProcessMonitor state file \"{path}\"");
821
822        match FileBuilder::new(path).open_existing(AccessMode::Write) {
823            Ok(f) => Ok(Some(f)),
824            Err(FileOpenError::FileDoesNotExist) => Ok(None),
825            Err(FileOpenError::IsDirectory) => {
826                fail!(from origin, with ProcessMonitorCreateError::IsDirectory,
827                    "{} since the path is a directory.", msg);
828            }
829            Err(FileOpenError::InsufficientPermissions) => {
830                fail!(from origin, with ProcessMonitorCreateError::InsufficientPermissions,
831                    "{} due to insufficient permissions.", msg);
832            }
833            Err(FileOpenError::Interrupt) => {
834                fail!(from origin, with ProcessMonitorCreateError::Interrupt,
835                    "{} since an interrupt signal was received.", msg);
836            }
837            Err(v) => {
838                fail!(from origin, with ProcessMonitorCreateError::UnknownError,
839                    "{} since an unknown failure occurred ({:?}).", msg, v);
840            }
841        }
842    }
843}
844
845/// A guard for the remains of an abnormal terminated process. The instance that owns the
846/// [`ProcessCleaner`] is allowed to cleanup all resources - no one else is. When it goes out of
847/// scope it will remove all state files that were created with [`ProcessGuard`].
848/// When the process that owns the [`ProcessCleaner`] terminates abnormally as well, the
849/// [`ProcessCleaner`] guard can be acquired by another process again.
850///
851/// ```no_run
852/// # extern crate iceoryx2_loggers;
853///
854/// use iceoryx2_bb_posix::process_state::*;
855///
856/// let process_state_path = FilePath::new(b"process_state_file").unwrap();
857///
858/// match ProcessCleaner::new(&process_state_path) {
859///     Ok(guard) => {/* cleanup all process resources */},
860///     Err(_) => (),
861/// }
862/// ```
863#[derive(Debug)]
864pub struct ProcessCleaner {
865    file: File,
866    owner_lock_file: File,
867}
868
869impl ProcessCleaner {
870    /// Creates a new [`ProcessCleaner`]. Succeeds when the process that creates the state files
871    /// with the [`ProcessGuard`] died an no other process has acquired the resources for cleanup
872    /// with [`ProcessCleaner::new()`].
873    pub fn new(path: &FilePath) -> Result<Self, ProcessCleanerCreateError> {
874        let msg = format!("Unable to instantiate ProcessCleaner \"{path}\"");
875        let origin = "ProcessCleaner::new()";
876        let owner_lock_path = match generate_owner_lock_path(path) {
877            Ok(f) => f,
878            Err(e) => {
879                fail!(from origin, with ProcessCleanerCreateError::InvalidCleanerPathName,
880                "{} since the corresponding owner_lock path name would be invalid ({:?}).", msg, e);
881            }
882        };
883
884        let owner_lock_file = match fail!(from origin, when ProcessMonitor::open_file(&owner_lock_path),
885            with ProcessCleanerCreateError::UnableToOpenCleanerFile,
886            "{} since the owner_lock file could not be opened.", msg)
887        {
888            Some(f) => f,
889            None => {
890                fail!(from origin, with ProcessCleanerCreateError::DoesNotExist,
891                "{} since the process owner_lock file does not exist.", msg);
892            }
893        };
894
895        let file = match fail!(from origin, when ProcessMonitor::open_file(path),
896            with ProcessCleanerCreateError::UnableToOpenStateFile,
897            "{} since the state file could not be opened.", msg)
898        {
899            Some(f) => f,
900            None => {
901                fail!(from origin, with ProcessCleanerCreateError::DoesNotExist,
902                "{} since the process state file does not exist.", msg);
903            }
904        };
905
906        let lock_state = fail!(from origin, when ProcessMonitor::get_lock_state(&file),
907            with ProcessCleanerCreateError::FailedToAcquireLockState,
908            "{} since the lock state could not be acquired.", msg);
909
910        if lock_state == posix::F_WRLCK as _ {
911            fail!(from origin, with ProcessCleanerCreateError::ProcessIsStillAlive,
912                "{} since the corresponding process is still alive.", msg);
913        }
914
915        match ProcessGuardBuilder::lock_state_file(&owner_lock_file) {
916            Ok(()) => {
917                file.acquire_ownership();
918                owner_lock_file.acquire_ownership();
919                Ok(Self {
920                    file,
921                    owner_lock_file,
922                })
923            }
924            Err(ProcessGuardLockError::OwnedByAnotherProcess) => {
925                fail!(from origin, with ProcessCleanerCreateError::OwnedByAnotherProcess,
926                    "{} since another process already has instantiated a ProcessCleaner.", msg);
927            }
928            Err(ProcessGuardLockError::Interrupt) => {
929                fail!(from origin, with ProcessCleanerCreateError::Interrupt,
930                    "{} since an interrupt signal was received.", msg);
931            }
932            Err(e) => {
933                fail!(from origin, with ProcessCleanerCreateError::UnknownError,
934                    "{} due to an unknown failure ({:?}).", msg, e);
935            }
936        }
937    }
938
939    /// Abandons the [`ProcessCleaner`] without removing the underlying resources. This is useful
940    /// when another process tried to cleanup the stale resources of the dead process but is unable
941    /// to due to insufficient permissions.
942    pub fn abandon(self) {
943        self.file.release_ownership();
944        self.owner_lock_file.release_ownership();
945    }
946}