Coverage Report

Created: 2025-06-22 06:29

/src/fwupd/libfwupdplugin/fu-hwids.c
Line
Count
Source (jump to first uncovered line)
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 "FuHwids"
8
9
#include "config.h"
10
11
#include <gio/gio.h>
12
#include <string.h>
13
14
#include "fwupd-common.h"
15
#include "fwupd-error.h"
16
17
#include "fu-common.h"
18
#include "fu-hwids-private.h"
19
#include "fu-path.h"
20
#include "fu-string.h"
21
22
/**
23
 * FuHwids:
24
 *
25
 * A the hardware IDs on the system.
26
 *
27
 * Note, these are called "CHIDs" in Microsoft Windows and the results here
28
 * will match that of `ComputerHardwareIds.exe`.
29
 *
30
 * See also: [class@FuSmbios]
31
 */
32
33
struct _FuHwids {
34
  GObject parent_instance;
35
  GHashTable *hash_values;   /* BiosVersion->"1.2.3 " */
36
  GHashTable *hash_values_display; /* BiosVersion->"1.2.3" */
37
  GHashTable *hash_guid;     /* a-c-b-d->1 */
38
  GPtrArray *array_guids;    /* a-c-b-d */
39
  GHashTable *chids;     /* "HardwareID-5"->"Manufacturer&ProductName" */
40
};
41
42
G_DEFINE_TYPE(FuHwids, fu_hwids, G_TYPE_OBJECT)
43
44
/**
45
 * fu_hwids_get_value:
46
 * @self: a #FuHwids
47
 * @key: a DMI ID, e.g. `BiosVersion`
48
 *
49
 * Gets the cached value for one specific key that is valid ASCII and suitable
50
 * for display.
51
 *
52
 * Returns: the string, e.g. `1.2.3`, or %NULL if not found
53
 *
54
 * Since: 0.9.3
55
 **/
56
const gchar *
57
fu_hwids_get_value(FuHwids *self, const gchar *key)
58
0
{
59
0
  return g_hash_table_lookup(self->hash_values_display, key);
60
0
}
61
62
/**
63
 * fu_hwids_has_guid:
64
 * @self: a #FuHwids
65
 * @guid: a GUID, e.g. `059eb22d-6dc7-59af-abd3-94bbe017f67c`
66
 *
67
 * Finds out if a hardware GUID exists.
68
 *
69
 * Returns: %TRUE if the GUID exists
70
 *
71
 * Since: 0.9.3
72
 **/
73
gboolean
74
fu_hwids_has_guid(FuHwids *self, const gchar *guid)
75
0
{
76
0
  return g_hash_table_lookup(self->hash_guid, guid) != NULL;
77
0
}
78
79
/**
80
 * fu_hwids_get_guids:
81
 * @self: a #FuHwids
82
 *
83
 * Returns all the defined HWIDs
84
 *
85
 * Returns: (transfer none) (element-type utf8): an array of GUIDs
86
 *
87
 * Since: 0.9.3
88
 **/
89
GPtrArray *
90
fu_hwids_get_guids(FuHwids *self)
91
0
{
92
0
  return self->array_guids;
93
0
}
94
95
/**
96
 * fu_hwids_get_keys:
97
 * @self: a #FuHwids
98
 *
99
 * Returns all the defined HWID keys.
100
 *
101
 * Returns: (transfer container) (element-type utf8): All the known keys,
102
 * e.g. %FU_HWIDS_KEY_FAMILY
103
 *
104
 * Since: 1.5.6
105
 **/
