Coverage Report

Created: 2026-06-10 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-backend.c
Line
Count
Source
1
/*
2
 * Copyright 2021 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "FuBackend"
8
9
#include "config.h"
10
11
#include "fu-device-locker.h"
12
#include "fu-device-private.h"
13
14
/**
15
 * FuBackend:
16
 *
17
 * An device discovery backend, for instance USB, BlueZ or UDev.
18
 *
19
 * See also: [class@FuDevice]
20
 */
21
22
typedef struct {
23
  FuContext *ctx;
24
  gchar *name;
25
  gboolean enabled;
26
  gboolean done_setup;
27
  gboolean can_invalidate;
28
  GType device_gtype;
29
  GHashTable *devices; /* device_id : * FuDevice */
30
  GThread *thread_init;
31
} FuBackendPrivate;
32
33
enum { SIGNAL_ADDED, SIGNAL_REMOVED, SIGNAL_CHANGED, SIGNAL_LAST };
34
35
enum { PROP_0, PROP_NAME, PROP_CAN_INVALIDATE, PROP_CONTEXT, PROP_DEVICE_GTYPE, PROP_LAST };
36
37
static guint signals[SIGNAL_LAST] = {0};
38
39
static void
40
fu_backend_codec_iface_init(FwupdCodecInterface *iface);
41
42
2
G_DEFINE_TYPE_EXTENDED(FuBackend,
43
2
           fu_backend,
44
2
           G_TYPE_OBJECT,
45
2
           0,
46
2
           G_ADD_PRIVATE(FuBackend)
47
2
         G_IMPLEMENT_INTERFACE(FWUPD_TYPE_CODEC, fu_backend_codec_iface_init));
48
2
49
2
#define GET_PRIVATE(o) (fu_backend_get_instance_private(o))
50
51
/**
52
 * fu_backend_device_added:
53
 * @self: a #FuBackend
54
 * @device: a device
55
 *
56
 * Emits a signal that indicates the device has been added.
57
 *
58
 * Since: 1.6.1
59
 **/
60
void
61
fu_backend_device_added(FuBackend *self, FuDevice *device)
62
0
{
63
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
64
0
  g_return_if_fail(FU_IS_BACKEND(self));
65
0
  g_return_if_fail(FU_IS_DEVICE(device));
66
0
  g_return_if_fail(priv->thread_init == g_thread_self());
67
68
  /* assign context if set */
69
0
  if (priv->ctx != NULL)
70
0
    fu_device_set_context(device, priv->ctx);
71
72
  /* we set this here to be able to get the parent in plugins */
73
0
  fu_device_set_backend(device, self);
74
75
  /* set backend ID if required */
76
0
  if (fu_device_get_backend_id(device) == NULL)
77
0
    fu_device_set_backend_id(device, priv->name);
78
79
  /* set created to *now* */
80
0
  if (fu_device_get_created_usec(device) == 0)
81
0
    fu_device_set_created_usec(device, g_get_real_time());
82
83
  /* sanity check */
84
0
  if (g_hash_table_contains(priv->devices, fu_device_get_backend_id(device))) {
85
0
    g_debug("replacing existing device with backend_id %s",
86
0
      fu_device_get_backend_id(device));
87
0
  }
88
89
  /* add */
90
0
  g_hash_table_insert(priv->devices,
91
0
          g_strdup(fu_device_get_backend_id(device)),
92
0
          g_object_ref(device));
93
0
  g_signal_emit(self, signals[SIGNAL_ADDED], 0, device);
94
0
}
95
96
/**
97
 * fu_backend_device_removed:
98
 * @self: a #FuBackend
99
 * @device: a device
100
 *
101
 * Emits a signal that indicates the device has been removed.
102
 *
103
 * Since: 1.6.1
104
 **/
105
void
106
fu_backend_device_removed(FuBackend *self, FuDevice *device)
107
0
{
108
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
109
0
  g_return_if_fail(FU_IS_BACKEND(self));
110
0
  g_return_if_fail(FU_IS_DEVICE(device));
111
0
  g_return_if_fail(priv->thread_init == g_thread_self());
112
0
  g_signal_emit(self, signals[SIGNAL_REMOVED], 0, device);
113
0
  g_hash_table_remove(priv->devices, fu_device_get_backend_id(device));
114
0
}
115
116
/**
117
 * fu_backend_device_changed:
118
 * @self: a #FuBackend
119
 * @device: a device
120
 *
121
 * Emits a signal that indicates the device has been changed.
122
 *
123
 * Since: 1.6.1
124
 **/
