From 3c6079ff5e25f383359c8c18f778d346a9cf5e2a Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Sat, 16 Mar 2024 17:10:49 +0100 Subject: [PATCH] re-use existing fd for stdout even if its a seek-able file this is important as the fd holds the file offset we need to use --- src/uu/dd/Cargo.toml | 4 +--- src/uu/dd/src/dd.rs | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index 1dbb37bde55..be51898fe3e 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -20,11 +20,9 @@ gcd = { workspace = true } libc = { workspace = true } uucore = { workspace = true, features = ["format", "quoting-style"] } -[target.'cfg(any(target_os = "linux"))'.dependencies] -nix = { workspace = true, features = ["fs"] } - [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] signal-hook = { workspace = true } +nix = { workspace = true, features = ["fs"] } [[bin]] name = "dd" diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 07a754deb51..799c8370697 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.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 fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE bufferedoutput +// spell-checker:ignore fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE bufferedoutput, SETFL mod blocks; mod bufferedoutput; @@ -16,6 +16,10 @@ mod progress; use crate::bufferedoutput::BufferedOutput; use blocks::conv_block_unblock_helper; use datastructures::*; +#[cfg(any(target_os = "linux", target_os = "android"))] +use nix::fcntl::FcntlArg::F_SETFL; +#[cfg(any(target_os = "linux", target_os = "android"))] +use nix::fcntl::OFlag; use parseargs::Parser; use progress::{gen_prog_updater, ProgUpdate, ReadStat, StatusLevel, WriteStat}; @@ -24,6 +28,8 @@ use std::env; use std::ffi::OsString; use std::fs::{File, OpenOptions}; use std::io::{self, Read, Seek, SeekFrom, Stdout, Write}; +use std::os::fd::AsFd; +use std::os::fd::OwnedFd; #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::unix::fs::OpenOptionsExt; #[cfg(unix)] @@ -557,7 +563,7 @@ impl Dest { return Ok(len); } } - f.seek(io::SeekFrom::Start(n)) + f.seek(io::SeekFrom::Current(n.try_into().unwrap())) } #[cfg(unix)] Self::Fifo(f) => { @@ -699,6 +705,11 @@ impl<'a> Output<'a> { if !settings.oconv.notrunc { dst.set_len(settings.seek).ok(); } + + Self::prepare_file(dst, settings) + } + + fn prepare_file(dst: File, settings: &'a Settings) -> UResult { let density = if settings.oconv.sparse { Density::Sparse } else { @@ -710,6 +721,28 @@ impl<'a> Output<'a> { Ok(Self { dst, settings }) } + /// Instantiate this struct with file descriptor as a destination. + /// + /// This is useful e.g. for the case when the file descriptor was + /// already opened by the system (stdout) and has a state + /// (current position) that shall be used. + fn new_file_from_fd(fd: OwnedFd, settings: &'a Settings) -> UResult { + fn open_dst(fd: OwnedFd, _oflags: &OFlags) -> Result { + #[cfg(any(target_os = "linux", target_os = "android"))] + if let Some(libc_flags) = make_linux_oflags(_oflags) { + nix::fcntl::fcntl(fd.as_raw_fd(), F_SETFL(OFlag::from_bits_retain(libc_flags)))?; + } + + Ok(File::from(fd)) + } + + let raw_fd = fd.as_raw_fd(); + let dst = open_dst(fd, &settings.oflags) + .map_err_context(|| format!("failed to open fd {}", raw_fd))?; + + Self::prepare_file(dst, settings) + } + /// Instantiate this struct with the given named pipe as a destination. #[cfg(unix)] fn new_fifo(filename: &Path, settings: &'a Settings) -> UResult { @@ -1288,7 +1321,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Some(ref outfile) if is_fifo(outfile) => Output::new_fifo(Path::new(&outfile), &settings)?, Some(ref outfile) => Output::new_file(Path::new(&outfile), &settings)?, None if is_stdout_redirected_to_seekable_file() => { - Output::new_file(Path::new(&stdout_canonicalized()), &settings)? + let fd = io::stdout().as_fd().try_clone_to_owned()?; + Output::new_file_from_fd(fd, &settings)? } None => Output::new_stdout(&settings)?, };