Coverage Report

Created: 2026-04-12 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-udev-device.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 "FuUdevDevice"
8
9
#include "config.h"
10
11
#include <fcntl.h>
12
#include <string.h>
13
#ifdef HAVE_ERRNO_H
14
#include <errno.h>
15
#endif
16
#ifdef HAVE_IOCTL_H
17
#include <sys/ioctl.h>
18
#endif
19
#include <glib/gstdio.h>
20
#include <sys/stat.h>
21
#include <sys/types.h>
22
#include <unistd.h>
23
24
#include "fu-byte-array.h"
25
#include "fu-device-event-private.h"
26
#include "fu-device-private.h"
27
#include "fu-dpaux-device.h"
28
#include "fu-ioctl-private.h"
29
#include "fu-output-stream.h"
30
#include "fu-path.h"
31
#include "fu-string.h"
32
#include "fu-udev-device-private.h"
33
34
/**
35
 * FuUdevDevice:
36
 *
37
 * A UDev device, typically only available on Linux.
38
 *
39
 * See also: [class@FuDevice]
40
 */
41
42
typedef struct {
43
  gchar *subsystem;
44
  gchar *bind_id;
45
  gchar *driver;
46
  gchar *device_file;
47
  gchar *devtype;
48
  guint64 number;
49
  FuIOChannel *io_channel;
50
  goffset emulated_offset;
51
  FuIoChannelOpenFlags open_flags;
52
  GHashTable *properties;
53
  gboolean properties_valid;
54
} FuUdevDevicePrivate;
55
56
1.11k
G_DEFINE_TYPE_WITH_PRIVATE(FuUdevDevice, fu_udev_device, FU_TYPE_DEVICE);
57
1.11k
58
1.11k
enum {
59
1.11k
  PROP_0,
60
1.11k
  PROP_SUBSYSTEM,
61
1.11k
  PROP_DRIVER,
62
1.11k
  PROP_DEVICE_FILE,
63
1.11k
  PROP_BIND_ID,
64
1.11k
  PROP_DEVTYPE,
65
1.11k
  PROP_LAST
66
1.11k
};
67
1.11k
68
1.11k
enum { SIGNAL_CHANGED, SIGNAL_LAST };
69
1.11k
70
1.11k
static guint signals[SIGNAL_LAST] = {0};
71
1.11k
72
2.22k
#define GET_PRIVATE(o) (fu_udev_device_get_instance_private(o))
73
74
/**
75
 * fu_udev_device_emit_changed:
76
 * @self: a #FuUdevDevice
77
 *
78
 * Emits the ::changed signal for the object.
79
 *
80
 * Since: 1.1.2
81
 **/
82
void
83
fu_udev_device_emit_changed(FuUdevDevice *self)
84
0
{
85
0
  g_autoptr(GError) error = NULL;
86
0
  g_return_if_fail(FU_IS_UDEV_DEVICE(self));
87
0
  g_debug("FuUdevDevice emit changed");
88
0
  if (!fu_device_rescan(FU_DEVICE(self), &error))
89
0
    g_debug("%s", error->message);
90
0
  g_signal_emit(self, signals[SIGNAL_CHANGED], 0);
91
0
}
92
93
static void
94
fu_udev_device_to_string(FuDevice *device, guint idt, GString *str)
95
0
{
96
0
  FuUdevDevice *self = FU_UDEV_DEVICE(device);
97
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
98
0
  g_autofree gchar *open_flags = fu_io_channel_open_flags_to_string(priv->open_flags);
99
100
0
  fwupd_codec_string_append_hex(str, idt, "Number", priv->number);
101
0
  fwupd_codec_string_append(str, idt, "Subsystem", priv->subsystem);
102
0
  fwupd_codec_string_append(str, idt, "Devtype", priv->devtype);
103
0
  fwupd_codec_string_append(str, idt, "Driver", priv->driver);
104
0
  fwupd_codec_string_append(str, idt, "BindId", priv->bind_id);
105
0
  fwupd_codec_string_append(str, idt, "DeviceFile", priv->device_file);
106
0
  fwupd_codec_string_append(str, idt, "OpenFlags", open_flags);
107
0
}
108
109
static gboolean
110
fu_udev_device_ensure_bind_id(FuUdevDevice *self, GError **error)
111
0
{
112
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
113
114
  /* sanity check */
115
0
  if (priv->bind_id != NULL)
116
0
    return TRUE;
117
118
  /* automatically set the bind ID from the subsystem */
119
0
  if (g_strcmp0(priv->subsystem, "pci") == 0) {
120
0
    priv->bind_id = fu_udev_device_read_property(self, "PCI_SLOT_NAME", error);
121
0
    return priv->bind_id != NULL;
122
0
  }
123
0
  if (g_strcmp0(priv->subsystem, "hid") == 0) {
124
0
    priv->bind_id = fu_udev_device_read_property(self, "HID_PHYS", error);
125
0
    return priv->bind_id != NULL;
126
0
  }
127
0
  if (g_strcmp0(priv->subsystem, "usb") == 0 || g_strcmp0(priv->subsystem, "i2c") == 0) {
128
0
    priv->bind_id = g_path_get_basename(fu_udev_device_get_sysfs_path(self));
129
0
    return TRUE;
130
0
  }
131
132
  /* nothing found automatically */
133
0
  g_set_error(error,
134
0
        FWUPD_ERROR,
135
0
        FWUPD_ERROR_NOT_SUPPORTED,
136
0
        "cannot derive bind-id from subsystem %s",
137
0
        priv->subsystem);
138
0
  return FALSE;
139
0
}
140
141
/* private */
142
void
143
fu_udev_device_set_subsystem(FuUdevDevice *self, const gchar *subsystem)
144
0
{
145
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
146
147
  /* not changed */
148
0
  if (g_strcmp0(priv->subsystem, subsystem) == 0)
149
0
    return;
150
151
0
  g_free(priv->subsystem);
152
0
  priv->subsystem = g_strdup(subsystem);
153
0
  g_object_notify(G_OBJECT(self), "subsystem");
154
0
}
155
156
/**
157
 * fu_udev_device_set_bind_id:
158
 * @self: a #FuUdevDevice
159
 * @bind_id: a bind-id string, e.g. `pci:0:0:1`
160
 *
161
 * Sets the device ID used for binding the device, e.g. `pci:1:2:3`
162
 *
163
 * Since: 1.7.2
164
 **/
165
void
166
fu_udev_device_set_bind_id(FuUdevDevice *self, const gchar *bind_id)
167
0
{
168
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
169
170
  /* not changed */
171
0
  if (g_strcmp0(priv->bind_id, bind_id) == 0)
172
0
    return;
173
174
0
  g_free(priv->bind_id);
175
0
  priv->bind_id = g_strdup(bind_id);
176
0
  g_object_notify(G_OBJECT(self), "bind-id");
177
0
}
178
179
static void
180
fu_udev_device_set_driver(FuUdevDevice *self, const gchar *driver)
181
0
{
182
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
183
184
  /* not changed */
185
0
  if (g_strcmp0(priv->driver, driver) == 0)
186
0
    return;
187
188
0
  g_free(priv->driver);
189
0
  priv->driver = g_strdup(driver);
190
0
  g_object_notify(G_OBJECT(self), "driver");
191
0
}
192
193
/**
194
 * fu_udev_device_set_device_file:
195
 * @self: a #FuUdevDevice
196
 * @device_file: (nullable): a device path
197
 *
198
 * Sets the device file to use for reading and writing.
199
 *
200
 * Since: 1.8.7
201
 **/
202
void
203
fu_udev_device_set_device_file(FuUdevDevice *self, const gchar *device_file)
204
0
{
205
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
206
207
  /* not changed */
208
0
  if (g_strcmp0(priv->device_file, device_file) == 0)
209
0
    return;
210
211
0
  g_free(priv->device_file);
212
0
  priv->device_file = g_strdup(device_file);
213
0
  g_object_notify(G_OBJECT(self), "device-file");
214
0
}
215
216
static gchar *
217
fu_udev_device_get_symlink_target(FuUdevDevice *self, const gchar *attr, GError **error)
218
0
{
219
0
  FuDeviceEvent *event = NULL;
220
0
  g_autofree gchar *event_id = NULL;
221
0
  g_autofree gchar *fn_attr = NULL;
222
0
  g_autofree gchar *symlink_target = NULL;
223
0
  g_autofree gchar *value = NULL;
224
225
0
  if (fu_udev_device_get_sysfs_path(self) == NULL) {
226
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no sysfs path");
227
0
    return NULL;
228
0
  }
229
230
  /* need event ID */
231
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
232
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
233
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
234
0
    event_id = g_strdup_printf("GetSymlinkTarget:Attr=%s", attr);
235
0
  }
236
237
  /* emulated */
238
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
239
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
240
0
    if (event == NULL)
241
0
      return NULL;
242
0
    return g_strdup(fu_device_event_get_str(event, "Data", error));
243
0
  }
244
245
  /* save */
246
0
  if (event_id != NULL)
247
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
248
249
  /* find target */
250
0
  fn_attr = g_build_filename(fu_udev_device_get_sysfs_path(self), attr, NULL);
251
0
  symlink_target = fu_path_get_symlink_target(fn_attr, error);
252
0
  if (symlink_target == NULL)
253
0
    return NULL;
254
0
  value = g_path_get_basename(symlink_target);
255
256
  /* save response */
257
0
  if (event != NULL)
258
0
    fu_device_event_set_str(event, "Data", value);
259
260
  /* success */
261
0
  return g_steal_pointer(&value);
