Coverage Report

Created: 2026-04-12 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-ioctl.c
Line
Count
Source
1
/*
2
 * Copyright 2024 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "FuIoctl"
8
9
#include "config.h"
10
11
#include "fu-ioctl-private.h"
12
#include "fu-udev-device-private.h"
13
14
struct _FuIoctl {
15
  GObject parent_instance;
16
  FuUdevDevice *udev_device; /* ref */
17
  GString *event_id;
18
  GPtrArray *fixups; /* of FuIoctlFixup */
19
};
20
21
0
G_DEFINE_TYPE(FuIoctl, fu_ioctl, G_TYPE_OBJECT)
22
0
23
0
typedef struct {
24
0
  gchar *key;
25
0
  gboolean is_mutable;
26
0
  guint8 *buf;
27
0
  gsize bufsz;
28
0
  FuIoctlFixupFunc fixup_cb;
29
0
} FuIoctlFixup;
30
0
31
0
static void
32
0
fu_ioctl_fixup_free(FuIoctlFixup *fixup)
33
0
{
34
0
  g_free(fixup->key);
35
0
  g_free(fixup);
36
0
}
37
38
static gchar *
39
fu_ioctl_fixup_build_key(FuIoctlFixup *fixup, const gchar *suffix)
40
0
{
41
0
  return g_strdup_printf("%s%s", fixup->key != NULL ? fixup->key : "", suffix);
42
0
}
43
44
/**
45
 * fu_ioctl_set_name:
46
 * @self: a #FuIoctl
47
 * @name: (nullable): a string, e.g. `Nvme`
48
 *
49
 * Adds a name for the ioctl, preserving compatibility with existing emulation data.
50
 *
51
 * NOTE: For new devices this is not required.
52
 *
53
 * Since: 2.0.2
54
 **/
55
void
56
fu_ioctl_set_name(FuIoctl *self, const gchar *name)
57
0
{
58
0
  g_return_if_fail(FU_IS_IOCTL(self));
59
0
  g_string_truncate(self->event_id, 0);
60
0
  g_string_append_printf(self->event_id, "%sIoctl:", name != NULL ? name : "");
61
0
}
62
63
/* private */
64
FuIoctl *
65
fu_ioctl_new(FuUdevDevice *udev_device)
66
0
{
67
0
  g_autoptr(FuIoctl) self = g_object_new(FU_TYPE_IOCTL, NULL);
68
69
0
  g_return_val_if_fail(FU_IS_UDEV_DEVICE(udev_device), NULL);
70
71
0
  self->udev_device = g_object_ref(udev_device);
72
0
  return g_steal_pointer(&self);
73
0
}
74
75
static void
76
fu_ioctl_append_key(GString *event_id, const gchar *key, const gchar *value)
77
0
{
78
0
  if (event_id->len > 0 && !g_str_has_suffix(event_id->str, ":"))
79
0
    g_string_append_c(event_id, ',');
80
0
  g_string_append_printf(event_id, "%s=%s", key, value);
81
0
}
82
83
static void
84
fu_ioctl_append_key_as_u8(GString *event_id, const gchar *key, gsize value)
85
0
{
86
0
  g_autofree gchar *value2 = g_strdup_printf("0x%02x", (guint)value);
87
0
  fu_ioctl_append_key(event_id, key, value2);
88
0
}
89
90
static void
91
fu_ioctl_append_key_as_u16(GString *event_id, const gchar *key, gsize value)
92
0
{
93
0
  g_autofree gchar *value2 = g_strdup_printf("0x%04x", (guint)value);
94
0
  fu_ioctl_append_key(event_id, key, value2);
95
0
}
96
97
static void
98
fu_ioctl_append_key_as_u32(GString *event_id, const gchar *key, guint32 value)
99
0
{
100
0
  g_autofree gchar *value2 = g_strdup_printf("0x%08x", (guint)value);
101
0
  fu_ioctl_append_key(event_id, key, value2);
102
0
}
103
104
static void
105
fu_ioctl_append_key_from_buf(GString *event_id, const gchar *key, const guint8 *buf, gsize bufsz)
106
0
{
107
0
  g_autofree gchar *key_data = g_strdup_printf("%sData", key != NULL ? key : "");
108
0
  g_autofree gchar *value_data = g_base64_encode(buf, bufsz);
109
0
  g_autofree gchar *key_length = g_strdup_printf("%sLength", key != NULL ? key : "");
110
0
  g_autofree gchar *value_length = g_strdup_printf("0x%x", (guint)bufsz);
111
112
0
  fu_ioctl_append_key(event_id, key_data, value_data);
113
0
  fu_ioctl_append_key(event_id, key_length, value_length);
114
0
}
115
116
/**
117
 * fu_ioctl_add_key_as_u8:
118
 * @self: a #FuIoctl
119
 * @key: a string, e.g. `Opcode`
120
 * @value: a integer value
121
 *
122
 * Adds a key for the emulation, formatting it as `0x%02x`.
123
 *
124
 * Since: 2.0.2
125
 **/
