From b03503a1169ad925f2c4146fb8499375fe477790 Mon Sep 17 00:00:00 2001 From: dongshengyuan <545258830@qq.com> Date: Sun, 26 Jul 2026 12:13:18 +0800 Subject: [PATCH 1/2] sysupdate: keep root-relative installdb paths absolute When recording installdb entries under --root=, keep the leading slash after stripping the root. Compare current transfer target paths in the same root-relative form during cleanup. This prevents cleanup from treating still-owned resources below --root= as orphaned. TEST-72-SYSUPDATE covers --root= cleanup keeping a still-owned file and its matching installdb entry. Repro: create a rooted transfer for /target/foo-@v.bin, add a matching installdb entry for /target/./foo-@v.bin, then run: build/systemd-sysupdate --root="$root" --verify=no cleanup Before: foo-1.bin and the installdb entry were removed. Follow-up for: d82e256bb9d151b185a8afec1fcacd8fbe80555c --- src/sysupdate/sysupdate-cleanup.c | 35 +++++++++++++++++++++++++++++-- test/units/TEST-72-SYSUPDATE.sh | 34 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/sysupdate/sysupdate-cleanup.c b/src/sysupdate/sysupdate-cleanup.c index fc18c594ac..af7b8cb130 100644 --- a/src/sysupdate/sysupdate-cleanup.c +++ b/src/sysupdate/sysupdate-cleanup.c @@ -112,6 +112,27 @@ static int installdb_make_names(const char *path, const char *pattern, char **re return 0; } +static const char* context_root_relative_path(const Context *c, const char *path) { + const char *p; + + assert(c); + assert(path); + + if (!c->root) + return path; + + p = path_startswith_full(path, c->root, PATH_STARTSWITH_RETURN_LEADING_SLASH); + if (p) + return p; + + /* path_startswith_full(..., PATH_STARTSWITH_RETURN_LEADING_SLASH) cannot return a pointer + * to a leading slash before the start of the original string when both paths are identical. */ + if (path_equal(path, c->root)) + return "/"; + + return NULL; +} + int context_installdb_record( Context *c, const char *path, @@ -129,7 +150,11 @@ int context_installdb_record( return 0; /* The provided path comes with c->root prefixed. Strip it here again */ - const char *p = c->root ? ASSERT_PTR(path_startswith(path, c->root)) : path; + const char *p = context_root_relative_path(c, path); + if (!p) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Resolved resource path '%s' is not below root '%s'.", + path, c->root); r = context_installdb_acquire_fd(c, /* make= */ true); if (r < 0) @@ -170,7 +195,13 @@ static int context_is_path_currently_owned( if (!RESOURCE_IS_FILESYSTEM(t->target.type)) continue; - if (!path_equal(t->target.path, path)) + const char *target_path = context_root_relative_path(c, t->target.path); + if (!target_path) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Resolved target path '%s' is not below root '%s'.", + t->target.path, c->root); + + if (!path_equal(target_path, path)) continue; /* OK, so we found a transfer that covers this directory. Now let's see if any of its patterns match */ diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh index c1978babd5..f1c4b951c9 100755 --- a/test/units/TEST-72-SYSUPDATE.sh +++ b/test/units/TEST-72-SYSUPDATE.sh @@ -907,6 +907,40 @@ test ! -f "$CLEANUP/target/alpha-v2.bin" rm -rf "$CONFIGDIR" "$INSTALLDB" "$CLEANUP" +# Under --root=, cleanup must compare current transfer target paths in the same +# root-relative form stored in the install database, and keep still-owned files. +ROOT_CLEANUP="$WORKDIR/root-cleanup" +ROOT_CLEANUP_ROOT="$ROOT_CLEANUP/root" +ROOT_CLEANUP_DB_VALUE="/target/./alpha-@v.bin" +ROOT_CLEANUP_DB_KEY="$(printf '%s' "$ROOT_CLEANUP_DB_VALUE" | sha256sum | cut -d' ' -f1)" +rm -rf "$ROOT_CLEANUP" +mkdir -p "$ROOT_CLEANUP_ROOT/etc/sysupdate.d" \ + "$ROOT_CLEANUP_ROOT/target" \ + "$ROOT_CLEANUP_ROOT/var/lib/systemd/sysupdate/installdb" + +cat >"$ROOT_CLEANUP_ROOT/etc/sysupdate.d/01-alpha.transfer" <"$ROOT_CLEANUP_ROOT/target/alpha-v1.bin" +ln -s "$ROOT_CLEANUP_DB_VALUE" \ + "$ROOT_CLEANUP_ROOT/var/lib/systemd/sysupdate/installdb/$ROOT_CLEANUP_DB_KEY" + +"$SYSUPDATE" --root="$ROOT_CLEANUP_ROOT" --verify=no cleanup +test -f "$ROOT_CLEANUP_ROOT/target/alpha-v1.bin" +test -L "$ROOT_CLEANUP_ROOT/var/lib/systemd/sysupdate/installdb/$ROOT_CLEANUP_DB_KEY" + +rm -rf "$ROOT_CLEANUP" + # Briefly check the "--component-all" switch of the "cleanup" verb. Each component # keeps its own install database (installdb.), and "cleanup # --component-all" must clean up orphaned resources across *all* of them in one From bbe99a925b92ac00f7539af953757ec838e1db60 Mon Sep 17 00:00:00 2001 From: dongshengyuan <545258830@qq.com> Date: Sun, 26 Jul 2026 12:17:17 +0800 Subject: [PATCH 2/2] sysupdate: include default component for feature-all --component-all is documented to include the default component-less installation. Do not drop it merely because the context operates on a root/image, or because all its transfers are currently disabled by features. This lets --component-all --feature-all enable-feature write the default component feature drop-ins instead of succeeding with no components selected. TEST-72-SYSUPDATE covers both all transfers disabled by features and the same default component feature operation under --root=. Repro: create a default feata.feature plus a transfer gated by feata, then run: build/systemd-sysupdate --root="$root" --component-all --feature-all enable-feature Before: no drop-in was written. Follow-up for: 4481661a75acc01b5d66aa36443a7b80b557e4ba --- src/sysupdate/sysupdate.c | 8 +++--- test/units/TEST-72-SYSUPDATE.sh | 44 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 07f6f7ab77..9ae80a2a8b 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -2949,15 +2949,13 @@ static int context_list_components(Context *context, char ***ret_component_names if (ret_component_names) *ret_component_names = TAKE_PTR(z); - /* Does the system have at least one transfer file in /etc/sysupdate.d, which can be considered a - * TARGET_HOST? See target_get_argument() in sysupdated.c */ + /* Does the system have at least one transfer file in sysupdate.d, which can be considered the + * default component-less installation? */ if (ret_has_default_component) *ret_has_default_component = !context->definitions && !context->component && - !context->root && - !context->image && - context->n_transfers > 0; + context->n_transfers + context->n_disabled_transfers > 0; return 0; } diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh index f1c4b951c9..1ddb2ac9b9 100755 --- a/test/units/TEST-72-SYSUPDATE.sh +++ b/test/units/TEST-72-SYSUPDATE.sh @@ -1833,6 +1833,50 @@ assert_dropin "$(feat_enable_dropin_default featc)" yes test ! -e "$(feat_enable_dropin_default featb)" set_machine_tags "" +# --component-all must still include the default component when all of its +# transfers are currently disabled by features. +compfeat_reset +compfeat_source v1 +mkdir -p /run/sysupdate.d +compfeat_transfer /run/sysupdate.d/50-feata.transfer feata "$CF/target-default" feata +cat >/run/sysupdate.d/feata.feature <"$ROOT_FEATURE_ROOT/etc/sysupdate.d/50-feata.transfer" <"$ROOT_FEATURE_ROOT/etc/sysupdate.d/feata.feature" <