Merge pull request #31435 from bluca/portable_fix_versioned

portable: assorted bug fixes
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2024-04-05 17:04:17 +02:00 committed by GitHub
commit c1e7f938ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 123 additions and 64 deletions

View file

@ -61,6 +61,39 @@ bool image_name_is_valid(const char *s) {
return true;
}
int path_extract_image_name(const char *path, char **ret) {
_cleanup_free_ char *fn = NULL;
int r;
assert(path);
assert(ret);
/* Extract last component from path, without any "/" suffixes. */
r = path_extract_filename(path, &fn);
if (r < 0)
return r;
if (r != O_DIRECTORY) {
/* Chop off any image suffixes we recognize (unless we already know this must refer to some dir */
FOREACH_STRING(suffix, ".sysext.raw", ".confext.raw", ".raw") {
char *m = endswith(fn, suffix);
if (m) {
*m = 0;
break;
}
}
}
/* Truncate the version/counting suffixes */
fn[strcspn(fn, "_+")] = 0;
if (!image_name_is_valid(fn))
return -EINVAL;
*ret = TAKE_PTR(fn);
return 0;
}
int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check) {
int r;
@ -230,9 +263,25 @@ int open_extension_release_at(
continue;
}
if (!relax_extension_release_check &&
extension_release_strict_xattr_value(fd, dir_path, de->d_name) != 0)
continue;
if (!relax_extension_release_check) {
_cleanup_free_ char *base_image_name = NULL, *base_extension = NULL;
r = path_extract_image_name(image_name, &base_image_name);
if (r < 0) {
log_debug_errno(r, "Failed to extract image name from %s/%s, ignoring: %m", dir_path, de->d_name);
continue;
}
r = path_extract_image_name(extension, &base_extension);
if (r < 0) {
log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", extension);
continue;
}
if (!streq(base_image_name, base_extension) &&
extension_release_strict_xattr_value(fd, dir_path, image_name) != 0)
continue;
}
/* We already found what we were looking for, but there's another candidate? We treat this as
* an error, as we want to enforce that there are no ambiguities in case we are in the

View file

@ -25,6 +25,7 @@ ImageClass image_class_from_string(const char *s) _pure_;
* in accordance with the OS extension specification, rather than for /usr/lib/ or /etc/os-release. */
bool image_name_is_valid(const char *s) _pure_;
int path_extract_image_name(const char *path, char **ret);
int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check);
static inline int path_is_os_tree(const char *path) {

View file

@ -1664,9 +1664,8 @@ int portable_attach(
return 0;
}
static bool marker_matches_images(const char *marker, const char *name_or_path, char **extension_image_paths) {
static bool marker_matches_images(const char *marker, const char *name_or_path, char **extension_image_paths, bool match_all) {
_cleanup_strv_free_ char **root_and_extensions = NULL;
const char *a;
int r;
assert(marker);
@ -1676,7 +1675,9 @@ static bool marker_matches_images(const char *marker, const char *name_or_path,
* list of images/paths. We enforce strict 1:1 matching, so that we are sure
* we are detaching exactly what was attached.
* For each image, starting with the root, we look for a token in the marker,
* and return a negative answer on any non-matching combination. */
* and return a negative answer on any non-matching combination.
* If a partial match is allowed, then return immediately once it is found, otherwise
* ensure that everything matches. */
root_and_extensions = strv_new(name_or_path);
if (!root_and_extensions)
@ -1686,70 +1687,33 @@ static bool marker_matches_images(const char *marker, const char *name_or_path,
if (r < 0)
return r;
STRV_FOREACH(image_name_or_path, root_and_extensions) {
_cleanup_free_ char *image = NULL;
/* Ensure the number of images passed matches the number of images listed in the marker */
while (!isempty(marker))
STRV_FOREACH(image_name_or_path, root_and_extensions) {
_cleanup_free_ char *image = NULL, *base_image = NULL, *base_image_name_or_path = NULL;
r = extract_first_word(&marker, &image, ":", EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
return log_debug_errno(r, "Failed to parse marker: %s", marker);
if (r == 0)
return false;
a = last_path_component(image);
if (image_name_is_valid(*image_name_or_path)) {
const char *e, *underscore;
/* We shall match against an image name. In that case let's compare the last component, and optionally
* allow either a suffix of ".raw" or a series of "/".
* But allow matching on a different version of the same image, when a "_" is used as a separator. */
underscore = strchr(*image_name_or_path, '_');
if (underscore) {
if (strneq(a, *image_name_or_path, underscore - *image_name_or_path))
continue;
return false;
}
e = startswith(a, *image_name_or_path);
if (!e)
r = extract_first_word(&marker, &image, ":", EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
return log_debug_errno(r, "Failed to parse marker: %s", marker);
if (r == 0)
return false;
if(!(e[strspn(e, "/")] == 0 || streq(e, ".raw")))
return false;
} else {
const char *b, *underscore;
size_t l;
r = path_extract_image_name(image, &base_image);
if (r < 0)
return log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", image);
/* We shall match against a path. Let's ignore any prefix here though, as often there are many ways to
* reach the same file. However, in this mode, let's validate any file suffix.
* But also ensure that we don't fail if both components don't have a '/' at all
* (strcspn returns the full length of the string in that case, which might not
* match as the versions might differ). */
r = path_extract_image_name(*image_name_or_path, &base_image_name_or_path);
if (r < 0)
return log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", *image_name_or_path);
l = strcspn(a, "/");
b = last_path_component(*image_name_or_path);
if ((a[l] != '/') != !strchr(b, '/')) /* One is a directory, the other is not */
return false;
if (a[l] != 0 && strcspn(b, "/") != l)
return false;
underscore = strchr(b, '_');
if (underscore)
l = underscore - b;
else { /* Either component could be versioned */
underscore = strchr(a, '_');
if (underscore)
l = underscore - a;
}
if (!strneq(a, b, l))
return false;
if (!streq(base_image, base_image_name_or_path)) {
if (match_all)
return false;
} else if (!match_all)
return true;
}
}
return true;
return match_all;
}
static int test_chroot_dropin(
@ -1804,7 +1768,9 @@ static int test_chroot_dropin(
if (!name_or_path)
r = true;
else
r = marker_matches_images(marker, name_or_path, extension_image_paths);
/* When detaching we want to match exactly on all images, but when inspecting we only need
* to get the state of one component */
r = marker_matches_images(marker, name_or_path, extension_image_paths, ret_marker != NULL);
if (ret_marker)
*ret_marker = TAKE_PTR(marker);

View file

@ -139,6 +139,19 @@ grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd
portablectl detach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0
# Ensure versioned images are accepted without needing to use --force to override the extension-release
# matching
cp /usr/share/app0.raw /tmp/app0_1.0.raw
portablectl "${ARGS[@]}" attach --now --runtime --extension /tmp/app0_1.0.raw /usr/share/minimal_0.raw app0
systemctl is-active app0.service
status="$(portablectl is-attached --extension app0_1 minimal_0)"
[[ "${status}" == "running-runtime" ]]
portablectl detach --now --runtime --extension /tmp/app0_1.0.raw /usr/share/minimal_1.raw app0
rm -f /tmp/app0_1.0.raw
portablectl "${ARGS[@]}" attach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
systemctl is-active app1.service
@ -211,6 +224,36 @@ test -f /run/systemd/system.attached/app0.service
test -L /run/systemd/system.attached/app0.service.d/10-profile.conf
portablectl detach --runtime --extension /usr/share/app0.raw /usr/share/minimal_0.raw app0
# Ensure that when two portables share the same base image, removing one doesn't remove the other too
portablectl "${ARGS[@]}" attach --runtime --extension /usr/share/app0.raw /usr/share/minimal_0.raw app0
portablectl "${ARGS[@]}" attach --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
status="$(portablectl is-attached --extension app0 minimal_0)"
[[ "${status}" == "attached-runtime" ]]
status="$(portablectl is-attached --extension app1 minimal_0)"
[[ "${status}" == "attached-runtime" ]]
(! portablectl detach --runtime /usr/share/minimal_0.raw app)
status="$(portablectl is-attached --extension app0 minimal_0)"
[[ "${status}" == "attached-runtime" ]]
status="$(portablectl is-attached --extension app1 minimal_0)"
[[ "${status}" == "attached-runtime" ]]
# Ensure 'portablectl list' shows the correct status for both images
portablectl list
portablectl list | grep -F "minimal_0" | grep -q -F "attached-runtime"
portablectl list | grep -F "app0" | grep -q -F "attached-runtime"
portablectl list | grep -F "app1" | grep -q -F "attached-runtime"
portablectl detach --runtime --extension /usr/share/app0.raw /usr/share/minimal_0.raw app
status="$(portablectl is-attached --extension app1 minimal_0)"
[[ "${status}" == "attached-runtime" ]]
portablectl detach --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app
# portablectl also works with directory paths rather than images
mkdir /tmp/rootdir /tmp/app0 /tmp/app1 /tmp/overlay /tmp/os-release-fix /tmp/os-release-fix/etc