ic_backup/ops/persistence/journal_lock/
mod.rs1use std::{fs, io, path::Path, path::PathBuf};
7
8use thiserror::Error as ThisError;
9
10#[derive(Debug, ThisError)]
17pub enum JournalLockError {
18 #[error("filesystem authority is locked: {lock_path}")]
20 Locked {
21 lock_path: String,
23 },
24
25 #[error("unsafe lock entry at {lock_path}: {kind}")]
27 UnsafeEntry {
28 lock_path: String,
30 kind: String,
32 },
33
34 #[error(transparent)]
36 Io(#[from] io::Error),
37}
38
39#[derive(Debug)]
46pub struct JournalLock {
47 journal: PathBuf,
48 #[cfg(unix)]
49 file: fs::File,
50}
51
52impl JournalLock {
53 #[must_use]
55 pub fn journal(&self) -> &Path {
56 &self.journal
57 }
58
59 pub fn acquire(journal_path: &Path) -> Result<Self, JournalLockError> {
64 let name = journal_path
65 .file_name()
66 .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput))?;
67 let parent = journal_path
68 .parent()
69 .filter(|parent| !parent.as_os_str().is_empty())
70 .unwrap_or_else(|| Path::new("."));
71 let journal = parent.canonicalize()?.join(name);
73 let path = journal_lock_path(&journal);
74
75 #[cfg(unix)]
76 {
77 acquire_supported(&path, journal)
78 }
79
80 #[cfg(not(unix))]
81 {
82 Err(io::Error::new(
83 io::ErrorKind::Unsupported,
84 format!(
85 "journal locking is unsupported on this host: {}",
86 path.display()
87 ),
88 )
89 .into())
90 }
91 }
92}
93
94#[cfg(unix)]
95impl Drop for JournalLock {
96 fn drop(&mut self) {
97 let _ = ic_host_fs::durable::unlock(&self.file);
98 }
99}
100
101#[cfg(unix)]
102fn acquire_supported(path: &Path, journal: PathBuf) -> Result<JournalLock, JournalLockError> {
103 use ic_host_fs::durable::{
104 RegularFileLockError, open_private_regular_lock_file, try_lock_exclusive,
105 };
106 let file = open_private_regular_lock_file(path).map_err(|error| match error {
107 RegularFileLockError::NotRegular => JournalLockError::UnsafeEntry {
108 lock_path: path.to_string_lossy().to_string(),
109 kind: match fs::symlink_metadata(path) {
110 Ok(metadata) if metadata.file_type().is_symlink() => "Symlink",
111 Ok(metadata) if metadata.is_dir() => "Directory",
112 _ => "Special",
113 }
114 .to_owned(),
115 },
116 error => JournalLockError::Io(error.into()),
117 })?;
118 try_lock_exclusive(&file).map_err(|error| {
119 if error.kind() == io::ErrorKind::WouldBlock {
120 JournalLockError::Locked {
121 lock_path: path.to_string_lossy().to_string(),
122 }
123 } else {
124 JournalLockError::Io(error)
125 }
126 })?;
127 Ok(JournalLock { journal, file })
128}
129
130fn journal_lock_path(path: &Path) -> PathBuf {
131 let mut lock_path = path.as_os_str().to_os_string();
132 lock_path.push(".lock");
133 PathBuf::from(lock_path)
134}
135
136#[cfg(test)]
137mod tests;