diff --git a/src/dir_walker.rs b/src/dir_walker.rs index f483639..0b05daa 100644 --- a/src/dir_walker.rs +++ b/src/dir_walker.rs @@ -115,9 +115,11 @@ fn sort_by_inode(a: &Node, b: &Node) -> std::cmp::Ordering { fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool { let is_dot_file = entry.file_name().to_str().unwrap_or("").starts_with('.'); let is_ignored_path = walk_data.ignore_directories.contains(&entry.path()); + let follow_links = + walk_data.follow_links && entry.file_type().map_or(false, |ft| ft.is_symlink()); if !walk_data.allowed_filesystems.is_empty() { - let size_inode_device = get_metadata(entry.path(), false); + let size_inode_device = get_metadata(entry.path(), false, follow_links); if let Some((_size, Some((_id, dev)), _gunk)) = size_inode_device { if !walk_data.allowed_filesystems.contains(&dev) { return true; @@ -128,7 +130,7 @@ fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool { || walk_data.filter_modified_time.is_some() || walk_data.filter_changed_time.is_some() { - let size_inode_device = get_metadata(entry.path(), false); + let size_inode_device = get_metadata(entry.path(), false, follow_links); if let Some((_, _, (modified_time, accessed_time, changed_time))) = size_inode_device { if entry.path().is_file() && [ @@ -251,7 +253,15 @@ fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option { } vec![] }; - build_node(dir, children, false, false, depth, walk_data) + let is_symlink = if walk_data.follow_links { + match fs::symlink_metadata(&dir) { + Ok(metadata) => metadata.file_type().is_symlink(), + Err(_) => false, + } + } else { + false + }; + build_node(dir, children, is_symlink, false, depth, walk_data) } mod tests { diff --git a/src/main.rs b/src/main.rs index 62e70cc..e02feef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -228,7 +228,7 @@ fn main() { let follow_links = options.get_flag("dereference_links"); let allowed_filesystems = limit_filesystem - .then(|| get_filesystem_devices(&target_dirs)) + .then(|| get_filesystem_devices(&target_dirs, follow_links)) .unwrap_or_default(); let simplified_dirs = simplify_dir_names(&target_dirs); diff --git a/src/node.rs b/src/node.rs index 08b28f3..d9a5359 100644 --- a/src/node.rs +++ b/src/node.rs @@ -28,16 +28,16 @@ pub fn build_node( let use_apparent_size = walk_data.use_apparent_size; let by_filecount = walk_data.by_filecount; - get_metadata(&dir, use_apparent_size).map(|data| { - let inode_device = if is_symlink && !use_apparent_size { - None - } else { - data.1 - }; + get_metadata( + &dir, + use_apparent_size, + walk_data.follow_links && is_symlink, + ) + .map(|data| { + let inode_device = data.1; let size = if is_filtered_out_due_to_regex(walk_data.filter_regex, &dir) || is_filtered_out_due_to_invert_regex(walk_data.invert_filter_regex, &dir) - || (is_symlink && !use_apparent_size) || by_filecount && !is_file || [ (&walk_data.filter_modified_time, data.2 .0), diff --git a/src/platform.rs b/src/platform.rs index 763e90d..1a3dc23 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -17,9 +17,15 @@ type FileTime = (i64, i64, i64); pub fn get_metadata>( path: P, use_apparent_size: bool, + follow_links: bool, ) -> Option<(u64, Option, FileTime)> { use std::os::unix::fs::MetadataExt; - match path.as_ref().metadata() { + let metadata = if follow_links { + path.as_ref().metadata() + } else { + path.as_ref().symlink_metadata() + }; + match metadata { Ok(md) => { if use_apparent_size { Some(( @@ -43,6 +49,7 @@ pub fn get_metadata>( pub fn get_metadata>( path: P, use_apparent_size: bool, + follow_links: bool, ) -> Option<(u64, Option, FileTime)> { // On windows opening the file to get size, file ID and volume can be very // expensive because 1) it causes a few system calls, and more importantly 2) it can cause @@ -142,7 +149,12 @@ pub fn get_metadata>( use std::os::windows::fs::MetadataExt; let path = path.as_ref(); - match path.metadata() { + let metadata = if follow_links { + path.metadata() + } else { + path.symlink_metadata() + }; + match metadata { Ok(ref md) => { const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20; const FILE_ATTRIBUTE_READONLY: u32 = 0x01; diff --git a/src/utils.rs b/src/utils.rs index 9be611c..e82f8a8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -34,13 +34,25 @@ pub fn simplify_dir_names>(dirs: &[P]) -> HashSet { top_level_names } -pub fn get_filesystem_devices>(paths: &[P]) -> HashSet { +pub fn get_filesystem_devices>(paths: &[P], follow_links: bool) -> HashSet { + use std::fs; // Gets the device ids for the filesystems which are used by the argument paths paths .iter() - .filter_map(|p| match get_metadata(p, false) { - Some((_size, Some((_id, dev)), _time)) => Some(dev), - _ => None, + .filter_map(|p| { + let follow_links = if follow_links { + // slow path: If dereference-links is set, then we check if the file is a symbolic link + match fs::symlink_metadata(p) { + Ok(metadata) => metadata.file_type().is_symlink(), + Err(_) => false, + } + } else { + false + }; + match get_metadata(p, false, follow_links) { + Some((_size, Some((_id, dev)), _time)) => Some(dev), + _ => None, + } }) .collect() }