fix(mac): coalesce unaligned writes/reads to /dev/rdisk* (.img.zst EINVAL)#1621
Merged
Conversation
…NVAL)
macOS raw block devices (/dev/rdisk*) reject pwrite() / pread() with
EINVAL when the length is not a multiple of the logical block size.
libarchive's zstd decompressor emits chunks of arbitrary length, so
writing a .img.zst custom OS fails mid-stream with "Error writing to
device". Reproduces around 30% on a typical Raspberry Pi OS image.
The legacy dispatch_io path didn't have this problem because GCD's
channel I/O coalesces unaligned writes internally. The current
implementation uses direct pwrite() and lost that property.
Fix: per-instance tail buffer + read-modify-write on flush.
- OpenDevice() caches the logical block size via DKIOCGETBLOCKSIZE
(default 512 fallback).
- New PwriteAligned() prepends any pending tail to the incoming
write, pwrites the block-aligned portion at offset, and stashes
the residue for the next contiguous write. Returns the caller-
visible bytes-committed (== requested size on success) even when
a partial block is deferred. tail_mutex_ guards concurrent
access from async-write workers.
- FlushAlignTail() reads the BS-byte block at tail_offset_,
splices in the residue, and writes it back. Invoked from
Flush() / ForceSync() / Close() so the final partial block
lands before the fd is released.
Routed through:
WriteSequential - sync write path (was using write(),
now uses PwriteAligned at
async_write_offset_ which already
tracks the logical cursor)
AsyncWriteSequential - async write path (was direct pwrite
in the dispatched block, now uses
PwriteAligned)
DrainAndSwitchToSync - sync-fallback replay of pending
async writes
ReadSequential - rounds the pread length up to a
block, returns at most the requested
bytes to the caller
Affects only the macOS code path; no protocol/ABI changes. Tested
against the same .img.zst image that reproduced the bug.
126e460 to
d136d53
Compare
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
macOS raw block devices (/dev/rdisk*) reject pwrite() / pread() with EINVAL when the length is not a multiple of the logical block size. libarchive's zstd decompressor emits chunks of arbitrary length, so writing a .img.zst custom OS fails mid-stream with "Error writing to device". Reproduces around 30% on a typical Raspberry Pi OS image.
The legacy dispatch_io path didn't have this problem because GCD's channel I/O coalesces unaligned writes internally. The current implementation uses direct pwrite() and lost that property.
Fix: per-instance tail buffer + read-modify-write on flush.