/src/fwupd/libfwupdplugin/fu-efi-load-option.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2023 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 1.30k | #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 | | G_DEFINE_TYPE_EXTENDED(FuEfiLoadOption, |
30 | | fu_efi_load_option, |
31 | | FU_TYPE_FIRMWARE, |
32 | | 0, |
33 | | G_IMPLEMENT_INTERFACE(FWUPD_TYPE_CODEC, fu_efi_load_option_codec_iface_init)) |
34 | | |
35 | 55.7k | #define FU_EFI_LOAD_OPTION_DESCRIPTION_SIZE_MAX 0x1000u /* bytes */ |
36 | | |
37 | 935 | #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 | 626 | { |
42 | 626 | g_return_if_fail(FU_IS_EFI_LOAD_OPTION(self)); |
43 | 626 | if (self->optional_data != NULL) { |
44 | 0 | g_bytes_unref(self->optional_data); |
45 | 0 | self->optional_data = NULL; |
46 | 0 | } |
47 | 626 | if (optional_data != NULL) |
48 | 626 | self->optional_data = g_bytes_ref(optional_data); |
49 | 626 | } |
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.26k | { |
135 | 4.26k | g_return_if_fail(FU_IS_EFI_LOAD_OPTION(self)); |
136 | 4.26k | g_return_if_fail(key != NULL); |
137 | | |
138 | 4.26k | if (value == NULL) { |
139 | 0 | g_hash_table_remove(self->metadata, key); |
140 | 0 | return; |
141 | 0 | } |
142 | | /* auto-set something sensible */ |
143 | 4.26k | if (self->kind == FU_EFI_LOAD_OPTION_KIND_UNKNOWN && |
144 | 4.26k | g_strcmp0(key, FU_EFI_LOAD_OPTION_METADATA_PATH) == 0) { |
145 | 133 | self->kind = FU_EFI_LOAD_OPTION_KIND_PATH; |
146 | 4.13k | } else { |
147 | 4.13k | self->kind = FU_EFI_LOAD_OPTION_KIND_HIVE; |
148 | 4.13k | } |
149 | 4.26k | if (g_strcmp0(key, FU_EFI_LOAD_OPTION_METADATA_PATH) == 0 && value != NULL && |
150 | 4.26k | g_str_has_prefix(value, "\\")) { |
151 | 69 | value++; |
152 | 69 | } |
153 | 4.26k | g_hash_table_insert(self->metadata, g_strdup(key), g_strdup(value)); |
154 | 4.26k | } |
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.42k | { |
162 | 1.42k | g_autoptr(FuStructShimHive) st = NULL; |
163 | 1.42k | guint8 items_count; |
164 | | |
165 | 1.42k | st = fu_struct_shim_hive_parse_stream(stream, offset, error); |
166 | 1.42k | if (st == NULL) |
167 | 490 | return FALSE; |
168 | 935 | if (fu_struct_shim_hive_get_header_version(st) < |
169 | 935 | 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 | 934 | offset += fu_struct_shim_hive_get_items_offset(st); |
178 | | |
179 | | /* items */ |
180 | 934 | items_count = fu_struct_shim_hive_get_items_count(st); |
181 | 5.14k | for (guint i = 0; i < items_count; i++) { |
182 | 4.79k | guint8 keysz; |
183 | 4.79k | guint32 valuesz; |
184 | 4.79k | g_autofree gchar *key = NULL; |
185 | 4.79k | g_autofree gchar *value = NULL; |
186 | 4.79k | g_autoptr(FuStructShimHiveItem) st_item = NULL; |
187 | | |
188 | 4.79k | st_item = fu_struct_shim_hive_item_parse_stream(stream, offset, error); |
189 | 4.79k | if (st_item == NULL) |
190 | 351 | return FALSE; |
191 | 4.44k | offset += st_item->len; |
192 | | |
193 | | /* key */ |
194 | 4.44k | keysz = fu_struct_shim_hive_item_get_key_length(st_item); |
195 | 4.44k | if (keysz == 0) { |
196 | 5 | g_set_error_literal(error, |
197 | 5 | FWUPD_ERROR, |
198 | 5 | FWUPD_ERROR_NOT_SUPPORTED, |
199 | 5 | "zero key size is not supported"); |
200 | 5 | return FALSE; |
201 | 5 | } |
202 | 4.43k | key = fu_input_stream_read_string(stream, offset, keysz, error); |
203 | 4.43k | if (key == NULL) |
204 | 108 | return FALSE; |
205 | 4.32k | offset += keysz; |
206 | | |
207 | | /* value */ |
208 | 4.32k | valuesz = fu_struct_shim_hive_item_get_value_length(st_item); |
209 | 4.32k | if (valuesz > 0) { |
210 | 1.78k | value = fu_input_stream_read_string(stream, offset, valuesz, error); |
211 | 1.78k | if (value == NULL) |
212 | 117 | return FALSE; |
213 | 1.66k | offset += valuesz; |
214 | 1.66k | } |
215 | 4.21k | fu_efi_load_option_set_metadata(self, key, value != NULL ? value : ""); |
216 | 4.21k | } |
217 | | |
218 | | /* success */ |
219 | 353 | return TRUE; |
220 | 934 | } |
221 | | |
222 | | static gboolean |
223 | | fu_efi_load_option_parse_optional_path(FuEfiLoadOption *self, GBytes *opt_blob, GError **error) |
224 | 681 | { |
225 | 681 | g_autofree gchar *optional_path = NULL; |
226 | | |
227 | | /* convert to UTF-8 */ |
228 | 681 | optional_path = fu_utf16_to_utf8_bytes(opt_blob, G_LITTLE_ENDIAN, error); |
229 | 681 | if (optional_path == NULL) |
230 | 273 | return FALSE; |
231 | | |
232 | | /* check is ASCII */ |
233 | 408 | if (optional_path[0] == '\0' || !g_str_is_ascii(optional_path)) { |
234 | 353 | g_set_error(error, |
235 | 353 | FWUPD_ERROR, |
236 | 353 | FWUPD_ERROR_NOT_SUPPORTED, |
237 | 353 | "not ASCII data: %s", |
238 | 353 | optional_path); |
239 | 353 | return FALSE; |
240 | 353 | } |
241 | 55 | fu_efi_load_option_set_metadata(self, FU_EFI_LOAD_OPTION_METADATA_PATH, optional_path); |
242 | | |
243 | | /* success */ |
244 | 55 | return TRUE; |
245 | 408 | } |
246 | | |
247 | | static gboolean |
248 | | fu_efi_load_option_parse_optional(FuEfiLoadOption *self, |
249 | | GInputStream *stream, |
250 | | gsize offset, |
251 | | GError **error) |
252 | 1.42k | { |
253 | 1.42k | gsize streamsz = 0; |
254 | 1.42k | g_autoptr(GBytes) opt_blob = NULL; |
255 | 1.42k | g_autoptr(GError) error_hive = NULL; |
256 | 1.42k | g_autoptr(GError) error_path = NULL; |
257 | | |
258 | | /* try hive structure first */ |
259 | 1.42k | if (!fu_efi_load_option_parse_optional_hive(self, stream, offset, &error_hive)) { |
260 | 1.07k | if (!g_error_matches(error_hive, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA)) { |
261 | 391 | g_propagate_error(error, g_steal_pointer(&error_hive)); |
262 | 391 | return FALSE; |
263 | 391 | } |
264 | 681 | g_debug("not a shim hive, ignoring: %s", error_hive->message); |
265 | 681 | } else { |
266 | 353 | self->kind = FU_EFI_LOAD_OPTION_KIND_HIVE; |
267 | 353 | return TRUE; |
268 | 353 | } |
269 | | |
270 | | /* then UCS-2 path, and on ASCII failure just treat as a raw data blob */ |
271 | 681 | if (!fu_input_stream_size(stream, &streamsz, error)) |
272 | 0 | return FALSE; |
273 | 681 | opt_blob = fu_input_stream_read_bytes(stream, offset, streamsz - offset, NULL, error); |
274 | 681 | if (opt_blob == NULL) |
275 | 0 | return FALSE; |
276 | 681 | if (!fu_efi_load_option_parse_optional_path(self, opt_blob, &error_path)) { |
277 | 626 | g_debug("not a path, saving as raw blob: %s", error_path->message); |
278 | 626 | fu_efi_load_option_set_optional_data(self, opt_blob); |
279 | 626 | } else { |
280 | 55 | self->kind = FU_EFI_LOAD_OPTION_KIND_PATH; |
281 | 55 | return TRUE; |
282 | 55 | } |
283 | | |
284 | | /* success */ |
285 | 626 | self->kind = FU_EFI_LOAD_OPTION_KIND_DATA; |
286 | 626 | return TRUE; |
287 | 681 | } |
288 | | |
289 | | static gboolean |
290 | | fu_efi_load_option_parse(FuFirmware *firmware, |
291 | | GInputStream *stream, |
292 | | FuFirmwareParseFlags flags, |
293 | | GError **error) |
294 | 2.06k | { |
295 | 2.06k | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); |
296 | 2.06k | gsize offset = 0; |
297 | 2.06k | gsize streamsz = 0; |
298 | 2.06k | g_autofree gchar *id = NULL; |
299 | 2.06k | g_autoptr(FuEfiDevicePathList) device_path_list = fu_efi_device_path_list_new(); |
300 | 2.06k | g_autoptr(GByteArray) buf_utf16 = g_byte_array_new(); |
301 | 2.06k | g_autoptr(GByteArray) st = NULL; |
302 | | |
303 | | /* parse header */ |
304 | 2.06k | st = fu_struct_efi_load_option_parse_stream(stream, offset, error); |
305 | 2.06k | if (st == NULL) |
306 | 9 | return FALSE; |
307 | 2.05k | self->attrs = fu_struct_efi_load_option_get_attrs(st); |
308 | 2.05k | offset += st->len; |
309 | | |
310 | | /* parse UTF-16 description */ |
311 | 2.05k | if (!fu_input_stream_size(stream, &streamsz, error)) |
312 | 0 | return FALSE; |
313 | 55.9k | for (; offset < streamsz; offset += 2) { |
314 | 55.7k | guint16 tmp = 0; |
315 | 55.7k | 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 | 55.7k | if (!fu_input_stream_read_u16(stream, offset, &tmp, G_LITTLE_ENDIAN, error)) |
324 | 30 | return FALSE; |
325 | 55.7k | if (tmp == 0) |
326 | 1.83k | break; |
327 | 53.9k | fu_byte_array_append_uint16(buf_utf16, tmp, G_LITTLE_ENDIAN); |
328 | 53.9k | } |
329 | 2.02k | id = fu_utf16_to_utf8_byte_array(buf_utf16, G_LITTLE_ENDIAN, error); |
330 | 2.02k | if (id == NULL) |
331 | 36 | return FALSE; |
332 | 1.98k | fu_firmware_set_id(firmware, id); |
333 | 1.98k | offset += 2; |
334 | | |
335 | | /* parse dp blob */ |
336 | 1.98k | if (!fu_firmware_parse_stream(FU_FIRMWARE(device_path_list), stream, offset, flags, error)) |
337 | 382 | return FALSE; |
338 | 1.60k | if (!fu_firmware_add_image_full(firmware, FU_FIRMWARE(device_path_list), error)) |
339 | 0 | return FALSE; |
340 | 1.60k | offset += fu_struct_efi_load_option_get_dp_size(st); |
341 | | |
342 | | /* optional data */ |
343 | 1.60k | if (offset < streamsz) { |
344 | 1.42k | if (!fu_efi_load_option_parse_optional(self, stream, offset, error)) |
345 | 391 | return FALSE; |
346 | 1.42k | } |
347 | | |
348 | | /* success */ |
349 | 1.21k | return TRUE; |
350 | 1.60k | } |
351 | | |
352 | | static GByteArray * |
353 | | fu_efi_load_option_write_hive(FuEfiLoadOption *self, GError **error) |
354 | 352 | { |
355 | 352 | GHashTableIter iter; |
356 | 352 | guint items_count = g_hash_table_size(self->metadata); |
357 | 352 | const gchar *key; |
358 | 352 | const gchar *value; |
359 | 352 | g_autoptr(FuStructShimHive) st = fu_struct_shim_hive_new(); |
360 | | |
361 | 352 | fu_struct_shim_hive_set_items_count(st, items_count); |
362 | 352 | fu_struct_shim_hive_set_items_offset(st, FU_STRUCT_SHIM_HIVE_SIZE); |
363 | 352 | g_hash_table_iter_init(&iter, self->metadata); |
364 | 1.19k | while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) { |
365 | 844 | guint32 keysz = strlen(key); |
366 | 844 | g_autoptr(FuStructShimHiveItem) st_item = fu_struct_shim_hive_item_new(); |
367 | 844 | g_autoptr(GString) value_safe = g_string_new(value); |
368 | | |
369 | | /* required prefix for a path */ |
370 | 844 | if (g_strcmp0(key, FU_EFI_LOAD_OPTION_METADATA_PATH) == 0 && value_safe->len > 0 && |
371 | 844 | !g_str_has_prefix(value_safe->str, "\\")) { |
372 | 66 | g_string_prepend(value_safe, "\\"); |
373 | 66 | } |
374 | 844 | fu_struct_shim_hive_item_set_key_length(st_item, keysz); |
375 | 844 | fu_struct_shim_hive_item_set_value_length(st_item, value_safe->len); |
376 | 844 | if (keysz > 0) |
377 | 844 | g_byte_array_append(st_item, (const guint8 *)key, keysz); |
378 | 844 | if (value_safe->len > 0) { |
379 | 576 | g_byte_array_append(st_item, |
380 | 576 | (const guint8 *)value_safe->str, |
381 | 576 | value_safe->len); |
382 | 576 | } |
383 | | |
384 | | /* add to hive */ |
385 | 844 | g_byte_array_append(st, st_item->data, st_item->len); |
386 | 844 | } |
387 | | |
388 | | /* this covers all items, and so has to be done last */ |
389 | 352 | fu_struct_shim_hive_set_crc32(st, fu_crc32(FU_CRC_KIND_B32_STANDARD, st->data, st->len)); |
390 | | |
391 | | /* success */ |
392 | 352 | return g_steal_pointer(&st); |
393 | 352 | } |
394 | | |
395 | | static GByteArray * |
396 | | fu_efi_load_option_write_path(FuEfiLoadOption *self, GError **error) |
397 | 46 | { |
398 | 46 | g_autoptr(GByteArray) buf = NULL; |
399 | 46 | const gchar *path = g_hash_table_lookup(self->metadata, FU_EFI_LOAD_OPTION_METADATA_PATH); |
400 | 46 | g_autoptr(GString) str = g_string_new(path); |
401 | | |
402 | | /* is required if a path */ |
403 | 46 | if (!g_str_has_prefix(str->str, "\\")) |
404 | 45 | g_string_prepend(str, "\\"); |
405 | 46 | buf = fu_utf8_to_utf16_byte_array(str->str, |
406 | 46 | G_LITTLE_ENDIAN, |
407 | 46 | FU_UTF_CONVERT_FLAG_APPEND_NUL, |
408 | 46 | error); |
409 | 46 | if (buf == NULL) |
410 | 0 | return NULL; |
411 | 46 | return g_steal_pointer(&buf); |
412 | 46 | } |
413 | | |
414 | | static GByteArray * |
415 | | fu_efi_load_option_write(FuFirmware *firmware, GError **error) |
416 | 1.21k | { |
417 | 1.21k | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); |
418 | 1.21k | g_autoptr(GByteArray) buf_utf16 = NULL; |
419 | 1.21k | g_autoptr(GByteArray) st = fu_struct_efi_load_option_new(); |
420 | 1.21k | g_autoptr(GBytes) dpbuf = NULL; |
421 | | |
422 | | /* header */ |
423 | 1.21k | fu_struct_efi_load_option_set_attrs(st, self->attrs); |
424 | | |
425 | | /* label */ |
426 | 1.21k | if (fu_firmware_get_id(firmware) == NULL) { |
427 | 0 | g_set_error_literal(error, |
428 | 0 | FWUPD_ERROR, |
429 | 0 | FWUPD_ERROR_INVALID_DATA, |
430 | 0 | "firmware ID required"); |
431 | 0 | return NULL; |
432 | 0 | } |
433 | 1.21k | buf_utf16 = fu_utf8_to_utf16_byte_array(fu_firmware_get_id(firmware), |
434 | 1.21k | G_LITTLE_ENDIAN, |
435 | 1.21k | FU_UTF_CONVERT_FLAG_APPEND_NUL, |
436 | 1.21k | error); |
437 | 1.21k | if (buf_utf16 == NULL) |
438 | 0 | return NULL; |
439 | 1.21k | g_byte_array_append(st, buf_utf16->data, buf_utf16->len); |
440 | | |
441 | | /* dpbuf */ |
442 | 1.21k | dpbuf = fu_firmware_get_image_by_gtype_bytes(firmware, FU_TYPE_EFI_DEVICE_PATH_LIST, error); |
443 | 1.21k | if (dpbuf == NULL) |
444 | 444 | return NULL; |
445 | 770 | fu_struct_efi_load_option_set_dp_size(st, g_bytes_get_size(dpbuf)); |
446 | 770 | fu_byte_array_append_bytes(st, dpbuf); |
447 | | |
448 | | /* hive, path or data */ |
449 | 770 | if (self->kind == FU_EFI_LOAD_OPTION_KIND_HIVE) { |
450 | 352 | g_autoptr(GByteArray) buf_hive = NULL; |
451 | 352 | buf_hive = fu_efi_load_option_write_hive(self, error); |
452 | 352 | if (buf_hive == NULL) |
453 | 0 | return NULL; |
454 | 352 | g_byte_array_append(st, buf_hive->data, buf_hive->len); |
455 | 352 | fu_byte_array_align_up(st, FU_FIRMWARE_ALIGNMENT_512, 0x0); /* make atomic */ |
456 | 418 | } else if (self->kind == FU_EFI_LOAD_OPTION_KIND_PATH) { |
457 | 46 | g_autoptr(GByteArray) buf_path = NULL; |
458 | 46 | buf_path = fu_efi_load_option_write_path(self, error); |
459 | 46 | if (buf_path == NULL) |
460 | 0 | return NULL; |
461 | 46 | g_byte_array_append(st, buf_path->data, buf_path->len); |
462 | 372 | } else if (self->kind == FU_EFI_LOAD_OPTION_KIND_DATA && self->optional_data != NULL) { |
463 | 291 | fu_byte_array_append_bytes(st, self->optional_data); |
464 | 291 | } |
465 | | |
466 | | /* success */ |
467 | 770 | return g_steal_pointer(&st); |
468 | 770 | } |
469 | | |
470 | | static gboolean |
471 | | fu_efi_load_option_build(FuFirmware *firmware, XbNode *n, GError **error) |
472 | 0 | { |
473 | 0 | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); |
474 | 0 | const gchar *str; |
475 | 0 | guint64 tmp; |
476 | 0 | g_autoptr(GPtrArray) metadata = NULL; |
477 | 0 | g_autoptr(XbNode) optional_data = NULL; |
478 | | |
479 | | /* simple properties */ |
480 | 0 | tmp = xb_node_query_text_as_uint(n, "attrs", NULL); |
481 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) |
482 | 0 | self->attrs = tmp; |
483 | | |
484 | | /* simple properties */ |
485 | 0 | str = xb_node_query_text(n, "kind", NULL); |
486 | 0 | if (str != NULL) { |
487 | 0 | self->kind = fu_efi_load_option_kind_from_string(str); |
488 | 0 | if (self->kind == FU_EFI_LOAD_OPTION_KIND_UNKNOWN) { |
489 | 0 | g_set_error(error, |
490 | 0 | FWUPD_ERROR, |
491 | 0 | FWUPD_ERROR_INVALID_DATA, |
492 | 0 | "invalid option kind type %s", |
493 | 0 | str); |
494 | 0 | return FALSE; |
495 | 0 | } |
496 | 0 | } |
497 | | |
498 | | /* optional data */ |
499 | 0 | optional_data = xb_node_query_first(n, "optional_data", NULL); |
500 | 0 | if (optional_data != NULL) { |
501 | 0 | g_autoptr(GBytes) blob = NULL; |
502 | 0 | if (xb_node_get_text(optional_data) != NULL) { |
503 | 0 | gsize bufsz = 0; |
504 | 0 | g_autofree guchar *buf = NULL; |
505 | 0 | buf = g_base64_decode(xb_node_get_text(optional_data), &bufsz); |
506 | 0 | blob = g_bytes_new(buf, bufsz); |
507 | 0 | } else { |
508 | 0 | blob = g_bytes_new(NULL, 0); |
509 | 0 | } |
510 | 0 | fu_efi_load_option_set_optional_data(self, blob); |
511 | 0 | self->kind = FU_EFI_LOAD_OPTION_KIND_DATA; |
512 | 0 | } |
513 | 0 | metadata = xb_node_query(n, "metadata/*", 0, NULL); |
514 | 0 | if (metadata != NULL) { |
515 | 0 | for (guint i = 0; i < metadata->len; i++) { |
516 | 0 | XbNode *c = g_ptr_array_index(metadata, i); |
517 | 0 | const gchar *value = xb_node_get_text(c); |
518 | 0 | if (xb_node_get_element(c) == NULL) |
519 | 0 | continue; |
520 | 0 | fu_efi_load_option_set_metadata(self, |
521 | 0 | xb_node_get_element(c), |
522 | 0 | value != NULL ? value : ""); |
523 | 0 | } |
524 | 0 | } |
525 | | |
526 | | /* success */ |
527 | 0 | return TRUE; |
528 | 0 | } |
529 | | |
530 | | static void |
531 | | fu_efi_load_option_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
532 | 0 | { |
533 | 0 | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); |
534 | |
|
535 | 0 | fu_xmlb_builder_insert_kx(bn, "attrs", self->attrs); |
536 | 0 | if (self->kind != FU_EFI_LOAD_OPTION_KIND_UNKNOWN) { |
537 | 0 | fu_xmlb_builder_insert_kv(bn, |
538 | 0 | "kind", |
539 | 0 | fu_efi_load_option_kind_to_string(self->kind)); |
540 | 0 | } |
541 | 0 | if (g_hash_table_size(self->metadata) > 0) { |
542 | 0 | GHashTableIter iter; |
543 | 0 | const gchar *key; |
544 | 0 | const gchar *value; |
545 | 0 | g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "metadata", NULL); |
546 | 0 | g_hash_table_iter_init(&iter, self->metadata); |
547 | 0 | while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) |
548 | 0 | xb_builder_node_insert_text(bc, key, value, NULL); |
549 | 0 | } |
550 | 0 | if (self->optional_data != NULL) { |
551 | 0 | gsize bufsz = 0; |
552 | 0 | const guint8 *buf = g_bytes_get_data(self->optional_data, &bufsz); |
553 | 0 | g_autofree gchar *datastr = g_base64_encode(buf, bufsz); |
554 | 0 | xb_builder_node_insert_text(bn, "optional_data", datastr, NULL); |
555 | 0 | } |
556 | 0 | } |
557 | | |
558 | | static void |
559 | | fu_efi_load_option_add_json(FwupdCodec *codec, JsonBuilder *builder, FwupdCodecFlags flags) |
560 | 0 | { |
561 | 0 | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(codec); |
562 | 0 | g_autoptr(FuFirmware) dp_list = NULL; |
563 | |
|
564 | 0 | fwupd_codec_json_append(builder, "Name", fu_firmware_get_id(FU_FIRMWARE(self))); |
565 | 0 | if (self->kind != FU_EFI_LOAD_OPTION_KIND_UNKNOWN) { |
566 | 0 | fwupd_codec_json_append(builder, |
567 | 0 | "Kind", |
568 | 0 | fu_efi_load_option_kind_to_string(self->kind)); |
569 | 0 | } |
570 | 0 | if (g_hash_table_size(self->metadata) > 0) { |
571 | 0 | GHashTableIter iter; |
572 | 0 | const gchar *key; |
573 | 0 | const gchar *value; |
574 | 0 | json_builder_set_member_name(builder, "Metadata"); |
575 | 0 | json_builder_begin_object(builder); |
576 | 0 | g_hash_table_iter_init(&iter, self->metadata); |
577 | 0 | while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) |
578 | 0 | fwupd_codec_json_append(builder, key, value); |
579 | 0 | json_builder_end_object(builder); |
580 | 0 | } |
581 | 0 | dp_list = |
582 | 0 | fu_firmware_get_image_by_gtype(FU_FIRMWARE(self), FU_TYPE_EFI_DEVICE_PATH_LIST, NULL); |
583 | 0 | if (dp_list != NULL) |
584 | 0 | fwupd_codec_to_json(FWUPD_CODEC(dp_list), builder, flags); |
585 | 0 | } |
586 | | |
587 | | static void |
588 | | fu_efi_load_option_codec_iface_init(FwupdCodecInterface *iface) |
589 | 1 | { |
590 | 1 | iface->add_json = fu_efi_load_option_add_json; |
591 | 1 | } |
592 | | |
593 | | static void |
594 | | fu_efi_load_option_finalize(GObject *obj) |
595 | 2.06k | { |
596 | 2.06k | FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(obj); |
597 | 2.06k | if (self->optional_data != NULL) |
598 | 626 | g_bytes_unref(self->optional_data); |
599 | 2.06k | g_hash_table_unref(self->metadata); |
600 | 2.06k | G_OBJECT_CLASS(fu_efi_load_option_parent_class)->finalize(obj); |
601 | 2.06k | } |
602 | | |
603 | | static void |
604 | | fu_efi_load_option_class_init(FuEfiLoadOptionClass *klass) |
605 | 1 | { |
606 | 1 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
607 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
608 | 1 | object_class->finalize = fu_efi_load_option_finalize; |
609 | 1 | firmware_class->parse = fu_efi_load_option_parse; |
610 | 1 | firmware_class->write = fu_efi_load_option_write; |
611 | 1 | firmware_class->build = fu_efi_load_option_build; |
612 | 1 | firmware_class->export = fu_efi_load_option_export; |
613 | 1 | } |
614 | | |
615 | | static void |
616 | | fu_efi_load_option_init(FuEfiLoadOption *self) |
617 | 2.06k | { |
618 | 2.06k | self->attrs = FU_EFI_LOAD_OPTION_ATTRS_ACTIVE; |
619 | 2.06k | self->metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); |
620 | 2.06k | g_type_ensure(FU_TYPE_EFI_DEVICE_PATH_LIST); |
621 | 2.06k | } |
622 | | |
623 | | /** |
624 | | * fu_efi_load_option_new: |
625 | | * |
626 | | * Returns: (transfer full): a #FuEfiLoadOption |
627 | | * |
628 | | * Since: 1.9.3 |
629 | | **/ |
630 | | FuEfiLoadOption * |
631 | | fu_efi_load_option_new(void) |
632 | 0 | { |
633 | 0 | return g_object_new(FU_TYPE_EFI_LOAD_OPTION, NULL); |
634 | 0 | } |