Coverage Report

Created: 2025-08-26 06:55

/src/fwupd/libfwupdplugin/fu-ioctl.c
Line
Count
Source (jump to first uncovered line)
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
G_DEFINE_TYPE(FuIoctl, fu_ioctl, G_TYPE_OBJECT)
22
23
typedef struct {
24
  gchar *key;
25
  gboolean is_mutable;
26
  guint8 *buf;
27
  gsize bufsz;
28
  FuIoctlFixupFunc fixup_cb;
29
} FuIoctlFixup;
30
31
static void
32
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_from_buf(GString *event_id, const gchar *key, const guint8 *buf, gsize bufsz)
99
0
{
100
0
  g_autofree gchar *key_data = g_strdup_printf("%sData", key != NULL ? key : "");
101
0
  g_autofree gchar *value_data = g_base64_encode(buf, bufsz);
102
0
  g_autofree gchar *key_length = g_strdup_printf("%sLength", key != NULL ? key : "");
103
0
  g_autofree gchar *value_length = g_strdup_printf("0x%x", (guint)bufsz);
104
105
0
  fu_ioctl_append_key(event_id, key_data, value_data);
106
0
  fu_ioctl_append_key(event_id, key_length, value_length);
107
0
}
108
109
/**
110
 * fu_ioctl_add_key_as_u8:
111
 * @self: a #FuIoctl
112
 * @key: a string, e.g. `Opcode`
113
 * @value: a integer value
114
 *
115
 * Adds a key for the emulation, formatting it as `0x%02x`.
116
 *
117
 * Since: 2.0.2
118
 **/
119
void
120
fu_ioctl_add_key_as_u8(FuIoctl *self, const gchar *key, gsize value)
121
0
{
122
0
  g_return_if_fail(FU_IS_IOCTL(self));
123
0
  g_return_if_fail(key != NULL);
124
0
  fu_ioctl_append_key_as_u8(self->event_id, key, value);
125
0
}
126
127
/**
128
 * fu_ioctl_add_key_as_u16:
129
 * @self: a #FuIoctl
130
 * @key: a string, e.g. `Opcode`
131
 * @value: a integer value
132
 *
133
 * Adds a key for the emulation, formatting it as `0x%04x`.
134
 *
135
 * Since: 2.0.2
136
 **/
137
void
138
fu_ioctl_add_key_as_u16(FuIoctl *self, const gchar *key, gsize value)
139
0
{
140
0
  g_return_if_fail(FU_IS_IOCTL(self));
141
0
  g_return_if_fail(key != NULL);
142
0
  fu_ioctl_append_key_as_u16(self->event_id, key, value);
143
0
}
144
145
static void
146
fu_ioctl_add_buffer(FuIoctl *self,
147
        const gchar *key,
148
        guint8 *buf,
149
        gsize bufsz,
150
        gboolean is_mutable,
151
        FuIoctlFixupFunc fixup_cb)
152
0
{
153
0
  fu_ioctl_append_key_from_buf(self->event_id, key, buf, bufsz);
154
0
  if (fixup_cb != NULL) {
155
0
    FuIoctlFixup *fixup = g_new0(FuIoctlFixup, 1);
156
0
    fixup->key = g_strdup(key);
157
0
    fixup->is_mutable = is_mutable;
158
0
    fixup->buf = buf;
159
0
    fixup->bufsz = bufsz;
160
0
    fixup->fixup_cb = fixup_cb;
161
0
    g_ptr_array_add(self->fixups, fixup);
162
0
  }
163
0
}
164
165
/**
166
 * fu_ioctl_add_mutable_buffer:
167
 * @self: a #FuIoctl
168
 * @key: a string, e.g. `Cdb`
169
 * @buf: (nullable): an optional buffer
170
 * @bufsz: Size of @buf
171
 * @fixup_cb: (scope forever): a function to call on the structure
172
 *
173
 * Adds a mutable buffer that can be used to fix up the ioctl-defined structure with the buffer and
174
 * size, and adds a key for the emulation.
175
 *
176
 * Since: 2.0.2
177
 **/
