/src/fwupd/libfwupdplugin/fu-smbios.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2017 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 0 | #define G_LOG_DOMAIN "FuSmbios" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include <gio/gio.h> |
12 | | #include <string.h> |
13 | | |
14 | | #ifdef _WIN32 |
15 | | #include <errhandlingapi.h> |
16 | | #include <sysinfoapi.h> |
17 | | #endif |
18 | | |
19 | | #include "fwupd-error.h" |
20 | | |
21 | | #include "fu-byte-array.h" |
22 | | #include "fu-common.h" |
23 | | #include "fu-input-stream.h" |
24 | | #include "fu-path.h" |
25 | | #include "fu-smbios-private.h" |
26 | | #include "fu-smbios-struct.h" |
27 | | #include "fu-string.h" |
28 | | |
29 | | /** |
30 | | * FuSmbios: |
31 | | * |
32 | | * Enumerate the SMBIOS data on the system. |
33 | | * |
34 | | * See also: [class@FuHwids] |
35 | | */ |
36 | | |
37 | | struct _FuSmbios { |
38 | | FuFirmware parent_instance; |
39 | | FuPathStore *pstore; |
40 | | guint32 structure_table_len; |
41 | | GPtrArray *items; |
42 | | }; |
43 | | |
44 | | typedef struct { |
45 | | guint8 type; |
46 | | guint16 handle; |
47 | | GByteArray *buf; |
48 | | GPtrArray *strings; |
49 | | } FuSmbiosItem; |
50 | | |
51 | 0 | G_DEFINE_TYPE(FuSmbios, fu_smbios, FU_TYPE_FIRMWARE) |
52 | 0 |
|
53 | 0 | static FuSmbiosItem * |
54 | 0 | fu_smbios_item_new(void) |
55 | 0 | { |
56 | 0 | FuSmbiosItem *item = g_new0(FuSmbiosItem, 1); |
57 | 0 | item->strings = g_ptr_array_new_with_free_func(g_free); |
58 | 0 | return item; |
59 | 0 | } |
60 | | |
61 | | static void |
62 | | fu_smbios_item_free(FuSmbiosItem *item) |
63 | 0 | { |
64 | 0 | if (item->buf != NULL) |
65 | 0 | g_byte_array_unref(item->buf); |
66 | 0 | g_ptr_array_unref(item->strings); |
67 | 0 | g_free(item); |
68 | 0 | } |
69 | | |
70 | | G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuSmbiosItem, fu_smbios_item_free) |
71 | | |
72 | | static FuSmbiosItem * |
73 | | fu_smbios_get_item_for_type_length(FuSmbios *self, guint8 type, guint8 length) |
74 | 0 | { |
75 | 0 | for (guint i = 0; i < self->items->len; i++) { |
76 | 0 | FuSmbiosItem *item = g_ptr_array_index(self->items, i); |
77 | 0 | if (item->type != type) |
78 | 0 | continue; |
79 | 0 | if (length != FU_SMBIOS_STRUCTURE_LENGTH_ANY && length != item->buf->len) { |
80 | 0 | g_debug("filtering SMBIOS structure by length: 0x%x != 0x%x", |
81 | 0 | length, |
82 | 0 | item->buf->len); |
83 | 0 | continue; |
84 | 0 | } |
85 | 0 | return item; |
86 | 0 | } |
87 | 0 | return NULL; |
88 | 0 | } |
89 | | |
90 | | static gboolean |
91 | | fu_smbios_setup_from_data(FuSmbios *self, const guint8 *buf, gsize bufsz, GError **error) |
92 | 0 | { |
93 | | /* go through each structure */ |
94 | 0 | for (gsize i = 0; i < bufsz; i++) { |
95 | 0 | FuSmbiosItem *item; |
96 | 0 | guint8 length; |
97 | 0 | g_autoptr(FuStructSmbiosStructure) st_str = NULL; |
98 | | |
99 | | /* sanity check */ |
100 | 0 | st_str = fu_struct_smbios_structure_parse(buf, bufsz, i, error); |
101 | 0 | if (st_str == NULL) |
102 | 0 | return FALSE; |
103 | 0 | length = fu_struct_smbios_structure_get_length(st_str); |
104 | 0 | if (length < st_str->buf->len) { |
105 | 0 | g_set_error(error, |
106 | 0 | FWUPD_ERROR, |
107 | 0 | FWUPD_ERROR_INVALID_FILE, |
108 | 0 | "structure smaller than allowed @0x%x", |
109 | 0 | (guint)i); |
110 | 0 | return FALSE; |
111 | 0 | } |
112 | 0 | if (i + length >= bufsz) { |
113 | 0 | g_set_error(error, |
114 | 0 | FWUPD_ERROR, |
115 | 0 | FWUPD_ERROR_INVALID_FILE, |
116 | 0 | "structure larger than available data @0x%x", |
117 | 0 | (guint)i); |
118 | 0 | return FALSE; |
119 | 0 | } |
120 | | |
121 | | /* create a new result */ |
122 | 0 | item = fu_smbios_item_new(); |
123 | 0 | item->type = fu_struct_smbios_structure_get_type(st_str); |
124 | 0 | item->handle = fu_struct_smbios_structure_get_handle(st_str); |
125 | 0 | item->buf = g_byte_array_sized_new(length); |
126 | 0 | g_byte_array_append(item->buf, buf + i, length); |
127 | 0 | g_ptr_array_add(self->items, item); |
128 | | |
129 | | /* jump to the end of the formatted area of the struct */ |
130 | 0 | i += length; |
131 | | |
132 | | /* add strings from table */ |
133 | 0 | while (i < bufsz) { |
134 | 0 | GString *str; |
135 | | |
136 | | /* end of string section */ |
137 | 0 | if (item->strings->len > 0 && buf[i] == 0x0) |
138 | 0 | break; |
139 | | |
140 | | /* copy into string table */ |
141 | 0 | str = fu_strdup((const gchar *)buf, bufsz, i); |
142 | 0 | i += str->len + 1; |
143 | 0 | g_ptr_array_add(item->strings, g_string_free(str, FALSE)); |
144 | 0 | } |
145 | 0 | } |
146 | | |
147 | | /* this has to exist */ |
148 | 0 | if (fu_smbios_get_item_for_type_length(self, |
149 | 0 | FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, |
150 | 0 | FU_SMBIOS_STRUCTURE_LENGTH_ANY) == NULL) { |
151 | 0 | g_set_error_literal(error, |
152 | 0 | FWUPD_ERROR, |
153 | 0 | FWUPD_ERROR_INVALID_FILE, |
154 | 0 | "no structure with required type SYSTEM"); |
155 | 0 | return FALSE; |
156 | 0 | } |
157 | | |
158 | | /* success */ |
159 | 0 | return TRUE; |
160 | 0 | } |
161 | | |
162 | | /** |
163 | | * fu_smbios_setup_from_file: |
164 | | * @self: a #FuSmbios |
165 | | * @filename: a filename |
166 | | * @error: (nullable): optional return location for an error |
167 | | * |
168 | | * Reads all the SMBIOS values from a DMI blob. |
169 | | * |
170 | | * Returns: %TRUE for success |
171 | | * |
172 | | * Since: 1.0.0 |
173 | | **/ |
174 | | gboolean |
175 | | fu_smbios_setup_from_file(FuSmbios *self, const gchar *filename, GError **error) |
176 | 0 | { |
177 | 0 | gsize sz = 0; |
178 | 0 | g_autofree gchar *buf = NULL; |
179 | |
|
180 | 0 | g_return_val_if_fail(FU_IS_SMBIOS(self), FALSE); |
181 | 0 | g_return_val_if_fail(filename != NULL, FALSE); |
182 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
183 | | |
184 | | /* DMI blob */ |
185 | 0 | if (!g_file_get_contents(filename, &buf, &sz, error)) |
186 | 0 | return FALSE; |
187 | 0 | return fu_smbios_setup_from_data(self, (guint8 *)buf, sz, error); |
188 | 0 | } |
189 | | |
190 | | static gboolean |
191 | | fu_smbios_parse_ep32(FuSmbios *self, const guint8 *buf, gsize bufsz, GError **error) |
192 | 0 | { |
193 | 0 | guint8 csum = 0; |
194 | 0 | g_autofree gchar *version_str = NULL; |
195 | 0 | g_autofree gchar *intermediate_anchor_str = NULL; |
196 | 0 | g_autoptr(FuStructSmbiosEp32) st_ep32 = NULL; |
197 | | |
198 | | /* verify checksum */ |
199 | 0 | st_ep32 = fu_struct_smbios_ep32_parse(buf, bufsz, 0x0, error); |
200 | 0 | if (st_ep32 == NULL) |
201 | 0 | return FALSE; |
202 | 0 | for (guint i = 0; i < bufsz; i++) |
203 | 0 | csum += buf[i]; |
204 | 0 | if (csum != 0x00) { |
205 | 0 | g_set_error_literal(error, |
206 | 0 | FWUPD_ERROR, |
207 | 0 | FWUPD_ERROR_INVALID_FILE, |
208 | 0 | "entry point checksum invalid"); |
209 | 0 | return FALSE; |
210 | 0 | } |
211 | | |
212 | | /* verify intermediate section */ |
213 | 0 | intermediate_anchor_str = fu_struct_smbios_ep32_get_intermediate_anchor_str(st_ep32); |
214 | 0 | if (g_strcmp0(intermediate_anchor_str, "_DMI_") != 0) { |
215 | 0 | g_set_error(error, |
216 | 0 | FWUPD_ERROR, |
217 | 0 | FWUPD_ERROR_INVALID_FILE, |
218 | 0 | "intermediate anchor signature invalid, got %s", |
219 | 0 | intermediate_anchor_str); |
220 | 0 | return FALSE; |
221 | 0 | } |
222 | 0 | for (guint i = 10; i < bufsz; i++) |
223 | 0 | csum += buf[i]; |
224 | 0 | if (csum != 0x00) { |
225 | 0 | g_set_error_literal(error, |
226 | 0 | FWUPD_ERROR, |
227 | 0 | FWUPD_ERROR_INVALID_FILE, |
228 | 0 | "intermediate checksum invalid"); |
229 | 0 | return FALSE; |
230 | 0 | } |
231 | 0 | self->structure_table_len = fu_struct_smbios_ep32_get_structure_table_len(st_ep32); |
232 | 0 | version_str = g_strdup_printf("%u.%u", |
233 | 0 | fu_struct_smbios_ep32_get_smbios_major_ver(st_ep32), |
234 | 0 | fu_struct_smbios_ep32_get_smbios_minor_ver(st_ep32)); |
235 | 0 | fu_firmware_set_version(FU_FIRMWARE(self), version_str); /* nocheck:set-version */ |
236 | 0 | fu_firmware_set_version_raw( |
237 | 0 | FU_FIRMWARE(self), |
238 | 0 | (((guint16)fu_struct_smbios_ep32_get_smbios_major_ver(st_ep32)) << 8) + |
239 | 0 | fu_struct_smbios_ep32_get_smbios_minor_ver(st_ep32)); |
240 | 0 | return TRUE; |
241 | 0 | } |
242 | | |
243 | | static gboolean |
244 | | fu_smbios_parse_ep64(FuSmbios *self, const guint8 *buf, gsize bufsz, GError **error) |
245 | 0 | { |
246 | 0 | guint8 csum = 0; |
247 | 0 | g_autofree gchar *version_str = NULL; |
248 | 0 | g_autoptr(FuStructSmbiosEp64) st_ep64 = NULL; |
249 | | |
250 | | /* verify checksum */ |
251 | 0 | st_ep64 = fu_struct_smbios_ep64_parse(buf, bufsz, 0x0, error); |
252 | 0 | if (st_ep64 == NULL) |
253 | 0 | return FALSE; |
254 | 0 | for (guint i = 0; i < bufsz; i++) |
255 | 0 | csum += buf[i]; |
256 | 0 | if (csum != 0x00) { |
257 | 0 | g_set_error_literal(error, |
258 | 0 | FWUPD_ERROR, |
259 | 0 | FWUPD_ERROR_INVALID_FILE, |
260 | 0 | "entry point checksum invalid"); |
261 | 0 | return FALSE; |
262 | 0 | } |
263 | 0 | self->structure_table_len = fu_struct_smbios_ep64_get_structure_table_len(st_ep64); |
264 | 0 | version_str = g_strdup_printf("%u.%u", |
265 | 0 | fu_struct_smbios_ep64_get_smbios_major_ver(st_ep64), |
266 | 0 | fu_struct_smbios_ep64_get_smbios_minor_ver(st_ep64)); |
267 | 0 | fu_firmware_set_version(FU_FIRMWARE(self), version_str); /* nocheck:set-version */ |
268 | 0 | return TRUE; |
269 | 0 | } |
270 | | |
271 | | /** |
272 | | * fu_smbios_setup_from_path: |
273 | | * @self: a #FuSmbios |
274 | | * @path: a path, e.g. `/sys/firmware/dmi/tables` |
275 | | * @error: (nullable): optional return location for an error |
276 | | * |
277 | | * Reads all the SMBIOS values from a specific path. |
278 | | * |
279 | | * Returns: %TRUE for success |
280 | | * |
281 | | * Since: 1.0.0 |
282 | | **/ |
283 | | gboolean |
284 | | fu_smbios_setup_from_path(FuSmbios *self, const gchar *path, GError **error) |
285 | 0 | { |
286 | 0 | gsize sz = 0; |
287 | 0 | g_autofree gchar *dmi_fn = NULL; |
288 | 0 | g_autofree gchar *dmi_raw = NULL; |
289 | 0 | g_autofree gchar *ep_fn = NULL; |
290 | 0 | g_autofree gchar *ep_raw = NULL; |
291 | |
|
292 | 0 | g_return_val_if_fail(FU_IS_SMBIOS(self), FALSE); |
293 | 0 | g_return_val_if_fail(path != NULL, FALSE); |
294 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
295 | | |
296 | | /* get the smbios entry point */ |
297 | 0 | ep_fn = g_build_filename(path, "smbios_entry_point", NULL); |
298 | 0 | if (!g_file_get_contents(ep_fn, &ep_raw, &sz, error)) { |
299 | 0 | fwupd_error_convert(error); |
300 | 0 | return FALSE; |
301 | 0 | } |
302 | | |
303 | | /* check we got enough data to read the signature */ |
304 | 0 | if (sz < 5) { |
305 | 0 | g_set_error(error, |
306 | 0 | FWUPD_ERROR, |
307 | 0 | FWUPD_ERROR_INVALID_FILE, |
308 | 0 | "invalid smbios entry point got 0x%x bytes, expected 0x%x or 0x%x", |
309 | 0 | (guint)sz, |
310 | 0 | (guint)FU_STRUCT_SMBIOS_EP32_SIZE, |
311 | 0 | (guint)FU_STRUCT_SMBIOS_EP64_SIZE); |
312 | 0 | return FALSE; |
313 | 0 | } |
314 | | |
315 | | /* parse 32 bit structure */ |
316 | 0 | if (memcmp(ep_raw, "_SM_", 4) == 0) { |
317 | 0 | if (!fu_smbios_parse_ep32(self, (const guint8 *)ep_raw, sz, error)) |
318 | 0 | return FALSE; |
319 | 0 | } else if (memcmp(ep_raw, "_SM3_", 5) == 0) { |
320 | 0 | if (!fu_smbios_parse_ep64(self, (const guint8 *)ep_raw, sz, error)) |
321 | 0 | return FALSE; |
322 | 0 | } else { |
323 | 0 | g_autofree gchar *tmp = g_strndup(ep_raw, 4); |
324 | 0 | g_set_error(error, |
325 | 0 | FWUPD_ERROR, |
326 | 0 | FWUPD_ERROR_INVALID_FILE, |
327 | 0 | "SMBIOS signature invalid, got %s", |
328 | 0 | tmp); |
329 | 0 | return FALSE; |
330 | 0 | } |
331 | | |
332 | | /* get the DMI data */ |
333 | 0 | dmi_fn = g_build_filename(path, "DMI", NULL); |
334 | 0 | if (!g_file_get_contents(dmi_fn, &dmi_raw, &sz, error)) { |
335 | 0 | fwupd_error_convert(error); |
336 | 0 | return FALSE; |
337 | 0 | } |
338 | 0 | if (sz > self->structure_table_len) { |
339 | 0 | g_set_error(error, |
340 | 0 | FWUPD_ERROR, |
341 | 0 | FWUPD_ERROR_INVALID_FILE, |
342 | 0 | "invalid DMI data size, got 0x%x bytes, expected 0x%x", |
343 | 0 | (guint)sz, |
344 | 0 | self->structure_table_len); |
345 | 0 | return FALSE; |
346 | 0 | } |
347 | | |
348 | | /* parse blob */ |
349 | 0 | return fu_smbios_setup_from_data(self, (guint8 *)dmi_raw, sz, error); |
350 | 0 | } |
351 | | |
352 | | static gboolean |
353 | | fu_smbios_parse(FuFirmware *firmware, |
354 | | GInputStream *stream, |
355 | | FuFirmwareParseFlags flags, |
356 | | GError **error) |
357 | 0 | { |
358 | 0 | FuSmbios *self = FU_SMBIOS(firmware); |
359 | 0 | g_autoptr(GBytes) fw = NULL; |
360 | 0 | fw = fu_input_stream_read_bytes(stream, 0x0, G_MAXSIZE, NULL, error); |
361 | 0 | if (fw == NULL) |
362 | 0 | return FALSE; |
363 | 0 | return fu_smbios_setup_from_data(self, |
364 | 0 | g_bytes_get_data(fw, NULL), |
365 | 0 | g_bytes_get_size(fw), |
366 | 0 | error); |
367 | 0 | } |
368 | | |
369 | | #ifdef _WIN32 |
370 | | #define FU_SMBIOS_FT_SIG_ACPI 0x41435049 |
371 | | #define FU_SMBIOS_FT_SIG_FIRM 0x4649524D |
372 | | #define FU_SMBIOS_FT_SIG_RSMB 0x52534D42 |
373 | | #define FU_SMBIOS_FT_RAW_OFFSET 0x08 |
374 | | #endif |
375 | | |
376 | | /** |
377 | | * fu_smbios_setup: |
378 | | * @self: a #FuSmbios |
379 | | * @error: (nullable): optional return location for an error |
380 | | * |
381 | | * Reads all the SMBIOS values from the hardware. |
382 | | * |
383 | | * Returns: %TRUE for success |
384 | | * |
385 | | * Since: 1.0.0 |
386 | | **/ |
387 | | gboolean |
388 | | fu_smbios_setup(FuSmbios *self, GError **error) |
389 | 0 | { |
390 | | #ifdef _WIN32 |
391 | | gsize bufsz; |
392 | | guint rc; |
393 | | g_autofree guint8 *buf = NULL; |
394 | | |
395 | | rc = GetSystemFirmwareTable(FU_SMBIOS_FT_SIG_RSMB, 0, 0, 0); |
396 | | if (rc <= 0) { |
397 | | g_set_error(error, |
398 | | FWUPD_ERROR, |
399 | | FWUPD_ERROR_INVALID_FILE, |
400 | | "failed to access RSMB [%u]", |
401 | | (guint)GetLastError()); |
402 | | return FALSE; |
403 | | } |
404 | | if (rc < FU_SMBIOS_FT_RAW_OFFSET || rc > 0x1000000) { |
405 | | g_set_error_literal(error, |
406 | | FWUPD_ERROR, |
407 | | FWUPD_ERROR_INVALID_FILE, |
408 | | "RSMB impossible size"); |
409 | | return FALSE; |
410 | | } |
411 | | bufsz = rc; |
412 | | buf = g_malloc0(bufsz); |
413 | | rc = GetSystemFirmwareTable(FU_SMBIOS_FT_SIG_RSMB, 0, buf, (DWORD)bufsz); |
414 | | if (rc <= 0) { |
415 | | g_set_error(error, |
416 | | FWUPD_ERROR, |
417 | | FWUPD_ERROR_INVALID_FILE, |
418 | | "failed to read RSMB [%u]", |
419 | | (guint)GetLastError()); |
420 | | return FALSE; |
421 | | } |
422 | | return fu_smbios_setup_from_data(self, |
423 | | buf + FU_SMBIOS_FT_RAW_OFFSET, |
424 | | bufsz - FU_SMBIOS_FT_RAW_OFFSET, |
425 | | error); |
426 | | #else |
427 | 0 | g_autofree gchar *path = NULL; |
428 | 0 | g_autoptr(GError) error_local = NULL; |
429 | |
|
430 | 0 | g_return_val_if_fail(FU_IS_SMBIOS(self), FALSE); |
431 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
432 | | |
433 | | /* DMI */ |
434 | 0 | path = fu_path_store_build_filename(self->pstore, |
435 | 0 | error, |
436 | 0 | FU_PATH_KIND_SYSFSDIR_FW, |
437 | 0 | "dmi", |
438 | 0 | "tables", |
439 | 0 | NULL); |
440 | 0 | if (path == NULL) |
441 | 0 | return FALSE; |
442 | 0 | if (!g_file_test(path, G_FILE_TEST_EXISTS)) { |
443 | 0 | g_set_error(error, |
444 | 0 | FWUPD_ERROR, |
445 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
446 | 0 | "SMBIOS tables not found at %s", |
447 | 0 | path); |
448 | 0 | return FALSE; |
449 | 0 | } |
450 | 0 | if (!fu_smbios_setup_from_path(self, path, &error_local)) { |
451 | 0 | if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) { |
452 | 0 | g_propagate_error(error, g_steal_pointer(&error_local)); |
453 | 0 | return FALSE; |
454 | 0 | } |
455 | 0 | g_debug("ignoring %s", error_local->message); |
456 | 0 | } |
457 | | |
458 | | /* success */ |
459 | 0 | return TRUE; |
460 | 0 | #endif |
461 | 0 | } |
462 | | |
463 | | static void |
464 | | fu_smbios_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
465 | 0 | { |
466 | 0 | FuSmbios *self = FU_SMBIOS(firmware); |
467 | |
|
468 | 0 | for (guint i = 0; i < self->items->len; i++) { |
469 | 0 | FuSmbiosItem *item = g_ptr_array_index(self->items, i); |
470 | 0 | g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "item", NULL); |
471 | 0 | g_autofree gchar *buf = fu_byte_array_to_string(item->buf); |
472 | 0 | fu_xmlb_builder_insert_kx(bc, "type", item->type); |
473 | 0 | fu_xmlb_builder_insert_kx(bc, "length", item->buf->len); |
474 | 0 | fu_xmlb_builder_insert_kx(bc, "handle", item->handle); |
475 | 0 | fu_xmlb_builder_insert_kv(bc, "buf", buf); |
476 | 0 | for (guint j = 0; j < item->strings->len; j++) { |
477 | 0 | const gchar *tmp = g_ptr_array_index(item->strings, j); |
478 | 0 | g_autofree gchar *title = g_strdup_printf("%02u", j); |
479 | 0 | g_autofree gchar *value = fu_strsafe(tmp, 40); |
480 | 0 | xb_builder_node_insert_text(bc, "string", value, "idx", title, NULL); |
481 | 0 | } |
482 | 0 | } |
483 | 0 | } |
484 | | |
485 | | static gboolean |
486 | | fu_smbios_build_item(FuSmbios *self, XbNode *n, GError **error) |
487 | 0 | { |
488 | 0 | const gchar *str; |
489 | 0 | guint64 tmp; |
490 | 0 | g_autoptr(FuSmbiosItem) item = fu_smbios_item_new(); |
491 | 0 | g_autoptr(GPtrArray) xb_strings = NULL; |
492 | | |
493 | | /* optional type */ |
494 | 0 | tmp = xb_node_query_text_as_uint(n, "type", NULL); |
495 | 0 | if (tmp != G_MAXUINT64) { |
496 | 0 | if (tmp > G_MAXUINT8) { |
497 | 0 | g_set_error_literal(error, |
498 | 0 | FWUPD_ERROR, |
499 | 0 | FWUPD_ERROR_INVALID_DATA, |
500 | 0 | "invalid item type"); |
501 | 0 | return FALSE; |
502 | 0 | } |
503 | 0 | item->type = tmp; |
504 | 0 | } |
505 | | |
506 | | /* optional handle */ |
507 | 0 | tmp = xb_node_query_text_as_uint(n, "handle", NULL); |
508 | 0 | if (tmp != G_MAXUINT64) { |
509 | 0 | if (tmp > G_MAXUINT16) { |
510 | 0 | g_set_error_literal(error, |
511 | 0 | FWUPD_ERROR, |
512 | 0 | FWUPD_ERROR_INVALID_DATA, |
513 | 0 | "invalid item handle"); |
514 | 0 | return FALSE; |
515 | 0 | } |
516 | 0 | item->handle = tmp; |
517 | 0 | } |
518 | | |
519 | | /* optional data buffer */ |
520 | 0 | str = xb_node_query_text(n, "buf", NULL); |
521 | 0 | if (str != NULL) { |
522 | 0 | item->buf = fu_byte_array_from_string(str, error); |
523 | 0 | if (item->buf == NULL) |
524 | 0 | return FALSE; |
525 | 0 | } |
526 | | |
527 | | /* optional string table */ |
528 | 0 | xb_strings = xb_node_query(n, "string", 0, NULL); |
529 | 0 | if (xb_strings != NULL) { |
530 | 0 | for (guint i = 0; i < xb_strings->len; i++) { |
531 | 0 | XbNode *c = g_ptr_array_index(xb_strings, i); |
532 | 0 | g_ptr_array_add(item->strings, g_strdup(xb_node_get_text(c))); |
533 | 0 | } |
534 | 0 | } |
535 | | |
536 | | /* success */ |
537 | 0 | g_ptr_array_add(self->items, g_steal_pointer(&item)); |
538 | 0 | return TRUE; |
539 | 0 | } |
540 | | |
541 | | static gboolean |
542 | | fu_smbios_build(FuFirmware *firmware, XbNode *n, GError **error) |
543 | 0 | { |
544 | 0 | FuSmbios *self = FU_SMBIOS(firmware); |
545 | 0 | g_autoptr(GPtrArray) xb_items = NULL; |
546 | | |
547 | | /* optional items */ |
548 | 0 | xb_items = xb_node_query(n, "item", 0, NULL); |
549 | 0 | if (xb_items != NULL) { |
550 | 0 | for (guint i = 0; i < xb_items->len; i++) { |
551 | 0 | XbNode *c = g_ptr_array_index(xb_items, i); |
552 | 0 | if (!fu_smbios_build_item(self, c, error)) |
553 | 0 | return FALSE; |
554 | 0 | } |
555 | 0 | } |
556 | | |
557 | | /* success */ |
558 | 0 | return TRUE; |
559 | 0 | } |
560 | | |
561 | | /** |
562 | | * fu_smbios_get_data: |
563 | | * @self: a #FuSmbios |
564 | | * @type: a structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS |
565 | | * @length: expected length of the structure, or %FU_SMBIOS_STRUCTURE_LENGTH_ANY |
566 | | * @error: (nullable): optional return location for an error |
567 | | * |
568 | | * Reads all the SMBIOS data blobs of a specified type. |
569 | | * |
570 | | * Returns: (transfer container) (element-type GBytes): a #GBytes, or %NULL if invalid or not found |
571 | | * |
572 | | * Since: 2.0.7 |
573 | | **/ |
574 | | GPtrArray * |
575 | | fu_smbios_get_data(FuSmbios *self, guint8 type, guint8 length, GError **error) |
576 | 0 | { |
577 | 0 | g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); |
578 | |
|
579 | 0 | g_return_val_if_fail(FU_IS_SMBIOS(self), NULL); |
580 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
581 | | |
582 | 0 | for (guint i = 0; i < self->items->len; i++) { |
583 | 0 | FuSmbiosItem *item = g_ptr_array_index(self->items, i); |
584 | 0 | if (item->type != type) |
585 | 0 | continue; |
586 | 0 | if (length != FU_SMBIOS_STRUCTURE_LENGTH_ANY && length != item->buf->len) |
587 | 0 | continue; |
588 | 0 | if (item->buf->len == 0) |
589 | 0 | continue; |
590 | 0 | g_ptr_array_add(array, g_bytes_new(item->buf->data, item->buf->len)); |
591 | 0 | } |
592 | 0 | if (array->len == 0) { |
593 | 0 | g_set_error(error, |
594 | 0 | FWUPD_ERROR, |
595 | 0 | FWUPD_ERROR_INVALID_FILE, |
596 | 0 | "no structures with type %02x", |
597 | 0 | type); |
598 | 0 | return NULL; |
599 | 0 | } |
600 | 0 | return g_steal_pointer(&array); |
601 | 0 | } |
602 | | |
603 | | /** |
604 | | * fu_smbios_get_integer: |
605 | | * @self: a #FuSmbios |
606 | | * @type: a structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS |
607 | | * @length: expected length of the structure, or %FU_SMBIOS_STRUCTURE_LENGTH_ANY |
608 | | * @offset: a structure offset |
609 | | * @error: (nullable): optional return location for an error |
610 | | * |
611 | | * Reads an integer value from the SMBIOS string table of a specific structure. |
612 | | * |
613 | | * The @type and @offset can be referenced from the DMTF SMBIOS specification: |
614 | | * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf |
615 | | * |
616 | | * Returns: an integer, or %G_MAXUINT if invalid or not found |
617 | | * |
618 | | * Since: 2.0.7 |
619 | | **/ |
620 | | guint |
621 | | fu_smbios_get_integer(FuSmbios *self, guint8 type, guint8 length, guint8 offset, GError **error) |
622 | 0 | { |
623 | 0 | FuSmbiosItem *item; |
624 | |
|
625 | 0 | g_return_val_if_fail(FU_IS_SMBIOS(self), 0); |
626 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, 0); |
627 | | |
628 | | /* get item */ |
629 | 0 | item = fu_smbios_get_item_for_type_length(self, type, length); |
630 | 0 | if (item == NULL) { |
631 | 0 | g_set_error(error, |
632 | 0 | FWUPD_ERROR, |
633 | 0 | FWUPD_ERROR_INVALID_FILE, |
634 | 0 | "no structure with type %02x", |
635 | 0 | type); |
636 | 0 | return G_MAXUINT; |
637 | 0 | } |
638 | | |
639 | | /* check offset valid */ |
640 | 0 | if (offset >= item->buf->len) { |
641 | 0 | g_set_error(error, |
642 | 0 | FWUPD_ERROR, |
643 | 0 | FWUPD_ERROR_INVALID_FILE, |
644 | 0 | "offset bigger than size %u", |
645 | 0 | item->buf->len); |
646 | 0 | return G_MAXUINT; |
647 | 0 | } |
648 | | |
649 | | /* success */ |
650 | 0 | return item->buf->data[offset]; |
651 | 0 | } |
652 | | |
653 | | /** |
654 | | * fu_smbios_get_string: |
655 | | * @self: a #FuSmbios |
656 | | * @type: a structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS |
657 | | * @length: expected length of the structure, or %FU_SMBIOS_STRUCTURE_LENGTH_ANY |
658 | | * @offset: a structure offset |
659 | | * @error: (nullable): optional return location for an error |
660 | | * |
661 | | * Reads a string from the SMBIOS string table of a specific structure. |
662 | | * |
663 | | * The @type and @offset can be referenced from the DMTF SMBIOS specification: |
664 | | * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf |
665 | | * |
666 | | * Returns: a string, or %NULL if invalid or not found |
667 | | * |
668 | | * Since: 2.0.7 |
669 | | **/ |
670 | | const gchar * |
671 | | fu_smbios_get_string(FuSmbios *self, guint8 type, guint8 length, guint8 offset, GError **error) |
672 | 0 | { |
673 | 0 | FuSmbiosItem *item; |
674 | |
|
675 | 0 | g_return_val_if_fail(FU_IS_SMBIOS(self), NULL); |
676 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
677 | | |
678 | | /* get item */ |
679 | 0 | item = fu_smbios_get_item_for_type_length(self, type, length); |
680 | 0 | if (item == NULL) { |
681 | 0 | g_set_error(error, |
682 | 0 | FWUPD_ERROR, |
683 | 0 | FWUPD_ERROR_INVALID_FILE, |
684 | 0 | "no structure with type %02x", |
685 | 0 | type); |
686 | 0 | return NULL; |
687 | 0 | } |
688 | | |
689 | | /* check offset valid */ |
690 | 0 | if (offset >= item->buf->len) { |
691 | 0 | g_set_error(error, |
692 | 0 | FWUPD_ERROR, |
693 | 0 | FWUPD_ERROR_INVALID_FILE, |
694 | 0 | "offset bigger than size %u", |
695 | 0 | item->buf->len); |
696 | 0 | return NULL; |
697 | 0 | } |
698 | 0 | if (item->buf->data[offset] == 0x00) { |
699 | 0 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no data available"); |
700 | 0 | return NULL; |
701 | 0 | } |
702 | | |
703 | | /* check string index valid */ |
704 | 0 | if (item->buf->data[offset] > item->strings->len) { |
705 | 0 | g_set_error(error, |
706 | 0 | FWUPD_ERROR, |
707 | 0 | FWUPD_ERROR_INVALID_FILE, |
708 | 0 | "index larger than string table %u", |
709 | 0 | item->strings->len); |
710 | 0 | return NULL; |
711 | 0 | } |
712 | 0 | return g_ptr_array_index(item->strings, item->buf->data[offset] - 1); |
713 | 0 | } |
714 | | |
715 | | static void |
716 | | fu_smbios_finalize(GObject *object) |
717 | 0 | { |
718 | 0 | FuSmbios *self = FU_SMBIOS(object); |
719 | 0 | if (self->pstore != NULL) |
720 | 0 | g_object_unref(self->pstore); |
721 | 0 | g_ptr_array_unref(self->items); |
722 | 0 | G_OBJECT_CLASS(fu_smbios_parent_class)->finalize(object); |
723 | 0 | } |
724 | | |
725 | | static void |
726 | | fu_smbios_class_init(FuSmbiosClass *klass) |
727 | 0 | { |
728 | 0 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
729 | 0 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
730 | 0 | object_class->finalize = fu_smbios_finalize; |
731 | 0 | firmware_class->parse = fu_smbios_parse; |
732 | 0 | firmware_class->build = fu_smbios_build; |
733 | 0 | firmware_class->export = fu_smbios_export; |
734 | 0 | } |
735 | | |
736 | | static void |
737 | | fu_smbios_init(FuSmbios *self) |
738 | 0 | { |
739 | 0 | self->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_smbios_item_free); |
740 | 0 | } |
741 | | |
742 | | /** |
743 | | * fu_smbios_new: |
744 | | * |
745 | | * Creates a new object to parse SMBIOS data. |
746 | | * |
747 | | * Returns: a #FuSmbios |
748 | | * |
749 | | * Since: 1.0.0 |
750 | | **/ |
751 | | FuSmbios * |
752 | | fu_smbios_new(FuPathStore *pstore) |
753 | 0 | { |
754 | 0 | FuSmbios *self; |
755 | |
|
756 | 0 | g_return_val_if_fail(FU_IS_PATH_STORE(pstore), NULL); |
757 | | |
758 | 0 | self = g_object_new(FU_TYPE_SMBIOS, NULL); |
759 | 0 | self->pstore = g_object_ref(pstore); |
760 | 0 | return FU_SMBIOS(self); |
761 | 0 | } |