Skip to content
Closed
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
119 changes: 103 additions & 16 deletions src/uu/dd/src/dd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ use progress::ProgUpdateType;
use progress::{ProgUpdate, ReadStat, StatusLevel, WriteStat, gen_prog_updater};
#[cfg(target_os = "linux")]
use progress::{check_and_reset_sigusr1, install_sigusr1_handler};
use uucore::io::OwnedFileDescriptorOrHandle;
use uucore::translate;

use std::cmp;
Expand All @@ -35,6 +34,7 @@ use std::ffi::OsString;
use std::fs::Metadata;
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::mem::ManuallyDrop;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::fd::AsFd;
#[cfg(any(target_os = "linux", target_os = "android"))]
Expand All @@ -45,7 +45,7 @@ use std::os::unix::{
io::{AsRawFd, FromRawFd},
};
#[cfg(windows)]
use std::os::windows::{fs::MetadataExt, io::AsHandle};
use std::os::windows::{fs::MetadataExt, io::{AsHandle, AsRawHandle, FromRawHandle}};
use std::path::Path;
use std::sync::atomic::AtomicU8;
use std::sync::{Arc, atomic::Ordering::Relaxed, mpsc};
Expand Down Expand Up @@ -601,14 +601,23 @@ enum Density {
/// Data destinations.
enum Dest {
/// Output to stdout.
Stdout(File),
///
/// ManuallyDrop ensures the file descriptor is never closed when dropped,
/// preventing loss of buffered data and maintaining stdout's lifetime.
Stdout(ManuallyDrop<File>),

/// Output to a file.
///
/// The [`Density`] component indicates whether to attempt to
/// write a sparse file when all-zero blocks are encountered.
File(File, Density),

/// Output to stdout when redirected to a seekable file.
///
/// Like `Stdout`, uses ManuallyDrop to prevent closing stdout's fd.
/// Unlike `File`, this represents stdout redirected to a file (e.g., `dd > out.txt`).
FileFromStdout(ManuallyDrop<File>, Density),

/// Output to a named pipe, also known as a FIFO.
#[cfg(unix)]
Fifo(File),
Expand All @@ -626,6 +635,10 @@ impl Dest {
f.flush()?;
f.sync_all()
}
Self::FileFromStdout(f, _) => {
(**f).flush()?;
(**f).sync_all()
}
#[cfg(unix)]
Self::Fifo(f) => {
f.flush()?;
Expand All @@ -643,6 +656,10 @@ impl Dest {
f.flush()?;
f.sync_data()
}
Self::FileFromStdout(f, _) => {
(**f).flush()?;
(**f).sync_data()
}
#[cfg(unix)]
Self::Fifo(f) => {
f.flush()?;
Expand All @@ -656,7 +673,7 @@ impl Dest {
#[cfg_attr(not(unix), allow(unused_variables))]
fn seek(&mut self, n: u64, obs: usize) -> io::Result<u64> {
match self {
Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout),
Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), &mut **stdout),
Self::File(f, _) => {
#[cfg(unix)]
if let Ok(Some(len)) = try_get_len_of_block_device(f)
Expand All @@ -673,6 +690,22 @@ impl Dest {
}
f.seek(SeekFrom::Current(n.try_into().unwrap()))
}
Self::FileFromStdout(f, _) => {
#[cfg(unix)]
if let Ok(Some(len)) = try_get_len_of_block_device(f)
&& len < n
{
// GNU compatibility:
// this case prints the stats but sets the exit code to 1
show_error!(
"{}",
translate!("dd-error-cannot-seek-invalid", "output" => "standard output")
);
set_exit_code(1);
return Ok(len);
}
f.seek(SeekFrom::Current(n.try_into().unwrap()))
}
#[cfg(unix)]
Self::Fifo(f) => {
// Seeking in a named pipe means *reading* from the pipe.
Expand All @@ -685,12 +718,15 @@ impl Dest {

/// Truncate the underlying file to the current stream position, if possible.
fn truncate(&mut self) -> io::Result<()> {
#[allow(clippy::match_wildcard_for_single_variants)]
match self {
Self::File(f, _) => {
let pos = f.stream_position()?;
f.set_len(pos)
}
Self::FileFromStdout(f, _) => {
let pos = f.stream_position()?;
f.set_len(pos)
}
_ => Ok(()),
}
}
Expand All @@ -708,6 +744,10 @@ impl Dest {
let advice = PosixFadviseAdvice::POSIX_FADV_DONTNEED;
posix_fadvise(f.as_fd(), offset, len, advice)
}
Self::FileFromStdout(f, _) => {
let advice = PosixFadviseAdvice::POSIX_FADV_DONTNEED;
posix_fadvise(f.as_fd(), offset, len, advice)
}
_ => Err(Errno::ESPIPE), // "Illegal seek"
}
}
Expand Down Expand Up @@ -777,6 +817,14 @@ impl Write for Dest {
f.seek(SeekFrom::Current(seek_amt))?;
Ok(buf.len())
}
Self::FileFromStdout(f, Density::Sparse) if is_sparse(buf) => {
let seek_amt: i64 = buf
.len()
.try_into()
.expect("Internal dd Error: Seek amount greater than signed 64-bit integer");
f.seek(SeekFrom::Current(seek_amt))?;
Ok(buf.len())
}
Self::File(f, _) => {
// Try the write first
match f.write(buf) {
Expand All @@ -792,6 +840,21 @@ impl Write for Dest {
Err(e) => Err(e),
}
}
Self::FileFromStdout(f, _) => {
// Try the write first
match f.write(buf) {
Ok(len) => Ok(len),
Err(e)
if e.kind() == io::ErrorKind::InvalidInput
&& e.raw_os_error() == Some(libc::EINVAL) =>
{
// This might be an O_DIRECT alignment issue.
// Try removing O_DIRECT temporarily and retry.
handle_o_direct_write(f, buf, e)
}
Err(e) => Err(e),
}
}
Self::Stdout(stdout) => stdout.write(buf),
#[cfg(unix)]
Self::Fifo(f) => f.write(buf),
Expand All @@ -804,6 +867,7 @@ impl Write for Dest {
match self {
Self::Stdout(stdout) => stdout.flush(),
Self::File(f, _) => f.flush(),
Self::FileFromStdout(f, _) => f.flush(),
#[cfg(unix)]
Self::Fifo(f) => f.flush(),
#[cfg(unix)]
Expand All @@ -829,8 +893,13 @@ struct Output<'a> {
impl<'a> Output<'a> {
/// Instantiate this struct with stdout as a destination.
fn new_stdout(settings: &'a Settings) -> UResult<Self> {
let fx = OwnedFileDescriptorOrHandle::from(io::stdout())?;
let mut dst = Dest::Stdout(fx.into_file());
// Use a "borrowed" File to avoid fcntl syscall from try_clone_to_owned
// ManuallyDrop ensures stdout is never closed when dropped
#[cfg(unix)]
let file = ManuallyDrop::new(unsafe { File::from_raw_fd(io::stdout().as_raw_fd()) });
#[cfg(windows)]
let file = ManuallyDrop::new(unsafe { File::from_raw_handle(io::stdout().as_raw_handle()) });
let mut dst = Dest::Stdout(file);
dst.seek(settings.seek, settings.obs)
.map_err_context(|| translate!("dd-error-write-error"))?;
Ok(Self { dst, settings })
Expand Down Expand Up @@ -890,16 +959,34 @@ impl<'a> Output<'a> {
/// already opened by the system (stdout) and has a state
/// (current position) that shall be used.
fn new_file_from_stdout(settings: &'a Settings) -> UResult<Self> {
let fx = OwnedFileDescriptorOrHandle::from(io::stdout())?;
#[cfg(any(target_os = "linux", target_os = "android"))]
if let Some(libc_flags) = make_linux_oflags(&settings.oflags) {
nix::fcntl::fcntl(
fx.as_raw().as_fd(),
FcntlArg::F_SETFL(OFlag::from_bits_retain(libc_flags)),
)?;
}
// Use a "borrowed" File to avoid fcntl syscall from try_clone_to_owned
// ManuallyDrop ensures stdout is never closed when dropped, even though
// it's being treated as a regular file (e.g., when stdout is redirected to a file)
#[cfg(unix)]
let file = {
let stdout = io::stdout();
let raw_fd = stdout.as_raw_fd();
#[cfg(any(target_os = "linux", target_os = "android"))]
if let Some(libc_flags) = make_linux_oflags(&settings.oflags) {
nix::fcntl::fcntl(
raw_fd,
FcntlArg::F_SETFL(OFlag::from_bits_retain(libc_flags)),
)?;
}
ManuallyDrop::new(unsafe { File::from_raw_fd(raw_fd) })
};
#[cfg(windows)]
let file = ManuallyDrop::new(unsafe { File::from_raw_handle(io::stdout().as_raw_handle()) });

Self::prepare_file(fx.into_file(), settings)
let density = if settings.oconv.sparse {
Density::Sparse
} else {
Density::Dense
};
let mut dst = Dest::FileFromStdout(file, density);
dst.seek(settings.seek, settings.obs)
.map_err_context(|| translate!("dd-error-failed-to-seek"))?;
Ok(Self { dst, settings })
}

/// Instantiate this struct with the given named pipe as a destination.
Expand Down
Loading