Skip to content

Conversation

romanstingler
Copy link

Addressed a bug in dd where partial writes to a slow pipe reader resulted in truncated output. Modified write_block in Output struct to retry writing until the full block is written, ignoring the fullblock flag for output operations. This ensures data integrity even with slow readers, matching GNU dd behavior. Fixes issue observed in tests with mismatched MD5 sums and partial record counts (e.g., 0+1 records out).

https://download.virtualbox.org/virtualbox/7.2.2/ -> VBoxGuestAdditions_7.2.2.iso

❯ sudo mkdir -p /mnt/vbox
  sudo mount -o loop VBoxGuestAdditions_7.2.2.iso /mnt/vbox
  cp /mnt/vbox/VBoxLinuxAdditions.run . 
  sudo umount /mnt/vbox
  rmdir /mnt/vbox
  ls -l VBoxLinuxAdditions.run 

Partial Write Bug in uutils dd

The partial write bug in uutils dd occurs when writing large blocks to a pipe with a slow reader, resulting in truncated output (as seen with 0+1 records out and mismatched MD5 sums). The key area of interest is how dd handles writing data to the output destination.

In dd.rs, the write_block method of the Output struct (lines 864-883) is responsible for writing a block of data to the destination.

Issue Identified

The current implementation retries writing if the write operation is interrupted (io::ErrorKind::Interrupted), which is correct. However, it does not handle the case where a partial write occurs (i.e., wlen < chunk[base_idx..].len()) without being interrupted. When writing to a pipe with a slow reader, the kernel may return a partial write (less than the requested amount) without an error, and the code exits the loop if !self.settings.iflags.fullblock is true. Since iflags.fullblock is typically not set for output operations (it's meant for input), the loop exits after the first partial write, leading to truncated output.

Root Cause

The condition if (base_idx >= full_len) || !self.settings.iflags.fullblock means that unless fullblock is set (which it often isn't for output), the function returns after the first write attempt, even if only part of the data was written. This mimics the behavior we observed in tests where uutils dd does not retry to write the remaining data, causing the 0+1 records out and mismatched byte counts/MD5 sums.

Proposed Fix

To fix the partial write issue, we need to ensure that write_block continues to retry writing until the entire block is written or an error occurs, regardless of the fullblock flag. The fullblock flag should only apply to input operations, not output. Here's how we can modify the code:

  • Remove the !self.settings.iflags.fullblock condition from the loop exit criteria in write_block.
  • Continue looping until base_idx >= full_len or a non-interrupted error occurs.

This change ensures that uutils dd matches the behavior of GNU dd in handling partial writes to slow pipes, preventing data truncation.

related
https://bugs.launchpad.net/ubuntu/+source/makeself/+bug/2125535
VirtualBox/virtualbox#226 (comment)
megastep/makeself@51e7299

Copy link

github-actions bot commented Oct 8, 2025

GNU testsuite comparison:

Skip an intermittent issue tests/tail/overlay-headers (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/misc/tee (passes in this run but fails in the 'main' branch)

Addressed a bug in `dd` where partial writes to a slow pipe reader resulted in truncated output. Modified `write_block` in `Output` struct to retry writing until the full block is written, ignoring the `fullblock` flag for output operations. This ensures data integrity even with slow readers, matching GNU `dd` behavior. Fixes issue observed in tests with mismatched MD5 sums and partial record counts (e.g., `0+1 records out`).
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/misc/tee (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/timeout/timeout (passes in this run but fails in the 'main' branch)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants