LCOV - code coverage report
Current view: top level - libs/utils/src - crashsafe.rs (source / functions) Coverage Total Hit
Test: 8ac049b474321fdc72ddcb56d7165153a1a900e8.info Lines: 91.4 % 139 127
Test Date: 2023-09-06 10:18:01 Functions: 78.1 % 32 25

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

Generated by: LCOV version 2.1-beta