From 99c1dc5ec61117bd8c47450798cc8198592627e5 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 24 Dec 2023 15:53:11 +0100 Subject: [PATCH 1/7] 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 72ffb22ffb5..b393e18823d 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))] @@ -991,3 +991,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"); +} From d1294a7bb1bc423f89e9b46801b87fbb32672c00 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 24 Dec 2023 17:21:49 +0100 Subject: [PATCH 2/7] du: prepare tests/du/files0-from.pl --- src/uu/du/src/du.rs | 27 +++++++++++++++++++++++++-- util/build-gnu.sh | 4 ++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 660b54ff33e..d1f10ce54b5 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -603,8 +603,20 @@ fn read_files_from(file_name: &str) -> Result, std::io::Error> { )); } - // Read from a file - Box::new(BufReader::new(File::open(file_name)?)) + // Attempt to open the file and handle the error if it does not exist + match File::open(file_name) { + Ok(file) => Box::new(BufReader::new(file)), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "cannot open '{}' for reading: No such file or directory", + file_name + ), + )) + } + Err(e) => return Err(e), + } }; let mut paths = Vec::new(); @@ -634,6 +646,17 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { )?; let files = if let Some(file_from) = matches.get_one::(options::FILES0_FROM) { + if file_from == "-" && matches.get_one::(options::FILE).is_some() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "extra operand {}\nfile operands cannot be combined with --files0-from", + matches.get_one::(options::FILE).unwrap().quote() + ), + ) + .into()); + } + // Read file paths from the specified file, separated by null characters read_files_from(file_from)? } else { diff --git a/util/build-gnu.sh b/util/build-gnu.sh index dbddc8a315e..18545dd4817 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -283,6 +283,10 @@ sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/printf/printf-cov.pl sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold ' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh +# Remove the extra output check +sed -i -e "s|Try '\$prog --help' for more information.\\\n||" tests/du/files0-from.pl +sed -i -e "s|when reading file names from stdin, no file name of\"|-: No such file or directory\n\"|" -e "s| '-' allowed\\\n||" tests/du/files0-from.pl + awk 'BEGIN {count=0} /compare exp out2/ && count < 6 {sub(/compare exp out2/, "grep -q \"cannot be used with\" out2"); count++} 1' tests/df/df-output.sh > tests/df/df-output.sh.tmp && mv tests/df/df-output.sh.tmp tests/df/df-output.sh # with ls --dired, in case of error, we have a slightly different error position From ab50108b8f2a265e92fb2efbef39dd7e069e3cb9 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 25 Dec 2023 11:12:36 +0100 Subject: [PATCH 3/7] fix the build on Windows --- tests/by-util/test_du.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index b393e18823d..7a28877f0b2 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -6,7 +6,6 @@ // 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))] use std::io::Write; #[cfg(any(target_os = "linux", target_os = "android"))] From 4a1a814ed23db628983f7f466369db02087b6d30 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 25 Dec 2023 11:50:01 +0100 Subject: [PATCH 4/7] add testfile to the ignore list --- tests/by-util/test_du.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 7a28877f0b2..9d765841abe 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 testfile1 testfile2 filelist testdir +// spell-checker:ignore (paths) sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink testfile1 testfile2 filelist testdir testfile #[cfg(not(windows))] use regex::Regex; use std::io::Write; From e386b3b3a4209f91fe9a747da8bfa72be5438834 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 26 Dec 2023 12:36:28 +0100 Subject: [PATCH 5/7] remove useless comment Co-authored-by: Daniel Hofstetter --- src/uu/du/src/du.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index d1f10ce54b5..03cfb23e580 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -596,7 +596,6 @@ fn read_files_from(file_name: &str) -> Result, std::io::Error> { // 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), From 99fbbe55246d1e15b61306c0376135e1dbfc67f1 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 26 Dec 2023 12:36:42 +0100 Subject: [PATCH 6/7] mkdir is enough Co-authored-by: Daniel Hofstetter --- tests/by-util/test_du.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 9d765841abe..92eba6a0156 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -1003,7 +1003,7 @@ fn test_du_files0_from() { let mut file2 = at.make_file("testfile2"); file2.write_all(b"content2").unwrap(); - at.mkdir_all("testdir"); + at.mkdir("testdir"); let mut file3 = at.make_file("testdir/testfile3"); file3.write_all(b"content3").unwrap(); From 13e8c32bb31f5d9d9959a10cbdf1aeaf11bc09fe Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 26 Dec 2023 12:42:14 +0100 Subject: [PATCH 7/7] address review comments --- src/uu/du/src/du.rs | 2 +- tests/by-util/test_du.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 03cfb23e580..e3f5a718654 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -588,6 +588,7 @@ pub fn div_ceil(a: u64, b: u64) -> u64 { (a + b - 1) / b } +// Read file paths from the specified file, separated by null characters fn read_files_from(file_name: &str) -> Result, std::io::Error> { let reader: Box = if file_name == "-" { // Read from standard input @@ -656,7 +657,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .into()); } - // Read file paths from the specified file, separated by null characters read_files_from(file_from)? } else { match matches.get_one::(options::FILE) { diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 92eba6a0156..27560cbdcca 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -1048,3 +1048,16 @@ fn test_du_files0_from_dir() { let result = ts.ucmd().arg("--files0-from=dir").fails(); assert_eq!(result.stderr_str(), "du: dir: read error: Is a directory\n"); } + +#[test] +fn test_du_files0_from_combined() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("dir"); + + let result = ts.ucmd().arg("--files0-from=-").arg("foo").fails(); + let stderr = result.stderr_str(); + + assert!(stderr.contains("file operands cannot be combined with --files0-from")); +}