/src/fwupd/libfwupdplugin/fu-csv-firmware.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 | | #include "config.h" |
8 | | |
9 | | #include "fu-byte-array.h" |
10 | | #include "fu-common.h" |
11 | | #include "fu-csv-entry.h" |
12 | | #include "fu-csv-firmware-private.h" |
13 | | #include "fu-string.h" |
14 | | |
15 | | /** |
16 | | * FuCsvFirmware: |
17 | | * |
18 | | * A comma seporated value file. |
19 | | * |
20 | | * See also: [class@FuFirmware] |
21 | | */ |
22 | | |
23 | | typedef struct { |
24 | | GPtrArray *column_ids; |
25 | | gboolean write_column_ids; |
26 | | } FuCsvFirmwarePrivate; |
27 | | |
28 | | G_DEFINE_TYPE_WITH_PRIVATE(FuCsvFirmware, fu_csv_firmware, FU_TYPE_FIRMWARE) |
29 | 14.4M | #define GET_PRIVATE(o) (fu_csv_firmware_get_instance_private(o)) |
30 | | |
31 | | /** |
32 | | * fu_csv_firmware_add_column_id: |
33 | | * @self: a #FuFirmware |
34 | | * @column_id: (not nullable): string, e.g. `component_generation` |
35 | | * |
36 | | * Adds a column ID. |
37 | | * |
38 | | * There are several optional magic column IDs that map to #FuFirmware properties: |
39 | | * |
40 | | * * `$id` sets the firmware ID |
41 | | * * `$idx` sets the firmware index |
42 | | * * `$version` sets the firmware version |
43 | | * * `$version_raw` sets the raw firmware version |
44 | | * |
45 | | * Since: 1.9.3 |
46 | | **/ |
47 | | void |
48 | | fu_csv_firmware_add_column_id(FuCsvFirmware *self, const gchar *column_id) |
49 | 14.1M | { |
50 | 14.1M | FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); |
51 | 14.1M | g_return_if_fail(FU_IS_CSV_FIRMWARE(self)); |
52 | 14.1M | g_return_if_fail(column_id != NULL); |
53 | 14.1M | g_ptr_array_add(priv->column_ids, g_strdup(column_id)); |
54 | 14.1M | } |
55 | | |
56 | | /** |
57 | | * fu_csv_firmware_get_column_id: |
58 | | * @self: a #FuFirmware |
59 | | * @idx: column ID idx |
60 | | * |
61 | | * Gets the column ID for a specific index position. |
62 | | * |
63 | | * Returns: a string, or %NULL if not found |
64 | | * |
65 | | * Since: 1.9.3 |
66 | | **/ |
67 | | const gchar * |
68 | | fu_csv_firmware_get_column_id(FuCsvFirmware *self, guint idx) |
69 | 253k | { |
70 | 253k | FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); |
71 | 253k | g_return_val_if_fail(FU_IS_CSV_FIRMWARE(self), NULL); |
72 | | |
73 | 253k | if (idx >= priv->column_ids->len) |
74 | 175k | return NULL; |
75 | 77.8k | return g_ptr_array_index(priv->column_ids, idx); |
76 | 253k | } |
77 | | |
78 | | /** |
79 | | * fu_csv_firmware_get_idx_for_column_id: |
80 | | * @self: a #FuFirmware |
81 | | * @column_id: (not nullable): string, e.g. `component_generation` |
82 | | * |
83 | | * Gets the column index for a given column ID. |
84 | | * |
85 | | * Returns: position, or %G_MAXUINT if unset |
86 | | * |
87 | | * Since: 1.9.3 |
88 | | **/ |
89 | | guint |
90 | | fu_csv_firmware_get_idx_for_column_id(FuCsvFirmware *self, const gchar *column_id) |
91 | 0 | { |
92 | 0 | FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); |
93 | 0 | g_return_val_if_fail(FU_IS_CSV_FIRMWARE(self), G_MAXUINT); |
94 | 0 | g_return_val_if_fail(column_id != NULL, G_MAXUINT); |
95 | 0 | for (guint i = 0; i < priv->column_ids->len; i++) { |
96 | 0 | const gchar *column_id_tmp = g_ptr_array_index(priv->column_ids, i); |
97 | 0 | if (g_strcmp0(column_id_tmp, column_id) == 0) |
98 | 0 | return i; |
99 | 0 | } |
100 | 0 | return G_MAXUINT; |
101 | 0 | } |
102 | | |
103 | | /** |
104 | | * fu_csv_firmware_set_write_column_ids: |
105 | | * @self: a #FuFirmware |
106 | | * @write_column_ids: boolean |
107 | | * |
108 | | * Sets if we should write the column ID headers on export. |
109 | | * |
110 | | * Since: 2.0.0 |
111 | | **/ |
112 | | void |
113 | | fu_csv_firmware_set_write_column_ids(FuCsvFirmware *self, gboolean write_column_ids) |
114 | 0 | { |
115 | 0 | FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); |
116 | 0 | g_return_if_fail(FU_IS_CSV_FIRMWARE(self)); |
117 | 0 | priv->write_column_ids = write_column_ids; |
118 | 0 | } |
119 | | |
120 | | /** |
121 | | * fu_csv_firmware_get_write_column_ids: |
122 | | * @self: a #FuFirmware |
123 | | * |
124 | | * Gets if we should write the column ID headers on export. |
125 | | * |
126 | | * Returns: boolean. |
127 | | * |
128 | | * Since: 2.0.0 |
129 | | **/ |
130 | | gboolean |
131 | | fu_csv_firmware_get_write_column_ids(FuCsvFirmware *self) |
132 | 0 | { |
133 | 0 | FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); |
134 | 0 | g_return_val_if_fail(FU_IS_CSV_FIRMWARE(self), FALSE); |
135 | 0 | return priv->write_column_ids; |
136 | 0 | } |
137 | | |
138 | | static gboolean |
139 | | fu_csv_firmware_parse_token_cb(GString *token, guint token_idx, gpointer user_data, GError **error) |
140 | 14.1M | { |
141 | 14.1M | FuCsvFirmware *self = FU_CSV_FIRMWARE(user_data); |
142 | 14.1M | fu_csv_firmware_add_column_id(self, token->str); |
143 | 14.1M | return TRUE; |
144 | 14.1M | } |
145 | | |
146 | | static gboolean |
147 | | fu_csv_firmware_parse_line_cb(GString *token, guint token_idx, gpointer user_data, GError **error) |
148 | 949k | { |
149 | 949k | FuCsvFirmware *self = FU_CSV_FIRMWARE(user_data); |
150 | 949k | g_autoptr(FuFirmware) entry = fu_csv_entry_new(); |
151 | 949k | g_autoptr(GBytes) fw = NULL; |
152 | | |
153 | | /* ignore blank lines */ |
154 | 949k | if (token->len == 0) |
155 | 755k | return TRUE; |
156 | | |
157 | | /* title */ |
158 | 193k | if (g_str_has_prefix(token->str, "#")) { |
159 | 9.35k | return fu_strsplit_full(token->str + 1, |
160 | 9.35k | token->len - 1, |
161 | 9.35k | ",", |
162 | 9.35k | fu_csv_firmware_parse_token_cb, |
163 | 9.35k | self, |
164 | 9.35k | error); |
165 | 9.35k | } |
166 | | |
167 | | /* parse entry */ |
168 | 184k | fw = g_bytes_new(token->str, token->len); |
169 | 184k | fu_firmware_set_idx(entry, token_idx); |
170 | 184k | if (!fu_firmware_add_image_full(FU_FIRMWARE(self), entry, error)) |
171 | 4 | return FALSE; |
172 | 184k | if (!fu_firmware_parse_bytes(entry, fw, 0x0, FU_FIRMWARE_PARSE_FLAG_NONE, error)) |
173 | 48 | return FALSE; |
174 | 184k | return TRUE; |
175 | 184k | } |
176 | | |
177 | | static gboolean |
178 | | fu_csv_firmware_parse(FuFirmware *firmware, |
179 | | GInputStream *stream, |
180 | | FuFirmwareParseFlags flags, |
181 | | GError **error) |
182 | 1.72k | { |
183 | 1.72k | FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware); |
184 | 1.72k | return fu_strsplit_stream(stream, 0x0, "\n", fu_csv_firmware_parse_line_cb, self, error); |
185 | 1.72k | } |
186 | | |
187 | | static GByteArray * |
188 | | fu_csv_firmware_write(FuFirmware *firmware, GError **error) |
189 | 1.67k | { |
190 | 1.67k | FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware); |
191 | 1.67k | FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); |
192 | 1.67k | g_autoptr(GByteArray) buf = g_byte_array_new(); |
193 | 1.67k | g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); |
194 | | |
195 | | /* title section */ |
196 | 1.67k | if (priv->write_column_ids) { |
197 | 1.67k | g_autoptr(GString) str = g_string_new("#"); |
198 | 12.0M | for (guint i = 0; i < priv->column_ids->len; i++) { |
199 | 12.0M | const gchar *column_id = g_ptr_array_index(priv->column_ids, i); |
200 | 12.0M | if (str->len > 1) |
201 | 9.83M | g_string_append(str, ","); |
202 | 12.0M | g_string_append(str, column_id); |
203 | 12.0M | } |
204 | 1.67k | g_string_append(str, "\n"); |
205 | 1.67k | g_byte_array_append(buf, (const guint8 *)str->str, str->len); |
206 | 1.67k | } |
207 | | |
208 | | /* each entry */ |
209 | 140k | for (guint i = 0; i < imgs->len; i++) { |
210 | 138k | FuFirmware *img = g_ptr_array_index(imgs, i); |
211 | 138k | g_autoptr(GBytes) img_blob = fu_firmware_write(img, error); |
212 | 138k | if (img_blob == NULL) |
213 | 0 | return NULL; |
214 | 138k | fu_byte_array_append_bytes(buf, img_blob); |
215 | 138k | } |
216 | | |
217 | | /* success */ |
218 | 1.67k | return g_steal_pointer(&buf); |
219 | 1.67k | } |
220 | | |
221 | | static void |
222 | | fu_csv_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
223 | 0 | { |
224 | 0 | FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware); |
225 | 0 | FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); |
226 | 0 | fu_xmlb_builder_insert_kb(bn, "write_column_ids", priv->write_column_ids); |
227 | 0 | } |
228 | | |
229 | | static gboolean |
230 | | fu_csv_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) |
231 | 0 | { |
232 | 0 | FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware); |
233 | 0 | FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); |
234 | 0 | const gchar *tmp; |
235 | | |
236 | | /* optional properties */ |
237 | 0 | tmp = xb_node_query_text(n, "write_column_ids", NULL); |
238 | 0 | if (tmp != NULL) { |
239 | 0 | if (!fu_strtobool(tmp, &priv->write_column_ids, error)) |
240 | 0 | return FALSE; |
241 | 0 | } |
242 | | |
243 | | /* success */ |
244 | 0 | return TRUE; |
245 | 0 | } |
246 | | |
247 | | static void |
248 | | fu_csv_firmware_init(FuCsvFirmware *self) |
249 | 1.72k | { |
250 | 1.72k | FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); |
251 | 1.72k | priv->column_ids = g_ptr_array_new_with_free_func(g_free); |
252 | 1.72k | priv->write_column_ids = TRUE; |
253 | 1.72k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); |
254 | 1.72k | fu_firmware_set_images_max(FU_FIRMWARE(self), 10000); |
255 | 1.72k | g_type_ensure(FU_TYPE_CSV_ENTRY); |
256 | 1.72k | } |
257 | | |
258 | | static void |
259 | | fu_csv_firmware_finalize(GObject *object) |
260 | 1.72k | { |
261 | 1.72k | FuCsvFirmware *self = FU_CSV_FIRMWARE(object); |
262 | 1.72k | FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); |
263 | 1.72k | g_ptr_array_unref(priv->column_ids); |
264 | 1.72k | G_OBJECT_CLASS(fu_csv_firmware_parent_class)->finalize(object); |
265 | 1.72k | } |
266 | | |
267 | | static void |
268 | | fu_csv_firmware_class_init(FuCsvFirmwareClass *klass) |
269 | 1 | { |
270 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
271 | 1 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
272 | 1 | object_class->finalize = fu_csv_firmware_finalize; |
273 | 1 | firmware_class->parse = fu_csv_firmware_parse; |
274 | 1 | firmware_class->write = fu_csv_firmware_write; |
275 | 1 | firmware_class->export = fu_csv_firmware_export; |
276 | 1 | firmware_class->build = fu_csv_firmware_build; |
277 | 1 | } |
278 | | |
279 | | /** |
280 | | * fu_csv_firmware_new: |
281 | | * |
282 | | * Creates a new #FuFirmware |
283 | | * |
284 | | * Since: 1.9.3 |
285 | | **/ |
286 | | FuFirmware * |
287 | | fu_csv_firmware_new(void) |
288 | 0 | { |
289 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_CSV_FIRMWARE, NULL)); |
290 | 0 | } |