262
0
}
263
264
/* private */
265
void
266
fu_udev_device_set_number(FuUdevDevice *self, guint64 number)
267
0
{
268
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
269
0
  g_return_if_fail(FU_IS_UDEV_DEVICE(self));
270
0
  priv->number = number;
271
0
}
272
273
/* private */
274
void
275
fu_udev_device_set_devtype(FuUdevDevice *self, const gchar *devtype)
276
0
{
277
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
278
279
  /* not changed */
280
0
  if (g_strcmp0(priv->devtype, devtype) == 0)
281
0
    return;
282
283
0
  g_free(priv->devtype);
284
0
  priv->devtype = g_strdup(devtype);
285
0
  g_object_notify(G_OBJECT(self), "devtype");
286
0
}
287
288
/* private */
289
gboolean
290
fu_udev_device_parse_number(FuUdevDevice *self, GError **error)
291
0
{
292
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
293
0
  g_autoptr(GString) path = g_string_new(fu_udev_device_get_sysfs_path(self));
294
295
0
  if (path->len == 0)
296
0
    return TRUE;
297
0
  for (guint i = path->len - 1; i > 0; i--) {
298
0
    if (!g_ascii_isdigit(path->str[i])) {
299
0
      g_string_erase(path, 0, i + 1);
300
0
      break;
301
0
    }
302
0
  }
303
0
  if (path->len > 0) {
304
0
    if (!fu_strtoull(path->str,
305
0
         &priv->number,
306
0
         0x0,
307
0
         G_MAXUINT64,
308
0
         FU_INTEGER_BASE_AUTO,
309
0
         error))
310
0
      return FALSE;
311
0
  }
312
313
  /* success */
314
0
  return TRUE;
315
0
}
316
317
static gboolean
318
fu_udev_device_ensure_devtype_from_modalias(FuUdevDevice *self, GError **error)
319
0
{
320
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
321
0
  const gchar *devtype_modalias[] = {"mmc", "platform", NULL};
322
0
  g_autofree gchar *prop_modalias = NULL;
323
0
  g_auto(GStrv) split_modalias = NULL;
324
325
  /* only some subsystems forget to set the DEVTYPE property */
326
0
  if (!g_strv_contains(devtype_modalias, priv->subsystem))
327
0
    return TRUE;
328
329
  /* parse out subsystem:devtype */
330
0
  prop_modalias = fu_udev_device_read_property(self, "MODALIAS", error);
331
0
  if (prop_modalias == NULL)
332
0
    return FALSE;
333
0
  split_modalias = g_strsplit(prop_modalias, ":", 2);
334
0
  if (g_strv_length(split_modalias) >= 2)
335
0
    priv->devtype = g_strdup(split_modalias[1]);
336
337
  /* success */
338
0
  return TRUE;
339
0
}
340
341
static gboolean
342
fu_udev_device_probe(FuDevice *device, GError **error)
343
0
{
344
0
  FuUdevDevice *self = FU_UDEV_DEVICE(device);
345
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
346
0
  g_autofree gchar *subsystem = NULL;
347
0
  g_autofree gchar *attr_device = NULL;
348
0
  g_autofree gchar *attr_vendor = NULL;
349
350
  /* find the subsystem, driver and devtype */
351
0
  if (priv->subsystem == NULL) {
352
0
    g_autofree gchar *subsystem_tmp =
353
0
        fu_udev_device_get_symlink_target(self, "subsystem", error);
354
0
    if (subsystem_tmp == NULL) {
355
0
      g_prefix_error_literal(error, "failed to read subsystem: ");
356
0
      return FALSE;
357
0
    }
358
0
    fu_udev_device_set_subsystem(self, subsystem_tmp);
359
0
  }
360
0
  if (priv->driver == NULL)
361
0
    priv->driver = fu_udev_device_get_symlink_target(self, "driver", NULL);
362
0
  if (priv->devtype == NULL) {
363
0
    priv->devtype = fu_udev_device_read_property(self, "DEVTYPE", NULL);
364
0
    if (priv->devtype == NULL) {
365
0
      if (!fu_udev_device_ensure_devtype_from_modalias(self, error))
366
0
        return FALSE;
367
0
    }
368
0
  }
369
0
  if (priv->device_file == NULL) {
370
0
    g_autofree gchar *prop_devname =
371
0
        fu_udev_device_read_property(self, "DEVNAME", NULL);
372
0
    if (prop_devname != NULL) {
373
0
      g_autofree gchar *device_file = g_strdup_printf("/dev/%s", prop_devname);
374
0
      fu_udev_device_set_device_file(self, device_file);
375
0
    }
376
0
  }
377
378
  /* get IDs */
379
0
  attr_vendor = fu_udev_device_read_sysfs(self,
380
0
            "vendor",
381
0
            FU_UDEV_DEVICE_ATTR_READ_TIMEOUT_DEFAULT,
382
0
            NULL);
383
0
  if (attr_vendor != NULL) {
384
0
    guint64 tmp64 = 0;
385
0
    if (!fu_strtoull(attr_vendor, &tmp64, 0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, NULL)) {
386
0
      fu_device_set_vendor(device, attr_vendor);
387
0
    } else {
388
0
      fu_device_set_vid(device, (guint16)tmp64);
389
0
    }
390
0
  }
391
0
  attr_device = fu_udev_device_read_sysfs(self,
392
0
            "device",
393
0
            FU_UDEV_DEVICE_ATTR_READ_TIMEOUT_DEFAULT,
394
0
            NULL);
395
0
  if (attr_device != NULL) {
396
0
    guint64 tmp64 = 0;
397
0
    if (!fu_strtoull(attr_device, &tmp64, 0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
398
0
      return FALSE;
399
0
    fu_device_set_pid(device, (guint16)tmp64);
400
0
  }
401
402
  /* set number */
403
0
  if (fu_udev_device_get_sysfs_path(self) != NULL) {
404
0
    g_autoptr(GError) error_local = NULL;
405
0
    if (!fu_udev_device_parse_number(self, &error_local))
406
0
      g_debug("failed to convert udev number: %s", error_local->message);
407
0
  }
408
409
  /* set vendor ID */
410
0
  if (priv->subsystem != NULL)
411
0
    subsystem = g_ascii_strup(priv->subsystem, -1);
412
0
  if (subsystem != NULL)
413
0
    fu_device_build_vendor_id_u16(device, subsystem, fu_device_get_vid(device));
414
415
  /* add GUIDs in order of priority */
416
0
  if (subsystem != NULL) {
417
0
    fu_device_build_instance_id_full(device,
418
0
             FU_DEVICE_INSTANCE_FLAG_GENERIC |
419
0
                 FU_DEVICE_INSTANCE_FLAG_QUIRKS,
420
0
             NULL,
421
0
             subsystem,
422
0
             "VEN",
423
0
             NULL);
424
0
    fu_device_build_instance_id_full(device,
425
0
             FU_DEVICE_INSTANCE_FLAG_GENERIC |
426
0
                 FU_DEVICE_INSTANCE_FLAG_VISIBLE |
427
0
                 FU_DEVICE_INSTANCE_FLAG_QUIRKS,
428
0
             NULL,
429
0
             subsystem,
430
0
             "VEN",
431
0
             "DEV",
432
0
             NULL);
433
0
  }
434
435
  /* add device class */
436
0
  if (subsystem != NULL) {
437
0
    g_autofree gchar *cls =
438
0
        fu_udev_device_read_sysfs(self,
439
0
                "class",
440
0
                FU_UDEV_DEVICE_ATTR_READ_TIMEOUT_DEFAULT,
441
0
                NULL);
442
0
    g_autofree gchar *devtype = fu_udev_device_read_property(self, "DEVTYPE", NULL);
443
0
    if (cls != NULL && g_str_has_prefix(cls, "0x"))
444
0
      fu_device_add_instance_strup(device, "CLASS", cls + 2);
445
0
    else
446
0
      fu_device_add_instance_strup(device, "CLASS", cls);
447
0
    fu_device_build_instance_id_full(device,
448
0
             FU_DEVICE_INSTANCE_FLAG_GENERIC |
449
0
                 FU_DEVICE_INSTANCE_FLAG_QUIRKS,
450
0
             NULL,
451
0
             subsystem,
452
0
             "VEN",
453
0
             "CLASS",
454
0
             NULL);
455
456
    /* add devtype */
457
0
    fu_device_add_instance_strup(device, "TYPE", devtype);
458
0
    fu_device_build_instance_id_full(device,
459
0
             FU_DEVICE_INSTANCE_FLAG_GENERIC |
460
0
                 FU_DEVICE_INSTANCE_FLAG_QUIRKS,
461
0
             NULL,
462
0
             subsystem,
463
0
             "TYPE",
464
0
             NULL);
465
466
    /* add the driver */
467
0
    fu_device_add_instance_str(device, "DRIVER", priv->driver);
468
0
    fu_device_build_instance_id_full(device,
469
0
             FU_DEVICE_INSTANCE_FLAG_GENERIC |
470
0
                 FU_DEVICE_INSTANCE_FLAG_QUIRKS,
471
0
             NULL,
472
0
             subsystem,
473
0
             "DRIVER",
474
0
             NULL);
475
0
  }
476
477
  /* success */
478
0
  return TRUE;
479
0
}
480
481
/**
482
 * fu_udev_device_get_subsystem_depth:
483
 * @self: a #FuUdevDevice
484
 * @subsystem: a subsystem
485
 *
486
 * Determine how far up a chain a given device is
487
 *
488
 * Returns: unsigned integer
489
 *
490
 * Since: 2.0.0
491
 **/
492
guint
493
fu_udev_device_get_subsystem_depth(FuUdevDevice *self, const gchar *subsystem)
494
0
{
495
0
  g_autoptr(FuDevice) device_tmp = NULL;
496
497
0
  device_tmp = fu_device_get_backend_parent_with_subsystem(FU_DEVICE(self), subsystem, NULL);
498
0
  if (device_tmp == NULL)
499
0
    return 0;
500
0
  if (g_strcmp0(fu_device_get_id(device_tmp), fu_device_get_id(FU_DEVICE(self))) == 0)
501
0
    return 0;
502
0
  for (guint i = 0;; i++) {
503
0
    g_autoptr(FuDevice) parent =
504
0
        fu_device_get_backend_parent(FU_DEVICE(device_tmp), NULL);
505
0
    if (parent == NULL)
506
0
      return i;
507
0
    g_set_object(&device_tmp, parent);
508
0
  }
509
0
  return 0;
510
0
}
511
512
static void
513
fu_udev_device_probe_complete(FuDevice *device)
514
0
{
515
0
  FuUdevDevice *self = FU_UDEV_DEVICE(device);
516
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
517
0
  g_hash_table_remove_all(priv->properties);
518
0
  priv->properties_valid = FALSE;
519
0
}
520
521
static gboolean
522
fu_udev_device_unbind_driver(FuDevice *device, GError **error)
523
0
{
524
0
  FuUdevDevice *self = FU_UDEV_DEVICE(device);
525
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
526
0
  g_autofree gchar *fn = NULL;
527
0
  g_autoptr(GOutputStream) stream = NULL;
528
529
  /* emulated */
530
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED))
531
0
    return TRUE;
532
533
  /* is already unbound */
534
0
  if (fu_udev_device_get_sysfs_path(self) == NULL) {
535
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "not initialized");
536
0
    return FALSE;
537
0
  }
538
0
  fn = g_build_filename(fu_udev_device_get_sysfs_path(self), "driver", "unbind", NULL);
539
0
  if (!g_file_test(fn, G_FILE_TEST_EXISTS))
540
0
    return TRUE;
541
542
  /* write bus ID to file */
543
0
  if (!fu_udev_device_ensure_bind_id(self, error))
544
0
    return FALSE;
545
0
  stream = fu_output_stream_from_path(fn, error);
546
0
  if (stream == NULL)
547
0
    return FALSE;
548
0
  return g_output_stream_write_all(stream,
549
0
           priv->bind_id,
550
0
           strlen(priv->bind_id),
551
0
           NULL,
552
0
           NULL,
553
0
           error);
554
0
}
555
556
static gboolean
557
fu_udev_device_bind_driver(FuDevice *device,
558
         const gchar *subsystem,
559
         const gchar *driver,
560
         GError **error)
561
0
{
562
0
  FuUdevDevice *self = FU_UDEV_DEVICE(device);
563
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
564
0
  g_autofree gchar *driver_safe = g_strdup(driver);
565
0
  g_autofree gchar *fn = NULL;
566
0
  g_autoptr(GFile) file = NULL;
567
0
  g_autoptr(GOutputStream) stream = NULL;
568
569
  /* emulated */
570
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED))
571
0
    return TRUE;
572
573
  /* copy the logic from modprobe */
574
0
  g_strdelimit(driver_safe, "-", '_');
575
576
  /* driver exists */
577
0
  fn = g_strdup_printf("/sys/module/%s/drivers/%s:%s/bind",
578
0
           driver_safe,
579
0
           subsystem,
580
0
           driver_safe);
581
0
  if (!g_file_test(fn, G_FILE_TEST_EXISTS)) {
582
0
    g_set_error(error,
583
0
          FWUPD_ERROR,
584
0
          FWUPD_ERROR_NOT_SUPPORTED,
585
0
          "cannot bind with %s:%s",
586
0
          subsystem,
587
0
          driver);
588
0
    return FALSE;
589
0
  }
590
591
  /* write bus ID to file */
592
0
  if (!fu_udev_device_ensure_bind_id(self, error))
593
0
    return FALSE;
594
0
  if (priv->bind_id == NULL) {
595
0
    g_set_error(error,
596
0
          FWUPD_ERROR,
597
0
          FWUPD_ERROR_NOT_SUPPORTED,
598
0
          "bind-id not set for subsystem %s",
599
0
          priv->subsystem);
600
0
    return FALSE;
601
0
  }
602
0
  file = g_file_new_for_path(fn);
603
0
  stream =
604
0
      G_OUTPUT_STREAM(g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error));
605
0
  if (stream == NULL)
606
0
    return FALSE;
607
0
  return g_output_stream_write_all(stream,
608
0
           priv->bind_id,
609
0
           strlen(priv->bind_id),
610
0
           NULL,
611
0
           NULL,
612
0
           error);
613
0
}
614
615
static FuIoChannelOpenFlags
616
fu_udev_device_get_open_flags(FuUdevDevice *self)
617
0
{
618
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
619
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0);
620
0
  return priv->open_flags;
621
0
}
622
623
static void
624
fu_udev_device_invalidate(FuDevice *device)
625
0
{
626
0
  FuUdevDevice *self = FU_UDEV_DEVICE(device);
627
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
628
0
  priv->properties_valid = FALSE;
629
0
  g_hash_table_remove_all(priv->properties);
630
0
}
631
632
static void
633
fu_udev_device_incorporate(FuDevice *device, FuDevice *donor)
634
0
{
635
0
  FuUdevDevice *uself = FU_UDEV_DEVICE(device);
636
0
  FuUdevDevice *udonor = FU_UDEV_DEVICE(donor);
637
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(uself);
638
639
0
  g_return_if_fail(FU_IS_UDEV_DEVICE(device));
640
0
  g_return_if_fail(FU_IS_UDEV_DEVICE(donor));
641
642
0
  if (priv->device_file == NULL)
643
0
    fu_udev_device_set_device_file(uself, fu_udev_device_get_device_file(udonor));
644
0
  if (priv->subsystem == NULL)
645
0
    fu_udev_device_set_subsystem(uself, fu_udev_device_get_subsystem(udonor));
646
0
  if (priv->bind_id == NULL)
647
0
    fu_udev_device_set_bind_id(uself, fu_udev_device_get_bind_id(udonor));
648
0
  if (priv->driver == NULL)
649
0
    fu_udev_device_set_driver(uself, fu_udev_device_get_driver(udonor));
650
0
  if (priv->devtype == NULL)
651
0
    fu_udev_device_set_devtype(uself, fu_udev_device_get_devtype(udonor));
652
0
  if (priv->number == 0x0)
653
0
    fu_udev_device_set_number(uself, fu_udev_device_get_number(udonor));
654
0
  if (priv->open_flags == FU_IO_CHANNEL_OPEN_FLAG_NONE)
655
0
    priv->open_flags = fu_udev_device_get_open_flags(udonor);
656
0
}
657
658
/**
659
 * fu_udev_device_get_subsystem:
660
 * @self: a #FuUdevDevice
661
 *
662
 * Gets the device subsystem, e.g. `pci`
663
 *
664
 * Returns: a subsystem, or NULL if unset or invalid
665
 *
666
 * Since: 1.1.2
667
 **/
668
const gchar *
669
fu_udev_device_get_subsystem(FuUdevDevice *self)
670
0
{
671
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
672
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
673
0
  return priv->subsystem;
674
0
}
675
676
/**
677
 * fu_udev_device_get_bind_id:
678
 * @self: a #FuUdevDevice
679
 *
680
 * Gets the device ID used for binding the device, e.g. `pci:1:2:3`
681
 *
682
 * Returns: a bind_id, or NULL if unset or invalid
683
 *
684
 * Since: 1.7.2
685
 **/
