From 4ad3d29a272b38c08897a8c7409ae6774e7ca2df Mon Sep 17 00:00:00 2001 From: Markus Wanner Date: Sat, 21 Jan 2023 20:33:22 +0100 Subject: [PATCH 1/2] Deduplicate by implementing From for file::CopyOptions. --- src/dir.rs | 28 +++++----------------------- src/file.rs | 12 ++++++++++++ src/lib.rs | 18 +++--------------- 3 files changed, 20 insertions(+), 38 deletions(-) diff --git a/src/dir.rs b/src/dir.rs index a5d1112..5920012 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -1,5 +1,6 @@ use crate::error::*; use std::collections::{HashMap, HashSet}; +use std::convert::From; use std::fs::{create_dir, create_dir_all, read_dir, remove_dir_all, Metadata}; use std::path::{Path, PathBuf}; use std::time::SystemTime; @@ -615,11 +616,7 @@ where let tp = Path::new(&file).strip_prefix(from)?; let path = to.join(&tp); - let file_options = super::file::CopyOptions { - overwrite: options.overwrite, - skip_exist: options.skip_exist, - buffer_size: options.buffer_size, - }; + let file_options = super::file::CopyOptions::from(options); let mut result_copy: Result; let mut work = true; @@ -927,12 +924,7 @@ where let file_name = file_name.unwrap(); to.push(file_name); - let mut file_options = super::file::CopyOptions { - overwrite: options.overwrite, - skip_exist: options.skip_exist, - buffer_size: options.buffer_size, - }; - + let mut file_options = super::file::CopyOptions::from(&options); if let Some(file_name) = file_name.to_str() { info_process.file_name = file_name.to_string(); } else { @@ -1122,12 +1114,7 @@ where let tp = Path::new(&file).strip_prefix(from)?; let path = to.join(&tp); - let file_options = super::file::CopyOptions { - overwrite: options.overwrite, - skip_exist: options.skip_exist, - buffer_size: options.buffer_size, - }; - + let file_options = super::file::CopyOptions::from(options); let mut result_copy: Result; let mut work = true; while work { @@ -1264,12 +1251,7 @@ where let file_name = file_name.unwrap(); to.push(file_name); - let mut file_options = super::file::CopyOptions { - overwrite: options.overwrite, - skip_exist: options.skip_exist, - buffer_size: options.buffer_size, - }; - + let mut file_options = super::file::CopyOptions::from(&options); if let Some(file_name) = file_name.to_str() { info_process.file_name = file_name.to_string(); } else { diff --git a/src/file.rs b/src/file.rs index 2b62576..ccc8bf0 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,10 +1,12 @@ use crate::error::{Error, ErrorKind, Result}; use std; +use std::convert::From; use std::fs::{remove_file, File}; use std::io::{Read, Write}; use std::path::Path; // Options and flags which can be used to configure how a file will be copied or moved. +#[derive(Debug, Copy, Clone)] pub struct CopyOptions { /// Sets the option true for overwrite existing files. pub overwrite: bool, @@ -58,6 +60,16 @@ impl Default for CopyOptions { } } +impl From<&super::dir::CopyOptions> for CopyOptions { + fn from(dir_options: &super::dir::CopyOptions) -> Self { + CopyOptions { + overwrite: dir_options.overwrite, + skip_exist: dir_options.skip_exist, + buffer_size: dir_options.buffer_size, + } + } +} + /// A structure which stores information about the current status of a file that's copied or moved. . pub struct TransitProcess { /// Copied bytes on this time. diff --git a/src/lib.rs b/src/lib.rs index 118643a..e07a9fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -350,11 +350,7 @@ where }; result += dir::copy_with_progress(item, &to, &dir_options, handler)?; } else { - let mut file_options = file::CopyOptions { - overwrite: options.overwrite, - skip_exist: options.skip_exist, - buffer_size: options.buffer_size, - }; + let mut file_options = file::CopyOptions::from(&options); if let Some(file_name) = item.file_name() { if let Some(file_name) = file_name.to_str() { @@ -537,11 +533,7 @@ where result += dir::move_dir(item, &to, options)?; } else { - let file_options = file::CopyOptions { - overwrite: options.overwrite, - skip_exist: options.skip_exist, - buffer_size: options.buffer_size, - }; + let file_options = file::CopyOptions::from(options); if let Some(file_name) = item.file_name() { if let Some(file_name) = file_name.to_str() { @@ -662,11 +654,7 @@ where }; result += dir::move_dir_with_progress(item, &to, &dir_options, handler)?; } else { - let mut file_options = file::CopyOptions { - overwrite: options.overwrite, - skip_exist: options.skip_exist, - buffer_size: options.buffer_size, - }; + let mut file_options = file::CopyOptions::from(&options); if let Some(file_name) = item.file_name() { if let Some(file_name) = file_name.to_str() { From fee492e61d20727918f656d743fe7071d4a8c0fe Mon Sep 17 00:00:00 2001 From: Markus Wanner Date: Sat, 21 Jan 2023 20:33:22 +0100 Subject: [PATCH 2/2] Add the option to use reflinks (i.e. copy on write semantics). Using the reflink crate, with the option to disallow completely (never), auto (use when available), or always (fail if not possible). Fully conditional on a new feature "reflink". --- Cargo.toml | 4 ++++ src/dir.rs | 9 ++++++++- src/file.rs | 40 +++++++++++++++++++++++++++++++++++++++- src/lib.rs | 18 ++++++++++++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e5af6fd..5958728 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,4 +17,8 @@ include = [ "CHANGELOG.md", ] +[features] +reflink = ["dep:reflink"] + [dependencies] +reflink = { version = "^0.1.0", optional = true } diff --git a/src/dir.rs b/src/dir.rs index 5920012..c7906c0 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -4,9 +4,11 @@ use std::convert::From; use std::fs::{create_dir, create_dir_all, read_dir, remove_dir_all, Metadata}; use std::path::{Path, PathBuf}; use std::time::SystemTime; +#[cfg(feature = "reflink")] +use super::RefLinkUsage; /// Options and flags which can be used to configure how a file will be copied or moved. -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct CopyOptions { /// Overwrite existing files if true (default: false). pub overwrite: bool, @@ -22,6 +24,9 @@ pub struct CopyOptions { /// /// Warning: Work only for copy operations! pub depth: u64, + /// Controls the usage of reflinks for files on filesystems supporting it. + #[cfg(feature = "reflink")] + pub reflink: RefLinkUsage, } impl CopyOptions { @@ -44,6 +49,8 @@ impl CopyOptions { copy_inside: false, content_only: false, depth: 0, + #[cfg(feature = "reflink")] + reflink: RefLinkUsage::Never, } } diff --git a/src/file.rs b/src/file.rs index ccc8bf0..1d1f7f2 100644 --- a/src/file.rs +++ b/src/file.rs @@ -4,6 +4,8 @@ use std::convert::From; use std::fs::{remove_file, File}; use std::io::{Read, Write}; use std::path::Path; +#[cfg(feature = "reflink")] +use super::RefLinkUsage; // Options and flags which can be used to configure how a file will be copied or moved. #[derive(Debug, Copy, Clone)] @@ -14,6 +16,9 @@ pub struct CopyOptions { pub skip_exist: bool, /// Sets buffer size for copy/move work only with receipt information about process work. pub buffer_size: usize, + /// Controls the usage of reflinks on filesystems supporting it. + #[cfg(feature = "reflink")] + pub reflink: RefLinkUsage, } impl CopyOptions { @@ -32,6 +37,8 @@ impl CopyOptions { overwrite: false, skip_exist: false, buffer_size: 64000, //64kb + #[cfg(feature = "reflink")] + reflink: RefLinkUsage::Never, } } @@ -66,6 +73,8 @@ impl From<&super::dir::CopyOptions> for CopyOptions { overwrite: dir_options.overwrite, skip_exist: dir_options.skip_exist, buffer_size: dir_options.buffer_size, + #[cfg(feature = "reflink")] + reflink: dir_options.reflink, } } } @@ -136,7 +145,24 @@ where } } - Ok(std::fs::copy(from, to)?) + Ok( + #[cfg(not(feature = "reflink"))] + { std::fs::copy(from, to)? }, + + #[cfg(feature = "reflink")] + match options.reflink { + RefLinkUsage::Never => std::fs::copy(from, to)?, + + #[cfg(feature = "reflink")] + RefLinkUsage::Auto => reflink::reflink_or_copy(from, to)?.unwrap_or(0), + + #[cfg(feature = "reflink")] + RefLinkUsage::Always => { + reflink::reflink(from, to)?; + 0 + }, + } + ) } /// Copies the contents of one file to another file with information about progress. @@ -205,6 +231,18 @@ where err!(&msg, ErrorKind::AlreadyExists); } } + + #[cfg(feature = "reflink")] + if options.reflink != RefLinkUsage::Never { + match reflink::reflink(&from, &to) { + Ok(()) => return Ok(0), + Err(e) if options.reflink == RefLinkUsage::Always => { + return Err(::std::convert::From::from(e)); + }, + Err(_) => { /* continue with plain copy */ } + } + } + let mut file_from = File::open(from)?; let mut buf = vec![0; options.buffer_size]; let file_size = file_from.metadata()?.len(); diff --git a/src/lib.rs b/src/lib.rs index e07a9fe..7d94e0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "reflink")] +extern crate reflink; + macro_rules! err { ($text:expr, $kind:expr) => { return Err(Error::new($kind, $text)) @@ -156,6 +159,21 @@ pub mod dir; use crate::error::*; use std::path::Path; +/// Possible values for the reflink field in CopyOptions. These +/// correspond to the `--reflink` option of the Unix `cp` command. +#[cfg(feature = "reflink")] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum RefLinkUsage { + /// Do not use reflinks. + Never, + /// Use reflinks if possible. + #[cfg(feature = "reflink")] + Auto, + /// Force use of reflinks, error out if not possible. + #[cfg(feature = "reflink")] + Always, +} + /// Copies a list of directories and files to another place recursively. This function will /// also copy the permission bits of the original files to destination files (not for /// directories).