125
void
126
fu_backend_device_changed(FuBackend *self, FuDevice *device)
127
0
{
128
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
129
0
  g_return_if_fail(FU_IS_BACKEND(self));
130
0
  g_return_if_fail(FU_IS_DEVICE(device));
131
0
  g_return_if_fail(priv->thread_init == g_thread_self());
132
0
  g_signal_emit(self, signals[SIGNAL_CHANGED], 0, device);
133
0
}
134
135
/**
136
 * fu_backend_registered:
137
 * @self: a #FuBackend
138
 * @device: a device
139
 *
140
 * Calls the ->registered() vfunc for the backend. This allows the backend to perform shared
141
 * backend actions on superclassed devices.
142
 *
143
 * Since: 1.7.4
144
 **/
145
void
146
fu_backend_registered(FuBackend *self, FuDevice *device)
147
0
{
148
0
  FuBackendClass *klass = FU_BACKEND_GET_CLASS(self);
149
0
  g_return_if_fail(FU_IS_BACKEND(self));
150
0
  g_return_if_fail(FU_IS_DEVICE(device));
151
0
  if (klass->registered != NULL)
152
0
    klass->registered(self, device);
153
0
}
154
155
/**
156
 * fu_backend_get_device_parent:
157
 * @self: a #FuBackend
158
 * @device: a #FuDevice
159
 * @subsystem: (nullable): an optional device subsystem, e.g. "usb:usb_device"
160
 * @error: (nullable): optional return location for an error
161
 *
162
 * Asks the backend to create the parent device (of the correct type) for a given device subsystem.
163
 *
164
 * Returns: (transfer full): a #FuDevice or %NULL if not found or unimplemented
165
 *
166
 * Since: 2.0.0
167
 **/
168
FuDevice *
169
fu_backend_get_device_parent(FuBackend *self,
170
           FuDevice *device,
171
           const gchar *subsystem,
172
           GError **error)
173
0
{
174
0
  FuBackendClass *klass = FU_BACKEND_GET_CLASS(self);
175
176
0
  g_return_val_if_fail(FU_IS_BACKEND(self), NULL);
177
0
  g_return_val_if_fail(FU_IS_DEVICE(device), NULL);
178
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
179
180
0
  if (klass->get_device_parent == NULL) {
181
0
    g_set_error(error,
182
0
          FWUPD_ERROR,
183
0
          FWUPD_ERROR_NOT_SUPPORTED,
184
0
          "->get_device_parent is not implemented in %s",
185
0
          G_OBJECT_TYPE_NAME(self));
186
0
    return NULL;
187
0
  }
188
0
  return klass->get_device_parent(self, device, subsystem, error);
189
0
}
190
191
/**
192
 * fu_backend_create_device:
193
 * @self: a #FuBackend
194
 * @backend_id: a backend ID, typically a sysfs path
195
 * @error: (nullable): optional return location for an error
196
 *
197
 * Asks the backend to create a device (of the correct type) for a given device backend ID.
198
 *
199
 * Returns: (transfer full): a #FuDevice or %NULL if not found or unimplemented
200
 *
201
 * Since: 2.0.0
202
 **/
203
FuDevice *
204
fu_backend_create_device(FuBackend *self, const gchar *backend_id, GError **error)
205
0
{
206
0
  FuBackendClass *klass = FU_BACKEND_GET_CLASS(self);
207
0
  g_autoptr(FuDevice) device = NULL;
208
209
0
  g_return_val_if_fail(FU_IS_BACKEND(self), NULL);
210
0
  g_return_val_if_fail(backend_id != NULL, NULL);
211
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
212
213
0
  if (klass->create_device == NULL) {
214
0
    g_set_error(error,
215
0
          FWUPD_ERROR,
216
0
          FWUPD_ERROR_NOT_SUPPORTED,
217
0
          "->create_device is not implemented in %s",
218
0
          G_OBJECT_TYPE_NAME(self));
219
0
    return NULL;
220
0
  }
221
0
  device = klass->create_device(self, backend_id, error);
222
0
  if (device == NULL)
223
0
    return NULL;
224
0
  fu_device_set_backend(device, self);
225
226
  /* success */
227
0
  return g_steal_pointer(&device);
228
0
}
229
230
/**
231
 * fu_backend_create_device_for_donor:
232
 * @self: a #FuBackend
233
 * @donor: a donor #FuDevice
234
 * @error: (nullable): optional return location for an error
235
 *
236
 * Asks the backend to create a device (of the correct type) for a given donor device.
237
 *
238
 * Returns: (transfer full): a #FuDevice or %NULL if not found or unimplemented
239
 *
240
 * Since: 2.0.0
241
 **/
242
FuDevice *
243
fu_backend_create_device_for_donor(FuBackend *self, FuDevice *donor, GError **error)
244
0
{
245
0
  FuBackendClass *klass = FU_BACKEND_GET_CLASS(self);
246
0
  g_autoptr(FuDevice) device = NULL;
247
248
0
  g_return_val_if_fail(FU_IS_BACKEND(self), NULL);
249
0
  g_return_val_if_fail(FU_IS_DEVICE(donor), NULL);
250
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
251
252
  /* no impl */
253
0
  if (klass->create_device_for_donor == NULL)
254
0
    return g_object_ref(donor);
255
256
  /* impl */
257
0
  device = klass->create_device_for_donor(self, donor, error);
258
0
  if (device == NULL)
259
0
    return NULL;
260
0
  fu_device_set_backend(device, self);
261
262
  /* success */
263
0
  return g_steal_pointer(&device);
264
0
}
265
266
/**
267
 * fu_backend_invalidate:
268
 * @self: a #FuBackend
269
 *
270
 * Normally when calling [method@FuBackend.setup] multiple times it is only actually done once.
271
 * Calling this method causes the next requests to [method@FuBackend.setup] to actually probe the
272
 * hardware.
273
 *
274
 * Only subclassed backends setting `can-invalidate=TRUE` at construction time can use this
275
 * method, as it is not always safe to call for backends shared between multiple plugins.
276
 *
277
 * This should be done in case the backing information source has changed, for instance if
278
 * a platform subsystem has been updated.
279
 *
280
 * This also optionally calls the ->invalidate() vfunc for the backend. This allows the backend
281
 * to perform shared backend actions on superclassed devices.
282
 *
283
 * Since: 1.8.0
284
 **/
285
void
286
fu_backend_invalidate(FuBackend *self)
287
0
{
288
0
  FuBackendClass *klass = FU_BACKEND_GET_CLASS(self);
289
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
290
291
0
  g_return_if_fail(FU_IS_BACKEND(self));
292
0
  g_return_if_fail(priv->can_invalidate);
293
294
0
  priv->done_setup = FALSE;
295
0
  if (klass->invalidate != NULL)
296
0
    klass->invalidate(self);
297
0
}
298
299
/**
300
 * fu_backend_add_string:
301
 * @self: a #FuBackend
302
 * @idt: indent level
303
 * @str: a string to append to
304
 *
305
 * Add backend-specific device metadata to an existing string.
306
 *
307
 * Since: 1.8.4
308
 **/
309
void
310
fu_backend_add_string(FuBackend *self, guint idt, GString *str)
311
0
{
312
0
  FuBackendClass *klass = FU_BACKEND_GET_CLASS(self);
313
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
314
315
0
  fwupd_codec_string_append(str, idt, G_OBJECT_TYPE_NAME(self), "");
316
0
  fwupd_codec_string_append(str, idt + 1, "Name", priv->name);
317
0
  fwupd_codec_string_append_bool(str, idt + 1, "Enabled", priv->enabled);
318
0
  fwupd_codec_string_append_bool(str, idt + 1, "DoneSetup", priv->done_setup);
319
0
  fwupd_codec_string_append_bool(str, idt + 1, "CanInvalidate", priv->can_invalidate);
320
321
  /* subclassed */
322
0
  if (klass->to_string != NULL)
323
0
    klass->to_string(self, idt, str);
324
0
}
325
326
/**
327
 * fu_backend_setup:
328
 * @self: a #FuBackend
329
 * @flags: some #FuBackendSetupFlags, e.g. %FU_BACKEND_SETUP_FLAG_USE_HOTPLUG
330
 * @progress: a #FuProgress
331
 * @error: (nullable): optional return location for an error
332
 *
333
 * Sets up the backend ready for use, which typically calls the subclassed setup
334
 * function. No devices should be added or removed at this point.
335
 *
336
 * Returns: %TRUE for success
337
 *
338
 * Since: 1.6.1
339
 **/
