Coverage Report

Created: 2026-04-01 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ostree/src/libostree/ostree-bootconfig-parser.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2013 Colin Walters <walters@verbum.org>
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published
6
 * by the Free Software Foundation; either version 2 of the licence or (at
7
 * your option) any later version.
8
 *
9
 * This library is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General
15
 * Public License along with this library. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
#include "config.h"
19
20
#include "ostree-bootconfig-parser-private.h"
21
#include "otutil.h"
22
23
struct _OstreeBootconfigParser
24
{
25
  GObject parent_instance;
26
27
  char *filename;
28
  const char *separators;
29
30
  guint64 tries_left;
31
  guint64 tries_done;
32
33
  GHashTable *options;
34
35
  /* Additional initrds; the primary initrd is in options. */
36
  char **overlay_initrds;
37
};
38
39
typedef GObjectClass OstreeBootconfigParserClass;
40
41
0
G_DEFINE_TYPE (OstreeBootconfigParser, ostree_bootconfig_parser, G_TYPE_OBJECT)
42
0
43
0
/**
44
0
 * ostree_bootconfig_parser_clone:
45
0
 * @self: Bootconfig to clone
46
0
 *
47
0
 * Returns: (transfer full): Copy of @self
48
0
 */
49
0
OstreeBootconfigParser *
50
0
ostree_bootconfig_parser_clone (OstreeBootconfigParser *self)
51
0
{
52
0
  OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
53
54
0
  GLNX_HASH_TABLE_FOREACH_KV (self->options, const char *, k, const char *, v)
55
0
    g_hash_table_replace (parser->options, g_strdup (k), g_strdup (v));
56
57
0
  parser->filename = g_strdup (self->filename);
58
0
  parser->overlay_initrds = g_strdupv (self->overlay_initrds);
59
60
0
  return parser;
61
0
}
62
63
/*
64
 * Parses a suffix of two counters in the form "+LEFT-DONE" from the end of the
65
 * filename (excluding file extension).
66
 */
67
static void
68
parse_bootloader_tries (const char *filename, guint64 *out_left, guint64 *out_done)
69
0
{
70
0
  *out_left = 0;
71
0
  *out_done = 0;
72
73
0
  const char *counter = strrchr (filename, '+');
74
0
  if (!counter)
75
0
    return;
76
0
  counter += 1;
77
78
0
  guint64 tries_left = 0;
79
0
  guint64 tries_done = 0;
80
81
  // Negative numbers are invalid
82
0
  if (*counter == '-')
83
0
    return;
84
85
0
  {
86
0
    char *endp = NULL;
87
0
    tries_left = g_ascii_strtoull (counter, &endp, 10);
88
0
    if (endp == counter || (tries_left == G_MAXUINT64 && errno == ERANGE))
89
0
      return;
90
0
    counter = endp;
91
0
  }
92
93
  /* Parse done counter only if present */
94
0
  if (*counter == '-')
95
0
    {
96
0
      counter += 1;
97
0
      char *endp = NULL;
98
0
      tries_done = g_ascii_strtoull (counter, &endp, 10);
99
0
      if (endp == counter || (tries_done == G_MAXUINT64 && errno == ERANGE))
100
0
        return;
101
0
    }
102
103
0
  *out_left = tries_left;
104
0
  *out_done = tries_done;
105
0
}
106
107
/**
108
 * ostree_bootconfig_parser_get_tries_left:
109
 * @self: Parser
110
 *
111
 * Returns: Amount of boot tries left
112
 *
113
 * Since: 2025.2
114
 */
115
guint64
116
ostree_bootconfig_parser_get_tries_left (OstreeBootconfigParser *self)
117
0
{
118
0
  return self->tries_left;
119
0
}
120
121
/**
122
 * ostree_bootconfig_parser_get_tries_done:
123
 * @self: Parser
124
 *
125
 * Returns: Amount of boot tries
126
 */
127
guint64
128
ostree_bootconfig_parser_get_tries_done (OstreeBootconfigParser *self)
129
0
{
130
0
  return self->tries_done;
131
0
}
132
133
const char *
134
_ostree_bootconfig_parser_filename (OstreeBootconfigParser *self)
135
0
{
136
0
  return self->filename;
137
0
}
138
139
/**
140
 * ostree_bootconfig_parser_parse_at:
141
 * @self: Parser
142
 * @dfd: Directory fd
143
 * @path: File path
144
 * @cancellable: Cancellable
145
 * @error: Error
146
 *
147
 * Initialize a bootconfig from the given file.
148
 */