686
const gchar *
687
fu_udev_device_get_bind_id(FuUdevDevice *self)
688
0
{
689
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
690
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
691
0
  fu_udev_device_ensure_bind_id(self, NULL);
692
0
  return priv->bind_id;
693
0
}
694
695
/**
696
 * fu_udev_device_get_driver:
697
 * @self: a #FuUdevDevice
698
 *
699
 * Gets the device driver, e.g. `psmouse`.
700
 *
701
 * Returns: a subsystem, or NULL if unset or invalid
702
 *
703
 * Since: 1.5.3
704
 **/
705
const gchar *
706
fu_udev_device_get_driver(FuUdevDevice *self)
707
0
{
708
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
709
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
710
0
  return priv->driver;
711
0
}
712
713
/**
714
 * fu_udev_device_get_device_file:
715
 * @self: a #FuUdevDevice
716
 *
717
 * Gets the device node.
718
 *
719
 * Returns: a device file, or NULL if unset
720
 *
721
 * Since: 1.3.1
722
 **/
723
const gchar *
724
fu_udev_device_get_device_file(FuUdevDevice *self)
725
0
{
726
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
727
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
728
0
  return priv->device_file;
729
0
}
730
731
/**
732
 * fu_udev_device_get_sysfs_path:
733
 * @self: a #FuUdevDevice
734
 *
735
 * Gets the device sysfs path, e.g. `/sys/devices/pci0000:00/0000:00:14.0`.
736
 *
737
 * Returns: a local path, or NULL if unset or invalid
738
 *
739
 * Since: 1.1.2
740
 **/
741
const gchar *
742
fu_udev_device_get_sysfs_path(FuUdevDevice *self)
743
0
{
744
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
745
746
0
  if (fu_device_has_private_flag(FU_DEVICE(self), FU_UDEV_DEVICE_FLAG_SYSFS_USE_PHYSICAL_ID))
747
0
    return fu_device_get_physical_id(FU_DEVICE(self));
748
0
  return fu_device_get_backend_id(FU_DEVICE(self));
749
0
}
750
751
/**
752
 * fu_udev_device_get_number:
753
 * @self: a #FuUdevDevice
754
 *
755
 * Gets the device number, if any.
756
 *
757
 * Returns: integer, 0 if the data is unavailable.
758
 *
759
 * Since: 1.5.0
760
 **/
761
guint64
762
fu_udev_device_get_number(FuUdevDevice *self)
763
0
{
764
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
765
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0);
766
0
  return priv->number;
767
0
}
768
769
static gchar *
770
fu_udev_device_get_parent_subsystems(FuUdevDevice *self)
771
0
{
772
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
773
0
  g_autoptr(GString) str = g_string_new(NULL);
774
0
  g_autoptr(FuUdevDevice) udev_device = g_object_ref(self);
775
776
  /* not true, but good enough for emulation */
777
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED))
778
0
    return g_strdup(priv->subsystem);
779
780
  /* find subsystems of self and all parent devices */
781
0
  while (TRUE) {
782
0
    g_autoptr(FuUdevDevice) parent = NULL;
783
0
    if (fu_udev_device_get_devtype(udev_device) != NULL) {
784
0
      g_string_append_printf(str,
785
0
                 "%s:%s,",
786
0
                 fu_udev_device_get_subsystem(udev_device),
787
0
                 fu_udev_device_get_devtype(udev_device));
788
0
    } else {
789
0
      g_string_append_printf(str,
790
0
                 "%s,",
791
0
                 fu_udev_device_get_subsystem(udev_device));
792
0
    }
793
0
    parent = FU_UDEV_DEVICE(
794
0
        fu_device_get_backend_parent_with_subsystem(FU_DEVICE(udev_device),
795
0
                NULL,
796
0
                NULL));
797
0
    if (parent == NULL)
798
0
      break;
799
0
    g_set_object(&udev_device, parent);
800
0
  }
801
0
  if (str->len > 0)
802
0
    g_string_truncate(str, str->len - 1);
803
0
  return g_string_free(g_steal_pointer(&str), FALSE);
804
0
}
805
806
/* private */
807
gboolean
808
fu_udev_device_match_subsystem(FuUdevDevice *self, const gchar *subsystem)
809
0
{
810
0
  g_auto(GStrv) subsys_devtype = NULL;
811
812
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
813
814
0
  if (subsystem == NULL)
815
0
    return TRUE;
816
0
  subsys_devtype = g_strsplit(subsystem, ":", 2);
817
0
  if (g_strcmp0(fu_udev_device_get_subsystem(self), subsys_devtype[0]) != 0)
818
0
    return FALSE;
819
0
  if (subsys_devtype[1] != NULL &&
820
0
      g_strcmp0(fu_udev_device_get_devtype(self), subsys_devtype[1]) != 0) {
821
0
    return FALSE;
822
0
  }
823
0
  return TRUE;
824
0
}
825
826
/* private */
827
gchar *
828
fu_udev_device_get_device_file_from_subsystem(FuUdevDevice *self,
829
                const gchar *subsystem,
830
                GError **error)
831
0
{
832
0
  const gchar *fn;
833
0
  g_autofree gchar *subsystem_dir = NULL;
834
0
  g_autoptr(GDir) dir = NULL;
835
0
  g_autoptr(GError) error_local = NULL;
836
837
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
838
0
  g_return_val_if_fail(subsystem != NULL, NULL);
839
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
840
841
0
  subsystem_dir =
842
0
      g_build_filename(fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(self)), subsystem, NULL);
843
0
  dir = g_dir_open(subsystem_dir, 0, &error_local);
844
0
  if (dir == NULL) {
845
0
    if (g_error_matches(error_local, G_FILE_ERROR_NOENT, G_FILE_ERROR_NOENT)) {
846
0
      g_set_error(error,
847
0
            FWUPD_ERROR,
848
0
            FWUPD_ERROR_NOT_FOUND,
849
0
            "failed to find subsystem directory %s",
850
0
            subsystem_dir);
851
0
      return NULL;
852
0
    }
853
0
    g_propagate_error(error, g_steal_pointer(&error_local));
854
0
    fwupd_error_convert(error);
855
0
    return NULL;
856
0
  }
857
0
  fn = g_dir_read_name(dir);
858
0
  if (fn == NULL) {
859
0
    g_set_error(error,
860
0
          FWUPD_ERROR,
861
0
          FWUPD_ERROR_NOT_FOUND,
862
0
          "failed to find subsystem device in %s",
863
0
          subsystem_dir);
864
0
    return NULL;
865
0
  }
866
0
  return g_strdup_printf("/dev/%s", fn);
867
0
}
868
869
/**
870
 * fu_udev_device_get_devpath:
871
 * @self: a #FuUdevDevice
872
 *
873
 * Sets the physical ID from the sysfs path, with the `/sys` prefixed removed.
874
 *
875
 * Returns: The udev-compatble device path, or %NULL on error.
876
 *
877
 * Since: 2.0.1
878
 **/
879
gchar *
880
fu_udev_device_get_devpath(FuUdevDevice *self)
881
0
{
882
0
  gchar *full_devpath;
883
0
  if (fu_udev_device_get_sysfs_path(self) == NULL)
884
0
    return NULL;
885
0
  full_devpath = g_strrstr(fu_udev_device_get_sysfs_path(self), "/sys");
886
0
  if (full_devpath == NULL)
887
0
    return NULL;
888
0
  return g_strdup(full_devpath + 4);
889
0
}
890
891
/**
892
 * fu_udev_device_set_physical_id:
893
 * @self: a #FuUdevDevice
894
 * @subsystems: a subsystem string, e.g. `pci,usb,scsi:scsi_target`
895
 * @error: (nullable): optional return location for an error
896
 *
897
 * Sets the physical ID from the device subsystem. Plugins should choose the
898
 * subsystem that is "deepest" in the udev tree, for instance choosing `usb`
899
 * over `pci` for a mouse device.
900
 *
901
 * The devtype can also be specified for a specific device, which is useful when the
902
 * subsystem alone is not enough to identify the physical device. e.g. ignoring the
903
 * specific LUNs for a SCSI device.
904
 *
905
 * Returns: %TRUE if the physical device was set.
906
 *
907
 * Since: 1.1.2
908
 **/
909
gboolean
910
fu_udev_device_set_physical_id(FuUdevDevice *self, const gchar *subsystems, GError **error)
911
0
{
912
0
  const gchar *subsystem = NULL;
913
0
  g_autofree gchar *physical_id = NULL;
914
0
  g_auto(GStrv) split = NULL;
915
0
  g_autoptr(FuUdevDevice) udev_device = NULL;
916
917
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
918
0
  g_return_val_if_fail(subsystems != NULL, FALSE);
919
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
920
921
  /* look for each subsystem[:devtype] in turn */
922
0
  split = g_strsplit(subsystems, ",", -1);
923
0
  for (guint i = 0; split[i] != NULL; i++) {
924
0
    g_autoptr(FuUdevDevice) device_parent = NULL;
925
926
    /* do we match */
927
0
    if (fu_udev_device_match_subsystem(self, split[i])) {
928
0
      udev_device = g_object_ref(self);
929
0
      break;
930
0
    }
931
932
    /* does a parent match? */
933
0
    device_parent = FU_UDEV_DEVICE(
934
0
        fu_device_get_backend_parent_with_subsystem(FU_DEVICE(self), split[i], NULL));
935
0
    if (device_parent != NULL) {
936
0
      udev_device = g_object_ref(device_parent);
937
0
      break;
938
0
    }
939
0
  }
940
0
  if (udev_device == NULL) {
941
0
    g_autofree gchar *str = fu_udev_device_get_parent_subsystems(self);
942
0
    g_set_error(error,
943
0
          FWUPD_ERROR,
944
0
          FWUPD_ERROR_NOT_FOUND,
945
0
          "failed to find device with subsystems %s, only got %s",
946
0
          subsystems,
947
0
          str);
948
0
    return FALSE;
949
0
  }
950
951
0
  subsystem = fu_udev_device_get_subsystem(udev_device);
952
0
  if (subsystem == NULL && fu_device_get_physical_id(FU_DEVICE(udev_device)) != NULL) {
953
0
    fu_device_set_physical_id(FU_DEVICE(self),
954
0
            fu_device_get_physical_id(FU_DEVICE(udev_device)));
955
0
    return TRUE;
956
0
  }
957
0
  if (g_strcmp0(subsystem, "pci") == 0) {
958
0
    g_autofree gchar *prop_id =
959
0
        fu_udev_device_read_property(udev_device, "PCI_SLOT_NAME", error);
960
0
    if (prop_id == NULL)
961
0
      return FALSE;
962
0
    physical_id = g_strdup_printf("PCI_SLOT_NAME=%s", prop_id);
963
0
  } else if (g_strcmp0(subsystem, "usb") == 0 || g_strcmp0(subsystem, "mmc") == 0 ||
964
0
       g_strcmp0(subsystem, "i2c") == 0 || g_strcmp0(subsystem, "platform") == 0 ||
965
0
       g_strcmp0(subsystem, "mtd") == 0 || g_strcmp0(subsystem, "block") == 0 ||
966
0
       g_strcmp0(subsystem, "gpio") == 0 || g_strcmp0(subsystem, "video4linux") == 0) {
967
0
    g_auto(GStrv) sysfs_parts =
968
0
        g_strsplit(fu_udev_device_get_sysfs_path(udev_device), "/sys", 2);
969
0
    if (sysfs_parts[1] != NULL)
970
0
      physical_id = g_strdup_printf("DEVPATH=%s", sysfs_parts[1]);
971
0
  } else if (g_strcmp0(subsystem, "hid") == 0) {
972
0
    g_autofree gchar *prop_id =
973
0
        fu_udev_device_read_property(udev_device, "HID_PHYS", error);
974
0
    if (prop_id == NULL)
975
0
      return FALSE;
976
0
    physical_id = g_strdup_printf("HID_PHYS=%s", prop_id);
977
0
  } else if (g_strcmp0(subsystem, "drm_dp_aux_dev") == 0) {
978
0
    g_autofree gchar *prop_id =
979
0
        fu_udev_device_read_property(udev_device, "DEVNAME", error);
980
0
    if (prop_id == NULL)
981
0
      return FALSE;
982
0
    physical_id = g_strdup_printf("DEVNAME=%s", prop_id);
983
0
  } else {
984
0
    g_set_error(error,
985
0
          FWUPD_ERROR,
986
0
          FWUPD_ERROR_NOT_SUPPORTED,
987
0
          "cannot handle subsystem %s",
988
0
          subsystem);
989
0
    return FALSE;
990
0
  }
991
992
  /* success */
993
0
  fu_device_set_physical_id(FU_DEVICE(self), physical_id);
994
0
  return TRUE;
995
0
}
996
997
/**
998
 * fu_udev_device_get_io_channel:
999
 * @self: a #FuUdevDevice
1000
 *
1001
 * Gets the IO channel.
1002
 *
1003
 * Returns: (transfer none): a #FuIOChannel, or %NULL if the device is not open
1004
 *
1005
 * Since: 1.9.8
1006
 **/
