mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 12:07:46 +00:00
uucore/detect_symlink_loop: add a function to detect symlink loops
This commit is contained in:
parent
aae3f2f99c
commit
6547bec2ef
3 changed files with 74 additions and 0 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3407,6 +3407,7 @@ dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
"sha3",
|
"sha3",
|
||||||
"sm3",
|
"sm3",
|
||||||
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
|
|
|
@ -54,6 +54,7 @@ nix = { workspace=true, features = ["fs", "uio", "zerocopy", "signal"] }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
clap = { workspace=true }
|
clap = { workspace=true }
|
||||||
once_cell = { workspace=true }
|
once_cell = { workspace=true }
|
||||||
|
tempfile = { workspace=true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi-util = { version= "0.1.5", optional=true }
|
winapi-util = { version= "0.1.5", optional=true }
|
||||||
|
|
|
@ -584,10 +584,45 @@ pub fn make_path_relative_to<P1: AsRef<Path>, P2: AsRef<Path>>(path: P1, to: P2)
|
||||||
components.iter().collect()
|
components.iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if there is a symlink loop in the given path.
|
||||||
|
///
|
||||||
|
/// A symlink loop is a chain of symlinks where the last symlink points back to one of the previous symlinks in the chain.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `path` - A reference to a `Path` representing the starting path to check for symlink loops.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `bool` - Returns `true` if a symlink loop is detected, `false` otherwise.
|
||||||
|
pub fn is_symlink_loop(path: &Path) -> bool {
|
||||||
|
let mut visited_symlinks = HashSet::new();
|
||||||
|
let mut current_path = path.to_path_buf();
|
||||||
|
|
||||||
|
while let (Ok(metadata), Ok(link)) = (
|
||||||
|
current_path.symlink_metadata(),
|
||||||
|
fs::read_link(¤t_path),
|
||||||
|
) {
|
||||||
|
if !metadata.file_type().is_symlink() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if !visited_symlinks.insert(current_path.clone()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
current_path = link;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
// Note this useful idiom: importing names from outer (for mod tests) scope.
|
// Note this useful idiom: importing names from outer (for mod tests) scope.
|
||||||
use super::*;
|
use super::*;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
struct NormalizePathTestCase<'a> {
|
struct NormalizePathTestCase<'a> {
|
||||||
path: &'a str,
|
path: &'a str,
|
||||||
|
@ -695,4 +730,41 @@ mod tests {
|
||||||
display_permissions_unix(S_IFCHR | S_ISVTX as mode_t | 0o054, true)
|
display_permissions_unix(S_IFCHR | S_ISVTX as mode_t | 0o054, true)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn test_is_symlink_loop_no_loop() {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let file_path = temp_dir.path().join("file.txt");
|
||||||
|
let symlink_path = temp_dir.path().join("symlink");
|
||||||
|
|
||||||
|
fs::write(&file_path, "test content").unwrap();
|
||||||
|
unix::fs::symlink(&file_path, &symlink_path).unwrap();
|
||||||
|
|
||||||
|
assert!(!is_symlink_loop(&symlink_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn test_is_symlink_loop_direct_loop() {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let symlink_path = temp_dir.path().join("loop");
|
||||||
|
|
||||||
|
unix::fs::symlink(&symlink_path, &symlink_path).unwrap();
|
||||||
|
|
||||||
|
assert!(is_symlink_loop(&symlink_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn test_is_symlink_loop_indirect_loop() {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let symlink1_path = temp_dir.path().join("symlink1");
|
||||||
|
let symlink2_path = temp_dir.path().join("symlink2");
|
||||||
|
|
||||||
|
unix::fs::symlink(&symlink1_path, &symlink2_path).unwrap();
|
||||||
|
unix::fs::symlink(&symlink2_path, &symlink1_path).unwrap();
|
||||||
|
|
||||||
|
assert!(is_symlink_loop(&symlink1_path));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue