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
122
123
124
125
126
127
128
129
130
131
132
|
package rrdformat
import (
"encoding/binary"
"fmt"
"math"
"git.lukeshu.com/go/librrd/rrdformat/rrdbinary"
)
func SniffArchitecture(data []byte) (rrdbinary.Architecture, error) {
var arch rrdbinary.Architecture
var header struct {
Cookie rrdbinary.String `rrdbinary:"size=4" xml:"-"`
Version rrdbinary.String `rrdbinary:"size=5" xml:"version"`
}
if err := rrdbinary.Unmarshal(arch, data, &header); err != nil {
return rrdbinary.Architecture{}, err
}
// 1. File header magic number
if header.Cookie != "RRD" {
return rrdbinary.Architecture{}, rrdbinary.NewBinError("not an RRD file: wrong magic number", data, 0, 4)
}
// 1. File format version string
switch header.Version {
case "0001", "0002", "0003", "0004", "0005":
// do nothing
default:
return rrdbinary.Architecture{}, rrdbinary.NewBinError(fmt.Sprintf("can't handle RRD file version %q", header.Version), data, 4, 5)
}
// 3. float cookie
//
// Assume IEEE 754 doubles. C doesn't assume 754 doubles, but anything that doesn't use 754 doubles is exotic
// enough that I'm OK saying "you're going to need to use `rrdtool dump`". This lets us assume that:
// - a 'double' is 8 bytes wide
// - the value will be exactly equal, and we don't need to worry about weird rounding.
arch.FloatWidth = 8
magicFloat := float64(8.642135e130)
floatAddrPacked := 9
floatAddr32 := ((floatAddrPacked + 3) / 4) * 4
floatAddr64 := ((floatAddrPacked + 7) / 8) * 8
var restOffset int
switch {
case len(data) < floatAddr32+arch.FloatWidth:
return rrdbinary.Architecture{}, rrdbinary.NewBinError("unexpected end-of-file", data, floatAddrPacked, floatAddr64+arch.FloatWidth-floatAddrPacked)
case math.Float64frombits(binary.LittleEndian.Uint64(data[floatAddr32:])) == magicFloat:
arch.ByteOrder = binary.LittleEndian
arch.FloatAlign = 4
restOffset = floatAddr32 + arch.FloatWidth
case math.Float64frombits(binary.BigEndian.Uint64(data[floatAddr32:])) == magicFloat:
arch.ByteOrder = binary.BigEndian
arch.FloatAlign = 4
restOffset = floatAddr32 + arch.FloatWidth
case len(data) < floatAddr64+arch.FloatWidth:
return rrdbinary.Architecture{}, rrdbinary.NewBinError("unexpected end-of-file", data, floatAddrPacked, floatAddr64+arch.FloatWidth-floatAddrPacked)
case math.Float64frombits(binary.LittleEndian.Uint64(data[floatAddr64:])) == magicFloat:
arch.ByteOrder = binary.LittleEndian
arch.FloatAlign = 8
restOffset = floatAddr64 + arch.FloatWidth
case math.Float64frombits(binary.BigEndian.Uint64(data[floatAddr64:])) == magicFloat:
arch.ByteOrder = binary.BigEndian
arch.FloatAlign = 8
restOffset = floatAddr64 + arch.FloatWidth
default:
return rrdbinary.Architecture{}, rrdbinary.NewBinError("failed to sniff byte-order and float-alignment",
data, floatAddrPacked, floatAddr64+arch.FloatWidth-floatAddrPacked)
}
// 5, 6. ds_cnt, rra_cnt
switch arch.FloatAlign {
case 4:
// Assume that if floats are only 32-bit aligned, then everything is 32-bit
arch.IntWidth = 4
arch.IntAlign = 4
case 8:
// If floats are 64-bit aligned, then this might be all-in on 64-bit, or it might 32-bit ints.
// (The following heuristic is borrowed from javascriptRRD, and adjusted to also work with big-endian.)
//
// The next 2 things after the float_cookie are ds_cnt and rra_cnt (both 'unsigned long'--which may be
// either 32 or 64 bit). We'll inspect the bytes a bit to guess how long a long is.
//
// By assuming
// 1. ds_cnt <= math.MaxUint32
// 2l. rra_cnt > 0 (relevant if little-endian)
// 2b. ds_cnt > 0 (relevant if big-endian)
// we can inspect the 4 bytes (marked "big" or "little" below) that are either
// - the most significant bits of 64-bit ds_cnt, or
// - the entirety of 32-bit rra_cnt (if littlen-endian) or 32-bit ds_cnt (if big-endian)
// If we see that those 4 bytes are all 0, then we assume that it's part of a 64-bit ds_cnt.
//
// | | | | | | | big | little |
// |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|
// 32 | R| R| D|\0| 0| 0| 0| 3|\0| |<----doublecookie----->|<--ds_cnt->|<-rra_cnt->|
// 64le | R| R| D|\0| 0| 0| 0| 3|\0| |<----doublecookie----->|<1111----ds_cnt---0000>|
// 64be | R| R| D|\0| 0| 0| 0| 3|\0| |<----doublecookie----->|<0000----ds_cnt---1111>|
if len(data) < restOffset+8 {
return rrdbinary.Architecture{}, rrdbinary.NewBinError("unexpected end of file", data, restOffset, 8)
}
offset := map[binary.ByteOrder]int{
binary.BigEndian: restOffset, // 24 in the above diagram
binary.LittleEndian: restOffset + 4, // 28 in the above diagram
}[arch.ByteOrder]
if arch.ByteOrder.Uint32(data[offset:]) == 0 {
arch.IntWidth = 8
arch.IntAlign = 8
} else {
arch.IntWidth = 4
arch.IntAlign = 4
}
}
// The above just os happens that FloatXXX >= IntXXX, so we
// can just set the Unival stuff to the Float Stuff.
arch.UnivalWidth = arch.FloatWidth // max(FloatWidth, IntWidth)
arch.UnivalAlign = arch.FloatAlign // max(FloatAlign, IntAlign)
// FIXME: Figure out how to sniff the sizeof(time_t).
//
// javascriptRRD assumes that it's the same as sizeof(long),
// which his historically been true, but
// - isn't true of proprietary 32-bit Unixen that are 2038-safe
// - isn't true of the Linux kernel with the x32 ABI
arch.TimeWidth = arch.IntWidth
arch.TimeAlign = arch.IntAlign
return arch, nil
}
|