1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "bus-locator.h"
8 #include "chase-symlinks.h"
9 #include "conf-files.h"
11 #include "dirent-util.h"
12 #include "dissect-image.h"
14 #include "format-table.h"
15 #include "glyph-util.h"
16 #include "hexdecoct.h"
17 #include "login-util.h"
18 #include "main-func.h"
19 #include "mount-util.h"
22 #include "parse-argument.h"
23 #include "parse-util.h"
24 #include "path-util.h"
25 #include "pretty-print.h"
27 #include "sort-util.h"
28 #include "string-util.h"
30 #include "sysupdate-transfer.h"
31 #include "sysupdate-update-set.h"
32 #include "sysupdate.h"
33 #include "terminal-util.h"
37 static char *arg_definitions
= NULL
;
39 uint64_t arg_instances_max
= UINT64_MAX
;
40 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
41 static PagerFlags arg_pager_flags
= 0;
42 static bool arg_legend
= true;
43 char *arg_root
= NULL
;
44 static char *arg_image
= NULL
;
45 static bool arg_reboot
= false;
46 static char *arg_component
= NULL
;
47 static int arg_verify
= -1;
49 STATIC_DESTRUCTOR_REGISTER(arg_definitions
, freep
);
50 STATIC_DESTRUCTOR_REGISTER(arg_root
, freep
);
51 STATIC_DESTRUCTOR_REGISTER(arg_image
, freep
);
52 STATIC_DESTRUCTOR_REGISTER(arg_component
, freep
);
54 typedef struct Context
{
58 UpdateSet
**update_sets
;
61 UpdateSet
*newest_installed
, *candidate
;
63 Hashmap
*web_cache
; /* Cache for downloaded resources, keyed by URL */
66 static Context
*context_free(Context
*c
) {
70 for (size_t i
= 0; i
< c
->n_transfers
; i
++)
71 transfer_free(c
->transfers
[i
]);
74 for (size_t i
= 0; i
< c
->n_update_sets
; i
++)
75 update_set_free(c
->update_sets
[i
]);
78 hashmap_free(c
->web_cache
);
83 DEFINE_TRIVIAL_CLEANUP_FUNC(Context
*, context_free
);
85 static Context
*context_new(void) {
86 /* For now, no fields to initialize non-zero */
87 return new0(Context
, 1);
90 static int context_read_definitions(
92 const char *directory
,
93 const char *component
,
97 _cleanup_strv_free_
char **files
= NULL
;
103 r
= conf_files_list_strv(&files
, ".conf", NULL
, CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
, (const char**) STRV_MAKE(directory
));
104 else if (component
) {
105 _cleanup_strv_free_
char **n
= NULL
;
106 char **l
= CONF_PATHS_STRV("");
109 n
= new0(char*, strv_length(l
) + 1);
116 j
= strjoin(*i
, "sysupdate.", component
, ".d");
123 r
= conf_files_list_strv(&files
, ".conf", root
, CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
, (const char**) n
);
125 r
= conf_files_list_strv(&files
, ".conf", root
, CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
, (const char**) CONF_PATHS_STRV("sysupdate.d"));
127 return log_error_errno(r
, "Failed to enumerate *.conf files: %m");
129 STRV_FOREACH(f
, files
) {
130 _cleanup_(transfer_freep
) Transfer
*t
= NULL
;
132 if (!GREEDY_REALLOC(c
->transfers
, c
->n_transfers
+ 1))
139 t
->definition_path
= strdup(*f
);
140 if (!t
->definition_path
)
143 r
= transfer_read_definition(t
, *f
);
147 c
->transfers
[c
->n_transfers
++] = TAKE_PTR(t
);
150 if (c
->n_transfers
== 0) {
152 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
),
153 "No transfer definitions for component '%s' found.", arg_component
);
155 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
),
156 "No transfer definitions found.");
159 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
160 r
= transfer_resolve_paths(c
->transfers
[i
], root
, node
);
168 static int context_load_installed_instances(Context
*c
) {
173 log_info("Discovering installed instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
175 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
176 r
= resource_load_instances(
177 &c
->transfers
[i
]->target
,
178 arg_verify
>= 0 ? arg_verify
: c
->transfers
[i
]->verify
,
187 static int context_load_available_instances(Context
*c
) {
192 log_info("Discovering available instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
194 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
195 assert(c
->transfers
[i
]);
197 r
= resource_load_instances(
198 &c
->transfers
[i
]->source
,
199 arg_verify
>= 0 ? arg_verify
: c
->transfers
[i
]->verify
,
208 static int context_discover_update_sets_by_flag(Context
*c
, UpdateSetFlags flags
) {
209 _cleanup_free_ Instance
**cursor_instances
= NULL
;
210 _cleanup_free_
char *boundary
= NULL
;
211 bool newest_found
= false;
215 assert(IN_SET(flags
, UPDATE_AVAILABLE
, UPDATE_INSTALLED
));
218 bool incomplete
= false, exists
= false;
219 UpdateSetFlags extra_flags
= 0;
220 _cleanup_free_
char *cursor
= NULL
;
221 UpdateSet
*us
= NULL
;
223 for (size_t k
= 0; k
< c
->n_transfers
; k
++) {
224 Transfer
*t
= c
->transfers
[k
];
225 bool cursor_found
= false;
230 if (flags
== UPDATE_AVAILABLE
)
233 assert(flags
== UPDATE_INSTALLED
);
237 for (size_t j
= 0; j
< rr
->n_instances
; j
++) {
238 Instance
*i
= rr
->instances
[j
];
242 /* Is the instance we are looking at equal or newer than the boundary? If so, we
243 * already checked this version, and it wasn't complete, let's ignore it. */
244 if (boundary
&& strverscmp_improved(i
->metadata
.version
, boundary
) >= 0)
248 if (strverscmp_improved(i
->metadata
.version
, cursor
) != 0)
251 cursor
= strdup(i
->metadata
.version
);
258 if (!cursor_instances
) {
259 cursor_instances
= new(Instance
*, c
->n_transfers
);
260 if (!cursor_instances
)
263 cursor_instances
[k
] = i
;
267 if (!cursor
) /* No suitable instance beyond the boundary found? Then we are done! */
271 /* Hmm, we didn't find the version indicated by 'cursor' among the instances
272 * of this transfer, let's skip it. */
277 if (t
->min_version
&& strverscmp_improved(t
->min_version
, cursor
) > 0)
278 extra_flags
|= UPDATE_OBSOLETE
;
280 if (strv_contains(t
->protected_versions
, cursor
))
281 extra_flags
|= UPDATE_PROTECTED
;
284 if (!cursor
) /* EOL */
287 r
= free_and_strdup_warn(&boundary
, cursor
);
291 if (incomplete
) /* One transfer was missing this version, ignore the whole thing */
294 /* See if we already have this update set in our table */
295 for (size_t i
= 0; i
< c
->n_update_sets
; i
++) {
296 if (strverscmp_improved(c
->update_sets
[i
]->version
, cursor
) != 0)
299 /* We only store the instances we found first, but we remember we also found it again */
300 c
->update_sets
[i
]->flags
|= flags
| extra_flags
;
309 /* Doesn't exist yet, let's add it */
310 if (!GREEDY_REALLOC(c
->update_sets
, c
->n_update_sets
+ 1))
313 us
= new(UpdateSet
, 1);
318 .flags
= flags
| (newest_found
? 0 : UPDATE_NEWEST
) | extra_flags
,
319 .version
= TAKE_PTR(cursor
),
320 .instances
= TAKE_PTR(cursor_instances
),
321 .n_instances
= c
->n_transfers
,
324 c
->update_sets
[c
->n_update_sets
++] = us
;
328 /* Remember which one is the newest installed */
329 if ((us
->flags
& (UPDATE_NEWEST
|UPDATE_INSTALLED
)) == (UPDATE_NEWEST
|UPDATE_INSTALLED
))
330 c
->newest_installed
= us
;
332 /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate" */
333 if ((us
->flags
& (UPDATE_NEWEST
|UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_OBSOLETE
)) == (UPDATE_NEWEST
|UPDATE_AVAILABLE
))
337 /* Newest installed is newer than or equal to candidate? Then suppress the candidate */
338 if (c
->newest_installed
&& c
->candidate
&& strverscmp_improved(c
->newest_installed
->version
, c
->candidate
->version
) >= 0)
344 static int context_discover_update_sets(Context
*c
) {
349 log_info("Determining installed update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
351 r
= context_discover_update_sets_by_flag(c
, UPDATE_INSTALLED
);
355 log_info("Determining available update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
357 r
= context_discover_update_sets_by_flag(c
, UPDATE_AVAILABLE
);
361 typesafe_qsort(c
->update_sets
, c
->n_update_sets
, update_set_cmp
);
365 static const char *update_set_flags_to_string(UpdateSetFlags flags
) {
367 switch ((unsigned) flags
) {
372 case UPDATE_INSTALLED
|UPDATE_NEWEST
:
373 case UPDATE_INSTALLED
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
374 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_NEWEST
:
375 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
378 case UPDATE_AVAILABLE
|UPDATE_NEWEST
:
379 case UPDATE_AVAILABLE
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
382 case UPDATE_INSTALLED
:
383 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
:
386 case UPDATE_INSTALLED
|UPDATE_PROTECTED
:
387 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_PROTECTED
:
390 case UPDATE_AVAILABLE
:
391 case UPDATE_AVAILABLE
|UPDATE_PROTECTED
:
394 case UPDATE_INSTALLED
|UPDATE_OBSOLETE
|UPDATE_NEWEST
:
395 case UPDATE_INSTALLED
|UPDATE_OBSOLETE
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
396 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_NEWEST
:
397 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
398 return "current+obsolete";
400 case UPDATE_INSTALLED
|UPDATE_OBSOLETE
:
401 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_OBSOLETE
:
402 return "installed+obsolete";
404 case UPDATE_INSTALLED
|UPDATE_OBSOLETE
|UPDATE_PROTECTED
:
405 case UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_PROTECTED
:
406 return "protected+obsolete";
408 case UPDATE_AVAILABLE
|UPDATE_OBSOLETE
:
409 case UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_PROTECTED
:
410 case UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_NEWEST
:
411 case UPDATE_AVAILABLE
|UPDATE_OBSOLETE
|UPDATE_NEWEST
|UPDATE_PROTECTED
:
412 return "available+obsolete";
415 assert_not_reached();
420 static int context_show_table(Context
*c
) {
421 _cleanup_(table_unrefp
) Table
*t
= NULL
;
426 t
= table_new("", "version", "installed", "available", "assessment");
430 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 0), 100);
431 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 2), 50);
432 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 3), 50);
434 for (size_t i
= 0; i
< c
->n_update_sets
; i
++) {
435 UpdateSet
*us
= c
->update_sets
[i
];
438 color
= update_set_flags_to_color(us
->flags
);
440 r
= table_add_many(t
,
441 TABLE_STRING
, update_set_flags_to_glyph(us
->flags
),
442 TABLE_SET_COLOR
, color
,
443 TABLE_STRING
, us
->version
,
444 TABLE_SET_COLOR
, color
,
445 TABLE_STRING
, special_glyph_check_mark_space(FLAGS_SET(us
->flags
, UPDATE_INSTALLED
)),
446 TABLE_SET_COLOR
, color
,
447 TABLE_STRING
, special_glyph_check_mark_space(FLAGS_SET(us
->flags
, UPDATE_AVAILABLE
)),
448 TABLE_SET_COLOR
, color
,
449 TABLE_STRING
, update_set_flags_to_string(us
->flags
),
450 TABLE_SET_COLOR
, color
);
452 return table_log_add_error(r
);
455 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
458 static UpdateSet
*context_update_set_by_version(Context
*c
, const char *version
) {
462 for (size_t i
= 0; i
< c
->n_update_sets
; i
++)
463 if (streq(c
->update_sets
[i
]->version
, version
))
464 return c
->update_sets
[i
];
469 static int context_show_version(Context
*c
, const char *version
) {
470 bool show_fs_columns
= false, show_partition_columns
= false,
471 have_fs_attributes
= false, have_partition_attributes
= false,
472 have_size
= false, have_tries
= false, have_no_auto
= false,
473 have_read_only
= false, have_growfs
= false, have_sha256
= false;
474 _cleanup_(table_unrefp
) Table
*t
= NULL
;
481 us
= context_update_set_by_version(c
, version
);
483 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "Update '%s' not found.", version
);
485 if (arg_json_format_flags
& (JSON_FORMAT_OFF
|JSON_FORMAT_PRETTY
|JSON_FORMAT_PRETTY_AUTO
))
486 (void) pager_open(arg_pager_flags
);
488 if (FLAGS_SET(arg_json_format_flags
, JSON_FORMAT_OFF
))
489 printf("%s%s%s Version: %s\n"
493 "Protected: %s%s%s\n"
494 " Obsolete: %s%s%s\n\n",
495 strempty(update_set_flags_to_color(us
->flags
)), update_set_flags_to_glyph(us
->flags
), ansi_normal(), us
->version
,
496 strempty(update_set_flags_to_color(us
->flags
)), update_set_flags_to_string(us
->flags
), ansi_normal(),
497 yes_no(us
->flags
& UPDATE_INSTALLED
), FLAGS_SET(us
->flags
, UPDATE_INSTALLED
|UPDATE_NEWEST
) ? " (newest)" : "",
498 yes_no(us
->flags
& UPDATE_AVAILABLE
), (us
->flags
& (UPDATE_INSTALLED
|UPDATE_AVAILABLE
|UPDATE_NEWEST
)) == (UPDATE_AVAILABLE
|UPDATE_NEWEST
) ? " (newest)" : "",
499 FLAGS_SET(us
->flags
, UPDATE_INSTALLED
|UPDATE_PROTECTED
) ? ansi_highlight() : "", yes_no(FLAGS_SET(us
->flags
, UPDATE_INSTALLED
|UPDATE_PROTECTED
)), ansi_normal(),
500 us
->flags
& UPDATE_OBSOLETE
? ansi_highlight_red() : "", yes_no(us
->flags
& UPDATE_OBSOLETE
), ansi_normal());
503 t
= table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
507 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 3), 100);
508 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 4), 100);
509 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 5), 100);
510 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 6), 100);
511 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 7), 100);
512 (void) table_set_align_percent(t
, table_get_cell(t
, 0, 8), 100);
513 (void) table_set_empty_string(t
, "-");
515 /* Determine if the target will make use of partition/fs attributes for any of the transfers */
516 for (size_t n
= 0; n
< c
->n_transfers
; n
++) {
517 Transfer
*tr
= c
->transfers
[n
];
519 if (tr
->target
.type
== RESOURCE_PARTITION
)
520 show_partition_columns
= true;
521 if (RESOURCE_IS_FILESYSTEM(tr
->target
.type
))
522 show_fs_columns
= true;
525 for (size_t n
= 0; n
< us
->n_instances
; n
++) {
526 Instance
*i
= us
->instances
[n
];
528 r
= table_add_many(t
,
529 TABLE_STRING
, resource_type_to_string(i
->resource
->type
),
530 TABLE_PATH
, i
->path
);
532 return table_log_add_error(r
);
534 if (i
->metadata
.partition_uuid_set
) {
535 have_partition_attributes
= true;
536 r
= table_add_cell(t
, NULL
, TABLE_UUID
, &i
->metadata
.partition_uuid
);
538 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
540 return table_log_add_error(r
);
542 if (i
->metadata
.partition_flags_set
) {
543 have_partition_attributes
= true;
544 r
= table_add_cell(t
, NULL
, TABLE_UINT64_HEX
, &i
->metadata
.partition_flags
);
546 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
548 return table_log_add_error(r
);
550 if (i
->metadata
.mtime
!= USEC_INFINITY
) {
551 have_fs_attributes
= true;
552 r
= table_add_cell(t
, NULL
, TABLE_TIMESTAMP
, &i
->metadata
.mtime
);
554 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
556 return table_log_add_error(r
);
558 if (i
->metadata
.mode
!= MODE_INVALID
) {
559 have_fs_attributes
= true;
560 r
= table_add_cell(t
, NULL
, TABLE_MODE
, &i
->metadata
.mode
);
562 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
564 return table_log_add_error(r
);
566 if (i
->metadata
.size
!= UINT64_MAX
) {
568 r
= table_add_cell(t
, NULL
, TABLE_SIZE
, &i
->metadata
.size
);
570 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
572 return table_log_add_error(r
);
574 if (i
->metadata
.tries_done
!= UINT64_MAX
) {
576 r
= table_add_cell(t
, NULL
, TABLE_UINT64
, &i
->metadata
.tries_done
);
578 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
580 return table_log_add_error(r
);
582 if (i
->metadata
.tries_left
!= UINT64_MAX
) {
584 r
= table_add_cell(t
, NULL
, TABLE_UINT64
, &i
->metadata
.tries_left
);
586 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
588 return table_log_add_error(r
);
590 if (i
->metadata
.no_auto
>= 0) {
594 b
= i
->metadata
.no_auto
;
595 r
= table_add_cell(t
, NULL
, TABLE_BOOLEAN
, &b
);
597 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
599 return table_log_add_error(r
);
600 if (i
->metadata
.read_only
>= 0) {
603 have_read_only
= true;
604 b
= i
->metadata
.read_only
;
605 r
= table_add_cell(t
, NULL
, TABLE_BOOLEAN
, &b
);
607 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
609 return table_log_add_error(r
);
611 if (i
->metadata
.growfs
>= 0) {
615 b
= i
->metadata
.growfs
;
616 r
= table_add_cell(t
, NULL
, TABLE_BOOLEAN
, &b
);
618 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
620 return table_log_add_error(r
);
622 if (i
->metadata
.sha256sum_set
) {
623 _cleanup_free_
char *formatted
= NULL
;
627 formatted
= hexmem(i
->metadata
.sha256sum
, sizeof(i
->metadata
.sha256sum
));
631 r
= table_add_cell(t
, NULL
, TABLE_STRING
, formatted
);
633 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
635 return table_log_add_error(r
);
638 /* Hide the fs/partition columns if we don't have any data to show there */
639 if (!have_fs_attributes
)
640 show_fs_columns
= false;
641 if (!have_partition_attributes
)
642 show_partition_columns
= false;
644 if (!show_partition_columns
)
645 (void) table_hide_column_from_display(t
, 2, 3);
646 if (!show_fs_columns
)
647 (void) table_hide_column_from_display(t
, 4, 5);
649 (void) table_hide_column_from_display(t
, 6);
651 (void) table_hide_column_from_display(t
, 7, 8);
653 (void) table_hide_column_from_display(t
, 9);
655 (void) table_hide_column_from_display(t
, 10);
657 (void) table_hide_column_from_display(t
, 11);
659 (void) table_hide_column_from_display(t
, 12);
661 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
664 static int context_vacuum(
667 const char *extra_protected_version
) {
674 log_info("Making room%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
676 log_info("Making room for %" PRIu64
" updates%s", space
,special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
678 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
679 r
= transfer_vacuum(c
->transfers
[i
], space
, extra_protected_version
);
683 count
= MAX(count
, r
);
687 log_info("Removed %i instances.", count
);
689 log_info("Removed no instances.");
694 static int context_make_offline(Context
**ret
, const char *node
) {
695 _cleanup_(context_freep
) Context
* context
= NULL
;
700 /* Allocates a context object and initializes everything we can initialize offline, i.e. without
701 * checking on the update source (i.e. the Internet) what versions are available */
703 context
= context_new();
707 r
= context_read_definitions(context
, arg_definitions
, arg_component
, arg_root
, node
);
711 r
= context_load_installed_instances(context
);
715 *ret
= TAKE_PTR(context
);
719 static int context_make_online(Context
**ret
, const char *node
) {
720 _cleanup_(context_freep
) Context
* context
= NULL
;
725 /* Like context_make_offline(), but also communicates with the update source looking for new
728 r
= context_make_offline(&context
, node
);
732 r
= context_load_available_instances(context
);
736 r
= context_discover_update_sets(context
);
740 *ret
= TAKE_PTR(context
);
744 static int context_apply(
747 UpdateSet
**ret_applied
) {
749 UpdateSet
*us
= NULL
;
755 us
= context_update_set_by_version(c
, version
);
757 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "Update '%s' not found.", version
);
760 log_info("No update needed.");
771 if (FLAGS_SET(us
->flags
, UPDATE_INSTALLED
)) {
772 log_info("Selected update '%s' is already installed. Skipping update.", us
->version
);
779 if (!FLAGS_SET(us
->flags
, UPDATE_AVAILABLE
))
780 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Selected update '%s' is not available, refusing.", us
->version
);
781 if (FLAGS_SET(us
->flags
, UPDATE_OBSOLETE
))
782 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Selected update '%s' is obsolete, refusing.", us
->version
);
784 assert((us
->flags
& (UPDATE_AVAILABLE
|UPDATE_INSTALLED
|UPDATE_OBSOLETE
)) == UPDATE_AVAILABLE
);
786 if (!FLAGS_SET(us
->flags
, UPDATE_NEWEST
))
787 log_notice("Selected update '%s' is not the newest, proceeding anyway.", us
->version
);
788 if (c
->newest_installed
&& strverscmp_improved(c
->newest_installed
->version
, us
->version
) > 0)
789 log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us
->version
);
791 log_info("Selected update '%s' for install.", us
->version
);
793 (void) sd_notifyf(false,
794 "STATUS=Making room for '%s'.", us
->version
);
796 /* Let's make some room. We make sure for each transfer we have one free space to fill. While
797 * removing stuff we'll protect the version we are trying to acquire. Why that? Maybe an earlier
798 * download succeeded already, in which case we shouldn't remove it just to acquire it again */
802 /* extra_protected_version = */ us
->version
);
809 (void) sd_notifyf(false,
810 "STATUS=Updating to '%s'.\n", us
->version
);
812 /* There should now be one instance picked for each transfer, and the order is the same */
813 assert(us
->n_instances
== c
->n_transfers
);
815 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
816 r
= transfer_acquire_instance(c
->transfers
[i
], us
->instances
[i
]);
824 for (size_t i
= 0; i
< c
->n_transfers
; i
++) {
825 r
= transfer_install_instance(c
->transfers
[i
], us
->instances
[i
], arg_root
);
830 log_info("%s Successfully installed update '%s'.", special_glyph(SPECIAL_GLYPH_SPARKLES
), us
->version
);
838 static int reboot_now(void) {
839 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
840 _cleanup_(sd_bus_close_unrefp
) sd_bus
*bus
= NULL
;
843 r
= sd_bus_open_system(&bus
);
845 return log_error_errno(r
, "Failed to open bus connection: %m");
847 r
= bus_call_method(bus
, bus_login_mgr
, "RebootWithFlags", &error
, NULL
, "t",
848 (uint64_t) SD_LOGIND_ROOT_CHECK_INHIBITORS
);
850 return log_error_errno(r
, "Failed to issue reboot request: %s", bus_error_message(&error
, r
));
855 static int process_image(
857 char **ret_mounted_dir
,
858 LoopDevice
**ret_loop_device
) {
860 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
861 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
864 assert(ret_mounted_dir
);
865 assert(ret_loop_device
);
872 r
= mount_image_privately_interactively(
874 (ro
? DISSECT_IMAGE_READ_ONLY
: 0) |
876 DISSECT_IMAGE_MKDIR
|
877 DISSECT_IMAGE_GROWFS
|
878 DISSECT_IMAGE_RELAX_VAR_CHECK
|
879 DISSECT_IMAGE_USR_NO_ROOT
|
880 DISSECT_IMAGE_GENERIC_ROOT
|
881 DISSECT_IMAGE_REQUIRE_ROOT
,
887 arg_root
= strdup(mounted_dir
);
891 *ret_mounted_dir
= TAKE_PTR(mounted_dir
);
892 *ret_loop_device
= TAKE_PTR(loop_device
);
897 static int verb_list(int argc
, char **argv
, void *userdata
) {
898 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
899 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
900 _cleanup_(context_freep
) Context
* context
= NULL
;
905 version
= argc
>= 2 ? argv
[1] : NULL
;
907 r
= process_image(/* ro= */ true, &mounted_dir
, &loop_device
);
911 r
= context_make_online(&context
, loop_device
? loop_device
->node
: NULL
);
916 return context_show_version(context
, version
);
918 return context_show_table(context
);
921 static int verb_check_new(int argc
, char **argv
, void *userdata
) {
922 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
923 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
924 _cleanup_(context_freep
) Context
* context
= NULL
;
929 r
= process_image(/* ro= */ true, &mounted_dir
, &loop_device
);
933 r
= context_make_online(&context
, loop_device
? loop_device
->node
: NULL
);
937 if (!context
->candidate
) {
938 log_debug("No candidate found.");
942 puts(context
->candidate
->version
);
946 static int verb_vacuum(int argc
, char **argv
, void *userdata
) {
947 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
948 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
949 _cleanup_(context_freep
) Context
* context
= NULL
;
954 r
= process_image(/* ro= */ false, &mounted_dir
, &loop_device
);
958 r
= context_make_offline(&context
, loop_device
? loop_device
->node
: NULL
);
962 return context_vacuum(context
, 0, NULL
);
965 static int verb_update(int argc
, char **argv
, void *userdata
) {
966 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
967 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
968 _cleanup_(context_freep
) Context
* context
= NULL
;
969 _cleanup_free_
char *booted_version
= NULL
;
970 UpdateSet
*applied
= NULL
;
975 version
= argc
>= 2 ? argv
[1] : NULL
;
978 /* If automatic reboot on completion is requested, let's first determine the currently booted image */
980 r
= parse_os_release(arg_root
, "IMAGE_VERSION", &booted_version
);
982 return log_error_errno(r
, "Failed to parse /etc/os-release: %m");
984 return log_error_errno(SYNTHETIC_ERRNO(ENODATA
), "/etc/os-release lacks IMAGE_VERSION field.");
987 r
= process_image(/* ro= */ false, &mounted_dir
, &loop_device
);
991 r
= context_make_online(&context
, loop_device
? loop_device
->node
: NULL
);
995 r
= context_apply(context
, version
, &applied
);
999 if (r
> 0 && arg_reboot
) {
1001 assert(booted_version
);
1003 if (strverscmp_improved(applied
->version
, booted_version
) > 0) {
1004 log_notice("Newly installed version is newer than booted version, rebooting.");
1005 return reboot_now();
1008 log_info("Booted version is newer or identical to newly installed version, not rebooting.");
1014 static int verb_pending_or_reboot(int argc
, char **argv
, void *userdata
) {
1015 _cleanup_(context_freep
) Context
* context
= NULL
;
1016 _cleanup_free_
char *booted_version
= NULL
;
1021 if (arg_image
|| arg_root
)
1022 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1023 "The --root=/--image switches may not be combined with the '%s' operation.", argv
[0]);
1025 r
= context_make_offline(&context
, NULL
);
1029 log_info("Determining installed update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
1031 r
= context_discover_update_sets_by_flag(context
, UPDATE_INSTALLED
);
1034 if (!context
->newest_installed
)
1035 return log_error_errno(SYNTHETIC_ERRNO(ENODATA
), "Couldn't find any suitable installed versions.");
1037 r
= parse_os_release(arg_root
, "IMAGE_VERSION", &booted_version
);
1038 if (r
< 0) /* yes, arg_root is NULL here, but we have to pass something, and it's a lot more readable
1039 * if we see what the first argument is about */
1040 return log_error_errno(r
, "Failed to parse /etc/os-release: %m");
1041 if (!booted_version
)
1042 return log_error_errno(SYNTHETIC_ERRNO(ENODATA
), "/etc/os-release lacks IMAGE_VERSION= field.");
1044 r
= strverscmp_improved(context
->newest_installed
->version
, booted_version
);
1046 log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
1047 context
->newest_installed
->version
, booted_version
,
1048 streq(argv
[0], "pending") ? " Reboot recommended." : "");
1050 if (streq(argv
[0], "reboot"))
1051 return reboot_now();
1053 return EXIT_SUCCESS
;
1055 log_info("Newest installed version '%s' matches booted version '%s'.",
1056 context
->newest_installed
->version
, booted_version
);
1058 log_warning("Newest installed version '%s' is older than booted version '%s'.",
1059 context
->newest_installed
->version
, booted_version
);
1061 if (streq(argv
[0], "pending")) /* When called as 'pending' tell the caller via failure exit code that there's nothing newer installed */
1062 return EXIT_FAILURE
;
1064 return EXIT_SUCCESS
;
1067 static int component_name_valid(const char *c
) {
1068 _cleanup_free_
char *j
= NULL
;
1070 /* See if the specified string enclosed in the directory prefix+suffix would be a valid file name */
1075 if (string_has_cc(c
, NULL
))
1078 if (!utf8_is_valid(c
))
1081 j
= strjoin("sysupdate.", c
, ".d");
1085 return filename_is_valid(j
);
1088 static int verb_components(int argc
, char **argv
, void *userdata
) {
1089 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
1090 _cleanup_(umount_and_rmdir_and_freep
) char *mounted_dir
= NULL
;
1091 _cleanup_(set_freep
) Set
*names
= NULL
;
1092 _cleanup_free_
char **z
= NULL
; /* We use simple free() rather than strv_free() here, since set_free() will free the strings for us */
1093 char **l
= CONF_PATHS_STRV("");
1094 bool has_default_component
= false;
1099 r
= process_image(/* ro= */ false, &mounted_dir
, &loop_device
);
1103 STRV_FOREACH(i
, l
) {
1104 _cleanup_closedir_
DIR *d
= NULL
;
1105 _cleanup_free_
char *p
= NULL
;
1107 r
= chase_symlinks_and_opendir(*i
, arg_root
, CHASE_PREFIX_ROOT
, &p
, &d
);
1111 return log_error_errno(r
, "Failed to open directory '%s': %m", *i
);
1114 _cleanup_free_
char *n
= NULL
;
1118 de
= readdir_ensure_type(d
);
1121 return log_error_errno(errno
, "Failed to enumerate directory '%s': %m", p
);
1126 if (de
->d_type
!= DT_DIR
)
1129 if (dot_or_dot_dot(de
->d_name
))
1132 if (streq(de
->d_name
, "sysupdate.d")) {
1133 has_default_component
= true;
1137 e
= startswith(de
->d_name
, "sysupdate.");
1141 a
= endswith(e
, ".d");
1145 n
= strndup(e
, a
- e
);
1149 r
= component_name_valid(n
);
1151 return log_error_errno(r
, "Unable to validate component name: %m");
1155 r
= set_ensure_consume(&names
, &string_hash_ops_free
, TAKE_PTR(n
));
1156 if (r
< 0 && r
!= -EEXIST
)
1157 return log_error_errno(r
, "Failed to add component to set: %m");
1161 if (!has_default_component
&& set_isempty(names
)) {
1162 log_info("No components defined.");
1166 z
= set_get_strv(names
);
1172 if (has_default_component
)
1173 printf("%s<default>%s\n",
1174 ansi_highlight(), ansi_normal());
1182 static int verb_help(int argc
, char **argv
, void *userdata
) {
1183 _cleanup_free_
char *link
= NULL
;
1186 r
= terminal_urlify_man("systemd-sysupdate", "1", &link
);
1190 printf("%1$s [OPTIONS...] [VERSION]\n"
1191 "\n%5$sUpdate OS images.%6$s\n"
1192 "\n%3$sCommands:%4$s\n"
1193 " list [VERSION] Show installed and available versions\n"
1194 " check-new Check if there's a new version available\n"
1195 " update [VERSION] Install new version now\n"
1196 " vacuum Make room, by deleting old versions\n"
1197 " pending Report whether a newer version is installed than\n"
1198 " currently booted\n"
1199 " reboot Reboot if a newer version is installed than booted\n"
1200 " components Show list of components\n"
1201 " -h --help Show this help\n"
1202 " --version Show package version\n"
1203 "\n%3$sOptions:%4$s\n"
1204 " -C --component=NAME Select component to update\n"
1205 " --definitions=DIR Find transfer definitions in specified directory\n"
1206 " --root=PATH Operate relative to root path\n"
1207 " --image=PATH Operate relative to image file\n"
1208 " -m --instances-max=INT How many instances to maintain\n"
1209 " --sync=BOOL Controls whether to sync data to disk\n"
1210 " --verify=BOOL Force signature verification on or off\n"
1211 " --reboot Reboot after updating to newer version\n"
1212 " --no-pager Do not pipe output into a pager\n"
1213 " --no-legend Do not show the headers and footers\n"
1214 " --json=pretty|short|off\n"
1215 " Generate JSON output\n"
1216 "\nSee the %2$s for details.\n"
1217 , program_invocation_short_name
1219 , ansi_underline(), ansi_normal()
1220 , ansi_highlight(), ansi_normal()
1226 static int parse_argv(int argc
, char *argv
[]) {
1229 ARG_VERSION
= 0x100,
1241 static const struct option options
[] = {
1242 { "help", no_argument
, NULL
, 'h' },
1243 { "version", no_argument
, NULL
, ARG_VERSION
},
1244 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1245 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
1246 { "definitions", required_argument
, NULL
, ARG_DEFINITIONS
},
1247 { "instances-max", required_argument
, NULL
, 'm' },
1248 { "sync", required_argument
, NULL
, ARG_SYNC
},
1249 { "json", required_argument
, NULL
, ARG_JSON
},
1250 { "root", required_argument
, NULL
, ARG_ROOT
},
1251 { "image", required_argument
, NULL
, ARG_IMAGE
},
1252 { "reboot", no_argument
, NULL
, ARG_REBOOT
},
1253 { "component", required_argument
, NULL
, 'C' },
1254 { "verify", required_argument
, NULL
, ARG_VERIFY
},
1263 while ((c
= getopt_long(argc
, argv
, "hm:C:", options
, NULL
)) >= 0) {
1268 return verb_help(0, NULL
, NULL
);
1274 arg_pager_flags
|= PAGER_DISABLE
;
1282 r
= safe_atou64(optarg
, &arg_instances_max
);
1284 return log_error_errno(r
, "Failed to parse --instances-max= parameter: %s", optarg
);
1289 r
= parse_boolean_argument("--sync=", optarg
, &arg_sync
);
1294 case ARG_DEFINITIONS
:
1295 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_definitions
);
1301 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
1308 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_root
);
1314 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_image
);
1324 if (isempty(optarg
)) {
1325 arg_component
= mfree(arg_component
);
1329 r
= component_name_valid(optarg
);
1331 return log_error_errno(r
, "Failed to determine if component name is valid: %m");
1333 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Component name invalid: %s", optarg
);
1335 r
= free_and_strdup_warn(&arg_component
, optarg
);
1344 r
= parse_boolean_argument("--verify=", optarg
, &b
);
1356 assert_not_reached();
1360 if (arg_image
&& arg_root
)
1361 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Please specify either --root= or --image=, the combination of both is not supported.");
1363 if ((arg_image
|| arg_root
) && arg_reboot
)
1364 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "The --reboot switch may not be combined with --root= or --image=.");
1366 if (arg_definitions
&& arg_component
)
1367 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "The --definitions= and --component= switches may not be combined.");
1372 static int sysupdate_main(int argc
, char *argv
[]) {
1374 static const Verb verbs
[] = {
1375 { "list", VERB_ANY
, 2, VERB_DEFAULT
, verb_list
},
1376 { "components", VERB_ANY
, 1, 0, verb_components
},
1377 { "check-new", VERB_ANY
, 1, 0, verb_check_new
},
1378 { "update", VERB_ANY
, 2, 0, verb_update
},
1379 { "vacuum", VERB_ANY
, 1, 0, verb_vacuum
},
1380 { "reboot", 1, 1, 0, verb_pending_or_reboot
},
1381 { "pending", 1, 1, 0, verb_pending_or_reboot
},
1382 { "help", VERB_ANY
, 1, 0, verb_help
},
1386 return dispatch_verb(argc
, argv
, verbs
, NULL
);
1389 static int run(int argc
, char *argv
[]) {
1394 r
= parse_argv(argc
, argv
);
1398 return sysupdate_main(argc
, argv
);
1401 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);