mirror of
				https://github.com/containers/podman.git
				synced 2025-10-31 01:50:50 +08:00 
			
		
		
		
	 5e0b7e54c0
			
		
	
	5e0b7e54c0
	
	
	
		
			
			Neither of the SparseWriter users actually _wants_ the underlying WriteSeeker to be closed; so, don't. That makes it clear where the responsibility for closing the file lies, and allows us to remove the reliance on the destinations reliably returning ErrClosed. Signed-off-by: Miloslav Trmač <mitr@redhat.com>
		
			
				
	
	
		
			140 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package compression
 | ||
| 
 | ||
| import (
 | ||
| 	"errors"
 | ||
| 	"fmt"
 | ||
| 	"io"
 | ||
| )
 | ||
| 
 | ||
| const zerosThreshold = 1024
 | ||
| 
 | ||
| type WriteSeekCloser interface {
 | ||
| 	io.Closer
 | ||
| 	io.WriteSeeker
 | ||
| }
 | ||
| 
 | ||
| type sparseWriter struct {
 | ||
| 	file io.WriteSeeker
 | ||
| 	// Invariant between method calls:
 | ||
| 	// The contents of the file match the contents passed to Write, except that pendingZeroes trailing zeroes have not been written.
 | ||
| 	// Also, the data that _has_ been written does not end with a zero byte (i.e. pendingZeroes is the largest possible value.
 | ||
| 	pendingZeroes int64
 | ||
| }
 | ||
| 
 | ||
| // NewSparseWriter returns a WriteCloser for underlying file which creates
 | ||
| // holes where appropriate.
 | ||
| // NOTE: The caller must .Close() both the returned sparseWriter AND the underlying file,
 | ||
| // in that order.
 | ||
