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
+38
View File
@@ -204,6 +204,31 @@
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
</varlistentry>
<varlistentry>
<term><option>cleanup</option></term>
<listitem><para>Removes orphaned files that were previously installed by a transfer, but are no
longer owned by any currently defined transfer file. Whenever a resource is installed into the file
system, <command>systemd-sysupdate</command> records the target directory and the matching pattern in
an installation database below <filename>/var/lib/systemd/sysupdate/</filename>. This command
iterates through these records, determines which files they match, and deletes those that are no
longer covered by any of the patterns of the transfer files currently in place. This is useful to
garbage-collect files that used to be owned by a transfer file that has since been modified, disabled
or removed altogether (for example because a component is no longer being updated).</para>
<para>By default only the selected component is processed (i.e. the one selected via
<option>--component=</option>, or the default one if none were selected). Use
<option>--component-all</option> to process all components known to the installation database in a
single invocation.</para>
<para>This operation only removes files that were installed into the file system (i.e. resources of
type <literal>regular-file</literal>, <literal>directory</literal> and <literal>subvolume</literal>,
see <citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>);
it does not touch partition-based resources.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
@@ -246,6 +271,19 @@
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--component-all</option></term>
<term><option>-A</option></term>
<listitem><para>Instead of operating on a single component, operate on all known components (as well as
the default, component-less installation). This is currently only supported for the
<command>cleanup</command> command; all other commands will fail if this switch is used.</para>
<para>This option may not be combined with <option>--component=</option>.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--definitions=</option></term>
+1
View File
@@ -2,6 +2,7 @@
systemd_sysupdate_sources = files(
'sysupdate-cache.c',
'sysupdate-cleanup.c',
'sysupdate-feature.c',
'sysupdate-instance.c',
'sysupdate-partition.c',
+450
View File
@@ -0,0 +1,450 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <unistd.h>
#include "alloc-util.h"
#include "chase.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "log.h"
#include "path-util.h"
#include "recurse-dir.h"
#include "rm-rf.h"
#include "sha256.h"
#include "string-util.h"
#include "strv.h"
#include "sysupdate.h"
#include "sysupdate-cleanup.h"
#include "sysupdate-pattern.h"
#include "sysupdate-resource.h"
#include "sysupdate-transfer.h"
#include "sysupdate-util.h"
/* This implements the "installdb", which is a simple database of directories + patterns that we ever
* installed something into, i.e. for any resource we ever considered "owned" by a transfer file. This is
* useful to automatically clean up "orphaned" files that used to be owned by a transfer file, but might no
* longer be in newer versions of those transfer files, or where the transfer files/components got
* removed/disabled altogether.
*
* This is ultimately just a per-component content-addressable database implemented via a special directory
* in /var/lib/systemd/sysupdate/ that carries symlinks to store the data. Whenever we drop a file into the
* system we create an entry in it. An entry symlink's filename is a SHA256 hash of the symlink's target. The
* target encodes the directory of the transfer file used, suffixed by the pattern used by the transfer
* file. If a transfer file lists multiple patterns, multiple entries are generated, one for each pattern.
*
* The on-disk layout hence looks roughly like this (for a component "foo" with a transfer file that has
* Path=/var/lib/machines/ and two patterns "image_@v.raw" and "image_@v.efi"):
*
* /var/lib/systemd/sysupdate/
* └── installdb.foo/
* ├── 8cbee6aa38b98811598118ebbc0eb4c1b7e479e7bfa4312c0b36edc765d1733b → /var/lib/machines/./image_@v.raw
* └── 042266dec8deae09c1e75f3d015734513b75a1daa38b4173c907b5345cf4ed41 → /var/lib/machines/./image_@v.efi
*
* (For the component-less case the directory is just called "installdb", without the ".foo" suffix.) The
* symlink names are the SHA256 hashes (in hex) of their respective targets, and the "/./" separates the
* directory part from the pattern part of the target.
*
* With this in place we have an always updated database of any file and pattern ever owned by any transfer
* file we operated on. When doing a clean-up run, we now iterate through all installdb directories (i.e
* every component ever installed), and all entries in them. We look for all files the entries match. We then
* check if the current set of transfer files also owns these files. If yes, we keep both those files and the
* installdb entry. If however no current transfer files own these files anymore, we first delete the files,
* and then the installdb entry, since it no longer matches any files. */
static int context_installdb_acquire_fd(Context *c, bool make) {
assert(c);
if (c->installdb_fd >= 0)
return 0;
_cleanup_free_ char *j = NULL;
const char *p;
if (c->component) {
j = strjoin("/var/lib/systemd/sysupdate/installdb.", c->component);
if (!j)
return log_oom();
p = j;
} else
p = "/var/lib/systemd/sysupdate/installdb";
ChaseFlags flags = CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT;
if (make)
flags |= CHASE_MKDIR_0755;
c->installdb_fd = chase_and_open(
p,
arg_root,
flags,
O_DIRECTORY|O_CLOEXEC|(make ? O_CREAT : 0),
/* ret_path= */ NULL);
if (c->installdb_fd == -ENOENT && !make)
return 0;
if (c->installdb_fd < 0)
return log_error_errno(c->installdb_fd, "Failed to open install database '%s%s': %m", empty_or_root(arg_root) ? "" : arg_root, p);
return 1;
}
static int installdb_make_names(const char *path, const char *pattern, char **ret_key, char **ret_value) {
assert(path);
assert(pattern);
/* We'll generate a string from the location and the pattern that looks a lot like a path, but
* actually isn't, it's a path concatenated with a pattern. We separate both parts with /./. */
_cleanup_free_ char *s = strjoin(path, "/./", pattern);
if (!s)
return log_oom();
_cleanup_free_ char *h = sha256_direct_hex(s, SIZE_MAX);
if (!h)
return log_oom();
if (ret_key)
*ret_key = TAKE_PTR(h);
if (ret_value)
*ret_value = TAKE_PTR(s);
return 0;
}
int context_installdb_record(
Context *c,
const char *path,
char **patterns) {
int r;
assert(c);
assert(path);
/* Creates installdb entries for the specified pairs of directory and pattern. This is called
* whenever we install a new file. */
if (strv_isempty(patterns))
return 0;
/* The provided path comes with arg_root prefixed. Strip it here again */
const char *p = arg_root ? ASSERT_PTR(path_startswith(path, arg_root)) : path;
r = context_installdb_acquire_fd(c, /* make= */ true);
if (r < 0)
return r;
int ret = 0;
STRV_FOREACH(i, patterns) {
_cleanup_free_ char *key = NULL, *value = NULL;
r = installdb_make_names(p, *i, &key, &value);
if (r < 0)
return r;
r = symlinkat_idempotent(value, c->installdb_fd, key, /* make_relative= */ false);
if (r < 0)
RET_GATHER(ret, log_warning_errno(r, "Failed to add '%s' in '%s' entry to install database: %m", *i, path));
}
return ret;
}
static int context_is_path_currently_owned(
Context *c,
const char *path,
const char *relpath) {
int r;
assert(c);
assert(path);
assert(relpath);
/* Checks if the there's a transfer file for the directoy 'path', and then if any of its patterns
* match 'relpath' */
FOREACH_ARRAY(_t, c->transfers, c->n_transfers) {
Transfer *t = *_t;
if (!RESOURCE_IS_FILESYSTEM(t->target.type))
continue;
if (!path_equal(t->target.path, path))
continue;
/* OK, so we found a transfer that covers this directory. Now let's see if any of its patterns match */
r = pattern_match_many(t->target.patterns, relpath, /* ret= */ NULL);
if (r < 0) {
_cleanup_free_ char *cl = strv_join(t->target.patterns, "', '");
if (!cl)
return log_oom();
return log_error_errno(r, "Failed to match patterns '%s' against '%s': %m", cl, relpath);
}
if (IN_SET(r, PATTERN_MATCH_YES, PATTERN_MATCH_RETRY)) /* Yay, this path is pinned by this transfer file */
return true;
assert(r == PATTERN_MATCH_NO);
}
return false; /* We found nothing! The path seems to be unowned. */
}
static int context_installdb_process_directory(
Context *c,
const char *path, /* The configured Path= in the original transfer file */
const char *relpath, /* For recursive path matches the path we encountered so far */
int dir_fd,
DirectoryEntries *de,
const char *pattern) {
int r;
assert(c);
assert(path);
assert(dir_fd >= 0);
assert(de);
assert(pattern);
int ret = 0;
bool keep_installdb = false;
FOREACH_ARRAY(_d, de->entries, de->n_entries) {
const struct dirent *d = *_d;
assert(IN_SET(d->d_type, DT_REG, DT_DIR)); /* caller must have filtered via readdir_all() RECURSE_DIR_MUST_BE_xyz flags already */
_cleanup_free_ char *j = NULL;
const char *p;
if (relpath) {
j = path_join(relpath, d->d_name);
if (!j)
return log_oom();
p = j;
} else
p = d->d_name;
/* Let's see if this entry matches the pattern we recorded in the installdb? */
r = pattern_match(pattern, p, /* ret= */ NULL);
if (r < 0) {
log_warning_errno(r, "Failed to match pattern '%s' against '%s', ignoring: %m", pattern, p);
/* Can't match, do not clean up */
continue;
}
if (r == PATTERN_MATCH_NO) /* No match, do not clean up */
continue;
if (r == PATTERN_MATCH_RETRY) {
/* Might match in a subdirectory */
if (d->d_type != DT_DIR)
continue;
_cleanup_close_ int subdir_fd = RET_NERRNO(openat(dir_fd, d->d_name, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW));
if (subdir_fd == -ENOENT)
continue;
if (subdir_fd < 0) {
RET_GATHER(ret, log_warning_errno(subdir_fd, "Failed to open directory '%s', skipping: %m", p));
continue;
}
_cleanup_free_ DirectoryEntries *subde = NULL;
r = readdir_all(subdir_fd, RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_MUST_BE_DIRECTORY|RECURSE_DIR_MUST_BE_REGULAR, &subde);
if (r < 0) {
RET_GATHER(ret, log_error_errno(r, "Failed to enumerate resource path '%s': %m", p));
continue;
}
r = context_installdb_process_directory(c, path, p, subdir_fd, subde, pattern);
if (r < 0)
RET_GATHER(ret, r);
else
keep_installdb = keep_installdb || r;
continue;
}
assert(r == PATTERN_MATCH_YES);
/* Ah, we have a match, this is a candidate for cleanup. Let's see if any of the currently defined transfer files want to own it */
r = context_is_path_currently_owned(c, path, p);
if (r < 0)
return r;
if (r > 0) {
log_debug("Path '%s' is owned by current transfer files, keeping.", p);
keep_installdb = true; /* We are keeping the file, let's also keep the installdb entry for it hence */
continue;
}
/* OK, we found an orphaned inode that was owned by a previous invocation, but is no longer
* owned by any of the current transfer files. Delete it. */
r = rm_rf_child(dir_fd, d->d_name, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD);
if (r < 0) {
if (r != -ENOENT)
RET_GATHER(ret, log_warning_errno(r, "Failed to remove '%s' which is no longer owned by any transfer files: %m", p));
continue;
}
log_info("Successfully removed '%s' which is no longer owned by any transfer files.", p);
}
/* Report back if there's a reason to keep the installdb entry for this directory */
return ret < 0 ? ret : keep_installdb;
}
static int context_installdb_process_entry(
Context *c,
const char *key,
const char *value) {
int r;
assert(c);
assert(key);
assert(value);
_cleanup_free_ char *h = sha256_direct_hex(value, SIZE_MAX);
if (!h)
return log_oom();
if (!streq(key, h)) {
log_notice("Invalid hash of install database entry '%s' → '%s', expunging.", key, value);
return 0;
}
const char *s = strstr(value, "/./");
if (!s) {
log_notice("Malformed install database entry '%s' → '%s', expunging.", key, value);
return 0;
}
_cleanup_free_ char *path = strndup(value, s - value);
if (!path)
return log_oom();
if (!path_is_absolute(path) || !path_is_normalized(path)) {
log_notice("Install database path '%s' of entry '%s' → '%s' is invalid, expunging database entry.", path, key, value);
return 0;
}
const char *pattern = s + 3;
/* NB: We set CHASE_PROHIBIT_SYMLINKS because the path was normalized by the writer of the entry
* already, and if it isn't anymore, then something is fishy. */
_cleanup_close_ int dir_fd = chase_and_open(path, arg_root, CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, /* ret_path= */ NULL);
if (dir_fd == -ENOENT) {
log_debug("Install database path '%s' does not exist, expunging database entry.", path);
return 0;
}
if (dir_fd < 0)
return log_error_errno(dir_fd, "Failed to open resource path '%s': %m", path);
_cleanup_free_ DirectoryEntries *de = NULL;
r = readdir_all(dir_fd, RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_MUST_BE_DIRECTORY|RECURSE_DIR_MUST_BE_REGULAR, &de);
if (r < 0)
return log_error_errno(r, "Failed to enumerate resource path '%s': %m", path);
return context_installdb_process_directory(c, path, /* relpath= */ NULL, dir_fd, de, pattern);
}
int installdb_cleanup_component(const char *node, const char *component) {
int r;
_cleanup_(context_freep) Context* context = NULL;
r = context_make_offline(
&context,
node,
component,
/* read_definitions_flags= */ 0);
if (r < 0)
return r;
r = context_installdb_acquire_fd(context, /* make= */ false);
if (r < 0)
return r;
if (r == 0) {
log_debug("Not cleaning up component '%s', install database is empty.", strna(component));
return 0;
}
_cleanup_free_ DirectoryEntries *de = NULL;
r = readdir_all(context->installdb_fd, RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_MUST_BE_SYMLINK, &de);
if (r < 0)
return log_error_errno(r, "Failed to enumerate install database for component '%s': %m", strna(component));
int ret = 0;
FOREACH_ARRAY(_d, de->entries, de->n_entries) {
const struct dirent *d = *_d;
_cleanup_free_ char *v = NULL;
r = readlinkat_malloc(context->installdb_fd, d->d_name, &v);
if (r == -ENOENT)
continue;
if (r < 0) {
log_warning_errno(r, "Failed to read symlink '%s', ignoring: %m", d->d_name);
continue;
}
r = context_installdb_process_entry(context, d->d_name, v);
if (r < 0) {
RET_GATHER(ret, r);
continue;
}
if (r > 0) /* Still good, keep installdb entry */
continue;
r = RET_NERRNO(unlinkat(context->installdb_fd, d->d_name, /* flags= */ 0));
if (r < 0 && r != -ENOENT)
RET_GATHER(ret, log_warning_errno(r, "Failed to remove install database entry '%s': %m", d->d_name));
}
return ret;
}
int installdb_list_components(char ***ret) {
int r;
assert(ret);
_cleanup_close_ int dir_fd = chase_and_open(
"/var/lib/systemd/sysupdate",
arg_root,
CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT,
O_DIRECTORY|O_CLOEXEC,
/* ret_path= */ NULL);
if (dir_fd == -ENOENT) {
*ret = NULL;
return 0;
}
if (dir_fd < 0)
return log_error_errno(dir_fd, "Failed to open '/var/lib/systemd/sysupdate/': %m");
_cleanup_free_ DirectoryEntries *de = NULL;
r = readdir_all(dir_fd, RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_MUST_BE_DIRECTORY, &de);
if (r < 0)
return log_error_errno(r, "Failed to enumerate installdb directory '/var/lib/systemd/sysupdate/': %m");
_cleanup_strv_free_ char **l = NULL;
FOREACH_ARRAY(_d, de->entries, de->n_entries) {
const struct dirent *d = *_d;
const char *e = startswith(d->d_name, "installdb.");
if (!e)
continue;
if (!component_name_valid(e))
continue;
if (strv_extend(&l, e) < 0)
return log_oom();
}
strv_sort_uniq(l);
*ret = TAKE_PTR(l);
return 0;
}
+9
View File
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sysupdate-forward.h"
int context_installdb_record(Context *c, const char *path, char **patterns);
int installdb_cleanup_component(const char *node, const char *component);
int installdb_list_components(char ***ret);
+3
View File
@@ -35,6 +35,7 @@
#include "strv.h"
#include "sync-util.h"
#include "sysupdate.h"
#include "sysupdate-cleanup.h"
#include "sysupdate-feature.h"
#include "sysupdate-instance.h"
#include "sysupdate-pattern.h"
@@ -1672,6 +1673,8 @@ int transfer_install_instance(
resource_type_to_string(t->target.type));
t->temporary_pending_path = mfree(t->temporary_pending_path);
(void) context_installdb_record(t->context, t->target.path, t->target.patterns);
}
if (t->final_partition_label) {
+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",
+31
View File
@@ -4,6 +4,37 @@
#include "specifier.h"
#include "sysupdate-forward.h"
typedef struct Context {
char *component;
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 */
int installdb_fd;
} Context;
Context* context_free(Context *c);
DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free);
typedef enum ReadDefinitionsFlags {
READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS = 1 << 0,
READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS = 1 << 1,
} ReadDefinitionsFlags;
int context_make_offline(Context **ret, const char *node, const char *component, ReadDefinitionsFlags read_definitions_flags);
extern bool arg_sync;
extern uint64_t arg_instances_max;
extern char *arg_root;