sysupdate: introduce "installdb" that keeps track of installed resources

Let's make sure we keep track of any file we drop into the system via a
database in /var/. This database is implemented based on symlinks, i.e.
reuses the fs as a simple database. Given the database most likely will
have <= 10 entries only (as we store *patterns* of installed file paths in
them, not the file paths themselves), this should be very efficient.

For implementation details see comments at top of
src/sysupdate/sysupdate-cleanup.c.
This commit is contained in:
Lennart Poettering
2026-06-22 14:44:51 +02:00
parent 39ff56374c
commit d82e256bb9
7 changed files with 665 additions and 51 deletions
+133 -51
View File
@@ -8,8 +8,11 @@
#include "conf-files.h"
#include "constants.h"
#include "dissect-image.h"
#include "errno-util.h"
#include "fd-util.h"
#include "format-table.h"
#include "glyph-util.h"
#include "hashmap.h"
#include "hexdecoct.h"
#include "image-policy.h"
#include "loop-util.h"
@@ -20,20 +23,18 @@
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "path-util.h"
#include "pretty-print.h"
#include "set.h"
#include "sort-util.h"
#include "specifier.h"
#include "string-util.h"
#include "strv.h"
#include "sysupdate.h"
#include "sysupdate-cleanup.h"
#include "sysupdate-feature.h"
#include "sysupdate-instance.h"
#include "sysupdate-transfer.h"
#include "sysupdate-update-set.h"
#include "sysupdate-util.h"
#include "utf8.h"
#include "verbs.h"
static char *arg_definitions = NULL;
@@ -46,6 +47,7 @@ char *arg_root = NULL;
static char *arg_image = NULL;
static bool arg_reboot = false;
static char *arg_component = NULL;
static bool arg_component_all = false;
static int arg_verify = -1;
static ImagePolicy *arg_image_policy = NULL;
static bool arg_offline = false;
@@ -64,27 +66,12 @@ const Specifier specifier_table[] = {
{}
};
typedef struct Context {
Transfer **transfers;
size_t n_transfers;
Transfer **disabled_transfers;
size_t n_disabled_transfers;
Hashmap *features; /* Defined features, keyed by ID */
UpdateSet **update_sets;
size_t n_update_sets;
UpdateSet *newest_installed, *candidate;
Hashmap *web_cache; /* Cache for downloaded resources, keyed by URL */
} Context;
static Context* context_free(Context *c) {
Context* context_free(Context *c) {
if (!c)
return NULL;
free(c->component);
FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
transfer_free(*tr);
free(c->transfers);
@@ -101,14 +88,21 @@ static Context* context_free(Context *c) {
hashmap_free(c->web_cache);
safe_close(c->installdb_fd);
return mfree(c);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free);
static Context* context_new(void) {
/* For now, no fields to initialize non-zero */
return new0(Context, 1);
Context *c = new(Context, 1);
if (!c)
return NULL;
*c = (Context) {
.installdb_fd = -EBADF,
};
return c;
}
static DEFINE_POINTER_ARRAY_FREE_FUNC(Transfer*, transfer_free);
@@ -171,11 +165,6 @@ static int read_definitions(
return 0;
}
typedef enum ReadDefinitionsFlags {
READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS = 1 << 0,
READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS = 1 << 1,
} ReadDefinitionsFlags;
static int context_read_definitions(Context *c, const char* node, ReadDefinitionsFlags flags) {
_cleanup_strv_free_ char **dirs = NULL;
int r;
@@ -184,7 +173,7 @@ static int context_read_definitions(Context *c, const char* node, ReadDefinition
if (arg_definitions)
dirs = strv_new(arg_definitions);
else if (arg_component) {
else if (c->component) {
char **l = CONF_PATHS_STRV("");
size_t i = 0;
@@ -195,7 +184,7 @@ static int context_read_definitions(Context *c, const char* node, ReadDefinition
STRV_FOREACH(dir, l) {
char *j;
j = strjoin(*dir, "sysupdate.", arg_component, ".d");
j = strjoin(*dir, "sysupdate.", c->component, ".d");
if (!j)
return log_oom();
@@ -251,10 +240,10 @@ static int context_read_definitions(Context *c, const char* node, ReadDefinition
if (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS) &&
c->n_transfers + (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS) ? 0 : c->n_disabled_transfers) == 0) {
if (arg_component)
if (c->component)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"No transfer definitions for component '%s' found.",
arg_component);
c->component);
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"No transfer definitions found.");
@@ -931,7 +920,11 @@ static int context_vacuum(
return 0;
}
static int context_make_offline(Context **ret, const char *node, ReadDefinitionsFlags read_definitions_flags) {
int context_make_offline(
Context **ret,
const char *node,
const char *component,
ReadDefinitionsFlags read_definitions_flags) {
_cleanup_(context_freep) Context* context = NULL;
int r;
@@ -944,6 +937,10 @@ static int context_make_offline(Context **ret, const char *node, ReadDefinitions
if (!context)
return log_oom();
r = free_and_strdup_warn(&context->component, component);
if (r < 0)
return r;
r = context_read_definitions(context, node, read_definitions_flags);
if (r < 0)
return r;
@@ -956,7 +953,11 @@ static int context_make_offline(Context **ret, const char *node, ReadDefinitions
return 0;
}
static int context_make_online(Context **ret, const char *node) {
static int context_make_online(
Context **ret,
const char *node,
const char *component) {
_cleanup_(context_freep) Context* context = NULL;
int r;
@@ -965,8 +966,11 @@ static int context_make_online(Context **ret, const char *node) {
/* Like context_make_offline(), but also communicates with the update source looking for new
* versions (as long as --offline is not specified on the command line). */
r = context_make_offline(&context, node,
READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS | READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
r = context_make_offline(
&context,
node,
component,
READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
if (r < 0)
return r;
@@ -1297,11 +1301,17 @@ static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
assert(argc <= 2);
version = argc >= 2 ? argv[1] : NULL;
if (arg_component_all)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
if (r < 0)
return r;
r = context_make_online(&context, loop_device ? loop_device->node : NULL);
r = context_make_online(
&context,
loop_device ? loop_device->node : NULL,
arg_component);
if (r < 0)
return r;
@@ -1368,12 +1378,18 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata
assert(argc <= 2);
feature_id = argc >= 2 ? argv[1] : NULL;
if (arg_component_all)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
if (r < 0)
return r;
r = context_make_offline(&context, loop_device ? loop_device->node : NULL,
READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
r = context_make_offline(
&context,
loop_device ? loop_device->node : NULL,
arg_component,
READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
if (r < 0)
return r;
@@ -1501,11 +1517,17 @@ static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdat
assert(argc <= 1);
if (arg_component_all)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
if (r < 0)
return r;
r = context_make_online(&context, loop_device ? loop_device->node : NULL);
r = context_make_online(
&context,
loop_device ? loop_device->node : NULL,
arg_component);
if (r < 0)
return r;
@@ -1551,6 +1573,9 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag
assert(argc <= 2);
version = argc >= 2 ? argv[1] : NULL;
if (arg_component_all)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
if (arg_instances_max < 2)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"The --instances-max argument must be >= 2 while updating");
@@ -1569,7 +1594,10 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag
if (r < 0)
return r;
r = context_make_online(&context, loop_device ? loop_device->node : NULL);
r = context_make_online(
&context,
loop_device ? loop_device->node : NULL,
arg_component);
if (r < 0)
return r;
@@ -1633,6 +1661,9 @@ static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata)
assert(argc <= 1);
if (arg_component_all)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
if (arg_instances_max < 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"The --instances-max argument must be >= 1 while vacuuming");
@@ -1641,8 +1672,11 @@ static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata)
if (r < 0)
return r;
r = context_make_offline(&context, loop_device ? loop_device->node : NULL,
READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
r = context_make_offline(
&context,
loop_device ? loop_device->node : NULL,
arg_component,
READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
if (r < 0)
return r;
@@ -1664,12 +1698,15 @@ static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"The --root=/--image= switches may not be combined with the '%s' operation.", argv[0]);
if (arg_component)
if (arg_component || arg_component_all)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"The --component= switch may not be combined with the '%s' operation, which only applies to the booted OS version.", argv[0]);
"The --component= and --component-all switches may not be combined with the '%s' operation, which only applies to the booted OS version.", argv[0]);
r = context_make_offline(&context, /* node= */ NULL,
READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS | READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
r = context_make_offline(
&context,
/* node= */ NULL,
arg_component,
READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
if (r < 0)
return r;
@@ -1722,11 +1759,18 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda
assert(argc <= 1);
if (arg_component_all)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
if (r < 0)
return r;
r = context_make_offline(&context, loop_device ? loop_device->node : NULL, 0);
r = context_make_offline(
&context,
loop_device ? loop_device->node : NULL,
arg_component,
/* read_definitions_flags= */ 0);
if (r < 0)
return r;
@@ -1738,7 +1782,7 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda
/* 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 */
has_default_component = (!arg_definitions &&
!arg_component &&
!context->component &&
!arg_root &&
!arg_image &&
context->n_transfers > 0);
@@ -1771,6 +1815,36 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda
return 0;
}
VERB_NOARG(verb_cleanup, "cleanup", "Clean up orphaned files");
static int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) {
int r;
assert(argc <= 1);
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
if (r < 0)
return r;
const char *node = loop_device ? loop_device->node : NULL;
int ret = 0;
RET_GATHER(ret, installdb_cleanup_component(node, arg_component));
if (arg_component_all) {
_cleanup_strv_free_ char **z = NULL;
r = installdb_list_components(&z);
if (r < 0)
return log_error_errno(r, "Failed to enumerate components: %m");
STRV_FOREACH(i, z)
RET_GATHER(ret, installdb_cleanup_component(node, *i));
}
return ret;
}
static int help(void) {
_cleanup_free_ char *link = NULL;
_cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL;
@@ -1844,6 +1918,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
"Select component to update"):
if (isempty(opts.arg)) {
arg_component = mfree(arg_component);
arg_component_all = false;
break;
}
@@ -1854,6 +1929,13 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
if (r < 0)
return r;
arg_component_all = false;
break;
OPTION('A', "component-all", NULL, "Process all components"):
arg_component = mfree(arg_component);
arg_component_all = true;
break;
OPTION_LONG("definitions", "DIR",