Skip to content
Merged
Show file tree
Hide file tree
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
32 changes: 29 additions & 3 deletions storage/sealer/fr32/readers.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,37 @@ func (r *unpadReader) readInner(out []byte) (int, error) {
r.left -= uint64(todo)

n, err := io.ReadAtLeast(r.src, r.work[:todo], int(todo))
if err != nil && err != io.EOF {
return n, err
if err == io.ErrUnexpectedEOF {
// We got a partial read. This happens when the underlying reader
// doesn't have as much data as expected (e.g., non-power-of-2 pieces).
// Process what we got.
if n > 0 {
// Round down to complete 128-byte chunks
completeChunks := n / 128
if completeChunks > 0 {
validBytes := completeChunks * 128
Unpad(r.work[:validBytes], out[:completeChunks*127])
// Adjust left to reflect that we couldn't read everything
r.left = 0
Comment thread
rvagg marked this conversation as resolved.
return completeChunks * 127, io.EOF
}
}
// Not enough data for even one chunk
return 0, io.EOF
}
if err == io.EOF {
// Clean EOF with no data
if n == 0 {
return 0, io.EOF
}
// Got some data with EOF - shouldn't happen with ReadAtLeast but handle it
return 0, xerrors.Errorf("unexpected EOF with partial read: %d bytes", n)
}
if err != nil {
return 0, err
}
if n < int(todo) {
return 0, xerrors.Errorf("didn't read enough: %d / %d, left %d, out %d", n, todo, r.left, len(out))
return 0, xerrors.Errorf("short read without EOF: got %d, expected %d", n, todo)
}

Unpad(r.work[:todo], out[:todo.Unpadded()])
Expand Down
47 changes: 47 additions & 0 deletions storage/sealer/fr32/readers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,50 @@ func TestUnpadReaderLargeReads(t *testing.T) {

require.Equal(t, unpadded, result)
}

// TestUnpadReaderPartialPiece tests the edge case where the underlying reader
// has less data than UnpadReader expects. This happens when dealing with pieces
// that are not exact power-of-2 sizes.
// This test captures the fix for the EOF errors that occurred after PR #12884.
func TestUnpadReaderPartialPiece(t *testing.T) {
actualUnpadded := abi.UnpaddedPieceSize(127 * 100) // 100 chunks = 12700 bytes
declaredSize := abi.PaddedPieceSize(128 * 128) // Declare 128 chunks but only have 100

Comment thread
rvagg marked this conversation as resolved.
// Generate test data
unpadded := make([]byte, actualUnpadded)
n, err := rand.Read(unpadded)
require.NoError(t, err)
require.Equal(t, int(actualUnpadded), n)

// Pad the data
paddedActual := actualUnpadded.Padded()
padded := make([]byte, paddedActual)
fr32.Pad(unpadded, padded)

// Create a reader that returns EOF after the actual data
limitedReader := bytes.NewReader(padded)

// Create UnpadReader with the larger declared size
unpadReader, err := fr32.NewUnpadReader(limitedReader, declaredSize)
require.NoError(t, err)

// Read all data
result := make([]byte, 0, actualUnpadded*2)
buf := make([]byte, 1024)

for {
n, err := unpadReader.Read(buf)
if n > 0 {
result = append(result, buf[:n]...)
}
if err == io.EOF {
break
}
// The fix allows UnpadReader to handle partial reads gracefully
require.NoError(t, err, "UnpadReader should handle partial pieces without error")
}

// Verify we got the correct data
require.Equal(t, int(actualUnpadded), len(result), "Should read all available data")
require.Equal(t, unpadded, result, "Data should match original")
}
Loading