1
0
mirror of https://github.com/systemd/systemd synced 2026-03-13 00:24:48 +01:00

Compare commits

..

No commits in common. "8ae4aa26c181cf20c9e613074d2e47ddf58dd82d" and "e4dcf0cbce7f3fc76e14ae946df8ae5c08a6c401" have entirely different histories.

16 changed files with 129 additions and 639 deletions

6
TODO
View File

@ -128,12 +128,6 @@ Features:
- metrics from pid1: suppress metrics form units that are inactive and have nothing to report - metrics from pid1: suppress metrics form units that are inactive and have nothing to report
- how to plug facts into this? i.e. hostname, ssh keys, and so on - how to plug facts into this? i.e. hostname, ssh keys, and so on
- switch to daan's suggested hierarchy? - switch to daan's suggested hierarchy?
- enforce naming rules: a backend can only report metrics with the prefix of its service name
- use that to optimize sorted report generation
- figure out report vs. metrics
- systemd-report: add prefix matching of metrics
- add "hint-suppress-zero" flag (which suppresses all metrics which are zero)
- add "hint-object" parameter (which only queries info about certain object)
* implement a varlink registry service, similar to the one of the reference * implement a varlink registry service, similar to the one of the reference
implementation, backed by /run/varlink/registry/. Then, also implement implementation, backed by /run/varlink/registry/. Then, also implement

View File

@ -292,7 +292,7 @@ node /org/freedesktop/resolve1 {
(no IDNA conversion is applied), followed by the 16-bit class and type fields (which may be (no IDNA conversion is applied), followed by the 16-bit class and type fields (which may be
ANY). Finally, a <varname>flags</varname> field may be passed in to alter behaviour of the look-up (see ANY). Finally, a <varname>flags</varname> field may be passed in to alter behaviour of the look-up (see
below). On completion, an array of RR items is returned. Each array entry consists of the network interface below). On completion, an array of RR items is returned. Each array entry consists of the network interface
index the RR was discovered on, the class and type field of the RR found, and a byte array of the raw index the RR was discovered on, the type and class field of the RR found, and a byte array of the raw
RR discovered. The raw RR data starts with the RR's domain name, in the original casing, followed RR discovered. The raw RR data starts with the RR's domain name, in the original casing, followed
by the RR type, class, TTL and RDATA, in the binary format documented in by the RR type, class, TTL and RDATA, in the binary format documented in
<ulink url="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035</ulink>. For RRs that support name <ulink url="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035</ulink>. For RRs that support name

View File

@ -128,33 +128,19 @@
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><option>update</option> <optional>--offline</optional> <optional><replaceable>VERSION</replaceable></optional></term> <term><option>update</option> <optional><replaceable>VERSION</replaceable></optional></term>
<listitem><para>Installs (updates to) the specified version, or if none is specified to the newest <listitem><para>Installs (updates to) the specified version, or if none is specified to the newest
version available. If the version is already installed or no newer version available, no operation is version available. If the version is already installed or no newer version available, no operation is
executed.</para> executed.</para>
<para>If <option>--offline</option> is specified, the update must already have been acquired using
<command>acquire</command> and, if so, this pre-acquired version is the one which will be updated
to.</para>
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
</varlistentry>
<varlistentry>
<term><option>acquire</option> <optional><replaceable>VERSION</replaceable></optional></term>
<listitem><para>Acquires (downloads) the specified version, ready to install it. If no version is
specified, the newest version available is acquired. If the version is already installed or no newer
version is available, no operation is executed.</para>
<para>If a new version to install/update to is found, old installed versions are deleted until at <para>If a new version to install/update to is found, old installed versions are deleted until at
least one new version can be installed, as configured via <varname>InstanceMax=</varname> in least one new version can be installed, as configured via <varname>InstanceMax=</varname> in
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>, or <citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>, or
via the available partition slots of the right type. This implicit operation can also be invoked via the available partition slots of the right type. This implicit operation can also be invoked
explicitly via the <command>vacuum</command> command described below.</para> explicitly via the <command>vacuum</command> command described below.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem> <xi:include href="version-info.xml" xpointer="v251"/></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -313,7 +299,7 @@
<varlistentry> <varlistentry>
<term><option>--reboot</option></term> <term><option>--reboot</option></term>
<listitem><para>When used in combination with the <command>update</command> commands and a new version is <listitem><para>When used in combination with the <command>update</command> command and a new version is
installed, automatically reboots the system immediately afterwards.</para> installed, automatically reboots the system immediately afterwards.</para>
<xi:include href="version-info.xml" xpointer="v251"/></listitem> <xi:include href="version-info.xml" xpointer="v251"/></listitem>
@ -326,9 +312,6 @@
This is most useful when used in combination with the <command>list</command> command, to query This is most useful when used in combination with the <command>list</command> command, to query
locally installed versions.</para> locally installed versions.</para>
<para>If used in combination with the <command>update</command> command, it allows updates to be
downloaded in advance (using <command>acquire</command>) and installed later.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem> <xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry> </varlistentry>

View File

@ -2035,10 +2035,6 @@ int vl_method_install(
if (p.context.root_fd < 0) if (p.context.root_fd < 0)
return log_debug_errno(p.context.root_fd, "Failed to acquire root fd from Varlink: %m"); return log_debug_errno(p.context.root_fd, "Failed to acquire root fd from Varlink: %m");
r = fd_verify_safe_flags_full(p.context.root_fd, O_DIRECTORY);
if (r < 0)
return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor");
r = fd_verify_directory(p.context.root_fd); r = fd_verify_directory(p.context.root_fd);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m"); return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m");
@ -2051,11 +2047,15 @@ int vl_method_install(
if (empty_or_root(p.context.root)) if (empty_or_root(p.context.root))
p.context.root = mfree(p.context.root); p.context.root = mfree(p.context.root);
} }
} else if (p.context.root) { }
if (p.context.root_fd < 0 && p.context.root) {
p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY); p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
if (p.context.root_fd < 0) if (p.context.root_fd < 0)
return log_debug_errno(errno, "Failed to open '%s': %m", p.context.root); return log_debug_errno(errno, "Failed to open '%s': %m", p.context.root);
} else }
if (p.context.root_fd < 0)
p.context.root_fd = XAT_FDROOT; p.context.root_fd = XAT_FDROOT;
if (p.context.entry_token_type < 0) if (p.context.entry_token_type < 0)

View File

@ -422,7 +422,6 @@ typedef struct Partition {
bool dropped; bool dropped;
bool factory_reset; bool factory_reset;
bool discarded;
int32_t priority; int32_t priority;
uint32_t weight, padding_weight; uint32_t weight, padding_weight;
@ -2749,7 +2748,7 @@ static bool partition_needs_populate(const Partition *p) {
static MakeFileSystemFlags partition_mkfs_flags(const Partition *p) { static MakeFileSystemFlags partition_mkfs_flags(const Partition *p) {
MakeFileSystemFlags flags = 0; MakeFileSystemFlags flags = 0;
if (arg_discard && !p->discarded) if (arg_discard)
flags |= MKFS_DISCARD; flags |= MKFS_DISCARD;
if (streq(p->format, "erofs") && !DEBUG_LOGGING && !isatty_safe(STDERR_FILENO)) if (streq(p->format, "erofs") && !DEBUG_LOGGING && !isatty_safe(STDERR_FILENO))
@ -4669,7 +4668,6 @@ static int context_discard_partition(Context *context, Partition *p) {
return log_error_errno(r, "Failed to discard data for future partition %" PRIu64 ".", p->partno); return log_error_errno(r, "Failed to discard data for future partition %" PRIu64 ".", p->partno);
log_info("Successfully discarded data from future partition %" PRIu64 ".", p->partno); log_info("Successfully discarded data from future partition %" PRIu64 ".", p->partno);
p->discarded = true;
return 1; return 1;
} }

View File

@ -128,7 +128,7 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to root file system to operate on"), SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to root file system to operate on"),
SD_VARLINK_DEFINE_INPUT(rootFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(rootFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Root directory to operate relative to. If both this and rootFileDescriptor is specified, this is purely informational. If only this is specified, it is what will be used."), SD_VARLINK_FIELD_COMMENT("Root directory to operate relative to. If both this and rootFileDescriptor is specified, this is purely informational. If only this is specified, it is what will be used."),
SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Selects how to identify boot entries"), SD_VARLINK_FIELD_COMMENT("Selects how to identify boot entries"),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(bootEntryTokenType, BootEntryTokenType, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT_BY_TYPE(bootEntryTokenType, BootEntryTokenType, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("If true the boot loader will be registered in an EFI boot entry via EFI variables, otherwise this is omitted"), SD_VARLINK_FIELD_COMMENT("If true the boot loader will be registered in an EFI boot entry via EFI variables, otherwise this is omitted"),

View File

@ -45,11 +45,8 @@ struct Instance {
InstanceMetadata metadata; InstanceMetadata metadata;
/* Where we found the instance */ /* Where we found the instance */
char *path; /* includes the `.sysupdate.partial.` (etc.) prefix, if applicable */ char *path;
PartitionInfo partition_info; PartitionInfo partition_info;
bool is_partial;
bool is_pending;
}; };
void instance_metadata_destroy(InstanceMetadata *m); void instance_metadata_destroy(InstanceMetadata *m);

View File

@ -16,33 +16,6 @@ void partition_info_destroy(PartitionInfo *p) {
p->device = mfree(p->device); p->device = mfree(p->device);
} }
int partition_info_copy(PartitionInfo *dest, const PartitionInfo *src) {
int r;
assert(dest);
assert(src);
r = free_and_strdup_warn(&dest->label, src->label);
if (r < 0)
return r;
r = free_and_strdup_warn(&dest->device, src->device);
if (r < 0)
return r;
dest->partno = src->partno;
dest->start = src->start;
dest->size = src->size;
dest->flags = src->flags;
dest->type = src->type;
dest->uuid = src->uuid;
dest->no_auto = src->no_auto;
dest->read_only = src->read_only;
dest->growfs = src->growfs;
return 0;
}
int read_partition_info( int read_partition_info(
struct fdisk_context *c, struct fdisk_context *c,
struct fdisk_table *t, struct fdisk_table *t,

View File

@ -36,7 +36,6 @@ typedef struct PartitionInfo {
} }
void partition_info_destroy(PartitionInfo *p); void partition_info_destroy(PartitionInfo *p);
int partition_info_copy(PartitionInfo *dest, const PartitionInfo *src);
int read_partition_info(struct fdisk_context *c, struct fdisk_table *t, size_t i, PartitionInfo *ret); int read_partition_info(struct fdisk_context *c, struct fdisk_table *t, size_t i, PartitionInfo *ret);

View File

@ -80,22 +80,15 @@ static int resource_load_from_directory_recursive(
Resource *rr, Resource *rr,
DIR* d, DIR* d,
const char* relpath, const char* relpath,
const char* relpath_for_matching, mode_t m) {
mode_t m,
bool ancestor_is_partial,
bool ancestor_is_pending) {
int r; int r;
for (;;) { for (;;) {
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL; _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
_cleanup_free_ char *joined = NULL, *rel_joined = NULL; _cleanup_free_ char *joined = NULL, *rel_joined = NULL;
_cleanup_free_ char *rel_joined_for_matching = NULL;
Instance *instance; Instance *instance;
struct dirent *de; struct dirent *de;
const char *de_d_name_stripped;
struct stat st; struct stat st;
bool is_partial = ancestor_is_partial, is_pending = ancestor_is_pending;
const char *stripped;
errno = 0; errno = 0;
de = readdir_no_dot(d); de = readdir_no_dot(d);
@ -135,26 +128,11 @@ static int resource_load_from_directory_recursive(
if (!(S_ISDIR(st.st_mode) && S_ISREG(m)) && ((st.st_mode & S_IFMT) != m)) if (!(S_ISDIR(st.st_mode) && S_ISREG(m)) && ((st.st_mode & S_IFMT) != m))
continue; continue;
if ((stripped = startswith(de->d_name, ".sysupdate.partial."))) {
de_d_name_stripped = stripped;
is_partial = true;
} else if ((stripped = startswith(de->d_name, ".sysupdate.pending."))) {
de_d_name_stripped = stripped;
is_pending = true;
} else
de_d_name_stripped = de->d_name;
rel_joined = path_join(relpath, de->d_name); rel_joined = path_join(relpath, de->d_name);
if (!rel_joined) if (!rel_joined)
return log_oom(); return log_oom();
/* Match against the filename with any `.sysupdate.partial.` (etc.) prefix stripped, so the r = pattern_match_many(rr->patterns, rel_joined, &extracted_fields);
* users patterns still apply. But dont use the stripped version in any paths or recursion. */
rel_joined_for_matching = path_join(relpath_for_matching, de_d_name_stripped);
if (!rel_joined_for_matching)
return log_oom();
r = pattern_match_many(rr->patterns, rel_joined_for_matching, &extracted_fields);
if (r == PATTERN_MATCH_RETRY) { if (r == PATTERN_MATCH_RETRY) {
_cleanup_closedir_ DIR *subdir = NULL; _cleanup_closedir_ DIR *subdir = NULL;
@ -162,7 +140,7 @@ static int resource_load_from_directory_recursive(
if (!subdir) if (!subdir)
continue; continue;
r = resource_load_from_directory_recursive(rr, subdir, rel_joined, rel_joined_for_matching, m, is_partial, is_pending); r = resource_load_from_directory_recursive(rr, subdir, rel_joined, m);
if (r < 0) if (r < 0)
return r; return r;
if (r == 0) if (r == 0)
@ -190,9 +168,6 @@ static int resource_load_from_directory_recursive(
if (instance->metadata.mode == MODE_INVALID) if (instance->metadata.mode == MODE_INVALID)
instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */ instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */
instance->is_partial = is_partial;
instance->is_pending = is_pending;
} }
return 0; return 0;
@ -217,7 +192,7 @@ static int resource_load_from_directory(
return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path); return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path);
} }
return resource_load_from_directory_recursive(rr, d, NULL, NULL, m, false, false); return resource_load_from_directory_recursive(rr, d, NULL, m);
} }
static int resource_load_from_blockdev(Resource *rr) { static int resource_load_from_blockdev(Resource *rr) {
@ -244,9 +219,6 @@ static int resource_load_from_blockdev(Resource *rr) {
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL; _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
_cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL; _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
Instance *instance; Instance *instance;
const char *pinfo_label_stripped;
bool is_partial = false, is_pending = false;
const char *stripped;
r = read_partition_info(c, t, i, &pinfo); r = read_partition_info(c, t, i, &pinfo);
if (r < 0) if (r < 0)
@ -264,18 +236,7 @@ static int resource_load_from_blockdev(Resource *rr) {
continue; continue;
} }
/* Match the label with any partial/pending prefix removed so the users existing patterns r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields);
* match regardless of the instances state. */
if ((stripped = startswith(pinfo.label, "PRT#"))) {
pinfo_label_stripped = stripped;
is_partial = true;
} else if ((stripped = startswith(pinfo.label, "PND#"))) {
pinfo_label_stripped = stripped;
is_pending = true;
} else
pinfo_label_stripped = pinfo.label;
r = pattern_match_many(rr->patterns, pinfo_label_stripped, &extracted_fields);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to match pattern: %m"); return log_error_errno(r, "Failed to match pattern: %m");
if (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY)) if (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY))
@ -301,9 +262,6 @@ static int resource_load_from_blockdev(Resource *rr) {
if (instance->metadata.read_only < 0) if (instance->metadata.read_only < 0)
instance->metadata.read_only = instance->partition_info.read_only; instance->metadata.read_only = instance->partition_info.read_only;
instance->is_partial = is_partial;
instance->is_pending = is_pending;
} }
return 0; return 0;
@ -581,11 +539,6 @@ static int resource_load_from_web(
memcpy(instance->metadata.sha256sum, h.iov_base, h.iov_len); memcpy(instance->metadata.sha256sum, h.iov_base, h.iov_len);
instance->metadata.sha256sum_set = true; instance->metadata.sha256sum_set = true;
} }
/* Web resources can only be a source, not a target, so
* can never be partial or pending. */
instance->is_partial = false;
instance->is_pending = false;
} }
} }