1007
FuIOChannel *
1008
fu_udev_device_get_io_channel(FuUdevDevice *self)
1009
0
{
1010
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1011
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
1012
0
  return priv->io_channel;
1013
0
}
1014
1015
/**
1016
 * fu_udev_device_set_io_channel:
1017
 * @self: a #FuUdevDevice
1018
 * @io_channel: a #FuIOChannel
1019
 *
1020
 * Replace the IO channel to use when the device has already been opened.
1021
 * This object will automatically unref @io_channel when fu_device_close() is called.
1022
 *
1023
 * Since: 1.9.8
1024
 **/
1025
void
1026
fu_udev_device_set_io_channel(FuUdevDevice *self, FuIOChannel *io_channel)
1027
0
{
1028
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1029
0
  g_return_if_fail(FU_IS_UDEV_DEVICE(self));
1030
0
  g_return_if_fail(FU_IS_IO_CHANNEL(io_channel));
1031
0
  g_set_object(&priv->io_channel, io_channel);
1032
0
}
1033
1034
/**
1035
 * fu_udev_device_remove_open_flag:
1036
 * @self: a #FuUdevDevice
1037
 * @flag: udev device flag, e.g. %FU_IO_CHANNEL_OPEN_FLAG_READ
1038
 *
1039
 * Removes a open flag.
1040
 *
1041
 * Since: 2.0.0
1042
 **/
1043
void
1044
fu_udev_device_remove_open_flag(FuUdevDevice *self, FuIoChannelOpenFlags flag)
1045
0
{
1046
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1047
0
  g_return_if_fail(FU_IS_UDEV_DEVICE(self));
1048
0
  priv->open_flags &= ~flag;
1049
0
}
1050
1051
/**
1052
 * fu_udev_device_add_open_flag:
1053
 * @self: a #FuUdevDevice
1054
 * @flag: udev device flag, e.g. %FU_IO_CHANNEL_OPEN_FLAG_READ
1055
 *
1056
 * Sets the parameters to use when opening the device.
1057
 *
1058
 * For example %FU_IO_CHANNEL_OPEN_FLAG_READ means that fu_device_open()
1059
 * would use `O_RDONLY` rather than `O_RDWR` which is the default.
1060
 *
1061
 * Since: 2.0.0
1062
 **/
1063
void
1064
fu_udev_device_add_open_flag(FuUdevDevice *self, FuIoChannelOpenFlags flag)
1065
0
{
1066
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1067
0
  g_return_if_fail(FU_IS_UDEV_DEVICE(self));
1068
1069
  /* already set */
1070
0
  if (priv->open_flags & flag)
1071
0
    return;
1072
0
  priv->open_flags |= flag;
1073
0
}
1074
1075
static gboolean
1076
fu_udev_device_open(FuDevice *device, GError **error)
1077
0
{
1078
0
  FuUdevDevice *self = FU_UDEV_DEVICE(device);
1079
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1080
1081
  /* emulated */
1082
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED))
1083
0
    return TRUE;
1084
1085
  /* old versions of fwupd used to start with OPEN_READ|OPEN_WRITE and then plugins
1086
   * could add more flags, or set the flags back to NONE -- detect and fixup */
1087
0
  if (priv->device_file != NULL && priv->open_flags == FU_IO_CHANNEL_OPEN_FLAG_NONE) {
1088
0
#ifndef SUPPORTED_BUILD
1089
0
    g_autofree gchar *id_display = fu_device_get_id_display(device);
1090
0
    g_critical("%s forgot to call fu_udev_device_add_open_flag() with "
1091
0
         "FU_IO_CHANNEL_OPEN_FLAG_READ and/or FU_IO_CHANNEL_OPEN_FLAG_WRITE",
1092
0
         id_display);
1093
0
#endif
1094
0
    fu_udev_device_add_open_flag(FU_UDEV_DEVICE(self), FU_IO_CHANNEL_OPEN_FLAG_READ);
1095
0
    fu_udev_device_add_open_flag(FU_UDEV_DEVICE(self), FU_IO_CHANNEL_OPEN_FLAG_WRITE);
1096
0
  }
1097
1098
  /* open device */
1099
0
  if (priv->device_file != NULL) {
1100
0
    g_autoptr(FuIOChannel) io_channel = NULL;
1101
0
    io_channel = fu_io_channel_new_file(priv->device_file, priv->open_flags, error);
1102
0
    if (io_channel == NULL)
1103
0
      return FALSE;
1104
0
    g_set_object(&priv->io_channel, io_channel);
1105
0
  }
1106
1107
  /* success */
1108
0
  return TRUE;
1109
0
}
1110
1111
static gboolean
1112
fu_udev_device_rescan(FuDevice *device, GError **error)
1113
0
{
1114
0
  if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED))
1115
0
    return TRUE;
1116
0
  fu_device_probe_invalidate(device);
1117
0
  return fu_device_probe(device, error);
1118
0
}
1119
1120
static gboolean
1121
fu_udev_device_close(FuDevice *device, GError **error)
1122
0
{
1123
0
  FuUdevDevice *self = FU_UDEV_DEVICE(device);
1124
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1125
1126
  /* emulated */
1127
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED))
1128
0
    return TRUE;
1129
1130
  /* optional */
1131
0
  if (priv->io_channel != NULL) {
1132
0
    if (!fu_io_channel_shutdown(priv->io_channel, error))
1133
0
      return FALSE;
1134
0
  }
1135
1136
  /* success */
1137
0
  return TRUE;
1138
0
}
1139
1140
/**
1141
 * fu_udev_device_reopen:
1142
 * @self: a #FuDevice
1143
 * @error: (nullable): optional return location for an error
1144
 *
1145
 * Closes and opens the device, typically used to close() and open() the device-file which is
1146
 * required by some ioctls.
1147
 *
1148
 * Returns: %TRUE for success
1149
 *
1150
 * Since: 2.0.9
1151
 **/
1152
gboolean
1153
fu_udev_device_reopen(FuUdevDevice *self, GError **error)
1154
0
{
1155
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
1156
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1157
1158
0
  if (!fu_udev_device_close(FU_DEVICE(self), error))
1159
0
    return FALSE;
1160
0
  return fu_udev_device_open(FU_DEVICE(self), error);
1161
0
}
1162
1163
/**
1164
 * fu_udev_device_ioctl_new:
1165
 * @self: a #FuUdevDevice
1166
 *
1167
 * Build a helper to control a device using a low-level request.
1168
 *
1169
 * Returns: (transfer full): a #FuIoctl, or %NULL on error
1170
 *
1171
 * Since: 2.0.2
1172
 **/
1173
FuIoctl *
1174
fu_udev_device_ioctl_new(FuUdevDevice *self)
1175
0
{
1176
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
1177
0
  return fu_ioctl_new(self);
1178
0
}
1179
1180
/* private */
1181
gboolean
1182
fu_udev_device_ioctl(FuUdevDevice *self,
1183
         gulong request,
1184
         guint8 *buf,
1185
         gsize bufsz,
1186
         gint *rc,
1187
         guint timeout,
1188
         FuIoctlFlags flags,
1189
         GError **error)
1190
0
{
1191
#ifdef HAVE_IOCTL_H
1192
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1193
  gint rc_tmp;
1194
  g_autoptr(GTimer) timer = g_timer_new();
1195
1196
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
1197
  g_return_val_if_fail(request != 0x0, FALSE);
1198
  g_return_val_if_fail(buf != NULL, FALSE);
1199
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1200
1201
  /* for fuzzing */
1202
  if (fu_device_has_private_flag(FU_DEVICE(self), FU_DEVICE_PRIVATE_FLAG_IS_FAKE)) {
1203
    memset(buf, 0x0, bufsz);
1204
    if (rc != NULL)
1205
      *rc = 0;
1206
    return TRUE;
1207
  }
1208
1209
  /* not open! */
1210
  if (priv->io_channel == NULL) {
1211
    g_autofree gchar *id_display = fu_device_get_id_display(FU_DEVICE(self));
1212
    g_set_error(error,
1213
          FWUPD_ERROR,
1214
          FWUPD_ERROR_INTERNAL,
1215
          "%s has not been opened",
1216
          id_display);
1217
    return FALSE;
1218
  }
1219
1220
  /* poll if required  up to the timeout */
1221
  do {
1222
    rc_tmp = ioctl(fu_io_channel_unix_get_fd(priv->io_channel), /* nocheck:blocked */
1223
             request,
1224
             buf);
1225
    if (rc_tmp >= 0)
1226
      break;
1227
  } while ((flags & FU_IOCTL_FLAG_RETRY) && (errno == EINTR || errno == EAGAIN) &&
1228
     g_timer_elapsed(timer, NULL) < timeout * 1000.f);
1229
  if (rc != NULL)
1230
    *rc = rc_tmp;
1231
  if (rc_tmp < 0) {
1232
#ifdef HAVE_ERRNO_H
1233
    if (errno == EPERM) {
1234
      g_set_error_literal(error,
1235
              FWUPD_ERROR,
1236
              FWUPD_ERROR_PERMISSION_DENIED,
1237
              "permission denied");
1238
      return FALSE;
1239
    }
1240
    if (errno == ENOTTY) {
1241
      g_set_error_literal(error,
1242
              FWUPD_ERROR,
1243
              FWUPD_ERROR_NOT_SUPPORTED,
1244
              "permission denied");
1245
      return FALSE;
1246
    }
1247
    g_set_error(error,
1248
          FWUPD_ERROR,
1249
          FWUPD_ERROR_INTERNAL,
1250
          "ioctl error: %s [%i]",
1251
          fwupd_strerror(errno),
1252
          errno);
1253
#else
1254
    g_set_error_literal(error,
1255
            FWUPD_ERROR,
1256
            FWUPD_ERROR_INTERNAL,
1257
            "unspecified ioctl error");
1258
#endif
1259
    return FALSE;
1260
  }
1261
1262
  /* success */
1263
  return TRUE;
1264
#else
1265
0
  g_set_error_literal(error,
1266
0
          FWUPD_ERROR,
1267
0
          FWUPD_ERROR_NOT_SUPPORTED,
1268
0
          "not supported as <sys/ioctl.h> not found");
1269
0
  return FALSE;
1270
0
#endif
1271
0
}
1272
1273
/**
1274
 * fu_udev_device_pread:
1275
 * @self: a #FuUdevDevice
1276
 * @port: offset address
1277
 * @buf: (in): data
1278
 * @bufsz: size of @buf
1279
 * @error: (nullable): optional return location for an error
1280
 *
1281
 * Read a buffer from a file descriptor at a given offset.
1282
 *
1283
 * Returns: %TRUE for success
1284
 *
1285
 * Since: 1.8.2
1286
 **/
1287
gboolean
1288
fu_udev_device_pread(FuUdevDevice *self, goffset port, guint8 *buf, gsize bufsz, GError **error)
1289
0
{
1290
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1291
0
  FuDeviceEvent *event = NULL;
1292
0
  g_autofree gchar *event_id = NULL;
1293
1294
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
1295
0
  g_return_val_if_fail(buf != NULL, FALSE);
1296
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1297
1298
  /* need event ID */
1299
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
1300
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
1301
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
1302
0
    event_id = g_strdup_printf("Pread:"
1303
0
             "Port=0x%x,"
1304
0
             "Length=0x%x",
1305
0
             (guint)port,
1306
0
             (guint)bufsz);
1307
0
  }
1308
1309
  /* emulated */
1310
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
1311
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
1312
0
    if (event == NULL)
1313
0
      return FALSE;
1314
0
    return fu_device_event_copy_data(event, "Data", buf, bufsz, NULL, error);
1315
0
  }
1316
1317
  /* save */
1318
0
  if (event_id != NULL)
1319
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
1320
1321
  /* not open! */
1322
0
  if (priv->io_channel == NULL) {
1323
0
    g_autofree gchar *id_display = fu_device_get_id_display(FU_DEVICE(self));
1324
0
    g_set_error(error,
1325
0
          FWUPD_ERROR,
1326
0
          FWUPD_ERROR_INTERNAL,
1327
0
          "%s has not been opened",
1328
0
          id_display);
1329
0
    return FALSE;
1330
0
  }
1331
1332
#ifdef HAVE_PWRITE
1333
  if (pread(fu_io_channel_unix_get_fd(priv->io_channel), buf, bufsz, port) != (gssize)bufsz) {
1334
    g_set_error(error,
1335
          G_IO_ERROR, /* nocheck:error */
1336
#ifdef HAVE_ERRNO_H
1337
          g_io_error_from_errno(errno),
1338
#else
1339
          G_IO_ERROR_FAILED, /* nocheck:error */
1340
#endif
1341
          "failed to read from port 0x%04x: %s",
1342
          (guint)port,
1343
          fwupd_strerror(errno));
1344
    fwupd_error_convert(error);
1345
    return FALSE;
1346
  }
1347
1348
  /* save response */
1349
  if (event != NULL)
1350
    fu_device_event_set_data(event, "Data", buf, bufsz);
1351
  return TRUE;
1352
#else
1353
0
  g_set_error_literal(error,
1354
0
          FWUPD_ERROR,
1355
0
          FWUPD_ERROR_NOT_SUPPORTED,
1356
0
          "Not supported as pread() is unavailable");
1357
0
  return FALSE;
1358
0
#endif
1359
0
}
1360
1361
/**
1362
 * fu_udev_device_seek:
1363
 * @self: a #FuUdevDevice
1364
 * @offset: offset address
1365
 * @error: (nullable): optional return location for an error
1366
 *
1367
 * Seeks a file descriptor to a given offset.
1368
 *
1369
 * Returns: %TRUE for success
1370
 *
1371
 * Since: 1.7.2
1372
 **/
