diff --git a/docs/APPSTREAM_BUNDLE.md b/docs/APPSTREAM_BUNDLE.md
new file mode 100644
index 0000000000..f953e169d0
--- /dev/null
+++ b/docs/APPSTREAM_BUNDLE.md
@@ -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 `` 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 `...`
+will be handled by [PackageKit](https://www.freedesktop.org/software/PackageKit/), and components
+with a `...` will be handled by [libflatpak](https://docs.flatpak.org/).
+This document will define how to format an appstream component's `` 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, `foobar` 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
+
+ com.example.Devel
+ com.example.OS
+ Development Tools
+ Tools essential to develop Example OS
+
+ gcc
+ g++
+ make
+ autoconf
+ cmake
+ meson
+ ninja
+
+ Example, inc.
+
+
+
+
+
+ devel
+
+```
+
+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
+
+ com.example.OS
+
+
+
+
This release includes various bug fixes and performance improvements
+
+
+
+
+
+```
+
+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
+
+ com.example.Foobar
+ Foobar Service
+ Service that does foo to bar
+ https://example.com/products/foobar/logo.svg
+ https://example.com/products/foobar
+
+ com.example.Foobar
+
+ Example, inc.
+
+
+
+
This release fixes a major security vulnerability. Please update ASAP.
+
+
+ CVE-2024-28153
+
+
+
+
+
+
Initial release!
+
+
+
+ foobar
+
+```
+
+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.
diff --git a/man/systemd-sysupdate.xml b/man/systemd-sysupdate.xml
index 8c1228c7d9..f77bd3d0d9 100644
--- a/man/systemd-sysupdate.xml
+++ b/man/systemd-sysupdate.xml
@@ -295,6 +295,16 @@
+
+
+
+ Prevents fetching metadata from the network (i.e. SHA256SUMS).
+ This is most useful when used in combination with the list command, to query
+ locally installed versions.
+
+
+
+
diff --git a/man/sysupdate.d.xml b/man/sysupdate.d.xml
index 125c5802b7..ef3b21d29d 100644
--- a/man/sysupdate.d.xml
+++ b/man/sysupdate.d.xml
@@ -487,6 +487,38 @@
+
+ ChangeLog=
+
+ Optionally takes a human-presentable URL to a website containing a change-log of
+ the resource being updated.
+
+ 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.
+
+ This setting supports specifier expansion. See below for details on supported
+ specifiers. This setting will also expand the @v wildcard pattern. See above
+ for details.
+
+
+
+
+
+ AppStream=
+
+ Optionally takes a URL to an
+ AppStream catalog
+ 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 special metadata
+ to be correctly associated with systemd-sysupdate by the software centers.
+
+ This setting supports specifier expansion. See below for details on supported
+ specifiers.
+
+
+
diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build
index 417b47a136..b1b1204a2a 100644
--- a/src/sysupdate/meson.build
+++ b/src/sysupdate/meson.build
@@ -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',
)
diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c
index 435c38020e..b1b3c662f6 100644
--- a/src/sysupdate/sysupdate-transfer.c
+++ b/src/sysupdate/sysupdate-transfer.c
@@ -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, ¬ify_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:
diff --git a/src/sysupdate/sysupdate-transfer.h b/src/sysupdate/sysupdate-transfer.h
index b0c2a6e455..c28b68cc92 100644
--- a/src/sysupdate/sysupdate-transfer.h
+++ b/src/sysupdate/sysupdate-transfer.h
@@ -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);
diff --git a/src/sysupdate/sysupdate-update-set-flags.c b/src/sysupdate/sysupdate-update-set-flags.c
new file mode 100644
index 0000000000..1aade456db
--- /dev/null
+++ b/src/sysupdate/sysupdate-update-set-flags.c
@@ -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();
+ }
+}
diff --git a/src/sysupdate/sysupdate-update-set-flags.h b/src/sysupdate/sysupdate-update-set-flags.h
new file mode 100644
index 0000000000..6f954d7c22
--- /dev/null
+++ b/src/sysupdate/sysupdate-update-set-flags.h
@@ -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);
diff --git a/src/sysupdate/sysupdate-update-set.c b/src/sysupdate/sysupdate-update-set.c
index f5a800429f..5682102d61 100644
--- a/src/sysupdate/sysupdate-update-set.c
+++ b/src/sysupdate/sysupdate-update-set.c
@@ -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 " ";
-}
diff --git a/src/sysupdate/sysupdate-update-set.h b/src/sysupdate/sysupdate-update-set.h
index 981535aedf..0b9bbfbe67 100644
--- a/src/sysupdate/sysupdate-update-set.h
+++ b/src/sysupdate/sysupdate-update-set.h
@@ -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);
diff --git a/src/sysupdate/sysupdate-util.c b/src/sysupdate/sysupdate-util.c
index eacc592f62..2b3acd0fd9 100644
--- a/src/sysupdate/sysupdate-util.c
+++ b/src/sysupdate/sysupdate-util.c
@@ -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;
+}
diff --git a/src/sysupdate/sysupdate-util.h b/src/sysupdate/sysupdate-util.h
new file mode 100644
index 0000000000..fdd6c8318e
--- /dev/null
+++ b/src/sysupdate/sysupdate-util.h
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#pragma once
+
+int reboot_now(void);
diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c
index 9a8915c3fe..870aa04d7c 100644
--- a/src/sysupdate/sysupdate.c
+++ b/src/sysupdate/sysupdate.c
@@ -4,8 +4,6 @@
#include
#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%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%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);
}
diff --git a/src/sysupdate/sysupdate.h b/src/sysupdate/sysupdate.h
index cba9bf489f..011b351375 100644
--- a/src/sysupdate/sysupdate.h
+++ b/src/sysupdate/sysupdate.h
@@ -4,6 +4,9 @@
#include
#include
+/* 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;