blob: a8d55cba592677286ff00154f1b9b0bddaf0d445 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Lorenz Brun1e0e3a42023-06-28 16:40:18 +02004package blockdev
5
6import (
7 "errors"
8 "fmt"
9 "io"
10)
11
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020012var ErrNotBlockDevice = errors.New("not a block device")
13
14// BlockDev represents a generic block device made up of equally-sized blocks.
15// All offsets and intervals are expressed in bytes and must be aligned to
16// BlockSize and are recommended to be aligned to OptimalBlockSize if feasible.
17// Unless stated otherwise, intervals are inclusive-exclusive, i.e. the
18// start byte is included but the end byte is not.
19type BlockDev interface {
20 io.ReaderAt
21 io.WriterAt
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020022
23 // BlockCount returns the number of blocks on the block device or -1 if it
24 // is an image with an undefined size.
25 BlockCount() int64
26
Jan Schära6da1712024-08-21 15:12:11 +020027 // BlockSize returns the block size of the block device in bytes. This must
28 // be a power of two and is commonly (but not always) either 512 or 4096.
29 BlockSize() int64
30
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020031 // OptimalBlockSize returns the optimal block size in bytes for aligning
32 // to as well as issuing I/O. IO operations with block sizes below this
33 // one might incur read-write overhead. This is the larger of the physical
34 // block size and a device-reported value if available.
35 OptimalBlockSize() int64
36
37 // Discard discards a continuous set of blocks. Discarding means the
38 // underlying device gets notified that the data in these blocks is no
39 // longer needed. This can improve performance of the device device (as it
40 // no longer needs to preserve the unused data) as well as bulk erase
41 // operations. This command is advisory and not all implementations support
42 // it. The contents of discarded blocks are implementation-defined.
43 Discard(startByte int64, endByte int64) error
44
45 // Zero zeroes a continouous set of blocks. On certain implementations this
46 // can be significantly faster than just calling Write with zeroes.
47 Zero(startByte, endByte int64) error
Jan Schära6da1712024-08-21 15:12:11 +020048
49 // Sync commits the current contents to stable storage.
50 Sync() error
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020051}
52
53func NewRWS(b BlockDev) *ReadWriteSeeker {
54 return &ReadWriteSeeker{b: b}
55}
56
57// ReadWriteSeeker provides an adapter implementing ReadWriteSeeker on top of
58// a blockdev.
59type ReadWriteSeeker struct {
60 b BlockDev
61 currPos int64
62}
63
64func (s *ReadWriteSeeker) Read(p []byte) (n int, err error) {
65 n, err = s.b.ReadAt(p, s.currPos)
66 s.currPos += int64(n)
67 return
68}
69
70func (s *ReadWriteSeeker) Write(p []byte) (n int, err error) {
71 n, err = s.b.WriteAt(p, s.currPos)
72 s.currPos += int64(n)
73 return
74}
75
76func (s *ReadWriteSeeker) Seek(offset int64, whence int) (int64, error) {
77 switch whence {
Jan Schära6da1712024-08-21 15:12:11 +020078 default:
79 return 0, errors.New("Seek: invalid whence")
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020080 case io.SeekStart:
Jan Schära6da1712024-08-21 15:12:11 +020081 case io.SeekCurrent:
82 offset += s.currPos
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020083 case io.SeekEnd:
Jan Schära6da1712024-08-21 15:12:11 +020084 offset += s.b.BlockCount() * s.b.BlockSize()
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020085 }
Jan Schära6da1712024-08-21 15:12:11 +020086 if offset < 0 {
87 return 0, errors.New("Seek: invalid offset")
88 }
89 s.currPos = offset
Lorenz Brun1e0e3a42023-06-28 16:40:18 +020090 return s.currPos, nil
91}
92
93var ErrOutOfBounds = errors.New("write out of bounds")
94
95// NewSection returns a new Section, implementing BlockDev over that subset
96// of blocks. The interval is inclusive-exclusive.
Jan Schära6da1712024-08-21 15:12:11 +020097func NewSection(b BlockDev, startBlock, endBlock int64) (*Section, error) {
98 if startBlock < 0 {
99 return nil, fmt.Errorf("invalid range: startBlock (%d) negative", startBlock)
100 }
101 if startBlock > endBlock {
102 return nil, fmt.Errorf("invalid range: startBlock (%d) bigger than endBlock (%d)", startBlock, endBlock)
103 }
104 if endBlock > b.BlockCount() {
105 return nil, fmt.Errorf("endBlock (%d) out of range (%d)", endBlock, b.BlockCount())
106 }
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200107 return &Section{
108 b: b,
109 startBlock: startBlock,
110 endBlock: endBlock,
Jan Schära6da1712024-08-21 15:12:11 +0200111 }, nil
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200112}
113
114// Section implements BlockDev on a slice of another BlockDev given a startBlock
115// and endBlock.
116type Section struct {
117 b BlockDev
118 startBlock, endBlock int64
119}
120
121func (s *Section) ReadAt(p []byte, off int64) (n int, err error) {
Jan Schära6da1712024-08-21 15:12:11 +0200122 if off < 0 {
123 return 0, errors.New("blockdev.Section.ReadAt: negative offset")
124 }
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200125 bOff := off + (s.startBlock * s.b.BlockSize())
126 bytesToEnd := (s.endBlock * s.b.BlockSize()) - bOff
Jan Schära6da1712024-08-21 15:12:11 +0200127 if bytesToEnd < 0 {
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200128 return 0, io.EOF
129 }
130 if bytesToEnd < int64(len(p)) {
Jan Schära6da1712024-08-21 15:12:11 +0200131 n, err := s.b.ReadAt(p[:bytesToEnd], bOff)
132 if err == nil {
133 err = io.EOF
134 }
135 return n, err
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200136 }
137 return s.b.ReadAt(p, bOff)
138}
139
140func (s *Section) WriteAt(p []byte, off int64) (n int, err error) {
141 bOff := off + (s.startBlock * s.b.BlockSize())
142 bytesToEnd := (s.endBlock * s.b.BlockSize()) - bOff
Jan Schära6da1712024-08-21 15:12:11 +0200143 if off < 0 || bytesToEnd < 0 {
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200144 return 0, ErrOutOfBounds
145 }
146 if bytesToEnd < int64(len(p)) {
Jan Schära6da1712024-08-21 15:12:11 +0200147 n, err := s.b.WriteAt(p[:bytesToEnd], bOff)
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200148 if err != nil {
149 // If an error happened, prioritize that error
150 return n, err
151 }
152 // Otherwise, return ErrOutOfBounds as even short writes must return an
153 // error.
154 return n, ErrOutOfBounds
155 }
Jan Schära6da1712024-08-21 15:12:11 +0200156 return s.b.WriteAt(p, bOff)
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200157}
158
159func (s *Section) BlockCount() int64 {
160 return s.endBlock - s.startBlock
161}
162
163func (s *Section) BlockSize() int64 {
164 return s.b.BlockSize()
165}
166
Jan Schära6da1712024-08-21 15:12:11 +0200167func (s *Section) OptimalBlockSize() int64 {
168 return s.b.OptimalBlockSize()
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200169}
170
171func (s *Section) Discard(startByte, endByte int64) error {
Jan Schära6da1712024-08-21 15:12:11 +0200172 if err := validAlignedRange(s, startByte, endByte); err != nil {
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200173 return err
174 }
Jan Schär0ea961c2024-04-11 13:41:40 +0200175 offset := s.startBlock * s.b.BlockSize()
176 return s.b.Discard(offset+startByte, offset+endByte)
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200177}
178
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200179func (s *Section) Zero(startByte, endByte int64) error {
Jan Schära6da1712024-08-21 15:12:11 +0200180 if err := validAlignedRange(s, startByte, endByte); err != nil {
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200181 return err
182 }
Jan Schär0ea961c2024-04-11 13:41:40 +0200183 offset := s.startBlock * s.b.BlockSize()
184 return s.b.Zero(offset+startByte, offset+endByte)
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200185}
186
Jan Schära6da1712024-08-21 15:12:11 +0200187func (s *Section) Sync() error {
188 return s.b.Sync()
189}
190
191func validAlignedRange(b BlockDev, startByte, endByte int64) error {
192 if startByte < 0 {
193 return fmt.Errorf("invalid range: startByte (%d) negative", startByte)
194 }
195 if startByte > endByte {
196 return fmt.Errorf("invalid range: startByte (%d) bigger than endByte (%d)", startByte, endByte)
197 }
198 devLen := b.BlockCount() * b.BlockSize()
199 if endByte > devLen {
200 return fmt.Errorf("endByte (%d) out of range (%d)", endByte, devLen)
201 }
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200202 if startByte%b.BlockSize() != 0 {
203 return fmt.Errorf("startByte (%d) needs to be aligned to block size (%d)", startByte, b.BlockSize())
204 }
205 if endByte%b.BlockSize() != 0 {
206 return fmt.Errorf("endByte (%d) needs to be aligned to block size (%d)", endByte, b.BlockSize())
207 }
Jan Schära6da1712024-08-21 15:12:11 +0200208 return nil
209}
210
211// GenericZero implements software-based zeroing. This can be used to implement
212// Zero when no acceleration is available or desired.
213func GenericZero(b BlockDev, startByte, endByte int64) error {
214 if err := validAlignedRange(b, startByte, endByte); err != nil {
215 return err
216 }
Lorenz Brun1e0e3a42023-06-28 16:40:18 +0200217 // Choose buffer size close to 16MiB or the range to be zeroed, whatever
218 // is smaller.
219 bufSizeTarget := int64(16 * 1024 * 1024)
220 if endByte-startByte < bufSizeTarget {
221 bufSizeTarget = endByte - startByte
222 }
223 bufSize := (bufSizeTarget / b.BlockSize()) * b.BlockSize()
224 buf := make([]byte, bufSize)
225 for i := startByte; i < endByte; i += bufSize {
226 if endByte-i < bufSize {
227 buf = buf[:endByte-i]
228 }
229 if _, err := b.WriteAt(buf, i); err != nil {
230 return fmt.Errorf("while writing zeroes: %w", err)
231 }
232 }
233 return nil
234}