-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserialize.go
More file actions
121 lines (113 loc) · 3.33 KB
/
Copy pathserialize.go
File metadata and controls
121 lines (113 loc) · 3.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package blockdev
import (
"encoding/binary"
"fmt"
"sort"
"time"
)
// EntrySize is the on-wire size of one changed-block record in the serialized
// blob: 8 bytes big-endian block number + BlockSize bytes of data.
const EntrySize = 8 + BlockSize
// Serialize emits the changed blocks in ascending block-number order with no
// header or trailer. Returns nil for an empty overlay so a fresh device costs
// zero allocations.
func (b *BlockDevice) Serialize() []byte {
var start time.Time
if b.cfg.observer != nil {
start = time.Now()
}
blob := b.serialize()
if b.cfg.observer != nil {
b.fireObserver(Event{
Op: OpSerialize,
Device: b.cfg.name,
Length: len(blob),
Blocks: len(blob) / EntrySize,
Duration: time.Since(start),
})
}
return blob
}
type serializedEntry struct {
block int64
data []byte
}
func (b *BlockDevice) serialize() []byte {
// Take a point-in-time snapshot under the read lock; stored slices are
// never mutated (overlay.put replaces, never edits in place) so it's safe
// to encode them after the lock is released.
var entries []serializedEntry
b.ov.each(func(block int64, data []byte) bool {
entries = append(entries, serializedEntry{block: block, data: data})
return true
})
if len(entries) == 0 {
return nil
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].block < entries[j].block
})
blob := make([]byte, len(entries)*EntrySize)
for i, e := range entries {
off := i * EntrySize
binary.BigEndian.PutUint64(blob[off:], uint64(e.block))
copy(blob[off+8:], e.data)
}
return blob
}
// Deserialize reconstructs a BlockDevice from a blob produced by Serialize
// and the original initial base. Rejects any blob that doesn't match what our
// encoder produces — length not a multiple of EntrySize, block numbers out of
// range, duplicate blocks, or non-ascending order.
func Deserialize(serialized, initial []byte, opts ...Option) (*BlockDevice, error) {
bd, err := New(initial, opts...)
if err != nil {
return nil, err
}
var start time.Time
if bd.cfg.observer != nil {
start = time.Now()
}
loadErr := bd.loadOverlay(serialized)
if bd.cfg.observer != nil {
bd.fireObserver(Event{
Op: OpDeserialize,
Device: bd.cfg.name,
Length: len(serialized),
Blocks: len(serialized) / EntrySize,
Duration: time.Since(start),
Err: loadErr,
})
}
if loadErr != nil {
return nil, loadErr
}
return bd, nil
}
func (b *BlockDevice) loadOverlay(serialized []byte) error {
if len(serialized) == 0 {
return nil
}
if len(serialized)%EntrySize != 0 {
return fmt.Errorf("blockdev.Deserialize: len(serialized)=%d not a multiple of %d: %w",
len(serialized), EntrySize, ErrBadFormat)
}
maxBlock := b.length / BlockSize
nEntries := len(serialized) / EntrySize
prevBlock := int64(-1)
for i := 0; i < nEntries; i++ {
off := i * EntrySize
blockNum := int64(binary.BigEndian.Uint64(serialized[off:]))
if blockNum < 0 || blockNum >= maxBlock {
return fmt.Errorf("blockdev.Deserialize: entry %d: block#=%d out of range [0, %d): %w",
i, blockNum, maxBlock, ErrBadFormat)
}
if blockNum <= prevBlock {
return fmt.Errorf("blockdev.Deserialize: entry %d: block#=%d not strictly ascending after %d: %w",
i, blockNum, prevBlock, ErrBadFormat)
}
prevBlock = blockNum
b.ov.put(blockNum, serialized[off+8:off+EntrySize])
}
return nil
}