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