blob: 9cf4ee27c66ce3233a2fe554c88b930120090eaa [file] [log] [blame]
Lorenz Brun1e0e3a42023-06-28 16:40:18 +02001package blockdev
2
3import (
4 "errors"
5 "fmt"
6 "io"
7)
8
Lorenz Brun1e0e3a42023-06-28 16:40:18 +02009var ErrNotBlockDevice = errors.New("not a block device")
10
11// BlockDev represents a generic block device made up of equally-sized blocks.
12// All offsets and intervals are expressed in bytes and must be aligned to
13// BlockSize and are recommended to be aligned to OptimalBlockSize if feasible.
14// Unless stated otherwise, intervals are inclusive-exclusive, i.e. the
15// start byte is included but the end byte is not.
16type BlockDev interface {
17 io.ReaderAt
18 io.WriterAt
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020019
20 // BlockCount returns the number of blocks on the block device or -1 if it
21 // is an image with an undefined size.
22 BlockCount() int64
23
Jan Schära6da1712024-08-21 15:12:11 +020024 // BlockSize returns the block size of the block device in bytes. This must
25 // be a power of two and is commonly (but not always) either 512 or 4096.
26 BlockSize() int64
27
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020028 // OptimalBlockSize returns the optimal block size in bytes for aligning
29 // to as well as issuing I/O. IO operations with block sizes below this
30 // one might incur read-write overhead. This is the larger of the physical
31 // block size and a device-reported value if available.
32 OptimalBlockSize() int64
33
34 // Discard discards a continuous set of blocks. Discarding means the
35 // underlying device gets notified that the data in these blocks is no
36 // longer needed. This can improve performance of the device device (as it
37 // no longer needs to preserve the unused data) as well as bulk erase
38 // operations. This command is advisory and not all implementations support
39 // it. The contents of discarded blocks are implementation-defined.
40 Discard(startByte int64, endByte int64) error
41
42 // Zero zeroes a continouous set of blocks. On certain implementations this
43 // can be significantly faster than just calling Write with zeroes.
44 Zero(startByte, endByte int64) error
Jan Schära6da1712024-08-21 15:12:11 +020045
46 // Sync commits the current contents to stable storage.
47 Sync() error
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020048}
49
50func NewRWS(b BlockDev) *ReadWriteSeeker {
51 return &ReadWriteSeeker{b: b}
52}
53
54// ReadWriteSeeker provides an adapter implementing ReadWriteSeeker on top of
55// a blockdev.
56type ReadWriteSeeker struct {
57 b BlockDev
58 currPos int64
59}
60
61func (s *ReadWriteSeeker) Read(p []byte) (n int, err error) {
62 n, err = s.b.ReadAt(p, s.currPos)
63 s.currPos += int64(n)
64 return
65}
66
67func (s *ReadWriteSeeker) Write(p []byte) (n int, err error) {
68 n, err = s.b.WriteAt(p, s.currPos)
69 s.currPos += int64(n)
70 return
71}
72
73func (s *ReadWriteSeeker) Seek(offset int64, whence int) (int64, error) {
74 switch whence {
Jan Schära6da1712024-08-21 15:12:11 +020075 default:
76 return 0, errors.New("Seek: invalid whence")
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020077 case io.SeekStart:
Jan Schära6da1712024-08-21 15:12:11 +020078 case io.SeekCurrent:
79 offset += s.currPos
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020080 case io.SeekEnd:
Jan Schära6da1712024-08-21 15:12:11 +020081 offset += s.b.BlockCount() * s.b.BlockSize()
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020082 }
Jan Schära6da1712024-08-21 15:12:11 +020083 if offset < 0 {
84 return 0, errors.New("Seek: invalid offset")
85 }
86 s.currPos = offset
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020087 return s.currPos, nil
88}
89
90var ErrOutOfBounds = errors.New("write out of bounds")
91
92// NewSection returns a new Section, implementing BlockDev over that subset
93// of blocks. The interval is inclusive-exclusive.
Jan Schära6da1712024-08-21 15:12:11 +020094func NewSection(b BlockDev, startBlock, endBlock int64) (*Section, error) {
95 if startBlock < 0 {
96 return nil, fmt.Errorf("invalid range: startBlock (%d) negative", startBlock)
97 }
98 if startBlock > endBlock {
99 return nil, fmt.Errorf("invalid range: startBlock (%d) bigger than endBlock (%d)", startBlock, endBlock)
100 }
101 if endBlock > b.BlockCount() {
102 return nil, fmt.Errorf("endBlock (%d) out of range (%d)", endBlock, b.BlockCount())
103 }
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200104 return &Section{
105 b: b,
106 startBlock: startBlock,
107 endBlock: endBlock,
Jan Schära6da1712024-08-21 15:12:11 +0200108 }, nil
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200109}
110
111// Section implements BlockDev on a slice of another BlockDev given a startBlock
112// and endBlock.
113type Section struct {
114 b BlockDev
115 startBlock, endBlock int64
116}
117
118func (s *Section) ReadAt(p []byte, off int64) (n int, err error) {
Jan Schära6da1712024-08-21 15:12:11 +0200119 if off < 0 {
120 return 0, errors.New("blockdev.Section.ReadAt: negative offset")
121 }
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200122 bOff := off + (s.startBlock * s.b.BlockSize())
123 bytesToEnd := (s.endBlock * s.b.BlockSize()) - bOff
Jan Schära6da1712024-08-21 15:12:11 +0200124 if bytesToEnd < 0 {
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200125 return 0, io.EOF
126 }
127 if bytesToEnd < int64(len(p)) {
Jan Schära6da1712024-08-21 15:12:11 +0200128 n, err := s.b.ReadAt(p[:bytesToEnd], bOff)
129 if err == nil {
130 err = io.EOF
131 }
132 return n, err
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200133 }
134 return s.b.ReadAt(p, bOff)
135}
136
137func (s *Section) WriteAt(p []byte, off int64) (n int, err error) {
138 bOff := off + (s.startBlock * s.b.BlockSize())
139 bytesToEnd := (s.endBlock * s.b.BlockSize()) - bOff
Jan Schära6da1712024-08-21 15:12:11 +0200140 if off < 0 || bytesToEnd < 0 {
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200141 return 0, ErrOutOfBounds
142 }
143 if bytesToEnd < int64(len(p)) {
Jan Schära6da1712024-08-21 15:12:11 +0200144 n, err := s.b.WriteAt(p[:bytesToEnd], bOff)
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200145 if err != nil {
146 // If an error happened, prioritize that error
147 return n, err
148 }
149 // Otherwise, return ErrOutOfBounds as even short writes must return an
150 // error.
151 return n, ErrOutOfBounds
152 }
Jan Schära6da1712024-08-21 15:12:11 +0200153 return s.b.WriteAt(p, bOff)
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200154}
155
156func (s *Section) BlockCount() int64 {
157 return s.endBlock - s.startBlock
158}
159
160func (s *Section) BlockSize() int64 {
161 return s.b.BlockSize()
162}
163
Jan Schära6da1712024-08-21 15:12:11 +0200164func (s *Section) OptimalBlockSize() int64 {
165 return s.b.OptimalBlockSize()
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200166}
167
168func (s *Section) Discard(startByte, endByte int64) error {
Jan Schära6da1712024-08-21 15:12:11 +0200169 if err := validAlignedRange(s, startByte, endByte); err != nil {
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200170 return err
171 }
Jan Schär0ea961c2024-04-11 13:41:40 +0200172 offset := s.startBlock * s.b.BlockSize()
173 return s.b.Discard(offset+startByte, offset+endByte)
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200174}
175
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200176func (s *Section) Zero(startByte, endByte int64) error {
Jan Schära6da1712024-08-21 15:12:11 +0200177 if err := validAlignedRange(s, startByte, endByte); err != nil {
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200178 return err
179 }
Jan Schär0ea961c2024-04-11 13:41:40 +0200180 offset := s.startBlock * s.b.BlockSize()
181 return s.b.Zero(offset+startByte, offset+endByte)
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200182}
183
Jan Schära6da1712024-08-21 15:12:11 +0200184func (s *Section) Sync() error {
185 return s.b.Sync()
186}
187
188func validAlignedRange(b BlockDev, startByte, endByte int64) error {
189 if startByte < 0 {
190 return fmt.Errorf("invalid range: startByte (%d) negative", startByte)
191 }
192 if startByte > endByte {
193 return fmt.Errorf("invalid range: startByte (%d) bigger than endByte (%d)", startByte, endByte)
194 }
195 devLen := b.BlockCount() * b.BlockSize()
196 if endByte > devLen {
197 return fmt.Errorf("endByte (%d) out of range (%d)", endByte, devLen)
198 }
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200199 if startByte%b.BlockSize() != 0 {
200 return fmt.Errorf("startByte (%d) needs to be aligned to block size (%d)", startByte, b.BlockSize())
201 }
202 if endByte%b.BlockSize() != 0 {
203 return fmt.Errorf("endByte (%d) needs to be aligned to block size (%d)", endByte, b.BlockSize())
204 }
Jan Schära6da1712024-08-21 15:12:11 +0200205 return nil
206}
207
208// GenericZero implements software-based zeroing. This can be used to implement
209// Zero when no acceleration is available or desired.
210func GenericZero(b BlockDev, startByte, endByte int64) error {
211 if err := validAlignedRange(b, startByte, endByte); err != nil {
212 return err
213 }
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200214 // Choose buffer size close to 16MiB or the range to be zeroed, whatever
215 // is smaller.
216 bufSizeTarget := int64(16 * 1024 * 1024)
217 if endByte-startByte < bufSizeTarget {
218 bufSizeTarget = endByte - startByte
219 }
220 bufSize := (bufSizeTarget / b.BlockSize()) * b.BlockSize()
221 buf := make([]byte, bufSize)
222 for i := startByte; i < endByte; i += bufSize {
223 if endByte-i < bufSize {
224 buf = buf[:endByte-i]
225 }
226 if _, err := b.WriteAt(buf, i); err != nil {
227 return fmt.Errorf("while writing zeroes: %w", err)
228 }
229 }
230 return nil
231}