Skip to content

Commit

Permalink
Merge pull request #3156 from jfinkels/dd-cbs-blocks
Browse files Browse the repository at this point in the history
dd: pad partial record with spaces in some cases
  • Loading branch information
sylvestre authored Mar 3, 2022
2 parents 1c023c6 + 9f367b7 commit 66e9956
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 21 deletions.
53 changes: 33 additions & 20 deletions src/uu/dd/src/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ use std::io::Read;
const NEWLINE: u8 = b'\n';
const SPACE: u8 = b' ';

/// Splits the content of buf into cbs-length blocks
/// Appends padding as specified by conv=block and cbs=N
/// Expects ascii encoded data
fn block(buf: &[u8], cbs: usize, rstat: &mut ReadStat) -> Vec<Vec<u8>> {
/// Split a slice into chunks, padding or truncating as necessary.
///
/// The slice `buf` is split on newlines, then each block is resized
/// to `cbs` bytes, padding with spaces if necessary. This function
/// expects the input bytes to be ASCII-encoded.
///
/// If `sync` is true and there has been at least one partial record
/// read from the input (as indicated in `rstat`), then leave an
/// all-spaces block at the end. Otherwise, remove the last block if
/// it is all spaces.
fn block(buf: &[u8], cbs: usize, sync: bool, rstat: &mut ReadStat) -> Vec<Vec<u8>> {
let mut blocks = buf
.split(|&e| e == NEWLINE)
.map(|split| split.to_vec())
Expand All @@ -31,8 +38,11 @@ fn block(buf: &[u8], cbs: usize, rstat: &mut ReadStat) -> Vec<Vec<u8>> {
blocks
});

// If `sync` is true and there has been at least one partial
// record read from the input, then leave the all-spaces block at
// the end. Otherwise, remove it.
if let Some(last) = blocks.last() {
if last.iter().all(|&e| e == SPACE) {
if (!sync || rstat.reads_partial == 0) && last.iter().all(|&e| e == SPACE) {
blocks.pop();
}
}
Expand Down Expand Up @@ -100,7 +110,7 @@ pub(crate) fn conv_block_unblock_helper<R: Read>(
// ascii input so perform the block first
let cbs = i.cflags.block.unwrap();

let mut blocks = block(&buf, cbs, rstat);
let mut blocks = block(&buf, cbs, i.cflags.sync.is_some(), rstat);

if let Some(ct) = i.cflags.ctable {
for buf in &mut blocks {
Expand All @@ -119,7 +129,10 @@ pub(crate) fn conv_block_unblock_helper<R: Read>(
apply_conversion(&mut buf, ct);
}

let blocks = block(&buf, cbs, rstat).into_iter().flatten().collect();
let blocks = block(&buf, cbs, i.cflags.sync.is_some(), rstat)
.into_iter()
.flatten()
.collect();

Ok(blocks)
} else if should_unblock_then_conv(i) {
Expand Down Expand Up @@ -167,7 +180,7 @@ mod tests {
fn block_test_no_nl() {
let mut rs = ReadStat::default();
let buf = [0u8, 1u8, 2u8, 3u8];
let res = block(&buf, 4, &mut rs);
let res = block(&buf, 4, false, &mut rs);

assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]);
}
Expand All @@ -176,7 +189,7 @@ mod tests {
fn block_test_no_nl_short_record() {
let mut rs = ReadStat::default();
let buf = [0u8, 1u8, 2u8, 3u8];
let res = block(&buf, 8, &mut rs);
let res = block(&buf, 8, false, &mut rs);

assert_eq!(
res,
Expand All @@ -188,7 +201,7 @@ mod tests {
fn block_test_no_nl_trunc() {
let mut rs = ReadStat::default();
let buf = [0u8, 1u8, 2u8, 3u8, 4u8];
let res = block(&buf, 4, &mut rs);
let res = block(&buf, 4, false, &mut rs);

// Commented section(s) should be truncated and appear for reference only.
assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8 /*, 4u8*/],]);
Expand All @@ -201,7 +214,7 @@ mod tests {
let buf = [
0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8,
];
let res = block(&buf, 4, &mut rs);
let res = block(&buf, 4, false, &mut rs);

assert_eq!(
res,
Expand All @@ -221,7 +234,7 @@ mod tests {
fn block_test_surrounded_nl() {
let mut rs = ReadStat::default();
let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8];
let res = block(&buf, 8, &mut rs);
let res = block(&buf, 8, false, &mut rs);

assert_eq!(
res,
Expand All @@ -238,7 +251,7 @@ mod tests {
let buf = [
0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8, 9u8,
];
let res = block(&buf, 8, &mut rs);
let res = block(&buf, 8, false, &mut rs);

assert_eq!(
res,
Expand All @@ -256,7 +269,7 @@ mod tests {
let buf = [
0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, NEWLINE, 8u8, 9u8,
];
let res = block(&buf, 8, &mut rs);
let res = block(&buf, 8, false, &mut rs);

assert_eq!(
res,
Expand All @@ -272,7 +285,7 @@ mod tests {
fn block_test_end_nl_diff_cbs_block() {
let mut rs = ReadStat::default();
let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE];
let res = block(&buf, 4, &mut rs);
let res = block(&buf, 4, false, &mut rs);

assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]);
}
Expand All @@ -281,7 +294,7 @@ mod tests {
fn block_test_end_nl_same_cbs_block() {
let mut rs = ReadStat::default();
let buf = [0u8, 1u8, 2u8, NEWLINE];
let res = block(&buf, 4, &mut rs);
let res = block(&buf, 4, false, &mut rs);

assert_eq!(res, vec![vec![0u8, 1u8, 2u8, SPACE]]);
}
Expand All @@ -290,7 +303,7 @@ mod tests {
fn block_test_double_end_nl() {
let mut rs = ReadStat::default();
let buf = [0u8, 1u8, 2u8, NEWLINE, NEWLINE];
let res = block(&buf, 4, &mut rs);
let res = block(&buf, 4, false, &mut rs);

assert_eq!(
res,
Expand All @@ -302,7 +315,7 @@ mod tests {
fn block_test_start_nl() {
let mut rs = ReadStat::default();
let buf = [NEWLINE, 0u8, 1u8, 2u8, 3u8];
let res = block(&buf, 4, &mut rs);
let res = block(&buf, 4, false, &mut rs);

assert_eq!(
res,
Expand All @@ -314,7 +327,7 @@ mod tests {
fn block_test_double_surrounded_nl_no_trunc() {
let mut rs = ReadStat::default();
let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8];
let res = block(&buf, 8, &mut rs);
let res = block(&buf, 8, false, &mut rs);

assert_eq!(
res,
Expand All @@ -332,7 +345,7 @@ mod tests {
let buf = [
0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8,
];
let res = block(&buf, 4, &mut rs);
let res = block(&buf, 4, false, &mut rs);

assert_eq!(
res,
Expand Down
25 changes: 24 additions & 1 deletion tests/by-util/test_dd.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// 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, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi
// 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, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg

use crate::common::util::*;

Expand Down Expand Up @@ -1116,3 +1116,26 @@ fn test_truncated_record() {
fn test_outfile_dev_null() {
new_ucmd!().arg("of=/dev/null").succeeds().no_stdout();
}

#[test]
fn test_block_sync() {
new_ucmd!()
.args(&["ibs=5", "cbs=5", "conv=block,sync", "status=noxfer"])
.pipe_in("012\nabcde\n")
.succeeds()
// blocks: 1 2
.stdout_is("012 abcde")
.stderr_is("2+0 records in\n0+1 records out\n");

// It seems that a partial record in is represented as an
// all-spaces block at the end of the output. The "1 truncated
// record" line is present in the status report due to the line
// "abcdefg\n" being truncated to "abcde".
new_ucmd!()
.args(&["ibs=5", "cbs=5", "conv=block,sync", "status=noxfer"])
.pipe_in("012\nabcdefg\n")
.succeeds()
// blocks: 1 2 3
.stdout_is("012 abcde ")
.stderr_is("2+1 records in\n0+1 records out\n1 truncated record\n");
}

0 comments on commit 66e9956

Please sign in to comment.