Skip to content

Commit f8b8599

Browse files
committed
sysroot: Support boot counting for boot entries
Add support for boot counting for bootloader entries [1]. The boot counting data is stored in the name of the boot loader entry. A boot loader entry file name may contain a plus (+) followed by a number. This may optionally be followed by a minus (-) followed by a second number. The dot (.) and file name suffix (conf or efi) must immediately follow. The feature is enabled via sysroot configuration: [sysroot] boot-counting-tries=3 Testing: $ ostree admin deploy 91fc19319be9e79d07159303dff125f40f10e5c25614630dcbed23d95e36f907 Copying /etc changes: 2 modified, 3 removed, 4 added bootfs is sufficient for calculated new size: 0 bytes Transaction complete; bootconfig swap: yes; bootversion: boot.0.1, deployment count change: 1 $ ls /boot/loader/entries ostree-1.conf ostree-2+3.conf [1] https://uapi-group.org/specifications/specs/boot_loader_specification/#boot-counting Signed-off-by: Igor Opaniuk <igor.opaniuk@foundries.io>
1 parent 74efebd commit f8b8599

File tree

6 files changed

+199
-9
lines changed

6 files changed

+199
-9
lines changed

man/ostree.repo-config.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,18 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
416416
</listitem>
417417
</varlistentry>
418418

419+
<varlistentry>
420+
<term><varname>boot-counting-tries</varname></term>
421+
<listitem><para>Integer value controlling the number of maximum boot attempts. The boot
422+
counting data is stored in the name of the boot loader entry. A boot loader entry file name
423+
may contain a plus (+) followed by a number. This may optionally be followed by
424+
a minus (-) followed by a second number. The dot (.) and file name suffix (conf or efi) must
425+
immediately follow. More details in the
426+
<ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification/#boot-counting">
427+
The Boot Loader Specification</ulink>
428+
</para></listitem>
429+
</varlistentry>
430+
419431
<varlistentry>
420432
<term><varname>bls-append-except-default</varname></term>
421433
<listitem><para>A semicolon separated string list of key-value pairs. For example:

src/libostree/ostree-bootconfig-parser.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ struct _OstreeBootconfigParser
2727
gboolean parsed;
2828
const char *separators;
2929

30+
guint64 tries_left;
31+
guint64 tries_done;
32+
3033
GHashTable *options;
3134

3235
/* Additional initrds; the primary initrd is in options. */
@@ -56,6 +59,94 @@ ostree_bootconfig_parser_clone (OstreeBootconfigParser *self)
5659
return parser;
5760
}
5861

