Skip to content

Commit 8b45dc4

Browse files
committed
sysroot: Preserve custom bootconfig keys across staged deployment finalization
When a deployment is staged via ostree_sysroot_stage_tree_with_options(), the deployment metadata is serialized to /run/ostree/staged-deployment as a GVariant. During deserialization in _ostree_sysroot_reload_staged(), _ostree_deployment_set_bootconfig_from_kargs() creates a fresh empty OstreeBootconfigParser and only populates the "options" key. Any non-standard BLS keys that were set on the bootconfig by consumers (such as rpm-ostree) are silently lost. This matters for the new rpm-ostree kargs --source feature which stores source ownership metadata as custom keys in the BLS config (e.g. "ostree-source-tuned"). Without this fix, these keys are dropped during finalization at shutdown, breaking the source tracking across reboots. Fix this by following the same pattern used for overlay-initrds: 1. Add _ostree_bootconfig_parser_get_extra_keys_variant() which returns all non-standard bootconfig keys as an a{ss} GVariant. Standard keys (title, version, options, linux, initrd, devicetree, fdtdir, aboot, abootcfg) that install_deployment_kernel() rebuilds from scratch are excluded. 2. In ostree_sysroot_stage_tree_with_options(), serialize any extra bootconfig keys as "bootconfig-extra" in the staged GVariant dict. 3. In _ostree_sysroot_reload_staged(), restore extra bootconfig keys from the "bootconfig-extra" dict onto the deployment's bootconfig. Backwards compatibility: - Old ostree versions ignore the unknown "bootconfig-extra" key in the a{sv} dict (custom keys silently lost, same as before this patch). - New ostree gracefully handles the absence of "bootconfig-extra" in staged data written by older versions (g_variant_dict_lookup returns FALSE, no restoration attempted). Resolves: RHEL-135363 See: bootc-dev/bootc#899 Assisted-by: OpenCode (Claude Opus 4.6) Signed-off-by: Joseph Marrero <jmarrero@redhat.com>
1 parent dfd9a63 commit 8b45dc4

File tree

5 files changed

+217
-0
lines changed

5 files changed

+217
-0
lines changed

src/libostree/ostree-bootconfig-parser-private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ G_BEGIN_DECLS
88

99
const char *_ostree_bootconfig_parser_filename (OstreeBootconfigParser *self);
1010

11+
GVariant *_ostree_bootconfig_parser_get_extra_keys_variant (OstreeBootconfigParser *self);
12+
1113
G_END_DECLS

src/libostree/ostree-bootconfig-parser.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,58 @@ ostree_bootconfig_parser_write (OstreeBootconfigParser *self, GFile *output,
339339
cancellable, error);
340340
}
341341

342+
/* Keys that are managed by install_deployment_kernel() and should not be
343+
* included in the "extra keys" serialization for staged deployments.
344+
* These are rebuilt from scratch during finalization.
345+
*/
346+
static const char *const standard_bls_keys[]
347+
= { "title", "version", "options", "devicetree", "linux", "initrd", "fdtdir", "aboot", "abootcfg", NULL };
348+
349+
static gboolean
350+
is_standard_bls_key (const char *key)
351+
{
352+
for (const char *const *k = standard_bls_keys; *k != NULL; k++)
353+
{
354+
if (g_str_equal (key, *k))
355+
return TRUE;
356+
}
357+
return FALSE;
358+
}
359+
360+
/**
361+
* _ostree_bootconfig_parser_get_extra_keys_variant:
362+
* @self: Parser
363+
*
364+
* Returns a GVariant of type "a{ss}" containing all bootconfig keys
365+
* that are NOT standard BLS keys (title, version, options, linux, initrd,
366+
* devicetree, fdtdir, aboot, abootcfg). These are custom keys set by
367+
* consumers like rpm-ostree (e.g. "ostree-source-tuned").
368+
*
369+
* Returns: (transfer full) (nullable): A new floating GVariant, or NULL if
370+
* there are no extra keys
371+
*/
372+
GVariant *
373+
_ostree_bootconfig_parser_get_extra_keys_variant (OstreeBootconfigParser *self)
374+
{
375+
g_auto (GVariantBuilder) builder = OT_VARIANT_BUILDER_INITIALIZER;
376+
gboolean has_entries = FALSE;
377+
378+
g_variant_builder_init (&builder, (GVariantType *)"a{ss}");
379+
380+
GLNX_HASH_TABLE_FOREACH_KV (self->options, const char *, k, const char *, v)
381+
{
382+
if (is_standard_bls_key (k))
383+
continue;
384+
g_variant_builder_add (&builder, "{ss}", k, v);
385+
has_entries = TRUE;
386+
}
387+
388+
if (!has_entries)
389+
return NULL;
390+
391+
return g_variant_builder_end (&builder);
392+
}
393+
342394
static void
343395
ostree_bootconfig_parser_finalize (GObject *object)
344396
{

src/libostree/ostree-sysroot-deploy.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3884,6 +3884,20 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, const char *osname,
38843884
g_variant_builder_add (builder, "{sv}", "overlay-initrds",
38853885
g_variant_new_strv ((const char *const *)opts->overlay_initrds, -1));
38863886

3887+
/* Serialize any non-standard bootconfig keys (e.g. ostree-source-tuned).
3888+
* These are custom keys set by consumers and need to survive the staging
3889+
* roundtrip so they are preserved during finalization at shutdown.
3890+
*/
3891+
{
3892+
OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment);
3893+
if (bootconfig)
3894+
{
3895+
GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (bootconfig);
3896+
if (extra)
3897+
g_variant_builder_add (builder, "{sv}", "bootconfig-extra", extra);
3898+
}
3899+
}
3900+
38873901
const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED));
38883902
if (!glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, cancellable, error))
38893903
return FALSE;

