Skip to content

bugfix: Reuse the same file pointer for repeated find output #511

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
7 changes: 4 additions & 3 deletions src/find/matchers/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use std::{
fs::File,
io::{stderr, Write},
sync::Arc,
};

use super::{Matcher, MatcherIO, WalkEntry};
Expand Down Expand Up @@ -110,11 +111,11 @@
}

pub struct Ls {
output_file: Option<File>,
output_file: Option<Arc<File>>,
}

impl Ls {
pub fn new(output_file: Option<File>) -> Self {
pub fn new(output_file: Option<Arc<File>>) -> Self {
Self { output_file }
}

Expand Down Expand Up @@ -268,7 +269,7 @@
impl Matcher for Ls {
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
if let Some(file) = &self.output_file {
self.print(file_info, matcher_io, file, true);
self.print(file_info, matcher_io, file.as_ref(), true);

Check warning on line 272 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L272

Added line #L272 was not covered by tests
} else {
self.print(
file_info,
Expand Down
104 changes: 53 additions & 51 deletions src/find/matchers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@
mod type_matcher;
mod user;

use ::regex::Regex;
use chrono::{DateTime, Datelike, NaiveDateTime, Utc};
use fs::FileSystemMatcher;
use ls::Ls;
use std::collections::HashMap;
use std::fs::{File, Metadata};
use std::path::Path;
use std::sync::Arc;
use std::time::SystemTime;
use std::{error::Error, str::FromStr};
use uucore::fs::FileInformation;

use self::access::AccessMatcher;
use self::delete::DeleteMatcher;
use self::empty::EmptyMatcher;
Expand Down Expand Up @@ -58,18 +70,7 @@
};
use self::type_matcher::{TypeMatcher, XtypeMatcher};
use self::user::{NoUserMatcher, UserMatcher};
use ::regex::Regex;
use chrono::{DateTime, Datelike, NaiveDateTime, Utc};
use fs::FileSystemMatcher;
use ls::Ls;
use std::{
error::Error,
fs::{File, Metadata},
io::Read,
path::Path,
str::FromStr,
time::SystemTime,
};
use std::io::Read;

use super::{Config, Dependencies};

Expand Down Expand Up @@ -272,13 +273,45 @@
}
}

// Used on file output arguments.
// If yes, use the same file pointer.
struct FileMemoizer {
mem: HashMap<FileInformation, Arc<File>>,
}
impl FileMemoizer {
fn new() -> Self {
Self {
mem: HashMap::new(),
}
}
fn get_or_create_file(&mut self, path: &str) -> Result<Arc<File>, Box<dyn Error>> {
let mut file_info = FileInformation::from_path(path, true);
match file_info {
Ok(info) => {
let file = self
.mem
.entry(info)
.or_insert(Arc::new(File::create(path)?));
Ok(file.clone())
}
Err(_) => {
let file = Arc::new(File::create(path)?);
file_info = FileInformation::from_path(path, true);
self.mem.insert(file_info?, file.clone());
Ok(file)
}
}
}
}

/// Builds a single `AndMatcher` containing the Matcher objects corresponding
/// to the passed in predicate arguments.
pub fn build_top_level_matcher(
args: &[&str],
config: &mut Config,
) -> Result<Box<dyn Matcher>, Box<dyn Error>> {
let (_, top_level_matcher) = (build_matcher_tree(args, config, 0, false))?;
let mut file_mem = FileMemoizer::new();
let (_, top_level_matcher) = (build_matcher_tree(args, config, &mut file_mem, 0, false))?;

// if the matcher doesn't have any side-effects, then we default to printing
if !top_level_matcher.has_side_effects() {
Expand Down Expand Up @@ -421,20 +454,14 @@
}
}

/// Creates a file if it doesn't exist.
/// If it does exist, it will be overwritten.
fn get_or_create_file(path: &str) -> Result<File, Box<dyn Error>> {
let file = File::create(path)?;
Ok(file)
}

/// The main "translate command-line args into a matcher" function. Will call
/// itself recursively if it encounters an opening bracket. A successful return
/// consists of a tuple containing the new index into the args array to use (if
/// called recursively) and the resulting matcher.
fn build_matcher_tree(
args: &[&str],
config: &mut Config,
file_mem: &mut FileMemoizer,
arg_index: usize,
mut expecting_bracket: bool,
) -> Result<(usize, Box<dyn Matcher>), Box<dyn Error>> {
Expand Down Expand Up @@ -465,7 +492,7 @@
}
i += 1;

let file = get_or_create_file(args[i])?;
let file = file_mem.get_or_create_file(args[i])?;
Some(Printer::new(PrintDelimiter::Newline, Some(file)).into_box())
}
"-fprintf" => {
Expand All @@ -477,7 +504,7 @@
// Args + 1: output file path
// Args + 2: format string
i += 1;
let file = get_or_create_file(args[i])?;
let file = file_mem.get_or_create_file(args[i])?;
i += 1;
Some(Printf::new(args[i], Some(file))?.into_box())
}
Expand All @@ -487,7 +514,7 @@
}
i += 1;

