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