c29f0fb05f54a86d559005ccbacc79a4a659eb7a
[rust-lightning] / lightning-persister / src / fs_store.rs
1 //! Objects related to [`FilesystemStore`] live here.
2 use lightning::util::persist::KVStore;
3 use lightning::util::string::PrintableString;
4
5 use std::collections::HashMap;
6 use std::fs;
7 use std::io::{BufReader, Read, Write};
8 use std::path::{Path, PathBuf};
9 use std::sync::{Arc, Mutex, RwLock};
10
11 #[cfg(not(target_os = "windows"))]
12 use std::os::unix::io::AsRawFd;
13
14 #[cfg(target_os = "windows")]
15 use {std::ffi::OsStr, std::os::windows::ffi::OsStrExt};
16
17 #[cfg(target_os = "windows")]
18 macro_rules! call {
19         ($e: expr) => {
20                 if $e != 0 {
21                         return Ok(());
22                 } else {
23                         return Err(std::io::Error::last_os_error());
24                 }
25         };
26 }
27
28 #[cfg(target_os = "windows")]
29 fn path_to_windows_str<T: AsRef<OsStr>>(path: T) -> Vec<u16> {
30         path.as_ref().encode_wide().chain(Some(0)).collect()
31 }
32
33 /// A [`KVStore`] implementation that writes to and reads from the file system.
34 pub struct FilesystemStore {
35         data_dir: PathBuf,
36         locks: Mutex<HashMap<(String, String), Arc<RwLock<()>>>>,
37 }
38
39 impl FilesystemStore {
40         /// Constructs a new [`FilesystemStore`].
41         pub fn new(data_dir: PathBuf) -> Self {
42                 let locks = Mutex::new(HashMap::new());
43                 Self { data_dir, locks }
44         }
45
46         /// Returns the data directory.
47         pub fn get_data_dir(&self) -> PathBuf {
48                 self.data_dir.clone()
49         }
50 }
51
52 impl KVStore for FilesystemStore {
53         type Reader = FilesystemReader;
54
55         fn read(&self, namespace: &str, key: &str) -> std::io::Result<Self::Reader> {
56                 if key.is_empty() {
57                         let msg = format!("Failed to read {}/{}: key may not be empty.",
58                                 PrintableString(namespace), PrintableString(key));
59                         return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
60                 }
61
62                 if namespace.chars().any(|c| !c.is_ascii() || c.is_control()) ||
63                         key.chars().any(|c| !c.is_ascii() || c.is_control()) {
64                         debug_assert!(false, "Failed to read {}/{}: namespace and key must be valid ASCII
65                                 strings.", PrintableString(namespace), PrintableString(key));
66                         let msg = format!("Failed to read {}/{}: namespace and key must be valid ASCII strings.",
67                                 PrintableString(namespace), PrintableString(key));
68                         return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
69                 }
70
71                 let mut outer_lock = self.locks.lock().unwrap();
72                 let lock_key = (namespace.to_string(), key.to_string());
73                 let inner_lock_ref = Arc::clone(&outer_lock.entry(lock_key).or_default());
74
75                 let mut dest_file_path = self.data_dir.clone();
76                 dest_file_path.push(namespace);
77                 dest_file_path.push(key);
78                 FilesystemReader::new(dest_file_path, inner_lock_ref)
79         }
80
81         fn write(&self, namespace: &str, key: &str, buf: &[u8]) -> std::io::Result<()> {
82                 if key.is_empty() {
83                         let msg = format!("Failed to write {}/{}: key may not be empty.",
84                                 PrintableString(namespace), PrintableString(key));
85                         return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
86                 }
87
88                 if namespace.chars().any(|c| !c.is_ascii() || c.is_control()) ||
89                         key.chars().any(|c| !c.is_ascii() || c.is_control()) {
90                         debug_assert!(false, "Failed to write {}/{}: namespace and key must be valid ASCII
91                                 strings.", PrintableString(namespace), PrintableString(key));
92                         let msg = format!("Failed to write {}/{}: namespace and key must be valid ASCII strings.",
93                                 PrintableString(namespace), PrintableString(key));
94                         return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
95                 }
96
97                 let mut outer_lock = self.locks.lock().unwrap();
98                 let lock_key = (namespace.to_string(), key.to_string());
99                 let inner_lock_ref = Arc::clone(&outer_lock.entry(lock_key).or_default());
100                 let _guard = inner_lock_ref.write().unwrap();
101
102                 let mut dest_file_path = self.data_dir.clone();
103                 dest_file_path.push(namespace);
104                 dest_file_path.push(key);
105
106                 let parent_directory = dest_file_path
107                         .parent()
108                         .ok_or_else(|| {
109                                 let msg =
110                                         format!("Could not retrieve parent directory of {}.", dest_file_path.display());
111                                 std::io::Error::new(std::io::ErrorKind::InvalidInput, msg)
112                         })?
113                         .to_path_buf();
114                 fs::create_dir_all(&parent_directory)?;
115
116                 // Do a crazy dance with lots of fsync()s to be overly cautious here...
117                 // We never want to end up in a state where we've lost the old data, or end up using the
118                 // old data on power loss after we've returned.
119                 // The way to atomically write a file on Unix platforms is:
120                 // open(tmpname), write(tmpfile), fsync(tmpfile), close(tmpfile), rename(), fsync(dir)
121                 let mut tmp_file_path = dest_file_path.clone();
122                 tmp_file_path.set_extension("tmp");
123
124                 {
125                         let mut tmp_file = fs::File::create(&tmp_file_path)?;
126                         tmp_file.write_all(&buf)?;
127                         tmp_file.sync_all()?;
128                 }
129
130                 #[cfg(not(target_os = "windows"))]
131                 {
132                         fs::rename(&tmp_file_path, &dest_file_path)?;
133                         let dir_file = fs::OpenOptions::new().read(true).open(&parent_directory)?;
134                         unsafe {
135                                 libc::fsync(dir_file.as_raw_fd());
136                         }
137                         Ok(())
138                 }
139
140                 #[cfg(target_os = "windows")]
141                 {
142                         if dest_file_path.exists() {
143                                 call!(unsafe {
144                                         windows_sys::Win32::Storage::FileSystem::ReplaceFileW(
145                                                 path_to_windows_str(dest_file_path).as_ptr(),
146                                                 path_to_windows_str(tmp_file_path).as_ptr(),
147                                                 std::ptr::null(),
148                                                 windows_sys::Win32::Storage::FileSystem::REPLACEFILE_IGNORE_MERGE_ERRORS,
149                                                 std::ptr::null_mut() as *const core::ffi::c_void,
150                                                 std::ptr::null_mut() as *const core::ffi::c_void,
151                                         )
152                                 });
153                         } else {
154                                 call!(unsafe {
155                                         windows_sys::Win32::Storage::FileSystem::MoveFileExW(
156                                                 path_to_windows_str(tmp_file_path).as_ptr(),
157                                                 path_to_windows_str(dest_file_path).as_ptr(),
158                                                 windows_sys::Win32::Storage::FileSystem::MOVEFILE_WRITE_THROUGH
159                                                         | windows_sys::Win32::Storage::FileSystem::MOVEFILE_REPLACE_EXISTING,
160                                         )
161                                 });
162                         }
163                 }
164         }
165
166         fn remove(&self, namespace: &str, key: &str) -> std::io::Result<()> {
167                 if key.is_empty() {
168                         let msg = format!("Failed to remove {}/{}: key may not be empty.",
169                                 PrintableString(namespace), PrintableString(key));
170                         return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
171                 }
172
173                 if namespace.chars().any(|c| !c.is_ascii() || c.is_control()) ||
174                         key.chars().any(|c| !c.is_ascii() || c.is_control()) {
175                         debug_assert!(false, "Failed to remove {}/{}: namespace and key must be valid ASCII
176                                 strings.", PrintableString(namespace), PrintableString(key));
177                         let msg = format!("Failed to remove {}/{}: namespace and key must be valid ASCII strings.",
178                                 PrintableString(namespace), PrintableString(key));
179                         return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
180                 }
181
182                 let mut outer_lock = self.locks.lock().unwrap();
183                 let lock_key = (namespace.to_string(), key.to_string());
184                 let inner_lock_ref = Arc::clone(&outer_lock.entry(lock_key.clone()).or_default());
185
186                 let _guard = inner_lock_ref.write().unwrap();
187
188                 let mut dest_file_path = self.data_dir.clone();
189                 dest_file_path.push(namespace);
190                 dest_file_path.push(key);
191
192                 if !dest_file_path.is_file() {
193                         return Ok(());
194                 }
195
196                 fs::remove_file(&dest_file_path)?;
197                 #[cfg(not(target_os = "windows"))]
198                 {
199                         let parent_directory = dest_file_path.parent().ok_or_else(|| {
200                                 let msg =
201                                         format!("Could not retrieve parent directory of {}.", dest_file_path.display());
202                                 std::io::Error::new(std::io::ErrorKind::InvalidInput, msg)
203                         })?;
204                         let dir_file = fs::OpenOptions::new().read(true).open(parent_directory)?;
205                         unsafe {
206                                 // The above call to `fs::remove_file` corresponds to POSIX `unlink`, whose changes
207                                 // to the inode might get cached (and hence possibly lost on crash), depending on
208                                 // the target platform and file system.
209                                 //
210                                 // In order to assert we permanently removed the file in question we therefore
211                                 // call `fsync` on the parent directory on platforms that support it,
212                                 libc::fsync(dir_file.as_raw_fd());
213                         }
214                 }
215
216                 if dest_file_path.is_file() {
217                         return Err(std::io::Error::new(std::io::ErrorKind::Other, "Removing key failed"));
218                 }
219
220                 if Arc::strong_count(&inner_lock_ref) == 2 {
221                         // It's safe to remove the lock entry if we're the only one left holding a strong
222                         // reference. Checking this is necessary to ensure we continue to distribute references to the
223                         // same lock as long as some Readers are around. However, we still want to
224                         // clean up the table when possible.
225                         //
226                         // Note that this by itself is still leaky as lock entries will remain when more Readers/Writers are
227                         // around, but is preferable to doing nothing *or* something overly complex such as
228                         // implementing yet another RAII structure just for this pupose.
229                         outer_lock.remove(&lock_key);
230                 }
231
232                 // Garbage collect all lock entries that are not referenced anymore.
233                 outer_lock.retain(|_, v| Arc::strong_count(&v) > 1);
234
235                 Ok(())
236         }
237
238         fn list(&self, namespace: &str) -> std::io::Result<Vec<String>> {
239                 let mut prefixed_dest = self.data_dir.clone();
240                 prefixed_dest.push(namespace);
241
242                 let mut keys = Vec::new();
243
244                 if !Path::new(&prefixed_dest).exists() {
245                         return Ok(Vec::new());
246                 }
247
248                 for entry in fs::read_dir(&prefixed_dest)? {
249                         let entry = entry?;
250                         let p = entry.path();
251
252                         if !p.is_file() {
253                                 continue;
254                         }
255
256                         if let Some(ext) = p.extension() {
257                                 if ext == "tmp" {
258                                         continue;
259                                 }
260                         }
261
262                         if let Some(relative_path) = p.strip_prefix(&prefixed_dest).ok()
263                                 .and_then(|p| p.to_str()) {
264                                         if relative_path.chars().all(|c| c.is_ascii() && !c.is_control()) {
265                                                 keys.push(relative_path.to_string())
266                                         }
267                         }
268                 }
269
270                 Ok(keys)
271         }
272 }
273
274 /// A buffered [`Read`] implementation as returned from [`FilesystemStore::read`].
275 pub struct FilesystemReader {
276         inner: BufReader<fs::File>,
277         lock_ref: Arc<RwLock<()>>,
278 }
279
280 impl FilesystemReader {
281         fn new(dest_file_path: PathBuf, lock_ref: Arc<RwLock<()>>) -> std::io::Result<Self> {
282                 let f = fs::File::open(dest_file_path.clone())?;
283                 let inner = BufReader::new(f);
284                 Ok(Self { inner, lock_ref })
285         }
286 }
287
288 impl Read for FilesystemReader {
289         fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
290                 let _guard = self.lock_ref.read().unwrap();
291                 self.inner.read(buf)
292         }
293 }
294
295 #[cfg(test)]
296 mod tests {
297         use super::*;
298         use crate::test_utils::do_read_write_remove_list_persist;
299
300         #[test]
301         fn read_write_remove_list_persist() {
302                 let temp_path = std::env::temp_dir();
303                 let fs_store = FilesystemStore::new(temp_path);
304                 do_read_write_remove_list_persist(&fs_store);
305         }
306 }