mirror of
https://github.com/denoland/deno.git
synced 2025-01-19 12:16:17 -05:00
ea30e188a8
Closes #26171 --------- Co-authored-by: David Sherret <dsherret@gmail.com>
153 lines
4.6 KiB
Rust
153 lines
4.6 KiB
Rust
// Copyright 2018-2025 the Deno authors. MIT license.
|
|
|
|
use std::io::ErrorKind;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
use std::time::Duration;
|
|
|
|
use sys_traits::FsCreateDirAll;
|
|
use sys_traits::FsDirEntry;
|
|
use sys_traits::FsHardLink;
|
|
use sys_traits::FsReadDir;
|
|
use sys_traits::FsRemoveFile;
|
|
use sys_traits::ThreadSleep;
|
|
|
|
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
|
pub enum HardLinkDirRecursiveError {
|
|
#[class(inherit)]
|
|
#[error(transparent)]
|
|
Io(#[from] std::io::Error),
|
|
#[class(inherit)]
|
|
#[error("Creating {path}")]
|
|
Creating {
|
|
path: PathBuf,
|
|
#[source]
|
|
#[inherit]
|
|
source: std::io::Error,
|
|
},
|
|
#[class(inherit)]
|
|
#[error("Creating {path}")]
|
|
Reading {
|
|
path: PathBuf,
|
|
#[source]
|
|
#[inherit]
|
|
source: std::io::Error,
|
|
},
|
|
#[class(inherit)]
|
|
#[error("Dir {from} to {to}")]
|
|
Dir {
|
|
from: PathBuf,
|
|
to: PathBuf,
|
|
#[source]
|
|
#[inherit]
|
|
source: Box<Self>,
|
|
},
|
|
#[class(inherit)]
|
|
#[error("Removing file to hard link {from} to {to}")]
|
|
RemoveFileToHardLink {
|
|
from: PathBuf,
|
|
to: PathBuf,
|
|
#[source]
|
|
#[inherit]
|
|
source: std::io::Error,
|
|
},
|
|
#[class(inherit)]
|
|
#[error("Hard linking {from} to {to}")]
|
|
HardLinking {
|
|
from: PathBuf,
|
|
to: PathBuf,
|
|
#[source]
|
|
#[inherit]
|
|
source: std::io::Error,
|
|
},
|
|
}
|
|
|
|
/// Hardlinks the files in one directory to another directory.
|
|
///
|
|
/// Note: Does not handle symlinks.
|
|
pub fn hard_link_dir_recursive<
|
|
TSys: FsCreateDirAll + FsHardLink + FsReadDir + FsRemoveFile + ThreadSleep,
|
|
>(
|
|
sys: &TSys,
|
|
from: &Path,
|
|
to: &Path,
|
|
) -> Result<(), HardLinkDirRecursiveError> {
|
|
sys.fs_create_dir_all(to).map_err(|source| {
|
|
HardLinkDirRecursiveError::Creating {
|
|
path: to.to_path_buf(),
|
|
source,
|
|
}
|
|
})?;
|
|
let read_dir = sys.fs_read_dir(from).map_err(|source| {
|
|
HardLinkDirRecursiveError::Reading {
|
|
path: from.to_path_buf(),
|
|
source,
|
|
}
|
|
})?;
|
|
|
|
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(sys, &new_from, &new_to).map_err(|source| {
|
|
HardLinkDirRecursiveError::Dir {
|
|
from: new_from.to_path_buf(),
|
|
to: new_to.to_path_buf(),
|
|
source: Box::new(source),
|
|
}
|
|
})?;
|
|
} 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) = sys.fs_hard_link(&new_from, &new_to) {
|
|
if err.kind() == ErrorKind::AlreadyExists {
|
|
if let Err(err) = sys.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.
|
|
sys.thread_sleep(Duration::from_millis(10));
|
|
} else {
|
|
return Err(HardLinkDirRecursiveError::RemoveFileToHardLink {
|
|
from: new_from.to_path_buf(),
|
|
to: new_to.to_path_buf(),
|
|
source: err,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 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) = sys.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 {
|
|
sys.thread_sleep(Duration::from_millis(10));
|
|
} else {
|
|
return Err(HardLinkDirRecursiveError::HardLinking {
|
|
from: new_from.to_path_buf(),
|
|
to: new_to.to_path_buf(),
|
|
source: err,
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
return Err(HardLinkDirRecursiveError::HardLinking {
|
|
from: new_from.to_path_buf(),
|
|
to: new_to.to_path_buf(),
|
|
source: err,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|