Skip to content
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

du: implement files0-from #5721

Merged
merged 7 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 74 additions & 7 deletions src/uu/du/src/du.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
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";
}
Expand Down Expand Up @@ -587,6 +588,49 @@
(a + b - 1) / b
}

// Read file paths from the specified file, separated by null characters
fn read_files_from(file_name: &str) -> Result<Vec<PathBuf>, std::io::Error> {
let reader: Box<dyn BufRead> = 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 Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("{}: read error: Is a directory", 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
),
))
}
Comment on lines +609 to +617
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case is not covered by a test.

Err(e) => return Err(e),

Check warning on line 618 in src/uu/du/src/du.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/du/src/du.rs#L618

Added line #L618 was not covered by tests
}
};

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<()> {
Expand All @@ -601,13 +645,28 @@
summarize,
)?;

let files = match matches.get_one::<String>(options::FILE) {
Some(_) => matches
.get_many::<String>(options::FILE)
.unwrap()
.map(PathBuf::from)
.collect(),
None => vec![PathBuf::from(".")],
let files = if let Some(file_from) = matches.get_one::<String>(options::FILES0_FROM) {
if file_from == "-" && matches.get_one::<String>(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::<String>(options::FILE).unwrap().quote()
),
)
.into());
}
sylvestre marked this conversation as resolved.
Show resolved Hide resolved

read_files_from(file_from)?
} else {
match matches.get_one::<String>(options::FILE) {
Some(_) => matches
.get_many::<String>(options::FILE)
.unwrap()
.map(PathBuf::from)
.collect(),
None => vec![PathBuf::from(".")],
}
};

let time = matches.contains_id(options::TIME).then(|| {
Expand Down Expand Up @@ -954,6 +1013,14 @@
.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)
Expand Down
74 changes: 72 additions & 2 deletions tests/by-util/test_du.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
// 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 testfile
#[cfg(not(windows))]
use regex::Regex;
#[cfg(not(windows))]
use std::io::Write;

#[cfg(any(target_os = "linux", target_os = "android"))]
Expand Down Expand Up @@ -991,3 +990,74 @@ 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();
sylvestre marked this conversation as resolved.
Show resolved Hide resolved
let mut file2 = at.make_file("testfile2");
file2.write_all(b"content2").unwrap();

at.mkdir("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");
}

#[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"));
}
4 changes: 4 additions & 0 deletions util/build-gnu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 <SIZE>' 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
Expand Down
Loading