Skip to content

Commit f6266b8

Browse files
committed
unset O_DIRECT flag on last chunk if irregular sized
1 parent e450ce8 commit f6266b8

File tree

3 files changed

+80
-9
lines changed

3 files changed

+80
-9
lines changed

src/uu/dd/Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ 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]
24+
nix = { workspace = true, features = ["fs"] }
2725
signal-hook = { workspace = true }
2826

2927
[[bin]]

src/uu/dd/src/dd.rs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// file that was distributed with this source code.
55

66
// 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
7+
// spell-checker:ignore GETFL SETFL
78

89
mod blocks;
910
mod bufferedoutput;
@@ -41,6 +42,8 @@ use std::time::{Duration, Instant};
4142

4243
use clap::{crate_version, Arg, Command};
4344
use gcd::Gcd;
45+
#[cfg(any(target_os = "android", target_os = "linux"))]
46+
use nix::fcntl::{fcntl, FcntlArg, OFlag};
4447
#[cfg(target_os = "linux")]
4548
use nix::{
4649
errno::Errno,
@@ -509,6 +512,20 @@ enum Dest {
509512
}
510513

511514
impl Dest {
515+
fn unset_direct(&mut self) -> io::Result<()> {
516+
match self {
517+
#[cfg(any(target_os = "linux", target_os = "android"))]
518+
Self::File(f, _d) => {
519+
let mut mode = OFlag::from_bits_retain(fcntl(f.as_raw_fd(), FcntlArg::F_GETFL)?);
520+
mode.remove(OFlag::O_DIRECT);
521+
nix::fcntl::fcntl(f.as_raw_fd(), FcntlArg::F_SETFL(mode))?;
522+
}
523+
_ => {}
524+
}
525+
526+
Ok(())
527+
}
528+
512529
fn fsync(&mut self) -> io::Result<()> {
513530
match self {
514531
Self::Stdout(stdout) => stdout.flush(),
@@ -774,7 +791,14 @@ impl<'a> Output<'a> {
774791
let mut writes_partial = 0;
775792
let mut bytes_total = 0;
776793

777-
for chunk in buf.chunks(self.settings.obs) {
794+
let chunk_size = self.settings.obs;
795+
for chunk in buf.chunks(chunk_size) {
796+
if (self.settings.oflags.direct) && (chunk.len() < chunk_size) {
797+
// in case of direct io, only buffers with chunk_size are accepted.
798+
// thus, for writing a (last) buffer with irregular length, we need to switch off the direct io.
799+
self.dst.unset_direct()?;
800+
}
801+
778802
let wlen = self.dst.write(chunk)?;
779803
if wlen < self.settings.obs {
780804
writes_partial += 1;
@@ -877,7 +901,7 @@ impl<'a> BlockWriter<'a> {
877901
///
878902
/// If there is a problem reading from the input or writing to
879903
/// this output.
880-
fn dd_copy(mut i: Input, o: Output) -> std::io::Result<()> {
904+
fn dd_copy(mut i: Input, o: Output) -> UResult<()> {
881905
// The read and write statistics.
882906
//
883907
// These objects are counters, initialized to zero. After each
@@ -992,11 +1016,18 @@ fn dd_copy(mut i: Input, o: Output) -> std::io::Result<()> {
9921016
// best buffer size for reading based on the number of
9931017
// blocks already read and the number of blocks remaining.
9941018
let loop_bsize = calc_loop_bsize(&i.settings.count, &rstat, &wstat, i.settings.ibs, bsize);
995-
let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)?;
1019+
let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)
1020+
.map_err_context(|| format!("reading, ls: {loop_bsize}, rbt: {}", rstat.bytes_total))?;
9961021
if rstat_update.is_empty() {
9971022
break;
9981023
}
999-
let wstat_update = o.write_blocks(&buf)?;
1024+
let wstat_update = o.write_blocks(&buf).map_err_context(|| {
1025+
format!(
1026+
"writing, ls: {}/{loop_bsize}, wbt: {}",
1027+
buf.len(),
1028+
wstat.bytes_total
1029+
)
1030+
})?;
10001031

10011032
// Discard the system file cache for the read portion of
10021033
// the input file.
@@ -1047,7 +1078,7 @@ fn finalize<T>(
10471078
prog_tx: &mpsc::Sender<ProgUpdate>,
10481079
output_thread: thread::JoinHandle<T>,
10491080
truncate: bool,
1050-
) -> std::io::Result<()> {
1081+
) -> UResult<()> {
10511082
// Flush the output in case a partial write has been buffered but
10521083
// not yet written.
10531084
let wstat_update = output.flush()?;
@@ -1292,7 +1323,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
12921323
}
12931324
None => Output::new_stdout(&settings)?,
12941325
};
1295-
dd_copy(i, o).map_err_context(|| "IO error".to_string())
1326+
dd_copy(i, o)
12961327
}
12971328

12981329
pub fn uu_app() -> Command {

tests/by-util/test_dd.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,3 +1713,45 @@ fn test_reading_partial_blocks_from_fifo_unbuffered() {
17131713
let expected = b"0+2 records in\n0+2 records out\n4 bytes copied";
17141714
assert!(output.stderr.starts_with(expected));
17151715
}
1716+
1717+
#[cfg(any(target_os = "linux", target_os = "android"))]
1718+
#[test]
1719+
fn test_reading_and_writing_with_direct_flag_from_and_to_files_with_irregular_size() {
1720+
let ts = TestScenario::new(util_name!());
1721+
let at = &ts.fixtures;
1722+
1723+
let min_direct_block_size = if cfg!(target_os = "android") {
1724+
4096
1725+
} else {
1726+
512
1727+
};
1728+
1729+
let p = |m: i32, o: i32| (min_direct_block_size * m + o).to_string();
1730+
1731+
ts.ccmd("truncate")
1732+
.args(&["-s", p(1, -1).as_str(), "short"])
1733+
.succeeds();
1734+
ts.ccmd("truncate")
1735+
.args(&["-s", p(16, 0).as_str(), "in"])
1736+
.succeeds();
1737+
ts.ccmd("truncate")
1738+
.args(&["-s", p(16, -1).as_str(), "m1"])
1739+
.succeeds();
1740+
ts.ccmd("truncate")
1741+
.args(&["-s", p(16, 1).as_str(), "p1"])
1742+
.succeeds();
1743+
1744+
ts.ucmd()
1745+
.arg(format!("bs={}", min_direct_block_size))
1746+
.args(&["if=in", "oflag=direct", "of=out"])
1747+
.succeeds();
1748+
1749+
for testfile in ["short", "m1", "p1"] {
1750+
at.remove("out");
1751+
ts.ucmd()
1752+
.arg(format!("if={testfile}"))
1753+
.arg(format!("bs={}", min_direct_block_size))
1754+
.args(&["iflag=direct", "oflag=direct", "of=out"])
1755+
.succeeds();
1756+
}
1757+
}

0 commit comments

Comments
 (0)