summaryrefslogtreecommitdiff
path: root/cli/fs_util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/fs_util.rs')
-rw-r--r--cli/fs_util.rs79
1 files changed, 79 insertions, 0 deletions
diff --git a/cli/fs_util.rs b/cli/fs_util.rs
index fa1535469..843f5e0cf 100644
--- a/cli/fs_util.rs
+++ b/cli/fs_util.rs
@@ -15,6 +15,7 @@ use std::io::ErrorKind;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
+use std::time::Duration;
use walkdir::WalkDir;
pub fn atomic_write_file<T: AsRef<[u8]>>(
@@ -357,6 +358,84 @@ pub fn copy_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> {
Ok(())
}
+/// Hardlinks the files in one directory to another directory.
+///
+/// Note: Does not handle symlinks.
+pub fn hard_link_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> {
+ std::fs::create_dir_all(&to)
+ .with_context(|| format!("Creating {}", to.display()))?;
+ let read_dir = std::fs::read_dir(&from)
+ .with_context(|| format!("Reading {}", from.display()))?;
+
+ for entry in read_dir {
+ let entry = entry?;
+ let file_type = entry.file_type()?;
+ let new_from = from.join(entry.file_name());
+ let new_to = to.join(entry.file_name());
+
+ if file_type.is_dir() {
+ hard_link_dir_recursive(&new_from, &new_to).with_context(|| {
+ format!("Dir {} to {}", new_from.display(), new_to.display())
+ })?;
+ } else if file_type.is_file() {
+ // note: chance for race conditions here between attempting to create,
+ // then removing, then attempting to create. There doesn't seem to be
+ // a way to hard link with overwriting in Rust, but maybe there is some
+ // way with platform specific code. The workaround here is to handle
+ // scenarios where something else might create or remove files.
+ if let Err(err) = std::fs::hard_link(&new_from, &new_to) {
+ if err.kind() == ErrorKind::AlreadyExists {
+ if let Err(err) = std::fs::remove_file(&new_to) {
+ if err.kind() == ErrorKind::NotFound {
+ // Assume another process/thread created this hard link to the file we are wanting
+ // to remove then sleep a little bit to let the other process/thread move ahead
+ // faster to reduce contention.
+ std::thread::sleep(Duration::from_millis(10));
+ } else {
+ return Err(err).with_context(|| {
+ format!(
+ "Removing file to hard link {} to {}",
+ new_from.display(),
+ new_to.display()
+ )
+ });
+ }
+ }
+
+ // Always attempt to recreate the hardlink. In contention scenarios, the other process
+ // might have been killed or exited after removing the file, but before creating the hardlink
+ if let Err(err) = std::fs::hard_link(&new_from, &new_to) {
+ // Assume another process/thread created this hard link to the file we are wanting
+ // to now create then sleep a little bit to let the other process/thread move ahead
+ // faster to reduce contention.
+ if err.kind() == ErrorKind::AlreadyExists {
+ std::thread::sleep(Duration::from_millis(10));
+ } else {
+ return Err(err).with_context(|| {
+ format!(
+ "Hard linking {} to {}",
+ new_from.display(),
+ new_to.display()
+ )
+ });
+ }
+ }
+ } else {
+ return Err(err).with_context(|| {
+ format!(
+ "Hard linking {} to {}",
+ new_from.display(),
+ new_to.display()
+ )
+ });
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
+
pub fn symlink_dir(oldpath: &Path, newpath: &Path) -> Result<(), AnyError> {
let err_mapper = |err: Error| {
Error::new(