2019-09-15 14:42:48 +02:00
|
|
|
use std::ffi::OsStr;
|
2019-01-01 22:52:08 +01:00
|
|
|
use std::io;
|
2021-11-26 23:43:43 +01:00
|
|
|
use std::mem;
|
2021-11-09 11:01:09 +01:00
|
|
|
use std::path::PathBuf;
|
2017-11-22 23:05:09 +01:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
2021-12-28 18:11:32 +01:00
|
|
|
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
|
2018-04-13 22:46:17 +02:00
|
|
|
use std::sync::{Arc, Mutex};
|
2017-10-10 08:01:17 +02:00
|
|
|
use std::thread;
|
2021-11-26 23:43:43 +01:00
|
|
|
use std::time::{Duration, Instant};
|
2021-11-15 09:02:20 +01:00
|
|
|
use std::{borrow::Cow, io::Write};
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2020-04-03 21:18:54 +02:00
|
|
|
use anyhow::{anyhow, Result};
|
2017-10-22 23:00:19 +02:00
|
|
|
use ignore::overrides::OverrideBuilder;
|
2018-04-13 22:46:17 +02:00
|
|
|
use ignore::{self, WalkBuilder};
|
2019-09-15 14:42:48 +02:00
|
|
|
use regex::bytes::Regex;
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2021-08-23 13:31:01 +02:00
|
|
|
use crate::config::Config;
|
2021-11-30 08:51:16 +01:00
|
|
|
use crate::dir_entry::DirEntry;
|
2020-04-03 21:18:54 +02:00
|
|
|
use crate::error::print_error;
|
|
|
|
use crate::exec;
|
|
|
|
use crate::exit_codes::{merge_exitcodes, ExitCode};
|
|
|
|
use crate::filesystem;
|
|
|
|
use crate::output;
|
|
|
|
|
2017-10-10 08:01:17 +02:00
|
|
|
/// The receiver thread can either be buffering results or directly streaming to the console.
|
2021-11-26 23:43:43 +01:00
|
|
|
#[derive(PartialEq)]
|
2017-10-10 08:01:17 +02:00
|
|
|
enum ReceiverMode {
|
|
|
|
/// Receiver is still buffering in order to sort the results, if the search finishes fast
|
|
|
|
/// enough.
|
|
|
|
Buffering,
|
|
|
|
|
|
|
|
/// Receiver is directly printing results to the output.
|
|
|
|
Streaming,
|
|
|
|
}
|
|
|
|
|
2018-09-30 15:01:23 +02:00
|
|
|
/// The Worker threads can result in a valid entry having PathBuf or an error.
|
|
|
|
pub enum WorkerResult {
|
2021-11-09 11:01:09 +01:00
|
|
|
Entry(DirEntry),
|
2018-09-30 22:56:32 +02:00
|
|
|
Error(ignore::Error),
|
2018-09-30 15:01:23 +02:00
|
|
|
}
|
|
|
|
|
2020-04-03 11:51:50 +02:00
|
|
|
/// Maximum size of the output buffer before flushing results to the console
|
|
|
|
pub const MAX_BUFFER_LENGTH: usize = 1000;
|
2021-08-21 22:44:35 +02:00
|
|
|
/// Default duration until output buffering switches to streaming.
|
2021-11-26 23:43:43 +01:00
|
|
|
pub const DEFAULT_MAX_BUFFER_TIME: Duration = Duration::from_millis(100);
|
2020-04-03 11:51:50 +02:00
|
|
|
|
2017-10-14 18:04:11 +02:00
|
|
|
/// Recursively scan the given search path for files / pathnames matching the pattern.
|
|
|
|
///
|
|
|
|
/// If the `--exec` argument was supplied, this will create a thread pool for executing
|
|
|
|
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
|
|
|
|
/// path will simply be written to standard output.
|
2021-08-23 13:31:01 +02:00
|
|
|
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> Result<ExitCode> {
|
2017-12-06 23:52:23 +01:00
|
|
|
let mut path_iter = path_vec.iter();
|
2018-01-01 12:16:43 +01:00
|
|
|
let first_path_buf = path_iter
|
|
|
|
.next()
|
|
|
|
.expect("Error: Path vector can not be empty");
|
2021-12-28 18:11:32 +01:00
|
|
|
let (tx, rx) = channel();
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2017-12-06 23:52:23 +01:00
|
|
|
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
|
2017-10-22 23:00:19 +02:00
|
|
|
|
2017-10-26 21:13:56 +02:00
|
|
|
for pattern in &config.exclude_patterns {
|
2020-04-03 21:18:54 +02:00
|
|
|
override_builder
|
|
|
|
.add(pattern)
|
|
|
|
.map_err(|e| anyhow!("Malformed exclude pattern: {}", e))?;
|
2017-10-22 23:00:19 +02:00
|
|
|
}
|
2020-04-03 21:18:54 +02:00
|
|
|
let overrides = override_builder
|
|
|
|
.build()
|
|
|
|
.map_err(|_| anyhow!("Mismatch in exclude patterns"))?;
|
2017-10-22 23:00:19 +02:00
|
|
|
|
2017-12-06 23:52:23 +01:00
|
|
|
let mut walker = WalkBuilder::new(first_path_buf.as_path());
|
|
|
|
walker
|
2017-10-12 08:01:51 +02:00
|
|
|
.hidden(config.ignore_hidden)
|
2018-10-27 18:11:50 +02:00
|
|
|
.ignore(config.read_fdignore)
|
2021-12-23 07:38:00 +01:00
|
|
|
.parents(config.read_parent_ignore && (config.read_fdignore || config.read_vcsignore))
|
2018-02-21 21:41:52 +01:00
|
|
|
.git_ignore(config.read_vcsignore)
|
|
|
|
.git_global(config.read_vcsignore)
|
|
|
|
.git_exclude(config.read_vcsignore)
|
2017-10-22 23:00:19 +02:00
|
|
|
.overrides(overrides)
|
2017-10-12 08:01:51 +02:00
|
|
|
.follow_links(config.follow_links)
|
2019-12-30 22:05:42 +01:00
|
|
|
// No need to check for supported platforms, option is unavailable on unsupported ones
|
|
|
|
.same_file_system(config.one_file_system)
|
2017-12-06 23:52:23 +01:00
|
|
|
.max_depth(config.max_depth);
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2018-02-21 21:41:52 +01:00
|
|
|
if config.read_fdignore {
|
|
|
|
walker.add_custom_ignore_filename(".fdignore");
|
|
|
|
}
|
|
|
|
|
2020-04-25 21:32:17 +02:00
|
|
|
if config.read_global_ignore {
|
|
|
|
#[cfg(target_os = "macos")]
|
2020-05-19 14:01:00 +02:00
|
|
|
let config_dir_op = std::env::var_os("XDG_CONFIG_HOME")
|
2020-04-25 21:32:17 +02:00
|
|
|
.map(PathBuf::from)
|
|
|
|
.filter(|p| p.is_absolute())
|
2020-10-09 18:11:15 +02:00
|
|
|
.or_else(|| dirs_next::home_dir().map(|d| d.join(".config")));
|
2020-04-25 21:32:17 +02:00
|
|
|
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
2020-10-09 18:11:15 +02:00
|
|
|
let config_dir_op = dirs_next::config_dir();
|
2020-04-25 21:32:17 +02:00
|
|
|
|
|
|
|
if let Some(global_ignore_file) = config_dir_op
|
|
|
|
.map(|p| p.join("fd").join("ignore"))
|
|
|
|
.filter(|p| p.is_file())
|
|
|
|
{
|
|
|
|
let result = walker.add_ignore(global_ignore_file);
|
|
|
|
match result {
|
|
|
|
Some(ignore::Error::Partial(_)) => (),
|
|
|
|
Some(err) => {
|
2022-02-28 09:16:42 +01:00
|
|
|
print_error(format!("Malformed pattern in global ignore file. {}.", err));
|
2020-04-25 21:32:17 +02:00
|
|
|
}
|
|
|
|
None => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-26 00:15:01 +02:00
|
|
|
for ignore_file in &config.ignore_files {
|
|
|
|
let result = walker.add_ignore(ignore_file);
|
2019-01-26 01:22:06 +01:00
|
|
|
match result {
|
|
|
|
Some(ignore::Error::Partial(_)) => (),
|
|
|
|
Some(err) => {
|
2022-02-28 09:16:42 +01:00
|
|
|
print_error(format!("Malformed pattern in custom ignore file. {}.", err));
|
2018-03-26 00:15:01 +02:00
|
|
|
}
|
2019-01-26 01:22:06 +01:00
|
|
|
None => (),
|
2018-03-26 00:15:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-06 23:52:23 +01:00
|
|
|
for path_entry in path_iter {
|
|
|
|
walker.add(path_entry.as_path());
|
|
|
|
}
|
|
|
|
|
2019-01-26 01:16:53 +01:00
|
|
|
let parallel_walker = walker.threads(config.threads).build_parallel();
|
2017-11-22 23:05:09 +01:00
|
|
|
|
2021-12-05 17:52:18 +01:00
|
|
|
// Flag for cleanly shutting down the parallel walk
|
|
|
|
let quit_flag = Arc::new(AtomicBool::new(false));
|
|
|
|
// Flag specifically for quitting due to ^C
|
|
|
|
let interrupt_flag = Arc::new(AtomicBool::new(false));
|
|
|
|
|
2018-01-24 19:44:17 +01:00
|
|
|
if config.ls_colors.is_some() && config.command.is_none() {
|
2021-12-05 17:52:18 +01:00
|
|
|
let quit_flag = Arc::clone(&quit_flag);
|
|
|
|
let interrupt_flag = Arc::clone(&interrupt_flag);
|
|
|
|
|
2018-01-01 12:16:43 +01:00
|
|
|
ctrlc::set_handler(move || {
|
2021-12-05 17:52:18 +01:00
|
|
|
quit_flag.store(true, Ordering::Relaxed);
|
|
|
|
|
|
|
|
if interrupt_flag.fetch_or(true, Ordering::Relaxed) {
|
2019-09-15 13:05:53 +02:00
|
|
|
// Ctrl-C has been pressed twice, exit NOW
|
2021-11-14 22:31:38 +01:00
|
|
|
ExitCode::KilledBySigint.exit();
|
2019-09-15 13:05:53 +02:00
|
|
|
}
|
2018-09-27 23:01:38 +02:00
|
|
|
})
|
|
|
|
.unwrap();
|
2017-11-22 23:05:09 +01:00
|
|
|
}
|
2017-12-06 23:52:23 +01:00
|
|
|
|
2017-10-10 08:01:17 +02:00
|
|
|
// Spawn the thread that receives all results through the channel.
|
2021-12-05 17:52:18 +01:00
|
|
|
let receiver_thread = spawn_receiver(&config, &quit_flag, &interrupt_flag, rx);
|
2019-01-26 01:01:01 +01:00
|
|
|
|
|
|
|
// Spawn the sender threads.
|
2021-12-05 17:52:18 +01:00
|
|
|
spawn_senders(&config, &quit_flag, pattern, parallel_walker, tx);
|
2019-01-26 01:01:01 +01:00
|
|
|
|
|
|
|
// Wait for the receiver thread to print out all results.
|
2019-09-13 22:26:27 +02:00
|
|
|
let exit_code = receiver_thread.join().unwrap();
|
2019-01-26 01:01:01 +01:00
|
|
|
|
2021-12-05 17:52:18 +01:00
|
|
|
if interrupt_flag.load(Ordering::Relaxed) {
|
2020-04-03 21:34:59 +02:00
|
|
|
Ok(ExitCode::KilledBySigint)
|
|
|
|
} else {
|
|
|
|
Ok(exit_code)
|
2019-01-26 01:01:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-26 23:43:43 +01:00
|
|
|
/// Wrapper for the receiver thread's buffering behavior.
|
|
|
|
struct ReceiverBuffer<W> {
|
|
|
|
/// The configuration.
|
|
|
|
config: Arc<Config>,
|
2021-12-05 17:52:18 +01:00
|
|
|
/// For shutting down the senders.
|
|
|
|
quit_flag: Arc<AtomicBool>,
|
2021-11-26 23:43:43 +01:00
|
|
|
/// The ^C notifier.
|
2021-12-05 17:52:18 +01:00
|
|
|
interrupt_flag: Arc<AtomicBool>,
|
2021-11-26 23:43:43 +01:00
|
|
|
/// Receiver for worker results.
|
|
|
|
rx: Receiver<WorkerResult>,
|
|
|
|
/// Standard output.
|
|
|
|
stdout: W,
|
|
|
|
/// The current buffer mode.
|
|
|
|
mode: ReceiverMode,
|
|
|
|
/// The deadline to switch to streaming mode.
|
|
|
|
deadline: Instant,
|
|
|
|
/// The buffer of quickly received paths.
|
2021-11-09 11:01:09 +01:00
|
|
|
buffer: Vec<DirEntry>,
|
2021-11-26 23:43:43 +01:00
|
|
|
/// Result count.
|
|
|
|
num_results: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<W: Write> ReceiverBuffer<W> {
|
|
|
|
/// Create a new receiver buffer.
|
|
|
|
fn new(
|
|
|
|
config: Arc<Config>,
|
2021-12-05 17:52:18 +01:00
|
|
|
quit_flag: Arc<AtomicBool>,
|
|
|
|
interrupt_flag: Arc<AtomicBool>,
|
2021-11-26 23:43:43 +01:00
|
|
|
rx: Receiver<WorkerResult>,
|
|
|
|
stdout: W,
|
|
|
|
) -> Self {
|
|
|
|
let max_buffer_time = config.max_buffer_time.unwrap_or(DEFAULT_MAX_BUFFER_TIME);
|
|
|
|
let deadline = Instant::now() + max_buffer_time;
|
|
|
|
|
|
|
|
Self {
|
|
|
|
config,
|
2021-12-05 17:52:18 +01:00
|
|
|
quit_flag,
|
|
|
|
interrupt_flag,
|
2021-11-26 23:43:43 +01:00
|
|
|
rx,
|
|
|
|
stdout,
|
|
|
|
mode: ReceiverMode::Buffering,
|
|
|
|
deadline,
|
|
|
|
buffer: Vec::with_capacity(MAX_BUFFER_LENGTH),
|
|
|
|
num_results: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Process results until finished.
|
|
|
|
fn process(&mut self) -> ExitCode {
|
|
|
|
loop {
|
|
|
|
if let Err(ec) = self.poll() {
|
2021-12-05 17:52:18 +01:00
|
|
|
self.quit_flag.store(true, Ordering::Relaxed);
|
2021-11-26 23:43:43 +01:00
|
|
|
return ec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Receive the next worker result.
|
|
|
|
fn recv(&self) -> Result<WorkerResult, RecvTimeoutError> {
|
|
|
|
match self.mode {
|
|
|
|
ReceiverMode::Buffering => {
|
|
|
|
// Wait at most until we should switch to streaming
|
2021-12-28 18:11:32 +01:00
|
|
|
let now = Instant::now();
|
|
|
|
self.deadline
|
|
|
|
.checked_duration_since(now)
|
|
|
|
.ok_or(RecvTimeoutError::Timeout)
|
|
|
|
.and_then(|t| self.rx.recv_timeout(t))
|
2021-11-26 23:43:43 +01:00
|
|
|
}
|
|
|
|
ReceiverMode::Streaming => {
|
|
|
|
// Wait however long it takes for a result
|
|
|
|
Ok(self.rx.recv()?)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Wait for a result or state change.
|
|
|
|
fn poll(&mut self) -> Result<(), ExitCode> {
|
|
|
|
match self.recv() {
|
2021-11-09 11:01:09 +01:00
|
|
|
Ok(WorkerResult::Entry(dir_entry)) => {
|
2021-11-26 23:43:43 +01:00
|
|
|
if self.config.quiet {
|
|
|
|
return Err(ExitCode::HasResults(true));
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.mode {
|
|
|
|
ReceiverMode::Buffering => {
|
2021-11-09 11:01:09 +01:00
|
|
|
self.buffer.push(dir_entry);
|
2021-11-26 23:43:43 +01:00
|
|
|
if self.buffer.len() > MAX_BUFFER_LENGTH {
|
|
|
|
self.stream()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ReceiverMode::Streaming => {
|
2021-11-09 11:01:09 +01:00
|
|
|
self.print(&dir_entry)?;
|
2021-11-26 23:43:43 +01:00
|
|
|
self.flush()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.num_results += 1;
|
|
|
|
if let Some(max_results) = self.config.max_results {
|
|
|
|
if self.num_results >= max_results {
|
|
|
|
return self.stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(WorkerResult::Error(err)) => {
|
|
|
|
if self.config.show_filesystem_errors {
|
|
|
|
print_error(err.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(RecvTimeoutError::Timeout) => {
|
|
|
|
self.stream()?;
|
|
|
|
}
|
|
|
|
Err(RecvTimeoutError::Disconnected) => {
|
|
|
|
return self.stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Output a path.
|
2021-11-09 11:01:09 +01:00
|
|
|
fn print(&mut self, entry: &DirEntry) -> Result<(), ExitCode> {
|
|
|
|
output::print_entry(&mut self.stdout, entry, &self.config);
|
2021-12-05 17:23:54 +01:00
|
|
|
|
2021-12-05 17:52:18 +01:00
|
|
|
if self.interrupt_flag.load(Ordering::Relaxed) {
|
2021-12-05 17:23:54 +01:00
|
|
|
// Ignore any errors on flush, because we're about to exit anyway
|
|
|
|
let _ = self.flush();
|
|
|
|
return Err(ExitCode::KilledBySigint);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2021-11-26 23:43:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Switch ourselves into streaming mode.
|
|
|
|
fn stream(&mut self) -> Result<(), ExitCode> {
|
|
|
|
self.mode = ReceiverMode::Streaming;
|
|
|
|
|
|
|
|
let buffer = mem::take(&mut self.buffer);
|
|
|
|
for path in buffer {
|
2021-12-05 17:23:54 +01:00
|
|
|
self.print(&path)?;
|
2021-11-26 23:43:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
self.flush()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Stop looping.
|
|
|
|
fn stop(&mut self) -> Result<(), ExitCode> {
|
|
|
|
if self.mode == ReceiverMode::Buffering {
|
2021-11-09 11:01:09 +01:00
|
|
|
self.buffer.sort_by(|e1, e2| e1.path().cmp(e2.path()));
|
2021-11-26 23:43:43 +01:00
|
|
|
self.stream()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.config.quiet {
|
|
|
|
Err(ExitCode::HasResults(self.num_results > 0))
|
|
|
|
} else {
|
|
|
|
Err(ExitCode::Success)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Flush stdout if necessary.
|
|
|
|
fn flush(&mut self) -> Result<(), ExitCode> {
|
|
|
|
if self.config.interactive_terminal && self.stdout.flush().is_err() {
|
|
|
|
// Probably a broken pipe. Exit gracefully.
|
|
|
|
return Err(ExitCode::GeneralError);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-26 01:01:01 +01:00
|
|
|
fn spawn_receiver(
|
2021-08-23 13:31:01 +02:00
|
|
|
config: &Arc<Config>,
|
2021-12-05 17:52:18 +01:00
|
|
|
quit_flag: &Arc<AtomicBool>,
|
|
|
|
interrupt_flag: &Arc<AtomicBool>,
|
2019-01-26 01:01:01 +01:00
|
|
|
rx: Receiver<WorkerResult>,
|
2019-09-13 22:26:27 +02:00
|
|
|
) -> thread::JoinHandle<ExitCode> {
|
2019-01-26 01:12:55 +01:00
|
|
|
let config = Arc::clone(config);
|
2021-12-05 17:52:18 +01:00
|
|
|
let quit_flag = Arc::clone(quit_flag);
|
|
|
|
let interrupt_flag = Arc::clone(interrupt_flag);
|
2019-01-26 01:12:55 +01:00
|
|
|
|
|
|
|
let show_filesystem_errors = config.show_filesystem_errors;
|
|
|
|
let threads = config.threads;
|
2020-06-23 21:28:15 +02:00
|
|
|
// This will be used to check if output should be buffered when only running a single thread
|
2021-08-09 09:02:30 +02:00
|
|
|
let enable_output_buffering: bool = threads > 1;
|
2019-01-26 01:01:01 +01:00
|
|
|
thread::spawn(move || {
|
2017-10-14 18:04:11 +02:00
|
|
|
// This will be set to `Some` if the `--exec` argument was supplied.
|
2019-01-26 01:12:55 +01:00
|
|
|
if let Some(ref cmd) = config.command {
|
2018-11-12 18:43:40 +01:00
|
|
|
if cmd.in_batch_mode() {
|
2022-02-28 08:39:52 +01:00
|
|
|
exec::batch(rx, cmd, show_filesystem_errors, config.batch_size)
|
2018-11-11 18:00:01 +01:00
|
|
|
} else {
|
|
|
|
let shared_rx = Arc::new(Mutex::new(rx));
|
|
|
|
|
|
|
|
let out_perm = Arc::new(Mutex::new(()));
|
|
|
|
|
|
|
|
// Each spawned job will store it's thread handle in here.
|
|
|
|
let mut handles = Vec::with_capacity(threads);
|
|
|
|
for _ in 0..threads {
|
|
|
|
let rx = Arc::clone(&shared_rx);
|
2019-01-29 23:09:45 +01:00
|
|
|
let cmd = Arc::clone(cmd);
|
2018-11-11 18:00:01 +01:00
|
|
|
let out_perm = Arc::clone(&out_perm);
|
|
|
|
|
|
|
|
// Spawn a job thread that will listen for and execute inputs.
|
2021-08-09 09:02:30 +02:00
|
|
|
let handle = thread::spawn(move || {
|
|
|
|
exec::job(
|
|
|
|
rx,
|
|
|
|
cmd,
|
|
|
|
out_perm,
|
|
|
|
show_filesystem_errors,
|
|
|
|
enable_output_buffering,
|
|
|
|
)
|
|
|
|
});
|
2018-11-11 18:00:01 +01:00
|
|
|
|
|
|
|
// Push the handle of the spawned thread into the vector for later joining.
|
|
|
|
handles.push(handle);
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2018-11-11 18:00:01 +01:00
|
|
|
// Wait for all threads to exit before exiting the program.
|
2021-08-21 22:43:17 +02:00
|
|
|
let exit_codes = handles
|
|
|
|
.into_iter()
|
|
|
|
.map(|handle| handle.join().unwrap())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
merge_exitcodes(exit_codes)
|
2017-10-14 20:04:04 +02:00
|
|
|
}
|
2017-10-14 18:04:11 +02:00
|
|
|
} else {
|
2019-01-01 22:52:08 +01:00
|
|
|
let stdout = io::stdout();
|
2021-11-15 09:02:20 +01:00
|
|
|
let stdout = stdout.lock();
|
2021-11-26 23:43:43 +01:00
|
|
|
let stdout = io::BufWriter::new(stdout);
|
2021-08-14 17:57:01 +02:00
|
|
|
|
2021-12-05 17:52:18 +01:00
|
|
|
let mut rxbuffer = ReceiverBuffer::new(config, quit_flag, interrupt_flag, rx, stdout);
|
2021-11-26 23:43:43 +01:00
|
|
|
rxbuffer.process()
|
2017-10-10 08:01:17 +02:00
|
|
|
}
|
2019-01-26 01:01:01 +01:00
|
|
|
})
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2019-01-26 01:01:01 +01:00
|
|
|
fn spawn_senders(
|
2021-08-23 13:31:01 +02:00
|
|
|
config: &Arc<Config>,
|
2021-12-05 17:52:18 +01:00
|
|
|
quit_flag: &Arc<AtomicBool>,
|
2019-01-26 01:01:01 +01:00
|
|
|
pattern: Arc<Regex>,
|
|
|
|
parallel_walker: ignore::WalkParallel,
|
|
|
|
tx: Sender<WorkerResult>,
|
|
|
|
) {
|
2017-12-06 23:52:23 +01:00
|
|
|
parallel_walker.run(|| {
|
2019-01-26 01:12:55 +01:00
|
|
|
let config = Arc::clone(config);
|
2017-10-10 08:01:17 +02:00
|
|
|
let pattern = Arc::clone(&pattern);
|
|
|
|
let tx_thread = tx.clone();
|
2021-12-05 17:52:18 +01:00
|
|
|
let quit_flag = Arc::clone(quit_flag);
|
2017-10-10 08:01:17 +02:00
|
|
|
|
|
|
|
Box::new(move |entry_o| {
|
2021-12-05 17:52:18 +01:00
|
|
|
if quit_flag.load(Ordering::Relaxed) {
|
2018-01-03 09:26:11 +01:00
|
|
|
return ignore::WalkState::Quit;
|
|
|
|
}
|
|
|
|
|
2017-10-10 08:01:17 +02:00
|
|
|
let entry = match entry_o {
|
2020-02-28 18:42:35 +01:00
|
|
|
Ok(ref e) if e.depth() == 0 => {
|
2020-02-28 18:19:54 +01:00
|
|
|
// Skip the root directory entry.
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
2021-10-11 18:21:27 +02:00
|
|
|
Ok(e) => DirEntry::normal(e),
|
2020-02-28 18:19:54 +01:00
|
|
|
Err(ignore::Error::WithPath {
|
|
|
|
path,
|
|
|
|
err: inner_err,
|
|
|
|
}) => match inner_err.as_ref() {
|
2020-02-28 18:26:09 +01:00
|
|
|
ignore::Error::Io(io_error)
|
|
|
|
if io_error.kind() == io::ErrorKind::NotFound
|
|
|
|
&& path
|
|
|
|
.symlink_metadata()
|
2020-02-28 18:42:35 +01:00
|
|
|
.ok()
|
2020-02-28 18:26:09 +01:00
|
|
|
.map_or(false, |m| m.file_type().is_symlink()) =>
|
|
|
|
{
|
2021-10-11 18:21:27 +02:00
|
|
|
DirEntry::broken_symlink(path)
|
2020-02-28 18:19:54 +01:00
|
|
|
}
|
|
|
|
_ => {
|
2021-08-09 09:57:53 +02:00
|
|
|
return match tx_thread.send(WorkerResult::Error(ignore::Error::WithPath {
|
2020-10-27 20:26:34 +01:00
|
|
|
path,
|
|
|
|
err: inner_err,
|
|
|
|
})) {
|
2021-08-09 09:57:53 +02:00
|
|
|
Ok(_) => ignore::WalkState::Continue,
|
|
|
|
Err(_) => ignore::WalkState::Quit,
|
2020-10-27 20:26:34 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2021-08-09 09:57:53 +02:00
|
|
|
Err(err) => {
|
|
|
|
return match tx_thread.send(WorkerResult::Error(err)) {
|
|
|
|
Ok(_) => ignore::WalkState::Continue,
|
|
|
|
Err(_) => ignore::WalkState::Quit,
|
2020-10-27 20:26:34 +01:00
|
|
|
}
|
2021-08-09 09:57:53 +02:00
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
};
|
|
|
|
|
2020-04-15 16:17:01 +02:00
|
|
|
if let Some(min_depth) = config.min_depth {
|
|
|
|
if entry.depth().map_or(true, |d| d < min_depth) {
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-26 03:17:42 +02:00
|
|
|
// Check the name first, since it doesn't require metadata
|
|
|
|
let entry_path = entry.path();
|
|
|
|
|
2019-09-15 14:42:48 +02:00
|
|
|
let search_str: Cow<OsStr> = if config.search_full_path {
|
2020-04-03 21:18:54 +02:00
|
|
|
let path_abs_buf = filesystem::path_absolute_form(entry_path)
|
|
|
|
.expect("Retrieving absolute path succeeds");
|
|
|
|
Cow::Owned(path_abs_buf.as_os_str().to_os_string())
|
2019-04-26 03:17:42 +02:00
|
|
|
} else {
|
2019-09-15 14:42:48 +02:00
|
|
|
match entry_path.file_name() {
|
|
|
|
Some(filename) => Cow::Borrowed(filename),
|
|
|
|
None => unreachable!(
|
|
|
|
"Encountered file system entry without a file name. This should only \
|
|
|
|
happen for paths like 'foo/bar/..' or '/' which are not supposed to \
|
|
|
|
appear in a file system traversal."
|
|
|
|
),
|
|
|
|
}
|
2019-04-26 03:17:42 +02:00
|
|
|
};
|
|
|
|
|
2020-04-03 12:04:47 +02:00
|
|
|
if !pattern.is_match(&filesystem::osstr_to_bytes(search_str.as_ref())) {
|
2019-09-15 14:42:48 +02:00
|
|
|
return ignore::WalkState::Continue;
|
2019-04-26 03:17:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Filter out unwanted extensions.
|
|
|
|
if let Some(ref exts_regex) = config.extensions {
|
2019-09-15 14:42:48 +02:00
|
|
|
if let Some(path_str) = entry_path.file_name() {
|
2020-04-03 12:04:47 +02:00
|
|
|
if !exts_regex.is_match(&filesystem::osstr_to_bytes(path_str)) {
|
2019-04-26 03:17:42 +02:00
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-10 08:01:17 +02:00
|
|
|
// Filter out unwanted file types.
|
2018-02-25 11:11:14 +01:00
|
|
|
if let Some(ref file_types) = config.file_types {
|
2021-08-10 09:46:12 +02:00
|
|
|
if file_types.should_ignore(&entry) {
|
2018-02-25 11:11:14 +01:00
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
}
|
|
|
|
|
2018-06-30 21:57:20 +02:00
|
|
|
#[cfg(unix)]
|
|
|
|
{
|
|
|
|
if let Some(ref owner_constraint) = config.owner_constraint {
|
2021-10-11 18:21:27 +02:00
|
|
|
if let Some(metadata) = entry.metadata() {
|
2021-07-27 08:48:44 +02:00
|
|
|
if !owner_constraint.matches(metadata) {
|
2018-06-30 21:57:20 +02:00
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-23 01:38:10 +02:00
|
|
|
// Filter out unwanted sizes if it is a file and we have been given size constraints.
|
2019-01-26 02:13:16 +01:00
|
|
|
if !config.size_constraints.is_empty() {
|
2018-04-25 08:25:02 +02:00
|
|
|
if entry_path.is_file() {
|
2021-10-11 18:21:27 +02:00
|
|
|
if let Some(metadata) = entry.metadata() {
|
2018-04-25 08:25:02 +02:00
|
|
|
let file_size = metadata.len();
|
|
|
|
if config
|
|
|
|
.size_constraints
|
|
|
|
.iter()
|
|
|
|
.any(|sc| !sc.is_within(file_size))
|
|
|
|
{
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
} else {
|
2018-04-23 01:38:10 +02:00
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
2018-04-25 08:25:02 +02:00
|
|
|
} else {
|
|
|
|
return ignore::WalkState::Continue;
|
2018-04-23 01:38:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-09 13:47:42 +02:00
|
|
|
// Filter out unwanted modification times
|
2018-10-10 12:13:19 +02:00
|
|
|
if !config.time_constraints.is_empty() {
|
|
|
|
let mut matched = false;
|
2021-10-11 18:21:27 +02:00
|
|
|
if let Some(metadata) = entry.metadata() {
|
2019-09-15 16:23:14 +02:00
|
|
|
if let Ok(modified) = metadata.modified() {
|
|
|
|
matched = config
|
|
|
|
.time_constraints
|
|
|
|
.iter()
|
|
|
|
.all(|tf| tf.applies_to(&modified));
|
2018-10-09 13:47:42 +02:00
|
|
|
}
|
|
|
|
}
|
2018-10-10 12:13:19 +02:00
|
|
|
if !matched {
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
2018-10-09 13:47:42 +02:00
|
|
|
}
|
|
|
|
|
2021-11-09 11:01:09 +01:00
|
|
|
let send_result = tx_thread.send(WorkerResult::Entry(entry));
|
2019-12-20 19:27:10 +01:00
|
|
|
|
2020-04-03 10:08:47 +02:00
|
|
|
if send_result.is_err() {
|
2019-12-20 19:27:10 +01:00
|
|
|
return ignore::WalkState::Quit;
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2020-10-25 08:16:01 +01:00
|
|
|
// Apply pruning.
|
|
|
|
if config.prune {
|
|
|
|
return ignore::WalkState::Skip;
|
|
|
|
}
|
|
|
|
|
2017-10-10 08:01:17 +02:00
|
|
|
ignore::WalkState::Continue
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|