Merge pull request #33520 from tomcoldrick-ct/coldtom/sysupdate-improvements

sysupdate: Add --offline mode, notifications, JSON output and additional metadata
This commit is contained in:
Lennart Poettering
2024-07-12 19:12:41 +02:00
committed by GitHub
14 changed files with 860 additions and 206 deletions
+118
View File
@@ -0,0 +1,118 @@
---
title: Appstream Bundle
category: Interfaces
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# Appstream Bundle
NOTE: This document is a work-in-progress.
NOTE: This isn't yet implemented in libappstream and the software centers.
[Appstream catalogs](https://www.freedesktop.org/software/appstream/docs/chap-CatalogData.html)
are a standardized way to expose metadata about system components, apps, and updates to software
centers (i.e. GNOME Software and KDE Discover). The `<bundle/>` tag links an appstream component
to a packaging format. This is used by the software centers to decide which code path (or plugin)
should handle the component. For instance: components with a `<bundle type="package">...</bundle>`
will be handled by [PackageKit](https://www.freedesktop.org/software/PackageKit/), and components
with a `<bundle type="flatpak">...</bundle>` will be handled by [libflatpak](https://docs.flatpak.org/).
This document will define how to format an appstream component's `<bundle>` tag such that software
centers will know to manage it using systemd. The following syntax will be supported:
A `type="systemd"` attribute. This tells the software center that it should treat the bundle tag
as described in this document.
A `class=""` attribute, with the following possible values: `sysupdate`, `extension`, `confext`,
or `portable`. These correspond to sysupdate components, sysexts, confexts, and portable services
respectively.
The value of the tag will be used as the name of the image (corresponding to the `class=` attribute).
So for instance, `<bundle type="systemd" class="extension">foobar</bundle>` corresponds to a sysext
named "foobar". For `class="sysupdate"`, there is a special case: if the value is empty, then the
bundle actually refers to the host system.
## Examples
```xml
<component type="addon">
<id>com.example.Devel</id>
<extends>com.example.OS</extends>
<name>Development Tools</name>
<summary>Tools essential to develop Example OS</summary>
<provides>
<binary>gcc</binary>
<binary>g++</binary>
<binary>make</binary>
<binary>autoconf</binary>
<binary>cmake</binary>
<binary>meson</binary>
<binary>ninja</binary>
</provides>
<developer_name>Example, inc.</developer_name>
<releases>
<release version="45" date="2024-01-15" />
<release version="44" date="2023-12-08" />
<release version="43" date="2023-11-10" />
</releases>
<bundle type="systemd" class="extension">devel</bundle>
</component>
```
defines a sysext named `devel` to be presented by the software center. It will be
updated via `systemd-sysupdated`'s `extension:devel` target. It will be treated
as a plugin for the operating system itself.
```xml
<component merge="append">
<id>com.example.OS</id>
<releases>
<release version="45" date="2024-01-15" urgency="high">
<description>
<p>This release includes various bug fixes and performance improvements</p>
</description>
</release>
</releases>
<bundle type="systemd" class="sysupdate" />
</component>
```
extends existing appstream metadata for the host OS with a changelog. It also tells the software
center that the host OS should be updated using the `host` target for `systemd-sysupdated`.
```xml
<component type="service">
<id>com.example.Foobar</id>
<name>Foobar Service</name>
<summary>Service that does foo to bar</summary>
<icon type="remote">https://example.com/products/foobar/logo.svg</icon>
<url type="homepage">https://example.com/products/foobar</url>
<provides>
<dbus type="system">com.example.Foobar</dbus>
</provides>
<developer_name>Example, inc.</developer_name>
<releases>
<release version="1.0.1" date="2024-02-16" urgency="critical">
<description>
<p>This release fixes a major security vulnerability. Please update ASAP.</p>
</description>
<issues>
<issue type="cve">CVE-2024-28153</issue>
</issues>
</release>
<release version="1.1-beta" date="2024-01-08" type="development" />
<release version="1.0" date="2023-11-23">
<description>
<p>Initial release!</p>
</description>
</release>
</releases>
<bundle type="systemd" class="portable">foobar</bundle>
</component>
```
defines a portable service named `foobar` to be presented by the software center. It will be
updated via `systemd-sysupdated`'s `portable:foobar` target. It will be marked as an
urgent update. It will be presented to the user with a display name, a description, and
a custom icon.
+10
View File
@@ -295,6 +295,16 @@
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--offline</option></term>
<listitem><para>Prevents fetching metadata from the network (i.e. <filename>SHA256SUMS</filename>).
This is most useful when used in combination with the <command>list</command> command, to query
locally installed versions.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="no-legend" />
<xi:include href="standard-options.xml" xpointer="json" />
+32
View File
@@ -487,6 +487,38 @@
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>ChangeLog=</varname></term>
<listitem><para>Optionally takes a human-presentable URL to a website containing a change-log of
the resource being updated.</para>
<para>This may be set multiple times in a single transfer definition. If set multiple times, the
values are gathered into a list of URLs. Adding a value of the empty string will clear the existing
list of all values.</para>
<para>This setting supports specifier expansion. See below for details on supported
specifiers. This setting will also expand the <literal>@v</literal> wildcard pattern. See above
for details.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>AppStream=</varname></term>
<listitem><para>Optionally takes a URL to an
<ulink url="https://www.freedesktop.org/software/appstream/docs/chap-CatalogData.html">AppStream catalog</ulink>
XML file. This may be used by software centers (such as GNOME Software or KDE Discover) to present
rich metadata about the resources being updated. This includes display names, changelogs, icons,
and more. The specified catalog must include <ulink url="https://systemd.io/APPSTREAM_BUNDLE">special metadata</ulink>
to be correctly associated with <command>systemd-sysupdate</command> by the software centers.</para>
<para>This setting supports specifier expansion. See below for details on supported
specifiers.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
+2
View File
@@ -8,6 +8,8 @@ systemd_sysupdate_sources = files(
'sysupdate-resource.c',
'sysupdate-transfer.c',
'sysupdate-update-set.c',
'sysupdate-update-set-flags.c',
'sysupdate-util.c',
'sysupdate.c',
)
+280 -14
View File
@@ -8,6 +8,7 @@
#include "chase.h"
#include "conf-parser.h"
#include "dirent-util.h"
#include "event-util.h"
#include "fd-util.h"
#include "glyph-util.h"
#include "gpt.h"
@@ -16,8 +17,12 @@
#include "mkdir.h"
#include "parse-helpers.h"
#include "parse-util.h"
#include "percent-util.h"
#include "process-util.h"
#include "random-util.h"
#include "rm-rf.h"
#include "signal-util.h"
#include "socket-util.h"
#include "specifier.h"
#include "stat-util.h"
#include "stdio-util.h"
@@ -45,6 +50,9 @@ Transfer *transfer_free(Transfer *t) {
free(t->current_symlink);
free(t->final_path);
strv_free(t->changelog);
strv_free(t->appstream);
partition_info_destroy(&t->partition_info);
resource_destroy(&t->source);
@@ -53,7 +61,7 @@ Transfer *transfer_free(Transfer *t) {
return mfree(t);
}
Transfer *transfer_new(void) {
Transfer *transfer_new(Context *ctx) {
Transfer *t;
t = new(Transfer, 1);
@@ -78,6 +86,8 @@ Transfer *transfer_new(void) {
.install_read_only = -1,
.partition_info = PARTITION_INFO_NULL,
.context = ctx,
};
return t;
@@ -161,6 +171,48 @@ static int config_parse_min_version(
return free_and_replace(*version, resolved);
}
static int config_parse_url_specifiers(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
char ***s = ASSERT_PTR(data);
_cleanup_free_ char *resolved = NULL;
int r;
assert(rvalue);
if (isempty(rvalue)) {
*s = strv_free(*s);
return 0;
}
r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue);
return 0;
}
if (!http_url_is_valid(resolved)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"%s= URL is not valid, ignoring: %s", lvalue, rvalue);
return 0;
}
r = strv_push(s, TAKE_PTR(resolved));
if (r < 0)
return log_oom();
return 0;
}
static int config_parse_current_symlink(
const char *unit,
const char *filename,
@@ -424,6 +476,8 @@ int transfer_read_definition(Transfer *t, const char *path) {
{ "Transfer", "MinVersion", config_parse_min_version, 0, &t->min_version },
{ "Transfer", "ProtectVersion", config_parse_protect_version, 0, &t->protected_versions },
{ "Transfer", "Verify", config_parse_bool, 0, &t->verify },
{ "Transfer", "ChangeLog", config_parse_url_specifiers, 0, &t->changelog },
{ "Transfer", "AppStream", config_parse_url_specifiers, 0, &t->appstream },
{ "Source", "Type", config_parse_resource_type, 0, &t->source.type },
{ "Source", "Path", config_parse_resource_path, 0, &t->source },
{ "Source", "PathRelativeTo", config_parse_resource_path_relto, 0, &t->source.path_relative_to },
@@ -784,30 +838,234 @@ static void compile_pattern_fields(
memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
}
typedef struct CalloutContext {
const Transfer *transfer;
const Instance *instance;
TransferProgress callback;
PidRef pid;
const char *name;
void* userdata;
} CalloutContext;
static CalloutContext *callout_context_free(CalloutContext *ctx) {
if (!ctx)
return NULL;
/* We don't own any data but need to clean up the job pid */
pidref_done(&ctx->pid);
return mfree(ctx);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(CalloutContext*, callout_context_free);
static int callout_context_new(const Transfer *t, const Instance *i, TransferProgress cb,
const char *name, void* userdata, CalloutContext **ret) {
_cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
assert(t);
assert(i);
assert(cb);
ctx = new(CalloutContext, 1);
if (!ctx)
return -ENOMEM;
*ctx = (CalloutContext) {
.transfer = t,
.instance = i,
.callback = cb,
.pid = PIDREF_NULL,
.name = name,
.userdata = userdata,
};
*ret = TAKE_PTR(ctx);
return 0;
}
static int helper_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
_cleanup_(callout_context_freep) CalloutContext *ctx = ASSERT_PTR(userdata);
int code;
assert(s);
assert(si);
assert(ctx);
if (si->si_code == CLD_EXITED) {
code = si->si_status;
if (code != EXIT_SUCCESS)
log_error("%s failed with exit status %i.", ctx->name, code);
else
log_debug("%s succeeded.", ctx->name);
} else {
code = -EPROTO;
if (IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED))
log_error("%s terminated by signal %s.", ctx->name, signal_to_string(si->si_status));
else
log_error("%s failed due to unknown reason.", ctx->name);
}
pidref_done(&ctx->pid);
return sd_event_exit(sd_event_source_get_event(s), code);
}
static int helper_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
char buf[NOTIFY_BUFFER_MAX+1];
struct iovec iovec = {
.iov_base = buf,
.iov_len = sizeof(buf)-1,
};
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control;
struct msghdr msghdr = {
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = &control,
.msg_controllen = sizeof(control),
};
struct ucred *ucred;
CalloutContext *ctx = ASSERT_PTR(userdata);
char* progress_str;
int progress;
ssize_t n;
n = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
if (n < 0) {
if (ERRNO_IS_TRANSIENT(n))
return 0;
return (int) n;
}
cmsg_close_all(&msghdr);
if (msghdr.msg_flags & MSG_TRUNC) {
log_warning("Got overly long notification datagram, ignoring.");
return 0;
}
ucred = CMSG_FIND_DATA(&msghdr, SOL_SOCKET, SCM_CREDENTIALS, struct ucred);
if (!ucred || ucred->pid <= 0) {
log_warning("Got notification datagram lacking credential information, ignoring.");
return 0;
}
if (ucred->pid != ctx->pid.pid) {
log_warning("Got notification datagram from unexpected peer, ignoring.");
return 0;
}
buf[n] = 0;
progress_str = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
if (!progress_str)
return 0;
truncate_nl(progress_str);
progress = parse_percent(progress_str);
if (progress < 0) {
log_warning("Got invalid percent value '%s', ignoring.", progress_str);
return 0;
}
return ctx->callback(ctx->transfer, ctx->instance, progress);
}
static int run_callout(
const char *name,
char *cmdline[]) {
char *cmdline[],
const Transfer *transfer,
const Instance *instance,
TransferProgress callback,
void *userdata) {
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_(sd_event_source_unrefp) sd_event_source *exit_source = NULL, *notify_source = NULL;
_cleanup_close_ int fd = -EBADF;
_cleanup_free_ char *bind_name = NULL;
union sockaddr_union bsa;
int r;
assert(name);
assert(cmdline);
assert(cmdline[0]);
r = safe_fork(name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL);
_cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
r = callout_context_new(transfer, instance, callback, name, userdata, &ctx);
if (r < 0)
return r;
return log_oom();
r = sd_event_new(&event);
if (r < 0)
return log_error_errno(r, "Failed to create event: %m");
/* Kill the helper & return an error if we get interrupted by a signal */
r = sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
if (r < 0)
return log_error_errno(r, "Failed to register signal to event: %m");
r = sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
if (r < 0)
return log_error_errno(r, "Failed to register signal to event: %m");
fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (fd < 0)
return log_error_errno(errno, "Failed to create UNIX socket for notification: %m");
if (asprintf(&bind_name, "@%" PRIx64 "/sysupdate/" PID_FMT "/notify", random_u64(), getpid_cached()) < 0)
return log_oom();
r = sockaddr_un_set_path(&bsa.un, bind_name);
if (r < 0)
return log_error_errno(r, "Failed to set socket path: %m");
if (bind(fd, &bsa.sa, r) < 0)
return log_error_errno(errno, "Failed to bind to notification socket: %m");
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
return log_error_errno(r, "Failed to set socket options: %m");
r = pidref_safe_fork(ctx->name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &ctx->pid);
if (r < 0)
return log_error_errno(r, "Failed to fork process %s: %m", ctx->name);
if (r == 0) {
/* Child */
if (setenv("NOTIFY_SOCKET", bind_name, 1) < 0) {
log_error_errno(errno, "setenv() failed: %m");
_exit(EXIT_FAILURE);
}
r = invoke_callout_binary(cmdline[0], (char *const*) cmdline);
log_error_errno(r, "Failed to execute %s tool: %m", cmdline[0]);
_exit(EXIT_FAILURE);
}
return 0;
/* Quit the loop w/ when child process exits */
r = event_add_child_pidref(event, &exit_source, &ctx->pid, WEXITED, helper_on_exit, (void*) ctx);
if (r < 0)
return log_error_errno(r, "Failed to add child process to event loop: %m");
r = sd_event_source_set_child_process_own(exit_source, true);
if (r < 0)
return log_error_errno(r, "Failed to take ownership of child process: %m");
/* Propagate sd_notify calls */
r = sd_event_add_io(event, &notify_source, fd, EPOLLIN, helper_on_notify, TAKE_PTR(ctx));
if (r < 0)
return log_error_errno(r, "Failed to add notification propagation to event loop: %m");
(void) sd_event_source_set_description(notify_source, "notify-socket");
(void) sd_event_source_set_priority(notify_source, SD_EVENT_PRIORITY_NORMAL - 5);
r = sd_event_source_set_io_fd_own(notify_source, true);
if (r < 0)
return log_error_errno(r, "Event loop failed to take ownership of notification source: %m");
TAKE_FD(fd);
/* Process events until the helper quits */
return sd_event_loop(event);
}
int transfer_acquire_instance(Transfer *t, Instance *i) {
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata) {
_cleanup_free_ char *formatted_pattern = NULL, *digest = NULL;
char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
const char *where = NULL;
@@ -819,6 +1077,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
assert(i);
assert(i->resource);
assert(t == container_of(i->resource, Transfer, source));
assert(cb);
/* 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);
@@ -914,7 +1173,8 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
"--direct", /* just copy/unpack the specified file, don't do anything else */
arg_sync ? "--sync=yes" : "--sync=no",
i->path,
t->temporary_path));
t->temporary_path),
t, i, cb, userdata);
break;
case RESOURCE_PARTITION:
@@ -930,7 +1190,8 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
"--size-max", max_size,
arg_sync ? "--sync=yes" : "--sync=no",
i->path,
t->target.path));
t->target.path),
t, i, cb, userdata);
break;
default:
@@ -953,7 +1214,8 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
arg_sync ? "--sync=yes" : "--sync=no",
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
i->path,
t->temporary_path));
t->temporary_path),
t, i, cb, userdata);
break;
case RESOURCE_TAR:
@@ -969,7 +1231,8 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
arg_sync ? "--sync=yes" : "--sync=no",
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
i->path,
t->temporary_path));
t->temporary_path),
t, i, cb, userdata);
break;
case RESOURCE_URL_FILE:
@@ -988,7 +1251,8 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
"--verify", digest, /* validate by explicit SHA256 sum */
arg_sync ? "--sync=yes" : "--sync=no",
i->path,
t->temporary_path));
t->temporary_path),
t, i, cb, userdata);
break;
case RESOURCE_PARTITION:
@@ -1005,7 +1269,8 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
"--size-max", max_size,
arg_sync ? "--sync=yes" : "--sync=no",
i->path,
t->target.path));
t->target.path),
t, i, cb, userdata);
break;
default:
@@ -1026,7 +1291,8 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
arg_sync ? "--sync=yes" : "--sync=no",
i->path,
t->temporary_path));
t->temporary_path),
t, i, cb, userdata);
break;
default:
+10 -2
View File
@@ -12,6 +12,7 @@ typedef struct Transfer Transfer;
#include "sysupdate-partition.h"
#include "sysupdate-resource.h"
#include "sysupdate.h"
struct Transfer {
char *definition_path;
@@ -25,6 +26,9 @@ struct Transfer {
uint64_t instances_max;
bool remove_temporary;
char **changelog;
char **appstream;
/* When creating a new partition/file, optionally override these attributes explicitly */
sd_id128_t partition_uuid;
bool partition_uuid_set;
@@ -44,9 +48,13 @@ struct Transfer {
/* If we write to a partition in a partition table, the metrics of it */
PartitionInfo partition_info;
PartitionChange partition_change;
Context *context;
};
Transfer *transfer_new(void);
typedef int (*TransferProgress)(const Transfer *t, const Instance *inst, unsigned percentage);
Transfer *transfer_new(Context *ctx);
Transfer *transfer_free(Transfer *t);
DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_free);
@@ -57,6 +65,6 @@ 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_acquire_instance(Transfer *t, Instance *i);
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata);
int transfer_install_instance(Transfer *t, Instance *i, const char *root);
@@ -0,0 +1,93 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "glyph-util.h"
#include "sysupdate-update-set-flags.h"
#include "terminal-util.h"
const char *update_set_flags_to_color(UpdateSetFlags flags) {
if (flags == 0 || (flags & UPDATE_OBSOLETE))
return (flags & UPDATE_NEWEST) ? ansi_highlight_grey() : ansi_grey();
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
return ansi_highlight();
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_PROTECTED))
return ansi_highlight_magenta();
if ((flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_OBSOLETE)) == (UPDATE_AVAILABLE|UPDATE_NEWEST))
return ansi_highlight_green();
return NULL;
}
const char *update_set_flags_to_glyph(UpdateSetFlags flags) {
if (flags == 0 || (flags & UPDATE_OBSOLETE))
return special_glyph(SPECIAL_GLYPH_MULTIPLICATION_SIGN);
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
return special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE);
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_PROTECTED))
return special_glyph(SPECIAL_GLYPH_WHITE_CIRCLE);
if ((flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_OBSOLETE)) == (UPDATE_AVAILABLE|UPDATE_NEWEST))
return special_glyph(SPECIAL_GLYPH_CIRCLE_ARROW);
return " ";
}
const char *update_set_flags_to_string(UpdateSetFlags flags) {
switch ((unsigned) flags) {
case 0:
return "n/a";
case UPDATE_INSTALLED|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_PROTECTED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
return "current";
case UPDATE_AVAILABLE|UPDATE_NEWEST:
case UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
return "candidate";
case UPDATE_INSTALLED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE:
return "installed";
case UPDATE_INSTALLED|UPDATE_PROTECTED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PROTECTED:
return "protected";
case UPDATE_AVAILABLE:
case UPDATE_AVAILABLE|UPDATE_PROTECTED:
return "available";
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
return "current+obsolete";
case UPDATE_INSTALLED|UPDATE_OBSOLETE:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE:
return "installed+obsolete";
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_PROTECTED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_PROTECTED:
return "protected+obsolete";
case UPDATE_AVAILABLE|UPDATE_OBSOLETE:
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_PROTECTED:
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST:
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
return "available+obsolete";
default:
assert_not_reached();
}
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
typedef enum UpdateSetFlags {
UPDATE_NEWEST = 1 << 0,
UPDATE_AVAILABLE = 1 << 1,
UPDATE_INSTALLED = 1 << 2,
UPDATE_OBSOLETE = 1 << 3,
UPDATE_PROTECTED = 1 << 4,
} UpdateSetFlags;
const char *update_set_flags_to_color(UpdateSetFlags flags);
const char *update_set_flags_to_glyph(UpdateSetFlags flags);
const char *update_set_flags_to_string(UpdateSetFlags flags);
-36
View File
@@ -1,10 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "glyph-util.h"
#include "string-util.h"
#include "sysupdate-update-set.h"
#include "terminal-util.h"
UpdateSet *update_set_free(UpdateSet *us) {
if (!us)
@@ -27,37 +25,3 @@ int update_set_cmp(UpdateSet *const*a, UpdateSet *const*b) {
/* Newest version at the beginning */
return -strverscmp_improved((*a)->version, (*b)->version);
}
const char* update_set_flags_to_color(UpdateSetFlags flags) {
if (flags == 0 || (flags & UPDATE_OBSOLETE))
return (flags & UPDATE_NEWEST) ? ansi_highlight_grey() : ansi_grey();
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
return ansi_highlight();
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_PROTECTED))
return ansi_highlight_magenta();
if ((flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_OBSOLETE)) == (UPDATE_AVAILABLE|UPDATE_NEWEST))
return ansi_highlight_green();
return NULL;
}
const char* update_set_flags_to_glyph(UpdateSetFlags flags) {
if (flags == 0 || (flags & UPDATE_OBSOLETE))
return special_glyph(SPECIAL_GLYPH_MULTIPLICATION_SIGN);
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
return special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE);
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_PROTECTED))
return special_glyph(SPECIAL_GLYPH_WHITE_CIRCLE);
if ((flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_OBSOLETE)) == (UPDATE_AVAILABLE|UPDATE_NEWEST))
return special_glyph(SPECIAL_GLYPH_CIRCLE_ARROW);
return " ";
}
+1 -12
View File
@@ -8,14 +8,7 @@
typedef struct UpdateSet UpdateSet;
#include "sysupdate-instance.h"
typedef enum UpdateSetFlags {
UPDATE_NEWEST = 1 << 0,
UPDATE_AVAILABLE = 1 << 1,
UPDATE_INSTALLED = 1 << 2,
UPDATE_OBSOLETE = 1 << 3,
UPDATE_PROTECTED = 1 << 4,
} UpdateSetFlags;
#include "sysupdate-update-set-flags.h"
struct UpdateSet {
UpdateSetFlags flags;
@@ -25,8 +18,4 @@ struct UpdateSet {
};
UpdateSet *update_set_free(UpdateSet *us);
int update_set_cmp(UpdateSet *const*a, UpdateSet *const*b);
const char* update_set_flags_to_color(UpdateSetFlags flags);
const char* update_set_flags_to_glyph(UpdateSetFlags flags);
+23 -1
View File
@@ -1,4 +1,26 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "path-util.h"
#include "sd-bus.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "login-util.h"
#include "sysupdate-util.h"
int reboot_now(void) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL;
int r;
r = sd_bus_open_system(&bus);
if (r < 0)
return log_error_errno(r, "Failed to open bus connection: %m");
r = bus_call_method(bus, bus_login_mgr, "RebootWithFlags", &error, NULL, "t",
(uint64_t) SD_LOGIND_ROOT_CHECK_INHIBITORS);
if (r < 0)
return log_error_errno(r, "Failed to issue reboot request: %s", bus_error_message(&error, r));
return 0;
}
+5
View File
@@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int reboot_now(void);
+269 -141
View File
@@ -4,8 +4,6 @@
#include <unistd.h>
#include "build.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "chase.h"
#include "conf-files.h"
#include "constants.h"
@@ -15,7 +13,7 @@
#include "format-table.h"
#include "glyph-util.h"
#include "hexdecoct.h"
#include "login-util.h"
#include "json-util.h"
#include "main-func.h"
#include "mount-util.h"
#include "os-util.h"
@@ -25,12 +23,14 @@
#include "path-util.h"
#include "pretty-print.h"
#include "set.h"
#include "signal-util.h"
#include "sort-util.h"
#include "string-util.h"
#include "strv.h"
#include "sysupdate.h"
#include "sysupdate-transfer.h"
#include "sysupdate-update-set.h"
#include "sysupdate.h"
#include "sysupdate-util.h"
#include "terminal-util.h"
#include "utf8.h"
#include "verbs.h"
@@ -47,6 +47,7 @@ static bool arg_reboot = false;
static char *arg_component = NULL;
static int arg_verify = -1;
static ImagePolicy *arg_image_policy = NULL;
static bool arg_offline = false;
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
@@ -70,12 +71,12 @@ static Context *context_free(Context *c) {
if (!c)
return NULL;
for (size_t i = 0; i < c->n_transfers; i++)
transfer_free(c->transfers[i]);
FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
transfer_free(*tr);
free(c->transfers);
for (size_t i = 0; i < c->n_update_sets; i++)
update_set_free(c->update_sets[i]);
FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
update_set_free(*us);
free(c->update_sets);
hashmap_free(c->web_cache);
@@ -135,7 +136,7 @@ static int context_read_definitions(
if (!GREEDY_REALLOC(c->transfers, c->n_transfers + 1))
return log_oom();
t = transfer_new();
t = transfer_new(c);
if (!t)
return log_oom();
@@ -159,8 +160,10 @@ static int context_read_definitions(
"No transfer definitions found.");
}
for (size_t i = 0; i < c->n_transfers; i++) {
r = transfer_resolve_paths(c->transfers[i], root, node);
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
Transfer *t = *tr;
r = transfer_resolve_paths(t, root, node);
if (r < 0)
return r;
}
@@ -175,10 +178,12 @@ static int context_load_installed_instances(Context *c) {
log_info("Discovering installed instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
for (size_t i = 0; i < c->n_transfers; i++) {
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
Transfer *t = *tr;
r = resource_load_instances(
&c->transfers[i]->target,
arg_verify >= 0 ? arg_verify : c->transfers[i]->verify,
&t->target,
arg_verify >= 0 ? arg_verify : t->verify,
&c->web_cache);
if (r < 0)
return r;
@@ -194,12 +199,13 @@ static int context_load_available_instances(Context *c) {
log_info("Discovering available instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
for (size_t i = 0; i < c->n_transfers; i++) {
assert(c->transfers[i]);
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
Transfer *t = *tr;
assert(t);
r = resource_load_instances(
&c->transfers[i]->source,
arg_verify >= 0 ? arg_verify : c->transfers[i]->verify,
&t->source,
arg_verify >= 0 ? arg_verify : t->verify,
&c->web_cache);
if (r < 0)
return r;
@@ -237,9 +243,8 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
rr = &t->target;
}
for (size_t j = 0; j < rr->n_instances; j++) {
Instance *i = rr->instances[j];
FOREACH_ARRAY(inst, rr->instances, rr->n_instances) {
Instance *i = *inst;
assert(i);
/* Is the instance we are looking at equal or newer than the boundary? If so, we
@@ -295,12 +300,13 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
continue;
/* See if we already have this update set in our table */
for (size_t i = 0; i < c->n_update_sets; i++) {
if (strverscmp_improved(c->update_sets[i]->version, cursor) != 0)
FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
UpdateSet *u = *update_set;
if (strverscmp_improved(u->version, cursor) != 0)
continue;
/* We only store the instances we found first, but we remember we also found it again */
c->update_sets[i]->flags |= flags | extra_flags;
u->flags |= flags | extra_flags;
exists = true;
newest_found = true;
break;
@@ -355,71 +361,18 @@ static int context_discover_update_sets(Context *c) {
if (r < 0)
return r;
log_info("Determining available update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
if (!arg_offline) {
log_info("Determining available update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
if (r < 0)
return r;
r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
if (r < 0)
return r;
}
typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
return 0;
}
static const char *update_set_flags_to_string(UpdateSetFlags flags) {
switch ((unsigned) flags) {
case 0:
return "n/a";
case UPDATE_INSTALLED|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_PROTECTED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
return "current";
case UPDATE_AVAILABLE|UPDATE_NEWEST:
case UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
return "candidate";
case UPDATE_INSTALLED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE:
return "installed";
case UPDATE_INSTALLED|UPDATE_PROTECTED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PROTECTED:
return "protected";
case UPDATE_AVAILABLE:
case UPDATE_AVAILABLE|UPDATE_PROTECTED:
return "available";
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
return "current+obsolete";
case UPDATE_INSTALLED|UPDATE_OBSOLETE:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE:
return "installed+obsolete";
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_PROTECTED:
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_PROTECTED:
return "protected+obsolete";
case UPDATE_AVAILABLE|UPDATE_OBSOLETE:
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_PROTECTED:
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST:
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
return "available+obsolete";
default:
assert_not_reached();
}
}
static int context_show_table(Context *c) {
_cleanup_(table_unrefp) Table *t = NULL;
int r;
@@ -434,8 +387,8 @@ static int context_show_table(Context *c) {
(void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
(void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
for (size_t i = 0; i < c->n_update_sets; i++) {
UpdateSet *us = c->update_sets[i];
FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
UpdateSet *us = *update_set;
const char *color;
color = update_set_flags_to_color(us->flags);
@@ -462,9 +415,9 @@ static UpdateSet *context_update_set_by_version(Context *c, const char *version)
assert(c);
assert(version);
for (size_t i = 0; i < c->n_update_sets; i++)
if (streq(c->update_sets[i]->version, version))
return c->update_sets[i];
FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets)
if (streq((*update_set)->version, version))
return *update_set;
return NULL;
}
@@ -474,7 +427,9 @@ static int context_show_version(Context *c, const char *version) {
have_fs_attributes = false, have_partition_attributes = false,
have_size = false, have_tries = false, have_no_auto = false,
have_read_only = false, have_growfs = false, have_sha256 = false;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
_cleanup_(table_unrefp) Table *t = NULL;
_cleanup_strv_free_ char **changelog_urls = NULL;
UpdateSet *us;
int r;
@@ -516,18 +471,34 @@ static int context_show_version(Context *c, const char *version) {
table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
/* Determine if the target will make use of partition/fs attributes for any of the transfers */
for (size_t n = 0; n < c->n_transfers; n++) {
Transfer *tr = c->transfers[n];
FOREACH_ARRAY(transfer, c->transfers, c->n_transfers) {
Transfer *tr = *transfer;
if (tr->target.type == RESOURCE_PARTITION)
show_partition_columns = true;
if (RESOURCE_IS_FILESYSTEM(tr->target.type))
show_fs_columns = true;
STRV_FOREACH(changelog, tr->changelog) {
assert(*changelog);
_cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
if (!changelog_url)
return log_oom();
/* Avoid duplicates */
if (strv_contains(changelog_urls, changelog_url))
continue;
/* changelog_urls takes ownership of expanded changelog_url */
r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
if (r < 0)
return log_oom();
}
}
for (size_t n = 0; n < us->n_instances; n++) {
Instance *i = us->instances[n];
FOREACH_ARRAY(inst, us->instances, us->n_instances) {
Instance *i = *inst;
r = table_add_many(t,
TABLE_STRING, resource_type_to_string(i->resource->type),
TABLE_PATH, i->path);
@@ -661,7 +632,55 @@ static int context_show_version(Context *c, const char *version) {
if (!have_sha256)
(void) table_hide_column_from_display(t, 12);
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
printf("%s%s%s Version: %s\n"
" State: %s%s%s\n"
"Installed: %s%s\n"
"Available: %s%s\n"
"Protected: %s%s%s\n"
" Obsolete: %s%s%s\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_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_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(),
us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
STRV_FOREACH(url, changelog_urls) {
_cleanup_free_ char *changelog_link = NULL;
r = terminal_urlify(*url, NULL, &changelog_link);
if (r < 0)
return log_oom();
printf("ChangeLog: %s\n", changelog_link);
}
printf("\n");
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
} else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *t_json = NULL;
r = table_to_json(t, &t_json);
if (r < 0)
return log_error_errno(r, "failed to convert table to JSON: %m");
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("version", us->version),
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("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
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_STRV("changelog_urls", changelog_urls),
SD_JSON_BUILD_PAIR_VARIANT("contents", t_json));
if (r < 0)
return log_error_errno(r, "Failed to create JSON: %m");
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
if (r < 0)
return log_error_errno(r, "Failed to print JSON: %m");
return 0;
}
}
static int context_vacuum(
@@ -678,18 +697,32 @@ static int context_vacuum(
else
log_info("Making room for %" PRIu64 " updates%s", space, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
for (size_t i = 0; i < c->n_transfers; i++) {
r = transfer_vacuum(c->transfers[i], space, extra_protected_version);
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
Transfer *t = *tr;
r = transfer_vacuum(t, space, extra_protected_version);
if (r < 0)
return r;
count = MAX(count, r);
}
if (count > 0)
log_info("Removed %i instances.", count);
else
log_info("Removed no instances.");
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
if (count > 0)
log_info("Removed %i instances.", count);
else
log_info("Removed no instances.");
} else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_INTEGER("removed", count));
if (r < 0)
return log_error_errno(r, "Failed to create JSON: %m");
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
if (r < 0)
return log_error_errno(r, "Failed to print JSON: %m");
}
return 0;
}
@@ -726,15 +759,17 @@ static int context_make_online(Context **ret, const char *node) {
assert(ret);
/* Like context_make_offline(), but also communicates with the update source looking for new
* versions. */
* versions (as long as --offline is not specified on the command line). */
r = context_make_offline(&context, node);
if (r < 0)
return r;
r = context_load_available_instances(context);
if (r < 0)
return r;
if (!arg_offline) {
r = context_load_available_instances(context);
if (r < 0)
return r;
}
r = context_discover_update_sets(context);
if (r < 0)
@@ -744,6 +779,30 @@ static int context_make_online(Context **ret, const char *node) {
return 0;
}
static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
const Context *c = ASSERT_PTR(t->context);
size_t i, n = c->n_transfers;
uint64_t base, scaled;
unsigned overall;
for (i = 0; i < n; i++)
if (c->transfers[i] == t)
break;
assert(i < n); /* We should have found the index */
base = (100 * 100 * i) / n;
scaled = (100 * percentage) / n;
overall = (unsigned) ((base + scaled) / 100);
assert(overall <= 100);
log_debug("Transfer %" PRIu64 "/%zu is %u%% complete (%u%% overall).", i+1, n, percentage, overall);
return sd_notifyf(/* unset= */ false, "X_SYSUPDATE_PROGRESS=%u\n"
"X_SYSUPDATE_TRANSFERS_LEFT=%zu\n"
"X_SYSUPDATE_TRANSFERS_DONE=%zu\n"
"STATUS=Updating to '%s' (%u%% complete).",
overall, n - i, i, inst->metadata.version, overall);
}
static int context_apply(
Context *c,
const char *version,
@@ -793,8 +852,10 @@ static int context_apply(
log_info("Selected update '%s' for install.", us->version);
(void) sd_notifyf(false,
"STATUS=Making room for '%s'.", us->version);
(void) sd_notifyf(/* unset= */ false,
"READY=1\n"
"X_SYSUPDATE_VERSION=%s\n"
"STATUS=Making room for '%s'.", us->version, us->version);
/* Let's make some room. We make sure for each transfer we have one free space to fill. While
* removing stuff we'll protect the version we are trying to acquire. Why that? Maybe an earlier
@@ -809,14 +870,15 @@ static int context_apply(
if (arg_sync)
sync();
(void) sd_notifyf(false,
"STATUS=Updating to '%s'.\n", us->version);
(void) sd_notifyf(/* unset= */ false,
"STATUS=Updating to '%s'.", 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++) {
r = transfer_acquire_instance(c->transfers[i], us->instances[i]);
r = transfer_acquire_instance(c->transfers[i], us->instances[i],
context_on_acquire_progress, c);
if (r < 0)
return r;
}
@@ -824,6 +886,9 @@ static int context_apply(
if (arg_sync)
sync();
(void) sd_notifyf(/* unset= */ false,
"STATUS=Installing '%s'.", us->version);
for (size_t i = 0; i < c->n_transfers; i++) {
r = transfer_install_instance(c->transfers[i], us->instances[i], arg_root);
if (r < 0)
@@ -838,23 +903,6 @@ static int context_apply(
return 1;
}
static int reboot_now(void) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL;
int r;
r = sd_bus_open_system(&bus);
if (r < 0)
return log_error_errno(r, "Failed to open bus connection: %m");
r = bus_call_method(bus, bus_login_mgr, "RebootWithFlags", &error, NULL, "t",
(uint64_t) SD_LOGIND_ROOT_CHECK_INHIBITORS);
if (r < 0)
return log_error_errno(r, "Failed to issue reboot request: %s", bus_error_message(&error, r));
return 0;
}
static int process_image(
bool ro,
char **ret_mounted_dir,
@@ -904,6 +952,7 @@ static int verb_list(int argc, char **argv, void *userdata) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
_cleanup_(context_freep) Context* context = NULL;
_cleanup_strv_free_ char **appstream_urls = NULL;
const char *version;
int r;
@@ -920,8 +969,48 @@ static int verb_list(int argc, char **argv, void *userdata) {
if (version)
return context_show_version(context, version);
else
else if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF))
return context_show_table(context);
else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
_cleanup_strv_free_ char **versions = NULL;
const char *current = NULL;
FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) {
UpdateSet *us = *update_set;
if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
FLAGS_SET(us->flags, UPDATE_NEWEST))
current = us->version;
r = strv_extend(&versions, us->version);
if (r < 0)
return log_oom();
}
FOREACH_ARRAY(tr, context->transfers, context->n_transfers)
STRV_FOREACH(appstream_url, (*tr)->appstream) {
/* Avoid duplicates */
if (strv_contains(appstream_urls, *appstream_url))
continue;
r = strv_extend(&appstream_urls, *appstream_url);
if (r < 0)
return log_oom();
}
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("current", current),
SD_JSON_BUILD_PAIR_STRV("all", versions),
SD_JSON_BUILD_PAIR_STRV("appstream_urls", appstream_urls));
if (r < 0)
return log_error_errno(r, "Failed to create JSON: %m");
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
if (r < 0)
return log_error_errno(r, "Failed to print JSON: %m");
return 0;
}
}
static int verb_check_new(int argc, char **argv, void *userdata) {
@@ -940,12 +1029,28 @@ static int verb_check_new(int argc, char **argv, void *userdata) {
if (r < 0)
return r;
if (!context->candidate) {
log_debug("No candidate found.");
return EXIT_FAILURE;
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
if (!context->candidate) {
log_debug("No candidate found.");
return EXIT_FAILURE;
}
puts(context->candidate->version);
} else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
if (context->candidate)
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context->candidate->version));
else
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available"));
if (r < 0)
return log_error_errno(r, "Failed to create JSON: %m");
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
if (r < 0)
return log_error_errno(r, "Failed to print JSON: %m");
}
puts(context->candidate->version);
return EXIT_SUCCESS;
}
@@ -1164,23 +1269,36 @@ static int verb_components(int argc, char **argv, void *userdata) {
}
}
if (!has_default_component && set_isempty(names)) {
log_info("No components defined.");
return 0;
}
z = set_get_strv(names);
if (!z)
return log_oom();
strv_sort(z);
if (has_default_component)
printf("%s<default>%s\n",
ansi_highlight(), ansi_normal());
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
if (!has_default_component && set_isempty(names)) {
log_info("No components defined.");
return 0;
}
STRV_FOREACH(i, z)
puts(*i);
if (has_default_component)
printf("%s<default>%s\n",
ansi_highlight(), ansi_normal());
STRV_FOREACH(i, z)
puts(*i);
} else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
SD_JSON_BUILD_PAIR_STRV("components", z));
if (r < 0)
return log_error_errno(r, "Failed to create JSON: %m");
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
if (r < 0)
return log_error_errno(r, "Failed to print JSON: %m");
}
return 0;
}
@@ -1217,6 +1335,7 @@ static int verb_help(int argc, char **argv, void *userdata) {
" --sync=BOOL Controls whether to sync data to disk\n"
" --verify=BOOL Force signature verification on or off\n"
" --reboot Reboot after updating to newer version\n"
" --offline Do not fetch metadata from the network\n"
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not show the headers and footers\n"
" --json=pretty|short|off\n"
@@ -1246,6 +1365,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_IMAGE_POLICY,
ARG_REBOOT,
ARG_VERIFY,
ARG_OFFLINE,
};
static const struct option options[] = {
@@ -1263,6 +1383,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "reboot", no_argument, NULL, ARG_REBOOT },
{ "component", required_argument, NULL, 'C' },
{ "verify", required_argument, NULL, ARG_VERIFY },
{ "offline", no_argument, NULL, ARG_OFFLINE },
{}
};
@@ -1366,6 +1487,10 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_OFFLINE:
arg_offline = true;
break;
case '?':
return -EINVAL;
@@ -1412,6 +1537,9 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
/* SIGCHLD signal must be blocked for sd_event_add_child to work */
BLOCK_SIGNALS(SIGCHLD);
return sysupdate_main(argc, argv);
}
+3
View File
@@ -4,6 +4,9 @@
#include <inttypes.h>
#include <stdbool.h>
/* Forward declare this type so that Transfers can point at it */
typedef struct Context Context;
extern bool arg_sync;
extern uint64_t arg_instances_max;
extern char *arg_root;