62+
/**
63+
* ostree_bootconfig_parser_parse_tries:
64+
* @self: Parser
65+
* @path: File path
66+
*
67+
* Parses a suffix of two counters in the form "+LEFT-DONE" from the end of the
68+
* filename (excluding file extension).
69+
*/
70+
static void
71+
ostree_bootconfig_parser_parse_tries (OstreeBootconfigParser *self, const char *filename)
72+
{
73+
gchar *counter = NULL;
74+
gchar *old_counter = NULL;
75+
self->tries_left = 0;
76+
self->tries_done = 0;
77+
78+
for (;;)
79+
{
80+
char *plus = strchr (counter ?: filename, '+');
81+
if (plus)
82+
{
83+
/* Look for the last "+" symbol in the filename */
84+
counter = plus + 1;
85+
continue;
86+
}
87+
if (counter)
88+
break;
89+
90+
/* No boot counter found */
91+
return;
92+
}
93+
94+
guint64 tries_left, tries_done = 0;
95+
96+
old_counter = counter;
97+
tries_left = g_ascii_strtoull (old_counter, &counter, 10);
98+
if ((old_counter == counter) || (tries_left > INT_MAX))
99+
return;
100+
101+
/* Parse done counter only if present */
102+
if (*counter == '-')
103+
{
104+
old_counter = counter;
105+
tries_done = g_ascii_strtoull (counter + 1, &counter, 10);
106+
if ((old_counter == counter) || (tries_left > INT_MAX))
107+
return;
108+
}
109+
110+
self->tries_left = tries_left;
111+
self->tries_done = tries_done;
112+
}
113+
114+
/**
115+
* ostree_bootconfig_parser_get_tries_left:
116+
* @self: Parser
117+
*
118+
* Returns: Amount of boot tries left
119+
*/
120+
guint64
121+
ostree_bootconfig_parser_get_tries_left (OstreeBootconfigParser *self)
122+
{
123+
return self->tries_left;
124+
}
125+
126+
/**
127+
* ostree_bootconfig_parser_get_tries_done:
128+
* @self: Parser
129+
*
130+
* Returns: Amount of boot tries
131+
*/
132+
guint64
133+
ostree_bootconfig_parser_get_tries_done (OstreeBootconfigParser *self)
134+
{
135+
return self->tries_done;
136+
}
137+
138+
/**
139+
* ostree_bootconfig_parser_get_tries_done:
140+
* @self: Parser
141+
*
142+
* Returns: TRUE if the boot config was parsed from existing boot config file
143+
*/
144+
gboolean
145+
ostree_bootconfig_parser_is_parsed (OstreeBootconfigParser *self)
146+
{
147+
return self->parsed;
148+
}
149+
59150
/**
60151
* ostree_bootconfig_parser_parse_at:
61152
* @self: Parser
@@ -116,6 +207,8 @@ ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, int dfd, const
116207
self->overlay_initrds = (char **)g_ptr_array_free (g_steal_pointer (&overlay_initrds), FALSE);
117208
}
118209

210+
ostree_bootconfig_parser_parse_tries(self, glnx_basename(path));
211+
119212
self->parsed = TRUE;
120213

121214
return TRUE;

src/libostree/ostree-bootconfig-parser.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,13 @@ void ostree_bootconfig_parser_set_overlay_initrds (OstreeBootconfigParser *self,
6767
_OSTREE_PUBLIC
6868
char **ostree_bootconfig_parser_get_overlay_initrds (OstreeBootconfigParser *self);
6969

70+
_OSTREE_PUBLIC
71+
guint64 ostree_bootconfig_parser_get_tries_left (OstreeBootconfigParser *self);
72+
73+
_OSTREE_PUBLIC
74+
guint64 ostree_bootconfig_parser_get_tries_done (OstreeBootconfigParser *self);
75+
76+
_OSTREE_PUBLIC
77+
gboolean ostree_bootconfig_parser_is_parsed (OstreeBootconfigParser *self);
78+
7079
G_END_DECLS

src/libostree/ostree-repo-private.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ struct OstreeRepo
247247
GHashTable
248248
*bls_append_values; /* Parsed key-values from bls-append-except-default key in config. */
249249
gboolean enable_bootprefix; /* If true, prepend bootloader entries with /boot */
250+
guint boot_counting;
250251

251252
OstreeRepo *parent_repo;
252253
};