126
void
127
fu_ioctl_add_key_as_u8(FuIoctl *self, const gchar *key, gsize value)
128
0
{
129
0
  g_return_if_fail(FU_IS_IOCTL(self));
130
0
  g_return_if_fail(key != NULL);
131
0
  fu_ioctl_append_key_as_u8(self->event_id, key, value);
132
0
}
133
134
/**
135
 * fu_ioctl_add_key_as_u16:
136
 * @self: a #FuIoctl
137
 * @key: a string, e.g. `Opcode`
138
 * @value: a integer value
139
 *
140
 * Adds a key for the emulation, formatting it as `0x%04x`.
141
 *
142
 * Since: 2.0.2
143
 **/
144
void
145
fu_ioctl_add_key_as_u16(FuIoctl *self, const gchar *key, gsize value)
146
0
{
147
0
  g_return_if_fail(FU_IS_IOCTL(self));
148
0
  g_return_if_fail(key != NULL);
149
0
  fu_ioctl_append_key_as_u16(self->event_id, key, value);
150
0
}
151
152
static void
153
fu_ioctl_add_buffer(FuIoctl *self,
154
        const gchar *key,
155
        guint8 *buf,
156
        gsize bufsz,
157
        gboolean is_mutable,
158
        FuIoctlFixupFunc fixup_cb)
159
0
{
160
0
  fu_ioctl_append_key_from_buf(self->event_id, key, buf, bufsz);
161
0
  if (fixup_cb != NULL) {
162
0
    FuIoctlFixup *fixup = g_new0(FuIoctlFixup, 1);
163
0
    fixup->key = g_strdup(key);
164
0
    fixup->is_mutable = is_mutable;
165
0
    fixup->buf = buf;
166
0
    fixup->bufsz = bufsz;
167
0
    fixup->fixup_cb = fixup_cb;
168
0
    g_ptr_array_add(self->fixups, fixup);
169
0
  }
170
0
}
171
172
/**
173
 * fu_ioctl_add_mutable_buffer:
174
 * @self: a #FuIoctl
175
 * @key: a string, e.g. `Cdb`
176
 * @buf: (nullable): an optional buffer
177
 * @bufsz: Size of @buf
178
 * @fixup_cb: (scope forever): a function to call on the structure
179
 *
180
 * Adds a mutable buffer that can be used to fix up the ioctl-defined structure with the buffer and
181
 * size, and adds a key for the emulation.
182
 *
183
 * Since: 2.0.2
184
 **/
185
void
186
fu_ioctl_add_mutable_buffer(FuIoctl *self,
187
          const gchar *key,
188
          guint8 *buf,
189
          gsize bufsz,
190
          FuIoctlFixupFunc fixup_cb)