340
gboolean
341
fu_backend_setup(FuBackend *self, FuBackendSetupFlags flags, FuProgress *progress, GError **error)
342
0
{
343
0
  FuBackendClass *klass = FU_BACKEND_GET_CLASS(self);
344
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
345
346
0
  g_return_val_if_fail(FU_IS_BACKEND(self), FALSE);
347
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
348
349
0
  if (priv->done_setup)
350
0
    return TRUE;
351
0
  if (klass->setup != NULL) {
352
0
    if (!klass->setup(self, flags, progress, error)) {
353
0
      priv->enabled = FALSE;
354
0
      return FALSE;
355
0
    }
356
0
  }
357
0
  priv->done_setup = TRUE;
358
0
  return TRUE;
359
0
}
360
361
static gboolean
362
fu_backend_from_json(FwupdCodec *codec, FwupdJsonObject *json_obj, GError **error)
363
0
{
364
0
  FuBackend *self = FU_BACKEND(codec);
365
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
366
0
  g_autoptr(FwupdJsonArray) json_arr = NULL;
367
0
  const gchar *fwupd_version;
368
0
  g_autoptr(GPtrArray) devices_added =
369
0
      g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
370
0
  g_autoptr(GPtrArray) devices_remove = NULL;
371
372
  /* no registered specialized GType */
373
0
  if (priv->device_gtype == FU_TYPE_DEVICE) {
374
0
    g_debug("no registered device GType for backend %s", fu_backend_get_name(self));
375
0
    return TRUE;
376
0
  }
377
378
  /* if recorded */
379
0
  fwupd_version = fwupd_json_object_get_string(json_obj, "FwupdVersion", NULL);
380
381
  /* four steps:
382
   *
383
   * 1. store all the existing devices matching the tag in devices_remove
384
   * 2. read the devices in the array:
385
   *    - if the platform-id exists: replace the event data & remove from devices_remove
386
   *    - otherwise add to devices_added
387
   * 3. emit devices in devices_remove
388
   * 4. emit devices in devices_added
389
   */
390
0
  devices_remove = fu_backend_get_devices(self);
391
0
  json_arr = fwupd_json_object_get_array(json_obj, "UsbDevices", NULL);
392
0
  if (json_arr == NULL) {
393
    /* remain compatible with all the old emulation files */
394
0
    return TRUE;
395
0
  }
396
0
  for (guint i = 0; i < fwupd_json_array_get_size(json_arr); i++) {
397
0
    FuDevice *device_old;
398
0
    const gchar *device_gtypestr;
399
0
    GType device_gtype;
400
0
    g_autofree gchar *id_display = NULL;
401
0
    g_autoptr(FuDevice) device_tmp = NULL;
402
0
    g_autoptr(FwupdJsonObject) object_tmp = NULL;
403
404
    /* sanity check */
405
0
    object_tmp = fwupd_json_array_get_object(json_arr, i, error);
406
0
    if (object_tmp == NULL)
407
0
      return FALSE;
408
409
    /* get the GType */
410
0
    device_gtypestr = fwupd_json_object_get_string(object_tmp, "GType", NULL);
411
0
    if (device_gtypestr == NULL)
412
0
      device_gtypestr = "FuUsbDevice";
413
0
    device_gtype = g_type_from_name(device_gtypestr);
414
0
    if (device_gtype == G_TYPE_INVALID) {
415
0
      g_set_error(error,
416
0
            FWUPD_ERROR,
417
0
            FWUPD_ERROR_NOT_SUPPORTED,
418
0
            "unknown GType name %s",
419
0
            device_gtypestr);
420
0
      return FALSE;
421
0
    }
422
0
    if (!g_type_is_a(device_gtype, priv->device_gtype)) {
423
0
      g_debug("ignoring device backend GType %s",
424
0
        g_type_name(priv->device_gtype));
425
0
      continue;
426
0
    }
427
428
    /* create device */
429
0
    device_tmp = g_object_new(device_gtype, "backend", self, NULL);
430
0
    fu_device_add_flag(device_tmp, FWUPD_DEVICE_FLAG_EMULATED);
431
0
    if (fwupd_version != NULL)
432
0
      fu_device_set_fwupd_version(device_tmp, fwupd_version);
433
0
    if (!fu_device_from_json(device_tmp, object_tmp, error))
434
0
      return FALSE;
435
0
    if (fu_device_get_backend_id(device_tmp) == NULL) {
436
0
      g_set_error(error,
437
0
            FWUPD_ERROR,
438
0
            FWUPD_ERROR_NOT_SUPPORTED,
439
0
            "no backend specified %s",
440
0
            device_gtypestr);
441
0
      return FALSE;
442
0
    }
443
0
    id_display = fu_device_get_id_display(device_tmp);
444
445
    /* does a device with this platform ID [and the same created date] already exist */
446
0
    device_old = fu_backend_lookup_by_id(self, fu_device_get_backend_id(device_tmp));
447
448
    /* yes, and it has the same timestamp */
449
0
    if (device_old != NULL) {
450
0
      g_debug("created timestamp %" G_GINT64_FORMAT "->%" G_GINT64_FORMAT,
451
0
        fu_device_get_created_usec(device_old),
452
0
        fu_device_get_created_usec(device_tmp));
453
0
    }
454
0
    if (device_old != NULL && fu_device_get_created_usec(device_old) ==
455
0
                fu_device_get_created_usec(device_tmp)) {
456
0
      GPtrArray *events = fu_device_get_events(device_tmp);
457
458
0
      fu_device_clear_events(device_old);
459
0
      for (guint j = 0; j < events->len; j++) {
460
0
        FuDeviceEvent *event = g_ptr_array_index(events, j);
461
0
        fu_device_add_event(device_old, event);
462
0
      }
463
0
      g_debug("changed %s", id_display);
464
0
      fu_backend_device_changed(self, device_old);
465
0
      g_ptr_array_remove(devices_remove, device_old);
466
0
      continue;
467
0
    }
468
469
    /* new to us! */
470
0
    g_debug("not found %s, adding", id_display);
471
0
    g_ptr_array_add(devices_added, g_object_ref(device_tmp));
472
0
  }
473
474
  /* emit removes then adds */
475
0
  for (guint i = 0; i < devices_remove->len; i++) {
476
0
    FuDevice *device = g_ptr_array_index(devices_remove, i);
477
0
    if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED))
