// SPDX-License-Identifier: MIT // // Copyright IBM Corp. 2023 // //! Collection of helper functions for pvapconfig // use regex::Regex; use std::error::Error; use std::fs; use std::fs::OpenOptions; use std::io::Write; use std::path::PathBuf; pub const PATH_PVAPCONFIG_LOCK: &str = "/run/lock/pvapconfig.lock"; /// For a given (sysfs) directory construct a list of all subdirs /// and give it back as a vector of strings. If there is no subdir, /// the vector is empty. pub fn sysfs_get_list_of_subdirs(dname: &str) -> Result, Box> { let mut v: Vec = Vec::new(); let entries = fs::read_dir(dname)?; for entry in entries.flatten() { let file_type = match entry.file_type() { Ok(ft) => ft, _ => continue, }; if !file_type.is_dir() { continue; } let fname = match entry.file_name().into_string() { Ok(s) => s, _ => continue, }; v.push(fname); } Ok(v) } /// For a given (sysfs) directory construct a list of all subdirs which /// match to the given regular expression and give the list back as a /// vector of strings. If there is no subdir, the vector is empty. pub fn sysfs_get_list_of_subdirs_matching_regex( dname: &str, regex: &str, ) -> Result, Box> { let mut v: Vec = Vec::new(); let re = Regex::new(regex)?; let entries = sysfs_get_list_of_subdirs(dname)?; for entry in entries { if re.is_match(&entry) { v.push(entry); } } Ok(v) } /// LockFile for inter-process locking /// /// Simple class for process locking for pvapconfig. /// The lock concept is simple: The existence of a file is used as the /// locking indicator. If the file exists something is locked, if it does /// not exist something is not locked. In the lock file the PID of the /// process created the file ("owning this file") is written in. /// With the ProcessLock object leaving scope the associated lock file /// is automatically deleted. It is assumed that the creation of a file /// is an atomic operation - that's true for most filesystems but may /// cause problems with network based filesystems. /// Example: /// ``` /// let lock = LockFile::lock("/var/lock/process.lock"); /// assert!(lock.is_ok()); /// let lock2 = LockFile::lock("/var/lock/process.lock"); /// assert!(lock2.is_err()); /// drop(lock); /// let lock3 = LockFile::lock("/var/lock/process.lock"); /// assert!(lock3.is_ok()); /// ``` #[derive(Debug)] pub struct LockFile { lockfile: PathBuf, } impl LockFile { /// Try to establish the lock file. /// Upon success the given file is fresh created and has the pid of this /// process written in. The function returns a new LockFile object /// which has implemented the Drop Trait. So with this object going out /// of scope the lock file is deleted. If establishing the lock file /// fails for any reason (for example the file already exists), the /// function fails with returning an Error string. This function does /// NOT panic if establishing the lock file fails for any reason. If /// the lock file could be esablished but writing in the PID fails, a /// warning is printed but the function continues with returning a /// LockFile object. pub fn try_lock(fname: &str) -> Result { let lockfile = PathBuf::from(fname); let mut file = match OpenOptions::new() .write(true) .create_new(true) .open(&lockfile) { Err(err) => { return Err(format!( "Failure trying to create lock file {fname}: {err:?}." )) } Ok(f) => f, }; let _ = file .write(format!("{}", std::process::id()).as_bytes()) .map_err(|err| { println!("Warning: could not write PID into lockfile {fname}: {err:?}.") }); Ok(Self { lockfile }) } } impl Drop for LockFile { fn drop(&mut self) { let _ = fs::remove_file(&self.lockfile).map_err(|err| { println!( "Warning: could not remove lockfile {}: {err:?}.", self.lockfile.display() ) }); } } #[cfg(test)] mod tests { use super::*; use utils::TemporaryDirectory; // Only very simple tests #[test] fn test_sysfs_get_list_of_subdirs() { let r = sysfs_get_list_of_subdirs("/proc/self"); assert!(r.is_ok()); let v = r.unwrap(); assert!(!v.is_empty()); } #[test] fn test_sysfs_get_list_of_subdirs_matching_regex() { let r = sysfs_get_list_of_subdirs_matching_regex("/proc/self", "fd.*"); assert!(r.is_ok()); let v = r.unwrap(); assert!(!v.is_empty()); for e in v { assert!(e.strip_prefix("fd").is_some()); } } #[test] fn test_lockfile() { let temp_dir = TemporaryDirectory::new().expect("creating a temporary directory should work"); let file_path = temp_dir.path().join("my.lock"); let file_path_str = file_path.to_str().expect("should work"); let r1 = LockFile::try_lock(file_path_str); assert!(r1.is_ok()); let r2 = LockFile::try_lock(file_path_str); assert!(r2.is_err()); drop(r1); let r3 = LockFile::try_lock(file_path_str); assert!(r3.is_ok()); } }