106
GPtrArray *
107
fu_hwids_get_keys(FuHwids *self)
108
0
{
109
0
  GPtrArray *array = g_ptr_array_new();
110
0
  const gchar *keys[] = {FU_HWIDS_KEY_BIOS_VENDOR,
111
0
             FU_HWIDS_KEY_BIOS_VERSION,
112
0
             FU_HWIDS_KEY_BIOS_MAJOR_RELEASE,
113
0
             FU_HWIDS_KEY_BIOS_MINOR_RELEASE,
114
0
             FU_HWIDS_KEY_FIRMWARE_MAJOR_RELEASE,
115
0
             FU_HWIDS_KEY_FIRMWARE_MINOR_RELEASE,
116
0
             FU_HWIDS_KEY_MANUFACTURER,
117
0
             FU_HWIDS_KEY_FAMILY,
118
0
             FU_HWIDS_KEY_PRODUCT_NAME,
119
0
             FU_HWIDS_KEY_PRODUCT_SKU,
120
0
             FU_HWIDS_KEY_ENCLOSURE_KIND,
121
0
             FU_HWIDS_KEY_BASEBOARD_MANUFACTURER,
122
0
             FU_HWIDS_KEY_BASEBOARD_PRODUCT,
123
0
             NULL};
124
0
  g_return_val_if_fail(FU_IS_HWIDS(self), NULL);
125
0
  for (guint i = 0; keys[i] != NULL; i++)
126
0
    g_ptr_array_add(array, (gpointer)keys[i]);
127
0
  return array;
128
0
}
129
130
static gchar *
131
fu_hwids_get_guid_for_str(const gchar *str, GError **error)
132
0
{
133
0
  glong items_written = 0;
134
0
  g_autofree gunichar2 *data = NULL;
135
136
  /* convert to UTF-16 and convert to GUID using custom namespace */
137
0
  data = g_utf8_to_utf16(str, -1, NULL, &items_written, error);
138
0
  if (data == NULL)
139
0
    return NULL;
140
141
0
  if (items_written == 0) {
142
0
    g_set_error_literal(error,
143
0
            FWUPD_ERROR,
144
0
            FWUPD_ERROR_INVALID_FILE,
145
0
            "no GUIDs in data");
146
0
    return NULL;
147
0
  }
148
149
  /* ensure the data is in little endian format */
150
0
  for (glong i = 0; i < items_written; i++)
151
0
    data[i] = GUINT16_TO_LE(data[i]); /* nocheck:blocked */
152
153
  /* convert to a GUID */
154
0
  return fwupd_guid_hash_data((guint8 *)data,
155
0
            items_written * 2,
156
0
            FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT);
157
0
}
158
159
/**
160
 * fu_hwids_get_replace_keys:
161
 * @self: a #FuHwids
162
 * @key: a CHID key, e.g. `HardwareID-03`
163
 *
164
 * Gets the defined values for a well known value.
165
 *
166
 * Returns: the replacement value, e.g. `Manufacturer&ProductName`, or %NULL for error.
167
 *
168
 * Since: 0.9.3
169
 **/
170
const gchar *
171
fu_hwids_get_replace_keys(FuHwids *self, const gchar *key)
172
0
{
173
0
  const gchar *value;
174
175
0
  g_return_val_if_fail(FU_IS_HWIDS(self), NULL);
176
0
  g_return_val_if_fail(key != NULL, NULL);
177
178
0
  value = g_hash_table_lookup(self->chids, key);
179
0
  if (value != NULL)
180
0
    return value;
181
0
  return key;
182
0
}
183
184
/**
185
 * fu_hwids_add_chid:
186
 * @self: a #FuHwids
187
 * @key: an textual ID, e.g. `HardwareID-05`
188
 * @value: a composite hardware key, e.g. `Manufacturer&ProductName`
189
 *
190
 * Defines a "Computer Hardware ID" in terms of a set of SMBIOS values.
191
 *
192
 * Since: 1.9.16
193
 **/
194
void
195
fu_hwids_add_chid(FuHwids *self, const gchar *key, const gchar *value)
196
0
{
197
0
  g_return_if_fail(FU_IS_HWIDS(self));
198
0
  g_return_if_fail(key != NULL);
199
0
  g_return_if_fail(value != NULL);
200
0
  g_hash_table_insert(self->chids, g_strdup(key), g_strdup(value));
201
0
}
202
203
static gint
204
fu_hwids_sort_keys_cb(gconstpointer a, gconstpointer b)
205
0
{
206
0
  const gchar *key1 = *((gchar **)a);
207
0
  const gchar *key2 = *((gchar **)b);
208
0
  return g_strcmp0(key1, key2);
209
0
}
210
211
/**
212
 * fu_hwids_get_chid_keys:
213
 * @self: a #FuHwids
214
 *
215
 * Returns all the CHID keys added by fu_hwids_add_chid().
216
 *
217
 * Returns: (transfer container) (element-type utf8): IDs
218
 *
219
 * Since: 1.9.16
220
 **/
