Skip to content

Commit 6966150

Browse files
authored
Merge pull request #6088 from cre4ture/fix/gnu_test_dd_not_rewound_sh
`dd` fix gnu test `not-rewound.sh`
2 parents 15e29b1 + 4057717 commit 6966150

File tree

6 files changed

+202
-11
lines changed

6 files changed

+202
-11
lines changed

src/uu/dd/Cargo.toml

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@ gcd = { workspace = true }
2020
libc = { workspace = true }
2121
uucore = { workspace = true, features = ["format", "quoting-style"] }
2222

23-
[target.'cfg(any(target_os = "linux"))'.dependencies]
24-
nix = { workspace = true, features = ["fs"] }
25-
2623
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
2724
signal-hook = { workspace = true }
25+
nix = { workspace = true, features = ["fs"] }
2826

2927
[[bin]]
3028
name = "dd"

src/uu/dd/src/dd.rs

+52-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
// 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
6+
// 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
77

88
mod blocks;
99
mod bufferedoutput;
@@ -16,8 +16,13 @@ mod progress;
1616
use crate::bufferedoutput::BufferedOutput;
1717
use blocks::conv_block_unblock_helper;
1818
use datastructures::*;
19+
#[cfg(any(target_os = "linux", target_os = "android"))]
20+
use nix::fcntl::FcntlArg::F_SETFL;
21+
#[cfg(any(target_os = "linux", target_os = "android"))]
22+
use nix::fcntl::OFlag;
1923
use parseargs::Parser;
2024
use progress::{gen_prog_updater, ProgUpdate, ReadStat, StatusLevel, WriteStat};
25+
use uucore::io::OwnedFileDescriptorOrHandle;
2126

