Skip to content

Commit 0a8aaa2

Browse files
committed
zenith_utils - add crashsafe_dir
Utility for creating directories and directory trees in a crash safe manor. Minimizes calls to fsync for trees.
1 parent e474790 commit 0a8aaa2

File tree

4 files changed

+130
-0
lines changed

4 files changed

+130
-0
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zenith_utils/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ rustls-split = "0.2.1"
3838
hex-literal = "0.3"
3939
bytes = "1.0"
4040
webpki = "0.21"
41+
tempfile = "3.2"

zenith_utils/src/crashsafe_dir.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use std::{
2+
fs::{self, File},
3+
io,
4+
path::Path,
5+
};
6+
7+
/// Similar to [`std::fs::create_dir`], except we fsync the
8+
/// created directory and its parent.
9+
pub fn create_dir(path: impl AsRef<Path>) -> io::Result<()> {
10+
let path = path.as_ref();
11+
12+
fs::create_dir(path)?;
13+
File::open(path)?.sync_all()?;
14+
15+
if let Some(parent) = path.parent() {
16+
File::open(parent)?.sync_all()
17+
} else {
18+
Err(io::Error::new(
19+
io::ErrorKind::InvalidInput,
20+
"can't find parent",
21+
))
22+
}
23+
}
24+
25+
/// Similar to [`std::fs::create_dir_all`], except we fsync all
26+
/// newly created directories and the pre-existing parent.
27+
pub fn create_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
28+
let mut path = path.as_ref();
29+
30+
let mut dirs_to_create = Vec::new();
31+
32+
// Figure out which directories we need to create.
33+
loop {
34+
match path.metadata() {
35+
Ok(metadata) if metadata.is_dir() => break,
36+
Ok(_) => {
37+
return Err(io::Error::new(
38+
io::ErrorKind::AlreadyExists,
39+
format!("non-directory found in path: {:?}", path),
40+
));
41+
}
42+
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
43+
Err(e) => return Err(e),
44+
}
45+
46+
dirs_to_create.push(path);
47+
48+
match path.parent() {
49+
Some(parent) => path = parent,
50+
None => {
51+
return Err(io::Error::new(
52+
io::ErrorKind::InvalidInput,
53+
"can't find parent",
54+
))
55+
}
56+
}
57+
}
58+
59+
// Create directories from parent to child.
60+
for &path in dirs_to_create.iter().rev() {
61+
fs::create_dir(path)?;
62+
}
63+
64+
// Fsync the created directories from child to parent.
65+
for &path in dirs_to_create.iter() {
66+
File::open(path)?.sync_all()?;
67+
}
68+
69+
// If we created any new directories, fsync the parent.
70+
if !dirs_to_create.is_empty() {
71+
File::open(path)?.sync_all()?;
72+
}
73+
74+
Ok(())
75+
}
76+
77+
#[cfg(test)]
78+
mod tests {
79+
use tempfile::tempdir;
80+
81+
use super::*;
82+
83+
#[test]
84+
fn test_create_dir_fsyncd() {
85+
let dir = tempdir().unwrap();
86+
87+
let existing_dir_path = dir.path();
88+
let err = create_dir(existing_dir_path).unwrap_err();
89+
assert_eq!(err.kind(), io::ErrorKind::AlreadyExists);
90+
91+
let child_dir = existing_dir_path.join("child");
92+
create_dir(child_dir).unwrap();
93+
94+
let nested_child_dir = existing_dir_path.join("child1").join("child2");
95+
let err = create_dir(nested_child_dir).unwrap_err();
96+
assert_eq!(err.kind(), io::ErrorKind::NotFound);
97+
}
98+
99+
#[test]
100+
fn test_create_dir_all_fsyncd() {
101+
let dir = tempdir().unwrap();
102+
103+
let existing_dir_path = dir.path();
104+
create_dir_all(existing_dir_path).unwrap();
105+
106+
let child_dir = existing_dir_path.join("child");
107+
assert!(!child_dir.exists());
108+
create_dir_all(&child_dir).unwrap();
109+
assert!(child_dir.exists());
110+
111+
let nested_child_dir = existing_dir_path.join("child1").join("child2");
112+
assert!(!nested_child_dir.exists());
113+
create_dir_all(&nested_child_dir).unwrap();
114+
assert!(nested_child_dir.exists());
115+
116+
let file_path = existing_dir_path.join("file");
117+
std::fs::write(&file_path, b"").unwrap();
118+
119+
let err = create_dir_all(&file_path).unwrap_err();
120+
assert_eq!(err.kind(), io::ErrorKind::AlreadyExists);
121+
122+
let invalid_dir_path = file_path.join("folder");
123+
create_dir_all(&invalid_dir_path).unwrap_err();
124+
}
125+
}

zenith_utils/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ pub mod pq_proto;
1818
// dealing with connstring parsing and handy access to it's parts
1919
pub mod connstring;
2020

21+
// helper functions for creating and fsyncing directories/trees
22+
pub mod crashsafe_dir;
23+
2124
// common authentication routines
2225
pub mod auth;
2326

0 commit comments

Comments
 (0)