| func NewSparseWriter(file io.WriteSeeker) *sparseWriter {
 | ||
| 	return &sparseWriter{
 | ||
| 		file:          file,
 | ||
| 		pendingZeroes: 0,
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func (sw *sparseWriter) createHole(size int64) error {
 | ||
| 	_, err := sw.file.Seek(size, io.SeekCurrent)
 | ||
| 	return err
 | ||
| }
 | ||
| 
 | ||
| func zeroSpanEnd(b []byte, i int) int {
 | ||
| 	for i < len(b) && b[i] == 0 {
 | ||
| 		i++
 | ||
| 	}
 | ||
| 	return i
 | ||
| }
 | ||
| 
 | ||
| func nonzeroSpanEnd(b []byte, i int) int {
 | ||
| 	for i < len(b) && b[i] != 0 {
 | ||
| 		i++
 | ||
| 	}
 | ||
| 	return i
 | ||
| }
 | ||
| 
 | ||
| // Write writes data to the file, creating holes for long sequences of zeros.
 | ||
| func (sw *sparseWriter) Write(data []byte) (int, error) {
 | ||
| 	initialZeroSpanLength := zeroSpanEnd(data, 0)
 | ||
| 	if initialZeroSpanLength == len(data) {
 | ||
| 		sw.pendingZeroes += int64(initialZeroSpanLength)
 | ||
| 		return initialZeroSpanLength, nil
 | ||
| 	}
 | ||
| 
 | ||
| 	// We have _some_ non-zero data to write.
 | ||
| 	// Think of the input as an alternating sequence of spans of zeroes / non-zeroes 0a0b…c0,
 | ||
| 	// where the starting/ending span of zeroes may be empty.
 | ||
| 
 | ||
| 	pendingWriteOffset := 0
 | ||
| 	// The expected condition for creating a hole would be sw.pendingZeroes + initialZeroSpanLength >= zerosThreshold; but
 | ||
| 	// if sw.pendingZeroes != 0, we are going to spend a syscall to deal with sw.pendingZeroes either way.
 | ||
| 	// We might just as well make it a createHole(), even if the hole size is below zeroThreshold.
 | ||
| 	if sw.pendingZeroes != 0 || initialZeroSpanLength >= zerosThreshold {
 | ||
| 		if err := sw.createHole(sw.pendingZeroes + int64(initialZeroSpanLength)); err != nil {
 | ||
| 			return -1, err
 | ||
| 		}
 | ||
| 		// We could set sw.pendingZeroes = 0 now; it would always be overwritten on successful return from this function.
 | ||
| 		pendingWriteOffset = initialZeroSpanLength
 | ||
| 	}
 | ||
| 
 | ||
| 	current := initialZeroSpanLength
 | ||
| 	for {
 | ||
| 		// Invariant at this point of this loop:
 | ||
| 		// - pendingWriteOffset <= current < len(data)
 | ||
| 		// - data[current] != 0
 | ||
| 		// - data[pendingWriteOffset:current] has not yet been written
 | ||
| 		if pendingWriteOffset > current || current >= len(data) {
 | ||
| 			return -1, fmt.Errorf("internal error: sparseWriter invariant violation: %d <= %d < %d", pendingWriteOffset, current, len(data))
 | ||
| 		}
 | ||
| 		if b := data[current]; b == 0 {
 | ||
| 			return -1, fmt.Errorf("internal error: sparseWriter invariant violation: %d@%d", b, current)
 | ||
| 		}
 | ||
| 
 | ||
| 		nonzeroSpanEnd := nonzeroSpanEnd(data, current)
 | ||
| 		if nonzeroSpanEnd == current {
 | ||
| 			return -1, fmt.Errorf("internal error: sparseWriter’s nonzeroSpanEnd didn’t advance")
 | ||
| 		}
 | ||
| 		zeroSpanEnd := zeroSpanEnd(data, nonzeroSpanEnd) // possibly == nonzeroSpanEnd
 | ||
| 		zeroSpanLength := zeroSpanEnd - nonzeroSpanEnd
 | ||
| 		if zeroSpanEnd < len(data) && zeroSpanLength < zerosThreshold {
 | ||
| 			// Too small a hole, keep going
 | ||
| 			current = zeroSpanEnd
 | ||
| 			continue
 | ||
| 		}
 | ||
| 
 | ||
| 		// We have either reached the end, or found an interesting hole. Issue a write.
 | ||
| 		if _, err := sw.file.Write(data[pendingWriteOffset:nonzeroSpanEnd]); err != nil {
 | ||
| 			return -1, err
 | ||
| 		}
 | ||
| 		if zeroSpanEnd == len(data) {
 | ||
| 			sw.pendingZeroes = int64(zeroSpanLength)
 | ||
| 			return zeroSpanEnd, nil
 | ||
| 		}
 | ||
| 
 | ||
| 		if err := sw.createHole(int64(zeroSpanLength)); err != nil {
 | ||
| 			return -1, err
 | ||
| 		}
 | ||
| 		pendingWriteOffset = zeroSpanEnd
 | ||
| 		current = zeroSpanEnd
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // Close closes the SparseWriter's underlying file.
 | ||
| func (sw *sparseWriter) Close() error {
 | ||
| 	if sw.file == nil {
 | ||
| 		return errors.New("file is already closed")
 | ||
| 	}
 | ||
| 	if sw.pendingZeroes != 0 {
 | ||
| 		if holeSize := sw.pendingZeroes - 1; holeSize >= zerosThreshold {
 | ||
| 			if err := sw.createHole(holeSize); err != nil {
 | ||
| 				return err
 | ||
| 			}
 | ||
| 			sw.pendingZeroes -= holeSize
 | ||
| 		}
 | ||
| 		var zeroArray [zerosThreshold]byte
 | ||
| 		if _, err := sw.file.Write(zeroArray[:sw.pendingZeroes]); err != nil {
 | ||
| 			return err
 | ||
| 		}
 | ||
| 	}
 | ||
| 	sw.file = nil
 | ||
| 	return nil
 | ||
| }
 |