2227
use std::cmp;
2328
use std::env;
@@ -31,6 +36,8 @@ use std::os::unix::{
3136
fs::FileTypeExt,
3237
io::{AsRawFd, FromRawFd},
3338
};
39+
#[cfg(windows)]
40+
use std::os::windows::{fs::MetadataExt, io::AsHandle};
3441
use std::path::Path;
3542
use std::sync::{
3643
atomic::{AtomicBool, Ordering::Relaxed},
@@ -227,7 +234,7 @@ impl Source {
227234
Err(e) => Err(e),
228235
}
229236
}
230-
Self::File(f) => f.seek(io::SeekFrom::Start(n)),
237+
Self::File(f) => f.seek(io::SeekFrom::Current(n.try_into().unwrap())),
231238
#[cfg(unix)]
232239
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
233240
}
@@ -283,7 +290,24 @@ impl<'a> Input<'a> {
283290
/// Instantiate this struct with stdin as a source.
284291
fn new_stdin(settings: &'a Settings) -> UResult<Self> {
285292
#[cfg(not(unix))]
286-
let mut src = Source::Stdin(io::stdin());
293+
let mut src = {
294+
let f = File::from(io::stdin().as_handle().try_clone_to_owned()?);
295+
let is_file = if let Ok(metadata) = f.metadata() {
296+
// this hack is needed as there is no other way on windows
297+
// to differentiate between the case where `seek` works
298+
// on a file handle or not. i.e. when the handle is no real
299+
// file but a pipe, `seek` is still successful, but following
300+
// `read`s are not affected by the seek.
301+
metadata.creation_time() != 0
302+
} else {
303+
false
304+
};
305+
if is_file {
306+
Source::File(f)
307+
} else {
308+
Source::Stdin(io::stdin())
309+
}
310+
};
287311
#[cfg(unix)]
288312
let mut src = Source::stdin_as_file();
289313
if settings.skip > 0 {
@@ -557,7 +581,7 @@ impl Dest {
557581
return Ok(len);
558582
}
559583
}
560-
f.seek(io::SeekFrom::Start(n))
584+
f.seek(io::SeekFrom::Current(n.try_into().unwrap()))
561585
}
562586
#[cfg(unix)]
563587
Self::Fifo(f) => {
@@ -699,6 +723,11 @@ impl<'a> Output<'a> {
699723
if !settings.oconv.notrunc {
700724
dst.set_len(settings.seek).ok();
701725
}
726+
727+
Self::prepare_file(dst, settings)
728+
}
729+
730+
fn prepare_file(dst: File, settings: &'a Settings) -> UResult<Self> {
702731
let density = if settings.oconv.sparse {
703732
Density::Sparse
704733
} else {
@@ -710,6 +739,24 @@ impl<'a> Output<'a> {
710739
Ok(Self { dst, settings })
711740
}
712741

742+
/// Instantiate this struct with file descriptor as a destination.
743+
///
744+
/// This is useful e.g. for the case when the file descriptor was
745+
/// already opened by the system (stdout) and has a state
746+
/// (current position) that shall be used.
747+
fn new_file_from_stdout(settings: &'a Settings) -> UResult<Self> {
748+
let fx = OwnedFileDescriptorOrHandle::from(io::stdout())?;
749+
#[cfg(any(target_os = "linux", target_os = "android"))]
750+
if let Some(libc_flags) = make_linux_oflags(&settings.oflags) {
751+
nix::fcntl::fcntl(
752+
fx.as_raw().as_raw_fd(),
753+
F_SETFL(OFlag::from_bits_retain(libc_flags)),
754+
)?;
755+
}
756+
757+
Self::prepare_file(fx.into_file(), settings)
758+
}
759+
713760
/// Instantiate this struct with the given named pipe as a destination.
714761
#[cfg(unix)]
715762
fn new_fifo(filename: &Path, settings: &'a Settings) -> UResult<Self> {
@@ -1287,9 +1334,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
12871334
#[cfg(unix)]
12881335
Some(ref outfile) if is_fifo(outfile) => Output::new_fifo(Path::new(&outfile), &settings)?,
12891336
Some(ref outfile) => Output::new_file(Path::new(&outfile), &settings)?,
1290-
None if is_stdout_redirected_to_seekable_file() => {
1291-
Output::new_file(Path::new(&stdout_canonicalized()), &settings)?
1292-
}
1337+
None if is_stdout_redirected_to_seekable_file() => Output::new_file_from_stdout(&settings)?,
12931338
None => Output::new_stdout(&settings)?,
12941339
};
12951340
dd_copy(i, o).map_err_context(|| "IO error".to_string())

src/uucore/src/lib/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub use uucore_procs::*;
2222
// * cross-platform modules
2323
pub use crate::mods::display;
2424
pub use crate::mods::error;
25+
pub use crate::mods::io;
2526
pub use crate::mods::line_ending;
2627
pub use crate::mods::os;
2728
pub use crate::mods::panic;

src/uucore/src/lib/mods.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
pub mod display;
88
pub mod error;
9+
pub mod io;
910
pub mod line_ending;
1011
pub mod os;
1112
pub mod panic;

src/uucore/src/lib/mods/io.rs

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// This file is part of the uutils coreutils package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
/// Encapsulates differences between OSs regarding the access to
7+
/// file handles / descriptors.
8+
/// This is useful when dealing with lower level stdin/stdout access.
9+
///
10+
/// In detail:
11+
/// On unix like OSs, file _descriptors_ are used in this context.
12+
/// On windows OSs, file _handles_ are used.
13+
///
14+
/// Even though they are distinct classes, they share common functionality.
15+
/// Access to this common functionality is provided in `OwnedFileDescriptorOrHandle`.
16+
17+
#[cfg(not(windows))]
18+
use std::os::fd::{AsFd, OwnedFd};
19+
#[cfg(windows)]
20+
use std::os::windows::io::{AsHandle, OwnedHandle};
21+
use std::{
22+
fs::{File, OpenOptions},
23+
io,
24+
path::Path,
25+
process::Stdio,
26+
};
27+
28+
#[cfg(windows)]
29+
type NativeType = OwnedHandle;
30+
#[cfg(not(windows))]
31+
type NativeType = OwnedFd;
32+
33+
/// abstraction wrapper for native file handle / file descriptor
34+
pub struct OwnedFileDescriptorOrHandle {
35+
fx: NativeType,
36+
}
37+
38+
impl OwnedFileDescriptorOrHandle {
39+
/// create from underlying native type
40+
pub fn new(x: NativeType) -> Self {
41+
Self { fx: x }
42+
}
43+
44+
/// create by opening a file
45+
pub fn open_file(options: &OpenOptions, path: &Path) -> io::Result<Self> {
46+
let f = options.open(path)?;
47+
Self::from(f)
48+
}
49+
50+
/// conversion from borrowed native type
51+
///
52+
/// e.g. `std::io::stdout()`, `std::fs::File`, ...
53+
#[cfg(windows)]
54+
pub fn from<T: AsHandle>(t: T) -> io::Result<Self> {
55+
Ok(Self {
56+
fx: t.as_handle().try_clone_to_owned()?,
57+
})
58+
}
59+
60+
/// conversion from borrowed native type
61+
///
62+
/// e.g. `std::io::stdout()`, `std::fs::File`, ...
63+
#[cfg(not(windows))]
64+
pub fn from<T: AsFd>(t: T) -> io::Result<Self> {
65+
Ok(Self {
66+
fx: t.as_fd().try_clone_to_owned()?,
67+
})
68+
}
69+
70+
/// instantiates a corresponding `File`
71+
pub fn into_file(self) -> File {
72+
File::from(self.fx)
73+
}
74+
75+
/// instantiates a corresponding `Stdio`
76+
pub fn into_stdio(self) -> Stdio {
77+
Stdio::from(self.fx)
78+
}
79+
80+
/// clones self. useful when needing another
81+
/// owned reference to same file
82+
pub fn try_clone(&self) -> io::Result<Self> {
83+
self.fx.try_clone().map(Self::new)
84+
}
85+
86+
/// provides native type to be used with
87+
/// OS specific functions without abstraction
88+
pub fn as_raw(&self) -> &NativeType {
89+
&self.fx
90+
}
91+
}
92+
93+
/// instantiates a corresponding `Stdio`
94+
impl From<OwnedFileDescriptorOrHandle> for Stdio {
95+
fn from(value: OwnedFileDescriptorOrHandle) -> Self {
96+
value.into_stdio()
97+
}
98+
}

tests/by-util/test_dd.rs

+49-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
5-
// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg fifoname
5+
// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg fifoname seekable
66

77
#[cfg(unix)]
88
use crate::common::util::run_ucmd_as_root_with_stdin_stdout;
@@ -11,6 +11,7 @@ use crate::common::util::TestScenario;
1111
use crate::common::util::{UCommand, TESTS_BINARY};
1212

1313
use regex::Regex;
14+
use uucore::io::OwnedFileDescriptorOrHandle;
1415

1516
use std::fs::{File, OpenOptions};
1617
use std::io::{BufReader, Read, Write};
@@ -1713,3 +1714,50 @@ fn test_reading_partial_blocks_from_fifo_unbuffered() {
17131714
let expected = b"0+2 records in\n0+2 records out\n4 bytes copied";
17141715
assert!(output.stderr.starts_with(expected));
17151716
}
1717+
1718+
#[test]
1719+
fn test_stdin_stdout_not_rewound_even_when_connected_to_seekable_file() {
1720+
use std::process::Stdio;
1721+
1722+
let ts = TestScenario::new(util_name!());
1723+
let at = &ts.fixtures;
1724+
1725+
at.write("in", "abcde");
1726+
1727+
let stdin = OwnedFileDescriptorOrHandle::open_file(
1728+
OpenOptions::new().read(true),
1729+
at.plus("in").as_path(),
1730+
)
1731+
.unwrap();
1732+
let stdout = OwnedFileDescriptorOrHandle::open_file(
1733+
OpenOptions::new().create(true).write(true),
1734+
at.plus("out").as_path(),
1735+
)
1736+
.unwrap();
1737+
let stderr = OwnedFileDescriptorOrHandle::open_file(
1738+
OpenOptions::new().create(true).write(true),
1739+
at.plus("err").as_path(),
1740+
)
1741+
.unwrap();
1742+
1743+
ts.ucmd()
1744+
.args(&["bs=1", "skip=1", "count=1"])
1745+
.set_stdin(Stdio::from(stdin.try_clone().unwrap()))
1746+
.set_stdout(Stdio::from(stdout.try_clone().unwrap()))
1747+
.set_stderr(Stdio::from(stderr.try_clone().unwrap()))
1748+
.succeeds();
1749+
1750+
ts.ucmd()
1751+
.args(&["bs=1", "skip=1"])
1752+
.set_stdin(stdin)
1753+
.set_stdout(stdout)
1754+
.set_stderr(stderr)
1755+
.succeeds();
1756+
1757+
let err_file_content = std::fs::read_to_string(at.plus_as_string("err")).unwrap();
1758+
println!("stderr:\n{}", err_file_content);
1759+
1760+
let out_file_content = std::fs::read_to_string(at.plus_as_string("out")).unwrap();
1761+
println!("stdout:\n{}", out_file_content);
1762+
assert_eq!(out_file_content, "bde");
1763+
}

0 commit comments

Comments
 (0)