| package blockdev | 
 |  | 
 | import ( | 
 | 	"errors" | 
 | 	"fmt" | 
 | 	"io" | 
 | ) | 
 |  | 
 | var ErrNotBlockDevice = errors.New("not a block device") | 
 |  | 
 | // BlockDev represents a generic block device made up of equally-sized blocks. | 
 | // All offsets and intervals are expressed in bytes and must be aligned to | 
 | // BlockSize and are recommended to be aligned to OptimalBlockSize if feasible. | 
 | // Unless stated otherwise, intervals are inclusive-exclusive, i.e. the | 
 | // start byte is included but the end byte is not. | 
 | type BlockDev interface { | 
 | 	io.ReaderAt | 
 | 	io.WriterAt | 
 | 	// BlockSize returns the block size of the block device in bytes. This must | 
 | 	// be a power of two and is commonly (but not always) either 512 or 4096. | 
 | 	BlockSize() int64 | 
 |  | 
 | 	// BlockCount returns the number of blocks on the block device or -1 if it | 
 | 	// is an image with an undefined size. | 
 | 	BlockCount() int64 | 
 |  | 
 | 	// OptimalBlockSize returns the optimal block size in bytes for aligning | 
 | 	// to as well as issuing I/O. IO operations with block sizes below this | 
 | 	// one might incur read-write overhead. This is the larger of the physical | 
 | 	// block size and a device-reported value if available. | 
 | 	OptimalBlockSize() int64 | 
 |  | 
 | 	// Discard discards a continuous set of blocks. Discarding means the | 
 | 	// underlying device gets notified that the data in these blocks is no | 
 | 	// longer needed. This can improve performance of the device device (as it | 
 | 	// no longer needs to preserve the unused data) as well as bulk erase | 
 | 	// operations. This command is advisory and not all implementations support | 
 | 	// it. The contents of discarded blocks are implementation-defined. | 
 | 	Discard(startByte int64, endByte int64) error | 
 |  | 
 | 	// Zero zeroes a continouous set of blocks. On certain implementations this | 
 | 	// can be significantly faster than just calling Write with zeroes. | 
 | 	Zero(startByte, endByte int64) error | 
 | } | 
 |  | 
 | func NewRWS(b BlockDev) *ReadWriteSeeker { | 
 | 	return &ReadWriteSeeker{b: b} | 
 | } | 
 |  | 
 | // ReadWriteSeeker provides an adapter implementing ReadWriteSeeker on top of | 
 | // a blockdev. | 
 | type ReadWriteSeeker struct { | 
 | 	b       BlockDev | 
 | 	currPos int64 | 
 | } | 
 |  | 
 | func (s *ReadWriteSeeker) Read(p []byte) (n int, err error) { | 
 | 	n, err = s.b.ReadAt(p, s.currPos) | 
 | 	s.currPos += int64(n) | 
 | 	return | 
 | } | 
 |  | 
 | func (s *ReadWriteSeeker) Write(p []byte) (n int, err error) { | 
 | 	n, err = s.b.WriteAt(p, s.currPos) | 
 | 	s.currPos += int64(n) | 
 | 	return | 
 | } | 
 |  | 
 | func (s *ReadWriteSeeker) Seek(offset int64, whence int) (int64, error) { | 
 | 	switch whence { | 
 | 	case io.SeekCurrent: | 
 | 		s.currPos += offset | 
 | 	case io.SeekStart: | 
 | 		s.currPos = offset | 
 | 	case io.SeekEnd: | 
 | 		s.currPos = (s.b.BlockCount() * s.b.BlockSize()) - offset | 
 | 	} | 
 | 	return s.currPos, nil | 
 | } | 
 |  | 
 | var ErrOutOfBounds = errors.New("write out of bounds") | 
 |  | 
 | // NewSection returns a new Section, implementing BlockDev over that subset | 
 | // of blocks. The interval is inclusive-exclusive. | 
 | func NewSection(b BlockDev, startBlock, endBlock int64) *Section { | 
 | 	return &Section{ | 
 | 		b:          b, | 
 | 		startBlock: startBlock, | 
 | 		endBlock:   endBlock, | 
 | 	} | 
 | } | 
 |  | 
 | // Section implements BlockDev on a slice of another BlockDev given a startBlock | 
 | // and endBlock. | 
 | type Section struct { | 
 | 	b                    BlockDev | 
 | 	startBlock, endBlock int64 | 
 | } | 
 |  | 
 | func (s *Section) ReadAt(p []byte, off int64) (n int, err error) { | 
 | 	bOff := off + (s.startBlock * s.b.BlockSize()) | 
 | 	bytesToEnd := (s.endBlock * s.b.BlockSize()) - bOff | 
 | 	if bytesToEnd <= 0 { | 
 | 		return 0, io.EOF | 
 | 	} | 
 | 	if bytesToEnd < int64(len(p)) { | 
 | 		return s.b.ReadAt(p[:bytesToEnd], bOff) | 
 | 	} | 
 | 	return s.b.ReadAt(p, bOff) | 
 | } | 
 |  | 
 | func (s *Section) WriteAt(p []byte, off int64) (n int, err error) { | 
 | 	bOff := off + (s.startBlock * s.b.BlockSize()) | 
 | 	bytesToEnd := (s.endBlock * s.b.BlockSize()) - bOff | 
 | 	if bytesToEnd <= 0 { | 
 | 		return 0, ErrOutOfBounds | 
 | 	} | 
 | 	if bytesToEnd < int64(len(p)) { | 
 | 		n, err := s.b.WriteAt(p[:bytesToEnd], off+(s.startBlock*s.b.BlockSize())) | 
 | 		if err != nil { | 
 | 			// If an error happened, prioritize that error | 
 | 			return n, err | 
 | 		} | 
 | 		// Otherwise, return ErrOutOfBounds as even short writes must return an | 
 | 		// error. | 
 | 		return n, ErrOutOfBounds | 
 | 	} | 
 | 	return s.b.WriteAt(p, off+(s.startBlock*s.b.BlockSize())) | 
 | } | 
 |  | 
 | func (s *Section) BlockCount() int64 { | 
 | 	return s.endBlock - s.startBlock | 
 | } | 
 |  | 
 | func (s *Section) BlockSize() int64 { | 
 | 	return s.b.BlockSize() | 
 | } | 
 |  | 
 | func (s *Section) inRange(startByte, endByte int64) error { | 
 | 	if startByte > endByte { | 
 | 		return fmt.Errorf("invalid range: startByte (%d) bigger than endByte (%d)", startByte, endByte) | 
 | 	} | 
 | 	sectionLen := s.BlockCount() * s.BlockSize() | 
 | 	if startByte >= sectionLen { | 
 | 		return fmt.Errorf("startByte (%d) out of range (%d)", startByte, sectionLen) | 
 | 	} | 
 | 	if endByte > sectionLen { | 
 | 		return fmt.Errorf("endBlock (%d) out of range (%d)", endByte, sectionLen) | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | func (s *Section) Discard(startByte, endByte int64) error { | 
 | 	if err := s.inRange(startByte, endByte); err != nil { | 
 | 		return err | 
 | 	} | 
 | 	return s.b.Discard(s.startBlock+startByte, s.startBlock+endByte) | 
 | } | 
 |  | 
 | func (s *Section) OptimalBlockSize() int64 { | 
 | 	return s.b.OptimalBlockSize() | 
 | } | 
 |  | 
 | func (s *Section) Zero(startByte, endByte int64) error { | 
 | 	if err := s.inRange(startByte, endByte); err != nil { | 
 | 		return err | 
 | 	} | 
 | 	return s.b.Zero(s.startBlock+startByte, s.startBlock+endByte) | 
 | } | 
 |  | 
 | // GenericZero implements software-based zeroing. This can be used to implement | 
 | // Zero when no acceleration is available or desired. | 
 | func GenericZero(b BlockDev, startByte, endByte int64) error { | 
 | 	if startByte%b.BlockSize() != 0 { | 
 | 		return fmt.Errorf("startByte (%d) needs to be aligned to block size (%d)", startByte, b.BlockSize()) | 
 | 	} | 
 | 	if endByte%b.BlockSize() != 0 { | 
 | 		return fmt.Errorf("endByte (%d) needs to be aligned to block size (%d)", endByte, b.BlockSize()) | 
 | 	} | 
 | 	// Choose buffer size close to 16MiB or the range to be zeroed, whatever | 
 | 	// is smaller. | 
 | 	bufSizeTarget := int64(16 * 1024 * 1024) | 
 | 	if endByte-startByte < bufSizeTarget { | 
 | 		bufSizeTarget = endByte - startByte | 
 | 	} | 
 | 	bufSize := (bufSizeTarget / b.BlockSize()) * b.BlockSize() | 
 | 	buf := make([]byte, bufSize) | 
 | 	for i := startByte; i < endByte; i += bufSize { | 
 | 		if endByte-i < bufSize { | 
 | 			buf = buf[:endByte-i] | 
 | 		} | 
 | 		if _, err := b.WriteAt(buf, i); err != nil { | 
 | 			return fmt.Errorf("while writing zeroes: %w", err) | 
 | 		} | 
 | 	} | 
 | 	return nil | 
 | } |