Skip to content

Commit

Permalink
Implement support for following symlinks
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Gonzalez <[email protected]>
  • Loading branch information
refi64 committed Jan 22, 2022
1 parent 50d2bde commit 80c0577
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 84 deletions.
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ filetime = "0.2"
predicates = "2"
serial_test = "0.5"

[patch.crates-io]
walkdir = { git = "https://github.com/refi64/walkdir", branch = "symlinks" }

[[bin]]
name = "find"
path = "src/find/main.rs"
Expand Down
34 changes: 11 additions & 23 deletions src/find/matchers/printf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,13 @@ fn get_starting_point(file_info: &walkdir::DirEntry) -> &Path {
.unwrap()
}

fn format_non_link_file_type(file_type: fs::FileType) -> char {
fn format_file_type(file_type: fs::FileType) -> char {
if file_type.is_file() {
'f'
} else if file_type.is_dir() {
'd'
} else if file_type.is_symlink() {
'l'
} else {
#[cfg(unix)]
if file_type.is_block_device() {
Expand All @@ -377,19 +379,7 @@ fn format_directive<'entry>(
directive: &FormatDirective,
meta_cell: &OnceCell<fs::Metadata>,
) -> Result<Cow<'entry, str>, Box<dyn Error>> {
let meta = || {
meta_cell.get_or_try_init(|| {
if file_info.path_is_symlink() && !file_info.file_type().is_symlink() {
// The file_info already followed the symlink, meaning that the
// metadata will be for the target file, which isn't the
// behavior we want, so manually re-compute the metadata for the
// symlink itself instead.
file_info.path().symlink_metadata()
} else {
file_info.metadata().map_err(|e| e.into())
}
})
};
let meta = || meta_cell.get_or_try_init(|| file_info.metadata());

// NOTE ON QUOTING:
// GNU find's man page claims that several directives that print names (like
Expand Down Expand Up @@ -542,7 +532,7 @@ fn format_directive<'entry>(
FormatDirective::StartingPoint => get_starting_point(file_info).to_string_lossy(),

FormatDirective::SymlinkTarget => {
if file_info.path_is_symlink() {
if meta()?.is_symlink() {
fs::read_link(file_info.path())?
.to_string_lossy()
.into_owned()
Expand All @@ -552,10 +542,10 @@ fn format_directive<'entry>(
}
}

FormatDirective::Type { follow_links } => if file_info.path_is_symlink() {
if *follow_links {
FormatDirective::Type { follow_links } => {
if file_info.file_type().is_symlink() && *follow_links {
match file_info.path().metadata() {
Ok(meta) => format_non_link_file_type(meta.file_type()),
Ok(meta) => format_file_type(meta.file_type()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => 'N',
// The ErrorKinds corresponding to ELOOP and ENOTDIR are
// nightly-only:
Expand All @@ -568,13 +558,11 @@ fn format_directive<'entry>(
Err(_) => '?',
}
} else {
'l'
format_file_type(file_info.file_type())
}
} else {
format_non_link_file_type(file_info.file_type())
.to_string()
.into()
}
.to_string()
.into(),

#[cfg(not(unix))]
FormatDirective::User { .. } => "0".into(),
Expand Down
18 changes: 17 additions & 1 deletion src/find/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::time::SystemTime;
use walkdir::WalkDir;

pub struct Config {
follow_links: bool,
depth_first: bool,
min_depth: usize,
max_depth: usize,
Expand All @@ -25,6 +26,7 @@ pub struct Config {
impl Default for Config {
fn default() -> Config {
Config {
follow_links: false,
depth_first: false,
min_depth: 0,
max_depth: usize::max_value(),
Expand Down Expand Up @@ -86,6 +88,16 @@ fn parse_args(args: &[&str]) -> Result<ParsedInfo, Box<dyn Error>> {
let mut i = 0;
let mut config = Config::default();

while i < args.len() {
if args[i] == "-L" {
config.follow_links = true;
} else {
break;
}

i += 1;
}

while i < args.len()
&& (args[i] == "-" || !args[i].starts_with('-'))
&& args[i] != "!"
Expand Down Expand Up @@ -115,7 +127,10 @@ fn process_dir<'a>(
let mut walkdir = WalkDir::new(dir)
.contents_first(config.depth_first)
.max_depth(config.max_depth)
.min_depth(config.min_depth);
.min_depth(config.min_depth)
.follow_links(config.follow_links)
.follow_root_link(config.follow_links)
.yield_link_on_error(true);
if config.sorted_output {
walkdir = walkdir.sort_by(|a, b| a.file_name().cmp(b.file_name()));
}
Expand Down Expand Up @@ -171,6 +186,7 @@ fn print_help() {
If no path is supplied then the current working directory is used by default.
Early alpha implementation. Currently the only expressions supported are
-L
-print
-printf
-name case-sensitive_filename_pattern
Expand Down
62 changes: 62 additions & 0 deletions tests/common/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,65 @@ pub fn get_dir_entry_for(directory: &str, filename: &str) -> DirEntry {
}
panic!("Couldn't find {} in {}", directory, filename);
}

pub fn create_testing_symlinks() {
use std::io::ErrorKind;

#[cfg(unix)]
use std::os::unix::fs::symlink;

#[cfg(windows)]
use std::os::windows::fs::{symlink_dir, symlink_file};

#[cfg(unix)]
{
if let Err(e) = symlink("abbbc", "test_data/links/link-f") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("subdir", "test_data/links/link-d") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("missing", "test_data/links/link-missing") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("abbbc/x", "test_data/links/link-notdir") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("link-loop", "test_data/links/link-loop") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
}
#[cfg(windows)]
{
if let Err(e) = symlink_file("abbbc", "test_data/links/link-f") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink_dir("subdir", "test_data/links/link-d") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink_file("missing", "test_data/links/link-missing") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink_file("abbbc/x", "test_data/links/link-notdir") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
}
}
96 changes: 38 additions & 58 deletions tests/find_cmd_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,10 @@
use assert_cmd::Command;
use predicates::prelude::*;
use serial_test::serial;
use std::env;
use std::fs::File;
use std::{env, io::ErrorKind};
use tempfile::Builder;

#[cfg(unix)]
use std::os::unix::fs::symlink;

#[cfg(windows)]
use std::os::windows::fs::{symlink_dir, symlink_file};

use common::test_helpers::*;

mod common;
Expand Down Expand Up @@ -126,60 +120,46 @@ fn delete_on_dot_dir() {
assert!(temp_dir.path().exists(), "temp dir should still exist");
}

#[serial(working_dir)]
#[test]
fn follow_links() {
create_testing_symlinks();

// By default, we should not follow the root if it's a link.
Command::cargo_bin("find")
.expect("found binary")
.args(&[
&fix_up_slashes("./test_data/links/link-d"),
"-printf",
"%p %y\n",
])
.assert()
.success()
.stderr(predicate::str::is_empty())
.stdout(predicate::str::diff(fix_up_slashes(
"./test_data/links/link-d l\n",
)));

Command::cargo_bin("find")
.expect("found binary")
.args(&[
"-L",
&fix_up_slashes("./test_data/links/link-d"),
"-printf",
"%p %y\n",
])
.assert()
.success()
.stderr(predicate::str::is_empty())
.stdout(predicate::str::diff(fix_up_slashes(
"./test_data/links/link-d d\n./test_data/links/link-d/test f\n",
)));
}

#[serial(working_dir)]
#[test]
fn find_printf() {
#[cfg(unix)]
{
if let Err(e) = symlink("abbbc", "test_data/links/link-f") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("subdir", "test_data/links/link-d") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("missing", "test_data/links/link-missing") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("abbbc/x", "test_data/links/link-notdir") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("link-loop", "test_data/links/link-loop") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
}
#[cfg(windows)]
{
if let Err(e) = symlink_file("abbbc", "test_data/links/link-f") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink_dir("subdir", "test_data/links/link-d") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink_file("missing", "test_data/links/link-missing") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink_file("abbbc/x", "test_data/links/link-notdir") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
}
create_testing_symlinks();

Command::cargo_bin("find")
.expect("found binary")
Expand Down

0 comments on commit 80c0577

Please sign in to comment.