src/libostree/ostree-sysroot.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,25 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self, GError **error)
12251225

12261226
_ostree_deployment_set_overlay_initrds (staged, overlay_initrds);
12271227

1228+
/* Restore any non-standard bootconfig keys (e.g. ostree-source-tuned)
1229+
* that were serialized during staging. This preserves custom BLS keys
1230+
* set by consumers like rpm-ostree through the staging roundtrip.
1231+
*/
1232+
{
1233+
g_autoptr (GVariant) bootconfig_extra = NULL;
1234+
if (g_variant_dict_lookup (staged_deployment_dict, "bootconfig-extra", "@a{ss}",
1235+
&bootconfig_extra))
1236+
{
1237+
OstreeBootconfigParser *bootconfig
1238+
= ostree_deployment_get_bootconfig (staged);
1239+
GVariantIter iter;
1240+
const char *key, *value;
1241+
g_variant_iter_init (&iter, bootconfig_extra);
1242+
while (g_variant_iter_next (&iter, "{&s&s}", &key, &value))
1243+
ostree_bootconfig_parser_set (bootconfig, key, value);
1244+
}
1245+
}
1246+
12281247
self->staged_deployment = g_steal_pointer (&staged);
12291248
self->staged_deployment_data = g_steal_pointer (&staged_deployment_data);
12301249
/* We set this flag for ostree_deployment_is_staged() because that API

tests/test-bootconfig-parser-internals.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,142 @@ test_parse_tries_invalid (void)
4949
g_assert_cmpuint (done, ==, 0);
5050
}
5151

52+
static void
53+
test_extra_keys_variant_empty (void)
54+
{
55+
/* A bootconfig with only standard BLS keys should return NULL */
56+
OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
57+
ostree_bootconfig_parser_set (parser, "title", "Fedora Linux 40");
58+
ostree_bootconfig_parser_set (parser, "version", "1");
59+
ostree_bootconfig_parser_set (parser, "options", "root=UUID=abc rw quiet");
60+
ostree_bootconfig_parser_set (parser, "linux", "/vmlinuz-6.8.0");
61+
ostree_bootconfig_parser_set (parser, "initrd", "/initramfs-6.8.0.img");
62+
63+
GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (parser);
64+
g_assert_null (extra);
65+
66+
g_object_unref (parser);
67+
}
68+
69+
static void
70+
test_extra_keys_variant_with_custom (void)
71+
{
72+
/* A bootconfig with standard + custom keys should return only the custom ones */
73+
OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
74+
ostree_bootconfig_parser_set (parser, "title", "Fedora Linux 40");
75+
ostree_bootconfig_parser_set (parser, "options", "root=UUID=abc rw");
76+
ostree_bootconfig_parser_set (parser, "linux", "/vmlinuz-6.8.0");
77+
ostree_bootconfig_parser_set (parser, "ostree-source-tuned", "nohz=full isolcpus=1-3");
78+
ostree_bootconfig_parser_set (parser, "ostree-source-dracut", "rd.driver.pre=vfio-pci");
79+
80+
GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (parser);
81+
g_assert_nonnull (extra);
82+
83+
/* Wrap it so it gets freed */
84+
GVariant *extra_owned = g_variant_ref_sink (extra);
85+
86+
/* Should be a{ss} with exactly 2 entries */
87+
g_assert_true (g_variant_is_of_type (extra_owned, G_VARIANT_TYPE ("a{ss}")));
88+
g_assert_cmpuint (g_variant_n_children (extra_owned), ==, 2);
89+
90+
/* Verify the contents - iterate and check both keys exist */
91+
GVariantIter iter;
92+
const char *key, *val;
93+
gboolean found_tuned = FALSE, found_dracut = FALSE;
94+
g_variant_iter_init (&iter, extra_owned);
95+
while (g_variant_iter_next (&iter, "{&s&s}", &key, &val))
96+
{
97+
if (g_str_equal (key, "ostree-source-tuned"))
98+
{
99+
g_assert_cmpstr (val, ==, "nohz=full isolcpus=1-3");
100+
found_tuned = TRUE;
101+
}
102+
else if (g_str_equal (key, "ostree-source-dracut"))
103+
{
104+
g_assert_cmpstr (val, ==, "rd.driver.pre=vfio-pci");
105+
found_dracut = TRUE;
106+
}
107+
else
108+
{
109+
g_assert_not_reached ();
110+
}
111+
}
112+
g_assert_true (found_tuned);
113+
g_assert_true (found_dracut);
114+
115+
g_variant_unref (extra_owned);
116+
g_object_unref (parser);
117+
}
118+
119+
static void
120+
test_extra_keys_variant_all_standard (void)
121+
{
122+
/* Test all known standard keys are excluded */
123+
OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
124+
ostree_bootconfig_parser_set (parser, "title", "Test");
125+
ostree_bootconfig_parser_set (parser, "version", "1");
126+
ostree_bootconfig_parser_set (parser, "options", "root=UUID=abc");
127+
ostree_bootconfig_parser_set (parser, "devicetree", "/dtbs/foo.dtb");
128+
ostree_bootconfig_parser_set (parser, "linux", "/vmlinuz");
129+
ostree_bootconfig_parser_set (parser, "initrd", "/initramfs.img");
130+
ostree_bootconfig_parser_set (parser, "fdtdir", "/dtbs/");
131+
ostree_bootconfig_parser_set (parser, "aboot", "/aboot.img");
132+
ostree_bootconfig_parser_set (parser, "abootcfg", "/aboot.cfg");
133+
134+
GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (parser);
135+
g_assert_null (extra);
136+
137+
g_object_unref (parser);
138+
}
139+
140+
static void
141+
test_extra_keys_roundtrip (void)
142+
{
143+
/* Test that extra keys can be serialized to a variant and restored */
144+
OstreeBootconfigParser *original = ostree_bootconfig_parser_new ();
145+
ostree_bootconfig_parser_set (original, "options", "root=UUID=abc rw");
146+
ostree_bootconfig_parser_set (original, "linux", "/vmlinuz");
147+
ostree_bootconfig_parser_set (original, "ostree-source-tuned", "nohz=full isolcpus=1-3");
148+
149+
/* Serialize */
150+
GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (original);
151+
g_assert_nonnull (extra);
152+
GVariant *extra_owned = g_variant_ref_sink (extra);
153+
154+
/* Create a new parser (simulating deserialization) with only standard keys */
155+
OstreeBootconfigParser *restored = ostree_bootconfig_parser_new ();
156+
ostree_bootconfig_parser_set (restored, "options", "root=UUID=abc rw nohz=full isolcpus=1-3");
157+
158+
/* Restore extra keys from variant */
159+
GVariantIter iter;
160+
const char *key, *value;
161+
g_variant_iter_init (&iter, extra_owned);
162+
while (g_variant_iter_next (&iter, "{&s&s}", &key, &value))
163+
ostree_bootconfig_parser_set (restored, key, value);
164+
165+
/* Verify the custom key survived the roundtrip */
166+
g_assert_cmpstr (ostree_bootconfig_parser_get (restored, "ostree-source-tuned"), ==,
167+
"nohz=full isolcpus=1-3");
168+
/* Standard keys should also be present */
169+
g_assert_cmpstr (ostree_bootconfig_parser_get (restored, "options"), ==,
170+
"root=UUID=abc rw nohz=full isolcpus=1-3");
171+
172+
g_variant_unref (extra_owned);
173+
g_object_unref (original);
174+
g_object_unref (restored);
175+
}
176+
52177
int
53178
main (int argc, char *argv[])
54179
{
55180
g_test_init (&argc, &argv, NULL);
56181

57182
g_test_add_func ("/bootconfig-parser/tries/valid", test_parse_tries_valid);
58183
g_test_add_func ("/bootconfig-parser/tries/invalid", test_parse_tries_invalid);
184+
g_test_add_func ("/bootconfig-parser/extra-keys/empty", test_extra_keys_variant_empty);
185+
g_test_add_func ("/bootconfig-parser/extra-keys/with-custom", test_extra_keys_variant_with_custom);
186+
g_test_add_func ("/bootconfig-parser/extra-keys/all-standard",
187+
test_extra_keys_variant_all_standard);
188+
g_test_add_func ("/bootconfig-parser/extra-keys/roundtrip", test_extra_keys_roundtrip);
59189
return g_test_run ();
60190
}

0 commit comments

Comments
 (0)