Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/uu/dd/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ dd-error-cannot-skip-invalid = '{ $file }': cannot skip: Invalid argument
dd-error-cannot-seek-invalid = '{ $output }': cannot seek: Invalid argument
dd-error-not-directory = setting flags for '{ $file }': Not a directory
dd-error-failed-discard-cache = failed to discard cache for: { $file }
dd-error-reading-invalid = error reading '{ $file }': Invalid argument
dd-error-writing-invalid = error writing '{ $file }': Invalid argument

# Parse errors
dd-error-unrecognized-operand = Unrecognized operand '{ $operand }'
Expand Down
97 changes: 92 additions & 5 deletions src/uu/dd/src/dd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,30 @@
}
}
}
#[cfg(not(unix))]
Self::File(f) => f.seek(SeekFrom::Current(n.try_into().unwrap())),
#[cfg(unix)]
Self::File(f) => {
let file_len = f.metadata().as_ref().map_or(u64::MAX, Metadata::len);
match n.try_into() {
Ok(n) => {
let pos = f.seek(SeekFrom::Current(n))?;
if file_len > 0 && pos > file_len {
Err(io::Error::new(io::ErrorKind::InvalidInput, "cannot skip"))
} else {
Ok(pos)
}
}
Err(err) => {
// println!("====debug1====, {:?}", err);
Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("unexpected: {n}, {err}"),
))
}
}
}
#[cfg(unix)]
Self::Fifo(f) => read_and_discard(f, n, ibs),
}
}
Expand Down Expand Up @@ -409,7 +431,21 @@

let mut src = Source::File(src);
if settings.skip > 0 {
src.skip(settings.skip, settings.ibs)?;
match src.skip(settings.skip, settings.ibs) {
Ok(_) => {}
Err(error)
if error.kind() == io::ErrorKind::InvalidInput
&& error.to_string() == "cannot skip" =>
{
// GNU compatibility:
// this case prints the stats but sets the exit code to 1
show_error!(
"{}",
translate!("dd-error-cannot-skip-offset", "file" => filename.to_string_lossy())
);
}
Err(error) => return Err(error.into()),
}
}
Ok(Self { src, settings })
}
Expand Down Expand Up @@ -478,7 +514,24 @@
Ok(len) => return Ok(len),
Err(e) if e.kind() == io::ErrorKind::Interrupted => (),
Err(_) if self.settings.iconv.noerror => return Ok(base_idx),
#[cfg(not(unix))]
Err(e) => return Err(e),
#[cfg(unix)]
Err(e) => {
if e.kind() == io::ErrorKind::InvalidInput {
// GNU compatibility:
// this case prints the stats but sets the exit code to 1
if let Some(infile) = &self.settings.infile {
show_error!(
"{}",
translate!("dd-error-reading-invalid", "file" => infile)
);
set_exit_code(1);
}
return Ok(0);
}
return Err(e);
}
}
}
}
Expand Down Expand Up @@ -883,7 +936,11 @@
};
let mut dst = Dest::File(dst, density);
dst.seek(settings.seek, settings.obs)
.map_err_context(|| translate!("dd-error-failed-to-seek"))?;
.map_err_context(|| translate!("dd-error-failed-to-seek"))
.map_err(|err| {

Check failure on line 940 in src/uu/dd/src/dd.rs

View workflow job for this annotation

GitHub Actions / Style and Lint (ubuntu-24.04, unix)

ERROR: `cargo clippy`: using `map_err` over `inspect_err` (file:'src/uu/dd/src/dd.rs', line:940)

Check failure on line 940 in src/uu/dd/src/dd.rs

View workflow job for this annotation

GitHub Actions / Style and Lint (unix)

ERROR: `cargo clippy`: using `map_err` over `inspect_err` (file:'src/uu/dd/src/dd.rs', line:940)
println!("catch error: {:?}", err.to_string());
err
})?;
Ok(Self { dst, settings })
}

Expand Down Expand Up @@ -982,7 +1039,15 @@
}
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => (),
Err(e) => return Err(e),
Err(e) => {
if e.kind() == io::ErrorKind::InvalidInput {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid input buffer",
));
}
return Err(e);
}
}
}
}
Expand Down Expand Up @@ -1235,7 +1300,6 @@
}
break;
}
let wstat_update = o.write_blocks(&buf)?;

