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