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;