// Discard the system file cache for the read portion of
// the input file.
Expand All @@ -1249,6 +1313,30 @@
}
read_offset += read_len;

// Update the read stats before writing for better error handling
rstat += rstat_update;

// we need to break the loop when encountering an invalid input buffer
let wstat_update = match o.write_blocks(&buf) {
Ok(wstat) => wstat,
Err(error) if error.kind() == io::ErrorKind::InvalidInput =>
// && error.to_string() == "invalid input buffer" =>
{
// GNU compatibility:
// this case prints the stats but sets the exit code to 1
if let Some(outfile) = &i.settings.outfile {
show_error!(
"{}",
translate!("dd-error-writing-invalid", "file" => outfile)
);
#[cfg(unix)]
set_exit_code(1);
}
break;
}
Err(error) => return Err(error),
};

// Discard the system file cache for the written portion
// of the output file.
//
Expand All @@ -1267,7 +1355,6 @@
// error. Since it is just reporting progress and is not
// crucial to the operation of `dd`, let's just ignore the
// error.
rstat += rstat_update;
wstat += wstat_update;
#[cfg(target_os = "linux")]
if check_and_reset_sigusr1() {
Expand Down
110 changes: 110 additions & 0 deletions tests/by-util/test_dd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2128,3 +2128,113 @@ fn test_bs_not_positive() {
}
}
}

#[test]
#[cfg(unix)]
fn test_skip_i64_max() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;

let infile_name = "infile";
at.touch(infile_name);
let infile = at.plus(infile_name);

new_ucmd!()
.args(&[
format!("if={}", infile.display()),
"of=outfile".to_string(),
"bs=1".to_string(),
"skip=9223372036854775807".to_string(),
])
.fails()
.no_stdout()
.code_is(1)
.stderr_does_not_contain(format!(
"dd: '{}': cannot skip to specified offset",
infile.display()
))
.stderr_contains(format!(
"dd: error reading '{}': Invalid argument\n0+0 records in\n0+0 records out\n",
infile.display()
));

at.write("infile", "abcd");

new_ucmd!()
.args(&[
format!("if={}", infile.display()),
"of=outfile".to_string(),
"bs=1".to_string(),
"skip=9223372036854775807".to_string(),
])
.fails()
.no_stdout()
.code_is(1)
.stderr_contains(format!(
"dd: '{}': cannot skip to specified offset\ndd: error reading '{}': Invalid argument\n0+0 records in\n0+0 records out\n",
infile.display(),
infile.display()
));
}

#[test]
#[cfg(unix)]
fn test_seek_i64_max_infile_not_empty() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;

let infile_name = "infile";
let outfile_name = "outfile";
let infile = at.plus(infile_name);
let outfile = at.plus(outfile_name);

at.write(infile_name, "a");
at.touch(outfile_name);

new_ucmd!()
.args(&[
format!("if={}", infile.display()),
format!("of={}", outfile.display()),
"bs=1".to_string(),
"seek=9223372036854775807".to_string(),
])
.fails()
.no_stdout()
.code_is(1)
.stderr_contains(format!(
"dd: error writing '{}': Invalid argument\n1+0 records in\n0+0 records out\n",
outfile.display()
));

at.write(infile_name, "ab");

new_ucmd!()
.args(&[
format!("if={}", infile.display()),
format!("of={}", outfile.display()),
"bs=1".to_string(),
"seek=9223372036854775807".to_string(),
])
.fails()
.no_stdout()
.code_is(1)
.stderr_contains(format!(
"dd: error writing '{}': Invalid argument\n1+0 records in\n0+0 records out\n",
outfile.display()
));

new_ucmd!()
.args(&[
format!("if={}", infile.display()),
format!("of={}", outfile.display()),
"bs=1".to_string(),
"seek=9223372036854775806".to_string(),
])
.fails()
.no_stdout()
.code_is(1)
.stderr_contains(format!(
"dd: error writing '{}': Invalid argument\n2+0 records in\n1+0 records out\n",
outfile.display()
));
}
Loading