Skip to content
Merged
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
58 changes: 54 additions & 4 deletions src/uu/dd/src/dd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1181,10 +1181,7 @@
BlockWriter::Unbuffered(o)
};

// Create a common empty buffer with a capacity of the block size.
// This is the max size needed.
let mut buf = Vec::new();
buf.try_reserve(bsize)?; // try_with_capacity is unstable https://github.com/rust-lang/rust/issues/91913
let mut buf = alloc_copy_buffer(bsize)?;

// The main read/write loop.
//
Expand Down Expand Up @@ -1348,6 +1345,26 @@
if flag == 0 { None } else { Some(flag) }
}

/// The copy buffer, `bsize` bytes long and ready to be read into.
///
/// `Read::read` fills an initialised slice, and zeroed pages are the only

Check warning on line 1350 in src/uu/dd/src/dd.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

WARNING: `cspell`: Unknown word 'initialised' (file:'src/uu/dd/src/dd.rs', line:1350)
/// initialisation that costs nothing: `vec![0; n]` allocates through

Check warning on line 1351 in src/uu/dd/src/dd.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

WARNING: `cspell`: Unknown word 'initialisation' (file:'src/uu/dd/src/dd.rs', line:1351)
/// `alloc_zeroed`, so the pages arrive from the kernel already zero and stay
/// untouched until something is read into them. Writing a fill byte over
/// reserved capacity instead faults in the whole of `bs=` before the first
/// read, which is what made a large `bs=` cost its full size in time and in
/// resident memory even with nothing to copy.
///
/// The reservation still happens first, because `vec![0; n]` aborts when the
/// allocation fails and `dd` reports that as an error instead.
fn alloc_copy_buffer(bsize: usize) -> io::Result<Vec<u8>> {
let mut probe: Vec<u8> = Vec::new();
// try_with_capacity is unstable https://github.com/rust-lang/rust/issues/91913
probe.try_reserve(bsize)?;
drop(probe);
Ok(vec![0u8; bsize])
}

/// Read from an input (that is, a source of bytes) into the given buffer.
///
/// This function also performs any conversions as specified by
Expand Down Expand Up @@ -1543,6 +1560,39 @@

use std::path::Path;

/// `dd` has to accept a `bs=` far larger than the data it will copy, the
/// way GNU dd does, so the copy buffer must not fault in its pages.
#[cfg(all(target_os = "linux", target_pointer_width = "64"))]
#[test]
fn alloc_copy_buffer_does_not_touch_its_pages() {
use crate::alloc_copy_buffer;

fn peak_rss_kib() -> u64 {
std::fs::read_to_string("/proc/self/status")
.unwrap()
.lines()
.find_map(|line| line.strip_prefix("VmHWM:"))
.and_then(|v| v.trim().trim_end_matches("kB").trim().parse().ok())
.unwrap()
}

// Larger than anything else this test binary allocates, so the reading
// below cannot be attributed to another test.
const BSIZE: usize = 4 << 30;
const SLACK_KIB: u64 = 64 << 10;

let before = peak_rss_kib();
let buf = alloc_copy_buffer(BSIZE).unwrap();
let after = peak_rss_kib();

assert_eq!(buf.len(), BSIZE);
assert!(
after - before < SLACK_KIB,
"a {BSIZE}-byte copy buffer raised peak RSS by {} KiB",
after - before
);
}

#[test]
fn bsize_test_primes() {
let (n, m) = (7901, 7919);
Expand Down
Loading