let file = get_or_create_file(args[i])?;
let file = file_mem.get_or_create_file(args[i])?;
Some(Printer::new(PrintDelimiter::Null, Some(file)).into_box())
}
"-ls" => Some(Ls::new(None).into_box()),
Expand All @@ -497,7 +524,7 @@
}
i += 1;

let file = get_or_create_file(args[i])?;
let file = file_mem.get_or_create_file(args[i])?;

Check warning on line 527 in src/find/matchers/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/mod.rs#L527

Added line #L527 was not covered by tests
Some(Ls::new(Some(file)).into_box())
}
"-true" => Some(TrueMatcher.into_box()),
Expand Down Expand Up @@ -814,7 +841,8 @@
None
}
"(" => {
let (new_arg_index, sub_matcher) = build_matcher_tree(args, config, i + 1, true)?;
let (new_arg_index, sub_matcher) =
build_matcher_tree(args, config, file_mem, i + 1, true)?;
i = new_arg_index;
Some(sub_matcher)
}
Expand Down Expand Up @@ -1797,30 +1825,4 @@
.expect("-version should stop parsing");
assert!(config.version_requested);
}

#[test]
fn get_or_create_file_test() {
use std::fs;

// remove file if hard link file exist.
// But you can't delete a file that doesn't exist,
// so ignore the error returned here.
let _ = fs::remove_file("test_data/get_or_create_file_test");

// test create file
let file = get_or_create_file("test_data/get_or_create_file_test");
assert!(file.is_ok());

let file = get_or_create_file("test_data/get_or_create_file_test");
assert!(file.is_ok());

// test error when file no permission
#[cfg(unix)]
{
let result = get_or_create_file("/etc/shadow");
assert!(result.is_err());
}

let _ = fs::remove_file("test_data/get_or_create_file_test");
}
}
9 changes: 5 additions & 4 deletions src/find/matchers/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use std::fs::File;
use std::io::{stderr, Write};
use std::sync::Arc;

use super::{Matcher, MatcherIO, WalkEntry};

Expand All @@ -26,11 +27,11 @@ impl std::fmt::Display for PrintDelimiter {
/// This matcher just prints the name of the file to stdout.
pub struct Printer {
delimiter: PrintDelimiter,
output_file: Option<File>,
output_file: Option<Arc<File>>,
}

impl Printer {
pub fn new(delimiter: PrintDelimiter, output_file: Option<File>) -> Self {
pub fn new(delimiter: PrintDelimiter, output_file: Option<Arc<File>>) -> Self {
Self {
delimiter,
output_file,
Expand Down Expand Up @@ -71,7 +72,7 @@ impl Printer {
impl Matcher for Printer {
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
if let Some(file) = &self.output_file {
self.print(file_info, matcher_io, file, true);
self.print(file_info, matcher_io, file.as_ref(), true);
} else {
self.print(
file_info,
Expand Down Expand Up @@ -127,7 +128,7 @@ mod tests {
let dev_full = File::open("/dev/full").unwrap();
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");

let matcher = Printer::new(PrintDelimiter::Newline, Some(dev_full));
let matcher = Printer::new(PrintDelimiter::Newline, Some(Arc::new(dev_full)));
let deps = FakeDependencies::new();

assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
Expand Down
7 changes: 4 additions & 3 deletions src/find/matchers/printf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use std::error::Error;
use std::fs::{self, File};
use std::path::Path;
use std::sync::Arc;
use std::time::SystemTime;
use std::{borrow::Cow, io::Write};

Expand Down Expand Up @@ -572,11 +573,11 @@ fn format_directive<'entry>(
/// find's printf syntax.
pub struct Printf {
format: FormatString,
output_file: Option<File>,
output_file: Option<Arc<File>>,
}

impl Printf {
pub fn new(format: &str, output_file: Option<File>) -> Result<Self, Box<dyn Error>> {
pub fn new(format: &str, output_file: Option<Arc<File>>) -> Result<Self, Box<dyn Error>> {
Ok(Self {
format: FormatString::parse(format)?,
output_file,
Expand Down Expand Up @@ -624,7 +625,7 @@ impl Printf {
impl Matcher for Printf {
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
if let Some(file) = &self.output_file {
self.print(file_info, file);
self.print(file_info, file.as_ref());
} else {
self.print(file_info, &mut *matcher_io.deps.get_output().borrow_mut());
}
Expand Down
Loading
Loading