1373
gboolean
1374
fu_udev_device_seek(FuUdevDevice *self, goffset offset, GError **error)
1375
0
{
1376
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1377
1378
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
1379
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1380
1381
  /* emulated */
1382
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
1383
0
    priv->emulated_offset = offset;
1384
0
    return TRUE;
1385
0
  }
1386
1387
  /* not open! */
1388
0
  if (priv->io_channel == NULL) {
1389
0
    g_autofree gchar *id_display = fu_device_get_id_display(FU_DEVICE(self));
1390
0
    g_set_error(error,
1391
0
          FWUPD_ERROR,
1392
0
          FWUPD_ERROR_INTERNAL,
1393
0
          "%s has not been opened",
1394
0
          id_display);
1395
0
    return FALSE;
1396
0
  }
1397
0
  return fu_io_channel_seek(priv->io_channel, offset, error);
1398
0
}
1399
1400
/**
1401
 * fu_udev_device_pwrite:
1402
 * @self: a #FuUdevDevice
1403
 * @port: offset address
1404
 * @buf: (out): data
1405
 * @bufsz: size of @data
1406
 * @error: (nullable): optional return location for an error
1407
 *
1408
 * Write a buffer to a file descriptor at a given offset.
1409
 *
1410
 * Returns: %TRUE for success
1411
 *
1412
 * Since: 1.8.2
1413
 **/
1414
gboolean
1415
fu_udev_device_pwrite(FuUdevDevice *self,
1416
          goffset port,
1417
          const guint8 *buf,
1418
          gsize bufsz,
1419
          GError **error)
1420
0
{
1421
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1422
0
  FuDeviceEvent *event = NULL;
1423
0
  g_autofree gchar *event_id = NULL;
1424
1425
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
1426
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1427
1428
  /* emulated */
1429
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) &&
1430
0
      !fu_device_check_fwupd_version(FU_DEVICE(self), "2.0.13")) {
1431
0
    return TRUE;
1432
0
  }
1433
1434
  /* need event ID */
1435
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
1436
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
1437
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
1438
0
    g_autofree gchar *data_base64 = g_base64_encode(buf, bufsz);
1439
0
    event_id = g_strdup_printf("Pwrite:"
1440
0
             "Port=0x%x,"
1441
0
             "Data=%s,"
1442
0
             "Length=0x%x",
1443
0
             (guint)port,
1444
0
             data_base64,
1445
0
             (guint)bufsz);
1446
0
  }
1447
1448
  /* emulated */
1449
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
1450
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
1451
0
    if (event == NULL)
1452
0
      return FALSE;
1453
0
    return TRUE;
1454
0
  }
1455
1456
  /* save */
1457
0
  if (event_id != NULL)
1458
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
1459
1460
  /* for fuzzing */
1461
0
  if (fu_device_has_private_flag(FU_DEVICE(self), FU_DEVICE_PRIVATE_FLAG_IS_FAKE))
1462
0
    return TRUE;
1463
1464
  /* not open! */
1465
0
  if (priv->io_channel == NULL) {
1466
0
    g_autofree gchar *id_display = fu_device_get_id_display(FU_DEVICE(self));
1467
0
    g_set_error(error,
1468
0
          FWUPD_ERROR,
1469
0
          FWUPD_ERROR_INTERNAL,
1470
0
          "%s has not been opened",
1471
0
          id_display);
1472
0
    return FALSE;
1473
0
  }
1474
1475
#ifdef HAVE_PWRITE
1476
  if (pwrite(fu_io_channel_unix_get_fd(priv->io_channel), buf, bufsz, port) !=
1477
      (gssize)bufsz) {
1478
    g_set_error(error,
1479
          G_IO_ERROR, /* nocheck:error */
1480
#ifdef HAVE_ERRNO_H
1481
          g_io_error_from_errno(errno),
1482
#else
1483
          G_IO_ERROR_FAILED, /* nocheck:blocked */
1484
#endif
1485
          "failed to write to port %04x: %s",
1486
          (guint)port,
1487
          fwupd_strerror(errno));
1488
    fwupd_error_convert(error);
1489
    return FALSE;
1490
  }
1491
1492
  /* save response */
1493
  if (event != NULL)
1494
    fu_device_event_set_data(event, "Data", buf, bufsz);
1495
1496
  /* success */
1497
  return TRUE;
1498
#else
1499
0
  g_set_error_literal(error,
1500
0
          FWUPD_ERROR,
1501
0
          FWUPD_ERROR_NOT_SUPPORTED,
1502
0
          "Not supported as pwrite() is unavailable");
1503
0
  return FALSE;
1504
0
#endif
1505
0
}
1506
1507
/**
1508
 * fu_udev_device_read:
1509
 * @self: a #FuUdevDevice
1510
 * @buf: (in): data
1511
 * @bufsz: size of @buf
1512
 * @bytes_read: (out) (nullable): data written to @buf
1513
 * @timeout_ms: timeout in ms
1514
 * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT
1515
 * @error: (nullable): optional return location for an error
1516
 *
1517
 * Read a buffer from a file descriptor.
1518
 *
1519
 * Returns: %TRUE for success
1520
 *
1521
 * Since: 2.0.4
1522
 **/
1523
gboolean
1524
fu_udev_device_read(FuUdevDevice *self,
1525
        guint8 *buf,
1526
        gsize bufsz,
1527
        gsize *bytes_read,
1528
        guint timeout_ms,
1529
        FuIoChannelFlags flags,
1530
        GError **error)
1531
0
{
1532
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1533
0
  FuDeviceEvent *event = NULL;
1534
0
  gsize buflen_tmp = 0;
1535
0
  g_autofree gchar *event_id = NULL;
1536
0
  g_autoptr(GError) error_local = NULL;
1537
1538
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
1539
0
  g_return_val_if_fail(buf != NULL, FALSE);
1540
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1541
1542
  /* need event ID */
1543
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
1544
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
1545
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
1546
    /* the created value is 2025-10-24T15:32:39.198922Z i.e. just bnr-dp-0.13.zip */
1547
0
    if (fu_device_check_fwupd_version(FU_DEVICE(self), "2.1.2") ||
1548
0
        fu_device_get_created(FU_DEVICE(self)) == 0x68FB9C17) {
1549
0
      event_id = g_strdup_printf("Read:Length=0x%x,Offset=0x%x",
1550
0
               (guint)bufsz,
1551
0
               (guint)priv->emulated_offset);
1552
0
    } else {
1553
0
      event_id = g_strdup_printf("Read:Length=0x%x", (guint)bufsz);
1554
0
    }
1555
0
  }
1556
1557
  /* emulated */
1558
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
1559
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
1560
0
    if (event == NULL)
1561
0
      return FALSE;
1562
0
    if (!fu_device_event_check_error(event, error))
1563
0
      return FALSE;
1564
0
    return fu_device_event_copy_data(event, "Data", buf, bufsz, bytes_read, error);
1565
0
  }
1566
1567
  /* save */
1568
0
  if (event_id != NULL)
1569
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
1570
1571
  /* for fuzzing */
1572
0
  if (fu_device_has_private_flag(FU_DEVICE(self), FU_DEVICE_PRIVATE_FLAG_IS_FAKE)) {
1573
0
    memset(buf, 0x0, bufsz);
1574
0
    if (bytes_read != NULL)
1575
0
      *bytes_read = bufsz;
1576
0
    return TRUE;
1577
0
  }
1578
1579
  /* not open! */
1580
0
  if (priv->io_channel == NULL) {
1581
0
    g_autofree gchar *id_display = fu_device_get_id_display(FU_DEVICE(self));
1582
0
    g_set_error(error,
1583
0
          FWUPD_ERROR,
1584
0
          FWUPD_ERROR_INTERNAL,
1585
0
          "%s has not been opened",
1586
0
          id_display);
1587
0
    return FALSE;
1588
0
  }
1589
0
  if (!fu_io_channel_read_raw(priv->io_channel,
1590
0
            buf,
1591
0
            bufsz,
1592
0
            &buflen_tmp,
1593
0
            timeout_ms,
1594
0
            flags,
1595
0
            &error_local)) {
1596
0
    if (event != NULL)
1597
0
      fu_device_event_set_error(event, error_local);
1598
0
    g_propagate_error(error, g_steal_pointer(&error_local));
1599
0
    return FALSE;
1600
0
  }
1601
0
  if (bytes_read != NULL)
1602
0
    *bytes_read = buflen_tmp;
1603
1604
  /* save response */
1605
0
  if (event != NULL)
1606
0
    fu_device_event_set_data(event, "Data", buf, buflen_tmp);
1607
0
  return TRUE;
1608
0
}
1609
1610
/**
1611
 * fu_udev_device_read_bytes:
1612
 * @self: a #FuUdevDevice
1613
 * @count: bytes to read
1614
 * @timeout_ms: timeout in ms
1615
 * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT
1616
 * @error: (nullable): optional return location for an error
1617
 *
1618
 * Read a buffer from a file descriptor.
1619
 *
1620
 * Returns: (transfer full): A #GBytes, or %NULL
1621
 *
1622
 * Since: 2.0.7
1623
 **/
1624
GBytes *
1625
fu_udev_device_read_bytes(FuUdevDevice *self,
1626
        gsize count,
1627
        guint timeout_ms,
1628
        FuIoChannelFlags flags,
1629
        GError **error)
1630
0
{
1631
0
  gsize bytes_read = 0;
1632
0
  g_autofree guint8 *buf = NULL;
1633
1634
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
1635
0
  g_return_val_if_fail(count > 0, NULL);
1636
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
1637
1638
0
  buf = g_malloc0(count);
1639
0
  if (!fu_udev_device_read(self, buf, count, &bytes_read, timeout_ms, flags, error))
1640
0
    return NULL;
1641
0
  return g_bytes_new(buf, bytes_read);
1642
0
}
1643
1644
/**
1645
 * fu_udev_device_read_byte_array:
1646
 * @self: a #FuUdevDevice
1647
 * @count: bytes to read
1648
 * @timeout_ms: timeout in ms
1649
 * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT
1650
 * @error: (nullable): optional return location for an error
1651
 *
1652
 * Read a buffer from a file descriptor.
1653
 *
1654
 * Returns: (transfer full): A #GByteArray, or %NULL
1655
 *
1656
 * Since: 2.0.18
1657
 **/
1658
GByteArray *
1659
fu_udev_device_read_byte_array(FuUdevDevice *self,
1660
             gsize count,
1661
             guint timeout_ms,
1662
             FuIoChannelFlags flags,
1663
             GError **error)
1664
0
{
1665
0
  gsize bytes_read = 0;
1666
0
  g_autoptr(GByteArray) buf = g_byte_array_new();
1667
1668
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
1669
0
  g_return_val_if_fail(count > 0, NULL);
1670
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
1671
1672
0
  fu_byte_array_set_size(buf, count, 0x0);
1673
0
  if (!fu_udev_device_read(self, buf->data, buf->len, &bytes_read, timeout_ms, flags, error))
1674
0
    return NULL;
1675
0
  g_byte_array_set_size(buf, bytes_read);
1676
0
  return g_steal_pointer(&buf);
1677
0
}
1678
1679
/**
1680
 * fu_udev_device_write:
1681
 * @self: a #FuUdevDevice
1682
 * @buf: (out): data
1683
 * @bufsz: size of @data
1684
 * @timeout_ms: timeout in ms
1685
 * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT
1686
 * @error: (nullable): optional return location for an error
1687
 *
1688
 * Write a buffer to a file descriptor.
1689
 *
1690
 * Returns: %TRUE for success
1691
 *
1692
 * Since: 2.0.4
1693
 **/
1694
gboolean
1695
fu_udev_device_write(FuUdevDevice *self,
1696
         const guint8 *buf,
1697
         gsize bufsz,
1698
         guint timeout_ms,
1699
         FuIoChannelFlags flags,
1700
         GError **error)
1701
0
{
1702
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
1703
0
  FuDeviceEvent *event = NULL;
1704
0
  g_autofree gchar *event_id = NULL;
1705
0
  g_autoptr(GError) error_local = NULL;
1706
1707
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
1708
0
  g_return_val_if_fail(buf != NULL, FALSE);
1709
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1710
1711
  /* sanity check */
1712
0
  if ((priv->open_flags & FU_IO_CHANNEL_OPEN_FLAG_WRITE) == 0) {
1713
0
    g_critical("trying to write without writable device-file; "
1714
0
         "use fu_udev_device_add_open_flag(self, FU_IO_CHANNEL_OPEN_FLAG_WRITE)");
1715
0
  }
1716
1717
  /* emulated */
1718
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
1719
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
1720
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
1721
0
    g_autofree gchar *data_base64 = g_base64_encode(buf, bufsz);
1722
0
    event_id = g_strdup_printf("Write:Data=%s,Length=0x%x", data_base64, (guint)bufsz);
1723
0
  }
1724
1725
  /* emulated */
1726
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
1727
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
1728
0
    if (event == NULL)
1729
0
      return FALSE;
1730
0
    if (!fu_device_event_check_error(event, error))
1731
0
      return FALSE;
1732
0
    return event != NULL;
1733
0
  }
1734
1735
  /* save */
1736
0
  if (event_id != NULL)
