/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 | } |