478
0
      continue;
479
0
    fu_backend_device_removed(self, device);
480
0
  }
481
0
  for (guint i = 0; i < devices_added->len; i++) {
482
0
    FuDevice *donor = g_ptr_array_index(devices_added, i);
483
0
    g_autoptr(FuDevice) device = NULL;
484
0
    g_autoptr(GError) error_local = NULL;
485
486
    /* convert from FuUdevDevice to the superclass, e.g. FuHidrawDevice */
487
0
    if (!fu_device_probe(donor, &error_local)) {
488
0
      if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) {
489
0
        g_propagate_error(error, g_steal_pointer(&error_local));
490
0
        return FALSE;
491
0
      }
492
0
    }
493
0
    device = fu_backend_create_device_for_donor(self, donor, error);
494
0
    if (device == NULL)
495
0
      return FALSE;
496
0
    fu_device_add_flag(device, FWUPD_DEVICE_FLAG_EMULATED);
497
498
    /* some devices only add plugin-matching instance IDs in FuDevice->setup() */
499
0
    if (fu_device_has_private_flag(device,
500
0
                 FU_DEVICE_PRIVATE_FLAG_EMULATED_REQUIRE_SETUP)) {
501
0
      g_autoptr(FuDeviceLocker) locker = fu_device_locker_new(device, error);
502
0
      if (locker == NULL)
503
0
        return FALSE;
504
0
    }
505
506
0
    fu_backend_device_added(self, device);
507
0
  }
508
509
  /* success */
510
0
  return TRUE;
511
0
}
512
513
static void
514
fu_backend_add_json(FwupdCodec *codec, FwupdJsonObject *json_obj, FwupdCodecFlags flags)
515
0
{
516
0
  FuBackend *self = FU_BACKEND(codec);
517
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
518
0
  g_autoptr(GList) devices = NULL;
519
0
  g_autoptr(FwupdJsonArray) json_arr = fwupd_json_array_new();
520
521
  /* remain compatible with all the old emulation files */
522
0
  fwupd_json_object_add_string(json_obj, "FwupdVersion", PACKAGE_VERSION);
523
0
  devices = g_hash_table_get_values(priv->devices);
524
0
  for (GList *l = devices; l != NULL; l = l->next) {
525
0
    FuDevice *device = FU_DEVICE(l->data);
526
0
    g_autoptr(FwupdJsonObject) json_device = fwupd_json_object_new();
527
0
    if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG))
