2018-08-30 02:53:52 +02:00
|
|
|
use std::fs::File;
|
2018-08-03 23:26:22 +02:00
|
|
|
use std::io;
|
|
|
|
use std::path::{Path, PathBuf};
|
2018-08-30 02:53:52 +02:00
|
|
|
use std::process::{Command, Stdio};
|
2018-08-03 23:26:22 +02:00
|
|
|
use std::time::Duration;
|
|
|
|
|
2018-08-30 02:53:52 +02:00
|
|
|
use grep::cli;
|
2018-08-03 23:26:22 +02:00
|
|
|
use grep::matcher::Matcher;
|
|
|
|
#[cfg(feature = "pcre2")]
|
2020-02-18 01:08:47 +02:00
|
|
|
use grep::pcre2::RegexMatcher as PCRE2RegexMatcher;
|
|
|
|
use grep::printer::{Standard, Stats, Summary, JSON};
|
|
|
|
use grep::regex::RegexMatcher as RustRegexMatcher;
|
binary: rejigger ripgrep's handling of binary files
This commit attempts to surface binary filtering in a slightly more
user friendly way. Namely, before, ripgrep would silently stop
searching a file if it detected a NUL byte, even if it had previously
printed a match. This can lead to the user quite reasonably assuming
that there are no more matches, since a partial search is fairly
unintuitive. (ripgrep has this behavior by default because it really
wants to NOT search binary files at all, just like it doesn't search
gitignored or hidden files.)
With this commit, if a match has already been printed and ripgrep detects
a NUL byte, then it will print a warning message indicating that the search
stopped prematurely.
Moreover, this commit adds a new flag, --binary, which causes ripgrep to
stop filtering binary files, but in a way that still avoids dumping
binary data into terminals. That is, the --binary flag makes ripgrep
behave more like grep's default behavior.
For files explicitly specified in a search, e.g., `rg foo some-file`,
then no binary filtering is applied (just like no gitignore and no
hidden file filtering is applied). Instead, ripgrep behaves as if you
gave the --binary flag for all explicitly given files.
This was a fairly invasive change, and potentially increases the UX
complexity of ripgrep around binary files. (Before, there were two
binary modes, where as now there are three.) However, ripgrep is now a
bit louder with warning messages when binary file detection might
otherwise be hiding potential matches, so hopefully this is a net
improvement.
Finally, the `-uuu` convenience now maps to `--no-ignore --hidden
--binary`, since this is closer to the actualy intent of the
`--unrestricted` flag, i.e., to reduce ripgrep's smart filtering. As a
consequence, `rg -uuu foo` should now search roughly the same number of
bytes as `grep -r foo`, and `rg -uuua foo` should search roughly the
same number of bytes as `grep -ra foo`. (The "roughly" weasel word is
used because grep's and ripgrep's binary file detection might differ
somewhat---perhaps based on buffer sizes---which can impact exactly what
is and isn't searched.)
See the numerous tests in tests/binary.rs for intended behavior.
Fixes #306, Fixes #855
2019-04-09 01:28:38 +02:00
|
|
|
use grep::searcher::{BinaryDetection, Searcher};
|
2018-09-05 04:45:24 +02:00
|
|
|
use ignore::overrides::Override;
|
2018-08-03 23:26:22 +02:00
|
|
|
use serde_json as json;
|
2019-01-19 17:15:56 +02:00
|
|
|
use serde_json::json;
|
2018-08-03 23:26:22 +02:00
|
|
|
use termcolor::WriteColor;
|
|
|
|
|
2019-01-19 17:15:56 +02:00
|
|
|
use crate::subject::Subject;
|
2018-08-03 23:26:22 +02:00
|
|
|
|
|
|
|
/// The configuration for the search worker. Among a few other things, the
|
|
|
|
/// configuration primarily controls the way we show search results to users
|
|
|
|
/// at a very high level.
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
struct Config {
|
|
|
|
json_stats: bool,
|
|
|
|
preprocessor: Option<PathBuf>,
|
2018-09-05 04:45:24 +02:00
|
|
|
preprocessor_globs: Override,
|
2018-08-03 23:26:22 +02:00
|
|
|
search_zip: bool,
|
binary: rejigger ripgrep's handling of binary files
This commit attempts to surface binary filtering in a slightly more
user friendly way. Namely, before, ripgrep would silently stop
searching a file if it detected a NUL byte, even if it had previously
printed a match. This can lead to the user quite reasonably assuming
that there are no more matches, since a partial search is fairly
unintuitive. (ripgrep has this behavior by default because it really
wants to NOT search binary files at all, just like it doesn't search
gitignored or hidden files.)
With this commit, if a match has already been printed and ripgrep detects
a NUL byte, then it will print a warning message indicating that the search
stopped prematurely.
Moreover, this commit adds a new flag, --binary, which causes ripgrep to
stop filtering binary files, but in a way that still avoids dumping
binary data into terminals. That is, the --binary flag makes ripgrep
behave more like grep's default behavior.
For files explicitly specified in a search, e.g., `rg foo some-file`,
then no binary filtering is applied (just like no gitignore and no
hidden file filtering is applied). Instead, ripgrep behaves as if you
gave the --binary flag for all explicitly given files.
This was a fairly invasive change, and potentially increases the UX
complexity of ripgrep around binary files. (Before, there were two
binary modes, where as now there are three.) However, ripgrep is now a
bit louder with warning messages when binary file detection might
otherwise be hiding potential matches, so hopefully this is a net
improvement.
Finally, the `-uuu` convenience now maps to `--no-ignore --hidden
--binary`, since this is closer to the actualy intent of the
`--unrestricted` flag, i.e., to reduce ripgrep's smart filtering. As a
consequence, `rg -uuu foo` should now search roughly the same number of
bytes as `grep -r foo`, and `rg -uuua foo` should search roughly the
same number of bytes as `grep -ra foo`. (The "roughly" weasel word is
used because grep's and ripgrep's binary file detection might differ
somewhat---perhaps based on buffer sizes---which can impact exactly what
is and isn't searched.)
See the numerous tests in tests/binary.rs for intended behavior.
Fixes #306, Fixes #855
2019-04-09 01:28:38 +02:00
|
|
|
binary_implicit: BinaryDetection,
|
|
|
|
binary_explicit: BinaryDetection,
|
2018-08-03 23:26:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Config {
|
|
|
|
fn default() -> Config {
|
|
|
|
Config {
|
|
|
|
json_stats: false,
|
|
|
|
preprocessor: None,
|
2018-09-05 04:45:24 +02:00
|
|
|
preprocessor_globs: Override::empty(),
|
2018-08-03 23:26:22 +02:00
|
|
|
search_zip: false,
|
binary: rejigger ripgrep's handling of binary files
This commit attempts to surface binary filtering in a slightly more
user friendly way. Namely, before, ripgrep would silently stop
searching a file if it detected a NUL byte, even if it had previously
printed a match. This can lead to the user quite reasonably assuming
that there are no more matches, since a partial search is fairly
unintuitive. (ripgrep has this behavior by default because it really
wants to NOT search binary files at all, just like it doesn't search
gitignored or hidden files.)
With this commit, if a match has already been printed and ripgrep detects
a NUL byte, then it will print a warning message indicating that the search
stopped prematurely.
Moreover, this commit adds a new flag, --binary, which causes ripgrep to
stop filtering binary files, but in a way that still avoids dumping
binary data into terminals. That is, the --binary flag makes ripgrep
behave more like grep's default behavior.
For files explicitly specified in a search, e.g., `rg foo some-file`,
then no binary filtering is applied (just like no gitignore and no
hidden file filtering is applied). Instead, ripgrep behaves as if you
gave the --binary flag for all explicitly given files.
This was a fairly invasive change, and potentially increases the UX
complexity of ripgrep around binary files. (Before, there were two
binary modes, where as now there are three.) However, ripgrep is now a
bit louder with warning messages when binary file detection might
otherwise be hiding potential matches, so hopefully this is a net
improvement.
Finally, the `-uuu` convenience now maps to `--no-ignore --hidden
--binary`, since this is closer to the actualy intent of the
`--unrestricted` flag, i.e., to reduce ripgrep's smart filtering. As a
consequence, `rg -uuu foo` should now search roughly the same number of
bytes as `grep -r foo`, and `rg -uuua foo` should search roughly the
same number of bytes as `grep -ra foo`. (The "roughly" weasel word is
used because grep's and ripgrep's binary file detection might differ
somewhat---perhaps based on buffer sizes---which can impact exactly what
is and isn't searched.)
See the numerous tests in tests/binary.rs for intended behavior.
Fixes #306, Fixes #855
2019-04-09 01:28:38 +02:00
|
|
|
binary_implicit: BinaryDetection::none(),
|
|
|
|
binary_explicit: BinaryDetection::none(),
|
2018-08-03 23:26:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A builder for configuring and constructing a search worker.
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct SearchWorkerBuilder {
|
|
|
|
config: Config,
|
2018-08-30 02:53:52 +02:00
|
|
|
command_builder: cli::CommandReaderBuilder,
|
|
|
|
decomp_builder: cli::DecompressionReaderBuilder,
|
2018-08-03 23:26:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for SearchWorkerBuilder {
|
|
|
|
fn default() -> SearchWorkerBuilder {
|
|
|
|
SearchWorkerBuilder::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SearchWorkerBuilder {
|
|
|
|
/// Create a new builder for configuring and constructing a search worker.
|
|
|
|
pub fn new() -> SearchWorkerBuilder {
|
2018-08-30 02:53:52 +02:00
|
|
|
let mut cmd_builder = cli::CommandReaderBuilder::new();
|
|
|
|
cmd_builder.async_stderr(true);
|
|
|
|
|
|
|
|
let mut decomp_builder = cli::DecompressionReaderBuilder::new();
|
|
|
|
decomp_builder.async_stderr(true);
|
|
|
|
|
|
|
|
SearchWorkerBuilder {
|
|
|
|
config: Config::default(),
|
|
|
|
command_builder: cmd_builder,
|
2020-03-15 15:04:39 +02:00
|
|
|
decomp_builder,
|
2018-08-30 02:53:52 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a new search worker using the given searcher, matcher and
|
|
|
|
/// printer.
|
|
|
|
pub fn build<W: WriteColor>(
|
|
|
|
&self,
|
|
|
|
matcher: PatternMatcher,
|
|
|
|
searcher: Searcher,
|
|
|
|
printer: Printer<W>,
|
|
|
|
) -> SearchWorker<W> {
|
|
|
|
let config = self.config.clone();
|
2018-08-30 02:53:52 +02:00
|
|
|
let command_builder = self.command_builder.clone();
|
|
|
|
let decomp_builder = self.decomp_builder.clone();
|
|
|
|
SearchWorker {
|
2020-02-18 01:08:47 +02:00
|
|
|
config,
|
|
|
|
command_builder,
|
|
|
|
decomp_builder,
|
|
|
|
matcher,
|
|
|
|
searcher,
|
|
|
|
printer,
|
2018-08-30 02:53:52 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Forcefully use JSON to emit statistics, even if the underlying printer
|
|
|
|
/// is not the JSON printer.
|
|
|
|
///
|
|
|
|
/// This is useful for implementing flag combinations like
|
|
|
|
/// `--json --quiet`, which uses the summary printer for implementing
|
|
|
|
/// `--quiet` but still wants to emit summary statistics, which should
|
|
|
|
/// be JSON formatted because of the `--json` flag.
|
|
|
|
pub fn json_stats(&mut self, yes: bool) -> &mut SearchWorkerBuilder {
|
|
|
|
self.config.json_stats = yes;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the path to a preprocessor command.
|
|
|
|
///
|
|
|
|
/// When this is set, instead of searching files directly, the given
|
|
|
|
/// command will be run with the file path as the first argument, and the
|
|
|
|
/// output of that command will be searched instead.
|
|
|
|
pub fn preprocessor(
|
|
|
|
&mut self,
|
|
|
|
cmd: Option<PathBuf>,
|
cli: fix arbitrary execution of program bug
This fixes a bug only present on Windows that would permit someone to
execute an arbitrary program if they crafted an appropriate directory
tree. Namely, if someone put an executable named 'xz.exe' in the root of
a directory tree and one ran 'rg -z foo' from the root of that tree,
then the 'xz.exe' executable in that tree would execute if there are any
'xz' files anywhere in the tree.
The root cause of this problem is that 'CreateProcess' on Windows will
implicitly look in the current working directory for an executable when
it is given a relative path to a program. Rust's standard library allows
this behavior to occur, so we work around it here. We work around it by
explicitly resolving programs like 'xz' via 'PATH'. That way, we only
ever pass an absolute path to 'CreateProcess', which avoids the implicit
behavior of checking the current working directory.
This fix doesn't apply to non-Windows systems as it is believed to only
impact Windows. In theory, the bug could apply on Unix if '.' is in
one's PATH, but at that point, you reap what you sow.
While the extent to which this is a security problem isn't clear, I
think users generally expect to be able to download or clone
repositories from the Internet and run ripgrep on them without fear of
anything too awful happening. Being able to execute an arbitrary program
probably violates that expectation. Therefore, CVE-2021-3013[1] was
created for this issue.
We apply the same logic to the --pre command, since the --pre command is
likely in a user's config file and it would be surprising for something
that the user is searching to modify which preprocessor command is used.
The --pre and -z/--search-zip flags are the only two ways that ripgrep
will invoke external programs, so this should cover any possible
exploitable cases of this bug.
[1] - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3013
2021-01-11 20:44:07 +02:00
|
|
|
) -> crate::Result<&mut SearchWorkerBuilder> {
|
|
|
|
if let Some(ref prog) = cmd {
|
|
|
|
let bin = cli::resolve_binary(prog)?;
|
|
|
|
self.config.preprocessor = Some(bin);
|
|
|
|
} else {
|
|
|
|
self.config.preprocessor = None;
|
|
|
|
}
|
|
|
|
Ok(self)
|
2018-08-03 23:26:22 +02:00
|
|
|
}
|
|
|
|
|
2018-09-05 04:45:24 +02:00
|
|
|
/// Set the globs for determining which files should be run through the
|
|
|
|
/// preprocessor. By default, with no globs and a preprocessor specified,
|
|
|
|
/// every file is run through the preprocessor.
|
|
|
|
pub fn preprocessor_globs(
|
|
|
|
&mut self,
|
|
|
|
globs: Override,
|
|
|
|
) -> &mut SearchWorkerBuilder {
|
|
|
|
self.config.preprocessor_globs = globs;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-08-03 23:26:22 +02:00
|
|
|
/// Enable the decompression and searching of common compressed files.
|
|
|
|
///
|
|
|
|
/// When enabled, if a particular file path is recognized as a compressed
|
|
|
|
/// file, then it is decompressed before searching.
|
|
|
|
///
|
|
|
|
/// Note that if a preprocessor command is set, then it overrides this
|
|
|
|
/// setting.
|
|
|
|
pub fn search_zip(&mut self, yes: bool) -> &mut SearchWorkerBuilder {
|
|
|
|
self.config.search_zip = yes;
|
|
|
|
self
|
|
|
|
}
|
binary: rejigger ripgrep's handling of binary files
This commit attempts to surface binary filtering in a slightly more
user friendly way. Namely, before, ripgrep would silently stop
searching a file if it detected a NUL byte, even if it had previously
printed a match. This can lead to the user quite reasonably assuming
that there are no more matches, since a partial search is fairly
unintuitive. (ripgrep has this behavior by default because it really
wants to NOT search binary files at all, just like it doesn't search
gitignored or hidden files.)
With this commit, if a match has already been printed and ripgrep detects
a NUL byte, then it will print a warning message indicating that the search
stopped prematurely.
Moreover, this commit adds a new flag, --binary, which causes ripgrep to
stop filtering binary files, but in a way that still avoids dumping
binary data into terminals. That is, the --binary flag makes ripgrep
behave more like grep's default behavior.
For files explicitly specified in a search, e.g., `rg foo some-file`,
then no binary filtering is applied (just like no gitignore and no
hidden file filtering is applied). Instead, ripgrep behaves as if you
gave the --binary flag for all explicitly given files.
This was a fairly invasive change, and potentially increases the UX
complexity of ripgrep around binary files. (Before, there were two
binary modes, where as now there are three.) However, ripgrep is now a
bit louder with warning messages when binary file detection might
otherwise be hiding potential matches, so hopefully this is a net
improvement.
Finally, the `-uuu` convenience now maps to `--no-ignore --hidden
--binary`, since this is closer to the actualy intent of the
`--unrestricted` flag, i.e., to reduce ripgrep's smart filtering. As a
consequence, `rg -uuu foo` should now search roughly the same number of
bytes as `grep -r foo`, and `rg -uuua foo` should search roughly the
same number of bytes as `grep -ra foo`. (The "roughly" weasel word is
used because grep's and ripgrep's binary file detection might differ
somewhat---perhaps based on buffer sizes---which can impact exactly what
is and isn't searched.)
See the numerous tests in tests/binary.rs for intended behavior.
Fixes #306, Fixes #855
2019-04-09 01:28:38 +02:00
|
|
|
|
|
|
|
/// Set the binary detection that should be used when searching files
|
|
|
|
/// found via a recursive directory search.
|
|
|
|
///
|
|
|
|
/// Generally, this binary detection may be `BinaryDetection::quit` if
|
|
|
|
/// we want to skip binary files completely.
|
|
|
|
///
|
|
|
|
/// By default, no binary detection is performed.
|
|
|
|
pub fn binary_detection_implicit(
|
|
|
|
&mut self,
|
|
|
|
detection: BinaryDetection,
|
|
|
|
) -> &mut SearchWorkerBuilder {
|
|
|
|
self.config.binary_implicit = detection;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the binary detection that should be used when searching files
|
|
|
|
/// explicitly supplied by an end user.
|
|
|
|
///
|
|
|
|
/// Generally, this binary detection should NOT be `BinaryDetection::quit`,
|
|
|
|
/// since we never want to automatically filter files supplied by the end
|
|
|
|
/// user.
|
|
|
|
///
|
|
|
|
/// By default, no binary detection is performed.
|
|
|
|
pub fn binary_detection_explicit(
|
|
|
|
&mut self,
|
|
|
|
detection: BinaryDetection,
|
|
|
|
) -> &mut SearchWorkerBuilder {
|
|
|
|
self.config.binary_explicit = detection;
|
|
|
|
self
|
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// The result of executing a search.
|
|
|
|
///
|
|
|
|
/// Generally speaking, the "result" of a search is sent to a printer, which
|
|
|
|
/// writes results to an underlying writer such as stdout or a file. However,
|
|
|
|
/// every search also has some aggregate statistics or meta data that may be
|
|
|
|
/// useful to higher level routines.
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
pub struct SearchResult {
|
|
|
|
has_match: bool,
|
|
|
|
stats: Option<Stats>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SearchResult {
|
|
|
|
/// Whether the search found a match or not.
|
|
|
|
pub fn has_match(&self) -> bool {
|
|
|
|
self.has_match
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return aggregate search statistics for a single search, if available.
|
|
|
|
///
|
|
|
|
/// It can be expensive to compute statistics, so these are only present
|
|
|
|
/// if explicitly enabled in the printer provided by the caller.
|
|
|
|
pub fn stats(&self) -> Option<&Stats> {
|
|
|
|
self.stats.as_ref()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The pattern matcher used by a search worker.
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub enum PatternMatcher {
|
|
|
|
RustRegex(RustRegexMatcher),
|
|
|
|
#[cfg(feature = "pcre2")]
|
|
|
|
PCRE2(PCRE2RegexMatcher),
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The printer used by a search worker.
|
|
|
|
///
|
|
|
|
/// The `W` type parameter refers to the type of the underlying writer.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Printer<W> {
|
|
|
|
/// Use the standard printer, which supports the classic grep-like format.
|
|
|
|
Standard(Standard<W>),
|
|
|
|
/// Use the summary printer, which supports aggregate displays of search
|
|
|
|
/// results.
|
|
|
|
Summary(Summary<W>),
|
|
|
|
/// A JSON printer, which emits results in the JSON Lines format.
|
|
|
|
JSON(JSON<W>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<W: WriteColor> Printer<W> {
|
|
|
|
fn print_stats(
|
|
|
|
&mut self,
|
|
|
|
total_duration: Duration,
|
|
|
|
stats: &Stats,
|
|
|
|
) -> io::Result<()> {
|
|
|
|
match *self {
|
2020-02-18 01:08:47 +02:00
|
|
|
Printer::JSON(_) => self.print_stats_json(total_duration, stats),
|
2018-08-03 23:26:22 +02:00
|
|
|
Printer::Standard(_) | Printer::Summary(_) => {
|
|
|
|
self.print_stats_human(total_duration, stats)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn print_stats_human(
|
|
|
|
&mut self,
|
|
|
|
total_duration: Duration,
|
|
|
|
stats: &Stats,
|
|
|
|
) -> io::Result<()> {
|
|
|
|
write!(
|
|
|
|
self.get_mut(),
|
|
|
|
"
|
|
|
|
{matches} matches
|
|
|
|
{lines} matched lines
|
|
|
|
{searches_with_match} files contained matches
|
|
|
|
{searches} files searched
|
|
|
|
{bytes_printed} bytes printed
|
|
|
|
{bytes_searched} bytes searched
|
|
|
|
{search_time:0.6} seconds spent searching
|
|
|
|
{process_time:0.6} seconds
|
|
|
|
",
|
|
|
|
matches = stats.matches(),
|
|
|
|
lines = stats.matched_lines(),
|
|
|
|
searches_with_match = stats.searches_with_match(),
|
|
|
|
searches = stats.searches(),
|
|
|
|
bytes_printed = stats.bytes_printed(),
|
|
|
|
bytes_searched = stats.bytes_searched(),
|
|
|
|
search_time = fractional_seconds(stats.elapsed()),
|
|
|
|
process_time = fractional_seconds(total_duration)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn print_stats_json(
|
|
|
|
&mut self,
|
|
|
|
total_duration: Duration,
|
|
|
|
stats: &Stats,
|
|
|
|
) -> io::Result<()> {
|
|
|
|
// We specifically match the format laid out by the JSON printer in
|
|
|
|
// the grep-printer crate. We simply "extend" it with the 'summary'
|
|
|
|
// message type.
|
|
|
|
let fractional = fractional_seconds(total_duration);
|
2020-02-18 01:08:47 +02:00
|
|
|
json::to_writer(
|
|
|
|
self.get_mut(),
|
|
|
|
&json!({
|
|
|
|
"type": "summary",
|
|
|
|
"data": {
|
|
|
|
"stats": stats,
|
|
|
|
"elapsed_total": {
|
|
|
|
"secs": total_duration.as_secs(),
|
|
|
|
"nanos": total_duration.subsec_nanos(),
|
|
|
|
"human": format!("{:0.6}s", fractional),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
)?;
|
2018-08-03 23:26:22 +02:00
|
|
|
write!(self.get_mut(), "\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a mutable reference to the underlying printer's writer.
|
|
|
|
pub fn get_mut(&mut self) -> &mut W {
|
|
|
|
match *self {
|
|
|
|
Printer::Standard(ref mut p) => p.get_mut(),
|
|
|
|
Printer::Summary(ref mut p) => p.get_mut(),
|
|
|
|
Printer::JSON(ref mut p) => p.get_mut(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A worker for executing searches.
|
|
|
|
///
|
|
|
|
/// It is intended for a single worker to execute many searches, and is
|
|
|
|
/// generally intended to be used from a single thread. When searching using
|
|
|
|
/// multiple threads, it is better to create a new worker for each thread.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct SearchWorker<W> {
|
|
|
|
config: Config,
|
2018-08-30 02:53:52 +02:00
|
|
|
command_builder: cli::CommandReaderBuilder,
|
|
|
|
decomp_builder: cli::DecompressionReaderBuilder,
|
2018-08-03 23:26:22 +02:00
|
|
|
matcher: PatternMatcher,
|
|
|
|
searcher: Searcher,
|
|
|
|
printer: Printer<W>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<W: WriteColor> SearchWorker<W> {
|
|
|
|
/// Execute a search over the given subject.
|
|
|
|
pub fn search(&mut self, subject: &Subject) -> io::Result<SearchResult> {
|
2020-02-18 01:08:47 +02:00
|
|
|
let bin = if subject.is_explicit() {
|
|
|
|
self.config.binary_explicit.clone()
|
|
|
|
} else {
|
|
|
|
self.config.binary_implicit.clone()
|
|
|
|
};
|
2019-06-19 02:23:47 +02:00
|
|
|
let path = subject.path();
|
2021-03-31 19:54:00 +02:00
|
|
|
log::trace!("{}: binary detection: {:?}", path.display(), bin);
|
|
|
|
|
|
|
|
self.searcher.set_binary_detection(bin);
|
2019-06-19 02:23:47 +02:00
|
|
|
if subject.is_stdin() {
|
cli: fix process leak
If ripgrep was called in a way where the entire contents of a file
aren't read (like --files-with-matches, among other methods), and if the
file was read through an external process, then ripgrep would never reap
that process.
We fix this by introducing an explicit 'close' method, which we now call
when using decompression or preprocessor searches.
The implementation of 'close' is a little hokey. In particular, when we
close stdout, this usually results in a broken pipe, and, consequently,
a non-zero code returned once the child process is reaped. This is
"situation normal," so we invent a (hopefully portable) heuristic for
detecting it.
Fixes #1766, Closes #1767
2020-12-28 22:08:19 +02:00
|
|
|
self.search_reader(path, &mut io::stdin().lock())
|
2019-06-19 02:23:47 +02:00
|
|
|
} else if self.should_preprocess(path) {
|
|
|
|
self.search_preprocessor(path)
|
|
|
|
} else if self.should_decompress(path) {
|
|
|
|
self.search_decompress(path)
|
|
|
|
} else {
|
|
|
|
self.search_path(path)
|
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a mutable reference to the underlying printer.
|
|
|
|
pub fn printer(&mut self) -> &mut Printer<W> {
|
|
|
|
&mut self.printer
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Print the given statistics to the underlying writer in a way that is
|
|
|
|
/// consistent with this searcher's printer's format.
|
|
|
|
///
|
|
|
|
/// While `Stats` contains a duration itself, this only corresponds to the
|
|
|
|
/// time spent searching, where as `total_duration` should roughly
|
|
|
|
/// approximate the lifespan of the ripgrep process itself.
|
|
|
|
pub fn print_stats(
|
|
|
|
&mut self,
|
|
|
|
total_duration: Duration,
|
|
|
|
stats: &Stats,
|
|
|
|
) -> io::Result<()> {
|
|
|
|
if self.config.json_stats {
|
|
|
|
self.printer().print_stats_json(total_duration, stats)
|
|
|
|
} else {
|
|
|
|
self.printer().print_stats(total_duration, stats)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-30 02:53:52 +02:00
|
|
|
/// Returns true if and only if the given file path should be
|
|
|
|
/// decompressed before searching.
|
|
|
|
fn should_decompress(&self, path: &Path) -> bool {
|
|
|
|
if !self.config.search_zip {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
self.decomp_builder.get_matcher().has_command(path)
|
|
|
|
}
|
|
|
|
|
2018-09-05 04:45:24 +02:00
|
|
|
/// Returns true if and only if the given file path should be run through
|
|
|
|
/// the preprocessor.
|
|
|
|
fn should_preprocess(&self, path: &Path) -> bool {
|
|
|
|
if !self.config.preprocessor.is_some() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if self.config.preprocessor_globs.is_empty() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
!self.config.preprocessor_globs.matched(path, false).is_ignore()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Search the given file path by first asking the preprocessor for the
|
|
|
|
/// data to search instead of opening the path directly.
|
2018-08-30 02:53:52 +02:00
|
|
|
fn search_preprocessor(
|
|
|
|
&mut self,
|
|
|
|
path: &Path,
|
|
|
|
) -> io::Result<SearchResult> {
|
2019-06-19 02:23:47 +02:00
|
|
|
let bin = self.config.preprocessor.as_ref().unwrap();
|
|
|
|
let mut cmd = Command::new(bin);
|
2018-08-30 02:53:52 +02:00
|
|
|
cmd.arg(path).stdin(Stdio::from(File::open(path)?));
|
|
|
|
|
cli: fix process leak
If ripgrep was called in a way where the entire contents of a file
aren't read (like --files-with-matches, among other methods), and if the
file was read through an external process, then ripgrep would never reap
that process.
We fix this by introducing an explicit 'close' method, which we now call
when using decompression or preprocessor searches.
The implementation of 'close' is a little hokey. In particular, when we
close stdout, this usually results in a broken pipe, and, consequently,
a non-zero code returned once the child process is reaped. This is
"situation normal," so we invent a (hopefully portable) heuristic for
detecting it.
Fixes #1766, Closes #1767
2020-12-28 22:08:19 +02:00
|
|
|
let mut rdr = self.command_builder.build(&mut cmd).map_err(|err| {
|
2020-02-18 01:08:47 +02:00
|
|
|
io::Error::new(
|
|
|
|
io::ErrorKind::Other,
|
|
|
|
format!(
|
|
|
|
"preprocessor command could not start: '{:?}': {}",
|
|
|
|
cmd, err,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
})?;
|
cli: fix process leak
If ripgrep was called in a way where the entire contents of a file
aren't read (like --files-with-matches, among other methods), and if the
file was read through an external process, then ripgrep would never reap
that process.
We fix this by introducing an explicit 'close' method, which we now call
when using decompression or preprocessor searches.
The implementation of 'close' is a little hokey. In particular, when we
close stdout, this usually results in a broken pipe, and, consequently,
a non-zero code returned once the child process is reaped. This is
"situation normal," so we invent a (hopefully portable) heuristic for
detecting it.
Fixes #1766, Closes #1767
2020-12-28 22:08:19 +02:00
|
|
|
let result = self.search_reader(path, &mut rdr).map_err(|err| {
|
2018-08-30 02:53:52 +02:00
|
|
|
io::Error::new(
|
|
|
|
io::ErrorKind::Other,
|
|
|
|
format!("preprocessor command failed: '{:?}': {}", cmd, err),
|
|
|
|
)
|
cli: fix process leak
If ripgrep was called in a way where the entire contents of a file
aren't read (like --files-with-matches, among other methods), and if the
file was read through an external process, then ripgrep would never reap
that process.
We fix this by introducing an explicit 'close' method, which we now call
when using decompression or preprocessor searches.
The implementation of 'close' is a little hokey. In particular, when we
close stdout, this usually results in a broken pipe, and, consequently,
a non-zero code returned once the child process is reaped. This is
"situation normal," so we invent a (hopefully portable) heuristic for
detecting it.
Fixes #1766, Closes #1767
2020-12-28 22:08:19 +02:00
|
|
|
});
|
|
|
|
let close_result = rdr.close();
|
|
|
|
let search_result = result?;
|
|
|
|
close_result?;
|
|
|
|
Ok(search_result)
|
2018-08-30 02:53:52 +02:00
|
|
|
}
|
|
|
|
|
2018-09-05 04:45:24 +02:00
|
|
|
/// Attempt to decompress the data at the given file path and search the
|
|
|
|
/// result. If the given file path isn't recognized as a compressed file,
|
|
|
|
/// then search it without doing any decompression.
|
2020-02-18 01:08:47 +02:00
|
|
|
fn search_decompress(&mut self, path: &Path) -> io::Result<SearchResult> {
|
cli: fix process leak
If ripgrep was called in a way where the entire contents of a file
aren't read (like --files-with-matches, among other methods), and if the
file was read through an external process, then ripgrep would never reap
that process.
We fix this by introducing an explicit 'close' method, which we now call
when using decompression or preprocessor searches.
The implementation of 'close' is a little hokey. In particular, when we
close stdout, this usually results in a broken pipe, and, consequently,
a non-zero code returned once the child process is reaped. This is
"situation normal," so we invent a (hopefully portable) heuristic for
detecting it.
Fixes #1766, Closes #1767
2020-12-28 22:08:19 +02:00
|
|
|
let mut rdr = self.decomp_builder.build(path)?;
|
|
|
|
let result = self.search_reader(path, &mut rdr);
|
|
|
|
let close_result = rdr.close();
|
|
|
|
let search_result = result?;
|
|
|
|
close_result?;
|
|
|
|
Ok(search_result)
|
2018-08-30 02:53:52 +02:00
|
|
|
}
|
|
|
|
|
2018-08-03 23:26:22 +02:00
|
|
|
/// Search the contents of the given file path.
|
|
|
|
fn search_path(&mut self, path: &Path) -> io::Result<SearchResult> {
|
|
|
|
use self::PatternMatcher::*;
|
|
|
|
|
|
|
|
let (searcher, printer) = (&mut self.searcher, &mut self.printer);
|
|
|
|
match self.matcher {
|
|
|
|
RustRegex(ref m) => search_path(m, searcher, printer, path),
|
|
|
|
#[cfg(feature = "pcre2")]
|
|
|
|
PCRE2(ref m) => search_path(m, searcher, printer, path),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Executes a search on the given reader, which may or may not correspond
|
|
|
|
/// directly to the contents of the given file path. Instead, the reader
|
|
|
|
/// may actually cause something else to be searched (for example, when
|
|
|
|
/// a preprocessor is set or when decompression is enabled). In those
|
|
|
|
/// cases, the file path is used for visual purposes only.
|
|
|
|
///
|
|
|
|
/// Generally speaking, this method should only be used when there is no
|
|
|
|
/// other choice. Searching via `search_path` provides more opportunities
|
|
|
|
/// for optimizations (such as memory maps).
|
|
|
|
fn search_reader<R: io::Read>(
|
|
|
|
&mut self,
|
|
|
|
path: &Path,
|
cli: fix process leak
If ripgrep was called in a way where the entire contents of a file
aren't read (like --files-with-matches, among other methods), and if the
file was read through an external process, then ripgrep would never reap
that process.
We fix this by introducing an explicit 'close' method, which we now call
when using decompression or preprocessor searches.
The implementation of 'close' is a little hokey. In particular, when we
close stdout, this usually results in a broken pipe, and, consequently,
a non-zero code returned once the child process is reaped. This is
"situation normal," so we invent a (hopefully portable) heuristic for
detecting it.
Fixes #1766, Closes #1767
2020-12-28 22:08:19 +02:00
|
|
|
rdr: &mut R,
|
2018-08-03 23:26:22 +02:00
|
|
|
) -> io::Result<SearchResult> {
|
|
|
|
use self::PatternMatcher::*;
|
|
|
|
|
|
|
|
let (searcher, printer) = (&mut self.searcher, &mut self.printer);
|
|
|
|
match self.matcher {
|
|
|
|
RustRegex(ref m) => search_reader(m, searcher, printer, path, rdr),
|
|
|
|
#[cfg(feature = "pcre2")]
|
|
|
|
PCRE2(ref m) => search_reader(m, searcher, printer, path, rdr),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Search the contents of the given file path using the given matcher,
|
|
|
|
/// searcher and printer.
|
|
|
|
fn search_path<M: Matcher, W: WriteColor>(
|
|
|
|
matcher: M,
|
|
|
|
searcher: &mut Searcher,
|
|
|
|
printer: &mut Printer<W>,
|
|
|
|
path: &Path,
|
|
|
|
) -> io::Result<SearchResult> {
|
|
|
|
match *printer {
|
|
|
|
Printer::Standard(ref mut p) => {
|
|
|
|
let mut sink = p.sink_with_path(&matcher, path);
|
|
|
|
searcher.search_path(&matcher, path, &mut sink)?;
|
|
|
|
Ok(SearchResult {
|
|
|
|
has_match: sink.has_match(),
|
|
|
|
stats: sink.stats().map(|s| s.clone()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
Printer::Summary(ref mut p) => {
|
|
|
|
let mut sink = p.sink_with_path(&matcher, path);
|
|
|
|
searcher.search_path(&matcher, path, &mut sink)?;
|
|
|
|
Ok(SearchResult {
|
|
|
|
has_match: sink.has_match(),
|
|
|
|
stats: sink.stats().map(|s| s.clone()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
Printer::JSON(ref mut p) => {
|
|
|
|
let mut sink = p.sink_with_path(&matcher, path);
|
|
|
|
searcher.search_path(&matcher, path, &mut sink)?;
|
|
|
|
Ok(SearchResult {
|
|
|
|
has_match: sink.has_match(),
|
|
|
|
stats: Some(sink.stats().clone()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Search the contents of the given reader using the given matcher, searcher
|
|
|
|
/// and printer.
|
|
|
|
fn search_reader<M: Matcher, R: io::Read, W: WriteColor>(
|
|
|
|
matcher: M,
|
|
|
|
searcher: &mut Searcher,
|
|
|
|
printer: &mut Printer<W>,
|
|
|
|
path: &Path,
|
cli: fix process leak
If ripgrep was called in a way where the entire contents of a file
aren't read (like --files-with-matches, among other methods), and if the
file was read through an external process, then ripgrep would never reap
that process.
We fix this by introducing an explicit 'close' method, which we now call
when using decompression or preprocessor searches.
The implementation of 'close' is a little hokey. In particular, when we
close stdout, this usually results in a broken pipe, and, consequently,
a non-zero code returned once the child process is reaped. This is
"situation normal," so we invent a (hopefully portable) heuristic for
detecting it.
Fixes #1766, Closes #1767
2020-12-28 22:08:19 +02:00
|
|
|
mut rdr: R,
|
2018-08-03 23:26:22 +02:00
|
|
|
) -> io::Result<SearchResult> {
|
|
|
|
match *printer {
|
|
|
|
Printer::Standard(ref mut p) => {
|
|
|
|
let mut sink = p.sink_with_path(&matcher, path);
|
cli: fix process leak
If ripgrep was called in a way where the entire contents of a file
aren't read (like --files-with-matches, among other methods), and if the
file was read through an external process, then ripgrep would never reap
that process.
We fix this by introducing an explicit 'close' method, which we now call
when using decompression or preprocessor searches.
The implementation of 'close' is a little hokey. In particular, when we
close stdout, this usually results in a broken pipe, and, consequently,
a non-zero code returned once the child process is reaped. This is
"situation normal," so we invent a (hopefully portable) heuristic for
detecting it.
Fixes #1766, Closes #1767
2020-12-28 22:08:19 +02:00
|
|
|
searcher.search_reader(&matcher, &mut rdr, &mut sink)?;
|
2018-08-03 23:26:22 +02:00
|
|
|
Ok(SearchResult {
|
|
|
|
has_match: sink.has_match(),
|
|
|
|
stats: sink.stats().map(|s| s.clone()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
Printer::Summary(ref mut p) => {
|
|
|
|
let mut sink = p.sink_with_path(&matcher, path);
|
cli: fix process leak
If ripgrep was called in a way where the entire contents of a file
aren't read (like --files-with-matches, among other methods), and if the
file was read through an external process, then ripgrep would never reap
that process.
We fix this by introducing an explicit 'close' method, which we now call
when using decompression or preprocessor searches.
The implementation of 'close' is a little hokey. In particular, when we
close stdout, this usually results in a broken pipe, and, consequently,
a non-zero code returned once the child process is reaped. This is
"situation normal," so we invent a (hopefully portable) heuristic for
detecting it.
Fixes #1766, Closes #1767
2020-12-28 22:08:19 +02:00
|
|
|
searcher.search_reader(&matcher, &mut rdr, &mut sink)?;
|
2018-08-03 23:26:22 +02:00
|
|
|
Ok(SearchResult {
|
|
|
|
has_match: sink.has_match(),
|
|
|
|
stats: sink.stats().map(|s| s.clone()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
Printer::JSON(ref mut p) => {
|
|
|
|
let mut sink = p.sink_with_path(&matcher, path);
|
cli: fix process leak
If ripgrep was called in a way where the entire contents of a file
aren't read (like --files-with-matches, among other methods), and if the
file was read through an external process, then ripgrep would never reap
that process.
We fix this by introducing an explicit 'close' method, which we now call
when using decompression or preprocessor searches.
The implementation of 'close' is a little hokey. In particular, when we
close stdout, this usually results in a broken pipe, and, consequently,
a non-zero code returned once the child process is reaped. This is
"situation normal," so we invent a (hopefully portable) heuristic for
detecting it.
Fixes #1766, Closes #1767
2020-12-28 22:08:19 +02:00
|
|
|
searcher.search_reader(&matcher, &mut rdr, &mut sink)?;
|
2018-08-03 23:26:22 +02:00
|
|
|
Ok(SearchResult {
|
|
|
|
has_match: sink.has_match(),
|
|
|
|
stats: Some(sink.stats().clone()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return the given duration as fractional seconds.
|
|
|
|
fn fractional_seconds(duration: Duration) -> f64 {
|
|
|
|
(duration.as_secs() as f64) + (duration.subsec_nanos() as f64 * 1e-9)
|
|
|
|
}
|