From afbdaa3a904383d38db5f5f9f9e0b0db0452fe0c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 24 Dec 2023 15:53:11 +0100 Subject: [PATCH] du: implement files0-from Should make tests/du/files0-from-dir pass --- src/uu/du/src/du.rs | 59 ++++++++++++++++++++++++++++++++++----- tests/by-util/test_du.rs | 60 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 8 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 1213e004f15..660b54ff33e 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -65,6 +65,7 @@ mod options { pub const INODES: &str = "inodes"; pub const EXCLUDE: &str = "exclude"; pub const EXCLUDE_FROM: &str = "exclude-from"; + pub const FILES0_FROM: &str = "files0-from"; pub const VERBOSE: &str = "verbose"; pub const FILE: &str = "FILE"; } @@ -587,6 +588,37 @@ pub fn div_ceil(a: u64, b: u64) -> u64 { (a + b - 1) / b } +fn read_files_from(file_name: &str) -> Result, std::io::Error> { + let reader: Box = if file_name == "-" { + // Read from standard input + Box::new(BufReader::new(std::io::stdin())) + } else { + // First, check if the file_name is a directory + let path = PathBuf::from(file_name); + if path.is_dir() { + // Return an error if it's a directory + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("{}: read error: Is a directory", file_name), + )); + } + + // Read from a file + Box::new(BufReader::new(File::open(file_name)?)) + }; + + let mut paths = Vec::new(); + + for line in reader.split(b'\0') { + let path = line?; + if !path.is_empty() { + paths.push(PathBuf::from(String::from_utf8_lossy(&path).to_string())); + } + } + + Ok(paths) +} + #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -601,13 +633,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { summarize, )?; - let files = match matches.get_one::(options::FILE) { - Some(_) => matches - .get_many::(options::FILE) - .unwrap() - .map(PathBuf::from) - .collect(), - None => vec![PathBuf::from(".")], + let files = if let Some(file_from) = matches.get_one::(options::FILES0_FROM) { + // Read file paths from the specified file, separated by null characters + read_files_from(file_from)? + } else { + match matches.get_one::(options::FILE) { + Some(_) => matches + .get_many::(options::FILE) + .unwrap() + .map(PathBuf::from) + .collect(), + None => vec![PathBuf::from(".")], + } }; let time = matches.contains_id(options::TIME).then(|| { @@ -954,6 +991,14 @@ pub fn uu_app() -> Command { .help("exclude files that match any pattern in FILE") .action(ArgAction::Append) ) + .arg( + Arg::new(options::FILES0_FROM) + .long("files0-from") + .value_name("FILE") + .value_hint(clap::ValueHint::FilePath) + .help("summarize device usage of the NUL-terminated file names specified in file F; if F is -, then read names from standard input") + .action(ArgAction::Append) + ) .arg( Arg::new(options::TIME) .long(options::TIME) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index cb1341432a0..83d065175f2 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (paths) sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink +// spell-checker:ignore (paths) sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink testfile1 testfile2 filelist testdir #[cfg(not(windows))] use regex::Regex; #[cfg(not(windows))] @@ -935,3 +935,61 @@ fn test_du_symlink_multiple_fail() { assert_eq!(result.code(), 1); result.stdout_contains("4\tfile1\n"); } + +#[test] +// Disable on Windows because of different path separators and handling of null characters +#[cfg(not(target_os = "windows"))] +fn test_du_files0_from() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + let mut file1 = at.make_file("testfile1"); + file1.write_all(b"content1").unwrap(); + let mut file2 = at.make_file("testfile2"); + file2.write_all(b"content2").unwrap(); + + at.mkdir_all("testdir"); + let mut file3 = at.make_file("testdir/testfile3"); + file3.write_all(b"content3").unwrap(); + + let mut file_list = at.make_file("filelist"); + write!(file_list, "testfile1\0testfile2\0testdir\0").unwrap(); + + ts.ucmd() + .arg("--files0-from=filelist") + .succeeds() + .stdout_contains("testfile1") + .stdout_contains("testfile2") + .stdout_contains("testdir"); +} + +#[test] +fn test_du_files0_from_stdin() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + let mut file1 = at.make_file("testfile1"); + file1.write_all(b"content1").unwrap(); + let mut file2 = at.make_file("testfile2"); + file2.write_all(b"content2").unwrap(); + + let input = "testfile1\0testfile2\0"; + + ts.ucmd() + .arg("--files0-from=-") + .pipe_in(input) + .succeeds() + .stdout_contains("testfile1") + .stdout_contains("testfile2"); +} + +#[test] +fn test_du_files0_from_dir() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("dir"); + + let result = ts.ucmd().arg("--files0-from=dir").fails(); + assert_eq!(result.stderr_str(), "du: dir: read error: Is a directory\n"); +}