1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "bus-locator.h"
10 #include "conf-files.h"
11 #include "constants.h"
12 #include "dirent-util.h"
13 #include "dissect-image.h"
15 #include "format-table.h"
16 #include "glyph-util.h"
17 #include "hexdecoct.h"
18 #include "login-util.h"
19 #include "main-func.h"
20 #include "mount-util.h"
23 #include "parse-argument.h"
24 #include "parse-util.h"
25 #include "path-util.h"
26 #include "pretty-print.h"
28 #include "sort-util.h"
29 #include "string-util.h"
31 #include "sysupdate-transfer.h"
32 #include "sysupdate-update-set.h"
33 #include "sysupdate.h"
34 #include "terminal-util.h"
38 static char *arg_definitions
= NULL
;
40 uint64_t arg_instances_max
= UINT64_MAX
;
41 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
42 static PagerFlags arg_pager_flags
= 0;
43 static bool arg_legend
= true;
44 char *arg_root
= NULL
;
45 static char *arg_image
= NULL
;
46 static bool arg_reboot
= false;
47 static char *arg_component
= NULL
;
48 static int arg_verify
= -1;
49 static ImagePolicy
*arg_image_policy
= NULL
;
51 STATIC_DESTRUCTOR_REGISTER(arg_definitions
, freep
);
52 STATIC_DESTRUCTOR_REGISTER(arg_root
, freep
);
53 STATIC_DESTRUCTOR_REGISTER(arg_image
, freep
);
54 STATIC_DESTRUCTOR_REGISTER(arg_component
, freep
);
55 STATIC_DESTRUCTOR_REGISTER(arg_image_policy
, image_policy_freep
);
57 typedef struct Context
{
61 UpdateSet
**update_sets
;
64 UpdateSet
*newest_installed
, *candidate
;
66 Hashmap
*web_cache
; /* Cache for downloaded resources, keyed by URL */
69 static Context
*context_free(Context
*c
) {
73 for (size_t i
= 0; i
< c
->n_transfers
; i
++)
74 transfer_free(c
->transfers
[i
]);
77 for (size_t i
= 0; i
< c
->n_update_sets
; i
++)
78 update_set_free(c
->update_sets
[i
]);
81 hashmap_free(c
->web_cache
);
86 DEFINE_TRIVIAL_CLEANUP_FUNC(Context
*, context_free
);
88 static Context
*context_new(void) {
89 /* For now, no fields to initialize non-zero */
90 return new0(Context
, 1);
93 static int context_read_definitions(
95 const char *directory
,
96 const char *component
,
100 _cleanup_strv_free_
char **files
= NULL
;
106 r
= conf_files_list_strv(&files
, ".conf", NULL
, CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
, (const char**) STRV_MAKE(directory
));
107 else if (component
) {
108 _cleanup_strv_free_
char **n
= NULL
;
109 char **l
= CONF_PATHS_STRV("");
112 n
= new0(char*, strv_length(l
) + 1);
119 j
= strjoin(*i
, "sysupdate.", component
, ".d");
126 r
= conf_files_list_strv(&files
, ".conf", root
, CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
, (const char**) n
);
128 r
= conf_files_list_strv(&files
, ".conf", root
, CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
, (const char**) CONF_PATHS_STRV("sysupdate.d"));
130 return log_error_errno(r
, "Failed to enumerate *.conf files: %m");
132 STRV_FOREACH(f
, files
) {
133 _cleanup_(transfer_freep
) Transfer
*t
= NULL
;
135 if (!GREEDY_REALLOC(c
->transfers
, c
->n_transfers
+ 1))
142 t
->definition_path
= strdup(*f
);
143 if (!t
->definition_path
)
146 r
= transfer_read_definition(t
, *f
);
150 c
->transfers
[c
->n_transfers
++] = TAKE_PTR(t
);
153 if (c
->n_transfers
== 0) {
155 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
),
156 "No transfer definitions for component '%s' found.", arg_component
);
158 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
),
159 "No transfer definitions found.");
162 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
163 r
= transfer_resolve_paths(c
->transfers
[i
], root
, node
);
171 static int context_load_installed_instances(Context
*c
) {
176 log_info("Discovering installed instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
178 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
179 r
= resource_load_instances(
180 &c
->transfers
[i
]->target
,
181 arg_verify
>= 0 ? arg_verify
: c
->transfers
[i
]->verify
,
190 static int context_load_available_instances(Context
*c
) {
195 log_info("Discovering available instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
197 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
198 assert(c
->transfers
[i
]);
200 r
= resource_load_instances(
201 &c
->transfers
[i
]->source
,
202 arg_verify
>= 0 ? arg_verify
: c
->transfers
[i
]->verify
,
211 static int context_discover_update_sets_by_flag(Context
*c
, UpdateSetFlags flags
) {
212 _cleanup_free_ Instance
**cursor_instances
= NULL
;
213 _cleanup_free_
char *boundary
= NULL
;
214 bool newest_found
= false;
218 assert(IN_SET(flags
, UPDATE_AVAILABLE
, UPDATE_INSTALLED
));
221 bool incomplete
= false, exists
= false;
222 UpdateSetFlags extra_flags
= 0;
223 _cleanup_free_
char *cursor
= NULL
;
224 UpdateSet
*us
= NULL
;
226 for (size_t k
= 0; k
< c
->n_transfers
; k
++) {
227 Transfer
*t
= c
->transfers
[k
];
228 bool cursor_found
= false;
233 if (flags
== UPDATE_AVAILABLE
)
236 assert(flags
== UPDATE_INSTALLED
);
240 for (size_t j
= 0; j
< rr
->n_instances
; j
++) {
241 Instance
*i
= rr
->instances
[j
];
245 /* Is the instance we are looking at equal or newer than the boundary? If so, we
246 * already checked this version, and it wasn't complete, let's ignore it. */
247 if (boundary
&& strverscmp_improved(i
->metadata
.version
, boundary
) >= 0)
251 if (strverscmp_improved(i
->metadata
.version
, cursor
) != 0)
254 cursor
= strdup(i
->metadata
.version
);
261 if (!cursor_instances
) {
262 cursor_instances
= new(Instance
*, c
->n_transfers
);
263 if (!cursor_instances
)
266 cursor_instances
[k
] = i
;
270 if (!cursor
) /* No suitable instance beyond the boundary found? Then we are done! */
274 /* Hmm, we didn't find the version indicated by 'cursor' among the instances
275 * of this transfer, let's skip it. */
280 if (t
->min_version
&& strverscmp_improved(t
->min_version
, cursor
) > 0)
281 extra_flags
|= UPDATE_OBSOLETE
;
283 if (strv_contains(t
->protected_versions
, cursor
))
284 extra_flags
|= UPDATE_PROTECTED
;
287 if (!cursor
) /* EOL */
290 r
= free_and_strdup_warn(&boundary
, cursor
);
294 if (incomplete
) /* One transfer was missing this version, ignore the whole thing */
297 /* See if we already have this update set in our table */
298 for (size_t i
= 0; i
< c
->n_update_sets
; i
++) {
299 if (strverscmp_improved(c
->update_sets
[i
]->version
, cursor
) != 0)
302 /* We only store the instances we found first, but we remember we also found it again */
303 c
->update_sets
[i
]->flags
|= flags
| extra_flags
;
312 /* Doesn't exist yet, let's add it */
313 if (!GREEDY_REALLOC(c
->update_sets
, c
->n_update_sets
+ 1))
316 us
= new(UpdateSet
, 1);
321 .flags
= flags
| (newest_found
? 0 : UPDATE_NEWEST
) | extra_flags
,
322 .version
= TAKE_PTR(cursor
),
323 .instances
= TAKE_PTR(cursor_instances
),
324 .n_instances
= c
->n_transfers
,
327 c
->update_sets
[c
->n_update_sets
++] = us
;
331 /* Remember which one is the newest installed */
332 if ((us
->flags
& (UPDATE_NEWEST
|UPDATE_INSTALLED
)) == (UPDATE_NEWEST
|UPDATE_INSTALLED
))
333 c
->newest_installed
= us
;
335 /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate" */
336 if ((us
->flags
& (UPDATE_NEWEST
|UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_OBSOLETE
)) == (UPDATE_NEWEST
|UPDATE_AVAILABLE
))
340 /* Newest installed is newer than or equal to candidate? Then suppress the candidate */
341 if (c
->newest_installed
&& c
->candidate
&& strverscmp_improved(c
->newest_installed
->version
, c
->candidate
->version
) >= 0)
347 static int context_discover_update_sets(Context
*c
) {
352 log_info("Determining installed update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
354 r
= context_discover_update_sets_by_flag(c
, UPDATE_INSTALLED
);
358 log_info("Determining available update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
360 r
= context_discover_update_sets_by_flag(c
, UPDATE_AVAILABLE
);
364 typesafe_qsort(c
->update_sets
, c
->n_update_sets
, update_set_cmp
);
368 static const char *update_set_flags_to_string(UpdateSetFlags flags
) {
370 switch ((unsigned) flags
) {
375 case UPDATE_INSTALLED
|UPDATE_NEWEST
:
376 case UPDATE_INSTALLED
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
377 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_NEWEST
:
378 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
381 case UPDATE_AVAILABLE
|UPDATE_NEWEST
:
382 case UPDATE_AVAILABLE
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
385 case UPDATE_INSTALLED
:
386 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
:
389 case UPDATE_INSTALLED
|UPDATE_PROTECTED
:
390 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_PROTECTED
:
393 case UPDATE_AVAILABLE
:
394 case UPDATE_AVAILABLE
|UPDATE_PROTECTED
:
397 case UPDATE_INSTALLED
|UPDATE_OBSOLETE
|UPDATE_NEWEST
:
398 case UPDATE_INSTALLED
|UPDATE_OBSOLETE
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
399 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_NEWEST
:
400 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
401 return "current+obsolete";
403 case UPDATE_INSTALLED
|UPDATE_OBSOLETE
:
404 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_OBSOLETE
:
405 return "installed+obsolete";
407 case UPDATE_INSTALLED
|UPDATE_OBSOLETE
|UPDATE_PROTECTED
:
408 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_PROTECTED
:
409 return "protected+obsolete";
411 case UPDATE_AVAILABLE
|UPDATE_OBSOLETE
:
412 case UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_PROTECTED
:
413 case UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_NEWEST
:
414 case UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
415 return "available+obsolete";
418 assert_not_reached();
423 static int context_show_table(Context
*c
) {
424 _cleanup_(table_unrefp
) Table
*t
= NULL
;
429 t
= table_new("", "version", "installed", "available", "assessment");
433 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 0), 100);
434 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 2), 50);
435 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 3), 50);
437 for (size_t i
= 0; i
< c
->n_update_sets
; i
++) {
438 UpdateSet
*us
= c
->update_sets
[i
];
441 color
= update_set_flags_to_color(us
->flags
);
443 r
= table_add_many(t
,
444 TABLE_STRING
, update_set_flags_to_glyph(us
->flags
),
445 TABLE_SET_COLOR
, color
,
446 TABLE_STRING
, us
->version
,
447 TABLE_SET_COLOR
, color
,
448 TABLE_STRING
, special_glyph_check_mark_space(FLAGS_SET(us
->flags
, UPDATE_INSTALLED
)),
449 TABLE_SET_COLOR
, color
,
450 TABLE_STRING
, special_glyph_check_mark_space(FLAGS_SET(us
->flags
, UPDATE_AVAILABLE
)),
451 TABLE_SET_COLOR
, color
,
452 TABLE_STRING
, update_set_flags_to_string(us
->flags
),
453 TABLE_SET_COLOR
, color
);
455 return table_log_add_error(r
);
458 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
461 static UpdateSet
*context_update_set_by_version(Context
*c
, const char *version
) {
465 for (size_t i
= 0; i
< c
->n_update_sets
; i
++)
466 if (streq(c
->update_sets
[i
]->version
, version
))
467 return c
->update_sets
[i
];
472 static int context_show_version(Context
*c
, const char *version
) {
473 bool show_fs_columns
= false, show_partition_columns
= false,
474 have_fs_attributes
= false, have_partition_attributes
= false,
475 have_size
= false, have_tries
= false, have_no_auto
= false,
476 have_read_only
= false, have_growfs
= false, have_sha256
= false;
477 _cleanup_(table_unrefp
) Table
*t
= NULL
;
484 us
= context_update_set_by_version(c
, version
);
486 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "Update '%s' not found.", version
);
488 if (arg_json_format_flags
& (JSON_FORMAT_OFF
|JSON_FORMAT_PRETTY
|JSON_FORMAT_PRETTY_AUTO
))
489 (void) pager_open(arg_pager_flags
);
491 if (FLAGS_SET(arg_json_format_flags
, JSON_FORMAT_OFF
))
492 printf("%s%s%s Version: %s\n"
496 "Protected: %s%s%s\n"
497 " Obsolete: %s%s%s\n\n",
498 strempty(update_set_flags_to_color(us
->flags
)), update_set_flags_to_glyph(us
->flags
), ansi_normal(), us
->version
,
499 strempty(update_set_flags_to_color(us
->flags
)), update_set_flags_to_string(us
->flags
), ansi_normal(),
500 yes_no(us
->flags
& UPDATE_INSTALLED
), FLAGS_SET(us
->flags
, UPDATE_INSTALLED
|UPDATE_NEWEST
) ? " (newest)" : "",
501 yes_no(us
->flags
& UPDATE_AVAILABLE
), (us
->flags
& (UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_NEWEST
)) == (UPDATE_AVAILABLE
|UPDATE_NEWEST
) ? " (newest)" : "",
502 FLAGS_SET(us
->flags
, UPDATE_INSTALLED
|UPDATE_PROTECTED
) ? ansi_highlight() : "", yes_no(FLAGS_SET(us
->flags
, UPDATE_INSTALLED
|UPDATE_PROTECTED
)), ansi_normal(),
503 us
->flags
& UPDATE_OBSOLETE
? ansi_highlight_red() : "", yes_no(us
->flags
& UPDATE_OBSOLETE
), ansi_normal());
506 t
= table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
510 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 3), 100);
511 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 4), 100);
512 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 5), 100);
513 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 6), 100);
514 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 7), 100);
515 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 8), 100);
516 table_set_ersatz_string(t
, TABLE_ERSATZ_DASH
);
518 /* Determine if the target will make use of partition/fs attributes for any of the transfers */
519 for (size_t n
= 0; n
< c
->n_transfers
; n
++) {
520 Transfer
*tr
= c
->transfers
[n
];
522 if (tr
->target
.type
== RESOURCE_PARTITION
)
523 show_partition_columns
= true;
524 if (RESOURCE_IS_FILESYSTEM(tr
->target
.type
))
525 show_fs_columns
= true;
528 for (size_t n
= 0; n
< us
->n_instances
; n
++) {
529 Instance
*i
= us
->instances
[n
];
531 r
= table_add_many(t
,
532 TABLE_STRING
, resource_type_to_string(i
->resource
->type
),
533 TABLE_PATH
, i
->path
);
535 return table_log_add_error(r
);
537 if (i
->metadata
.partition_uuid_set
) {
538 have_partition_attributes
= true;
539 r
= table_add_cell(t
, NULL
, TABLE_UUID
, &i
->metadata
.partition_uuid
);
541 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
543 return table_log_add_error(r
);
545 if (i
->metadata
.partition_flags_set
) {
546 have_partition_attributes
= true;
547 r
= table_add_cell(t
, NULL
, TABLE_UINT64_HEX
, &i
->metadata
.partition_flags
);
549 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
551 return table_log_add_error(r
);
553 if (i
->metadata
.mtime
!= USEC_INFINITY
) {
554 have_fs_attributes
= true;
555 r
= table_add_cell(t
, NULL
, TABLE_TIMESTAMP
, &i
->metadata
.mtime
);
557 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
559 return table_log_add_error(r
);
561 if (i
->metadata
.mode
!= MODE_INVALID
) {
562 have_fs_attributes
= true;
563 r
= table_add_cell(t
, NULL
, TABLE_MODE
, &i
->metadata
.mode
);
565 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
567 return table_log_add_error(r
);
569 if (i
->metadata
.size
!= UINT64_MAX
) {
571 r
= table_add_cell(t
, NULL
, TABLE_SIZE
, &i
->metadata
.size
);
573 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
575 return table_log_add_error(r
);
577 if (i
->metadata
.tries_done
!= UINT64_MAX
) {
579 r
= table_add_cell(t
, NULL
, TABLE_UINT64
, &i
->metadata
.tries_done
);
581 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
583 return table_log_add_error(r
);
585 if (i
->metadata
.tries_left
!= UINT64_MAX
) {
587 r
= table_add_cell(t
, NULL
, TABLE_UINT64
, &i
->metadata
.tries_left
);
589 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
591 return table_log_add_error(r
);
593 if (i
->metadata
.no_auto
>= 0) {
597 b
= i
->metadata
.no_auto
;
598 r
= table_add_cell(t
, NULL
, TABLE_BOOLEAN
, &b
);
600 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
602 return table_log_add_error(r
);
603 if (i
->metadata
.read_only
>= 0) {
606 have_read_only
= true;
607 b
= i
->metadata
.read_only
;
608 r
= table_add_cell(t
, NULL
, TABLE_BOOLEAN
, &b
);
610 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
612 return table_log_add_error(r
);
614 if (i
->metadata
.growfs
>= 0) {
618 b
= i
->metadata
.growfs
;
619 r
= table_add_cell(t
, NULL
, TABLE_BOOLEAN
, &b
);
621 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
623 return table_log_add_error(r
);
625 if (i
->metadata
.sha256sum_set
) {
626 _cleanup_free_
char *formatted
= NULL
;
630 formatted
= hexmem(i
->metadata
.sha256sum
, sizeof(i
->metadata
.sha256sum
));
634 r
= table_add_cell(t
, NULL
, TABLE_STRING
, formatted
);
636 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
638 return table_log_add_error(r
);
641 /* Hide the fs/partition columns if we don't have any data to show there */
642 if (!have_fs_attributes
)
643 show_fs_columns
= false;
644 if (!have_partition_attributes
)
645 show_partition_columns
= false;
647 if (!show_partition_columns
)
648 (void) table_hide_column_from_display(t
, 2, 3);
649 if (!show_fs_columns
)
650 (void) table_hide_column_from_display(t
, 4, 5);
652 (void) table_hide_column_from_display(t
, 6);
654 (void) table_hide_column_from_display(t
, 7, 8);
656 (void) table_hide_column_from_display(t
, 9);
658 (void) table_hide_column_from_display(t
, 10);
660 (void) table_hide_column_from_display(t
, 11);
662 (void) table_hide_column_from_display(t
, 12);
664 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
667 static int context_vacuum(
670 const char *extra_protected_version
) {
677 log_info("Making room%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
679 log_info("Making room for %" PRIu64
" updates%s", space
,special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
681 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
682 r
= transfer_vacuum(c
->transfers
[i
], space
, extra_protected_version
);
686 count
= MAX(count
, r
);
690 log_info("Removed %i instances.", count
);
692 log_info("Removed no instances.");
697 static int context_make_offline(Context
**ret
, const char *node
) {
698 _cleanup_(context_freep
) Context
* context
= NULL
;
703 /* Allocates a context object and initializes everything we can initialize offline, i.e. without
704 * checking on the update source (i.e. the Internet) what versions are available */
706 context
= context_new();
710 r
= context_read_definitions(context
, arg_definitions
, arg_component
, arg_root
, node
);
714 r
= context_load_installed_instances(context
);
718 *ret
= TAKE_PTR(context
);
722 static int context_make_online(Context
**ret
, const char *node
) {
723 _cleanup_(context_freep
) Context
* context
= NULL
;
728 /* Like context_make_offline(), but also communicates with the update source looking for new
731 r
= context_make_offline(&context
, node
);
735 r
= context_load_available_instances(context
);
739 r
= context_discover_update_sets(context
);
743 *ret
= TAKE_PTR(context
);
747 static int context_apply(
750 UpdateSet
**ret_applied
) {
752 UpdateSet
*us
= NULL
;
758 us
= context_update_set_by_version(c
, version
);
760 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "Update '%s' not found.", version
);
763 log_info("No update needed.");
774 if (FLAGS_SET(us
->flags
, UPDATE_INSTALLED
)) {
775 log_info("Selected update '%s' is already installed. Skipping update.", us
->version
);
782 if (!FLAGS_SET(us
->flags
, UPDATE_AVAILABLE
))
783 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Selected update '%s' is not available, refusing.", us
->version
);
784 if (FLAGS_SET(us
->flags
, UPDATE_OBSOLETE
))
785 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Selected update '%s' is obsolete, refusing.", us
->version
);
787 assert((us
->flags
& (UPDATE_AVAILABLE
|UPDATE_INSTALLED
|UPDATE_OBSOLETE
)) == UPDATE_AVAILABLE
);
789 if (!FLAGS_SET(us
->flags
, UPDATE_NEWEST
))
790 log_notice("Selected update '%s' is not the newest, proceeding anyway.", us
->version
);
791 if (c
->newest_installed
&& strverscmp_improved(c
->newest_installed
->version
, us
->version
) > 0)
792 log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us
->version
);
794 log_info("Selected update '%s' for install.", us
->version
);
796 (void) sd_notifyf(false,
797 "STATUS=Making room for '%s'.", us
->version
);
799 /* Let's make some room. We make sure for each transfer we have one free space to fill. While
800 * removing stuff we'll protect the version we are trying to acquire. Why that? Maybe an earlier
801 * download succeeded already, in which case we shouldn't remove it just to acquire it again */
805 /* extra_protected_version = */ us
->version
);
812 (void) sd_notifyf(false,
813 "STATUS=Updating to '%s'.\n", us
->version
);
815 /* There should now be one instance picked for each transfer, and the order is the same */
816 assert(us
->n_instances
== c
->n_transfers
);
818 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
819 r
= transfer_acquire_instance(c
->transfers
[i
], us
->instances
[i
]);
827 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
828 r
= transfer_install_instance(c
->transfers
[i
], us
->instances
[i
], arg_root
);
833 log_info("%s Successfully installed update '%s'.", special_glyph(SPECIAL_GLYPH_SPARKLES
), us
->version
);
841 static int reboot_now(void) {
842 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
843 _cleanup_(sd_bus_close_unrefp
) sd_bus
*bus
= NULL
;
846 r
= sd_bus_open_system(&bus
);
848 return log_error_errno(r
, "Failed to open bus connection: %m");
850 r
= bus_call_method(bus
, bus_login_mgr
, "RebootWithFlags", &error
, NULL
, "t",
851 (uint64_t) SD_LOGIND_ROOT_CHECK_INHIBITORS
);
853 return log_error_errno(r
, "Failed to issue reboot request: %s", bus_error_message(&error
, r
));
858 static int process_image(
860 char **ret_mounted_dir
,
861 LoopDevice
**ret_loop_device
) {
863 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
864 _cleanup_(umount_and_freep
) char *mounted_dir
= NULL
;
867 assert(ret_mounted_dir
);
868 assert(ret_loop_device
);
875 r
= mount_image_privately_interactively(
878 (ro
? DISSECT_IMAGE_READ_ONLY
: 0) |
880 DISSECT_IMAGE_MKDIR
|
881 DISSECT_IMAGE_GROWFS
|
882 DISSECT_IMAGE_RELAX_VAR_CHECK
|
883 DISSECT_IMAGE_USR_NO_ROOT
|
884 DISSECT_IMAGE_GENERIC_ROOT
|
885 DISSECT_IMAGE_REQUIRE_ROOT
|
886 DISSECT_IMAGE_ALLOW_USERSPACE_VERITY
,
888 /* ret_dir_fd= */ NULL
,
893 arg_root
= strdup(mounted_dir
);
897 *ret_mounted_dir
= TAKE_PTR(mounted_dir
);
898 *ret_loop_device
= TAKE_PTR(loop_device
);
903 static int verb_list(int argc
, char **argv
, void *userdata
) {
904 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
905 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
906 _cleanup_(context_freep
) Context
* context
= NULL
;
911 version
= argc
>= 2 ? argv
[1] : NULL
;
913 r
= process_image(/* ro= */ true, &mounted_dir
, &loop_device
);
917 r
= context_make_online(&context
, loop_device
? loop_device
->node
: NULL
);
922 return context_show_version(context
, version
);
924 return context_show_table(context
);
927 static int verb_check_new(int argc
, char **argv
, void *userdata
) {
928 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
929 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
930 _cleanup_(context_freep
) Context
* context
= NULL
;
935 r
= process_image(/* ro= */ true, &mounted_dir
, &loop_device
);
939 r
= context_make_online(&context
, loop_device
? loop_device
->node
: NULL
);
943 if (!context
->candidate
) {
944 log_debug("No candidate found.");
948 puts(context
->candidate
->version
);
952 static int verb_vacuum(int argc
, char **argv
, void *userdata
) {
953 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
954 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
955 _cleanup_(context_freep
) Context
* context
= NULL
;
960 r
= process_image(/* ro= */ false, &mounted_dir
, &loop_device
);
964 r
= context_make_offline(&context
, loop_device
? loop_device
->node
: NULL
);
968 return context_vacuum(context
, 0, NULL
);
971 static int verb_update(int argc
, char **argv
, void *userdata
) {
972 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
973 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
974 _cleanup_(context_freep
) Context
* context
= NULL
;
975 _cleanup_free_
char *booted_version
= NULL
;
976 UpdateSet
*applied
= NULL
;
981 version
= argc
>= 2 ? argv
[1] : NULL
;
984 /* If automatic reboot on completion is requested, let's first determine the currently booted image */
986 r
= parse_os_release(arg_root
, "IMAGE_VERSION", &booted_version
);
988 return log_error_errno(r
, "Failed to parse /etc/os-release: %m");
990 return log_error_errno(SYNTHETIC_ERRNO(ENODATA
), "/etc/os-release lacks IMAGE_VERSION field.");
993 r
= process_image(/* ro= */ false, &mounted_dir
, &loop_device
);
997 r
= context_make_online(&context
, loop_device
? loop_device
->node
: NULL
);
1001 r
= context_apply(context
, version
, &applied
);
1005 if (r
> 0 && arg_reboot
) {
1007 assert(booted_version
);
1009 if (strverscmp_improved(applied
->version
, booted_version
) > 0) {
1010 log_notice("Newly installed version is newer than booted version, rebooting.");
1011 return reboot_now();
1014 log_info("Booted version is newer or identical to newly installed version, not rebooting.");
1020 static int verb_pending_or_reboot(int argc
, char **argv
, void *userdata
) {
1021 _cleanup_(context_freep
) Context
* context
= NULL
;
1022 _cleanup_free_
char *booted_version
= NULL
;
1027 if (arg_image
|| arg_root
)
1028 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1029 "The --root=/--image= switches may not be combined with the '%s' operation.", argv
[0]);
1031 r
= context_make_offline(&context
, NULL
);
1035 log_info("Determining installed update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
1037 r
= context_discover_update_sets_by_flag(context
, UPDATE_INSTALLED
);
1040 if (!context
->newest_installed
)
1041 return log_error_errno(SYNTHETIC_ERRNO(ENODATA
), "Couldn't find any suitable installed versions.");
1043 r
= parse_os_release(arg_root
, "IMAGE_VERSION", &booted_version
);
1044 if (r
< 0) /* yes, arg_root is NULL here, but we have to pass something, and it's a lot more readable
1045 * if we see what the first argument is about */
1046 return log_error_errno(r
, "Failed to parse /etc/os-release: %m");
1047 if (!booted_version
)
1048 return log_error_errno(SYNTHETIC_ERRNO(ENODATA
), "/etc/os-release lacks IMAGE_VERSION= field.");
1050 r
= strverscmp_improved(context
->newest_installed
->version
, booted_version
);
1052 log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
1053 context
->newest_installed
->version
, booted_version
,
1054 streq(argv
[0], "pending") ? " Reboot recommended." : "");
1056 if (streq(argv
[0], "reboot"))
1057 return reboot_now();
1059 return EXIT_SUCCESS
;
1061 log_info("Newest installed version '%s' matches booted version '%s'.",
1062 context
->newest_installed
->version
, booted_version
);
1064 log_warning("Newest installed version '%s' is older than booted version '%s'.",
1065 context
->newest_installed
->version
, booted_version
);
1067 if (streq(argv
[0], "pending")) /* When called as 'pending' tell the caller via failure exit code that there's nothing newer installed */
1068 return EXIT_FAILURE
;
1070 return EXIT_SUCCESS
;
1073 static int component_name_valid(const char *c
) {
1074 _cleanup_free_
char *j
= NULL
;
1076 /* See if the specified string enclosed in the directory prefix+suffix would be a valid file name */
1081 if (string_has_cc(c
, NULL
))
1084 if (!utf8_is_valid(c
))
1087 j
= strjoin("sysupdate.", c
, ".d");
1091 return filename_is_valid(j
);
1094 static int verb_components(int argc
, char **argv
, void *userdata
) {
1095 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
1096 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
1097 _cleanup_set_free_ Set
*names
= NULL
;
1098 _cleanup_free_
char **z
= NULL
; /* We use simple free() rather than strv_free() here, since set_free() will free the strings for us */
1099 char **l
= CONF_PATHS_STRV("");
1100 bool has_default_component
= false;
1105 r
= process_image(/* ro= */ false, &mounted_dir
, &loop_device
);
1109 STRV_FOREACH(i
, l
) {
1110 _cleanup_closedir_
DIR *d
= NULL
;
1111 _cleanup_free_
char *p
= NULL
;
1113 r
= chase_and_opendir(*i
, arg_root
, CHASE_PREFIX_ROOT
, &p
, &d
);
1117 return log_error_errno(r
, "Failed to open directory '%s': %m", *i
);
1120 _cleanup_free_
char *n
= NULL
;
1124 de
= readdir_ensure_type(d
);
1127 return log_error_errno(errno
, "Failed to enumerate directory '%s': %m", p
);
1132 if (de
->d_type
!= DT_DIR
)
1135 if (dot_or_dot_dot(de
->d_name
))
1138 if (streq(de
->d_name
, "sysupdate.d")) {
1139 has_default_component
= true;
1143 e
= startswith(de
->d_name
, "sysupdate.");
1147 a
= endswith(e
, ".d");
1151 n
= strndup(e
, a
- e
);
1155 r
= component_name_valid(n
);
1157 return log_error_errno(r
, "Unable to validate component name: %m");
1161 r
= set_ensure_consume(&names
, &string_hash_ops_free
, TAKE_PTR(n
));
1162 if (r
< 0 && r
!= -EEXIST
)
1163 return log_error_errno(r
, "Failed to add component to set: %m");
1167 if (!has_default_component
&& set_isempty(names
)) {
1168 log_info("No components defined.");
1172 z
= set_get_strv(names
);
1178 if (has_default_component
)
1179 printf("%s<default>%s\n",
1180 ansi_highlight(), ansi_normal());
1188 static int verb_help(int argc
, char **argv
, void *userdata
) {
1189 _cleanup_free_
char *link
= NULL
;
1192 r
= terminal_urlify_man("systemd-sysupdate", "8", &link
);
1196 printf("%1$s [OPTIONS...] [VERSION]\n"
1197 "\n%5$sUpdate OS images.%6$s\n"
1198 "\n%3$sCommands:%4$s\n"
1199 " list [VERSION] Show installed and available versions\n"
1200 " check-new Check if there's a new version available\n"
1201 " update [VERSION] Install new version now\n"
1202 " vacuum Make room, by deleting old versions\n"
1203 " pending Report whether a newer version is installed than\n"
1204 " currently booted\n"
1205 " reboot Reboot if a newer version is installed than booted\n"
1206 " components Show list of components\n"
1207 " -h --help Show this help\n"
1208 " --version Show package version\n"
1209 "\n%3$sOptions:%4$s\n"
1210 " -C --component=NAME Select component to update\n"
1211 " --definitions=DIR Find transfer definitions in specified directory\n"
1212 " --root=PATH Operate on an alternate filesystem root\n"
1213 " --image=PATH Operate on disk image as filesystem root\n"
1214 " --image-policy=POLICY\n"
1215 " Specify disk image dissection policy\n"
1216 " -m --instances-max=INT How many instances to maintain\n"
1217 " --sync=BOOL Controls whether to sync data to disk\n"
1218 " --verify=BOOL Force signature verification on or off\n"
1219 " --reboot Reboot after updating to newer version\n"
1220 " --no-pager Do not pipe output into a pager\n"
1221 " --no-legend Do not show the headers and footers\n"
1222 " --json=pretty|short|off\n"
1223 " Generate JSON output\n"
1224 "\nSee the %2$s for details.\n"
1225 , program_invocation_short_name
1227 , ansi_underline(), ansi_normal()
1228 , ansi_highlight(), ansi_normal()
1234 static int parse_argv(int argc
, char *argv
[]) {
1237 ARG_VERSION
= 0x100,
1250 static const struct option options
[] = {
1251 { "help", no_argument
, NULL
, 'h' },
1252 { "version", no_argument
, NULL
, ARG_VERSION
},
1253 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1254 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
1255 { "definitions", required_argument
, NULL
, ARG_DEFINITIONS
},
1256 { "instances-max", required_argument
, NULL
, 'm' },
1257 { "sync", required_argument
, NULL
, ARG_SYNC
},
1258 { "json", required_argument
, NULL
, ARG_JSON
},
1259 { "root", required_argument
, NULL
, ARG_ROOT
},
1260 { "image", required_argument
, NULL
, ARG_IMAGE
},
1261 { "image-policy", required_argument
, NULL
, ARG_IMAGE_POLICY
},
1262 { "reboot", no_argument
, NULL
, ARG_REBOOT
},
1263 { "component", required_argument
, NULL
, 'C' },
1264 { "verify", required_argument
, NULL
, ARG_VERIFY
},
1273 while ((c
= getopt_long(argc
, argv
, "hm:C:", options
, NULL
)) >= 0) {
1278 return verb_help(0, NULL
, NULL
);
1284 arg_pager_flags
|= PAGER_DISABLE
;
1292 r
= safe_atou64(optarg
, &arg_instances_max
);
1294 return log_error_errno(r
, "Failed to parse --instances-max= parameter: %s", optarg
);
1299 r
= parse_boolean_argument("--sync=", optarg
, &arg_sync
);
1304 case ARG_DEFINITIONS
:
1305 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_definitions
);
1311 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
1318 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_root
);
1324 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_image
);
1329 case ARG_IMAGE_POLICY
:
1330 r
= parse_image_policy_argument(optarg
, &arg_image_policy
);
1340 if (isempty(optarg
)) {
1341 arg_component
= mfree(arg_component
);
1345 r
= component_name_valid(optarg
);
1347 return log_error_errno(r
, "Failed to determine if component name is valid: %m");
1349 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Component name invalid: %s", optarg
);
1351 r
= free_and_strdup_warn(&arg_component
, optarg
);
1360 r
= parse_boolean_argument("--verify=", optarg
, &b
);
1372 assert_not_reached();
1376 if (arg_image
&& arg_root
)
1377 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Please specify either --root= or --image=, the combination of both is not supported.");
1379 if ((arg_image
|| arg_root
) && arg_reboot
)
1380 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "The --reboot switch may not be combined with --root= or --image=.");
1382 if (arg_definitions
&& arg_component
)
1383 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "The --definitions= and --component= switches may not be combined.");
1388 static int sysupdate_main(int argc
, char *argv
[]) {
1390 static const Verb verbs
[] = {
1391 { "list", VERB_ANY
, 2, VERB_DEFAULT
, verb_list
},
1392 { "components", VERB_ANY
, 1, 0, verb_components
},
1393 { "check-new", VERB_ANY
, 1, 0, verb_check_new
},
1394 { "update", VERB_ANY
, 2, 0, verb_update
},
1395 { "vacuum", VERB_ANY
, 1, 0, verb_vacuum
},
1396 { "reboot", 1, 1, 0, verb_pending_or_reboot
},
1397 { "pending", 1, 1, 0, verb_pending_or_reboot
},
1398 { "help", VERB_ANY
, 1, 0, verb_help
},
1402 return dispatch_verb(argc
, argv
, verbs
, NULL
);
1405 static int run(int argc
, char *argv
[]) {
1410 r
= parse_argv(argc
, argv
);
1414 return sysupdate_main(argc
, argv
);
1417 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);