mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-20 19:49:03 +02:00
dmi: add dmi_info metric via GetSystemFirmwareTable (#2500)
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sysinfoapi
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// SMBIOSSystemInfo holds the fields extracted from the SMBIOS Type 1
|
||||
// (System Information) structure.
|
||||
type SMBIOSSystemInfo struct {
|
||||
Manufacturer string
|
||||
ProductName string
|
||||
Version string
|
||||
SerialNumber string
|
||||
UUID string
|
||||
}
|
||||
|
||||
// rawSMBIOSDataHeaderSize is the size of the RawSMBIOSData header returned
|
||||
// by GetSystemFirmwareTable with the 'RSMB' provider:
|
||||
// Used20CallingMethod (1) + SMBIOSMajorVersion (1) + SMBIOSMinorVersion (1) +
|
||||
// DMARevision (1) + Length (4) = 8 bytes.
|
||||
const rawSMBIOSDataHeaderSize = 8
|
||||
|
||||
// parseSMBIOSSystemInfo parses the raw RSMB firmware table and extracts
|
||||
// the Type 1 (System Information) structure.
|
||||
func parseSMBIOSSystemInfo(data []byte) (*SMBIOSSystemInfo, error) {
|
||||
if len(data) < rawSMBIOSDataHeaderSize {
|
||||
return nil, errors.New("SMBIOS data too short for header")
|
||||
}
|
||||
|
||||
smbiosMajor := data[1]
|
||||
smbiosMinor := data[2]
|
||||
|
||||
tableLen := binary.LittleEndian.Uint32(data[4:8])
|
||||
tableData := data[rawSMBIOSDataHeaderSize:]
|
||||
|
||||
if uint32(len(tableData)) < tableLen {
|
||||
return nil, errors.New("SMBIOS table data truncated")
|
||||
}
|
||||
|
||||
tableData = tableData[:tableLen]
|
||||
|
||||
// Walk structures looking for Type 1.
|
||||
offset := 0
|
||||
for offset < len(tableData) {
|
||||
if offset+4 > len(tableData) {
|
||||
break
|
||||
}
|
||||
|
||||
structType := tableData[offset]
|
||||
structLen := int(tableData[offset+1])
|
||||
|
||||
if structLen < 4 || offset+structLen > len(tableData) {
|
||||
break
|
||||
}
|
||||
|
||||
// Collect the unformatted (string) area that follows the formatted part.
|
||||
// It is terminated by a double NUL (\0\0).
|
||||
stringsStart := offset + structLen
|
||||
stringsEnd := stringsStart
|
||||
foundTerminator := false
|
||||
|
||||
for stringsEnd < len(tableData)-1 {
|
||||
if tableData[stringsEnd] == 0 && tableData[stringsEnd+1] == 0 {
|
||||
stringsEnd += 2
|
||||
foundTerminator = true
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
stringsEnd++
|
||||
}
|
||||
|
||||
if !foundTerminator {
|
||||
return nil, errors.New("SMBIOS string area not properly terminated (missing double-NUL)")
|
||||
}
|
||||
|
||||
if structType == 1 && structLen >= 0x08 {
|
||||
// Type 1: System Information
|
||||
// Offsets (within the formatted area):
|
||||
// 04h: Manufacturer (string index)
|
||||
// 05h: Product Name (string index)
|
||||
// 06h: Version (string index)
|
||||
// 07h: Serial Number (string index)
|
||||
// 08h-17h: UUID (16 bytes) — present only when structLen >= 0x19
|
||||
strs := extractStrings(tableData[stringsStart:stringsEnd])
|
||||
|
||||
info := &SMBIOSSystemInfo{
|
||||
Manufacturer: getStringByIndex(strs, tableData[offset+0x04]),
|
||||
ProductName: getStringByIndex(strs, tableData[offset+0x05]),
|
||||
Version: getStringByIndex(strs, tableData[offset+0x06]),
|
||||
SerialNumber: getStringByIndex(strs, tableData[offset+0x07]),
|
||||
}
|
||||
|
||||
// UUID is only available in SMBIOS 2.1+ (structLen >= 0x19).
|
||||
if structLen >= 0x19 {
|
||||
// SMBIOS 2.6+ uses little-endian byte order for the first three UUID fields;
|
||||
// earlier versions use network (big-endian) order throughout.
|
||||
smbios26Plus := smbiosMajor > 2 || (smbiosMajor == 2 && smbiosMinor >= 6)
|
||||
info.UUID = formatUUID(tableData[offset+0x08:offset+0x18], smbios26Plus)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
offset = stringsEnd
|
||||
}
|
||||
|
||||
return nil, errors.New("SMBIOS Type 1 (System Information) structure not found")
|
||||
}
|
||||
|
||||
// extractStrings splits the unformatted string area into individual strings.
|
||||
// Each string is NUL-terminated; the area ends with a double NUL.
|
||||
// Empty strings are preserved by position so that 1-based indices remain correct.
|
||||
func extractStrings(data []byte) []string {
|
||||
var result []string
|
||||
|
||||
start := 0
|
||||
|
||||
for i := range data {
|
||||
if data[i] == 0 {
|
||||
result = append(result, string(data[start:i]))
|
||||
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// getStringByIndex returns the 1-based string from the list, or empty if
|
||||
// the index is 0 or out of range.
|
||||
func getStringByIndex(strs []string, index byte) string {
|
||||
if index == 0 || int(index) > len(strs) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strs[index-1]
|
||||
}
|
||||
|
||||
// formatUUID formats the 16-byte SMBIOS UUID according to RFC 4122.
|
||||
// When smbios26Plus is true, the first three fields are interpreted as
|
||||
// little-endian (SMBIOS 2.6+ convention). When false, all fields are
|
||||
// in network (big-endian) byte order (pre-2.6 convention).
|
||||
func formatUUID(raw []byte, smbios26Plus bool) string {
|
||||
if len(raw) < 16 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Check for "not present" (all 0xFF) or "not settable" (all 0x00).
|
||||
allFF := true
|
||||
allZero := true
|
||||
|
||||
for _, b := range raw {
|
||||
if b != 0xFF {
|
||||
allFF = false
|
||||
}
|
||||
|
||||
if b != 0x00 {
|
||||
allZero = false
|
||||
}
|
||||
}
|
||||
|
||||
if allFF || allZero {
|
||||
return ""
|
||||
}
|
||||
|
||||
if smbios26Plus {
|
||||
// SMBIOS 2.6+: first three components are little-endian.
|
||||
// TimeLow (4 bytes LE), TimeMid (2 bytes LE), TimeHiAndVersion (2 bytes LE),
|
||||
// ClockSeq (2 bytes BE), Node (6 bytes).
|
||||
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
|
||||
binary.LittleEndian.Uint32(raw[0:4]),
|
||||
binary.LittleEndian.Uint16(raw[4:6]),
|
||||
binary.LittleEndian.Uint16(raw[6:8]),
|
||||
binary.BigEndian.Uint16(raw[8:10]),
|
||||
raw[10:16],
|
||||
)
|
||||
}
|
||||
|
||||
// Pre-2.6: all fields in network byte order.
|
||||
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
|
||||
binary.BigEndian.Uint32(raw[0:4]),
|
||||
binary.BigEndian.Uint16(raw[4:6]),
|
||||
binary.BigEndian.Uint16(raw[6:8]),
|
||||
binary.BigEndian.Uint16(raw[8:10]),
|
||||
raw[10:16],
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user