149
gboolean
150
ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, int dfd, const char *path,
151
                                   GCancellable *cancellable, GError **error)
152
0
{
153
0
  g_assert (!self->filename);
154
155
0
  g_autofree char *contents = glnx_file_get_contents_utf8_at (dfd, path, NULL, cancellable, error);
156
0
  if (!contents)
157
0
    return FALSE;
158
159
0
  g_autoptr (GPtrArray) overlay_initrds = NULL;
160
161
0
  g_auto (GStrv) lines = g_strsplit (contents, "\n", -1);
162
0
  for (char **iter = lines; *iter; iter++)
163
0
    {
164
0
      const char *line = *iter;
165
166
0
      if (g_ascii_isalpha (*line))
167
0
        {
168
0
          char **items = NULL;
169
0
          items = g_strsplit_set (line, self->separators, 2);
170
0
          if (g_strv_length (items) == 2 && items[0][0] != '\0')
171
0
            {
172
0
              if (g_str_equal (items[0], "initrd")
173
0
                  && g_hash_table_contains (self->options, "initrd"))
174
0
                {
175
0
                  if (!overlay_initrds)
176
0
                    overlay_initrds = g_ptr_array_new_with_free_func (g_free);
177
0
                  g_ptr_array_add (overlay_initrds, items[1]);
178
0
                  g_free (items[0]);
179
0
                }
180
0
              else
181
0
                {
182
0
                  g_hash_table_insert (self->options, items[0], items[1]);
183
0
                }
184
0
              g_free (items); /* Free container; we stole the elements */
185
0
            }
186
0
          else
187
0
            {
188
0
              g_strfreev (items);
189
0
            }
190
0
        }
191
0
    }
192
193
0
  if (overlay_initrds)
194
0
    {
195
0
      g_ptr_array_add (overlay_initrds, NULL);
196
0
      self->overlay_initrds = (char **)g_ptr_array_free (g_steal_pointer (&overlay_initrds), FALSE);
197
0
    }
198
199
0
  const char *basename = glnx_basename (path);
200
0
  parse_bootloader_tries (basename, &self->tries_left, &self->tries_done);
201
202
0
  self->filename = g_strdup (basename);
203
0
  return TRUE;
204
0
}
205
206
gboolean
207
ostree_bootconfig_parser_parse (OstreeBootconfigParser *self, GFile *path,
208
                                GCancellable *cancellable, GError **error)
209
0
{
210
0
  return ostree_bootconfig_parser_parse_at (self, AT_FDCWD, gs_file_get_path_cached (path),
211
0
                                            cancellable, error);
212
0
}
213
214
/**
215
 * ostree_bootconfig_parser_set:
216
 * @self: Parser
217
 * @key: the key
218
 * @value: the key
219
 *
220
 * Set the @key/@value pair to the boot configuration dictionary.
221
 */
222
void
223
ostree_bootconfig_parser_set (OstreeBootconfigParser *self, const char *key, const char *value)
224
0
{
225
0
  g_hash_table_replace (self->options, g_strdup (key), g_strdup (value));
226
0
}
227
228
/**
229
 * ostree_bootconfig_parser_get:
230
 * @self: Parser
231
 * @key: the key name to retrieve
232
 *
233
 * Get the value corresponding to @key from the boot configuration dictionary.
234
 *
235
 * Returns: (nullable): The corresponding value, or %NULL if the key hasn't been
236
 * found.
237
 */
238
const char *
239
ostree_bootconfig_parser_get (OstreeBootconfigParser *self, const char *key)
240
0
{
241
0
  return g_hash_table_lookup (self->options, key);
242
0
}
243
244
/**
245
 * ostree_bootconfig_parser_set_overlay_initrds:
246
 * @self: Parser
247
 * @initrds: (array zero-terminated=1) (transfer none) (allow-none): Array of overlay
248
 *    initrds or %NULL to unset.
249
 *
250
 * These are rendered as additional `initrd` keys in the final bootloader configs. The
251
 * base initrd is part of the primary keys.
252
 *
253
 * Since: 2020.7
254
 */
255
void
256
ostree_bootconfig_parser_set_overlay_initrds (OstreeBootconfigParser *self, char **initrds)
257
0
{
258
0
  g_assert (g_hash_table_contains (self->options, "initrd"));
259
0
  g_strfreev (self->overlay_initrds);
260
0
  self->overlay_initrds = g_strdupv (initrds);
261
0
}
262
263
/**
264
 * ostree_bootconfig_parser_get_overlay_initrds:
265
 * @self: Parser
266
 *
267
 * Returns: (array zero-terminated=1) (transfer none) (nullable): Array of initrds or %NULL
268
 * if none are set.
269
 *
270
 * Since: 2020.7
271
 */