221
GPtrArray *
222
fu_hwids_get_chid_keys(FuHwids *self)
223
0
{
224
0
  GHashTableIter iter;
225
0
  gpointer key;
226
0
  g_autoptr(GPtrArray) keys = g_ptr_array_new_with_free_func(g_free);
227
228
0
  g_return_val_if_fail(FU_IS_HWIDS(self), NULL);
229
230
0
  g_hash_table_iter_init(&iter, self->chids);
231
0
  while (g_hash_table_iter_next(&iter, &key, NULL))
232
0
    g_ptr_array_add(keys, g_strdup(key));
233
0
  g_ptr_array_sort(keys, fu_hwids_sort_keys_cb);
234
235
0
  return g_steal_pointer(&keys);
236
0
}
237
238
/**
239
 * fu_hwids_add_value:
240
 * @self: a #FuHwids
241
 * @key: a key, e.g. %FU_HWIDS_KEY_PRODUCT_SKU
242
 * @value: (nullable): a new value, e.g. `ExampleModel`
243
 *
244
 * Sets override values so you can emulate another system.
245
 *
246
 * This function has no effect if called after fu_hwids_setup()
247
 *
248
 * Since: 1.8.10
249
 **/
250
void
251
fu_hwids_add_value(FuHwids *self, const gchar *key, const gchar *value)
252
0
{
253
0
  g_return_if_fail(FU_IS_HWIDS(self));
254
0
  g_return_if_fail(key != NULL);
255
256
  /* does not replace; first value set wins */
257
0
  if (g_hash_table_contains(self->hash_values, key))
258
0
    return;
259
0
  g_hash_table_insert(self->hash_values, g_strdup(key), g_strdup(value));
260
261
  /* make suitable for display */
262
0
  if (value != NULL) {
263
0
    g_autofree gchar *value_safe = g_str_to_ascii(value, "C");
264
0
    g_strdelimit(value_safe, "\n\r", '\0');
265
0
    g_strchomp(value_safe);
266
0
    g_hash_table_insert(self->hash_values_display,
267
0
            g_strdup(key),
268
0
            g_steal_pointer(&value_safe));
269
0
  } else {
270
0
    g_hash_table_insert(self->hash_values_display, g_strdup(key), NULL);
271
0
  }
272
0
}
273
274
/**
275
 * fu_hwids_get_replace_values:
276
 * @self: a #FuHwids
277
 * @keys: a key, e.g. `HardwareID-3` or %FU_HWIDS_KEY_PRODUCT_SKU
278
 * @error: (nullable): optional return location for an error
279
 *
280
 * Gets the replacement values for a HardwareID key or plain key.
281
 *
282
 * Returns: a string, e.g. `LENOVO&ThinkPad T440s`, or %NULL for error.
283
 *
284
 * Since: 0.9.3
285
 **/
