LCOV - differential code coverage report
Current view: top level - libs/utils/src - crashsafe.rs (source / functions) Coverage Total Hit UBC CBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 91.2 % 137 125 12 125
Current Date: 2023-10-19 02:04:12 Functions: 78.1 % 32 25 7 25
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

           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                 : }
        

Generated by: LCOV version 2.1-beta