/src/fwupd/libfwupdplugin/fu-efi-load-option.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2023 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 1.14k | #define G_LOG_DOMAIN "FuEfiLoadOption" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-byte-array.h" |
12 | | #include "fu-common.h" |
13 | | #include "fu-efi-device-path-list.h" |
14 | | #include "fu-efi-load-option.h" |
15 | | #include "fu-input-stream.h" |
16 | | #include "fu-string.h" |
17 | | |
18 | | struct _FuEfiLoadOption { |
19 | | FuFirmware parent_instance; |
20 | | guint32 attrs; |
21 | | FuEfiLoadOptionKind kind; |
22 | | GBytes *optional_data; /* only used when not a hive or path */ |
23 | | GHashTable *metadata; /* element-type: utf8:utf8 */ |
24 | | }; |
25 | | |
26 | | static void |
27 | | fu_efi_load_option_codec_iface_init(FwupdCodecInterface *iface); |
28 | | |
29 | 12.4k | G_DEFINE_TYPE_EXTENDED(FuEfiLoadOption, |
30 | 12.4k | fu_efi_load_option, |
31 | 12.4k | FU_TYPE_FIRMWARE, |
32 | 12.4k | 0, |
33 | 12.4k | G_IMPLEMENT_INTERFACE(FWUPD_TYPE_CODEC, fu_efi_load_option_codec_iface_init)) |
34 | 12.4k | |
35 | 34.3k | #define FU_EFI_LOAD_OPTION_DESCRIPTION_SIZE_MAX 0x1000u /* bytes */ |
36 | | |
37 | 888 | #define FU_EFI_LOAD_OPTION_HIVE_HEADER_VERSION_MIN 1 |
38 | | |
39 | | static void |
40 | | fu_efi_load_option_set_optional_data(FuEfiLoadOption *self, GBytes *optional_data) |
41 | 539 | { |
42 | 539 | g_return_if_fail(FU_IS_EFI_LOAD_OPTION(self)); |
43 | 539 | if (self->optional_data != NULL) { |
44 | 0 | g_bytes_unref(self->optional_data); |
45 | 0 | self->optional_data = NULL; |
46 | 0 | } |
47 | 539 | if (optional_data != NULL) |
48 | 539 | self->optional_data = g_bytes_ref(optional_data); |
49 | 539 | } |
50 | | |
51 | | /** |
52 | | * fu_efi_load_option_get_metadata: |
53 | | * @self: a #FuEfiLoadOption |
54 | | * @key: (not nullable): UTF-8 string |
55 | | * @error: (nullable): optional return location for an error |
56 | | * |
57 | | * Gets an optional attribute. |
58 | | * |
59 | | * Returns: UTF-8 string, or %NULL |
60 | | * |
61 | | * Since: 2.0.0 |
62 | | **/ |
63 | | const gchar * |
64 | | fu_efi_load_option_get_metadata(FuEfiLoadOption *self, const gchar *key, GError **error) |
65 | 0 | { |
66 | 0 | const gchar *value; |
67 | |
|
68 | 0 | g_return_val_if_fail(FU_IS_EFI_LOAD_OPTION(self), NULL); |
69 | 0 | g_return_val_if_fail(key != NULL, NULL); |
70 | | |
71 | 0 | value = g_hash_table_lookup(self->metadata, key); |
72 | 0 | if (value == NULL) { |
73 | 0 | g_set_error(error, |
74 | 0 | FWUPD_ERROR, |
75 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
76 | 0 | "no attribute value for %s", |
77 | 0 | key); |
78 | 0 | return NULL; |
79 | 0 | } |
80 | | |
81 | | /* success */ |
82 | 0 | return value; |
83 | 0 | } |
84 | | |
85 | | /** |
86 | | * fu_efi_load_option_get_kind: |
87 | | * @self: a #FuEfiLoadOption |
88 | | * |
89 | | * Gets the loadopt kind. |
90 | | * |
91 | | * Returns: a #FuEfiLoadOptionKind, e.g. %FU_EFI_LOAD_OPTION_KIND_HIVE |
92 | | * |
93 | | * Since: 2.0.6 |
94 | | **/ |
95 | | FuEfiLoadOptionKind |
96 | | fu_efi_load_option_get_kind(FuEfiLoadOption *self) |
97 | 0 | { |
98 | 0 | g_return_val_if_fail(FU_IS_EFI_LOAD_OPTION(self), FU_EFI_LOAD_OPTION_KIND_UNKNOWN); |
99 | 0 | return self->kind; |
100 | 0 | } |
101 | | |
102 | | /** |
103 | | * fu_efi_load_option_set_kind: |
104 | | * @self: a #FuEfiLoadOption |
105 | | * @kind: a #FuEfiLoadOptionKind, e.g. %FU_EFI_LOAD_OPTION_KIND_HIVE |
106 | | * |
107 | | * Sets the loadopt kind. |
108 | | * |
109 | | * Since: 2.0.6 |
110 | | **/ |
111 | | void |
112 | | fu_efi_load_option_set_kind(FuEfiLoadOption *self, FuEfiLoadOptionKind kind) |
113 | 0 | { |
114 | 0 | g_return_if_fail(FU_IS_EFI_LOAD_OPTION(self)); |
115 | 0 | g_return_if_fail(kind < FU_EFI_LOAD_OPTION_KIND_LAST); |
116 | 0 | self->kind = kind; |
117 | 0 | } |
118 | | |
119 | | /** |
120 | | * fu_efi_load_option_set_metadata: |
121 | | * @self: a #FuEfiLoadOption |
122 | | * @key: (not nullable): UTF-8 string |
123 | | * @value: (nullable): UTF-8 string, or %NULL |
124 | | * |
125 | | * Sets an optional attribute. If @value is %NULL then the key will be removed. |
126 | | * |
127 | | * NOTE: When the key is `Path`, any leading backslash will be stripped automatically and added |
128 | | * back as-required on export. |
129 | | * |
130 | | * Since: 2.0.0 |
131 | | **/ |
132 | | void |
133 | | fu_efi_load_option_set_metadata(FuEfiLoadOption *self, const gchar *key, const gchar *value) |
134 | 4.64k | { |
135 | 4.64k | g_return_if_fail(FU_IS_EFI_LOAD_OPTION(self)); |
136 | 4.64k | g_return_if_fail(key != NULL); |
137 | | |
138 | 4.64k | if (value == NULL) { |
139 | 0 | g_hash_table_remove(self->metadata, key); |
140 | 0 | return; |
141 | 0 | } |
142 | | /* auto-set something sensible */ |
143 | 4.64k | if (self->kind == FU_EFI_LOAD_OPTION_KIND_UNKNOWN && |
144 | 725 | g_strcmp0(key, FU_EFI_LOAD_OPTION_METADATA_PATH) == 0) { |
145 | 122 | self->kind = FU_EFI_LOAD_OPTION_KIND_PATH; |
146 | 4.52k | } else { |
147 | 4.52k | self->kind = FU_EFI_LOAD_OPTION_KIND_HIVE; |
148 | 4.52k | } |
149 | 4.64k | if (g_strcmp0(key, FU_EFI_LOAD_OPTION_METADATA_PATH) == 0 && value != NULL && |
150 | 440 | g_str_has_prefix(value, "\\")) { |
151 | 200 | value++; |
152 | 200 | } |
153 | 4.64k | g_hash_table_insert(self->metadata, g_strdup(key), g_strdup(value)); |
154 | 4.64k | } |
155 | | |
156 | | static gboolean |
157 | | fu_efi_load_option_parse_optional_hive(FuEfiLoadOption *self, |
158 | | GInputStream *stream, |
159 | | gsize offset, |
160 | | GError **error) |
161 | 1.37k | { |
162 | 1.37k | g_autoptr(FuStructShimHive) st = NULL; |
163 | 1.37k | guint8 items_count; |
164 | | |
165 | 1.37k | st = fu_struct_shim_hive_parse_stream(stream, offset, error); |
166 | 1.37k | if (st == NULL) |
167 | 491 | return FALSE; |
168 | 888 | if (fu_struct_shim_hive_get_header_version(st) < |
169 | 888 | FU_EFI_LOAD_OPTION_HIVE_HEADER_VERSION_MIN) { |
170 | 1 | g_set_error(error, |
171 | 1 | FWUPD_ERROR, |
172 | 1 | FWUPD_ERROR_NOT_SUPPORTED, |
173 | 1 | "header version %u is not supported", |
174 | 1 | fu_struct_shim_hive_get_header_version(st)); |
175 | 1 | return FALSE; |
176 | 1 | } |
177 | 887 | offset += fu_struct_shim_hive_get_items_offset(st); |
178 | | |
179 | | /* items */ |
180 | 887 | items_count = fu_struct_shim_hive_get_items_count(st); |
181 | 5.46k | for (guint i = 0; i < items_count; i++) { |
182 | 5.09k | guint8 keysz; |
183 | 5.09k | guint32 valuesz; |
184 | 5.09k | g_autofree gchar *key = NULL; |
185 | 5.09k | g_autofree gchar *value = NULL; |
186 | 5.09k | g_autoptr(FuStructShimHiveItem) st_item = NULL; |
187 | | |
188 | 5.09k | st_item = fu_struct_shim_hive_item_parse_stream(stream, offset, error); |
189 | 5.09k | if (st_item == NULL) |
190 | 259 | return FALSE; |
191 | 4.83k | offset += st_item->buf->len; |
192 | | |
193 | | /* key */ |
194 | 4.83k | keysz = fu_struct_shim_hive_item_get_key_length(st_item); |
195 | 4.83k | if (keysz == 0) { |
196 | 3 | g_set_error_literal(error, |
197 | 3 | FWUPD_ERROR, |
198 | 3 | FWUPD_ERROR_NOT_SUPPORTED, |
199 | 3 | "zero key size is not supported"); |
200 | 3 | return FALSE; |
201 | 3 | } |
202 | 4.83k | key = fu_input_stream_read_string(stream, offset, keysz, error); |
203 | 4.83k | if (key == NULL) |
204 | 130 | return FALSE; |
205 | 4.70k | offset += keysz; |
206 | | |
207 | | /* value */ |
208 | 4.70k | valuesz = fu_struct_shim_hive_item_get_value_length(st_item); |
209 | 4.70k | if (valuesz > 0) { |
210 | 1.98k | value = fu_input_stream_read_string(stream, offset, valuesz, error); |
211 | 1.98k | if (value == NULL) |
212 | 121 | return FALSE; |
213 | 1.86k | offset += valuesz; |
214 | 1.86k | } |
215 | 4.58k | fu_efi_load_option_set_metadata(self, key, value != NULL ? value : ""); |
216 | 4.58k | } |
217 | | |
218 | | /* success */ |
219 | 374 | return TRUE; |
220 | 887 | } |
221 | | |
222 | | static gboolean |
223 | | fu_efi_load_option_parse_optional_path(FuEfiLoadOption *self, GBytes *opt_blob, GError **error) |
224 | 604 | { |
225 | 604 | g_autofree gchar *optional_path = NULL; |
226 | | |
227 | | /* convert to UTF-8 */ |
228 | 604 | optional_path = fu_utf16_to_utf8_bytes(opt_blob, G_LITTLE_ENDIAN, error); |
229 | 604 | if (optional_path == NULL) |
230 | 210 | return FALSE; |
231 | | |
232 | | /* check is ASCII */ |
233 | 394 | if (optional_path[0] == '\0' || !g_str_is_ascii(optional_path)) { |
234 | 329 | g_set_error(error, |
235 | 329 | FWUPD_ERROR, |
236 | 329 | FWUPD_ERROR_NOT_SUPPORTED, |
237 | 329 | "not ASCII data: %s", |
238 | 329 | optional_path); |
239 | 329 | return FALSE; |
240 | 329 | } |
241 | 65 | fu_efi_load_option_set_metadata(self, FU_EFI_LOAD_OPTION_METADATA_PATH, optional_path); |
242 | | |
243 | | /* success */ |
244 | 65 | return TRUE; |
245 | 394 | } |
246 | | |
247 | | static gboolean |
248 | | fu_efi_load_option_parse_optional(FuEfiLoadOption *self, |
249 | | GInputStream *stream, |
250 | | gsize offset, |
251 | | GError **error) |
252 | 1.37k | { |
253 | 1.37k | gsize streamsz = 0; |
254 | 1.37k | g_autoptr(GBytes) opt_blob = NULL; |
255 | 1.37k | g_autoptr(GError) error_hive = NULL; |
256 | 1.37k | g_autoptr(GError) error_path = NULL; |
257 | | |
258 | | /* try hive structure first */ |
259 | 1.37k | if (!fu_efi_load_option_parse_optional_hive(self, stream, offset, &error_hive)) { |
260 | 1.00k | if (!g_error_matches(error_hive, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA)) { |
261 | 401 | g_propagate_error(error, g_steal_pointer(&error_hive)); |
262 | 401 | return FALSE; |
263 | 401 | } |
264 | 604 | g_debug("not a shim hive, ignoring: %s", error_hive->message); |
265 | 604 | } else { |
266 | 374 | self->kind = FU_EFI_LOAD_OPTION_KIND_HIVE; |
267 | 374 | return TRUE; |
268 | 374 | } |
269 | | |
270 | | /* then UCS-2 path, and on ASCII failure just treat as a raw data blob */ |
271 | 604 | if (!fu_input_stream_size(stream, &streamsz, error)) |
272 | 0 | return FALSE; |
273 | 604 | opt_blob = fu_input_stream_read_bytes(stream, offset, streamsz - offset, NULL, error); |
274 | 604 | if (opt_blob == NULL) |
275 | 0 | return FALSE; |
276 | 604 | if (!fu_efi_load_option_parse_optional_path(self, opt_blob, &error_path)) { |
277 | 539 | g_debug("not a path, saving as raw blob: %s", error_path->message); |
278 | 539 | fu_efi_load_option_set_optional_data(self, opt_blob); |
279 | 539 | } else { |
280 | 65 | self->kind = FU_EFI_LOAD_OPTION_KIND_PATH; |
281 | 65 | return TRUE; |
282 | 65 | } |
283 | | |
284 | | /* success */ |
285 | 539 | self->kind = FU_EFI_LOAD_OPTION_KIND_DATA; |
286 | 539 | return TRUE; |
287 | 604 | } |
288 | | |
289 | | static gboolean |
290 | | fu_efi_load_option_parse(FuFirmware *firmware, |
291 | | GInputStream *stream, |
292 | | FuFirmwareParseFlags flags, |
293 | | GError **error) |
294 | 2.02k | { |
295 | 2.02k | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); |
296 | 2.02k | gsize offset = 0; |
297 | 2.02k | gsize streamsz = 0; |
298 | 2.02k | g_autofree gchar *id = NULL; |
299 | 2.02k | g_autoptr(FuEfiDevicePathList) device_path_list = fu_efi_device_path_list_new(); |
300 | 2.02k | g_autoptr(GByteArray) buf_utf16 = g_byte_array_new(); |
301 | 2.02k | g_autoptr(FuStructEfiLoadOption) st = NULL; |
302 | | |
303 | | /* parse header */ |
304 | 2.02k | st = fu_struct_efi_load_option_parse_stream(stream, offset, error); |
305 | 2.02k | if (st == NULL) |
306 | 8 | return FALSE; |
307 | 2.01k | self->attrs = fu_struct_efi_load_option_get_attrs(st); |
308 | 2.01k | offset += st->buf->len; |
309 | | |
310 | | /* parse UTF-16 description */ |
311 | 2.01k | if (!fu_input_stream_size(stream, &streamsz, error)) |
312 | 0 | return FALSE; |
313 | 34.5k | for (; offset < streamsz; offset += 2) { |
314 | 34.3k | guint16 tmp = 0; |
315 | 34.3k | if (buf_utf16->len > FU_EFI_LOAD_OPTION_DESCRIPTION_SIZE_MAX) { |
316 | 2 | g_set_error(error, |
317 | 2 | FWUPD_ERROR, |
318 | 2 | FWUPD_ERROR_INVALID_DATA, |
319 | 2 | "description was too long, limit is 0x%x chars", |
320 | 2 | FU_EFI_LOAD_OPTION_DESCRIPTION_SIZE_MAX / 2); |
321 | 2 | return FALSE; |
322 | 2 | } |
323 | 34.3k | if (!fu_input_stream_read_u16(stream, offset, &tmp, G_LITTLE_ENDIAN, error)) |
324 | 17 | return FALSE; |
325 | 34.2k | if (tmp == 0) |
326 | 1.80k | break; |
327 | 32.4k | fu_byte_array_append_uint16(buf_utf16, tmp, G_LITTLE_ENDIAN); |
328 | 32.4k | } |
329 | 1.99k | id = fu_utf16_to_utf8_byte_array(buf_utf16, G_LITTLE_ENDIAN, error); |
330 | 1.99k | if (id == NULL) |
331 | 43 | return FALSE; |
332 | 1.95k | fu_firmware_set_id(firmware, id); |
333 | 1.95k | offset += 2; |
334 | | |
335 | | /* parse dp blob */ |
336 | 1.95k | if (!fu_firmware_parse_stream(FU_FIRMWARE(device_path_list), stream, offset, flags, error)) |
337 | 392 | return FALSE; |
338 | 1.56k | if (!fu_firmware_add_image(firmware, FU_FIRMWARE(device_path_list), error)) |
339 | 0 | return FALSE; |
340 | 1.56k | offset += fu_struct_efi_load_option_get_dp_size(st); |
341 | | |
342 | | /* optional data */ |
343 | 1.56k | if (offset < streamsz) { |
344 | 1.37k | if (!fu_efi_load_option_parse_optional(self, stream, offset, error)) |
345 | 401 | return FALSE; |
346 | 1.37k | } |
347 | | |
348 | | /* success */ |
349 | 1.16k | return TRUE; |
350 | 1.56k | } |
351 | | |
352 | | static GByteArray * |
353 | | fu_efi_load_option_write_hive(FuEfiLoadOption *self, GError **error) |
354 | 374 | { |
355 | 374 | GHashTableIter iter; |
356 | 374 | guint items_count = g_hash_table_size(self->metadata); |
357 | 374 | const gchar *key; |
358 | 374 | const gchar *value; |
359 | 374 | g_autoptr(FuStructShimHive) st = fu_struct_shim_hive_new(); |
360 | | |
361 | 374 | fu_struct_shim_hive_set_items_count(st, items_count); |
362 | 374 | fu_struct_shim_hive_set_items_offset(st, FU_STRUCT_SHIM_HIVE_SIZE); |
363 | 374 | g_hash_table_iter_init(&iter, self->metadata); |
364 | 1.40k | while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) { |
365 | 1.03k | guint32 keysz = strlen(key); |
366 | 1.03k | g_autoptr(FuStructShimHiveItem) st_item = fu_struct_shim_hive_item_new(); |
367 | 1.03k | g_autoptr(GString) value_safe = g_string_new(value); |
368 | | |
369 | | /* required prefix for a path */ |
370 | 1.03k | if (g_strcmp0(key, FU_EFI_LOAD_OPTION_METADATA_PATH) == 0 && value_safe->len > 0 && |
371 | 40 | !g_str_has_prefix(value_safe->str, "\\")) { |
372 | 39 | g_string_prepend(value_safe, "\\"); |
373 | 39 | } |
374 | 1.03k | fu_struct_shim_hive_item_set_key_length(st_item, keysz); |
375 | 1.03k | fu_struct_shim_hive_item_set_value_length(st_item, value_safe->len); |
376 | 1.03k | if (keysz > 0) |
377 | 1.03k | g_byte_array_append(st_item->buf, (const guint8 *)key, keysz); |
378 | 1.03k | if (value_safe->len > 0) { |
379 | 673 | g_byte_array_append(st_item->buf, |
380 | 673 | (const guint8 *)value_safe->str, |
381 | 673 | value_safe->len); |
382 | 673 | } |
383 | | |
384 | | /* add to hive */ |
385 | 1.03k | fu_byte_array_append_array(st->buf, st_item->buf); |
386 | 1.03k | } |
387 | | |
388 | | /* this covers all items, and so has to be done last */ |
389 | 374 | fu_struct_shim_hive_set_crc32( |
390 | 374 | st, |
391 | 374 | fu_crc32(FU_CRC_KIND_B32_STANDARD, st->buf->data, st->buf->len)); |
392 | | |
393 | | /* success */ |
394 | 374 | return g_steal_pointer(&st->buf); |
395 | 374 | } |
396 | | |
397 | | static GByteArray * |
398 | | fu_efi_load_option_write_path(FuEfiLoadOption *self, GError **error) |
399 | 52 | { |
400 | 52 | g_autoptr(GByteArray) buf = NULL; |
401 | 52 | const gchar *path = g_hash_table_lookup(self->metadata, FU_EFI_LOAD_OPTION_METADATA_PATH); |
402 | 52 | g_autoptr(GString) str = g_string_new(path); |
403 | | |
404 | | /* is required if a path */ |
405 | 52 | if (!g_str_has_prefix(str->str, "\\")) |
406 | 51 | g_string_prepend(str, "\\"); |
407 | 52 | buf = fu_utf8_to_utf16_byte_array(str->str, |
408 | 52 | G_LITTLE_ENDIAN, |
409 | 52 | FU_UTF_CONVERT_FLAG_APPEND_NUL, |
410 | 52 | error); |
411 | 52 | if (buf == NULL) |
412 | 0 | return NULL; |
413 | 52 | return g_steal_pointer(&buf); |
414 | 52 | } |
415 | | |
416 | | static GByteArray * |
417 | | fu_efi_load_option_write(FuFirmware *firmware, GError **error) |
418 | 1.16k | { |
419 | 1.16k | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); |
420 | 1.16k | g_autoptr(GByteArray) buf_utf16 = NULL; |
421 | 1.16k | g_autoptr(FuStructEfiLoadOption) st = fu_struct_efi_load_option_new(); |
422 | 1.16k | g_autoptr(GBytes) dpbuf = NULL; |
423 | | |
424 | | /* header */ |
425 | 1.16k | fu_struct_efi_load_option_set_attrs(st, self->attrs); |
426 | | |
427 | | /* label */ |
428 | 1.16k | if (fu_firmware_get_id(firmware) == NULL) { |
429 | 0 | g_set_error_literal(error, |
430 | 0 | FWUPD_ERROR, |
431 | 0 | FWUPD_ERROR_INVALID_DATA, |
432 | 0 | "firmware ID required"); |
433 | 0 | return NULL; |
434 | 0 | } |
435 | 1.16k | buf_utf16 = fu_utf8_to_utf16_byte_array(fu_firmware_get_id(firmware), |
436 | 1.16k | G_LITTLE_ENDIAN, |
437 | 1.16k | FU_UTF_CONVERT_FLAG_APPEND_NUL, |
438 | 1.16k | error); |
439 | 1.16k | if (buf_utf16 == NULL) |
440 | 0 | return NULL; |
441 | 1.16k | g_byte_array_append(st->buf, buf_utf16->data, buf_utf16->len); |
442 | | |
443 | | /* dpbuf */ |
444 | 1.16k | dpbuf = fu_firmware_get_image_by_gtype_bytes(firmware, FU_TYPE_EFI_DEVICE_PATH_LIST, error); |
445 | 1.16k | if (dpbuf == NULL) |
446 | 383 | return NULL; |
447 | 778 | fu_struct_efi_load_option_set_dp_size(st, g_bytes_get_size(dpbuf)); |
448 | 778 | fu_byte_array_append_bytes(st->buf, dpbuf); |
449 | | |
450 | | /* hive, path or data */ |
451 | 778 | if (self->kind == FU_EFI_LOAD_OPTION_KIND_HIVE) { |
452 | 374 | g_autoptr(GByteArray) buf_hive = NULL; |
453 | 374 | buf_hive = fu_efi_load_option_write_hive(self, error); |
454 | 374 | if (buf_hive == NULL) |
455 | 0 | return NULL; |
456 | 374 | g_byte_array_append(st->buf, buf_hive->data, buf_hive->len); |
457 | 374 | fu_byte_array_align_up(st->buf, FU_FIRMWARE_ALIGNMENT_512, 0x0); /* make atomic */ |
458 | 404 | } else if (self->kind == FU_EFI_LOAD_OPTION_KIND_PATH) { |
459 | 52 | g_autoptr(GByteArray) buf_path = NULL; |
460 | 52 | buf_path = fu_efi_load_option_write_path(self, error); |
461 | 52 | if (buf_path == NULL) |
462 | 0 | return NULL; |
463 | 52 | g_byte_array_append(st->buf, buf_path->data, buf_path->len); |
464 | 352 | } else if (self->kind == FU_EFI_LOAD_OPTION_KIND_DATA && self->optional_data != NULL) { |
465 | 285 | fu_byte_array_append_bytes(st->buf, self->optional_data); |
466 | 285 | } |
467 | | |
468 | | /* success */ |
469 | 778 | return g_steal_pointer(&st->buf); |
470 | 778 | } |
471 | | |
472 | | static gboolean |
473 | | fu_efi_load_option_build(FuFirmware *firmware, XbNode *n, GError **error) |
474 | 0 | { |
475 | 0 | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); |
476 | 0 | const gchar *str; |
477 | 0 | guint64 tmp; |
478 | 0 | g_autoptr(GPtrArray) metadata = NULL; |
479 | 0 | g_autoptr(XbNode) optional_data = NULL; |
480 | | |
481 | | /* simple properties */ |
482 | 0 | tmp = xb_node_query_text_as_uint(n, "attrs", NULL); |
483 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) |
484 | 0 | self->attrs = tmp; |
485 | | |
486 | | /* simple properties */ |
487 | 0 | str = xb_node_query_text(n, "kind", NULL); |
488 | 0 | if (str != NULL) { |
489 | 0 | self->kind = fu_efi_load_option_kind_from_string(str); |
490 | 0 | if (self->kind == FU_EFI_LOAD_OPTION_KIND_UNKNOWN) { |
491 | 0 | g_set_error(error, |
492 | 0 | FWUPD_ERROR, |
493 | 0 | FWUPD_ERROR_INVALID_DATA, |
494 | 0 | "invalid option kind type %s", |
495 | 0 | str); |
496 | 0 | return FALSE; |
497 | 0 | } |
498 | 0 | } |
499 | | |
500 | | /* optional data */ |
501 | 0 | optional_data = xb_node_query_first(n, "optional_data", NULL); |
502 | 0 | if (optional_data != NULL) { |
503 | 0 | g_autoptr(GBytes) blob = NULL; |
504 | 0 | if (xb_node_get_text(optional_data) != NULL) { |
505 | 0 | gsize bufsz = 0; |
506 | 0 | g_autofree guchar *buf = NULL; |
507 | 0 | buf = g_base64_decode(xb_node_get_text(optional_data), &bufsz); |
508 | 0 | blob = g_bytes_new(buf, bufsz); |
509 | 0 | } else { |
510 | 0 | blob = g_bytes_new(NULL, 0); |
511 | 0 | } |
512 | 0 | fu_efi_load_option_set_optional_data(self, blob); |
513 | 0 | self->kind = FU_EFI_LOAD_OPTION_KIND_DATA; |
514 | 0 | } |
515 | 0 | metadata = xb_node_query(n, "metadata/*", 0, NULL); |
516 | 0 | if (metadata != NULL) { |
517 | 0 | for (guint i = 0; i < metadata->len; i++) { |
518 | 0 | XbNode *c = g_ptr_array_index(metadata, i); |
519 | 0 | const gchar *value = xb_node_get_text(c); |
520 | 0 | if (xb_node_get_element(c) == NULL) |
521 | 0 | continue; |
522 | 0 | fu_efi_load_option_set_metadata(self, |
523 | 0 | xb_node_get_element(c), |
524 | 0 | value != NULL ? value : ""); |
525 | 0 | } |
526 | 0 | } |
527 | | |
528 | | /* success */ |
529 | 0 | return TRUE; |
530 | 0 | } |
531 | | |
532 | | static void |
533 | | fu_efi_load_option_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
534 | 0 | { |
535 | 0 | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); |
536 | |
|
537 | 0 | fu_xmlb_builder_insert_kx(bn, "attrs", self->attrs); |
538 | 0 | if (self->kind != FU_EFI_LOAD_OPTION_KIND_UNKNOWN) { |
539 | 0 | fu_xmlb_builder_insert_kv(bn, |
540 | 0 | "kind", |
541 | 0 | fu_efi_load_option_kind_to_string(self->kind)); |
542 | 0 | } |
543 | 0 | if (g_hash_table_size(self->metadata) > 0) { |
544 | 0 | GHashTableIter iter; |
545 | 0 | const gchar *key; |
546 | 0 | const gchar *value; |
547 | 0 | g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "metadata", NULL); |
548 | 0 | g_hash_table_iter_init(&iter, self->metadata); |
549 | 0 | while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) |
550 | 0 | xb_builder_node_insert_text(bc, key, value, NULL); |
551 | 0 | } |
552 | 0 | if (self->optional_data != NULL) { |
553 | 0 | gsize bufsz = 0; |
554 | 0 | const guint8 *buf = g_bytes_get_data(self->optional_data, &bufsz); |
555 | 0 | g_autofree gchar *datastr = g_base64_encode(buf, bufsz); |
556 | 0 | xb_builder_node_insert_text(bn, "optional_data", datastr, NULL); |
557 | 0 | } |
558 | 0 | } |
559 | | |
560 | | static void |
561 | | fu_efi_load_option_add_json(FwupdCodec *codec, JsonBuilder *builder, FwupdCodecFlags flags) |
562 | 0 | { |
563 | 0 | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(codec); |
564 | 0 | g_autoptr(FuFirmware) dp_list = NULL; |
565 | |
|
566 | 0 | fwupd_codec_json_append(builder, "Name", fu_firmware_get_id(FU_FIRMWARE(self))); |
567 | 0 | if (self->kind != FU_EFI_LOAD_OPTION_KIND_UNKNOWN) { |
568 | 0 | fwupd_codec_json_append(builder, |
569 | 0 | "Kind", |
570 | 0 | fu_efi_load_option_kind_to_string(self->kind)); |
571 | 0 | } |
572 | 0 | if (g_hash_table_size(self->metadata) > 0) { |
573 | 0 | GHashTableIter iter; |
574 | 0 | const gchar *key; |
575 | 0 | const gchar *value; |
576 | 0 | json_builder_set_member_name(builder, "Metadata"); |
577 | 0 | json_builder_begin_object(builder); |
578 | 0 | g_hash_table_iter_init(&iter, self->metadata); |
579 | 0 | while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) |
580 | 0 | fwupd_codec_json_append(builder, key, value); |
581 | 0 | json_builder_end_object(builder); |
582 | 0 | } |
583 | 0 | dp_list = |
584 | 0 | fu_firmware_get_image_by_gtype(FU_FIRMWARE(self), FU_TYPE_EFI_DEVICE_PATH_LIST, NULL); |
585 | 0 | if (dp_list != NULL) |
586 | 0 | fwupd_codec_to_json(FWUPD_CODEC(dp_list), builder, flags); |
587 | 0 | } |
588 | | |
589 | | static void |
590 | | fu_efi_load_option_codec_iface_init(FwupdCodecInterface *iface) |
591 | 1 | { |
592 | 1 | iface->add_json = fu_efi_load_option_add_json; |
593 | 1 | } |
594 | | |
595 | | static void |
596 | | fu_efi_load_option_finalize(GObject *obj) |
597 | 2.02k | { |
598 | 2.02k | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(obj); |
599 | 2.02k | if (self->optional_data != NULL) |
600 | 539 | g_bytes_unref(self->optional_data); |
601 | 2.02k | g_hash_table_unref(self->metadata); |
602 | 2.02k | G_OBJECT_CLASS(fu_efi_load_option_parent_class)->finalize(obj); |
603 | 2.02k | } |
604 | | |
605 | | static void |
606 | | fu_efi_load_option_class_init(FuEfiLoadOptionClass *klass) |
607 | 1 | { |
608 | 1 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
609 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
610 | 1 | object_class->finalize = fu_efi_load_option_finalize; |
611 | 1 | firmware_class->parse = fu_efi_load_option_parse; |
612 | 1 | firmware_class->write = fu_efi_load_option_write; |
613 | 1 | firmware_class->build = fu_efi_load_option_build; |
614 | 1 | firmware_class->export = fu_efi_load_option_export; |
615 | 1 | } |
616 | | |
617 | | static void |
618 | | fu_efi_load_option_init(FuEfiLoadOption *self) |
619 | 2.02k | { |
620 | 2.02k | self->attrs = FU_EFI_LOAD_OPTION_ATTR_ACTIVE; |
621 | 2.02k | self->metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); |
622 | 2.02k | g_type_ensure(FU_TYPE_EFI_DEVICE_PATH_LIST); |
623 | 2.02k | } |
624 | | |
625 | | /** |
626 | | * fu_efi_load_option_new: |
627 | | * |
628 | | * Returns: (transfer full): a #FuEfiLoadOption |
629 | | * |
630 | | * Since: 1.9.3 |
631 | | **/ |
632 | | FuEfiLoadOption * |
633 | | fu_efi_load_option_new(void) |
634 | 0 | { |
635 | 0 | return g_object_new(FU_TYPE_EFI_LOAD_OPTION, NULL); |
636 | 0 | } |