1737
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
1738
1739
  /* for fuzzing */
1740
0
  if (fu_device_has_private_flag(FU_DEVICE(self), FU_DEVICE_PRIVATE_FLAG_IS_FAKE))
1741
0
    return TRUE;
1742
1743
  /* not open! */
1744
0
  if (priv->io_channel == NULL) {
1745
0
    g_autofree gchar *id_display = fu_device_get_id_display(FU_DEVICE(self));
1746
0
    g_set_error(error,
1747
0
          FWUPD_ERROR,
1748
0
          FWUPD_ERROR_INTERNAL,
1749
0
          "%s has not been opened",
1750
0
          id_display);
1751
0
    return FALSE;
1752
0
  }
1753
0
  if (!fu_io_channel_write_raw(priv->io_channel,
1754
0
             buf,
1755
0
             bufsz,
1756
0
             timeout_ms,
1757
0
             flags,
1758
0
             &error_local)) {
1759
0
    if (event != NULL)
1760
0
      fu_device_event_set_error(event, error_local);
1761
0
    g_propagate_error(error, g_steal_pointer(&error_local));
1762
0
    return FALSE;
1763
0
  }
1764
1765
  /* success */
1766
0
  return TRUE;
1767
0
}
1768
1769
/**
1770
 * fu_udev_device_write_bytes:
1771
 * @self: a #FuUdevDevice
1772
 * @blob: a #GBytes
1773
 * @timeout_ms: timeout in ms
1774
 * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT
1775
 * @error: (nullable): optional return location for an error
1776
 *
1777
 * Write a buffer to a file descriptor.
1778
 *
1779
 * Returns: %TRUE for success
1780
 *
1781
 * Since: 2.0.7
1782
 **/
1783
gboolean
1784
fu_udev_device_write_bytes(FuUdevDevice *self,
1785
         GBytes *blob,
1786
         guint timeout_ms,
1787
         FuIoChannelFlags flags,
1788
         GError **error)
1789
0
{
1790
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
1791
0
  g_return_val_if_fail(blob != NULL, FALSE);
1792
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1793
0
  return fu_udev_device_write(self,
1794
0
            g_bytes_get_data(blob, NULL),
1795
0
            g_bytes_get_size(blob),
1796
0
            timeout_ms,
1797
0
            flags,
1798
0
            error);
1799
0
}
1800
1801
/**
1802
 * fu_udev_device_write_byte_array:
1803
 * @self: a #FuUdevDevice
1804
 * @buf: a #GByteArray
1805
 * @timeout_ms: timeout in ms
1806
 * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT
1807
 * @error: (nullable): optional return location for an error
1808
 *
1809
 * Write a buffer to a file descriptor.
1810
 *
1811
 * Returns: %TRUE for success
1812
 *
1813
 * Since: 2.0.18
1814
 **/
1815
gboolean
1816
fu_udev_device_write_byte_array(FuUdevDevice *self,
1817
        GByteArray *buf,
1818
        guint timeout_ms,
1819
        FuIoChannelFlags flags,
1820
        GError **error)
1821
0
{
1822
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
1823
0
  g_return_val_if_fail(buf != NULL, FALSE);
1824
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1825
0
  return fu_udev_device_write(self, buf->data, buf->len, timeout_ms, flags, error);
1826
0
}
1827
1828
/**
1829
 * fu_udev_device_list_sysfs:
1830
 * @self: a #FuUdevDevice
1831
 * @error: (nullable): optional return location for an error
1832
 *
1833
 * Lists all the sysfs attributes.
1834
 *
1835
 * Returns: (transfer container) (element-type utf8): basenames, or %NULL
1836
 *
1837
 * Since: 2.0.9
1838
 **/
1839
GPtrArray *
1840
fu_udev_device_list_sysfs(FuUdevDevice *self, GError **error)
1841
0
{
1842
0
  FuDeviceEvent *event = NULL;
1843
0
  const gchar *basename;
1844
0
  g_autofree gchar *event_id = NULL;
1845
0
  g_autoptr(GDir) dir = NULL;
1846
0
  g_autoptr(GPtrArray) attrs = g_ptr_array_new_with_free_func(g_free);
1847
1848
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
1849
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
1850
1851
  /* need event ID */
1852
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
1853
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
1854
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
1855
0
    event_id = g_strdup("ListAttr");
1856
0
  }
1857
1858
  /* emulated */
1859
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
1860
0
    const gchar *value;
1861
0
    g_auto(GStrv) attrs_strv = NULL;
1862
1863
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
1864
0
    if (event == NULL)
1865
0
      return NULL;
1866
0
    value = fu_device_event_get_str(event, "Data", error);
1867
0
    if (value == NULL)
1868
0
      return NULL;
1869
0
    attrs_strv = g_strsplit(value, "\n", -1);
1870
0
    for (guint i = 0; attrs_strv[i] != NULL; i++)
1871
0
      g_ptr_array_add(attrs, g_strdup(attrs_strv[i]));
1872
0
    return g_steal_pointer(&attrs);
1873
0
  }
1874
1875
  /* save */
1876
0
  if (event_id != NULL)
1877
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
1878
1879
  /* list the files and directories */
1880
0
  if (fu_udev_device_get_sysfs_path(self) == NULL) {
1881
0
    g_set_error_literal(error,
1882
0
            FWUPD_ERROR,
1883
0
            FWUPD_ERROR_INTERNAL,
1884
0
            "sysfs_path undefined");
1885
0
    return NULL;
1886
0
  }
1887
0
  dir = g_dir_open(fu_udev_device_get_sysfs_path(self), 0, error);
1888
0
  if (dir == NULL) {
1889
0
    fwupd_error_convert(error);
1890
0
    return NULL;
1891
0
  }
1892
0
  while ((basename = g_dir_read_name(dir)) != NULL)
1893
0
    g_ptr_array_add(attrs, g_strdup(basename));
1894
1895
  /* save for emulation */
1896
0
  if (event != NULL) {
1897
0
    g_autofree gchar *value = fu_strjoin("\n", attrs);
1898
0
    fu_device_event_set_str(event, "Data", value);
1899
0
  }
1900
1901
  /* success */
1902
0
  return g_steal_pointer(&attrs);
1903
0
}
1904
1905
/**
1906
 * fu_udev_device_read_sysfs:
1907
 * @self: a #FuUdevDevice
1908
 * @attr: sysfs attribute name
1909
 * @timeout_ms: IO timeout in milliseconds
1910
 * @error: (nullable): optional return location for an error
1911
 *
1912
 * Reads data from a sysfs attribute, removing any newline trailing chars.
1913
 *
1914
 * Returns: (transfer full): string value, or %NULL
1915
 *
1916
 * Since: 2.0.0
1917
 **/
1918
gchar *
1919
fu_udev_device_read_sysfs(FuUdevDevice *self, const gchar *attr, guint timeout_ms, GError **error)
1920
0
{
1921
0
  FuDeviceEvent *event = NULL;
1922
0
  g_autofree gchar *event_id = NULL;
1923
0
  g_autofree gchar *path = NULL;
1924
0
  g_autofree gchar *value = NULL;
1925
0
  g_autoptr(FuIOChannel) io_channel = NULL;
1926
0
  g_autoptr(GByteArray) buf = NULL;
1927
1928
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
1929
0
  g_return_val_if_fail(attr != NULL, NULL);
1930
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
1931
1932
  /* need event ID */
1933
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
1934
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
1935
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
1936
0
    event_id = g_strdup_printf("ReadAttr:Attr=%s", attr);
1937
0
  }
1938
1939
  /* emulated */
1940
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
1941
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
1942
0
    if (event == NULL)
1943
0
      return NULL;
1944
0
    return g_strdup(fu_device_event_get_str(event, "Data", error));
1945
0
  }
1946
1947
  /* save */
1948
0
  if (event_id != NULL)
1949
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
1950
1951
  /* open the file */
1952
0
  if (fu_udev_device_get_sysfs_path(self) == NULL) {
1953
0
    g_set_error_literal(error,
1954
0
            FWUPD_ERROR,
1955
0
            FWUPD_ERROR_INTERNAL,
1956
0
            "sysfs_path undefined");
1957
0
    return NULL;
1958
0
  }
1959
0
  path = g_build_filename(fu_udev_device_get_sysfs_path(self), attr, NULL);
1960
0
  io_channel = fu_io_channel_new_file(path, FU_IO_CHANNEL_OPEN_FLAG_READ, error);
1961
0
  if (io_channel == NULL)
1962
0
    return NULL;
1963
0
  buf = fu_io_channel_read_byte_array(io_channel,
1964
0
              -1,
1965
0
              timeout_ms,
1966
0
              FU_IO_CHANNEL_FLAG_NONE,
1967
0
              error);
1968
0
  if (buf == NULL) {
1969
0
    g_prefix_error(error, "failed read of %s: ", path);
1970
0
    return NULL;
1971
0
  }
1972
0
  if (!g_utf8_validate((const gchar *)buf->data, buf->len, NULL)) {
1973
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "non UTF-8 data");
1974
0
    return NULL;
1975
0
  }
1976
1977
  /* save response */
1978
0
  value = g_strndup((const gchar *)buf->data, buf->len);
1979
1980
  /* remove the trailing newline */
1981
0
  if (buf->len > 0) {
1982
0
    if (value[buf->len - 1] == '\n')
1983
0
      value[buf->len - 1] = '\0';
1984
0
  }
1985
1986
  /* save for emulation */
1987
0
  if (event != NULL)
1988
0
    fu_device_event_set_str(event, "Data", value);
1989
1990
  /* success */
1991
0
  return g_steal_pointer(&value);
1992
0
}
1993
1994
/**
1995
 * fu_udev_device_read_sysfs_bytes:
1996
 * @self: a #FuUdevDevice
1997
 * @attr: sysfs attribute name
1998
 * @count: maximum bytes to read, or -1 for no limit
1999
 * @timeout_ms: IO timeout in milliseconds
2000
 * @error: (nullable): optional return location for an error
2001
 *
2002
 * Reads raw data from a sysfs attribute.
2003
 *
2004
 * Returns: (transfer full): string value, or %NULL
2005
 *
2006
 * Since: 2.0.1
2007
 **/
2008
GBytes *
2009
fu_udev_device_read_sysfs_bytes(FuUdevDevice *self,
2010
        const gchar *attr,
2011
        gssize count,
2012
        guint timeout_ms,
2013
        GError **error)
2014
0
{
2015
0
  FuDeviceEvent *event = NULL;
2016
0
  g_autofree gchar *event_id = NULL;
2017
0
  g_autofree gchar *path = NULL;
2018
0
  g_autoptr(FuIOChannel) io_channel = NULL;
2019
0
  g_autoptr(GBytes) blob = NULL;
2020
2021
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
2022
0
  g_return_val_if_fail(attr != NULL, NULL);
2023
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
2024
2025
  /* need event ID */
2026
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
2027
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
2028
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
2029
0
    event_id = g_strdup_printf("ReadAttr:Attr=%s", attr);
2030
0
  }
2031
2032
  /* emulated */
2033
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
2034
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
2035
0
    if (event == NULL)
2036
0
      return NULL;
2037
0
    return fu_device_event_get_bytes(event, "Data", error);
2038
0
  }
2039
2040
  /* save */
2041
0
  if (event_id != NULL)
2042
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
2043
2044
  /* open the file */
2045
0
  if (fu_udev_device_get_sysfs_path(self) == NULL) {
2046
0
    g_set_error_literal(error,
2047
0
            FWUPD_ERROR,
2048
0
            FWUPD_ERROR_INTERNAL,
2049
0
            "sysfs_path undefined");
2050
0
    return NULL;
2051
0
  }
2052
0
  path = g_build_filename(fu_udev_device_get_sysfs_path(self), attr, NULL);
2053
0
  io_channel = fu_io_channel_new_file(path, FU_IO_CHANNEL_OPEN_FLAG_READ, error);
2054
0
  if (io_channel == NULL)
2055
0
    return NULL;
2056
0
  blob =
2057
0
      fu_io_channel_read_bytes(io_channel, count, timeout_ms, FU_IO_CHANNEL_FLAG_NONE, error);
2058
0
  if (blob == NULL) {
2059
0
    g_prefix_error(error, "failed read of %s: ", path);
2060
0
    return NULL;
2061
0
  }
2062
2063
  /* save for emulation */
2064
0
  if (event != NULL)
2065
0
    fu_device_event_set_bytes(event, "Data", blob);
2066
2067
  /* success */
2068
0
  return g_steal_pointer(&blob);
2069
0
}
2070
2071
/**
2072
 * fu_udev_device_write_sysfs:
2073
 * @self: a #FuUdevDevice
2074
 * @attr: sysfs attribute name
2075
 * @val: data to write into the attribute
2076
 * @timeout_ms: IO timeout in milliseconds
2077
 * @error: (nullable): optional return location for an error
2078
 *
2079
 * Writes data into a sysfs attribute
2080
 *
2081
 * Returns: %TRUE for success
2082
 *
2083
 * Since: 2.0.0
2084
 **/
2085
gboolean
2086
fu_udev_device_write_sysfs(FuUdevDevice *self,
2087
         const gchar *attr,
2088
         const gchar *val,
2089
         guint timeout_ms,
2090
         GError **error)