528
0
      continue;
529
0
    fu_device_add_json(device, json_device, FWUPD_CODEC_FLAG_NONE);
530
0
    fwupd_json_array_add_object(json_arr, json_device);
531
0
  }
532
0
  fwupd_json_object_add_array(json_obj, "UsbDevices", json_arr);
533
0
}
534
535
/**
536
 * fu_backend_coldplug:
537
 * @self: a #FuBackend
538
 * @progress: a #FuProgress
539
 * @error: (nullable): optional return location for an error
540
 *
541
 * Adds devices using the subclassed backend. If [method@FuBackend.setup] has not
542
 * already been called then it is run before this function automatically.
543
 *
544
 * Returns: %TRUE for success
545
 *
546
 * Since: 1.6.1
547
 **/
548
gboolean
549
fu_backend_coldplug(FuBackend *self, FuProgress *progress, GError **error)
550
0
{
551
0
  FuBackendClass *klass = FU_BACKEND_GET_CLASS(self);
552
0
  g_return_val_if_fail(FU_IS_BACKEND(self), FALSE);
553
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
554
0
  if (!fu_backend_setup(self, FU_BACKEND_SETUP_FLAG_NONE, progress, error))
555
0
    return FALSE;
556
0
  if (klass->coldplug == NULL)
557
0
    return TRUE;
558
0
  return klass->coldplug(self, progress, error);
559
0
}
560
561
/**
562
 * fu_backend_get_name:
563
 * @self: a #FuBackend
564
 *
565
 * Return the name of the backend, which is normally set by the subclass.
566
 *
567
 * Returns: backend name
568
 *
569
 * Since: 1.6.1
570
 **/
571
const gchar *
572
fu_backend_get_name(FuBackend *self)
573
0
{
574
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
575
0
  g_return_val_if_fail(FU_IS_BACKEND(self), NULL);
576
0
  return priv->name;
577
0
}
578
579
/**
580
 * fu_backend_get_context:
581
 * @self: a #FuBackend
582
 *
583
 * Gets the context for a backend.
584
 *
585
 * Returns: (transfer none): a #FuContext or %NULL if not set
586
 *
587
 * Since: 1.6.1
588
 **/
589
FuContext *
590
fu_backend_get_context(FuBackend *self)
591
0
{
592
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
593
0
  return priv->ctx;
594
0
}
595
596
/**
597
 * fu_backend_get_enabled:
598
 * @self: a #FuBackend
599
 *
600
 * Return the boolean value of a key if it's been configured
601
 *
602
 * Returns: %TRUE if the backend is enabled
603
 *
604
 * Since: 1.6.1
605
 **/
606
gboolean
607
fu_backend_get_enabled(FuBackend *self)
608
0
{
609
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
610
0
  g_return_val_if_fail(FU_IS_BACKEND(self), FALSE);
611
0
  return priv->enabled;
612
0
}
613
614
/**
615
 * fu_backend_set_enabled:
616
 * @self: a #FuBackend
617
 * @enabled: enabled state
618
 *
619
 * Sets the backend enabled state.
620
 *
621
 * Since: 1.6.1
622
 **/
623
void
624
fu_backend_set_enabled(FuBackend *self, gboolean enabled)
625
0
{
626
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
627
0
  g_return_if_fail(FU_IS_BACKEND(self));
628
0
  priv->enabled = FALSE;
629
0
}
630
631
/**
632
 * fu_backend_lookup_by_id:
633
 * @self: a #FuBackend
634
 * @backend_id: a device backend ID
635
 *
636
 * Gets a device previously added by the backend.
637
 *
638
 * Returns: (transfer none): device, or %NULL if not found
639
 *
640
 * Since: 1.6.1
641
 **/
