Skip to content

Commit

Permalink
dd: handle stdout redirected to seekable file
Browse files Browse the repository at this point in the history
Fix a bug in `dd` where null bytes would be unintentionally written if
stdout were redirected to a seekable file. For example, before this
commit, if `dd` were invoked from the command-line as

    dd if=infile bs=1 count=10 seek=5 > /dev/sda1

then five zeros would be written to `/dev/sda1` before copying ten
bytes of `infile` to `/dev/sda1`. After this commit, `dd` will
correctly seek five bytes forward in `/dev/sda1` before copying the
ten bytes of `infile`.

Fixes uutils#3542.
  • Loading branch information
jfinkels authored and sylvestre committed Sep 5, 2022
1 parent 3529889 commit 59e3d9c
Showing 1 changed file with 62 additions and 5 deletions.
67 changes: 62 additions & 5 deletions src/uu/dd/src/dd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ use blocks::conv_block_unblock_helper;

use std::cmp;
use std::env;
use std::ffi::OsString;
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Seek, Write};
use std::io::{self, Read, Seek, SeekFrom, Write};
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
Expand Down Expand Up @@ -682,6 +683,49 @@ fn append_dashes_if_not_present(mut acc: Vec<String>, mut s: String) -> Vec<Stri
acc
}

/// Canonicalized file name of `/dev/stdout`.
///
/// For example, if this process were invoked from the command line as
/// `dd`, then this function returns the [`OsString`] form of
/// `"/dev/stdout"`. However, if this process were invoked as `dd >
/// outfile`, then this function returns the canonicalized path to
/// `outfile`, something like `"/path/to/outfile"`.
fn stdout_canonicalized() -> OsString {
match Path::new("/dev/stdout").canonicalize() {
Ok(p) => p.into_os_string(),
Err(_) => OsString::from("/dev/stdout"),
}
}

/// Decide whether stdout is being redirected to a seekable file.
///
/// For example, if this process were invoked from the command line as
///
/// ```sh
/// dd if=/dev/zero bs=1 count=10 seek=5 > /dev/sda1
/// ```
///
/// where `/dev/sda1` is a seekable block device then this function
/// would return true. If invoked as
///
/// ```sh
/// dd if=/dev/zero bs=1 count=10 seek=5
/// ```
///
/// then this function would return false.
fn is_stdout_redirected_to_seekable_file() -> bool {
let s = stdout_canonicalized();
let p = Path::new(&s);
match File::open(p) {
Ok(mut f) => {
f.seek(SeekFrom::Current(0)).is_ok()
&& f.seek(SeekFrom::End(0)).is_ok()
&& f.seek(SeekFrom::Start(0)).is_ok()
}
Err(_) => false,
}
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let dashed_args = args
Expand Down Expand Up @@ -720,6 +764,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
(true, true) => {
let ifname = matches.value_of(options::INFILE).unwrap();
let ofname = matches.value_of(options::OUTFILE).unwrap();

let i = Input::<File>::new(
ibs,
print_level,
Expand All @@ -742,13 +787,25 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let fname = matches.value_of(options::INFILE).unwrap();
let i =
Input::<File>::new(ibs, print_level, count, icflags, iflags, fname, skip_amount)?;
let o = Output::<io::Stdout>::new(obs, oflags, ocflags, seek_amount, "-")?;
o.dd_out(i).map_err_context(|| "IO error".to_string())
if is_stdout_redirected_to_seekable_file() {
let ofname = stdout_canonicalized().into_string().unwrap();
let o = Output::<File>::new(obs, oflags, ocflags, seek_amount, &ofname)?;
o.dd_out(i).map_err_context(|| "IO error".to_string())
} else {
let o = Output::<io::Stdout>::new(obs, oflags, ocflags, seek_amount, "-")?;
o.dd_out(i).map_err_context(|| "IO error".to_string())
}
}
(false, false) => {
let i = Input::<io::Stdin>::new(ibs, print_level, count, icflags, iflags, skip_amount)?;
let o = Output::<io::Stdout>::new(obs, oflags, ocflags, seek_amount, "-")?;
o.dd_out(i).map_err_context(|| "IO error".to_string())
if is_stdout_redirected_to_seekable_file() {
let ofname = stdout_canonicalized().into_string().unwrap();
let o = Output::<File>::new(obs, oflags, ocflags, seek_amount, &ofname)?;
o.dd_out(i).map_err_context(|| "IO error".to_string())
} else {
let o = Output::<io::Stdout>::new(obs, oflags, ocflags, seek_amount, "-")?;
o.dd_out(i).map_err_context(|| "IO error".to_string())
}
}
}
}
Expand Down

0 comments on commit 59e3d9c

Please sign in to comment.