178
void
179
fu_ioctl_add_mutable_buffer(FuIoctl *self,
180
          const gchar *key,
181
          guint8 *buf,
182
          gsize bufsz,
183
          FuIoctlFixupFunc fixup_cb)
184
0
{
185
0
  fu_ioctl_add_buffer(self, key, buf, bufsz, TRUE, fixup_cb);
186
0
}
187
188
/**
189
 * fu_ioctl_add_const_buffer:
190
 * @self: a #FuIoctl
191
 * @key: a string, e.g. `Cdb`
192
 * @buf: (nullable): an optional buffer
193
 * @bufsz: Size of @buf
194
 * @fixup_cb: (scope forever): a function to call on the structure
195
 *
196
 * Adds a constant buffer that can be used to fix up the ioctl-defined structure with the buffer
197
 * and size, and adds a key for the emulation.
198
 *
199
 * Since: 2.0.2
200
 **/
201
void
202
fu_ioctl_add_const_buffer(FuIoctl *self,
203
        const gchar *key,
204
        const guint8 *buf,
205
        gsize bufsz,
206
        FuIoctlFixupFunc fixup_cb)
207
0
{
208
0
  fu_ioctl_add_buffer(self, key, (guint8 *)buf, bufsz, FALSE, fixup_cb);
209
0
}
210
211
/**
212
 * fu_ioctl_execute:
213
 * @self: a #FuIoctl
214
 * @request: request number
215
 * @buf: a buffer to use, which *must* be large enough for the request
216
 * @bufsz: the size of @buf
217
 * @rc: (out) (nullable): the raw return value from the ioctl
218
 * @timeout: timeout in ms for the retry action, see %FU_IOCTL_FLAG_RETRY
219
 * @flags: some #FuIoctlFlags, e.g. %FU_IOCTL_FLAG_RETRY
220
 * @error: (nullable): optional return location for an error
221
 *
222
 * Executes the ioctl, emulating as required. Each fixup defined using fu_ioctl_add_mutable_buffer()
223
 * of fu_ioctl_add_const_buffer() is run before the ioctl is executed.
224
 *
225
 * If there are no fixups defined, the @buf is emulated, and so you must ensure that there are no
226
 * ioctl wrapper structures that use indirect pointer values.
227
 *
228
 * Returns: %TRUE for success
229
 *
230
 * Since: 2.0.2
231
 **/
232
gboolean
233
fu_ioctl_execute(FuIoctl *self,
234
     gulong request,
235
     gpointer buf,
236
     gsize bufsz,
237
     gint *rc,
238
     guint timeout,
239
     FuIoctlFlags flags,
240
     GError **error)