191
0
{
192
0
  fu_ioctl_add_buffer(self, key, buf, bufsz, TRUE, fixup_cb);
193
0
}
194
195
/**
196
 * fu_ioctl_add_const_buffer:
197
 * @self: a #FuIoctl
198
 * @key: a string, e.g. `Cdb`
199
 * @buf: (nullable): an optional buffer
200
 * @bufsz: Size of @buf
201
 * @fixup_cb: (scope forever): a function to call on the structure
202
 *
203
 * Adds a constant buffer that can be used to fix up the ioctl-defined structure with the buffer
204
 * and size, and adds a key for the emulation.
205
 *
206
 * Since: 2.0.2
207
 **/
208
void
209
fu_ioctl_add_const_buffer(FuIoctl *self,
210
        const gchar *key,
211
        const guint8 *buf,
212
        gsize bufsz,
213
        FuIoctlFixupFunc fixup_cb)
214
0
{
215
0
  fu_ioctl_add_buffer(self, key, (guint8 *)buf, bufsz, FALSE, fixup_cb);
216
0
}
217
218
/**
219
 * fu_ioctl_execute:
220
 * @self: a #FuIoctl
221
 * @request: request number
222
 * @buf: a buffer to use, which *must* be large enough for the request
223
 * @bufsz: the size of @buf
224
 * @rc: (out) (nullable): the raw return value from the ioctl
225
 * @timeout: timeout in ms for the retry action, see %FU_IOCTL_FLAG_RETRY
226
 * @flags: some #FuIoctlFlags, e.g. %FU_IOCTL_FLAG_RETRY
227
 * @error: (nullable): optional return location for an error
228
 *
229
 * Executes the ioctl, emulating as required. Each fixup defined using fu_ioctl_add_mutable_buffer()
230
 * of fu_ioctl_add_const_buffer() is run before the ioctl is executed.
231
 *
232
 * If there are no fixups defined, the @buf is emulated, and so you must ensure that there are no
233
 * ioctl wrapper structures that use indirect pointer values.
234
 *
235
 * Returns: %TRUE for success
236
 *
237
 * Since: 2.0.2
238
 **/
239
gboolean
240
fu_ioctl_execute(FuIoctl *self,
241
     gulong request,
242
     gpointer buf,
243
     gsize bufsz,
244
     gint *rc,
245
     guint timeout,
246
     FuIoctlFlags flags,
247
     GError **error)
248
0
{
249
0
  FuDeviceEvent *event = NULL;
250
0
  g_autoptr(GString) event_id = NULL;
251
252
  /* need event ID */
253
0
  if (fu_device_has_flag(FU_DEVICE(self->udev_device), FWUPD_DEVICE_FLAG_EMULATED) ||
254
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self->udev_device)),
255
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
256
0
    event_id = g_string_new(self->event_id->str);
257
0
    if (g_strcmp0(event_id->str, "Ioctl:") == 0) {
258
0
      fu_ioctl_append_key_as_u16(event_id, "Request", request);
259
0
      if (flags & FU_IOCTL_FLAG_PTR_AS_INTEGER) {
260
0
        fu_ioctl_append_key_as_u32(event_id, NULL, GPOINTER_TO_UINT(buf));
261
0
      } else {
262
0
        fu_ioctl_append_key_from_buf(event_id, NULL, buf, bufsz);
263
0
      }
264
0
    }
265
0
  }
266
267
  /* emulated */