View File

@ -41,6 +41,7 @@
#include "sysupdate-resource.h" #include "sysupdate-resource.h"
#include "sysupdate-transfer.h" #include "sysupdate-transfer.h"
#include "time-util.h" #include "time-util.h"
#include "tmpfile-util.h"
#include "web-util.h" #include "web-util.h"
/* Default value for InstancesMax= for fs object targets */ /* Default value for InstancesMax= for fs object targets */
@ -50,8 +51,7 @@ Transfer* transfer_free(Transfer *t) {
if (!t) if (!t)
return NULL; return NULL;
free(t->temporary_partial_path); t->temporary_path = rm_rf_subvolume_and_free(t->temporary_path);
free(t->temporary_pending_path);
free(t->id); free(t->id);
@ -67,9 +67,6 @@ Transfer* transfer_free(Transfer *t) {
strv_free(t->appstream); strv_free(t->appstream);
partition_info_destroy(&t->partition_info); partition_info_destroy(&t->partition_info);
free(t->temporary_partial_partition_label);
free(t->temporary_pending_partition_label);
free(t->final_partition_label);
resource_destroy(&t->source); resource_destroy(&t->source);
resource_destroy(&t->target); resource_destroy(&t->target);
@ -743,49 +740,6 @@ static void transfer_remove_temporary(Transfer *t) {
} }
} }
static int transfer_instance_vacuum(
Transfer *t,
Instance *instance) {
int r;
assert(t);
assert(instance);
switch (t->target.type) {
case RESOURCE_REGULAR_FILE:
case RESOURCE_DIRECTORY:
case RESOURCE_SUBVOLUME:
r = rm_rf(instance->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", instance->path);
(void) rmdir_parents(instance->path, t->target.path);
break;
case RESOURCE_PARTITION: {
PartitionInfo pinfo = instance->partition_info;
/* label "_empty" means "no contents" for our purposes */
pinfo.label = (char*) "_empty";
log_debug("Relabelling partition '%s' to '%s'.", pinfo.device, pinfo.label);
r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
if (r < 0)
return r;
t->target.n_empty++;
break;
}
default:
assert_not_reached();
}
return 0;
}
int transfer_vacuum( int transfer_vacuum(
Transfer *t, Transfer *t,
uint64_t space, uint64_t space,
@ -798,45 +752,7 @@ int transfer_vacuum(
transfer_remove_temporary(t); transfer_remove_temporary(t);
/* First, remove any partial or pending instances (unless protected) */ /* First, calculate how many instances to keep, based on the instance limit — but keep at least one */
for (size_t i = 0; i < t->target.n_instances;) {
Instance *instance = t->target.instances[i];
assert(instance);
if (!instance->is_pending && !instance->is_partial) {
i++;
continue;
}
/* If this is listed among the protected versions, then let's not remove it */
if (strv_contains(t->protected_versions, instance->metadata.version) ||
(extra_protected_version && streq(extra_protected_version, instance->metadata.version))) {
log_debug("Version '%s' is pending/partial but protected, not removing.", instance->metadata.version);
i++;
continue;
}
assert(instance->resource);
log_info("%s Removing old %s '%s' (%s).",
glyph(GLYPH_RECYCLING),
instance->is_partial ? "partial" : "pending",
instance->path,
resource_type_to_string(instance->resource->type));
r = transfer_instance_vacuum(t, instance);
if (r < 0)
return 0;
instance_free(instance);
memmove(t->target.instances + i, t->target.instances + i + 1, (t->target.n_instances - i - 1) * sizeof(Instance*));
t->target.n_instances--;
count++;
}
/* Second, calculate how many instances to keep, based on the instance limit — but keep at least one */
instances_max = arg_instances_max != UINT64_MAX ? arg_instances_max : t->instances_max; instances_max = arg_instances_max != UINT64_MAX ? arg_instances_max : t->instances_max;
assert(instances_max >= 1); assert(instances_max >= 1);
@ -924,9 +840,36 @@ int transfer_vacuum(
oldest->path, oldest->path,
resource_type_to_string(oldest->resource->type)); resource_type_to_string(oldest->resource->type));
r = transfer_instance_vacuum(t, oldest); switch (t->target.type) {
if (r < 0)
return 0; case RESOURCE_REGULAR_FILE:
case RESOURCE_DIRECTORY:
case RESOURCE_SUBVOLUME:
r = rm_rf(oldest->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", oldest->path);
(void) rmdir_parents(oldest->path, t->target.path);
break;
case RESOURCE_PARTITION: {
PartitionInfo pinfo = oldest->partition_info;
/* label "_empty" means "no contents" for our purposes */
pinfo.label = (char*) "_empty";
r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
if (r < 0)
return r;
t->target.n_empty++;
break;
}
default:
assert_not_reached();
}
instance_free(oldest); instance_free(oldest);
memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*)); memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
@ -1168,100 +1111,8 @@ static int run_callout(
return sd_event_loop(event); return sd_event_loop(event);
} }
/* Build the filenames and paths which is normally done by transfer_acquire_instance(), but for partial
* and pending instances which are about to be installed (in which case, transfer_acquire_instance() is
* skipped). */
static int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) {
_cleanup_free_ char *formatted_pattern = NULL, *formatted_partial_pattern = NULL, *formatted_pending_pattern = NULL;
int r;
assert(t);
assert(i);
assert(!t->final_path);
assert(!t->temporary_partial_path);
assert(!t->temporary_pending_path);
assert(!t->final_partition_label);
assert(!t->temporary_partial_partition_label);
assert(!t->temporary_pending_partition_label);
assert(!strv_isempty(t->target.patterns));
/* Format the target name using the first pattern specified */
compile_pattern_fields(t, i, f);
r = pattern_format(t->target.patterns[0], f, &formatted_pattern);
if (r < 0)
return log_error_errno(r, "Failed to format target pattern: %m");
if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
_cleanup_free_ char *final_dir = NULL, *final_filename = NULL, *partial_filename = NULL, *pending_filename = NULL;
if (!path_is_safe(formatted_pattern))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
t->final_path = path_join(t->target.path, formatted_pattern);
if (!t->final_path)
return log_oom();
/* Build the paths for the partial and pending files, which hold the resource while its
* being acquired and after its been acquired (but before its moved to the final_path
* when its installed).
*
* Split the filename off the `final_path`, then add a prefix to it for each of partial and
* pending, then join them back on to the same directory. */
r = path_split_prefix_filename(t->final_path, &final_dir, &final_filename);
if (r < 0)
return log_error_errno(r, "Failed to parse path: %m");
if (!strprepend(&partial_filename, ".sysupdate.partial.", final_filename))
return log_oom();
if (!strprepend(&pending_filename, ".sysupdate.pending.", final_filename))
return log_oom();
t->temporary_partial_path = path_join(final_dir, partial_filename);
if (!t->temporary_partial_path)
return log_oom();
t->temporary_pending_path = path_join(final_dir, pending_filename);
if (!t->temporary_pending_path)
return log_oom();
}
if (t->target.type == RESOURCE_PARTITION) {
r = gpt_partition_label_valid(formatted_pattern);
if (r < 0)
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
if (!r)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
if (!strprepend(&formatted_partial_pattern, "PRT#", formatted_pattern))
return log_oom();
r = gpt_partition_label_valid(formatted_partial_pattern);
if (r < 0)
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_partial_pattern);
if (!r)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_partial_pattern);
free_and_replace(t->temporary_partial_partition_label, formatted_partial_pattern);
if (!strprepend(&formatted_pending_pattern, "PND#", formatted_pattern))
return log_oom();
r = gpt_partition_label_valid(formatted_pending_pattern);
if (r < 0)
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pending_pattern);
if (!r)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pending_pattern);
free_and_replace(t->temporary_pending_partition_label, formatted_pending_pattern);
t->final_partition_label = TAKE_PTR(formatted_pattern);
}
return 0;
}
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata) { int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata) {
_cleanup_free_ char *digest = NULL; _cleanup_free_ char *formatted_pattern = NULL, *digest = NULL;
char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1]; char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
const char *where = NULL; const char *where = NULL;
InstanceMetadata f; InstanceMetadata f;
@ -1275,35 +1126,48 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
/* Does this instance already exist in the target? Then we don't need to acquire anything */ /* Does this instance already exist in the target? Then we don't need to acquire anything */
existing = resource_find_instance(&t->target, i->metadata.version); existing = resource_find_instance(&t->target, i->metadata.version);
if (existing && (existing->is_partial || existing->is_pending))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to acquire '%s', instance is already partial or pending in the target.", i->path);
if (existing) { if (existing) {
log_info("No need to acquire '%s', already installed.", i->path); log_info("No need to acquire '%s', already installed.", i->path);
return 0; return 0;
} }
/* Compute up the temporary paths */ assert(!t->final_path);
r = transfer_compute_temporary_paths(t, i, &f); assert(!t->temporary_path);
assert(!strv_isempty(t->target.patterns));
/* Format the target name using the first pattern specified */
compile_pattern_fields(t, i, &f);
r = pattern_format(t->target.patterns[0], &f, &formatted_pattern);
if (r < 0) if (r < 0)
return r; return log_error_errno(r, "Failed to format target pattern: %m");
if (RESOURCE_IS_FILESYSTEM(t->target.type)) { if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
r = mkdir_parents(t->temporary_partial_path, 0755);
if (r < 0)
return log_error_errno(r, "Cannot create target directory: %m");
r = mkdir_parents(t->temporary_pending_path, 0755); if (!path_is_safe(formatted_pattern))
if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
return log_error_errno(r, "Cannot create target directory: %m");
t->final_path = path_join(t->target.path, formatted_pattern);
if (!t->final_path)
return log_oom();
r = mkdir_parents(t->final_path, 0755); r = mkdir_parents(t->final_path, 0755);
if (r < 0) if (r < 0)
return log_error_errno(r, "Cannot create target directory: %m"); return log_error_errno(r, "Cannot create target directory: %m");
r = tempfn_random(t->final_path, "sysupdate", &t->temporary_path);
if (r < 0)
return log_error_errno(r, "Failed to generate temporary target path: %m");
where = t->final_path; where = t->final_path;
} }
if (t->target.type == RESOURCE_PARTITION) { if (t->target.type == RESOURCE_PARTITION) {
r = gpt_partition_label_valid(formatted_pattern);
if (r < 0)
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
if (!r)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
r = find_suitable_partition( r = find_suitable_partition(
t->target.path, t->target.path,
i->metadata.size, i->metadata.size,
@ -1316,20 +1180,6 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
xsprintf(max_size, "%" PRIu64, t->partition_info.size); xsprintf(max_size, "%" PRIu64, t->partition_info.size);
where = t->partition_info.device; where = t->partition_info.device;
/* Rename the partition to `PRT#<VERSION>` to indicate that a transfer to it is in progress. */
r = free_and_strdup_warn(&t->partition_info.label, t->temporary_partial_partition_label);
if (r < 0)
return r;
t->partition_change = PARTITION_LABEL;
log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
r = patch_partition(
t->target.path,
&t->partition_info,
t->partition_change);
if (r < 0)
return r;
} }
assert(where); assert(where);
@ -1368,7 +1218,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
"--direct", /* just copy/unpack the specified file, don't do anything else */ "--direct", /* just copy/unpack the specified file, don't do anything else */
arg_sync ? "--sync=yes" : "--sync=no", arg_sync ? "--sync=yes" : "--sync=no",
i->path, i->path,
t->temporary_partial_path), t->temporary_path),
t, i, cb, userdata); t, i, cb, userdata);
break; break;
@ -1409,7 +1259,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
arg_sync ? "--sync=yes" : "--sync=no", arg_sync ? "--sync=yes" : "--sync=no",
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
i->path, i->path,
t->temporary_partial_path), t->temporary_path),
t, i, cb, userdata); t, i, cb, userdata);
break; break;
@ -1426,7 +1276,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
arg_sync ? "--sync=yes" : "--sync=no", arg_sync ? "--sync=yes" : "--sync=no",
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
i->path, i->path,
t->temporary_partial_path), t->temporary_path),
t, i, cb, userdata); t, i, cb, userdata);
break; break;
@ -1446,7 +1296,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
"--verify", digest, /* validate by explicit SHA256 sum */ "--verify", digest, /* validate by explicit SHA256 sum */
arg_sync ? "--sync=yes" : "--sync=no", arg_sync ? "--sync=yes" : "--sync=no",
i->path, i->path,
t->temporary_partial_path), t->temporary_path),
t, i, cb, userdata); t, i, cb, userdata);
break; break;
@ -1486,7 +1336,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
arg_sync ? "--sync=yes" : "--sync=no", arg_sync ? "--sync=yes" : "--sync=no",
i->path, i->path,
t->temporary_partial_path), t->temporary_path),
t, i, cb, userdata); t, i, cb, userdata);
break; break;
@ -1498,8 +1348,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
if (RESOURCE_IS_FILESYSTEM(t->target.type)) { if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
bool need_sync = false; bool need_sync = false;
assert(t->temporary_partial_path); assert(t->temporary_path);
assert(t->temporary_pending_path);
/* Apply file attributes if set */ /* Apply file attributes if set */
if (f.mtime != USEC_INFINITY) { if (f.mtime != USEC_INFINITY) {
@ -1507,8 +1356,8 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
timespec_store(&ts, f.mtime); timespec_store(&ts, f.mtime);
if (utimensat(AT_FDCWD, t->temporary_partial_path, (struct timespec[2]) { ts, ts }, AT_SYMLINK_NOFOLLOW) < 0) if (utimensat(AT_FDCWD, t->temporary_path, (struct timespec[2]) { ts, ts }, AT_SYMLINK_NOFOLLOW) < 0)
return log_error_errno(errno, "Failed to adjust mtime of '%s': %m", t->temporary_partial_path); return log_error_errno(errno, "Failed to adjust mtime of '%s': %m", t->temporary_path);
need_sync = true; need_sync = true;
} }
@ -1517,9 +1366,9 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
/* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older /* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older
* kernels don't support that however, in that case we fall back to chmod(). Not as * kernels don't support that however, in that case we fall back to chmod(). Not as
* safe, but shouldn't be a problem, given that we don't create symlinks here. */ * safe, but shouldn't be a problem, given that we don't create symlinks here. */
if (fchmodat(AT_FDCWD, t->temporary_partial_path, f.mode, AT_SYMLINK_NOFOLLOW) < 0 && if (fchmodat(AT_FDCWD, t->temporary_path, f.mode, AT_SYMLINK_NOFOLLOW) < 0 &&
(!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_partial_path, f.mode) < 0)) (!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_path, f.mode) < 0))
return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_partial_path); return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_path);
need_sync = true; need_sync = true;
} }
@ -1527,34 +1376,20 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
/* Synchronize */ /* Synchronize */
if (arg_sync && need_sync) { if (arg_sync && need_sync) {
if (t->target.type == RESOURCE_REGULAR_FILE) if (t->target.type == RESOURCE_REGULAR_FILE)
r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_partial_path); r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_path);
else { else {
assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME)); assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
r = syncfs_path(AT_FDCWD, t->temporary_partial_path); r = syncfs_path(AT_FDCWD, t->temporary_path);
} }
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_partial_path); return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_path);
} }
t->install_read_only = f.read_only; t->install_read_only = f.read_only;
/* Rename the file from `.sysupdate.partial.<VERSION>` to `.sysupdate.pending.<VERSION>` to indicate its ready to install. */
log_debug("Renaming resource instance '%s' to '%s'.", t->temporary_partial_path, t->temporary_pending_path);
r = install_file(AT_FDCWD, t->temporary_partial_path,
AT_FDCWD, t->temporary_pending_path,
INSTALL_REPLACE|
(t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
(t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
if (r < 0)
return log_error_errno(r, "Failed to move '%s' into pending place: %m", t->temporary_pending_path);
} }
if (t->target.type == RESOURCE_PARTITION) { if (t->target.type == RESOURCE_PARTITION) {
/* Now rename the partition again to `PND#<VERSION>` to indicate that the acquire is complete free_and_replace(t->partition_info.label, formatted_pattern);
* and the partition is ready for install. */
r = free_and_strdup_warn(&t->partition_info.label, t->temporary_pending_partition_label);
if (r < 0)
return r;
t->partition_change = PARTITION_LABEL; t->partition_change = PARTITION_LABEL;
if (f.partition_uuid_set) { if (f.partition_uuid_set) {
@ -1581,14 +1416,6 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
t->partition_info.growfs = f.growfs; t->partition_info.growfs = f.growfs;
t->partition_change |= PARTITION_GROWFS; t->partition_change |= PARTITION_GROWFS;
} }
log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
r = patch_partition(
t->target.path,
&t->partition_info,
t->partition_change);
if (r < 0)
return r;
} }
/* For regular file cases the only step left is to install the file in place, which install_file() /* For regular file cases the only step left is to install the file in place, which install_file()
@ -1599,42 +1426,6 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
return 0; return 0;
} }
int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) {
InstanceMetadata f;
Instance *existing;
int r;
assert(t);
assert(i);
log_debug("transfer_process_partial_and_pending_instance %s", i->path);
/* Does this instance already exist in the target but isnt pending? */
existing = resource_find_instance(&t->target, i->metadata.version);
if (existing && !existing->is_pending)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to acquire '%s', instance is already in the target but is not pending.", i->path);
/* All we need to do is compute the temporary paths. We dont need to do any of the other work in
* transfer_acquire_instance(). */
r = transfer_compute_temporary_paths(t, i, &f);
if (r < 0)
return r;
/* This is the analogue of find_suitable_partition(), but since finding the suitable partition has
* already happened in the acquire phase, the target should already have that information and it
* should already have been claimed as `PND#`. */
if (t->target.type == RESOURCE_PARTITION) {
assert(i->resource == &t->target);
assert(i->is_pending);
r = partition_info_copy(&t->partition_info, &i->partition_info);
if (r < 0)
return r;
}
return 0;
}
int transfer_install_instance( int transfer_install_instance(
Transfer *t, Transfer *t,
Instance *i, Instance *i,
@ -1645,15 +1436,13 @@ int transfer_install_instance(
assert(t); assert(t);
assert(i); assert(i);
assert(i->resource); assert(i->resource);
assert(i->is_pending || t == container_of(i->resource, Transfer, source)); assert(t == container_of(i->resource, Transfer, source));
log_debug("transfer_install_instance %s %s %s %d", i->path, t->temporary_pending_path, t->final_partition_label, t->partition_change); if (t->temporary_path) {
if (t->temporary_pending_path) {
assert(RESOURCE_IS_FILESYSTEM(t->target.type)); assert(RESOURCE_IS_FILESYSTEM(t->target.type));
assert(t->final_path); assert(t->final_path);
r = install_file(AT_FDCWD, t->temporary_pending_path, r = install_file(AT_FDCWD, t->temporary_path,
AT_FDCWD, t->final_path, AT_FDCWD, t->final_path,
INSTALL_REPLACE| INSTALL_REPLACE|
(t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)| (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
@ -1667,17 +1456,11 @@ int transfer_install_instance(
t->final_path, t->final_path,
resource_type_to_string(t->target.type)); resource_type_to_string(t->target.type));
t->temporary_pending_path = mfree(t->temporary_pending_path); t->temporary_path = mfree(t->temporary_path);
} }
if (t->temporary_pending_partition_label) { if (t->partition_change != 0) {
assert(t->target.type == RESOURCE_PARTITION); assert(t->target.type == RESOURCE_PARTITION);
assert(t->final_partition_label);
r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
if (r < 0)
return r;
t->partition_change = PARTITION_LABEL;
r = patch_partition( r = patch_partition(
t->target.path, t->target.path,

View File

@ -39,17 +39,13 @@ typedef struct Transfer {
int growfs; int growfs;
/* If we create a new file/dir/subvol in the fs, the temporary and final path we create it under, as well as the read-only flag for it */ /* If we create a new file/dir/subvol in the fs, the temporary and final path we create it under, as well as the read-only flag for it */
char *temporary_partial_path; char *temporary_path;
char *temporary_pending_path;
char *final_path; char *final_path;
int install_read_only; int install_read_only;
/* If we write to a partition in a partition table, the metrics of it */ /* If we write to a partition in a partition table, the metrics of it */
PartitionInfo partition_info; PartitionInfo partition_info;
PartitionChange partition_change; PartitionChange partition_change;
char *final_partition_label;
char *temporary_partial_partition_label;
char *temporary_pending_partition_label;
Context *context; Context *context;
} Transfer; } Transfer;
@ -67,6 +63,5 @@ int transfer_resolve_paths(Transfer *t, const char *root, const char *node);
int transfer_vacuum(Transfer *t, uint64_t space, const char *extra_protected_version); int transfer_vacuum(Transfer *t, uint64_t space, const char *extra_protected_version);
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata); int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata);
int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i);
int transfer_install_instance(Transfer *t, Instance *i, const char *root); int transfer_install_instance(Transfer *t, Instance *i, const char *root);

View File

@ -9,9 +9,6 @@ const char* update_set_flags_to_color(UpdateSetFlags flags) {
if (flags == 0 || (flags & UPDATE_OBSOLETE)) if (flags == 0 || (flags & UPDATE_OBSOLETE))
return (flags & UPDATE_NEWEST) ? ansi_highlight_grey() : ansi_grey(); return (flags & UPDATE_NEWEST) ? ansi_highlight_grey() : ansi_grey();
if (flags & (UPDATE_PARTIAL|UPDATE_PENDING))
return ansi_highlight_cyan();
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_INCOMPLETE)) if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_INCOMPLETE))
return ansi_highlight_yellow(); return ansi_highlight_yellow();
@ -32,9 +29,6 @@ const char* update_set_flags_to_glyph(UpdateSetFlags flags) {
if (flags == 0 || (flags & UPDATE_OBSOLETE)) if (flags == 0 || (flags & UPDATE_OBSOLETE))
return glyph(GLYPH_MULTIPLICATION_SIGN); return glyph(GLYPH_MULTIPLICATION_SIGN);
if (flags & (UPDATE_PARTIAL|UPDATE_PENDING))
return glyph(GLYPH_DOWNLOAD);
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST)) if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
return glyph(GLYPH_BLACK_CIRCLE); return glyph(GLYPH_BLACK_CIRCLE);
@ -60,18 +54,6 @@ const char* update_set_flags_to_string(UpdateSetFlags flags) {
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED: case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
return "current"; return "current";
case UPDATE_INSTALLED|UPDATE_PENDING|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_PENDING|UPDATE_NEWEST|UPDATE_PROTECTED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PENDING|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PENDING|UPDATE_NEWEST|UPDATE_PROTECTED:
return "current+pending";
case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_NEWEST|UPDATE_PROTECTED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_NEWEST|UPDATE_PROTECTED:
return "current+partial";
case UPDATE_AVAILABLE|UPDATE_NEWEST: case UPDATE_AVAILABLE|UPDATE_NEWEST:
case UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED: case UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
return "candidate"; return "candidate";

View File

@ -10,8 +10,6 @@ typedef enum UpdateSetFlags {
UPDATE_OBSOLETE = 1 << 3, UPDATE_OBSOLETE = 1 << 3,
UPDATE_PROTECTED = 1 << 4, UPDATE_PROTECTED = 1 << 4,
UPDATE_INCOMPLETE = 1 << 5, UPDATE_INCOMPLETE = 1 << 5,
UPDATE_PARTIAL = 1 << 6,
UPDATE_PENDING = 1 << 7,
} UpdateSetFlags; } UpdateSetFlags;
const char* update_set_flags_to_color(UpdateSetFlags flags); const char* update_set_flags_to_color(UpdateSetFlags flags);

View File

@ -397,7 +397,7 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
assert(flags == UPDATE_INSTALLED); assert(flags == UPDATE_INSTALLED);
match = resource_find_instance(&t->target, cursor); match = resource_find_instance(&t->target, cursor);
if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING))) { if (!match) {
/* When we're looking for installed versions, let's be robust and treat /* When we're looking for installed versions, let's be robust and treat
* an incomplete installation as an installation. Otherwise, there are * an incomplete installation as an installation. Otherwise, there are
* situations that can lead to sysupdate wiping the currently booted OS. * situations that can lead to sysupdate wiping the currently booted OS.
@ -413,14 +413,6 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
if (strv_contains(t->protected_versions, cursor)) if (strv_contains(t->protected_versions, cursor))
extra_flags |= UPDATE_PROTECTED; extra_flags |= UPDATE_PROTECTED;
/* Partial or pending updates by definition are not incomplete, theyre
* partial/pending instead */
if (match && match->is_partial)
extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
if (match && match->is_pending)
extra_flags = (extra_flags | UPDATE_PENDING) & ~UPDATE_INCOMPLETE;
} }
r = free_and_strdup_warn(&boundary, cursor); r = free_and_strdup_warn(&boundary, cursor);
@ -439,9 +431,7 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
/* Merge in what we've learned and continue onto the next version */ /* Merge in what we've learned and continue onto the next version */
if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE) || if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE)) {
FLAGS_SET(u->flags, UPDATE_PARTIAL) ||
FLAGS_SET(u->flags, UPDATE_PENDING)) {
assert(u->n_instances == c->n_transfers); assert(u->n_instances == c->n_transfers);
/* Incomplete updates will have picked NULL instances for the transfers that /* Incomplete updates will have picked NULL instances for the transfers that
@ -460,7 +450,7 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
/* If this is the newest installed version, that is incomplete and just became marked /* If this is the newest installed version, that is incomplete and just became marked
* as available, and if there is no other candidate available, we promote this to be * as available, and if there is no other candidate available, we promote this to be
* the candidate. Ignore partial or pending status on the update set. */ * the candidate. */
if (FLAGS_SET(u->flags, UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_AVAILABLE) && if (FLAGS_SET(u->flags, UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_AVAILABLE) &&
!c->candidate && !FLAGS_SET(u->flags, UPDATE_OBSOLETE)) !c->candidate && !FLAGS_SET(u->flags, UPDATE_OBSOLETE))
c->candidate = u; c->candidate = u;
@ -496,8 +486,7 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED)) if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
c->newest_installed = us; c->newest_installed = us;
/* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate". /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate" */
* It may be partial or pending. */
if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE)) if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
c->candidate = us; c->candidate = us;
} }
@ -507,11 +496,6 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0) c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
c->candidate = NULL; c->candidate = NULL;
/* Newest installed is still pending and no candidate is set? Then it becomes the candidate. */
if (c->newest_installed && FLAGS_SET(c->newest_installed->flags, UPDATE_PENDING) &&
!c->candidate)
c->candidate = c->newest_installed;
return 0; return 0;
} }
@ -611,14 +595,13 @@ static int context_show_version(Context *c, const char *version) {
if (!sd_json_format_enabled(arg_json_format_flags)) if (!sd_json_format_enabled(arg_json_format_flags))
printf("%s%s%s Version: %s\n" printf("%s%s%s Version: %s\n"
" State: %s%s%s\n" " State: %s%s%s\n"
"Installed: %s%s%s%s\n" "Installed: %s%s\n"
"Available: %s%s\n" "Available: %s%s\n"
"Protected: %s%s%s\n" "Protected: %s%s%s\n"
" Obsolete: %s%s%s\n\n", " Obsolete: %s%s%s\n\n",
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version, strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(), strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "", yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "", yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal()); us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
@ -811,7 +794,7 @@ static int context_show_version(Context *c, const char *version) {
if (!sd_json_format_enabled(arg_json_format_flags)) { if (!sd_json_format_enabled(arg_json_format_flags)) {
printf("%s%s%s Version: %s\n" printf("%s%s%s Version: %s\n"
" State: %s%s%s\n" " State: %s%s%s\n"
"Installed: %s%s%s%s%s%s%s\n" "Installed: %s%s%s%s%s\n"
"Available: %s%s\n" "Available: %s%s\n"
"Protected: %s%s%s\n" "Protected: %s%s%s\n"
" Obsolete: %s%s%s\n", " Obsolete: %s%s%s\n",
@ -819,7 +802,6 @@ static int context_show_version(Context *c, const char *version) {
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(), strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "", yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(), FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(),
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "", yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal()); us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
@ -845,8 +827,6 @@ static int context_show_version(Context *c, const char *version) {
SD_JSON_BUILD_PAIR_BOOLEAN("newest", FLAGS_SET(us->flags, UPDATE_NEWEST)), SD_JSON_BUILD_PAIR_BOOLEAN("newest", FLAGS_SET(us->flags, UPDATE_NEWEST)),
SD_JSON_BUILD_PAIR_BOOLEAN("available", FLAGS_SET(us->flags, UPDATE_AVAILABLE)), SD_JSON_BUILD_PAIR_BOOLEAN("available", FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)), SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
SD_JSON_BUILD_PAIR_BOOLEAN("partial", FLAGS_SET(us->flags, UPDATE_PARTIAL)),
SD_JSON_BUILD_PAIR_BOOLEAN("pending", FLAGS_SET(us->flags, UPDATE_PENDING)),
SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)), SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)),
SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)), SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)),
SD_JSON_BUILD_PAIR_BOOLEAN("incomplete", FLAGS_SET(us->flags, UPDATE_INCOMPLETE)), SD_JSON_BUILD_PAIR_BOOLEAN("incomplete", FLAGS_SET(us->flags, UPDATE_INCOMPLETE)),
@ -1002,11 +982,10 @@ static int context_on_acquire_progress(const Transfer *t, const Instance *inst,
overall, n - i, i, inst->metadata.version, overall); overall, n - i, i, inst->metadata.version, overall);
} }
static int context_process_partial_and_pending(Context *c, const char *version); static int context_apply(
static int context_acquire(
Context *c, Context *c,
const char *version) { const char *version,
UpdateSet **ret_applied) {
UpdateSet *us = NULL; UpdateSet *us = NULL;
int r; int r;
@ -1021,6 +1000,9 @@ static int context_acquire(
if (!c->candidate) { if (!c->candidate) {
log_info("No update needed."); log_info("No update needed.");
if (ret_applied)
*ret_applied = NULL;
return 0; return 0;
} }
@ -1029,17 +1011,12 @@ static int context_acquire(
if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE)) if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version); log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) { else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
log_info("Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version);
return 0;
} else if (FLAGS_SET(us->flags, UPDATE_PENDING)) {
log_info("Selected update '%s' is already acquired and pending installation.", us->version);
return context_process_partial_and_pending(c, version);
} else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
log_info("Selected update '%s' is already installed. Skipping update.", us->version); log_info("Selected update '%s' is already installed. Skipping update.", us->version);
if (ret_applied)
*ret_applied = NULL;
return 0; return 0;
} }
@ -1098,106 +1075,14 @@ static int context_acquire(
if (arg_sync) if (arg_sync)
sync(); sync();
return 1; (void) sd_notifyf(/* unset_environment= */ false,
}
/* Check to see if we have an update set acquired and pending installation. */
static int context_process_partial_and_pending(
Context *c,
const char *version) {
UpdateSet *us = NULL;
int r;
assert(c);
if (version) {
us = context_update_set_by_version(c, version);
if (!us)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
} else {
if (!c->candidate) {
log_info("No update needed.");
return 0;
}
us = c->candidate;
}
if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
else if ((us->flags & (UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_INSTALLED)) == UPDATE_INSTALLED) {
log_info("Selected update '%s' is already installed. Skipping update.", us->version);
return 0;
}
if (FLAGS_SET(us->flags, UPDATE_PARTIAL))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is only partially downloaded, refusing.", us->version);
if (!FLAGS_SET(us->flags, UPDATE_PENDING))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not pending installation, refusing.", us->version);
if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
log_info("Selected update '%s' for install.", us->version);
/* There should now be one instance picked for each transfer, and the order is the same */
assert(us->n_instances == c->n_transfers);
for (size_t i = 0; i < c->n_transfers; i++) {
Instance *inst = us->instances[i];
Transfer *t = c->transfers[i];
assert(inst);
r = transfer_process_partial_and_pending_instance(t, inst);
if (r < 0)
return r;
}
return 1;
}
static int context_install(
Context *c,
const char *version,
UpdateSet **ret_applied) {
UpdateSet *us = NULL;
int r;
assert(c);
if (version) {
us = context_update_set_by_version(c, version);
if (!us)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
} else {
if (!c->candidate) {
log_info("No update needed.");
return 0;
}
us = c->candidate;
}
(void) sd_notifyf(/* unset_environment=*/ false,
"STATUS=Installing '%s'.", us->version); "STATUS=Installing '%s'.", us->version);
for (size_t i = 0; i < c->n_transfers; i++) { for (size_t i = 0; i < c->n_transfers; i++) {
Instance *inst = us->instances[i]; Instance *inst = us->instances[i];
Transfer *t = c->transfers[i]; Transfer *t = c->transfers[i];
if (inst->resource == &t->target && if (inst->resource == &t->target)
!inst->is_pending)
continue; continue;
r = transfer_install_instance(t, inst, arg_root); r = transfer_install_instance(t, inst, arg_root);
@ -1288,16 +1173,13 @@ static int verb_list(int argc, char **argv, void *userdata) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
_cleanup_strv_free_ char **versions = NULL; _cleanup_strv_free_ char **versions = NULL;
const char *current = NULL; const char *current = NULL;
bool current_is_pending = false;
FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) { FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) {
UpdateSet *us = *update_set; UpdateSet *us = *update_set;
if (FLAGS_SET(us->flags, UPDATE_INSTALLED) && if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
FLAGS_SET(us->flags, UPDATE_NEWEST)) { FLAGS_SET(us->flags, UPDATE_NEWEST))
current = us->version; current = us->version;
current_is_pending = FLAGS_SET(us->flags, UPDATE_PENDING);
}
r = strv_extend(&versions, us->version); r = strv_extend(&versions, us->version);
if (r < 0) if (r < 0)
@ -1315,7 +1197,7 @@ static int verb_list(int argc, char **argv, void *userdata) {
return log_oom(); return log_oom();
} }
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING(current_is_pending ? "current+pending" : "current", current), r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("current", current),
SD_JSON_BUILD_PAIR_STRV("all", versions), SD_JSON_BUILD_PAIR_STRV("all", versions),
SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls)); SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls));
if (r < 0) if (r < 0)
@ -1527,12 +1409,7 @@ static int verb_vacuum(int argc, char **argv, void *userdata) {
return context_vacuum(context, 0, NULL); return context_vacuum(context, 0, NULL);
} }
typedef enum { static int verb_update(int argc, char **argv, void *userdata) {
UPDATE_ACTION_ACQUIRE = 1 << 0,
UPDATE_ACTION_INSTALL = 1 << 1,
} UpdateActionFlags;
static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
_cleanup_(context_freep) Context* context = NULL; _cleanup_(context_freep) Context* context = NULL;
@ -1566,15 +1443,7 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag
if (r < 0) if (r < 0)
return r; return r;
if (action_flags & UPDATE_ACTION_ACQUIRE) r = context_apply(context, version, &applied);
r = context_acquire(context, version);
else
r = context_process_partial_and_pending(context, version);
if (r < 0)
return r; /* error */
if (action_flags & UPDATE_ACTION_INSTALL && r > 0) /* update needed */
r = context_install(context, version, &applied);
if (r < 0) if (r < 0)
return r; return r;
@ -1599,19 +1468,6 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag
return 0; return 0;
} }
static int verb_update(int argc, char **argv, void *userdata) {
UpdateActionFlags flags = UPDATE_ACTION_INSTALL;
if (!arg_offline)
flags |= UPDATE_ACTION_ACQUIRE;
return verb_update_impl(argc, argv, flags);
}
static int verb_acquire(int argc, char **argv, void *userdata) {
return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE);
}
static int verb_pending_or_reboot(int argc, char **argv, void *userdata) { static int verb_pending_or_reboot(int argc, char **argv, void *userdata) {
_cleanup_(context_freep) Context* context = NULL; _cleanup_(context_freep) Context* context = NULL;
_cleanup_free_ char *booted_version = NULL; _cleanup_free_ char *booted_version = NULL;
@ -1791,7 +1647,6 @@ static int verb_help(int argc, char **argv, void *userdata) {
" features [FEATURE] Show optional features\n" " features [FEATURE] Show optional features\n"
" check-new Check if there's a new version available\n" " check-new Check if there's a new version available\n"
" update [VERSION] Install new version now\n" " update [VERSION] Install new version now\n"
" acquire [VERSION] Acquire (download) new version now\n"
" vacuum Make room, by deleting old versions\n" " vacuum Make room, by deleting old versions\n"
" pending Report whether a newer version is installed than\n" " pending Report whether a newer version is installed than\n"
" currently booted\n" " currently booted\n"
@ -2005,7 +1860,6 @@ static int sysupdate_main(int argc, char *argv[]) {
{ "features", VERB_ANY, 2, 0, verb_features }, { "features", VERB_ANY, 2, 0, verb_features },
{ "check-new", VERB_ANY, 1, 0, verb_check_new }, { "check-new", VERB_ANY, 1, 0, verb_check_new },
{ "update", VERB_ANY, 2, 0, verb_update }, { "update", VERB_ANY, 2, 0, verb_update },
{ "acquire", VERB_ANY, 2, 0, verb_acquire },
{ "vacuum", VERB_ANY, 1, 0, verb_vacuum }, { "vacuum", VERB_ANY, 1, 0, verb_vacuum },
{ "reboot", 1, 1, 0, verb_pending_or_reboot }, { "reboot", 1, 1, 0, verb_pending_or_reboot },
{ "pending", 1, 1, 0, verb_pending_or_reboot }, { "pending", 1, 1, 0, verb_pending_or_reboot },

View File

@ -92,27 +92,11 @@ new_version() {
} }
update_now() { update_now() {
local update_type="${1:?}"
# Update to newest version. First there should be an update ready, then we # Update to newest version. First there should be an update ready, then we
# do the update, and then there should not be any ready anymore # do the update, and then there should not be any ready anymore
#
# The update can either be done monolithically (by calling the `update`
# verb) or split (`acquire` then `update`). Both options are allowed for
# most updates in the test suite, so the test suite can be run to test both
# modes. Some updates in the test suite need to be monolithic (e.g. when
# repairing an installation), so that can be overridden via the local.
"$SYSUPDATE" --verify=no check-new "$SYSUPDATE" --verify=no check-new
if [[ "$update_type" == "monolithic" ]]; then "$SYSUPDATE" --verify=no update
"$SYSUPDATE" --verify=no update
elif [[ "$update_type" == "split-offline" ]]; then
"$SYSUPDATE" --verify=no acquire
"$SYSUPDATE" --verify=no update --offline
elif [[ "$update_type" == "split" ]]; then
"$SYSUPDATE" --verify=no acquire
"$SYSUPDATE" --verify=no update
fi
(! "$SYSUPDATE" --verify=no check-new) (! "$SYSUPDATE" --verify=no check-new)
} }
@ -151,7 +135,6 @@ verify_version_current() {
} }
for sector_size in "${SECTOR_SIZES[@]}"; do for sector_size in "${SECTOR_SIZES[@]}"; do
for update_type in monolithic split-offline split; do
# Disk size of: # Disk size of:
# - 1MB for GPT # - 1MB for GPT
# - 4 partitions of 2048 sectors each # - 4 partitions of 2048 sectors each
@ -284,18 +267,18 @@ EOF
# Install initial version and verify # Install initial version and verify
new_version "$sector_size" v1 new_version "$sector_size" v1
update_now "$update_type" update_now
verify_version_current "$blockdev" "$sector_size" v1 1 verify_version_current "$blockdev" "$sector_size" v1 1
# Create second version, update and verify that it is added # Create second version, update and verify that it is added
new_version "$sector_size" v2 new_version "$sector_size" v2
update_now "$update_type" update_now
verify_version "$blockdev" "$sector_size" v1 1 verify_version "$blockdev" "$sector_size" v1 1
verify_version_current "$blockdev" "$sector_size" v2 2 verify_version_current "$blockdev" "$sector_size" v2 2
# Create third version, update and verify it replaced the first version # Create third version, update and verify it replaced the first version
new_version "$sector_size" v3 new_version "$sector_size" v3
update_now "$update_type" update_now
verify_version_current "$blockdev" "$sector_size" v3 1 verify_version_current "$blockdev" "$sector_size" v3 1
verify_version "$blockdev" "$sector_size" v2 2 verify_version "$blockdev" "$sector_size" v2 2
test ! -f "$WORKDIR/xbootldr/EFI/Linux/uki_v1+3-0.efi" test ! -f "$WORKDIR/xbootldr/EFI/Linux/uki_v1+3-0.efi"
@ -312,17 +295,16 @@ EOF
# Create a fifth version, that's complete on the server side. We should # Create a fifth version, that's complete on the server side. We should
# completely skip the incomplete v4 and install v5 instead. # completely skip the incomplete v4 and install v5 instead.
new_version "$sector_size" v5 new_version "$sector_size" v5
update_now "$update_type" update_now
verify_version "$blockdev" "$sector_size" v3 1 verify_version "$blockdev" "$sector_size" v3 1
verify_version_current "$blockdev" "$sector_size" v5 2 verify_version_current "$blockdev" "$sector_size" v5 2
# Make the local installation of v5 incomplete by deleting a file, then make # Make the local installation of v5 incomplete by deleting a file, then make
# sure that sysupdate still recognizes the installation and can complete it # sure that sysupdate still recognizes the installation and can complete it
# in place # in place
# Always do this as a monolithic update for the repair to work.
rm -r "$WORKDIR/xbootldr/EFI/Linux/uki_v5.efi.extra.d" rm -r "$WORKDIR/xbootldr/EFI/Linux/uki_v5.efi.extra.d"
"$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null "$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null
update_now "monolithic" update_now
"$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null "$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null
verify_version "$blockdev" "$sector_size" v3 1 verify_version "$blockdev" "$sector_size" v3 1
verify_version_current "$blockdev" "$sector_size" v5 2 verify_version_current "$blockdev" "$sector_size" v5 2
@ -334,7 +316,7 @@ EOF
mkdir "$CONFIGDIR/optional.feature.d" mkdir "$CONFIGDIR/optional.feature.d"
echo -e "[Feature]\nEnabled=true" > "$CONFIGDIR/optional.feature.d/enable.conf" echo -e "[Feature]\nEnabled=true" > "$CONFIGDIR/optional.feature.d/enable.conf"
"$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null "$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null
update_now "monolithic" update_now
"$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null "$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null
verify_version "$blockdev" "$sector_size" v3 1 verify_version "$blockdev" "$sector_size" v3 1
verify_version_current "$blockdev" "$sector_size" v5 2 verify_version_current "$blockdev" "$sector_size" v5 2
@ -358,7 +340,7 @@ EOF
updatectl update updatectl update
else else
# If no updatectl, gracefully fall back to systemd-sysupdate # If no updatectl, gracefully fall back to systemd-sysupdate
update_now "$update_type" update_now
fi fi
# User-facing updatectl returns 0 if there's no updates, so use the low-level # User-facing updatectl returns 0 if there's no updates, so use the low-level
# utility to make sure we did upgrade # utility to make sure we did upgrade
@ -415,7 +397,7 @@ MatchPattern=dir-@v
InstancesMax=3 InstancesMax=3
EOF EOF
update_now "$update_type" update_now
verify_version "$blockdev" "$sector_size" v6 1 verify_version "$blockdev" "$sector_size" v6 1
verify_version_current "$blockdev" "$sector_size" v7 2 verify_version_current "$blockdev" "$sector_size" v7 2
@ -438,7 +420,7 @@ EOF
# (what .transfer files were called before v257) # (what .transfer files were called before v257)
for i in "$CONFIGDIR/"*.conf; do echo mv "$i" "${i%.conf}.transfer"; done for i in "$CONFIGDIR/"*.conf; do echo mv "$i" "${i%.conf}.transfer"; done
new_version "$sector_size" v8 new_version "$sector_size" v8
update_now "$update_type" update_now
verify_version_current "$blockdev" "$sector_size" v8 1 verify_version_current "$blockdev" "$sector_size" v8 1
verify_version "$blockdev" "$sector_size" v7 2 verify_version "$blockdev" "$sector_size" v7 2
@ -446,6 +428,5 @@ EOF
[[ -b "$blockdev" ]] && losetup --detach "$blockdev" [[ -b "$blockdev" ]] && losetup --detach "$blockdev"
rm "$BACKING_FILE" rm "$BACKING_FILE"
done done
done
touch /testok touch /testok