-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlinux.go
More file actions
262 lines (217 loc) · 7.24 KB
/
linux.go
File metadata and controls
262 lines (217 loc) · 7.24 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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
//go:build linux
package machineid
import (
"context"
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
)
// collectIdentifiers gathers Linux-specific hardware identifiers based on provider config.
func collectIdentifiers(ctx context.Context, p *Provider, diag *DiagnosticInfo) ([]string, error) {
var identifiers []string
logger := p.logger
if p.includeCPU {
identifiers = appendIdentifierIfValid(identifiers, func() (string, error) {
return linuxCPUID(logger)
}, "cpu:", diag, ComponentCPU, logger)
}
if p.includeSystemUUID {
identifiers = appendIdentifierIfValid(identifiers, func() (string, error) {
return linuxSystemUUID(logger)
}, "uuid:", diag, ComponentSystemUUID, logger)
identifiers = appendIdentifierIfValid(identifiers, func() (string, error) {
return linuxMachineID(logger)
}, "machine:", diag, ComponentMachineID, logger)
}
if p.includeMotherboard {
identifiers = appendIdentifierIfValid(identifiers, func() (string, error) {
return linuxMotherboardSerial(logger)
}, "mb:", diag, ComponentMotherboard, logger)
}
if p.includeMAC {
identifiers = appendIdentifiersIfValid(identifiers, func() ([]string, error) {
return collectMACAddresses(p.macFilter, logger)
}, "mac:", diag, ComponentMAC, logger)
}
if p.includeDisk {
identifiers = appendIdentifiersIfValid(identifiers, func() ([]string, error) {
return linuxDiskSerials(ctx, p.commandExecutor, logger)
}, "disk:", diag, ComponentDisk, logger)
}
return identifiers, nil
}
// linuxCPUID retrieves CPU information from /proc/cpuinfo.
func linuxCPUID(logger *slog.Logger) (string, error) {
const path = "/proc/cpuinfo"
data, err := os.ReadFile(path)
if err != nil {
if logger != nil {
logger.Debug("failed to read CPU info", "path", path, "error", err)
}
return "", err
}
if logger != nil {
logger.Debug("read CPU info", "path", path)
}
return parseCPUInfo(string(data)), nil
}
// parseCPUInfo extracts CPU information from /proc/cpuinfo content.
func parseCPUInfo(content string) string {
lines := strings.Split(content, "\n")
var processor, vendorID, modelName, flags string
for _, line := range lines {
line = strings.TrimSpace(line)
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
continue
}
switch {
case strings.HasPrefix(line, "processor"):
processor = strings.TrimSpace(parts[1])
case strings.HasPrefix(line, "vendor_id"):
vendorID = strings.TrimSpace(parts[1])
case strings.HasPrefix(line, "model name"):
modelName = strings.TrimSpace(parts[1])
case strings.HasPrefix(line, "flags"):
flags = strings.TrimSpace(parts[1])
}
}
// Combine CPU information for unique identifier
return fmt.Sprintf("%s:%s:%s:%s", processor, vendorID, modelName, flags)
}
// linuxSystemUUID retrieves system UUID from DMI.
func linuxSystemUUID(logger *slog.Logger) (string, error) {
// Try multiple locations for system UUID
locations := []string{
"/sys/class/dmi/id/product_uuid",
"/sys/devices/virtual/dmi/id/product_uuid",
}
return readFirstValidFromLocations(locations, isValidUUID, logger)
}
// linuxMotherboardSerial retrieves motherboard serial number from DMI.
func linuxMotherboardSerial(logger *slog.Logger) (string, error) {
locations := []string{
"/sys/class/dmi/id/board_serial",
"/sys/devices/virtual/dmi/id/board_serial",
}
return readFirstValidFromLocations(locations, isValidSerial, logger)
}
// linuxMachineID retrieves systemd machine ID.
func linuxMachineID(logger *slog.Logger) (string, error) {
locations := []string{
"/etc/machine-id",
"/var/lib/dbus/machine-id",
}
return readFirstValidFromLocations(locations, isNonEmpty, logger)
}
// readFirstValidFromLocations reads from multiple locations until a valid value is found.
func readFirstValidFromLocations(locations []string, validator func(string) bool, logger *slog.Logger) (string, error) {
for _, location := range locations {
data, err := os.ReadFile(location)
if err == nil {
value := strings.TrimSpace(string(data))
if validator(value) {
if logger != nil {
logger.Debug("read value from file", "path", location)
}
return value, nil
}
if logger != nil {
logger.Debug("file value failed validation", "path", location)
}
} else if logger != nil {
logger.Debug("failed to read file", "path", location, "error", err)
}
}
return "", ErrNotFound
}
// isValidUUID reports whether the UUID is valid (not empty or null).
func isValidUUID(uuid string) bool {
return uuid != "" && uuid != "00000000-0000-0000-0000-000000000000"
}
// isValidSerial reports whether the serial is valid (not empty or placeholder).
func isValidSerial(serial string) bool {
return serial != "" && serial != biosFirmwareMessage
}
// isNonEmpty reports whether the value is not empty.
func isNonEmpty(value string) bool {
return value != ""
}
// linuxDiskSerials retrieves disk serial numbers using various methods.
// Results are deduplicated across sources to prevent the same serial
// from appearing multiple times.
func linuxDiskSerials(ctx context.Context, executor CommandExecutor, logger *slog.Logger) ([]string, error) {
seen := make(map[string]struct{})
var serials []string
// Try using lsblk command first
if lsblkSerials, err := linuxDiskSerialsLSBLK(ctx, executor, logger); err == nil {
for _, s := range lsblkSerials {
if _, exists := seen[s]; !exists {
seen[s] = struct{}{}
serials = append(serials, s)
}
}
if logger != nil {
logger.Debug("collected disk serials via lsblk", "count", len(lsblkSerials))
}
} else if logger != nil {
logger.Debug("lsblk failed, trying /sys/block", "error", err)
}
// Try reading from /sys/block
if sysSerials, err := linuxDiskSerialsSys(logger); err == nil {
for _, s := range sysSerials {
if _, exists := seen[s]; !exists {
seen[s] = struct{}{}
serials = append(serials, s)
}
}
if logger != nil {
logger.Debug("collected disk serials via /sys/block", "count", len(sysSerials))
}
} else if logger != nil {
logger.Debug("/sys/block read failed", "error", err)
}
return serials, nil
}
// linuxDiskSerialsLSBLK retrieves disk serials using lsblk command.
func linuxDiskSerialsLSBLK(ctx context.Context, executor CommandExecutor, logger *slog.Logger) ([]string, error) {
output, err := executeCommand(ctx, executor, logger, "lsblk", "-d", "-n", "-o", "SERIAL")
if err != nil {
return nil, err
}
var serials []string
lines := strings.SplitSeq(output, "\n")
for line := range lines {
serial := strings.TrimSpace(line)
if serial != "" {
serials = append(serials, serial)
}
}
return serials, nil
}
// linuxDiskSerialsSys retrieves disk serials from /sys/block.
func linuxDiskSerialsSys(logger *slog.Logger) ([]string, error) {
var serials []string
blockDir := "/sys/block"
entries, err := os.ReadDir(blockDir)
if err != nil {
return nil, err
}
for _, entry := range entries {
if entry.IsDir() && !strings.HasPrefix(entry.Name(), "loop") {
serialFile := filepath.Join(blockDir, entry.Name(), "device", "serial")
if data, err := os.ReadFile(serialFile); err == nil {
serial := strings.TrimSpace(string(data))
if serial != "" {
serials = append(serials, serial)
if logger != nil {
logger.Debug("read disk serial from sysfs", "disk", entry.Name(), "path", serialFile)
}
}
}
}
}
return serials, nil
}