2091
0
{
2092
0
  FuDeviceEvent *event = NULL;
2093
0
  g_autofree gchar *event_id = NULL;
2094
0
  g_autofree gchar *path = NULL;
2095
0
  g_autoptr(FuIOChannel) io_channel = NULL;
2096
2097
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
2098
0
  g_return_val_if_fail(attr != NULL, FALSE);
2099
0
  g_return_val_if_fail(val != NULL, FALSE);
2100
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
2101
2102
  /* need event ID */
2103
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
2104
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
2105
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
2106
0
    event_id = g_strdup_printf("WriteAttr:Attr=%s,Data=%s", attr, val);
2107
0
  }
2108
2109
  /* emulated */
2110
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
2111
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
2112
0
    return event != NULL;
2113
0
  }
2114
2115
  /* for fuzzing */
2116
0
  if (fu_device_has_private_flag(FU_DEVICE(self), FU_DEVICE_PRIVATE_FLAG_IS_FAKE))
2117
0
    return TRUE;
2118
2119
  /* open the file */
2120
0
  if (fu_udev_device_get_sysfs_path(self) == NULL) {
2121
0
    g_set_error_literal(error,
2122
0
            FWUPD_ERROR,
2123
0
            FWUPD_ERROR_INTERNAL,
2124
0
            "sysfs_path undefined");
2125
0
    return FALSE;
2126
0
  }
2127
0
  path = g_build_filename(fu_udev_device_get_sysfs_path(self), attr, NULL);
2128
0
  io_channel = fu_io_channel_new_file(path, FU_IO_CHANNEL_OPEN_FLAG_WRITE, error);
2129
0
  if (io_channel == NULL)
2130
0
    return FALSE;
2131
2132
  /* save */
2133
0
  if (event_id != NULL)
2134
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
2135
0
  if (!fu_io_channel_write_raw(io_channel,
2136
0
             (const guint8 *)val,
2137
0
             strlen(val),
2138
0
             timeout_ms,
2139
0
             FU_IO_CHANNEL_FLAG_NONE,
2140
0
             error)) {
2141
0
    g_prefix_error(error, "failed write of %s: ", path);
2142
0
    return FALSE;
2143
0
  }
2144
2145
  /* success */
2146
0
  return TRUE;
2147
0
}
2148
2149
/**
2150
 * fu_udev_device_write_sysfs_byte_array:
2151
 * @self: a #FuUdevDevice
2152
 * @attr: sysfs attribute name
2153
 * @buf: data to write into the attribute
2154
 * @timeout_ms: IO timeout in milliseconds
2155
 * @error: (nullable): optional return location for an error
2156
 *
2157
 * Writes raw data into a sysfs attribute.
2158
 *
2159
 * Returns: %TRUE for success
2160
 *
2161
 * Since: 2.0.1
2162
 **/
2163
gboolean
2164
fu_udev_device_write_sysfs_byte_array(FuUdevDevice *self,
2165
              const gchar *attr,
2166
              GByteArray *buf,
2167
              guint timeout_ms,
2168
              GError **error)
2169
0
{
2170
0
  FuDeviceEvent *event = NULL;
2171
0
  g_autofree gchar *event_id = NULL;
2172
0
  g_autofree gchar *path = NULL;
2173
0
  g_autoptr(FuIOChannel) io_channel = NULL;
2174
2175
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
2176
0
  g_return_val_if_fail(attr != NULL, FALSE);
2177
0
  g_return_val_if_fail(buf != NULL, FALSE);
2178
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
2179
2180
  /* need event ID */
2181
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
2182
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
2183
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
2184
0
    g_autofree gchar *buf_base64 = g_base64_encode(buf->data, buf->len);
2185
0
    event_id = g_strdup_printf("WriteAttr:Attr=%s,Data=%s", attr, buf_base64);
2186
0
  }
2187
2188
  /* emulated */
2189
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
2190
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
2191
0
    return event != NULL;
2192
0
  }
2193
2194
  /* for fuzzing */
2195
0
  if (fu_device_has_private_flag(FU_DEVICE(self), FU_DEVICE_PRIVATE_FLAG_IS_FAKE))
2196
0
    return TRUE;
2197
2198
  /* open the file */
2199
0
  if (fu_udev_device_get_sysfs_path(self) == NULL) {
2200
0
    g_set_error_literal(error,
2201
0
            FWUPD_ERROR,
2202
0
            FWUPD_ERROR_INTERNAL,
2203
0
            "sysfs_path undefined");
2204
0
    return FALSE;
2205
0
  }
2206
0
  path = g_build_filename(fu_udev_device_get_sysfs_path(self), attr, NULL);
2207
0
  io_channel = fu_io_channel_new_file(path, FU_IO_CHANNEL_OPEN_FLAG_WRITE, error);
2208
0
  if (io_channel == NULL)
2209
0
    return FALSE;
2210
2211
  /* save */
2212
0
  if (event_id != NULL)
2213
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
2214
0
  if (!fu_io_channel_write_byte_array(io_channel,
2215
0
              buf,
2216
0
              timeout_ms,
2217
0
              FU_IO_CHANNEL_FLAG_NONE,
2218
0
              error)) {
2219
0
    g_prefix_error(error, "failed write of %s: ", path);
2220
0
    return FALSE;
2221
0
  }
2222
2223
  /* success */
2224
0
  return TRUE;
2225
0
}
2226
2227
/**
2228
 * fu_udev_device_write_sysfs_bytes:
2229
 * @self: a #FuUdevDevice
2230
 * @attr: sysfs attribute name
2231
 * @blob: data to write into the attribute
2232
 * @timeout_ms: IO timeout in milliseconds
2233
 * @error: (nullable): optional return location for an error
2234
 *
2235
 * Writes raw data into a sysfs attribute.
2236
 *
2237
 * Returns: %TRUE for success
2238
 *
2239
 * Since: 2.0.1
2240
 **/
2241
gboolean
2242
fu_udev_device_write_sysfs_bytes(FuUdevDevice *self,
2243
         const gchar *attr,
2244
         GBytes *blob,
2245
         guint timeout_ms,
2246
         GError **error)
2247
0
{
2248
0
  FuDeviceEvent *event = NULL;
2249
0
  g_autofree gchar *event_id = NULL;
2250
0
  g_autofree gchar *path = NULL;
2251
0
  g_autoptr(FuIOChannel) io_channel = NULL;
2252
2253
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE);
2254
0
  g_return_val_if_fail(attr != NULL, FALSE);
2255
0
  g_return_val_if_fail(blob != NULL, FALSE);
2256
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
2257
2258
  /* need event ID */
2259
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
2260
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
2261
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
2262
0
    g_autofree gchar *buf_base64 =
2263
0
        g_base64_encode(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob));
2264
0
    event_id = g_strdup_printf("WriteAttr:Attr=%s,Data=%s", attr, buf_base64);
2265
0
  }
2266
2267
  /* emulated */
2268
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
2269
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
2270
0
    return event != NULL;
2271
0
  }
2272
2273
  /* for fuzzing */
2274
0
  if (fu_device_has_private_flag(FU_DEVICE(self), FU_DEVICE_PRIVATE_FLAG_IS_FAKE))
2275
0
    return TRUE;
2276
2277
  /* open the file */
2278
0
  if (fu_udev_device_get_sysfs_path(self) == NULL) {
2279
0
    g_set_error_literal(error,
2280
0
            FWUPD_ERROR,
2281
0
            FWUPD_ERROR_INTERNAL,
2282
0
            "sysfs_path undefined");
2283
0
    return FALSE;
2284
0
  }
2285
0
  path = g_build_filename(fu_udev_device_get_sysfs_path(self), attr, NULL);
2286
0
  io_channel = fu_io_channel_new_file(path, FU_IO_CHANNEL_OPEN_FLAG_WRITE, error);
2287
0
  if (io_channel == NULL)
2288
0
    return FALSE;
2289
2290
  /* save */
2291
0
  if (event_id != NULL)
2292
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
2293
0
  if (!fu_io_channel_write_bytes(io_channel,
2294
0
               blob,
2295
0
               timeout_ms,
2296
0
               FU_IO_CHANNEL_FLAG_NONE,
2297
0
               error)) {
2298
0
    g_prefix_error(error, "failed write of %s: ", path);
2299
0
    return FALSE;
2300
0
  }
2301
2302
  /* success */
2303
0
  return TRUE;
2304
0
}
2305
2306
/**
2307
 * fu_udev_device_get_devtype:
2308
 * @self: a #FuUdevDevice
2309
 *
2310
 * Returns the Udev device type
2311
 *
2312
 * Returns: device type specified in the uevent
2313
 *
2314
 * Since: 1.4.5
2315
 **/
2316
const gchar *
2317
fu_udev_device_get_devtype(FuUdevDevice *self)
2318
0
{
2319
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
2320
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
2321
0
  return priv->devtype;
2322
0
}
2323
2324
/**
2325
 * fu_udev_device_get_subsystem_devtype:
2326
 * @self: a #FuUdevDevice
2327
 *
2328
 * Returns the Udev subsystem and device type, as a string.
2329
 *
2330
 * Returns: (transfer full): `subsystem:devtype`, or just `subsystem` if the latter is unset
2331
 *
2332
 * Since: 2.0.2
2333
 **/
2334
gchar *
2335
fu_udev_device_get_subsystem_devtype(FuUdevDevice *self)
2336
0
{
2337
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
2338
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
2339
0
  if (priv->devtype != NULL)
2340
0
    return g_strdup_printf("%s:%s", priv->subsystem, priv->devtype);
2341
0
  return g_strdup(priv->subsystem);
2342
0
}
2343
2344
/* private */
2345
void
2346
fu_udev_device_add_property(FuUdevDevice *self, const gchar *key, const gchar *value)
2347
0
{
2348
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
2349
0
  g_return_if_fail(FU_IS_UDEV_DEVICE(self));
2350
0
  g_return_if_fail(key != NULL);
2351
2352
  /* this are explicit properties too */
2353
0
  if (g_strcmp0(key, "DEVNAME") == 0)
2354
0
    fu_udev_device_set_device_file(self, value);
2355
0
  if (g_strcmp0(key, "DEVTYPE") == 0)
2356
0
    fu_udev_device_set_devtype(self, value);
2357
2358
0
  g_hash_table_insert(priv->properties, g_strdup(key), g_strdup(value));
2359
0
}
2360
2361
/**
2362
 * fu_udev_device_read_property:
2363
 * @self: a #FuUdevDevice
2364
 * @key: uevent key name, e.g. `HID_PHYS`
2365
 * @error: (nullable): optional return location for an error
2366
 *
2367
 * Gets a value from the `uevent` file.
2368
 *
2369
 * Returns: %TRUE for success
2370
 *
2371
 * Since: 2.0.0
2372
 **/
2373
gchar *
2374
fu_udev_device_read_property(FuUdevDevice *self, const gchar *key, GError **error)
2375
0
{
2376
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
2377
0
  FuDeviceEvent *event = NULL;
2378
0
  g_autofree gchar *event_id = NULL;
2379
0
  g_autofree gchar *value = NULL;
2380
2381
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL);
2382
0
  g_return_val_if_fail(key != NULL, NULL);
2383
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
2384
2385
  /* need event ID */
2386
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED) ||
2387
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self)),
2388
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
2389
0
    event_id = g_strdup_printf("ReadProp:Key=%s", key);
2390
0
  }
2391
2392
  /* emulated */
2393
0
  if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) {
2394
0
    event = fu_device_load_event(FU_DEVICE(self), event_id, error);
2395
0
    if (event == NULL)
2396
0
      return NULL;
2397
0
    return g_strdup(fu_device_event_get_str(event, "Data", error));
2398
0
  }
2399
2400
  /* save */
2401
0
  if (event_id != NULL)
2402
0
    event = fu_device_save_event(FU_DEVICE(self), event_id);
2403
2404
  /* parse key */
2405
0
  if (!priv->properties_valid) {
2406
0
    g_autofree gchar *str = NULL;
2407
0
    g_auto(GStrv) uevent_lines = NULL;
2408
0
    str = fu_udev_device_read_sysfs(self,
2409
0
            "uevent",
2410
0
            FU_UDEV_DEVICE_ATTR_READ_TIMEOUT_DEFAULT,
2411
0
            error);
2412
0
    if (str == NULL)
2413
0
      return NULL;
2414
0
    uevent_lines = g_strsplit(str, "\n", -1);
2415
0
    for (guint i = 0; uevent_lines[i] != NULL; i++) {
2416
      /* only split KEY=VALUE */
2417
0
      if (g_strstr_len(uevent_lines[i], -1, "=") != NULL) {
2418
0
        g_autofree gchar **kvs = g_strsplit(uevent_lines[i], "=", 2);
2419
0
        g_hash_table_insert(priv->properties,
2420
0
                g_steal_pointer(&kvs[0]),
2421
0
                g_steal_pointer(&kvs[1]));
2422
0
      }
2423
0
    }
2424
0
    priv->properties_valid = TRUE;
2425
0
  }
2426
0
  value = g_strdup(g_hash_table_lookup(priv->properties, key));
2427
0
  if (value == NULL) {
2428
0
    g_set_error(error,
2429
0
          FWUPD_ERROR,
2430
0
          FWUPD_ERROR_NOT_FOUND,
2431
0
          "property key %s was not found",
2432
0
          key);
2433
0
    return NULL;
2434
0
  }
