Coverage Report

Created: 2025-08-24 07:10

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