src/libostree/ostree-repo.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3297,6 +3297,20 @@ reload_remote_config (OstreeRepo *self, GCancellable *cancellable, GError **erro
32973297
static gboolean
32983298
reload_sysroot_config (OstreeRepo *self, GCancellable *cancellable, GError **error)
32993299
{
3300+
{
3301+
g_autofree char *boot_counting_str = NULL;
3302+
3303+
(void)ot_keyfile_get_value_with_default_group_optional (self->config, "sysroot", "boot-counting-tries", "0",
3304+
&boot_counting_str, NULL);
3305+
3306+
if (boot_counting_str)
3307+
/* Ensure boot count value is in [0,5] */
3308+
self->boot_counting
3309+
= MAX (0, MIN (INT_MAX, g_ascii_strtoull (boot_counting_str, NULL, 10)));
3310+
else
3311+
self->boot_counting = 0;
3312+
}
3313+
33003314
g_autofree char *bootloader = NULL;
33013315

33023316
if (!ot_keyfile_get_value_with_default_group_optional (self->config, "sysroot", "bootloader",

src/libostree/ostree-sysroot-deploy.c

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1801,13 +1801,13 @@ parse_os_release (const char *contents, const char *split)
18011801
return ret;
18021802
}
18031803

1804-
/* Generate the filename we will use in /boot/loader/entries for this deployment.
1804+
/* Generate the entry name we will use in /boot/loader/entries for this deployment.
18051805
* The provided n_deployments should be the total number of target deployments (which
18061806
* might be different from the cached value in the sysroot).
18071807
*/
18081808
static char *
1809-
bootloader_entry_filename (OstreeSysroot *sysroot, guint n_deployments,
1810-
OstreeDeployment *deployment)
1809+
bootloader_entry_name (OstreeSysroot *sysroot, guint n_deployments,
1810+
OstreeDeployment *deployment)
18111811
{
18121812
guint index = n_deployments - ostree_deployment_get_index (deployment);
18131813
// Allow opt-out to dropping the stateroot in case of compatibility issues.
@@ -1817,14 +1817,34 @@ bootloader_entry_filename (OstreeSysroot *sysroot, guint n_deployments,
18171817
if (use_old_naming)
18181818
{
18191819
const char *stateroot = ostree_deployment_get_osname (deployment);
1820-
return g_strdup_printf ("ostree-%d-%s.conf", index, stateroot);
1820+
return g_strdup_printf ("ostree-%d-%s", index, stateroot);
18211821
}
18221822
else
18231823
{
1824-
return g_strdup_printf ("ostree-%d.conf", index);
1824+
return g_strdup_printf ("ostree-%d", index);
18251825
}
18261826
}
18271827

1828+
static guint
1829+
bootloader_get_max_boot_tries (OstreeSysroot *self, GCancellable *cancellable, GError **error)
1830+
{
1831+
g_autoptr (OstreeRepo) repo = NULL;
1832+
if (!ostree_sysroot_get_repo (self, &repo, cancellable, error))
1833+
return 0;
1834+
1835+
return repo->boot_counting;
1836+
}
1837+
1838+
static gboolean
1839+
bootloader_is_boot_count_enabled (OstreeSysroot *self, GCancellable *cancellable, GError **error)
1840+
{
1841+
g_autoptr (OstreeRepo) repo = NULL;
1842+
if (!ostree_sysroot_get_repo (self, &repo, cancellable, error))
1843+
return FALSE;
1844+
1845+
return (repo->boot_counting != 0 ? TRUE : FALSE);
1846+
}
1847+
18281848
/* Given @deployment, prepare it to be booted; basically copying its
18291849
* kernel/initramfs into /boot/ostree (if needed) and writing out an entry in
18301850
* /boot/loader/entries.
@@ -1859,7 +1879,7 @@ install_deployment_kernel (OstreeSysroot *sysroot, int new_bootversion,
18591879
const char *bootcsum = ostree_deployment_get_bootcsum (deployment);
18601880
g_autofree char *bootcsumdir = g_strdup_printf ("ostree/%s-%s", osname, bootcsum);
18611881
g_autofree char *bootconfdir = g_strdup_printf ("loader.%d/entries", new_bootversion);
1862-
g_autofree char *bootconf_name = bootloader_entry_filename (sysroot, n_deployments, deployment);
1882+
g_autofree char *bootconf_name = bootloader_entry_name (sysroot, n_deployments, deployment);
18631883

18641884
if (!glnx_shutil_mkdir_p_at (sysroot->boot_fd, bootcsumdir, 0775, cancellable, error))
18651885
return FALSE;
@@ -2173,8 +2193,28 @@ install_deployment_kernel (OstreeSysroot *sysroot, int new_bootversion,
21732193
if (!glnx_opendirat (sysroot->boot_fd, bootconfdir, TRUE, &bootconf_dfd, error))
21742194
return FALSE;
21752195

2196+
g_autofree char *bootconf_filename;
2197+
if (bootloader_is_boot_count_enabled(sysroot, cancellable, error))
2198+
{
2199+
guint max_tries = bootloader_get_max_boot_tries (sysroot, cancellable, error);
2200+
2201+
if (!ostree_bootconfig_parser_is_parsed(bootconfig))
2202+
bootconf_filename = g_strdup_printf ("%s+%u.conf", bootconf_name, max_tries);
2203+
else if (!ostree_bootconfig_parser_get_tries_left(bootconfig) &&
2204+
!ostree_bootconfig_parser_get_tries_done(bootconfig) )
2205+
bootconf_filename = g_strdup_printf ("%s.conf", bootconf_name);
2206+
else
2207+
bootconf_filename = g_strdup_printf ("%s+%" PRIu64 "-%" PRIu64 ".conf", bootconf_name,
2208+
ostree_bootconfig_parser_get_tries_left(bootconfig),
2209+
ostree_bootconfig_parser_get_tries_done(bootconfig));
2210+
}
2211+
else
2212+
{
2213+
bootconf_filename = g_strdup_printf ("%s.conf", bootconf_name);
2214+
}
2215+
21762216
if (!ostree_bootconfig_parser_write_at (ostree_deployment_get_bootconfig (deployment),
2177-
bootconf_dfd, bootconf_name, cancellable, error))
2217+
bootconf_dfd, bootconf_filename, cancellable, error))
21782218
return FALSE;
21792219

21802220
return TRUE;
@@ -4213,14 +4253,35 @@ ostree_sysroot_deployment_set_kargs_in_place (OstreeSysroot *self, OstreeDeploym
42134253
ostree_bootconfig_parser_set (new_bootconfig, "options", kargs_str);
42144254

42154255
g_autofree char *bootconf_name
4216-
= bootloader_entry_filename (self, self->deployments->len, deployment);
4256+
= bootloader_entry_name (self, self->deployments->len, deployment);
42174257

42184258
g_autofree char *bootconfdir = g_strdup_printf ("loader.%d/entries", self->bootversion);
42194259
glnx_autofd int bootconf_dfd = -1;
42204260
if (!glnx_opendirat (self->boot_fd, bootconfdir, TRUE, &bootconf_dfd, error))
42214261
return FALSE;
42224262

4223-
if (!ostree_bootconfig_parser_write_at (new_bootconfig, bootconf_dfd, bootconf_name,
4263+
g_autofree char *bootconf_filename;
4264+
if (bootloader_is_boot_count_enabled(self, cancellable, error))
4265+
{
4266+
guint max_tries = bootloader_get_max_boot_tries (self, cancellable, error);
4267+
4268+
if (!ostree_bootconfig_parser_is_parsed (new_bootconfig))
4269+
bootconf_filename = g_strdup_printf ("%s+%u.conf", bootconf_name, max_tries);
4270+
else if (!ostree_bootconfig_parser_get_tries_left (new_bootconfig)
4271+
&& !ostree_bootconfig_parser_get_tries_done (new_bootconfig))
4272+
bootconf_filename = g_strdup_printf ("%s.conf", bootconf_name);
4273+
else
4274+
bootconf_filename
4275+
= g_strdup_printf ("%s+%lu-%lu.conf", bootconf_name,
4276+
ostree_bootconfig_parser_get_tries_left (new_bootconfig),
4277+
ostree_bootconfig_parser_get_tries_done (new_bootconfig));
4278+
}
4279+
else
4280+
{
4281+
bootconf_filename = g_strdup_printf ("%s.conf", bootconf_name);
4282+
}
4283+
4284+
if (!ostree_bootconfig_parser_write_at (new_bootconfig, bootconf_dfd, bootconf_filename,
42244285
cancellable, error))
42254286
return FALSE;
42264287
}

0 commit comments

Comments
 (0)