642
FuDevice *
643
fu_backend_lookup_by_id(FuBackend *self, const gchar *backend_id)
644
0
{
645
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
646
0
  g_return_val_if_fail(FU_IS_BACKEND(self), NULL);
647
0
  g_return_val_if_fail(backend_id != NULL, NULL);
648
0
  return g_hash_table_lookup(priv->devices, backend_id);
649
0
}
650
651
static gint
652
fu_backend_get_devices_sort_cb(gconstpointer a, gconstpointer b)
653
0
{
654
0
  FuDevice *deva = *((FuDevice **)a);
655
0
  FuDevice *devb = *((FuDevice **)b);
656
0
  return g_strcmp0(fu_device_get_backend_id(deva), fu_device_get_backend_id(devb));
657
0
}
658
659
/**
660
 * fu_backend_get_devices:
661
 * @self: a #FuBackend
662
 *
663
 * Gets all the devices added by the backend.
664
 *
665
 * Returns: (transfer container) (element-type FuDevice): devices
666
 *
667
 * Since: 1.6.1
668
 **/
669
GPtrArray *
670
fu_backend_get_devices(FuBackend *self)
671
0
{
672
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
673
0
  g_autoptr(GList) values = NULL;
674
0
  g_autoptr(GPtrArray) devices = NULL;
675
676
0
  g_return_val_if_fail(FU_IS_BACKEND(self), NULL);
677
678
0
  devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
679
0
  values = g_hash_table_get_values(priv->devices);
680
0
  for (GList *l = values; l != NULL; l = l->next)
681
0
    g_ptr_array_add(devices, g_object_ref(l->data));
682
0
  g_ptr_array_sort(devices, fu_backend_get_devices_sort_cb);
683
0
  return g_steal_pointer(&devices);
684
0
}
685
686
static void
687
fu_backend_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
688
0
{
689
0
  FuBackend *self = FU_BACKEND(object);
690
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
691
0
  switch (prop_id) {
692
0
  case PROP_NAME:
693
0
    g_value_set_string(value, priv->name);
694
0
    break;
695
0
  case PROP_CAN_INVALIDATE:
696
0
    g_value_set_boolean(value, priv->can_invalidate);
697
0
    break;
698
0
  case PROP_CONTEXT:
699
0
    g_value_set_object(value, priv->ctx);
700
0
    break;
701
0
  case PROP_DEVICE_GTYPE:
702
0
    g_value_set_gtype(value, priv->device_gtype);
703
0
    break;
704
0
  default:
705
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
706
0
    break;
707
0
  }
708
0
}
709
710
static void
711
fu_backend_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
712
0
{
713
0
  FuBackend *self = FU_BACKEND(object);
714
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
715
0
  switch (prop_id) {
716
0
  case PROP_NAME:
717
0
    priv->name = g_value_dup_string(value);
718
0
    break;
719
0
  case PROP_CAN_INVALIDATE:
720
0
    priv->can_invalidate = g_value_get_boolean(value);
721
0
    break;
722
0
  case PROP_CONTEXT:
723
0
    g_set_object(&priv->ctx, g_value_get_object(value));
724
0
    break;
725
0
  case PROP_DEVICE_GTYPE:
726
0
    priv->device_gtype = g_value_get_gtype(value);
727
0
    break;
728
0
  default:
729
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
730
0
    break;
731
0
  }
732
0
}
733
734
static void
735
fu_backend_codec_iface_init(FwupdCodecInterface *iface)
736
0
{
737
0
  iface->add_json = fu_backend_add_json;
738
0
  iface->from_json = fu_backend_from_json;
739
0
}
740
741
static void
742
fu_backend_init(FuBackend *self)
743
0
{
744
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
745
0
  priv->enabled = TRUE;
746
0
  priv->device_gtype = FU_TYPE_DEVICE;
747
0
  priv->thread_init = g_thread_self();
748
0
  priv->devices =
749
0
      g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_object_unref);
