blob: 193f93cafd1df95a81593ea985fba516399ba240 [file] [log] [blame]
Lorenz Brun1e0e3a42023-06-28 16:40:18 +02001package blockdev
2
3import (
4 "errors"
5 "fmt"
6 "io"
7 "math/bits"
8)
9
10// Memory is a memory-backed implementation of BlockDev. It is optimal
11// for testing and temporary use, as it is fast and platform-independent.
12type Memory struct {
13 blockSize int64
14 blockCount int64
15 data []byte
16}
17
18// NewMemory returns a new memory-backed block device with the given geometry.
19func NewMemory(blockSize, blockCount int64) (*Memory, error) {
20 if blockSize <= 0 {
21 return nil, errors.New("block size cannot be zero or negative")
22 }
23 if bits.OnesCount64(uint64(blockSize)) > 1 {
24 return nil, fmt.Errorf("block size must be a power of two (got %d)", blockSize)
25 }
26 if blockCount < 0 {
27 return nil, errors.New("block count cannot be negative")
28 }
29 return &Memory{
30 blockSize: blockSize,
31 blockCount: blockCount,
32 data: make([]byte, blockSize*blockCount),
33 }, nil
34}
35
36// MustNewMemory works exactly like NewMemory, but panics when NewMemory would
37// return an error. Intended for use in tests.
38func MustNewMemory(blockSize, blockCount int64) *Memory {
39 m, err := NewMemory(blockSize, blockCount)
40 if err != nil {
41 panic(err)
42 }
43 return m
44}
45
46func (m *Memory) ReadAt(p []byte, off int64) (int, error) {
47 devSize := m.blockSize * m.blockCount
48 if off > devSize {
49 return 0, io.EOF
50 }
51 // TODO: Alignment checks?
52 copy(p, m.data[off:])
53 n := len(m.data[off:])
54 if n < len(p) {
55 return n, io.EOF
56 }
57 return len(p), nil
58}
59
60func (m *Memory) WriteAt(p []byte, off int64) (int, error) {
61 devSize := m.blockSize * m.blockCount
62 if off > devSize {
63 return 0, io.EOF
64 }
65 // TODO: Alignment checks?
66 copy(m.data[off:], p)
67 n := len(m.data[off:])
68 if n < len(p) {
69 return n, io.EOF
70 }
71 return len(p), nil
72}
73
74func (m *Memory) BlockSize() int64 {
75 return m.blockSize
76}
77
78func (m *Memory) BlockCount() int64 {
79 return m.blockCount
80}
81
82func (m *Memory) OptimalBlockSize() int64 {
83 return m.blockSize
84}
85
86func (m *Memory) validRange(startByte, endByte int64) error {
87 if startByte > endByte {
88 return fmt.Errorf("startByte (%d) larger than endByte (%d), invalid interval", startByte, endByte)
89 }
90 devSize := m.blockSize * m.blockCount
91 if startByte >= devSize || startByte < 0 {
92 return fmt.Errorf("startByte (%d) out of range (0-%d)", endByte, devSize)
93 }
94 if endByte > devSize || endByte < 0 {
95 return fmt.Errorf("endByte (%d) out of range (0-%d)", endByte, devSize)
96 }
97 // Alignment check works for powers of two by looking at every bit below
98 // the bit set in the block size.
99 if startByte&(m.blockSize-1) != 0 {
100 return fmt.Errorf("startByte (%d) is not aligned to blockSize (%d)", startByte, m.blockSize)
101 }
102 if endByte&(m.blockSize-1) != 0 {
103 return fmt.Errorf("endByte (%d) is not aligned to blockSize (%d)", startByte, m.blockSize)
104 }
105 return nil
106}
107
108func (m *Memory) Discard(startByte, endByte int64) error {
109 if err := m.validRange(startByte, endByte); err != nil {
110 return err
111 }
112 for i := startByte; i < endByte; i++ {
113 // Intentionally don't set to zero as Discard doesn't guarantee
114 // any specific contents. Call Zero if you need this.
115 m.data[i] = 0xaa
116 }
117 return nil
118}
119
120func (m *Memory) Zero(startByte, endByte int64) error {
121 if err := m.validRange(startByte, endByte); err != nil {
122 return err
123 }
124 for i := startByte; i < endByte; i++ {
125 m.data[i] = 0x00
126 }
127 return nil
128}