Line data Source code
1 : use std::{
2 : borrow::Cow,
3 : ffi::OsStr,
4 : fs::{self, File},
5 : io,
6 : path::{Path, PathBuf},
7 : };
8 :
9 : /// Similar to [`std::fs::create_dir`], except we fsync the
10 : /// created directory and its parent.
11 1564 : pub fn create_dir(path: impl AsRef<Path>) -> io::Result<()> {
12 1564 : let path = path.as_ref();
13 1564 :
14 1564 : fs::create_dir(path)?;
15 1562 : fsync_file_and_parent(path)?;
16 1562 : Ok(())
17 1564 : }
18 :
19 : /// Similar to [`std::fs::create_dir_all`], except we fsync all
20 : /// newly created directories and the pre-existing parent.
21 893 : pub fn create_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
22 893 : let mut path = path.as_ref();
23 893 :
24 893 : let mut dirs_to_create = Vec::new();
25 :
26 : // Figure out which directories we need to create.
27 : loop {
28 1784 : match path.metadata() {
29 891 : Ok(metadata) if metadata.is_dir() => break,
30 : Ok(_) => {
31 1 : return Err(io::Error::new(
32 1 : io::ErrorKind::AlreadyExists,
33 1 : format!("non-directory found in path: {}", path.display()),
34 1 : ));
35 : }
36 892 : Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
37 1 : Err(e) => return Err(e),
38 : }
39 :
40 891 : dirs_to_create.push(path);
41 891 :
42 891 : match path.parent() {
43 891 : Some(parent) => path = parent,
44 : None => {
45 0 : return Err(io::Error::new(
46 0 : io::ErrorKind::InvalidInput,
47 0 : format!("can't find parent of path '{}'", path.display()).as_str(),
48 0 : ));
49 : }
50 : }
51 : }
52 :
53 : // Create directories from parent to child.
54 891 : for &path in dirs_to_create.iter().rev() {
55 891 : fs::create_dir(path)?;
56 : }
57 :
58 : // Fsync the created directories from child to parent.
59 891 : for &path in dirs_to_create.iter() {
60 891 : fsync(path)?;
61 : }
62 :
63 : // If we created any new directories, fsync the parent.
64 891 : if !dirs_to_create.is_empty() {
65 890 : fsync(path)?;
66 1 : }
67 :
68 891 : Ok(())
69 893 : }
70 :
71 : /// Adds a suffix to the file(directory) name, either appending the suffix to the end of its extension,
72 : /// or if there's no extension, creates one and puts a suffix there.
73 20086 : pub fn path_with_suffix_extension(original_path: impl AsRef<Path>, suffix: &str) -> PathBuf {
74 20086 : let new_extension = match original_path
75 20086 : .as_ref()
76 20086 : .extension()
77 20086 : .map(OsStr::to_string_lossy)
78 : {
79 1728 : Some(extension) => Cow::Owned(format!("{extension}.{suffix}")),
80 18358 : None => Cow::Borrowed(suffix),
81 : };
82 20086 : original_path
83 20086 : .as_ref()
84 20086 : .with_extension(new_extension.as_ref())
85 20086 : }
86 :
87 4138 : pub fn fsync_file_and_parent(file_path: &Path) -> io::Result<()> {
88 4138 : let parent = file_path.parent().ok_or_else(|| {
89 0 : io::Error::new(
90 0 : io::ErrorKind::Other,
91 0 : format!("File {file_path:?} has no parent"),
92 0 : )
93 4138 : })?;
94 :
95 4138 : fsync(file_path)?;
96 4138 : fsync(parent)?;
97 4138 : Ok(())
98 4138 : }
99 :
100 12182 : pub fn fsync(path: &Path) -> io::Result<()> {
101 12182 : File::open(path)
102 12182 : .map_err(|e| io::Error::new(e.kind(), format!("Failed to open the file {path:?}: {e}")))
103 12182 : .and_then(|file| {
104 12181 : file.sync_all().map_err(|e| {
105 0 : io::Error::new(
106 0 : e.kind(),
107 0 : format!("Failed to sync file {path:?} data and metadata: {e}"),
108 0 : )
109 12181 : })
110 12182 : })
111 12182 : .map_err(|e| io::Error::new(e.kind(), format!("Failed to fsync file {path:?}: {e}")))
112 12182 : }
113 :
114 1353 : pub async fn fsync_async(path: impl AsRef<std::path::Path>) -> Result<(), std::io::Error> {
115 1353 : tokio::fs::File::open(path).await?.sync_all().await
116 1353 : }
117 :
118 : #[cfg(test)]
119 : mod tests {
120 : use tempfile::tempdir;
121 :
122 : use super::*;
123 :
124 1 : #[test]
125 1 : fn test_create_dir_fsyncd() {
126 1 : let dir = tempdir().unwrap();
127 1 :
128 1 : let existing_dir_path = dir.path();
129 1 : let err = create_dir(existing_dir_path).unwrap_err();
130 1 : assert_eq!(err.kind(), io::ErrorKind::AlreadyExists);
131 :
132 1 : let child_dir = existing_dir_path.join("child");
133 1 : create_dir(child_dir).unwrap();
134 1 :
135 1 : let nested_child_dir = existing_dir_path.join("child1").join("child2");
136 1 : let err = create_dir(nested_child_dir).unwrap_err();
137 1 : assert_eq!(err.kind(), io::ErrorKind::NotFound);
138 1 : }
139 :
140 1 : #[test]
141 1 : fn test_create_dir_all_fsyncd() {
142 1 : let dir = tempdir().unwrap();
143 1 :
144 1 : let existing_dir_path = dir.path();
145 1 : create_dir_all(existing_dir_path).unwrap();
146 1 :
147 1 : let child_dir = existing_dir_path.join("child");
148 1 : assert!(!child_dir.exists());
149 1 : create_dir_all(&child_dir).unwrap();
150 1 : assert!(child_dir.exists());
151 :
152 1 : let nested_child_dir = existing_dir_path.join("child1").join("child2");
153 1 : assert!(!nested_child_dir.exists());
154 1 : create_dir_all(&nested_child_dir).unwrap();
155 1 : assert!(nested_child_dir.exists());
156 :
157 1 : let file_path = existing_dir_path.join("file");
158 1 : std::fs::write(&file_path, b"").unwrap();
159 1 :
160 1 : let err = create_dir_all(&file_path).unwrap_err();
161 1 : assert_eq!(err.kind(), io::ErrorKind::AlreadyExists);
162 :
163 1 : let invalid_dir_path = file_path.join("folder");
164 1 : create_dir_all(invalid_dir_path).unwrap_err();
165 1 : }
166 :
167 1 : #[test]
168 1 : fn test_path_with_suffix_extension() {
169 1 : let p = PathBuf::from("/foo/bar");
170 1 : assert_eq!(
171 1 : &path_with_suffix_extension(p, "temp").to_string_lossy(),
172 1 : "/foo/bar.temp"
173 1 : );
174 1 : let p = PathBuf::from("/foo/bar");
175 1 : assert_eq!(
176 1 : &path_with_suffix_extension(p, "temp.temp").to_string_lossy(),
177 1 : "/foo/bar.temp.temp"
178 1 : );
179 1 : let p = PathBuf::from("/foo/bar.baz");
180 1 : assert_eq!(
181 1 : &path_with_suffix_extension(p, "temp.temp").to_string_lossy(),
182 1 : "/foo/bar.baz.temp.temp"
183 1 : );
184 1 : let p = PathBuf::from("/foo/bar.baz");
185 1 : assert_eq!(
186 1 : &path_with_suffix_extension(p, ".temp").to_string_lossy(),
187 1 : "/foo/bar.baz..temp"
188 1 : );
189 1 : let p = PathBuf::from("/foo/bar/dir/");
190 1 : assert_eq!(
191 1 : &path_with_suffix_extension(p, ".temp").to_string_lossy(),
192 1 : "/foo/bar/dir..temp"
193 1 : );
194 1 : }
195 : }
|