Merge pull request #33520 from tomcoldrick-ct/coldtom/sysupdate-improvements
sysupdate: Add --offline mode, notifications, JSON output and additional metadata
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: Appstream Bundle
|
||||
category: Interfaces
|
||||
layout: default
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
---
|
||||
|
||||
# Appstream Bundle
|
||||
|
||||
NOTE: This document is a work-in-progress.
|
||||
|
||||
NOTE: This isn't yet implemented in libappstream and the software centers.
|
||||
|
||||
[Appstream catalogs](https://www.freedesktop.org/software/appstream/docs/chap-CatalogData.html)
|
||||
are a standardized way to expose metadata about system components, apps, and updates to software
|
||||
centers (i.e. GNOME Software and KDE Discover). The `<bundle/>` tag links an appstream component
|
||||
to a packaging format. This is used by the software centers to decide which code path (or plugin)
|
||||
should handle the component. For instance: components with a `<bundle type="package">...</bundle>`
|
||||
will be handled by [PackageKit](https://www.freedesktop.org/software/PackageKit/), and components
|
||||
with a `<bundle type="flatpak">...</bundle>` will be handled by [libflatpak](https://docs.flatpak.org/).
|
||||
This document will define how to format an appstream component's `<bundle>` tag such that software
|
||||
centers will know to manage it using systemd. The following syntax will be supported:
|
||||
|
||||
A `type="systemd"` attribute. This tells the software center that it should treat the bundle tag
|
||||
as described in this document.
|
||||
|
||||
A `class=""` attribute, with the following possible values: `sysupdate`, `extension`, `confext`,
|
||||
or `portable`. These correspond to sysupdate components, sysexts, confexts, and portable services
|
||||
respectively.
|
||||
|
||||
The value of the tag will be used as the name of the image (corresponding to the `class=` attribute).
|
||||
So for instance, `<bundle type="systemd" class="extension">foobar</bundle>` corresponds to a sysext
|
||||
named "foobar". For `class="sysupdate"`, there is a special case: if the value is empty, then the
|
||||
bundle actually refers to the host system.
|
||||
|
||||
## Examples
|
||||
|
||||
```xml
|
||||
<component type="addon">
|
||||
<id>com.example.Devel</id>
|
||||
<extends>com.example.OS</extends>
|
||||
<name>Development Tools</name>
|
||||
<summary>Tools essential to develop Example OS</summary>
|
||||
<provides>
|
||||
<binary>gcc</binary>
|
||||
<binary>g++</binary>
|
||||
<binary>make</binary>
|
||||
<binary>autoconf</binary>
|
||||
<binary>cmake</binary>
|
||||
<binary>meson</binary>
|
||||
<binary>ninja</binary>
|
||||
</provides>
|
||||
<developer_name>Example, inc.</developer_name>
|
||||
<releases>
|
||||
<release version="45" date="2024-01-15" />
|
||||
<release version="44" date="2023-12-08" />
|
||||
<release version="43" date="2023-11-10" />
|
||||
</releases>
|
||||
<bundle type="systemd" class="extension">devel</bundle>
|
||||
</component>
|
||||
```
|
||||
|
||||
defines a sysext named `devel` to be presented by the software center. It will be
|
||||
updated via `systemd-sysupdated`'s `extension:devel` target. It will be treated
|
||||
as a plugin for the operating system itself.
|
||||
|
||||
```xml
|
||||
<component merge="append">
|
||||
<id>com.example.OS</id>
|
||||
<releases>
|
||||
<release version="45" date="2024-01-15" urgency="high">
|
||||
<description>
|
||||
<p>This release includes various bug fixes and performance improvements</p>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
<bundle type="systemd" class="sysupdate" />
|
||||
</component>
|
||||
```
|
||||
|
||||
extends existing appstream metadata for the host OS with a changelog. It also tells the software
|
||||
center that the host OS should be updated using the `host` target for `systemd-sysupdated`.
|
||||
|
||||
```xml
|
||||
<component type="service">
|
||||
<id>com.example.Foobar</id>
|
||||
<name>Foobar Service</name>
|
||||
<summary>Service that does foo to bar</summary>
|
||||
<icon type="remote">https://example.com/products/foobar/logo.svg</icon>
|
||||
<url type="homepage">https://example.com/products/foobar</url>
|
||||
<provides>
|
||||
<dbus type="system">com.example.Foobar</dbus>
|
||||
</provides>
|
||||
<developer_name>Example, inc.</developer_name>
|
||||
<releases>
|
||||
<release version="1.0.1" date="2024-02-16" urgency="critical">
|
||||
<description>
|
||||
<p>This release fixes a major security vulnerability. Please update ASAP.</p>
|
||||
</description>
|
||||
<issues>
|
||||
<issue type="cve">CVE-2024-28153</issue>
|
||||
</issues>
|
||||
</release>
|
||||
<release version="1.1-beta" date="2024-01-08" type="development" />
|
||||
<release version="1.0" date="2023-11-23">
|
||||
<description>
|
||||
<p>Initial release!</p>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
<bundle type="systemd" class="portable">foobar</bundle>
|
||||
</component>
|
||||
```
|
||||
|
||||
defines a portable service named `foobar` to be presented by the software center. It will be
|
||||
updated via `systemd-sysupdated`'s `portable:foobar` target. It will be marked as an
|
||||
urgent update. It will be presented to the user with a display name, a description, and
|
||||
a custom icon.
|
||||
@@ -295,6 +295,16 @@
|
||||
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--offline</option></term>
|
||||
|
||||
<listitem><para>Prevents fetching metadata from the network (i.e. <filename>SHA256SUMS</filename>).
|
||||
This is most useful when used in combination with the <command>list</command> command, to query
|
||||
locally installed versions.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||
<xi:include href="standard-options.xml" xpointer="no-legend" />
|
||||
<xi:include href="standard-options.xml" xpointer="json" />
|
||||
|
||||
@@ -487,6 +487,38 @@
|
||||
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>ChangeLog=</varname></term>
|
||||
|
||||
<listitem><para>Optionally takes a human-presentable URL to a website containing a change-log of
|
||||
the resource being updated.</para>
|
||||
|
||||
<para>This may be set multiple times in a single transfer definition. If set multiple times, the
|
||||
values are gathered into a list of URLs. Adding a value of the empty string will clear the existing
|
||||
list of all values.</para>
|
||||
|
||||
<para>This setting supports specifier expansion. See below for details on supported
|
||||
specifiers. This setting will also expand the <literal>@v</literal> wildcard pattern. See above
|
||||
for details.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>AppStream=</varname></term>
|
||||
|
||||
<listitem><para>Optionally takes a URL to an
|
||||
<ulink url="https://www.freedesktop.org/software/appstream/docs/chap-CatalogData.html">AppStream catalog</ulink>
|
||||
XML file. This may be used by software centers (such as GNOME Software or KDE Discover) to present
|
||||
rich metadata about the resources being updated. This includes display names, changelogs, icons,
|
||||
and more. The specified catalog must include <ulink url="https://systemd.io/APPSTREAM_BUNDLE">special metadata</ulink>
|
||||
to be correctly associated with <command>systemd-sysupdate</command> by the software centers.</para>
|
||||
|
||||
<para>This setting supports specifier expansion. See below for details on supported
|
||||
specifiers.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -12,6 +12,7 @@ typedef struct Transfer Transfer;
|
||||
|
||||
#include "sysupdate-partition.h"
|
||||
#include "sysupdate-resource.h"
|
||||
#include "sysupdate.h"
|
||||
|
||||
struct Transfer {
|
||||
char *definition_path;
|
||||
@@ -25,6 +26,9 @@ struct Transfer {
|
||||
uint64_t instances_max;
|
||||
bool remove_temporary;
|
||||
|
||||
char **changelog;
|
||||
char **appstream;
|
||||
|
||||
/* When creating a new partition/file, optionally override these attributes explicitly */
|
||||
sd_id128_t partition_uuid;
|
||||
bool partition_uuid_set;
|
||||
@@ -44,9 +48,13 @@ struct Transfer {
|
||||
/* If we write to a partition in a partition table, the metrics of it */
|
||||
PartitionInfo partition_info;
|
||||
PartitionChange partition_change;
|
||||
|
||||
Context *context;
|
||||
};
|
||||
|
||||
Transfer *transfer_new(void);
|
||||
typedef int (*TransferProgress)(const Transfer *t, const Instance *inst, unsigned percentage);
|
||||
|
||||
Transfer *transfer_new(Context *ctx);
|
||||
|
||||
Transfer *transfer_free(Transfer *t);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_free);
|
||||
@@ -57,6 +65,6 @@ int transfer_resolve_paths(Transfer *t, const char *root, const char *node);
|
||||
|
||||
int transfer_vacuum(Transfer *t, uint64_t space, const char *extra_protected_version);
|
||||
|
||||
int transfer_acquire_instance(Transfer *t, Instance *i);
|
||||
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata);
|
||||
|
||||
int transfer_install_instance(Transfer *t, Instance *i, const char *root);
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "glyph-util.h"
|
||||
#include "sysupdate-update-set-flags.h"
|
||||
#include "terminal-util.h"
|
||||
|
||||
const char *update_set_flags_to_color(UpdateSetFlags flags) {
|
||||
|
||||
if (flags == 0 || (flags & UPDATE_OBSOLETE))
|
||||
return (flags & UPDATE_NEWEST) ? ansi_highlight_grey() : ansi_grey();
|
||||
|
||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
|
||||
return ansi_highlight();
|
||||
|
||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_PROTECTED))
|
||||
return ansi_highlight_magenta();
|
||||
|
||||
if ((flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_OBSOLETE)) == (UPDATE_AVAILABLE|UPDATE_NEWEST))
|
||||
return ansi_highlight_green();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *update_set_flags_to_glyph(UpdateSetFlags flags) {
|
||||
|
||||
if (flags == 0 || (flags & UPDATE_OBSOLETE))
|
||||
return special_glyph(SPECIAL_GLYPH_MULTIPLICATION_SIGN);
|
||||
|
||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
|
||||
return special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE);
|
||||
|
||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_PROTECTED))
|
||||
return special_glyph(SPECIAL_GLYPH_WHITE_CIRCLE);
|
||||
|
||||
if ((flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_OBSOLETE)) == (UPDATE_AVAILABLE|UPDATE_NEWEST))
|
||||
return special_glyph(SPECIAL_GLYPH_CIRCLE_ARROW);
|
||||
|
||||
return " ";
|
||||
}
|
||||
|
||||
const char *update_set_flags_to_string(UpdateSetFlags flags) {
|
||||
|
||||
switch ((unsigned) flags) {
|
||||
|
||||
case 0:
|
||||
return "n/a";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "current";
|
||||
|
||||
case UPDATE_AVAILABLE|UPDATE_NEWEST:
|
||||
case UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "candidate";
|
||||
|
||||
case UPDATE_INSTALLED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE:
|
||||
return "installed";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PROTECTED:
|
||||
return "protected";
|
||||
|
||||
case UPDATE_AVAILABLE:
|
||||
case UPDATE_AVAILABLE|UPDATE_PROTECTED:
|
||||
return "available";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "current+obsolete";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE:
|
||||
return "installed+obsolete";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_PROTECTED:
|
||||
return "protected+obsolete";
|
||||
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE:
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_PROTECTED:
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST:
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "available+obsolete";
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
typedef enum UpdateSetFlags {
|
||||
UPDATE_NEWEST = 1 << 0,
|
||||
UPDATE_AVAILABLE = 1 << 1,
|
||||
UPDATE_INSTALLED = 1 << 2,
|
||||
UPDATE_OBSOLETE = 1 << 3,
|
||||
UPDATE_PROTECTED = 1 << 4,
|
||||
} UpdateSetFlags;
|
||||
|
||||
const char *update_set_flags_to_color(UpdateSetFlags flags);
|
||||
const char *update_set_flags_to_glyph(UpdateSetFlags flags);
|
||||
const char *update_set_flags_to_string(UpdateSetFlags flags);
|
||||
@@ -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 " ";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
int reboot_now(void);
|
||||
+269
-141
@@ -4,8 +4,6 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "build.h"
|
||||
#include "bus-error.h"
|
||||
#include "bus-locator.h"
|
||||
#include "chase.h"
|
||||
#include "conf-files.h"
|
||||
#include "constants.h"
|
||||
@@ -15,7 +13,7 @@
|
||||
#include "format-table.h"
|
||||
#include "glyph-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "login-util.h"
|
||||
#include "json-util.h"
|
||||
#include "main-func.h"
|
||||
#include "mount-util.h"
|
||||
#include "os-util.h"
|
||||
@@ -25,12 +23,14 @@
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "set.h"
|
||||
#include "signal-util.h"
|
||||
#include "sort-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "sysupdate.h"
|
||||
#include "sysupdate-transfer.h"
|
||||
#include "sysupdate-update-set.h"
|
||||
#include "sysupdate.h"
|
||||
#include "sysupdate-util.h"
|
||||
#include "terminal-util.h"
|
||||
#include "utf8.h"
|
||||
#include "verbs.h"
|
||||
@@ -47,6 +47,7 @@ static bool arg_reboot = false;
|
||||
static char *arg_component = NULL;
|
||||
static int arg_verify = -1;
|
||||
static ImagePolicy *arg_image_policy = NULL;
|
||||
static bool arg_offline = false;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
||||
@@ -70,12 +71,12 @@ static Context *context_free(Context *c) {
|
||||
if (!c)
|
||||
return NULL;
|
||||
|
||||
for (size_t i = 0; i < c->n_transfers; i++)
|
||||
transfer_free(c->transfers[i]);
|
||||
FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
|
||||
transfer_free(*tr);
|
||||
free(c->transfers);
|
||||
|
||||
for (size_t i = 0; i < c->n_update_sets; i++)
|
||||
update_set_free(c->update_sets[i]);
|
||||
FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
|
||||
update_set_free(*us);
|
||||
free(c->update_sets);
|
||||
|
||||
hashmap_free(c->web_cache);
|
||||
@@ -135,7 +136,7 @@ static int context_read_definitions(
|
||||
if (!GREEDY_REALLOC(c->transfers, c->n_transfers + 1))
|
||||
return log_oom();
|
||||
|
||||
t = transfer_new();
|
||||
t = transfer_new(c);
|
||||
if (!t)
|
||||
return log_oom();
|
||||
|
||||
@@ -159,8 +160,10 @@ static int context_read_definitions(
|
||||
"No transfer definitions found.");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < c->n_transfers; i++) {
|
||||
r = transfer_resolve_paths(c->transfers[i], root, node);
|
||||
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
|
||||
Transfer *t = *tr;
|
||||
|
||||
r = transfer_resolve_paths(t, root, node);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@@ -175,10 +178,12 @@ static int context_load_installed_instances(Context *c) {
|
||||
|
||||
log_info("Discovering installed instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
||||
|
||||
for (size_t i = 0; i < c->n_transfers; i++) {
|
||||
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
|
||||
Transfer *t = *tr;
|
||||
|
||||
r = resource_load_instances(
|
||||
&c->transfers[i]->target,
|
||||
arg_verify >= 0 ? arg_verify : c->transfers[i]->verify,
|
||||
&t->target,
|
||||
arg_verify >= 0 ? arg_verify : t->verify,
|
||||
&c->web_cache);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -194,12 +199,13 @@ static int context_load_available_instances(Context *c) {
|
||||
|
||||
log_info("Discovering available instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
||||
|
||||
for (size_t i = 0; i < c->n_transfers; i++) {
|
||||
assert(c->transfers[i]);
|
||||
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
|
||||
Transfer *t = *tr;
|
||||
assert(t);
|
||||
|
||||
r = resource_load_instances(
|
||||
&c->transfers[i]->source,
|
||||
arg_verify >= 0 ? arg_verify : c->transfers[i]->verify,
|
||||
&t->source,
|
||||
arg_verify >= 0 ? arg_verify : t->verify,
|
||||
&c->web_cache);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -237,9 +243,8 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
||||
rr = &t->target;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < rr->n_instances; j++) {
|
||||
Instance *i = rr->instances[j];
|
||||
|
||||
FOREACH_ARRAY(inst, rr->instances, rr->n_instances) {
|
||||
Instance *i = *inst;
|
||||
assert(i);
|
||||
|
||||
/* Is the instance we are looking at equal or newer than the boundary? If so, we
|
||||
@@ -295,12 +300,13 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
||||
continue;
|
||||
|
||||
/* See if we already have this update set in our table */
|
||||
for (size_t i = 0; i < c->n_update_sets; i++) {
|
||||
if (strverscmp_improved(c->update_sets[i]->version, cursor) != 0)
|
||||
FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
|
||||
UpdateSet *u = *update_set;
|
||||
if (strverscmp_improved(u->version, cursor) != 0)
|
||||
continue;
|
||||
|
||||
/* We only store the instances we found first, but we remember we also found it again */
|
||||
c->update_sets[i]->flags |= flags | extra_flags;
|
||||
u->flags |= flags | extra_flags;
|
||||
exists = true;
|
||||
newest_found = true;
|
||||
break;
|
||||
@@ -355,71 +361,18 @@ static int context_discover_update_sets(Context *c) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_info("Determining available update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
||||
if (!arg_offline) {
|
||||
log_info("Determining available update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
||||
|
||||
r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *update_set_flags_to_string(UpdateSetFlags flags) {
|
||||
|
||||
switch ((unsigned) flags) {
|
||||
|
||||
case 0:
|
||||
return "n/a";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "current";
|
||||
|
||||
case UPDATE_AVAILABLE|UPDATE_NEWEST:
|
||||
case UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "candidate";
|
||||
|
||||
case UPDATE_INSTALLED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE:
|
||||
return "installed";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PROTECTED:
|
||||
return "protected";
|
||||
|
||||
case UPDATE_AVAILABLE:
|
||||
case UPDATE_AVAILABLE|UPDATE_PROTECTED:
|
||||
return "available";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "current+obsolete";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE:
|
||||
return "installed+obsolete";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_PROTECTED:
|
||||
return "protected+obsolete";
|
||||
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE:
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_PROTECTED:
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST:
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "available+obsolete";
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int context_show_table(Context *c) {
|
||||
_cleanup_(table_unrefp) Table *t = NULL;
|
||||
int r;
|
||||
@@ -434,8 +387,8 @@ static int context_show_table(Context *c) {
|
||||
(void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
|
||||
(void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
|
||||
|
||||
for (size_t i = 0; i < c->n_update_sets; i++) {
|
||||
UpdateSet *us = c->update_sets[i];
|
||||
FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
|
||||
UpdateSet *us = *update_set;
|
||||
const char *color;
|
||||
|
||||
color = update_set_flags_to_color(us->flags);
|
||||
@@ -462,9 +415,9 @@ static UpdateSet *context_update_set_by_version(Context *c, const char *version)
|
||||
assert(c);
|
||||
assert(version);
|
||||
|
||||
for (size_t i = 0; i < c->n_update_sets; i++)
|
||||
if (streq(c->update_sets[i]->version, version))
|
||||
return c->update_sets[i];
|
||||
FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets)
|
||||
if (streq((*update_set)->version, version))
|
||||
return *update_set;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -474,7 +427,9 @@ static int context_show_version(Context *c, const char *version) {
|
||||
have_fs_attributes = false, have_partition_attributes = false,
|
||||
have_size = false, have_tries = false, have_no_auto = false,
|
||||
have_read_only = false, have_growfs = false, have_sha256 = false;
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||
_cleanup_(table_unrefp) Table *t = NULL;
|
||||
_cleanup_strv_free_ char **changelog_urls = NULL;
|
||||
UpdateSet *us;
|
||||
int r;
|
||||
|
||||
@@ -516,18 +471,34 @@ static int context_show_version(Context *c, const char *version) {
|
||||
table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
|
||||
|
||||
/* Determine if the target will make use of partition/fs attributes for any of the transfers */
|
||||
for (size_t n = 0; n < c->n_transfers; n++) {
|
||||
Transfer *tr = c->transfers[n];
|
||||
FOREACH_ARRAY(transfer, c->transfers, c->n_transfers) {
|
||||
Transfer *tr = *transfer;
|
||||
|
||||
if (tr->target.type == RESOURCE_PARTITION)
|
||||
show_partition_columns = true;
|
||||
if (RESOURCE_IS_FILESYSTEM(tr->target.type))
|
||||
show_fs_columns = true;
|
||||
|
||||
STRV_FOREACH(changelog, tr->changelog) {
|
||||
assert(*changelog);
|
||||
|
||||
_cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
|
||||
if (!changelog_url)
|
||||
return log_oom();
|
||||
|
||||
/* Avoid duplicates */
|
||||
if (strv_contains(changelog_urls, changelog_url))
|
||||
continue;
|
||||
|
||||
/* changelog_urls takes ownership of expanded changelog_url */
|
||||
r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t n = 0; n < us->n_instances; n++) {
|
||||
Instance *i = us->instances[n];
|
||||
|
||||
FOREACH_ARRAY(inst, us->instances, us->n_instances) {
|
||||
Instance *i = *inst;
|
||||
r = table_add_many(t,
|
||||
TABLE_STRING, resource_type_to_string(i->resource->type),
|
||||
TABLE_PATH, i->path);
|
||||
@@ -661,7 +632,55 @@ static int context_show_version(Context *c, const char *version) {
|
||||
if (!have_sha256)
|
||||
(void) table_hide_column_from_display(t, 12);
|
||||
|
||||
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
|
||||
|
||||
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
|
||||
printf("%s%s%s Version: %s\n"
|
||||
" State: %s%s%s\n"
|
||||
"Installed: %s%s\n"
|
||||
"Available: %s%s\n"
|
||||
"Protected: %s%s%s\n"
|
||||
" Obsolete: %s%s%s\n",
|
||||
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
|
||||
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
|
||||
yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
|
||||
yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
|
||||
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
|
||||
us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
|
||||
|
||||
STRV_FOREACH(url, changelog_urls) {
|
||||
_cleanup_free_ char *changelog_link = NULL;
|
||||
r = terminal_urlify(*url, NULL, &changelog_link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
printf("ChangeLog: %s\n", changelog_link);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
|
||||
} else {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *t_json = NULL;
|
||||
|
||||
r = table_to_json(t, &t_json);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "failed to convert table to JSON: %m");
|
||||
|
||||
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("version", us->version),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("newest", FLAGS_SET(us->flags, UPDATE_NEWEST)),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("available", FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)),
|
||||
SD_JSON_BUILD_PAIR_STRV("changelog_urls", changelog_urls),
|
||||
SD_JSON_BUILD_PAIR_VARIANT("contents", t_json));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create JSON: %m");
|
||||
|
||||
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to print JSON: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int context_vacuum(
|
||||
@@ -678,18 +697,32 @@ static int context_vacuum(
|
||||
else
|
||||
log_info("Making room for %" PRIu64 " updates%s", space, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
||||
|
||||
for (size_t i = 0; i < c->n_transfers; i++) {
|
||||
r = transfer_vacuum(c->transfers[i], space, extra_protected_version);
|
||||
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
|
||||
Transfer *t = *tr;
|
||||
|
||||
r = transfer_vacuum(t, space, extra_protected_version);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
count = MAX(count, r);
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
log_info("Removed %i instances.", count);
|
||||
else
|
||||
log_info("Removed no instances.");
|
||||
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
|
||||
if (count > 0)
|
||||
log_info("Removed %i instances.", count);
|
||||
else
|
||||
log_info("Removed no instances.");
|
||||
} else {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||
|
||||
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_INTEGER("removed", count));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create JSON: %m");
|
||||
|
||||
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to print JSON: %m");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -726,15 +759,17 @@ static int context_make_online(Context **ret, const char *node) {
|
||||
assert(ret);
|
||||
|
||||
/* Like context_make_offline(), but also communicates with the update source looking for new
|
||||
* versions. */
|
||||
* versions (as long as --offline is not specified on the command line). */
|
||||
|
||||
r = context_make_offline(&context, node);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = context_load_available_instances(context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!arg_offline) {
|
||||
r = context_load_available_instances(context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = context_discover_update_sets(context);
|
||||
if (r < 0)
|
||||
@@ -744,6 +779,30 @@ static int context_make_online(Context **ret, const char *node) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
|
||||
const Context *c = ASSERT_PTR(t->context);
|
||||
size_t i, n = c->n_transfers;
|
||||
uint64_t base, scaled;
|
||||
unsigned overall;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
if (c->transfers[i] == t)
|
||||
break;
|
||||
assert(i < n); /* We should have found the index */
|
||||
|
||||
base = (100 * 100 * i) / n;
|
||||
scaled = (100 * percentage) / n;
|
||||
overall = (unsigned) ((base + scaled) / 100);
|
||||
assert(overall <= 100);
|
||||
|
||||
log_debug("Transfer %" PRIu64 "/%zu is %u%% complete (%u%% overall).", i+1, n, percentage, overall);
|
||||
return sd_notifyf(/* unset= */ false, "X_SYSUPDATE_PROGRESS=%u\n"
|
||||
"X_SYSUPDATE_TRANSFERS_LEFT=%zu\n"
|
||||
"X_SYSUPDATE_TRANSFERS_DONE=%zu\n"
|
||||
"STATUS=Updating to '%s' (%u%% complete).",
|
||||
overall, n - i, i, inst->metadata.version, overall);
|
||||
}
|
||||
|
||||
static int context_apply(
|
||||
Context *c,
|
||||
const char *version,
|
||||
@@ -793,8 +852,10 @@ static int context_apply(
|
||||
|
||||
log_info("Selected update '%s' for install.", us->version);
|
||||
|
||||
(void) sd_notifyf(false,
|
||||
"STATUS=Making room for '%s'.", us->version);
|
||||
(void) sd_notifyf(/* unset= */ false,
|
||||
"READY=1\n"
|
||||
"X_SYSUPDATE_VERSION=%s\n"
|
||||
"STATUS=Making room for '%s'.", us->version, us->version);
|
||||
|
||||
/* Let's make some room. We make sure for each transfer we have one free space to fill. While
|
||||
* removing stuff we'll protect the version we are trying to acquire. Why that? Maybe an earlier
|
||||
@@ -809,14 +870,15 @@ static int context_apply(
|
||||
if (arg_sync)
|
||||
sync();
|
||||
|
||||
(void) sd_notifyf(false,
|
||||
"STATUS=Updating to '%s'.\n", us->version);
|
||||
(void) sd_notifyf(/* unset= */ false,
|
||||
"STATUS=Updating to '%s'.", us->version);
|
||||
|
||||
/* There should now be one instance picked for each transfer, and the order is the same */
|
||||
assert(us->n_instances == c->n_transfers);
|
||||
|
||||
for (size_t i = 0; i < c->n_transfers; i++) {
|
||||
r = transfer_acquire_instance(c->transfers[i], us->instances[i]);
|
||||
r = transfer_acquire_instance(c->transfers[i], us->instances[i],
|
||||
context_on_acquire_progress, c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@@ -824,6 +886,9 @@ static int context_apply(
|
||||
if (arg_sync)
|
||||
sync();
|
||||
|
||||
(void) sd_notifyf(/* unset= */ false,
|
||||
"STATUS=Installing '%s'.", us->version);
|
||||
|
||||
for (size_t i = 0; i < c->n_transfers; i++) {
|
||||
r = transfer_install_instance(c->transfers[i], us->instances[i], arg_root);
|
||||
if (r < 0)
|
||||
@@ -838,23 +903,6 @@ static int context_apply(
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int reboot_now(void) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL;
|
||||
int r;
|
||||
|
||||
r = sd_bus_open_system(&bus);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open bus connection: %m");
|
||||
|
||||
r = bus_call_method(bus, bus_login_mgr, "RebootWithFlags", &error, NULL, "t",
|
||||
(uint64_t) SD_LOGIND_ROOT_CHECK_INHIBITORS);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to issue reboot request: %s", bus_error_message(&error, r));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_image(
|
||||
bool ro,
|
||||
char **ret_mounted_dir,
|
||||
@@ -904,6 +952,7 @@ static int verb_list(int argc, char **argv, void *userdata) {
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
||||
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
|
||||
_cleanup_(context_freep) Context* context = NULL;
|
||||
_cleanup_strv_free_ char **appstream_urls = NULL;
|
||||
const char *version;
|
||||
int r;
|
||||
|
||||
@@ -920,8 +969,48 @@ static int verb_list(int argc, char **argv, void *userdata) {
|
||||
|
||||
if (version)
|
||||
return context_show_version(context, version);
|
||||
else
|
||||
else if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF))
|
||||
return context_show_table(context);
|
||||
else {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||
_cleanup_strv_free_ char **versions = NULL;
|
||||
const char *current = NULL;
|
||||
|
||||
FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) {
|
||||
UpdateSet *us = *update_set;
|
||||
|
||||
if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
|
||||
FLAGS_SET(us->flags, UPDATE_NEWEST))
|
||||
current = us->version;
|
||||
|
||||
r = strv_extend(&versions, us->version);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
FOREACH_ARRAY(tr, context->transfers, context->n_transfers)
|
||||
STRV_FOREACH(appstream_url, (*tr)->appstream) {
|
||||
/* Avoid duplicates */
|
||||
if (strv_contains(appstream_urls, *appstream_url))
|
||||
continue;
|
||||
|
||||
r = strv_extend(&appstream_urls, *appstream_url);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("current", current),
|
||||
SD_JSON_BUILD_PAIR_STRV("all", versions),
|
||||
SD_JSON_BUILD_PAIR_STRV("appstream_urls", appstream_urls));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create JSON: %m");
|
||||
|
||||
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to print JSON: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int verb_check_new(int argc, char **argv, void *userdata) {
|
||||
@@ -940,12 +1029,28 @@ static int verb_check_new(int argc, char **argv, void *userdata) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!context->candidate) {
|
||||
log_debug("No candidate found.");
|
||||
return EXIT_FAILURE;
|
||||
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
|
||||
if (!context->candidate) {
|
||||
log_debug("No candidate found.");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
puts(context->candidate->version);
|
||||
} else {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||
|
||||
if (context->candidate)
|
||||
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context->candidate->version));
|
||||
else
|
||||
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available"));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create JSON: %m");
|
||||
|
||||
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to print JSON: %m");
|
||||
}
|
||||
|
||||
puts(context->candidate->version);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1164,23 +1269,36 @@ static int verb_components(int argc, char **argv, void *userdata) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_default_component && set_isempty(names)) {
|
||||
log_info("No components defined.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
z = set_get_strv(names);
|
||||
if (!z)
|
||||
return log_oom();
|
||||
|
||||
strv_sort(z);
|
||||
|
||||
if (has_default_component)
|
||||
printf("%s<default>%s\n",
|
||||
ansi_highlight(), ansi_normal());
|
||||
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
|
||||
if (!has_default_component && set_isempty(names)) {
|
||||
log_info("No components defined.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
STRV_FOREACH(i, z)
|
||||
puts(*i);
|
||||
if (has_default_component)
|
||||
printf("%s<default>%s\n",
|
||||
ansi_highlight(), ansi_normal());
|
||||
|
||||
STRV_FOREACH(i, z)
|
||||
puts(*i);
|
||||
} else {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||
|
||||
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
|
||||
SD_JSON_BUILD_PAIR_STRV("components", z));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create JSON: %m");
|
||||
|
||||
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to print JSON: %m");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1217,6 +1335,7 @@ static int verb_help(int argc, char **argv, void *userdata) {
|
||||
" --sync=BOOL Controls whether to sync data to disk\n"
|
||||
" --verify=BOOL Force signature verification on or off\n"
|
||||
" --reboot Reboot after updating to newer version\n"
|
||||
" --offline Do not fetch metadata from the network\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --no-legend Do not show the headers and footers\n"
|
||||
" --json=pretty|short|off\n"
|
||||
@@ -1246,6 +1365,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_IMAGE_POLICY,
|
||||
ARG_REBOOT,
|
||||
ARG_VERIFY,
|
||||
ARG_OFFLINE,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@@ -1263,6 +1383,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "reboot", no_argument, NULL, ARG_REBOOT },
|
||||
{ "component", required_argument, NULL, 'C' },
|
||||
{ "verify", required_argument, NULL, ARG_VERIFY },
|
||||
{ "offline", no_argument, NULL, ARG_OFFLINE },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -1366,6 +1487,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_OFFLINE:
|
||||
arg_offline = true;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
@@ -1412,6 +1537,9 @@ static int run(int argc, char *argv[]) {
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
/* SIGCHLD signal must be blocked for sd_event_add_child to work */
|
||||
BLOCK_SIGNALS(SIGCHLD);
|
||||
|
||||
return sysupdate_main(argc, argv);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* Forward declare this type so that Transfers can point at it */
|
||||
typedef struct Context Context;
|
||||
|
||||
extern bool arg_sync;
|
||||
extern uint64_t arg_instances_max;
|
||||
extern char *arg_root;
|
||||
|
||||
Reference in New Issue
Block a user