2435
2436
  /* save response */
2437
0
  if (event != NULL)
2438
0
    fu_device_event_set_str(event, "Data", value);
2439
2440
  /* success */
2441
0
  return g_steal_pointer(&value);
2442
0
}
2443
2444
static void
2445
fu_udev_device_add_json(FuDevice *device, FwupdJsonObject *json_obj, FwupdCodecFlags flags)
2446
0
{
2447
0
  FuUdevDevice *self = FU_UDEV_DEVICE(device);
2448
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
2449
2450
  /* optional properties */
2451
0
  fwupd_json_object_add_string(json_obj, "GType", "FuUdevDevice");
2452
0
  if (fu_udev_device_get_sysfs_path(self) != NULL) {
2453
0
    fwupd_json_object_add_string(json_obj,
2454
0
               "BackendId",
2455
0
               fu_udev_device_get_sysfs_path(self));
2456
0
  }
2457
0
  if (priv->device_file != NULL)
2458
0
    fwupd_json_object_add_string(json_obj, "DeviceFile", priv->device_file);
2459
0
  if (priv->subsystem != NULL)
2460
0
    fwupd_json_object_add_string(json_obj, "Subsystem", priv->subsystem);
2461
0
  if (priv->devtype != NULL)
2462
0
    fwupd_json_object_add_string(json_obj, "Devtype", priv->devtype);
2463
0
  if (priv->driver != NULL)
2464
0
    fwupd_json_object_add_string(json_obj, "Driver", priv->driver);
2465
0
  if (priv->bind_id != NULL)
2466
0
    fwupd_json_object_add_string(json_obj, "BindId", priv->bind_id);
2467
0
  if (fu_device_get_vid(device) != 0)
2468
0
    fwupd_json_object_add_integer(json_obj, "Vendor", fu_device_get_vid(device));
2469
0
  if (fu_device_get_pid(device) != 0)
2470
0
    fwupd_json_object_add_integer(json_obj, "Model", fu_device_get_pid(device));
2471
0
}
2472
2473
static gboolean
2474
fu_udev_device_from_json(FuDevice *device, FwupdJsonObject *json_obj, GError **error)
2475
0
{
2476
0
  FuUdevDevice *self = FU_UDEV_DEVICE(device);
2477
0
  const gchar *tmp;
2478
0
  gint64 tmp64 = 0;
2479
2480
0
  tmp = fwupd_json_object_get_string(json_obj, "BackendId", NULL);
2481
0
  if (tmp != NULL)
2482
0
    fu_device_set_backend_id(device, tmp);
2483
0
  tmp = fwupd_json_object_get_string(json_obj, "Subsystem", NULL);
2484
0
  if (tmp != NULL)
2485
0
    fu_udev_device_set_subsystem(self, tmp);
2486
0
  tmp = fwupd_json_object_get_string(json_obj, "Devtype", NULL);
2487
0
  if (tmp != NULL)
2488
0
    fu_udev_device_set_devtype(self, tmp);
2489
0
  tmp = fwupd_json_object_get_string(json_obj, "Driver", NULL);
2490
0
  if (tmp != NULL)
2491
0
    fu_udev_device_set_driver(self, tmp);
2492
0
  tmp = fwupd_json_object_get_string(json_obj, "BindId", NULL);
2493
0
  if (tmp != NULL)
2494
0
    fu_udev_device_set_bind_id(self, tmp);
2495
0
  tmp = fwupd_json_object_get_string(json_obj, "DeviceFile", NULL);
2496
0
  if (tmp != NULL)
2497
0
    fu_udev_device_set_device_file(self, tmp);
2498
0
  if (!fwupd_json_object_get_integer_with_default(json_obj, "Vendor", &tmp64, 0x0, error))
2499
0
    return FALSE;
2500
0
  if (tmp64 != 0)
2501
0
    fu_device_set_vid(device, tmp64);
2502
0
  if (!fwupd_json_object_get_integer_with_default(json_obj, "Model", &tmp64, 0x0, error))
2503
0
    return FALSE;
2504
0
  if (tmp64 != 0)
2505
0
    fu_device_set_pid(device, tmp64);
2506
2507
  /* success */
2508
0
  return TRUE;
2509
0
}
2510
2511
static void
2512
fu_udev_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
2513
0
{
2514
0
  FuUdevDevice *self = FU_UDEV_DEVICE(object);
2515
0
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
2516
0
  switch (prop_id) {
2517
0
  case PROP_SUBSYSTEM:
2518
0
    g_value_set_string(value, priv->subsystem);
2519
0
    break;
2520
0
  case PROP_BIND_ID:
2521
0
    g_value_set_string(value, priv->bind_id);
2522
0
    break;
2523
0
  case PROP_DRIVER:
2524
0
    g_value_set_string(value, priv->driver);
2525
0
    break;
2526
0
  case PROP_DEVICE_FILE:
2527
0
    g_value_set_string(value, priv->device_file);
2528
0
    break;
2529
0
  case PROP_DEVTYPE:
2530
0
    g_value_set_string(value, priv->devtype);
2531
0
    break;
2532
0
  default:
2533
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
2534
0
    break;
2535
0
  }
2536
0
}
2537
2538
static void
2539
fu_udev_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
2540
0
{
2541
0
  FuUdevDevice *self = FU_UDEV_DEVICE(object);
2542
0
  switch (prop_id) {
2543
0
  case PROP_SUBSYSTEM:
2544
0
    fu_udev_device_set_subsystem(self, g_value_get_string(value));
2545
0
    break;
2546
0
  case PROP_BIND_ID:
2547
0
    fu_udev_device_set_bind_id(self, g_value_get_string(value));
2548
0
    break;
2549
0
  case PROP_DRIVER:
2550
0
    fu_udev_device_set_driver(self, g_value_get_string(value));
2551
0
    break;
2552
0
  case PROP_DEVICE_FILE:
2553
0
    fu_udev_device_set_device_file(self, g_value_get_string(value));
2554
0
    break;
2555
0
  case PROP_DEVTYPE:
2556
0
    fu_udev_device_set_devtype(self, g_value_get_string(value));
2557
0
    break;
2558
0
  default:
2559
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
2560
0
    break;
2561
0
  }
2562
0
}
2563
2564
static void
2565
fu_udev_device_finalize(GObject *object)
2566
1.11k
{
2567
1.11k
  FuUdevDevice *self = FU_UDEV_DEVICE(object);
2568
1.11k
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
2569
2570
1.11k
  g_hash_table_unref(priv->properties);
2571
1.11k
  g_free(priv->subsystem);
2572
1.11k
  g_free(priv->devtype);
2573
1.11k
  g_free(priv->bind_id);
2574
1.11k
  g_free(priv->driver);
2575
1.11k
  g_free(priv->device_file);
2576
1.11k
  if (priv->io_channel != NULL)
2577
0
    g_object_unref(priv->io_channel);
2578
2579
1.11k
  G_OBJECT_CLASS(fu_udev_device_parent_class)->finalize(object);
2580
1.11k
}
2581
2582
static void
2583
fu_udev_device_vid_notify_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data)
2584
692
{
2585
692
  fu_device_add_instance_u16(device, "VEN", fu_device_get_vid(device));
2586
692
}
2587
2588
static void
2589
fu_udev_device_pid_notify_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data)
2590
714
{
2591
714
  fu_device_add_instance_u16(device, "DEV", fu_device_get_pid(device));
2592
714
}
2593
2594
static void
2595
fu_udev_device_init(FuUdevDevice *self)
2596
1.11k
{
2597
1.11k
  FuUdevDevicePrivate *priv = GET_PRIVATE(self);
2598
1.11k
  priv->properties = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2599
1.11k
  fu_device_set_acquiesce_delay(FU_DEVICE(self), 2500);
2600
1.11k
  fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_EMULATION_TAG);
2601
1.11k
  fu_device_register_private_flag(FU_DEVICE(self), FU_UDEV_DEVICE_FLAG_SYSFS_USE_PHYSICAL_ID);
2602
1.11k
  g_signal_connect(FU_DEVICE(self),
2603
1.11k
       "notify::vid",
2604
1.11k
       G_CALLBACK(fu_udev_device_vid_notify_cb),
2605
1.11k
       NULL);
2606
1.11k
  g_signal_connect(FU_DEVICE(self),
2607
1.11k
       "notify::pid",
2608
1.11k
       G_CALLBACK(fu_udev_device_pid_notify_cb),
2609
1.11k
       NULL);
2610
1.11k
}
2611
2612
static void
2613
fu_udev_device_class_init(FuUdevDeviceClass *klass)
2614
2
{
2615
2
  FuDeviceClass *device_class = FU_DEVICE_CLASS(klass);
2616
2
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
2617
2
  GParamSpec *pspec;
2618
2619
2
  object_class->finalize = fu_udev_device_finalize;
2620
2
  object_class->get_property = fu_udev_device_get_property;
2621
2
  object_class->set_property = fu_udev_device_set_property;
2622
2
  device_class->probe = fu_udev_device_probe;
2623
2
  device_class->rescan = fu_udev_device_rescan;
2624
2
  device_class->incorporate = fu_udev_device_incorporate;
2625
2
  device_class->invalidate = fu_udev_device_invalidate;
2626
2
  device_class->open = fu_udev_device_open;
2627
2
  device_class->close = fu_udev_device_close;
2628
2
  device_class->to_string = fu_udev_device_to_string;
2629
2
  device_class->bind_driver = fu_udev_device_bind_driver;
2630
2
  device_class->unbind_driver = fu_udev_device_unbind_driver;
2631
2
  device_class->probe_complete = fu_udev_device_probe_complete;
2632
2
  device_class->from_json = fu_udev_device_from_json;
2633
2
  device_class->add_json = fu_udev_device_add_json;
2634
2635
  /**
2636
   * FuUdevDevice::changed:
2637
   * @self: the #FuUdevDevice instance that emitted the signal
2638
   *
2639
   * The ::changed signal is emitted when the low-level GUdevDevice has changed.
2640
   *
2641
   * Since: 1.1.2
2642
   **/
2643
2
  signals[SIGNAL_CHANGED] = g_signal_new("changed",
2644
2
                 G_TYPE_FROM_CLASS(object_class),
2645
2
                 G_SIGNAL_RUN_LAST,
2646
2
                 0,
2647
2
                 NULL,
2648
2
                 NULL,
2649
2
                 g_cclosure_marshal_VOID__VOID,
2650
2
                 G_TYPE_NONE,
2651
2
                 0);
2652
2653
  /**
2654
   * FuUdevDevice:subsystem:
2655
   *
2656
   * The device subsystem.
2657
   *
2658
   * Since: 1.1.2
2659
   */
2660
2
  pspec = g_param_spec_string("subsystem",
2661
2
            NULL,
2662
2
            NULL,
2663
2
            NULL,
2664
2
            G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
2665
2
  g_object_class_install_property(object_class, PROP_SUBSYSTEM, pspec);
2666
2667
  /**
2668
   * FuUdevDevice:bind-id:
2669
   *
2670
   * The bind ID to use when binding a new driver.
2671
   *
2672
   * Since: 1.7.2
2673
   */
2674
2
  pspec = g_param_spec_string("bind-id",
2675
2
            NULL,
2676
2
            NULL,
2677
2
            NULL,
2678
2
            G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
2679
2
  g_object_class_install_property(object_class, PROP_BIND_ID, pspec);
2680
2681
  /**
2682
   * FuUdevDevice:driver:
2683
   *
2684
   * The driver being used for the device.
2685
   *
2686
   * Since: 1.5.3
2687
   */
2688
2
  pspec = g_param_spec_string("driver",
2689
2
            NULL,
2690
2
            NULL,
2691
2
            NULL,
2692
2
            G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
2693
2
  g_object_class_install_property(object_class, PROP_DRIVER, pspec);
2694
2695
  /**
2696
   * FuUdevDevice:device-file:
2697
   *
2698
   * The low level file to use for device access.
2699
   *
2700
   * Since: 1.3.1
2701
   */
2702
2
  pspec = g_param_spec_string("device-file",
2703
2
            NULL,
2704
2
            NULL,
2705
2
            NULL,
2706
2
            G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
2707
2
  g_object_class_install_property(object_class, PROP_DEVICE_FILE, pspec);
2708
2709
  /**
2710
   * FuUdevDevice:devtype:
2711
   *
2712
   * The device type.
2713
   *
2714
   * Since: 2.0.0
2715
   */
2716
2
  pspec = g_param_spec_string("devtype",
2717
2
            NULL,
2718
2
            NULL,
2719
2
            NULL,
2720
2
            G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
2721
2
  g_object_class_install_property(object_class, PROP_DEVTYPE, pspec);
2722
2
}
2723
2724
/**
2725
 * fu_udev_device_new:
2726
 * @ctx: (nullable): a #FuContext
2727
 * @sysfs_path: a sysfs path
2728
 *
2729
 * Creates a new #FuUdevDevice.
2730
 *
2731
 * Returns: (transfer full): a #FuUdevDevice
2732
 *
2733
 * Since: 2.0.0
2734
 **/
2735
FuUdevDevice *
2736
fu_udev_device_new(FuContext *ctx, const gchar *sysfs_path)
2737
0
{
2738
0
  return g_object_new(FU_TYPE_UDEV_DEVICE, "context", ctx, "backend-id", sysfs_path, NULL);
2739
0
}