mirror of
https://github.com/systemd/systemd.git
synced 2026-08-09 17:38:42 +00:00
bootctl: add helpers that format a type1 menu entry filename from a commit nr
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "boot-entry.h"
|
||||
#include "bootspec-util.h"
|
||||
#include "devnum-util.h"
|
||||
#include "efi-loader.h"
|
||||
#include "errno-util.h"
|
||||
#include "log.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "utf8.h"
|
||||
|
||||
int boot_config_load_and_select(
|
||||
BootConfig *config,
|
||||
@@ -39,3 +46,210 @@ int boot_config_load_and_select(
|
||||
|
||||
return boot_config_select_special_entries(config, /* skip_efivars= */ !!root);
|
||||
}
|
||||
|
||||
int boot_entry_make_commit_filename(
|
||||
const char *entry_token,
|
||||
uint64_t entry_commit,
|
||||
const char *version,
|
||||
unsigned profile_nr,
|
||||
unsigned tries_left,
|
||||
char **ret) {
|
||||
|
||||
assert(entry_token);
|
||||
assert(ret);
|
||||
|
||||
/* Generate a new entry filename from the entry token, the commit number, and (optionally) the
|
||||
* image/OS version, (if non-zero) the profile number, and (unless UINT_MAX) the number of tries
|
||||
* left. */
|
||||
|
||||
if (!boot_entry_token_valid(entry_token))
|
||||
return -EINVAL;
|
||||
if (!entry_commit_valid(entry_commit))
|
||||
return -EINVAL;
|
||||
|
||||
_cleanup_free_ char *filename = asprintf_safe("%s-commit_%" PRIu64, entry_token, entry_commit);
|
||||
if (!filename)
|
||||
return -ENOMEM;
|
||||
if (version && !strextend(&filename, ".", version))
|
||||
return -ENOMEM;
|
||||
if (profile_nr > 0 && strextendf(&filename, "@%u", profile_nr) < 0)
|
||||
return -ENOMEM;
|
||||
if (tries_left != UINT_MAX && strextendf(&filename, "+%u", tries_left) < 0)
|
||||
return -ENOMEM;
|
||||
if (!strextend(&filename, ".conf"))
|
||||
return -ENOMEM;
|
||||
|
||||
if (!filename_is_valid(filename) || string_has_cc(filename, /* ok= */ NULL) || !utf8_is_valid(filename))
|
||||
return -EINVAL;
|
||||
|
||||
*ret = TAKE_PTR(filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int boot_entry_parse_commit_filename(
|
||||
const char *filename,
|
||||
char **ret_entry_token,
|
||||
uint64_t *ret_entry_commit) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
|
||||
if (!filename_is_valid(filename))
|
||||
return -EINVAL;
|
||||
|
||||
_cleanup_free_ char *stripped = NULL;
|
||||
r = boot_filename_extract_tries(filename, &stripped, /* ret_tries_left= */ NULL, /* ret_tries_done= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
const char *a = strrstr_no_case(stripped, "-commit_");
|
||||
if (!a)
|
||||
return -EBADMSG;
|
||||
|
||||
const char *c = endswith_no_case(stripped, ".conf");
|
||||
if (!c)
|
||||
return -EBADMSG;
|
||||
|
||||
assert(a < c);
|
||||
|
||||
_cleanup_free_ char *entry_token = strndup(stripped, a - stripped);
|
||||
if (!entry_token)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!boot_entry_token_valid(entry_token))
|
||||
return -EBADMSG;
|
||||
|
||||
const char *b = a + STRLEN("-commit_");
|
||||
size_t n = strspn(b, DIGITS);
|
||||
if (n <= 0 || !IN_SET(b[n], '+', '.', '@'))
|
||||
return -EBADMSG;
|
||||
|
||||
_cleanup_free_ char *entry_commit_string = strndup(b, n);
|
||||
if (!entry_commit_string)
|
||||
return -ENOMEM;
|
||||
|
||||
uint64_t entry_commit;
|
||||
r = safe_atou64_full(entry_commit_string, 10, &entry_commit);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!entry_commit_valid(entry_commit))
|
||||
return -EBADMSG;
|
||||
|
||||
if (ret_entry_token)
|
||||
*ret_entry_token = TAKE_PTR(entry_token);
|
||||
if (ret_entry_commit)
|
||||
*ret_entry_commit = entry_commit;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int boot_entry_parse_commit(
|
||||
BootEntry *entry,
|
||||
char **ret_entry_token,
|
||||
uint64_t *ret_entry_commit) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(entry);
|
||||
|
||||
if (entry->type != BOOT_ENTRY_TYPE1)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
r = path_extract_filename(entry->path, &fn);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return boot_entry_parse_commit_filename(fn, ret_entry_token, ret_entry_commit);
|
||||
}
|
||||
|
||||
int boot_config_find_oldest_commit(
|
||||
BootConfig *config,
|
||||
const char *entry_token,
|
||||
char ***ret_ids) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(config);
|
||||
assert(entry_token);
|
||||
assert(ret_ids);
|
||||
|
||||
uint64_t commit_oldest = UINT64_MAX, commit_2nd_oldest = UINT64_MAX, commit_blocked = UINT64_MAX;
|
||||
|
||||
/* First, determine which commit is the oldest (that isn't the current one), and hence the candidate
|
||||
* to be removed */
|
||||
FOREACH_ARRAY(b, config->entries, config->n_entries) {
|
||||
_cleanup_free_ char *et = NULL;
|
||||
uint64_t ec;
|
||||
|
||||
r = boot_entry_parse_commit(b, &et, &ec);
|
||||
if (r == -EADDRNOTAVAIL)
|
||||
continue;
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to parse entry filename of '%s', ignoring: %m", strna(b->id));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!streq(et, entry_token)) /* Not ours? */
|
||||
continue;
|
||||
|
||||
if (ec < commit_oldest) {
|
||||
commit_2nd_oldest = commit_oldest;
|
||||
commit_oldest = ec;
|
||||
} else if (ec > commit_oldest && ec < commit_2nd_oldest)
|
||||
commit_2nd_oldest = ec;
|
||||
|
||||
if (boot_config_selected_entry(config) == b) {
|
||||
assert(commit_blocked == UINT64_MAX);
|
||||
commit_blocked = ec;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t commit_picked;
|
||||
if (commit_oldest == UINT64_MAX)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "No matching entry found while determining oldest entry.");
|
||||
if (commit_oldest != commit_blocked)
|
||||
commit_picked = commit_oldest;
|
||||
else {
|
||||
if (commit_2nd_oldest == UINT64_MAX)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Only matching entry found while determining oldest entry is current one, skipping it.");
|
||||
|
||||
assert(commit_2nd_oldest != commit_blocked);
|
||||
commit_picked = commit_2nd_oldest;
|
||||
}
|
||||
|
||||
log_debug("Determined commit %" PRIu64 " to be oldest.", commit_picked);
|
||||
|
||||
/* Second loop: actually remove all entries matching this commit (which can be multiple, since UKIs
|
||||
* have profiles) */
|
||||
_cleanup_(strv_freep) char **l = NULL;
|
||||
FOREACH_ARRAY(b, config->entries, config->n_entries) {
|
||||
_cleanup_free_ char *et = NULL;
|
||||
uint64_t ec;
|
||||
|
||||
r = boot_entry_parse_commit(b, &et, &ec);
|
||||
if (r == -EADDRNOTAVAIL)
|
||||
continue;
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to parse entry filename of '%s', ignoring: %m", strna(b->id));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!streq(et, entry_token)) /* Not ours? */
|
||||
continue;
|
||||
|
||||
if (ec != commit_picked)
|
||||
continue;
|
||||
|
||||
r = strv_extend(&l, b->id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
/* The list cannot be empty, the first loop above and the 2nd loop must have found the same matching
|
||||
* entries, and if the first loop didn't find any we'd not come this far. */
|
||||
assert(!strv_isempty(l));
|
||||
*ret_ids = TAKE_PTR(l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -4,3 +4,15 @@
|
||||
#include "bootspec.h"
|
||||
|
||||
int boot_config_load_and_select(BootConfig *config, const char *root, const char *esp_path, dev_t esp_devid, const char *xbootldr_path, dev_t xbootldr_devid);
|
||||
|
||||
static inline bool entry_commit_valid(uint64_t commit) {
|
||||
return commit > 0 && commit < UINT64_MAX;
|
||||
}
|
||||
|
||||
int boot_entry_make_commit_filename(const char *entry_token, uint64_t entry_commit, const char *version, unsigned profile_nr, unsigned tries_left, char **ret);
|
||||
|
||||
int boot_entry_parse_commit_filename(const char *filename, char **ret_entry_token, uint64_t *ret_entry_commit);
|
||||
|
||||
int boot_entry_parse_commit(BootEntry *entry, char **ret_entry_token, uint64_t *ret_entry_commit);
|
||||
|
||||
int boot_config_find_oldest_commit(BootConfig *config, const char *entry_token, char ***ret_ids);
|
||||
|
||||
@@ -25,4 +25,10 @@ executables += [
|
||||
'link_with' : boot_link_with,
|
||||
'dependencies' : [libopenssl_cflags],
|
||||
},
|
||||
test_template + {
|
||||
'sources' : files(
|
||||
'test-bootspec-util.c',
|
||||
'bootspec-util.c',
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
51
src/bootctl/test-bootspec-util.c
Normal file
51
src/bootctl/test-bootspec-util.c
Normal file
@@ -0,0 +1,51 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "bootspec-util.h"
|
||||
#include "tests.h"
|
||||
|
||||
static void test_one(
|
||||
const char *entry_token,
|
||||
uint64_t entry_commit,
|
||||
const char *version,
|
||||
unsigned profile_nr,
|
||||
unsigned tries_left,
|
||||
const char *expected) {
|
||||
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
ASSERT_OK(boot_entry_make_commit_filename(entry_token, entry_commit, version, profile_nr, tries_left, &fn));
|
||||
ASSERT_STREQ(fn, expected);
|
||||
|
||||
_cleanup_free_ char *token = NULL;
|
||||
uint64_t commit = 0;
|
||||
ASSERT_OK(boot_entry_parse_commit_filename(fn, &token, &commit));
|
||||
ASSERT_STREQ(token, entry_token);
|
||||
ASSERT_EQ(commit, entry_commit);
|
||||
}
|
||||
|
||||
TEST(boot_entry_commit_filename) {
|
||||
test_one("foo", 1, NULL, 0, UINT_MAX, "foo-commit_1.conf");
|
||||
test_one("foo", 42, "1.0", 0, UINT_MAX, "foo-commit_42.1.0.conf");
|
||||
test_one("foo", 42, "1.0", 3, UINT_MAX, "foo-commit_42.1.0@3.conf");
|
||||
test_one("foo", 42, "1.0", 3, 5, "foo-commit_42.1.0@3+5.conf");
|
||||
test_one("foo", 42, NULL, 3, UINT_MAX, "foo-commit_42@3.conf");
|
||||
test_one("foo", 42, NULL, 3, 7, "foo-commit_42@3+7.conf");
|
||||
test_one("foo", 42, NULL, 0, 9, "foo-commit_42+9.conf");
|
||||
test_one("my-token", 123456, "v2", 0, UINT_MAX, "my-token-commit_123456.v2.conf");
|
||||
|
||||
/* Invalid inputs for make */
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
ASSERT_ERROR(boot_entry_make_commit_filename("foo/bar", 1, NULL, 0, UINT_MAX, &fn), EINVAL);
|
||||
ASSERT_ERROR(boot_entry_make_commit_filename("foo", 0, NULL, 0, UINT_MAX, &fn), EINVAL);
|
||||
ASSERT_ERROR(boot_entry_make_commit_filename("foo", UINT64_MAX, NULL, 0, UINT_MAX, &fn), EINVAL);
|
||||
|
||||
/* Invalid inputs for parse */
|
||||
_cleanup_free_ char *token = NULL;
|
||||
uint64_t commit = 0;
|
||||
ASSERT_ERROR(boot_entry_parse_commit_filename("foo.conf", &token, &commit), EBADMSG);
|
||||
ASSERT_ERROR(boot_entry_parse_commit_filename("foo-commit_.conf", &token, &commit), EBADMSG);
|
||||
ASSERT_ERROR(boot_entry_parse_commit_filename("foo-commit_abc.conf", &token, &commit), EBADMSG);
|
||||
ASSERT_ERROR(boot_entry_parse_commit_filename("foo-commit_0.conf", &token, &commit), EBADMSG);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
Reference in New Issue
Block a user