268
0
  if (fu_device_has_flag(FU_DEVICE(self->udev_device), FWUPD_DEVICE_FLAG_EMULATED)) {
269
0
    event = fu_device_load_event(FU_DEVICE(self->udev_device), event_id->str, error);
270
0
    if (event == NULL)
271
0
      return FALSE;
272
0
    if (self->fixups->len == 0) {
273
0
      if ((flags & FU_IOCTL_FLAG_PTR_AS_INTEGER) == 0) {
274
0
        if (!fu_device_event_copy_data(event,
275
0
                     "DataOut",
276
0
                     buf,
277
0
                     bufsz,
278
0
                     NULL,
279
0
                     error))
280
0
          return FALSE;
281
0
      }
282
0
    }
283
0
    for (guint i = 0; i < self->fixups->len; i++) {
284
0
      FuIoctlFixup *fixup = g_ptr_array_index(self->fixups, i);
285
0
      g_autofree gchar *key = fu_ioctl_fixup_build_key(fixup, "DataOut");
286
0
      if (!fixup->is_mutable)
287
0
        continue;
288
0
      if (!fu_device_event_copy_data(event,
289
0
                   key,
290
0
                   fixup->buf,
291
0
                   fixup->bufsz,
292
0
                   NULL,
293
0
                   error))
294
0
        return FALSE;
295
0
    }
296
0
    if (rc != NULL) {
297
0
      gint64 rc_tmp = fu_device_event_get_i64(event, "Rc", NULL);
298
0
      if (rc_tmp != G_MAXINT64)
299
0
        *rc = (gint)rc_tmp;
300
0
    }
301
0
    return TRUE;
302
0
  }
303
304
  /* save */
305
0
  if (fu_context_has_flag(fu_device_get_context(FU_DEVICE(self->udev_device)),
306
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
307
0
    event = fu_device_save_event(FU_DEVICE(self->udev_device), event_id->str);
308
0
  }
309
310
  /* the buffer might be specified indirectly */
311
0
  if (buf != NULL) {
312
0
    for (guint i = 0; i < self->fixups->len; i++) {
313
0
      FuIoctlFixup *fixup = g_ptr_array_index(self->fixups, i);
314
0
      if (!fixup->fixup_cb(self, buf, fixup->buf, fixup->bufsz, error))
315
0
        return FALSE;
316
0
    }
317
0
  }
318
0
  if (!fu_udev_device_ioctl(self->udev_device,
319
0
          request,
320
0
          buf,
321
0
          bufsz,
322
0
          rc,
323
0
          timeout,
324
0
          flags,
325
0
          error))
326
0
    return FALSE;
327
328
  /* save response */
329
0
  if (event != NULL) {
330
0
    if (rc != NULL && *rc != 0)
331
0
      fu_device_event_set_i64(event, "Rc", *rc);
332
0
    if (self->fixups->len == 0) {
333
0
      if ((flags & FU_IOCTL_FLAG_PTR_AS_INTEGER) == 0)
334
0
        fu_device_event_set_data(event, "DataOut", buf, bufsz);
335
0
    }
336
0
    for (guint i = 0; i < self->fixups->len; i++) {
337
0
      FuIoctlFixup *fixup = g_ptr_array_index(self->fixups, i);
338
0
      g_autofree gchar *key = fu_ioctl_fixup_build_key(fixup, "DataOut");
339
0
      if (!fixup->is_mutable)
340
0
        continue;
341
0
      fu_device_event_set_data(event, key, fixup->buf, fixup->bufsz);
342
0
    }
343
0
  }
344
345
  /* success */
346
0
  return TRUE;
347
0
}
348
349
static void
350
fu_ioctl_init(FuIoctl *self)
351
0
{
352
0
  self->event_id = g_string_new("Ioctl:");
353
0
  self->fixups = g_ptr_array_new_with_free_func((GDestroyNotify)fu_ioctl_fixup_free);
354
0
}
355
356
static void
357
fu_ioctl_finalize(GObject *object)
358
0
{
359
0
  FuIoctl *self = FU_IOCTL(object);
360
361
0
  g_string_free(self->event_id, TRUE);
362
0
  g_ptr_array_unref(self->fixups);
363
0
  if (self->udev_device != NULL)
364
0
    g_object_unref(self->udev_device);
365
366
0
  G_OBJECT_CLASS(fu_ioctl_parent_class)->finalize(object);
367
0
}
368
369
static void
370
fu_ioctl_class_init(FuIoctlClass *klass)
371
0
{
372
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
373
0
  object_class->finalize = fu_ioctl_finalize;
374
0
}