Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/libostree/ostree-bootconfig-parser-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ G_BEGIN_DECLS

const char *_ostree_bootconfig_parser_filename (OstreeBootconfigParser *self);

GVariant *_ostree_bootconfig_parser_get_extra_keys_variant (OstreeBootconfigParser *self);

G_END_DECLS
54 changes: 54 additions & 0 deletions src/libostree/ostree-bootconfig-parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,60 @@ ostree_bootconfig_parser_write (OstreeBootconfigParser *self, GFile *output,
cancellable, error);
}

/* Standard BLS keys that are managed by ostree's own deployment code.
* These are rebuilt from scratch during staged deployment finalization
* (title, version, linux, initrd from the deployment; options from the
* serialized kargs), so they must NOT be duplicated into bootconfig-extra.
*/
static const char *const standard_bls_keys[]
= { "title", "version", "options", "linux", "initrd", "devicetree", NULL };

static gboolean
is_standard_bls_key (const char *key)
{
for (const char *const *p = standard_bls_keys; *p != NULL; p++)
{
if (strcmp (key, *p) == 0)
return TRUE;
}
return FALSE;
}

/**
* _ostree_bootconfig_parser_get_extra_keys_variant:
* @self: Parser
*
* Returns a GVariant of type "a{ss}" containing all bootconfig keys
* that are not part of the standard BLS set managed by ostree. These
* are extension keys set by consumers like bootc (e.g.
* "x-options-source-tuned") that need to survive the staged deployment
* serialization roundtrip.
*
* Returns: (transfer full) (nullable): A new floating GVariant, or NULL if
* there are no extra keys
*/
GVariant *
_ostree_bootconfig_parser_get_extra_keys_variant (OstreeBootconfigParser *self)
{
g_auto (GVariantBuilder) builder = OT_VARIANT_BUILDER_INITIALIZER;
gboolean has_entries = FALSE;

g_variant_builder_init (&builder, (GVariantType *)"a{ss}");

GLNX_HASH_TABLE_FOREACH_KV (self->options, const char *, k, const char *, v)
{
if (is_standard_bls_key (k))
continue;
g_variant_builder_add (&builder, "{ss}", k, v);
has_entries = TRUE;
}

if (!has_entries)
return NULL;

return g_variant_builder_end (&builder);
}

static void
ostree_bootconfig_parser_finalize (GObject *object)
{
Expand Down
27 changes: 27 additions & 0 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -3884,6 +3884,33 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, const char *osname,
g_variant_builder_add (builder, "{sv}", "overlay-initrds",
g_variant_new_strv ((const char *const *)opts->overlay_initrds, -1));

/* Serialize any extension BLS keys (e.g. x-options-source-tuned).
* These are custom keys set by consumers like bootc and need to survive
* the staging roundtrip so they are preserved during finalization at shutdown.
*
* First check the new deployment's bootconfig (in case the caller set keys
* on it directly). If none found, fall back to the merge deployment's
* bootconfig, which carries the keys from the currently deployed BLS entry.
* This ensures that x-prefixed keys are inherited across staged deployments
* even though _ostree_deployment_set_bootconfig_from_kargs() creates a fresh
* bootconfig containing only the "options" key.
*/
{
GVariant *extra = NULL;
OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment);
if (bootconfig)
extra = _ostree_bootconfig_parser_get_extra_keys_variant (bootconfig);
if (!extra && merge_deployment)
{
OstreeBootconfigParser *merge_bootconfig
= ostree_deployment_get_bootconfig (merge_deployment);
if (merge_bootconfig)
extra = _ostree_bootconfig_parser_get_extra_keys_variant (merge_bootconfig);
}
if (extra)
g_variant_builder_add (builder, "{sv}", "bootconfig-extra", extra);
}

const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED));
if (!glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, cancellable, error))
return FALSE;
Expand Down
18 changes: 18 additions & 0 deletions src/libostree/ostree-sysroot.c
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,24 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self, GError **error)

_ostree_deployment_set_overlay_initrds (staged, overlay_initrds);

/* Restore any extension BLS keys (e.g. x-options-source-tuned)
* that were serialized during staging. This preserves custom keys
* set by consumers like bootc through the staging roundtrip.
*/
{
g_autoptr (GVariant) bootconfig_extra = NULL;
if (g_variant_dict_lookup (staged_deployment_dict, "bootconfig-extra", "@a{ss}",
&bootconfig_extra))
{
OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (staged);
GVariantIter iter;
const char *key, *value;
g_variant_iter_init (&iter, bootconfig_extra);
while (g_variant_iter_next (&iter, "{&s&s}", &key, &value))
ostree_bootconfig_parser_set (bootconfig, key, value);
}
}