286
gchar *
287
fu_hwids_get_replace_values(FuHwids *self, const gchar *keys, GError **error)
288
0
{
289
0
  g_auto(GStrv) split = NULL;
290
0
  g_autoptr(GString) str = g_string_new(NULL);
291
292
0
  g_return_val_if_fail(FU_IS_HWIDS(self), NULL);
293
0
  g_return_val_if_fail(keys != NULL, NULL);
294
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
295
296
  /* do any replacements */
297
0
  keys = fu_hwids_get_replace_keys(self, keys);
298
299
  /* get each part of the HWID */
300
0
  split = g_strsplit(keys, "&", -1);
301
0
  for (guint j = 0; split[j] != NULL; j++) {
302
0
    const gchar *tmp = g_hash_table_lookup(self->hash_values, split[j]);
303
0
    if (tmp == NULL) {
304
0
      g_set_error(error,
305
0
            FWUPD_ERROR,
306
0
            FWUPD_ERROR_NOT_FOUND,
307
0
            "not available as '%s' unknown",
308
0
            split[j]);
309
0
      return NULL;
310
0
    }
311
0
    g_string_append_printf(str, "%s&", tmp);
312
0
  }
313
0
  if (str->len > 0)
314
0
    g_string_truncate(str, str->len - 1);
315
0
  return g_strdup(str->str);
316
0
}
317
318
/**
319
 * fu_hwids_get_guid:
320
 * @self: a #FuHwids
321
 * @keys: a key, e.g. `HardwareID-3` or %FU_HWIDS_KEY_PRODUCT_SKU
322
 * @error: (nullable): optional return location for an error
323
 *
324
 * Gets the GUID for a specific key.
325
 *
326
 * Returns: a string, or %NULL for error.
327
 *
328
 * Since: 0.9.3
329
 **/
330
gchar *
331
fu_hwids_get_guid(FuHwids *self, const gchar *keys, GError **error)
332
0
{
333
0
  g_autofree gchar *tmp = NULL;
334
335
0
  g_return_val_if_fail(FU_IS_HWIDS(self), NULL);
336
0
  g_return_val_if_fail(keys != NULL, NULL);
337
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
338
339
0
  tmp = fu_hwids_get_replace_values(self, keys, error);
340
0
  if (tmp == NULL)
341
0
    return NULL;
342
0
  return fu_hwids_get_guid_for_str(tmp, error);
343
0
}
344
345
/**
346
 * fu_hwids_add_guid:
347
 * @self: a #FuHwids
348
 * @guid: a GUID
349
 *
350
 * Adds a HWID GUID value.
351
 *
352
 * Since: 1.8.10
353
 **/
354
void
355
fu_hwids_add_guid(FuHwids *self, const gchar *guid)
356
0
{
357
0
  g_return_if_fail(FU_IS_HWIDS(self));
358
0
  g_return_if_fail(guid != NULL);
359
0
  g_hash_table_insert(self->hash_guid, g_strdup(guid), GUINT_TO_POINTER(1));
360
0
  g_ptr_array_add(self->array_guids, g_strdup(guid));
361
0
}
362
363
/**
364
 * fu_hwids_setup:
365
 * @self: a #FuHwids
366
 * @error: (nullable): optional return location for an error
367
 *
368
 * Adds all the `HardwareID` GUIDs from the previously supplied data.
369
 *
370
 * Returns: %TRUE for success
371
 *
372
 * Since: 0.9.3
373
 **/
