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