1#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5 html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6 issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
7)]
8#![cfg_attr(not(test), warn(unused_crate_dependencies))]
9use serde::{de::DeserializeOwned, Serialize};
10use std::{
11 fs::{self, File, OpenOptions, ReadDir},
12 io::{self, BufWriter, Error, Write},
13 path::{Path, PathBuf},
14};
15
16pub type Result<T> = std::result::Result<T, FsPathError>;
18
19#[derive(Debug, thiserror::Error)]
22pub enum FsPathError {
23 #[error("failed to write to {path:?}: {source}")]
25 Write {
26 source: io::Error,
28 path: PathBuf,
30 },
31
32 #[error("failed to read from {path:?}: {source}")]
34 Read {
35 source: io::Error,
37 path: PathBuf,
39 },
40
41 #[error("failed to read from {path:?}: {source}")]
43 ReadLink {
44 source: io::Error,
46 path: PathBuf,
48 },
49
50 #[error("failed to create file {path:?}: {source}")]
52 CreateFile {
53 source: io::Error,
55 path: PathBuf,
57 },
58
59 #[error("failed to remove file {path:?}: {source}")]
61 RemoveFile {
62 source: io::Error,
64 path: PathBuf,
66 },
67
68 #[error("failed to create dir {path:?}: {source}")]
70 CreateDir {
71 source: io::Error,
73 path: PathBuf,
75 },
76
77 #[error("failed to remove dir {path:?}: {source}")]
79 RemoveDir {
80 source: io::Error,
82 path: PathBuf,
84 },
85
86 #[error("failed to read dir {path:?}: {source}")]
88 ReadDir {
89 source: io::Error,
91 path: PathBuf,
93 },
94
95 #[error("failed to rename {from:?} to {to:?}: {source}")]
97 Rename {
98 source: io::Error,
100 from: PathBuf,
102 to: PathBuf,
104 },
105
106 #[error("failed to open file {path:?}: {source}")]
108 Open {
109 source: io::Error,
111 path: PathBuf,
113 },
114
115 #[error("failed to parse json file: {path:?}: {source}")]
117 ReadJson {
118 source: serde_json::Error,
120 path: PathBuf,
122 },
123
124 #[error("failed to write to json file: {path:?}: {source}")]
126 WriteJson {
127 source: serde_json::Error,
129 path: PathBuf,
131 },
132
133 #[error("failed to get metadata for {path:?}: {source}")]
135 Metadata {
136 source: io::Error,
138 path: PathBuf,
140 },
141 #[error("failed to sync path {path:?}: {source}")]
143 Fsync {
144 source: io::Error,
146 path: PathBuf,
148 },
149}
150
151impl FsPathError {
152 pub fn write(source: io::Error, path: impl Into<PathBuf>) -> Self {
154 Self::Write { source, path: path.into() }
155 }
156
157 pub fn read(source: io::Error, path: impl Into<PathBuf>) -> Self {
159 Self::Read { source, path: path.into() }
160 }
161
162 pub fn read_link(source: io::Error, path: impl Into<PathBuf>) -> Self {
164 Self::ReadLink { source, path: path.into() }
165 }
166
167 pub fn create_file(source: io::Error, path: impl Into<PathBuf>) -> Self {
169 Self::CreateFile { source, path: path.into() }
170 }
171
172 pub fn remove_file(source: io::Error, path: impl Into<PathBuf>) -> Self {
174 Self::RemoveFile { source, path: path.into() }
175 }
176
177 pub fn create_dir(source: io::Error, path: impl Into<PathBuf>) -> Self {
179 Self::CreateDir { source, path: path.into() }
180 }
181
182 pub fn remove_dir(source: io::Error, path: impl Into<PathBuf>) -> Self {
184 Self::RemoveDir { source, path: path.into() }
185 }
186
187 pub fn read_dir(source: io::Error, path: impl Into<PathBuf>) -> Self {
189 Self::ReadDir { source, path: path.into() }
190 }
191
192 pub fn open(source: io::Error, path: impl Into<PathBuf>) -> Self {
194 Self::Open { source, path: path.into() }
195 }
196
197 pub fn rename(source: io::Error, from: impl Into<PathBuf>, to: impl Into<PathBuf>) -> Self {
199 Self::Rename { source, from: from.into(), to: to.into() }
200 }
201
202 pub fn metadata(source: io::Error, path: impl Into<PathBuf>) -> Self {
204 Self::Metadata { source, path: path.into() }
205 }
206
207 pub fn fsync(source: io::Error, path: impl Into<PathBuf>) -> Self {
209 Self::Fsync { source, path: path.into() }
210 }
211}
212
213pub fn open(path: impl AsRef<Path>) -> Result<File> {
215 let path = path.as_ref();
216 File::open(path).map_err(|err| FsPathError::open(err, path))
217}
218
219pub fn read_to_string(path: impl AsRef<Path>) -> Result<String> {
221 let path = path.as_ref();
222 fs::read_to_string(path).map_err(|err| FsPathError::read(err, path))
223}
224
225pub fn read(path: impl AsRef<Path>) -> Result<Vec<u8>> {
229 let path = path.as_ref();
230 fs::read(path).map_err(|err| FsPathError::read(err, path))
231}
232
233pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
235 let path = path.as_ref();
236 fs::write(path, contents).map_err(|err| FsPathError::write(err, path))
237}
238
239pub fn remove_dir_all(path: impl AsRef<Path>) -> Result<()> {
241 let path = path.as_ref();
242 fs::remove_dir_all(path).map_err(|err| FsPathError::remove_dir(err, path))
243}
244
245pub fn create_file(path: impl AsRef<Path>) -> Result<fs::File> {
247 let path = path.as_ref();
248 File::create(path).map_err(|err| FsPathError::create_file(err, path))
249}
250
251pub fn remove_file(path: impl AsRef<Path>) -> Result<()> {
253 let path = path.as_ref();
254 fs::remove_file(path).map_err(|err| FsPathError::remove_file(err, path))
255}
256
257pub fn create_dir_all(path: impl AsRef<Path>) -> Result<()> {
259 let path = path.as_ref();
260 fs::create_dir_all(path).map_err(|err| FsPathError::create_dir(err, path))
261}
262
263pub fn read_dir(path: impl AsRef<Path>) -> Result<ReadDir> {
265 let path = path.as_ref();
266 fs::read_dir(path).map_err(|err| FsPathError::read_dir(err, path))
267}
268
269pub fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
271 let from = from.as_ref();
272 let to = to.as_ref();
273 fs::rename(from, to).map_err(|err| FsPathError::rename(err, from, to))
274}
275
276pub fn metadata(path: impl AsRef<Path>) -> Result<fs::Metadata> {
278 let path = path.as_ref();
279 fs::metadata(path).map_err(|err| FsPathError::metadata(err, path))
280}
281
282pub fn read_json_file<T: DeserializeOwned>(path: &Path) -> Result<T> {
284 let bytes = read(path)?;
287 serde_json::from_slice(&bytes)
288 .map_err(|source| FsPathError::ReadJson { source, path: path.into() })
289}
290
291pub fn write_json_file<T: Serialize>(path: &Path, obj: &T) -> Result<()> {
293 let file = create_file(path)?;
294 let mut writer = BufWriter::new(file);
295 serde_json::to_writer_pretty(&mut writer, obj)
296 .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?;
297 writer.flush().map_err(|e| FsPathError::write(e, path))
298}
299
300pub fn atomic_write_file<F, E>(file_path: &Path, write_fn: F) -> Result<()>
312where
313 F: FnOnce(&mut File) -> std::result::Result<(), E>,
314 E: Into<Box<dyn core::error::Error + Send + Sync>>,
315{
316 #[cfg(windows)]
317 use std::os::windows::fs::OpenOptionsExt;
318
319 let mut tmp_path = file_path.to_path_buf();
320 tmp_path.set_extension("tmp");
321
322 let mut file =
324 File::create(&tmp_path).map_err(|err| FsPathError::create_file(err, &tmp_path))?;
325
326 write_fn(&mut file).map_err(|err| FsPathError::Write {
327 source: Error::other(err.into()),
328 path: tmp_path.clone(),
329 })?;
330
331 file.sync_all().map_err(|err| FsPathError::fsync(err, &tmp_path))?;
333
334 rename(&tmp_path, file_path)?;
336
337 if let Some(parent) = file_path.parent() {
339 #[cfg(windows)]
340 OpenOptions::new()
341 .read(true)
342 .write(true)
343 .custom_flags(0x02000000) .open(parent)
345 .map_err(|err| FsPathError::open(err, parent))?
346 .sync_all()
347 .map_err(|err| FsPathError::fsync(err, parent))?;
348
349 #[cfg(not(windows))]
350 OpenOptions::new()
351 .read(true)
352 .open(parent)
353 .map_err(|err| FsPathError::open(err, parent))?
354 .sync_all()
355 .map_err(|err| FsPathError::fsync(err, parent))?;
356 }
357
358 Ok(())
359}