self->staged_deployment = g_steal_pointer (&staged);
self->staged_deployment_data = g_steal_pointer (&staged_deployment_data);
/* We set this flag for ostree_deployment_is_staged() because that API
Expand Down
213 changes: 213 additions & 0 deletions tests/test-bootconfig-parser-internals.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,225 @@ test_parse_tries_invalid (void)
g_assert_cmpuint (done, ==, 0);
}

static void
test_extra_keys_variant_empty (void)
{
/* A bootconfig with no x-prefixed keys should return NULL */
OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
ostree_bootconfig_parser_set (parser, "title", "Fedora Linux 43");
ostree_bootconfig_parser_set (parser, "version", "1");
ostree_bootconfig_parser_set (parser, "options", "root=UUID=abc rw quiet");
ostree_bootconfig_parser_set (parser, "linux", "/vmlinuz-6.8.0");
ostree_bootconfig_parser_set (parser, "initrd", "/initramfs-6.8.0.img");

GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (parser);
g_assert_null (extra);

g_object_unref (parser);
}

static void
test_extra_keys_variant_with_extension_keys (void)
{
/* Standard keys should be excluded, only extension keys returned */
OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
ostree_bootconfig_parser_set (parser, "title", "Fedora Linux 43");
ostree_bootconfig_parser_set (parser, "options", "root=UUID=abc rw");
ostree_bootconfig_parser_set (parser, "linux", "/vmlinuz-6.8.0");
ostree_bootconfig_parser_set (parser, "x-options-source-tuned", "nohz=full isolcpus=1-3");
ostree_bootconfig_parser_set (parser, "x-options-source-dracut", "rd.driver.pre=vfio-pci");

GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (parser);
g_assert_nonnull (extra);

GVariant *extra_owned = g_variant_ref_sink (extra);

/* Should be a{ss} with exactly 2 entries */
g_assert_true (g_variant_is_of_type (extra_owned, G_VARIANT_TYPE ("a{ss}")));
g_assert_cmpuint (g_variant_n_children (extra_owned), ==, 2);

/* Verify the contents */
GVariantIter iter;
const char *key, *val;
gboolean found_tuned = FALSE, found_dracut = FALSE;
g_variant_iter_init (&iter, extra_owned);
while (g_variant_iter_next (&iter, "{&s&s}", &key, &val))
{
if (g_str_equal (key, "x-options-source-tuned"))
{
g_assert_cmpstr (val, ==, "nohz=full isolcpus=1-3");
found_tuned = TRUE;
}
else if (g_str_equal (key, "x-options-source-dracut"))
{
g_assert_cmpstr (val, ==, "rd.driver.pre=vfio-pci");
found_dracut = TRUE;
}
else
{
g_assert_not_reached ();
}
}
g_assert_true (found_tuned);
g_assert_true (found_dracut);

g_variant_unref (extra_owned);
g_object_unref (parser);
}

static void
test_extra_keys_variant_standard_excluded (void)
{
/* Standard BLS keys (title, version, options, linux, initrd, devicetree)
* should be excluded. All other keys should be preserved.
*/
OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
ostree_bootconfig_parser_set (parser, "title", "Test");
ostree_bootconfig_parser_set (parser, "version", "1.0");
ostree_bootconfig_parser_set (parser, "options", "root=UUID=abc");
ostree_bootconfig_parser_set (parser, "linux", "/vmlinuz");
ostree_bootconfig_parser_set (parser, "initrd", "/initramfs.img");
ostree_bootconfig_parser_set (parser, "devicetree", "/dtb");

/* Only standard keys -- should return NULL */
GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (parser);
g_assert_null (extra);

/* Add non-standard keys -- all should be preserved */
ostree_bootconfig_parser_set (parser, "my-custom-key", "some-value");
ostree_bootconfig_parser_set (parser, "x-options-source-tuned", "nohz=full");

extra = _ostree_bootconfig_parser_get_extra_keys_variant (parser);
g_assert_nonnull (extra);
GVariant *extra_owned = g_variant_ref_sink (extra);

g_assert_cmpuint (g_variant_n_children (extra_owned), ==, 2);

GVariantIter iter;
const char *key, *val;
gboolean found_custom = FALSE, found_tuned = FALSE;
g_variant_iter_init (&iter, extra_owned);
while (g_variant_iter_next (&iter, "{&s&s}", &key, &val))
{
if (g_str_equal (key, "my-custom-key"))
{
g_assert_cmpstr (val, ==, "some-value");
found_custom = TRUE;
}
else if (g_str_equal (key, "x-options-source-tuned"))
{
g_assert_cmpstr (val, ==, "nohz=full");
found_tuned = TRUE;
}
else
g_assert_not_reached ();
}
g_assert_true (found_custom);
g_assert_true (found_tuned);

g_variant_unref (extra_owned);
g_object_unref (parser);
}