241
0
{
242
0
  FuDeviceEvent *event = NULL;
243
0
  g_autoptr(GString) event_id = NULL;
244
245
  /* need event ID */
246
0
  if (fu_device_has_flag(FU_DEVICE(self->udev_device), FWUPD_DEVICE_FLAG_EMULATED) ||
247
0
      fu_context_has_flag(fu_device_get_context(FU_DEVICE(self->udev_device)),
248
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
249
0
    event_id = g_string_new(self->event_id->str);
250
0
    if (g_strcmp0(event_id->str, "Ioctl:") == 0) {
251
0
      fu_ioctl_append_key_as_u16(event_id, "Request", request);
252
0
      fu_ioctl_append_key_from_buf(event_id, NULL, buf, bufsz);
253
0
    }
254
0
  }
255
256
  /* emulated */
257
0
  if (fu_device_has_flag(FU_DEVICE(self->udev_device), FWUPD_DEVICE_FLAG_EMULATED)) {
258
0
    event = fu_device_load_event(FU_DEVICE(self->udev_device), event_id->str, error);
259
0
    if (event == NULL)
260
0
      return FALSE;
261
0
    if (self->fixups->len == 0) {
262
0
      if (!fu_device_event_copy_data(event, "DataOut", buf, bufsz, NULL, error))
263
0
        return FALSE;
264
0
    }
265
0
    for (guint i = 0; i < self->fixups->len; i++) {
266
0
      FuIoctlFixup *fixup = g_ptr_array_index(self->fixups, i);
267
0
      g_autofree gchar *key = fu_ioctl_fixup_build_key(fixup, "DataOut");
268
0
      if (!fixup->is_mutable)
269
0
        continue;
270
0
      if (!fu_device_event_copy_data(event,
271
0
                   key,
272
0
                   fixup->buf,
273
0
                   fixup->bufsz,
274
0
                   NULL,
275
0
                   error))
276
0
        return FALSE;
277
0
    }
278
0
    if (rc != NULL) {
279
0
      gint64 rc_tmp = fu_device_event_get_i64(event, "Rc", NULL);
280
0
      if (rc_tmp != G_MAXINT64)
281
0
        *rc = (gint)rc_tmp;
282
0
    }
283
0
    return TRUE;
284
0
  }
285
286
  /* save */
287
0
  if (fu_context_has_flag(fu_device_get_context(FU_DEVICE(self->udev_device)),
288
0
        FU_CONTEXT_FLAG_SAVE_EVENTS)) {
289
0
    event = fu_device_save_event(FU_DEVICE(self->udev_device), event_id->str);
290
0
  }
291
292
  /* the buffer might be specified indirectly */
293
0
  if (buf != NULL) {
294
0
    for (guint i = 0; i < self->fixups->len; i++) {
295
0
      FuIoctlFixup *fixup = g_ptr_array_index(self->fixups, i);
296
0
      if (!fixup->fixup_cb(self, buf, fixup->buf, fixup->bufsz, error))
297
0
        return FALSE;
298
0
    }
299
0
  }
300
0
  if (!fu_udev_device_ioctl(self->udev_device,
301
0
          request,
302
0
          buf,
303
0
          bufsz,
304
0
          rc,
305
0
          timeout,
306
0
          flags,
307
0
          error))
308
0
    return FALSE;
309
310
  /* save response */
311
0
  if (event != NULL) {
312
0
    if (rc != NULL && *rc != 0)
313
0
      fu_device_event_set_i64(event, "Rc", *rc);
314
0
    if (self->fixups->len == 0)
315
0
      fu_device_event_set_data(event, "DataOut", buf, bufsz);
316
0
    for (guint i = 0; i < self->fixups->len; i++) {
317
0
      FuIoctlFixup *fixup = g_ptr_array_index(self->fixups, i);
318
0
      g_autofree gchar *key = fu_ioctl_fixup_build_key(fixup, "DataOut");
319
0
      if (!fixup->is_mutable)
320
0
        continue;
321
0
      fu_device_event_set_data(event, key, fixup->buf, fixup->bufsz);
322
0
    }
323
0
  }
324
325
  /* success */
326
0
  return TRUE;
327
0
}
328
329
static void
330
fu_ioctl_init(FuIoctl *self)
331
0
{
332
0
  self->event_id = g_string_new("Ioctl:");
333
0
  self->fixups = g_ptr_array_new_with_free_func((GDestroyNotify)fu_ioctl_fixup_free);
334
0
}
335
336
static void
337
fu_ioctl_finalize(GObject *object)
338
0
{
339
0
  FuIoctl *self = FU_IOCTL(object);
340
341
0
  g_string_free(self->event_id, TRUE);
342
0
  g_ptr_array_unref(self->fixups);
343
0
  if (self->udev_device != NULL)
344
0
    g_object_unref(self->udev_device);
345
346
0
  G_OBJECT_CLASS(fu_ioctl_parent_class)->finalize(object);
347
0
}
348
349
static void
350
fu_ioctl_class_init(FuIoctlClass *klass)
351
0
{
352
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
353
0
  object_class->finalize = fu_ioctl_finalize;
354
0
}