272
char **
273
ostree_bootconfig_parser_get_overlay_initrds (OstreeBootconfigParser *self)
274
0
{
275
0
  return self->overlay_initrds;
276
0
}
277
278
static void
279
write_key (OstreeBootconfigParser *self, GString *buf, const char *key, const char *value)
280
0
{
281
0
  g_string_append (buf, key);
282
0
  g_string_append_c (buf, self->separators[0]);
283
0
  g_string_append (buf, value);
284
0
  g_string_append_c (buf, '\n');
285
0
}
286
287
gboolean
288
ostree_bootconfig_parser_write_at (OstreeBootconfigParser *self, int dfd, const char *path,
289
                                   GCancellable *cancellable, GError **error)
290
0
{
291
  /* Write the fields in a deterministic order, following what is used
292
   * in the bootconfig example of the BootLoaderspec document:
293
   * https://systemd.io/BOOT_LOADER_SPECIFICATION
294
   */
295
0
  const char *fields[] = { "title", "version", "options", "devicetree", "linux", "initrd" };
296
0
  g_autoptr (GHashTable) keys_written = g_hash_table_new (g_str_hash, g_str_equal);
297
0
  g_autoptr (GString) buf = g_string_new ("");
298
299
0
  for (guint i = 0; i < G_N_ELEMENTS (fields); i++)
300
0
    {
301
0
      const char *key = fields[i];
302
0
      const char *value = g_hash_table_lookup (self->options, key);
303
0
      if (value != NULL)
304
0
        {
305
0
          write_key (self, buf, key, value);
306
0
          g_hash_table_add (keys_written, (gpointer)key);
307
0
        }
308
0
    }
309
310
  /* Write overlay initrds */
311
0
  if (self->overlay_initrds && (g_strv_length (self->overlay_initrds) > 0))
312
0
    {
313
      /* we should've written the primary initrd already */
314
0
      g_assert (g_hash_table_contains (keys_written, "initrd"));
315
0
      for (char **it = self->overlay_initrds; it && *it; it++)
316
0
        write_key (self, buf, "initrd", *it);
317
0
    }
318
319
  /* Write unknown fields */
320
0
  GLNX_HASH_TABLE_FOREACH_KV (self->options, const char *, k, const char *, v)
321
0
    {
322
0
      if (g_hash_table_lookup (keys_written, k))
323
0
        continue;
324
0
      write_key (self, buf, k, v);
325
0
    }
326
327
0
  if (!glnx_file_replace_contents_at (dfd, path, (guint8 *)buf->str, buf->len,
328
0
                                      GLNX_FILE_REPLACE_NODATASYNC, cancellable, error))
329
0
    return FALSE;
330
331
0
  return TRUE;
332
0
}
333
334
gboolean
335
ostree_bootconfig_parser_write (OstreeBootconfigParser *self, GFile *output,
336
                                GCancellable *cancellable, GError **error)
337
0
{
338
0
  return ostree_bootconfig_parser_write_at (self, AT_FDCWD, gs_file_get_path_cached (output),
339
0
                                            cancellable, error);
340
0
}
341
342
static void
343
ostree_bootconfig_parser_finalize (GObject *object)
344
0
{
345
0
  OstreeBootconfigParser *self = OSTREE_BOOTCONFIG_PARSER (object);
346
347
0
  g_free (self->filename);
348
0
  g_strfreev (self->overlay_initrds);
349
0
  g_hash_table_unref (self->options);
350
351
0
  G_OBJECT_CLASS (ostree_bootconfig_parser_parent_class)->finalize (object);
352
0
}
353
354
static void
355
ostree_bootconfig_parser_init (OstreeBootconfigParser *self)
356
0
{
357
0
  self->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
358
0
}
359
360
void
361
ostree_bootconfig_parser_class_init (OstreeBootconfigParserClass *class)
362
0
{
363
0
  GObjectClass *object_class = G_OBJECT_CLASS (class);
364
365
0
  object_class->finalize = ostree_bootconfig_parser_finalize;
366
0
}
367
368
OstreeBootconfigParser *
369
ostree_bootconfig_parser_new (void)
370
0
{
371
0
  OstreeBootconfigParser *self = NULL;
372
373
0
  self = g_object_new (OSTREE_TYPE_BOOTCONFIG_PARSER, NULL);
374
0
  self->separators = " \t";
375
0
  return self;
376
0
}