750
0
}
751
752
static void
753
fu_backend_dispose(GObject *object)
754
0
{
755
0
  FuBackend *self = FU_BACKEND(object);
756
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
757
0
  g_hash_table_remove_all(priv->devices);
758
0
  g_clear_object(&priv->ctx);
759
0
  G_OBJECT_CLASS(fu_backend_parent_class)->dispose(object);
760
0
}
761
762
static void
763
fu_backend_finalize(GObject *object)
764
0
{
765
0
  FuBackend *self = FU_BACKEND(object);
766
0
  FuBackendPrivate *priv = GET_PRIVATE(self);
767
0
  g_free(priv->name);
768
0
  g_hash_table_unref(priv->devices);
769
0
  G_OBJECT_CLASS(fu_backend_parent_class)->finalize(object);
770
0
}
771
772
static void
773
fu_backend_class_init(FuBackendClass *klass)
774
0
{
775
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
776
0
  GParamSpec *pspec;
777
778
0
  object_class->get_property = fu_backend_get_property;
779
0
  object_class->set_property = fu_backend_set_property;
780
0
  object_class->finalize = fu_backend_finalize;
781
0
  object_class->dispose = fu_backend_dispose;
782
783
  /**
784
   * FuBackend:name:
785
   *
786
   * The backend name.
787
   *
788
   * Since: 1.6.1
789
   */
790
0
  pspec =
791
0
      g_param_spec_string("name",
792
0
        NULL,
793
0
        NULL,
794
0
        NULL,
795
0
        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
796
0
  g_object_class_install_property(object_class, PROP_NAME, pspec);
797
798
  /**
799
   * FuBackend:can-invalidate:
800
   *
801
   * If the backend can be invalidated.
802
   *
803
   * Since: 1.8.0
804
   */
805
0
  pspec =
806
0
      g_param_spec_boolean("can-invalidate",
807
0
         NULL,
808
0
         NULL,
809
0
         FALSE,
810
0
         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
811
0
  g_object_class_install_property(object_class, PROP_CAN_INVALIDATE, pspec);
812
813
  /**
814
   * FuBackend:context:
815
   *
816
   * The #FuContent to use.
817
   *
818
   * Since: 1.6.1
819
   */
820
0
  pspec =
821
0
      g_param_spec_object("context",
822
0
        NULL,
823
0
        NULL,
824
0
        FU_TYPE_CONTEXT,
825
0
        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
826
0
  g_object_class_install_property(object_class, PROP_CONTEXT, pspec);
827
828
  /**
829
   * FuBackend:device-gtype:
830
   *
831
   * The #GType to use when creating emulated devices.
832
   *
833
   * Since: 2.0.0
834
   */
835
0
  pspec = g_param_spec_gtype("device-gtype",
836
0
           NULL,
837
0
           NULL,
838
0
           FU_TYPE_DEVICE,
839
0
           G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
840
0
  g_object_class_install_property(object_class, PROP_DEVICE_GTYPE, pspec);
841
842
  /**
843
   * FuBackend::device-added:
844
   * @self: the #FuBackend instance that emitted the signal
845
   * @device: the #FuDevice
846
   *
847
   * The ::device-added signal is emitted when a device has been added.
848
   *
849
   * Since: 1.6.1
850
   **/
851
0
  signals[SIGNAL_ADDED] = g_signal_new("device-added",
852
0
               G_TYPE_FROM_CLASS(object_class),
853
0
               G_SIGNAL_RUN_LAST,
854
0
               0,
855
0
               NULL,
856
0
               NULL,
857
0
               g_cclosure_marshal_VOID__OBJECT,
858
0
               G_TYPE_NONE,
859
0
               1,
860
0
               FU_TYPE_DEVICE);
861
  /**
862
   * FuBackend::device-removed:
863
   * @self: the #FuBackend instance that emitted the signal
864
   * @device: the #FuDevice
865
   *
866
   * The ::device-removed signal is emitted when a device has been removed.
867
   *
868
   * Since: 1.6.1
869
   **/
870
0
  signals[SIGNAL_REMOVED] = g_signal_new("device-removed",
871
0
                 G_TYPE_FROM_CLASS(object_class),
872
0
                 G_SIGNAL_RUN_LAST,
873
0
                 0,
874
0
                 NULL,
875
0
                 NULL,
876
0
                 g_cclosure_marshal_VOID__OBJECT,
877
0
                 G_TYPE_NONE,
878
0
                 1,
879
0
                 FU_TYPE_DEVICE);
880
  /**
881
   * FuBackend::device-changed:
882
   * @self: the #FuBackend instance that emitted the signal
883
   * @device: the #FuDevice
884
   *
885
   * The ::device-changed signal is emitted when a device has been changed.
886
   *
887
   * Since: 1.6.1
888
   **/
889
0
  signals[SIGNAL_CHANGED] = g_signal_new("device-changed",
890
0
                 G_TYPE_FROM_CLASS(object_class),
891
0
                 G_SIGNAL_RUN_LAST,
892
0
                 0,
893
0
                 NULL,
894
0
                 NULL,
895
0
                 g_cclosure_marshal_VOID__OBJECT,
896
0
                 G_TYPE_NONE,
897
0
                 1,
898
0
                 FU_TYPE_DEVICE);
899
0
}