Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for following symlinks #131

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -26,6 +26,9 @@ predicates = "2"
serial_test = "0.5"
tempfile = "3"

[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
-print0
-printf
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 @@ -191,60 +185,46 @@ fn regex_types() {
.stdout(predicate::str::contains("teeest"));
}

#[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