static void
test_extra_keys_roundtrip (void)
{
/* Test that extra keys can be serialized to a variant and restored */
OstreeBootconfigParser *original = ostree_bootconfig_parser_new ();
ostree_bootconfig_parser_set (original, "options", "root=UUID=abc rw");
ostree_bootconfig_parser_set (original, "linux", "/vmlinuz");
ostree_bootconfig_parser_set (original, "x-options-source-tuned", "nohz=full isolcpus=1-3");

/* Serialize */
GVariant *extra = _ostree_bootconfig_parser_get_extra_keys_variant (original);
g_assert_nonnull (extra);
GVariant *extra_owned = g_variant_ref_sink (extra);

/* Create a new parser (simulating deserialization) with only standard keys */
OstreeBootconfigParser *restored = ostree_bootconfig_parser_new ();
ostree_bootconfig_parser_set (restored, "options", "root=UUID=abc rw nohz=full isolcpus=1-3");

/* Restore extra keys from variant */
GVariantIter iter;
const char *key, *value;
g_variant_iter_init (&iter, extra_owned);
while (g_variant_iter_next (&iter, "{&s&s}", &key, &value))
ostree_bootconfig_parser_set (restored, key, value);

/* Verify the extension key survived the roundtrip */
g_assert_cmpstr (ostree_bootconfig_parser_get (restored, "x-options-source-tuned"), ==,
"nohz=full isolcpus=1-3");
/* Standard keys should also be present */
g_assert_cmpstr (ostree_bootconfig_parser_get (restored, "options"), ==,
"root=UUID=abc rw nohz=full isolcpus=1-3");

g_variant_unref (extra_owned);
g_object_unref (original);
g_object_unref (restored);
}

static void
test_extra_keys_parse_write_roundtrip (void)
{
/* Test that x-prefixed keys survive a parse -> write -> parse roundtrip
* via the BLS file format.
*/
const char *bls_content = "title Fedora Linux 43\n"
"version 6.8.0-300.fc40.x86_64\n"
"linux /vmlinuz-6.8.0\n"
"initrd /initramfs-6.8.0.img\n"
"options root=UUID=abc rw nohz=full\n"
"x-options-source-tuned nohz=full\n";

/* Write the BLS content to a temp file */
g_autofree char *tmpdir = g_dir_make_tmp ("ostree-test-XXXXXX", NULL);
g_assert_nonnull (tmpdir);
g_autofree char *tmpfile = g_build_filename (tmpdir, "ostree-test.conf", NULL);
g_assert_true (g_file_set_contents (tmpfile, bls_content, -1, NULL));

/* Parse */
OstreeBootconfigParser *parser = ostree_bootconfig_parser_new ();
g_assert_true (ostree_bootconfig_parser_parse_at (parser, AT_FDCWD, tmpfile, NULL, NULL));

/* The x-prefixed key should have been parsed */
g_assert_cmpstr (ostree_bootconfig_parser_get (parser, "x-options-source-tuned"), ==,
"nohz=full");
g_assert_cmpstr (ostree_bootconfig_parser_get (parser, "options"), ==,
"root=UUID=abc rw nohz=full");

/* Write it back out */
g_autofree char *outfile = g_build_filename (tmpdir, "ostree-test-out.conf", NULL);
g_assert_true (ostree_bootconfig_parser_write_at (parser, AT_FDCWD, outfile, NULL, NULL));

/* Parse the output and verify the key survived */
OstreeBootconfigParser *parser2 = ostree_bootconfig_parser_new ();
g_assert_true (ostree_bootconfig_parser_parse_at (parser2, AT_FDCWD, outfile, NULL, NULL));
g_assert_cmpstr (ostree_bootconfig_parser_get (parser2, "x-options-source-tuned"), ==,
"nohz=full");
g_assert_cmpstr (ostree_bootconfig_parser_get (parser2, "options"), ==,
"root=UUID=abc rw nohz=full");

g_object_unref (parser);
g_object_unref (parser2);
(void)unlink (tmpfile);
(void)unlink (outfile);
(void)rmdir (tmpdir);
}

int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);

g_test_add_func ("/bootconfig-parser/tries/valid", test_parse_tries_valid);
g_test_add_func ("/bootconfig-parser/tries/invalid", test_parse_tries_invalid);
g_test_add_func ("/bootconfig-parser/extra-keys/empty", test_extra_keys_variant_empty);
g_test_add_func ("/bootconfig-parser/extra-keys/with-extension-keys",
test_extra_keys_variant_with_extension_keys);
g_test_add_func ("/bootconfig-parser/extra-keys/standard-excluded",
test_extra_keys_variant_standard_excluded);
g_test_add_func ("/bootconfig-parser/extra-keys/roundtrip", test_extra_keys_roundtrip);
g_test_add_func ("/bootconfig-parser/extra-keys/parse-write-roundtrip",
test_extra_keys_parse_write_roundtrip);
return g_test_run ();
}
Loading