From 8db5e9b65707741f482a1e37cdfe13a826498faf Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Thu, 22 Jun 2023 19:47:20 -0400 Subject: [PATCH 1/7] sysupdate: Report download progress via sd_notify We set up a NOTIFY_SOCKET to get download progress notifications from each individual import helper. Along with the number of import jobs we have to run, this gives an overall progress value which we report using sd_notify --- src/sysupdate/sysupdate-transfer.c | 247 +++++++++++++++++++++++++++-- src/sysupdate/sysupdate-transfer.h | 9 +- src/sysupdate/sysupdate.c | 46 +++++- src/sysupdate/sysupdate.h | 3 + 4 files changed, 283 insertions(+), 22 deletions(-) diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index 435c38020e..3756798193 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" @@ -53,7 +58,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 +83,8 @@ Transfer *transfer_new(void) { .install_read_only = -1, .partition_info = PARTITION_INFO_NULL, + + .context = ctx, }; return t; @@ -784,30 +791,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 +1030,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 +1126,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 +1143,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 +1167,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 +1184,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 +1204,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 +1222,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 +1244,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..41a849430a 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; @@ -44,9 +45,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 +62,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.c b/src/sysupdate/sysupdate.c index 9a8915c3fe..de90506c50 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -25,6 +25,7 @@ #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" @@ -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(); @@ -744,6 +745,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 +818,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 +836,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 +852,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) @@ -1412,6 +1443,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; From c0d61862274694763e1e332e7498548d6040ca9f Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Tue, 11 Jul 2023 18:35:54 -0400 Subject: [PATCH 2/7] sysupdate: Add --offline mode This prevents sysupdate from going out to the network to enumerate available instances. When combined with the list command, this lets us query installed instances --- man/systemd-sysupdate.xml | 10 ++++++++++ src/sysupdate/sysupdate.c | 28 ++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 8 deletions(-) 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/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index de90506c50..462ae5ee01 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -48,6 +48,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); @@ -356,11 +357,13 @@ 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; @@ -727,15 +730,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) @@ -1248,6 +1253,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" @@ -1277,6 +1283,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_IMAGE_POLICY, ARG_REBOOT, ARG_VERIFY, + ARG_OFFLINE, }; static const struct option options[] = { @@ -1294,6 +1301,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 }, {} }; @@ -1397,6 +1405,10 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_OFFLINE: + arg_offline = true; + break; + case '?': return -EINVAL; From 42c0b689a800b2ec7cceb1528d3834bf9b3417f8 Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Tue, 11 Jul 2023 18:46:11 -0400 Subject: [PATCH 3/7] sysupdate: Implement JSON output Previously, the JSON output happened mostly as an accident (i.e. just dumped tables intended for viewing). Now we have more complete JSON output. --- src/sysupdate/sysupdate.c | 149 ++++++++++++++++++++++++++++++++------ 1 file changed, 128 insertions(+), 21 deletions(-) diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 462ae5ee01..f3af1a306a 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -15,6 +15,7 @@ #include "format-table.h" #include "glyph-util.h" #include "hexdecoct.h" +#include "json-util.h" #include "login-util.h" #include "main-func.h" #include "mount-util.h" @@ -423,7 +424,6 @@ static const char *update_set_flags_to_string(UpdateSetFlags flags) { } } - static int context_show_table(Context *c) { _cleanup_(table_unrefp) Table *t = NULL; int r; @@ -478,6 +478,7 @@ 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; UpdateSet *us; int r; @@ -665,7 +666,44 @@ 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\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()); + + 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_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( @@ -690,10 +728,22 @@ static int context_vacuum( 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; } @@ -956,8 +1006,36 @@ 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; + + for (size_t i = 0; i < context->n_update_sets; i++) { + UpdateSet *us = context->update_sets[i]; + + 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(); + } + + r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("current", current), + SD_JSON_BUILD_PAIR_STRV("all", versions)); + 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) { @@ -976,12 +1054,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; } @@ -1200,23 +1294,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; } From db8849f2d4ab24c767f806f324587939e7f5c8e6 Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Wed, 19 Jul 2023 14:43:58 -0400 Subject: [PATCH 4/7] sysupdate: Support changelogs & appstream metadata Makes it possible to specify URLs to a changelog and an appstream catalog XML in the sysupdate.d/*.conf files. This will be passed along to the clients of systemd-sysupdated, which can then present this data. --- docs/APPSTREAM_BUNDLE.md | 118 +++++++++++++++++++++++++++++ man/sysupdate.d.xml | 32 ++++++++ src/sysupdate/sysupdate-transfer.c | 47 ++++++++++++ src/sysupdate/sysupdate-transfer.h | 3 + src/sysupdate/sysupdate.c | 60 +++++++++++++-- 5 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 docs/APPSTREAM_BUNDLE.md 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/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/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index 3756798193..b1b3c662f6 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -50,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); @@ -168,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, @@ -431,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 }, diff --git a/src/sysupdate/sysupdate-transfer.h b/src/sysupdate/sysupdate-transfer.h index 41a849430a..c28b68cc92 100644 --- a/src/sysupdate/sysupdate-transfer.h +++ b/src/sysupdate/sysupdate-transfer.h @@ -26,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; diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index f3af1a306a..000f6fa767 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -162,8 +162,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; } @@ -480,6 +482,7 @@ static int context_show_version(Context *c, const char *version) { 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; @@ -521,13 +524,30 @@ 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++) { @@ -666,13 +686,14 @@ static int context_show_version(Context *c, const char *version) { if (!have_sha256) (void) table_hide_column_from_display(t, 12); + 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\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)" : "", @@ -680,6 +701,15 @@ static int context_show_version(Context *c, const char *version) { 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; @@ -694,6 +724,7 @@ static int context_show_version(Context *c, const char *version) { 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"); @@ -990,6 +1021,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; @@ -1013,8 +1045,8 @@ static int verb_list(int argc, char **argv, void *userdata) { _cleanup_strv_free_ char **versions = NULL; const char *current = NULL; - for (size_t i = 0; i < context->n_update_sets; i++) { - UpdateSet *us = context->update_sets[i]; + 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)) @@ -1025,8 +1057,20 @@ static int verb_list(int argc, char **argv, void *userdata) { 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("all", versions), + SD_JSON_BUILD_PAIR_STRV("appstream_urls", appstream_urls)); if (r < 0) return log_error_errno(r, "Failed to create JSON: %m"); From 42e46597809941ed68742c43d9c15886bf3b8ee4 Mon Sep 17 00:00:00 2001 From: Tom Coldrick Date: Thu, 27 Jun 2024 12:45:17 +0100 Subject: [PATCH 5/7] sysupdate: Use FOREACH_ARRAY where possible Replace simple loops over arrays with the FOREACH_ARRAY macro. --- src/sysupdate/sysupdate.c | 58 +++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 000f6fa767..b94e2244d7 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -73,12 +73,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); @@ -180,10 +180,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; @@ -199,12 +201,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; @@ -242,9 +245,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 @@ -300,12 +302,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; @@ -440,8 +443,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); @@ -468,9 +471,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; } @@ -550,9 +553,8 @@ static int context_show_version(Context *c, const char *version) { } } - 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); @@ -751,8 +753,10 @@ 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; From 3f79921c08f75d1ed31290b64693a4572bb00db0 Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Thu, 13 Jul 2023 14:47:30 -0400 Subject: [PATCH 6/7] sysupdate: Split UpdateSetFlags out from UpdateSet This will let us reuse UpdateSetFlags in updatectl --- src/sysupdate/meson.build | 1 + src/sysupdate/sysupdate-update-set-flags.c | 93 ++++++++++++++++++++++ src/sysupdate/sysupdate-update-set-flags.h | 14 ++++ src/sysupdate/sysupdate-update-set.c | 36 --------- src/sysupdate/sysupdate-update-set.h | 13 +-- src/sysupdate/sysupdate.c | 54 ------------- 6 files changed, 109 insertions(+), 102 deletions(-) create mode 100644 src/sysupdate/sysupdate-update-set-flags.c create mode 100644 src/sysupdate/sysupdate-update-set-flags.h diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build index 417b47a136..3e50c42afe 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -8,6 +8,7 @@ systemd_sysupdate_sources = files( 'sysupdate-resource.c', 'sysupdate-transfer.c', 'sysupdate-update-set.c', + 'sysupdate-update-set-flags.c', 'sysupdate.c', ) 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.c b/src/sysupdate/sysupdate.c index b94e2244d7..e2f154d0e8 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -375,60 +375,6 @@ static int context_discover_update_sets(Context *c) { 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; From 8ba9a9ac9678ccd1cf4b3f9234f68c03de1a3ba2 Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Wed, 19 Jul 2023 17:55:18 -0400 Subject: [PATCH 7/7] sysupdate: Split reboot_now into utils This will let us reuse reboot_now in updatectl --- src/sysupdate/meson.build | 1 + src/sysupdate/sysupdate-util.c | 24 +++++++++++++++++++++++- src/sysupdate/sysupdate-util.h | 5 +++++ src/sysupdate/sysupdate.c | 23 ++--------------------- 4 files changed, 31 insertions(+), 22 deletions(-) create mode 100644 src/sysupdate/sysupdate-util.h diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build index 3e50c42afe..b1b1204a2a 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -9,6 +9,7 @@ systemd_sysupdate_sources = files( 'sysupdate-transfer.c', 'sysupdate-update-set.c', 'sysupdate-update-set-flags.c', + 'sysupdate-util.c', 'sysupdate.c', ) 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 e2f154d0e8..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" @@ -16,7 +14,6 @@ #include "glyph-util.h" #include "hexdecoct.h" #include "json-util.h" -#include "login-util.h" #include "main-func.h" #include "mount-util.h" #include "os-util.h" @@ -30,9 +27,10 @@ #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" @@ -905,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,