374
gboolean
375
fu_hwids_setup(FuHwids *self, GError **error)
376
0
{
377
0
  g_autoptr(GPtrArray) chids = fu_hwids_get_chid_keys(self);
378
379
0
  g_return_val_if_fail(FU_IS_HWIDS(self), FALSE);
380
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
381
382
  /* add GUIDs */
383
0
  for (guint i = 0; i < chids->len; i++) {
384
0
    const gchar *key = g_ptr_array_index(chids, i);
385
0
    g_autofree gchar *guid = NULL;
386
0
    g_autoptr(GError) error_local = NULL;
387
388
    /* get the GUID and add to hash */
389
0
    guid = fu_hwids_get_guid(self, key, &error_local);
390
0
    if (guid == NULL) {
391
0
      g_debug("%s is not available, %s", key, error_local->message);
392
0
      continue;
393
0
    }
394
0
    fu_hwids_add_guid(self, guid);
395
0
  }
396
397
0
  return TRUE;
398
0
}
399
400
static void
401
fu_hwids_finalize(GObject *object)
402
0
{
403
0
  FuHwids *self;
404
0
  g_return_if_fail(FU_IS_HWIDS(object));
405
0
  self = FU_HWIDS(object);
406
407
0
  g_hash_table_unref(self->hash_values);
408
0
  g_hash_table_unref(self->hash_values_display);
409
0
  g_hash_table_unref(self->hash_guid);
410
0
  g_hash_table_unref(self->chids);
411
0
  g_ptr_array_unref(self->array_guids);
412
413
0
  G_OBJECT_CLASS(fu_hwids_parent_class)->finalize(object);
414
0
}
415
416
static void
417
fu_hwids_class_init(FuHwidsClass *klass)
418
0
{
419
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
420
0
  object_class->finalize = fu_hwids_finalize;
421
0
}
422
423
static void
424
fu_hwids_init(FuHwids *self)
425
0
{
426
0
  self->hash_values = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
427
0
  self->hash_values_display = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
428
0
  self->hash_guid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
429
0
  self->chids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
430
0
  self->array_guids = g_ptr_array_new_with_free_func(g_free);
431
432
  /* Windows 10 CHIDs */
433
0
  fu_hwids_add_chid(self,
434
0
        "HardwareID-00",
435
0
        "Manufacturer&Family&ProductName&ProductSku&BiosVendor&BiosVersion&"
436
0
        "BiosMajorRelease&BiosMinorRelease");
437
0
  fu_hwids_add_chid(self,
438
0
        "HardwareID-01",
439
0
        "Manufacturer&Family&ProductName&BiosVendor&BiosVersion&"
440
0
        "BiosMajorRelease&BiosMinorRelease");
441
0
  fu_hwids_add_chid(self,
442
0
        "HardwareID-02",
443
0
        "Manufacturer&ProductName&BiosVendor&BiosVersion&"
444
0
        "BiosMajorRelease&BiosMinorRelease");
445
0
  fu_hwids_add_chid(self,
446
0
        "HardwareID-03",
447
0
        "Manufacturer&Family&ProductName&ProductSku&"
448
0
        "BaseboardManufacturer&BaseboardProduct");
449
0
  fu_hwids_add_chid(self, "HardwareID-04", "Manufacturer&Family&ProductName&ProductSku");
450
0
  fu_hwids_add_chid(self, "HardwareID-05", "Manufacturer&Family&ProductName");
451
0
  fu_hwids_add_chid(self,
452
0
        "HardwareID-06",
453
0
        "Manufacturer&ProductSku&BaseboardManufacturer&BaseboardProduct");
454
0
  fu_hwids_add_chid(self, "HardwareID-07", "Manufacturer&ProductSku");
455
0
  fu_hwids_add_chid(self,
456
0
        "HardwareID-08",
457
0
        "Manufacturer&ProductName&BaseboardManufacturer&BaseboardProduct");
458
0
  fu_hwids_add_chid(self, "HardwareID-09", "Manufacturer&ProductName");
459
0
  fu_hwids_add_chid(self,
460
0
        "HardwareID-10",
461
0
        "Manufacturer&Family&BaseboardManufacturer&BaseboardProduct");
462
0
  fu_hwids_add_chid(self, "HardwareID-11", "Manufacturer&Family");
463
0
  fu_hwids_add_chid(self, "HardwareID-12", "Manufacturer&EnclosureKind");
464
0
  fu_hwids_add_chid(self,
465
0
        "HardwareID-13",
466
0
        "Manufacturer&BaseboardManufacturer&BaseboardProduct");
467
0
  fu_hwids_add_chid(self, "HardwareID-14", "Manufacturer");
468
469
  /* used by the flashrom plugin */
470
0
  fu_hwids_add_chid(self,
471
0
        "fwupd-04",
472
0
        "Manufacturer&Family&ProductName&ProductSku&BiosVendor");
473
0
  fu_hwids_add_chid(self, "fwupd-05", "Manufacturer&Family&ProductName&BiosVendor");
474
0
  fu_hwids_add_chid(self, "fwupd-14", "Manufacturer&BiosVendor");
475
0
}
476
477
/**
478
 * fu_hwids_new:
479
 *
480
 * Creates a new #FuHwids
481
 *
482
 * Since: 0.9.3
483
 **/
484
FuHwids *
485
fu_hwids_new(void)
486
0
{
487
0
  FuHwids *self;
488
0
  self = g_object_new(FU_TYPE_HWIDS, NULL);
489
0
  return FU_HWIDS(self);
490
0
}