]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sysupdate/sysupdate.c
tree-wide: drop unused reference to DecryptedImage
[thirdparty/systemd.git] / src / sysupdate / sysupdate.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4 #include <unistd.h>
5
6 #include "bus-error.h"
7 #include "bus-locator.h"
8 #include "chase-symlinks.h"
9 #include "conf-files.h"
10 #include "def.h"
11 #include "dirent-util.h"
12 #include "dissect-image.h"
13 #include "fd-util.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"
20 #include "os-util.h"
21 #include "pager.h"
22 #include "parse-argument.h"
23 #include "parse-util.h"
24 #include "path-util.h"
25 #include "pretty-print.h"
26 #include "set.h"
27 #include "sort-util.h"
28 #include "string-util.h"
29 #include "strv.h"
30 #include "sysupdate-transfer.h"
31 #include "sysupdate-update-set.h"
32 #include "sysupdate.h"
33 #include "terminal-util.h"
34 #include "utf8.h"
35 #include "verbs.h"
36
37 static char *arg_definitions = NULL;
38 bool arg_sync = true;
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;
48
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);
53
54 typedef struct Context {
55 Transfer **transfers;
56 size_t n_transfers;
57
58 UpdateSet **update_sets;
59 size_t n_update_sets;
60
61 UpdateSet *newest_installed, *candidate;
62
63 Hashmap *web_cache; /* Cache for downloaded resources, keyed by URL */
64 } Context;
65
66 static Context *context_free(Context *c) {
67 if (!c)
68 return NULL;
69
70 for (size_t i = 0; i < c->n_transfers; i++)
71 transfer_free(c->transfers[i]);
72 free(c->transfers);
73
74 for (size_t i = 0; i < c->n_update_sets; i++)
75 update_set_free(c->update_sets[i]);
76 free(c->update_sets);
77
78 hashmap_free(c->web_cache);
79
80 return mfree(c);
81 }
82
83 DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free);
84
85 static Context *context_new(void) {
86 /* For now, no fields to initialize non-zero */
87 return new0(Context, 1);
88 }
89
90 static int context_read_definitions(
91 Context *c,
92 const char *directory,
93 const char *component,
94 const char *root,
95 const char *node) {
96
97 _cleanup_strv_free_ char **files = NULL;
98 int r;
99
100 assert(c);
101
102 if (directory)
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("");
107 size_t k = 0;
108
109 n = new0(char*, strv_length(l) + 1);
110 if (!n)
111 return log_oom();
112
113 STRV_FOREACH(i, l) {
114 char *j;
115
116 j = strjoin(*i, "sysupdate.", component, ".d");
117 if (!j)
118 return log_oom();
119
120 n[k++] = j;
121 }
122
123 r = conf_files_list_strv(&files, ".conf", root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char**) n);
124 } else
125 r = conf_files_list_strv(&files, ".conf", root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char**) CONF_PATHS_STRV("sysupdate.d"));
126 if (r < 0)
127 return log_error_errno(r, "Failed to enumerate *.conf files: %m");
128
129 STRV_FOREACH(f, files) {
130 _cleanup_(transfer_freep) Transfer *t = NULL;
131
132 if (!GREEDY_REALLOC(c->transfers, c->n_transfers + 1))
133 return log_oom();
134
135 t = transfer_new();
136 if (!t)
137 return log_oom();
138
139 t->definition_path = strdup(*f);
140 if (!t->definition_path)
141 return log_oom();
142
143 r = transfer_read_definition(t, *f);
144 if (r < 0)
145 return r;
146
147 c->transfers[c->n_transfers++] = TAKE_PTR(t);
148 }
149
150 if (c->n_transfers == 0) {
151 if (arg_component)
152 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
153 "No transfer definitions for component '%s' found.", arg_component);
154
155 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
156 "No transfer definitions found.");
157 }
158
159 for (size_t i = 0; i < c->n_transfers; i++) {
160 r = transfer_resolve_paths(c->transfers[i], root, node);
161 if (r < 0)
162 return r;
163 }
164
165 return 0;
166 }
167
168 static int context_load_installed_instances(Context *c) {
169 int r;
170
171 assert(c);
172
173 log_info("Discovering installed instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
174
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,
179 &c->web_cache);
180 if (r < 0)
181 return r;
182 }
183
184 return 0;
185 }
186
187 static int context_load_available_instances(Context *c) {
188 int r;
189
190 assert(c);
191
192 log_info("Discovering available instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
193
194 for (size_t i = 0; i < c->n_transfers; i++) {
195 assert(c->transfers[i]);
196
197 r = resource_load_instances(
198 &c->transfers[i]->source,
199 arg_verify >= 0 ? arg_verify : c->transfers[i]->verify,
200 &c->web_cache);
201 if (r < 0)
202 return r;
203 }
204
205 return 0;
206 }
207
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;
212 int r;
213
214 assert(c);
215 assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
216
217 for (;;) {
218 bool incomplete = false, exists = false;
219 UpdateSetFlags extra_flags = 0;
220 _cleanup_free_ char *cursor = NULL;
221 UpdateSet *us = NULL;
222
223 for (size_t k = 0; k < c->n_transfers; k++) {
224 Transfer *t = c->transfers[k];
225 bool cursor_found = false;
226 Resource *rr;
227
228 assert(t);
229
230 if (flags == UPDATE_AVAILABLE)
231 rr = &t->source;
232 else {
233 assert(flags == UPDATE_INSTALLED);
234 rr = &t->target;
235 }
236
237 for (size_t j = 0; j < rr->n_instances; j++) {
238 Instance *i = rr->instances[j];
239
240 assert(i);
241
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)
245 continue;
246
247 if (cursor) {
248 if (strverscmp_improved(i->metadata.version, cursor) != 0)
249 continue;
250 } else {
251 cursor = strdup(i->metadata.version);
252 if (!cursor)
253 return log_oom();
254 }
255
256 cursor_found = true;
257
258 if (!cursor_instances) {
259 cursor_instances = new(Instance*, c->n_transfers);
260 if (!cursor_instances)
261 return -ENOMEM;
262 }
263 cursor_instances[k] = i;
264 break;
265 }
266
267 if (!cursor) /* No suitable instance beyond the boundary found? Then we are done! */
268 break;
269
270 if (!cursor_found) {
271 /* Hmm, we didn't find the version indicated by 'cursor' among the instances
272 * of this transfer, let's skip it. */
273 incomplete = true;
274 break;
275 }
276
277 if (t->min_version && strverscmp_improved(t->min_version, cursor) > 0)
278 extra_flags |= UPDATE_OBSOLETE;
279
280 if (strv_contains(t->protected_versions, cursor))
281 extra_flags |= UPDATE_PROTECTED;
282 }
283
284 if (!cursor) /* EOL */
285 break;
286
287 r = free_and_strdup_warn(&boundary, cursor);
288 if (r < 0)
289 return r;
290
291 if (incomplete) /* One transfer was missing this version, ignore the whole thing */
292 continue;
293
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)
297 continue;
298
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;
301 exists = true;
302 newest_found = true;
303 break;
304 }
305
306 if (exists)
307 continue;
308
309 /* Doesn't exist yet, let's add it */
310 if (!GREEDY_REALLOC(c->update_sets, c->n_update_sets + 1))
311 return log_oom();
312
313 us = new(UpdateSet, 1);
314 if (!us)
315 return log_oom();
316
317 *us = (UpdateSet) {
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,
322 };
323
324 c->update_sets[c->n_update_sets++] = us;
325
326 newest_found = true;
327
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;
331
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))
334 c->candidate = us;
335 }
336
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)
339 c->candidate = NULL;
340
341 return 0;
342 }
343
344 static int context_discover_update_sets(Context *c) {
345 int r;
346
347 assert(c);
348
349 log_info("Determining installed update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
350
351 r = context_discover_update_sets_by_flag(c, UPDATE_INSTALLED);
352 if (r < 0)
353 return r;
354
355 log_info("Determining available update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
356
357 r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
358 if (r < 0)
359 return r;
360
361 typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
362 return 0;
363 }
364
365 static const char *update_set_flags_to_string(UpdateSetFlags flags) {
366
367 switch ((unsigned) flags) {
368
369 case 0:
370 return "n/a";
371
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:
376 return "current";
377
378 case UPDATE_AVAILABLE|UPDATE_NEWEST:
379 case UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
380 return "candidate";
381
382 case UPDATE_INSTALLED:
383 case UPDATE_INSTALLED|UPDATE_AVAILABLE:
384 return "installed";
385
386 case UPDATE_INSTALLED|UPDATE_PROTECTED:
387 case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PROTECTED:
388 return "protected";
389
390 case UPDATE_AVAILABLE:
391 case UPDATE_AVAILABLE|UPDATE_PROTECTED:
392 return "available";
393
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";
399
400 case UPDATE_INSTALLED|UPDATE_OBSOLETE:
401 case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE:
402 return "installed+obsolete";
403
404 case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_PROTECTED:
405 case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_PROTECTED:
406 return "protected+obsolete";
407
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";
413
414 default:
415 assert_not_reached();
416 }
417 }
418
419
420 static int context_show_table(Context *c) {
421 _cleanup_(table_unrefp) Table *t = NULL;
422 int r;
423
424 assert(c);
425
426 t = table_new("", "version", "installed", "available", "assessment");
427 if (!t)
428 return log_oom();
429
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);
433
434 for (size_t i = 0; i < c->n_update_sets; i++) {
435 UpdateSet *us = c->update_sets[i];
436 const char *color;
437
438 color = update_set_flags_to_color(us->flags);
439
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);
451 if (r < 0)
452 return table_log_add_error(r);
453 }
454
455 return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
456 }
457
458 static UpdateSet *context_update_set_by_version(Context *c, const char *version) {
459 assert(c);
460 assert(version);
461
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];
465
466 return NULL;
467 }
468
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;
475 UpdateSet *us;
476 int r;
477
478 assert(c);
479 assert(version);
480
481 us = context_update_set_by_version(c, version);
482 if (!us)
483 return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
484
485 if (arg_json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
486 (void) pager_open(arg_pager_flags);
487
488 if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
489 printf("%s%s%s Version: %s\n"
490 " State: %s%s%s\n"
491 "Installed: %s%s\n"
492 "Available: %s%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());
501
502
503 t = table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
504 if (!t)
505 return log_oom();
506
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, "-");
514
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];
518
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;
523 }
524
525 for (size_t n = 0; n < us->n_instances; n++) {
526 Instance *i = us->instances[n];
527
528 r = table_add_many(t,
529 TABLE_STRING, resource_type_to_string(i->resource->type),
530 TABLE_PATH, i->path);
531 if (r < 0)
532 return table_log_add_error(r);
533
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);
537 } else
538 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
539 if (r < 0)
540 return table_log_add_error(r);
541
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);
545 } else
546 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
547 if (r < 0)
548 return table_log_add_error(r);
549
550 if (i->metadata.mtime != USEC_INFINITY) {
551 have_fs_attributes = true;
552 r = table_add_cell(t, NULL, TABLE_TIMESTAMP, &i->metadata.mtime);
553 } else
554 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
555 if (r < 0)
556 return table_log_add_error(r);
557
558 if (i->metadata.mode != MODE_INVALID) {
559 have_fs_attributes = true;
560 r = table_add_cell(t, NULL, TABLE_MODE, &i->metadata.mode);
561 } else
562 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
563 if (r < 0)
564 return table_log_add_error(r);
565
566 if (i->metadata.size != UINT64_MAX) {
567 have_size = true;
568 r = table_add_cell(t, NULL, TABLE_SIZE, &i->metadata.size);
569 } else
570 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
571 if (r < 0)
572 return table_log_add_error(r);
573
574 if (i->metadata.tries_done != UINT64_MAX) {
575 have_tries = true;
576 r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_done);
577 } else
578 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
579 if (r < 0)
580 return table_log_add_error(r);
581
582 if (i->metadata.tries_left != UINT64_MAX) {
583 have_tries = true;
584 r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_left);
585 } else
586 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
587 if (r < 0)
588 return table_log_add_error(r);
589
590 if (i->metadata.no_auto >= 0) {
591 bool b;
592
593 have_no_auto = true;
594 b = i->metadata.no_auto;
595 r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
596 } else
597 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
598 if (r < 0)
599 return table_log_add_error(r);
600 if (i->metadata.read_only >= 0) {
601 bool b;
602
603 have_read_only = true;
604 b = i->metadata.read_only;
605 r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
606 } else
607 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
608 if (r < 0)
609 return table_log_add_error(r);
610
611 if (i->metadata.growfs >= 0) {
612 bool b;
613
614 have_growfs = true;
615 b = i->metadata.growfs;
616 r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
617 } else
618 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
619 if (r < 0)
620 return table_log_add_error(r);
621
622 if (i->metadata.sha256sum_set) {
623 _cleanup_free_ char *formatted = NULL;
624
625 have_sha256 = true;
626
627 formatted = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
628 if (!formatted)
629 return log_oom();
630
631 r = table_add_cell(t, NULL, TABLE_STRING, formatted);
632 } else
633 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
634 if (r < 0)
635 return table_log_add_error(r);
636 }
637
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;
643
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);
648 if (!have_size)
649 (void) table_hide_column_from_display(t, 6);
650 if (!have_tries)
651 (void) table_hide_column_from_display(t, 7, 8);
652 if (!have_no_auto)
653 (void) table_hide_column_from_display(t, 9);
654 if (!have_read_only)
655 (void) table_hide_column_from_display(t, 10);
656 if (!have_growfs)
657 (void) table_hide_column_from_display(t, 11);
658 if (!have_sha256)
659 (void) table_hide_column_from_display(t, 12);
660
661 return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
662 }
663
664 static int context_vacuum(
665 Context *c,
666 uint64_t space,
667 const char *extra_protected_version) {
668
669 int r, count = 0;
670
671 assert(c);
672
673 if (space == 0)
674 log_info("Making room%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
675 else
676 log_info("Making room for %" PRIu64 " updates%s", space,special_glyph(SPECIAL_GLYPH_ELLIPSIS));
677
678 for (size_t i = 0; i < c->n_transfers; i++) {
679 r = transfer_vacuum(c->transfers[i], space, extra_protected_version);
680 if (r < 0)
681 return r;
682
683 count = MAX(count, r);
684 }
685
686 if (count > 0)
687 log_info("Removed %i instances.", count);
688 else
689 log_info("Removed no instances.");
690
691 return 0;
692 }
693
694 static int context_make_offline(Context **ret, const char *node) {
695 _cleanup_(context_freep) Context* context = NULL;
696 int r;
697
698 assert(ret);
699
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 */
702
703 context = context_new();
704 if (!context)
705 return log_oom();
706
707 r = context_read_definitions(context, arg_definitions, arg_component, arg_root, node);
708 if (r < 0)
709 return r;
710
711 r = context_load_installed_instances(context);
712 if (r < 0)
713 return r;
714
715 *ret = TAKE_PTR(context);
716 return 0;
717 }
718
719 static int context_make_online(Context **ret, const char *node) {
720 _cleanup_(context_freep) Context* context = NULL;
721 int r;
722
723 assert(ret);
724
725 /* Like context_make_offline(), but also communicates with the update source looking for new
726 * versions. */
727
728 r = context_make_offline(&context, node);
729 if (r < 0)
730 return r;
731
732 r = context_load_available_instances(context);
733 if (r < 0)
734 return r;
735
736 r = context_discover_update_sets(context);
737 if (r < 0)
738 return r;
739
740 *ret = TAKE_PTR(context);
741 return 0;
742 }
743
744 static int context_apply(
745 Context *c,
746 const char *version,
747 UpdateSet **ret_applied) {
748
749 UpdateSet *us = NULL;
750 int r;
751
752 assert(c);
753
754 if (version) {
755 us = context_update_set_by_version(c, version);
756 if (!us)
757 return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
758 } else {
759 if (!c->candidate) {
760 log_info("No update needed.");
761
762 if (ret_applied)
763 *ret_applied = NULL;
764
765 return 0;
766 }
767
768 us = c->candidate;
769 }
770
771 if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
772 log_info("Selected update '%s' is already installed. Skipping update.", us->version);
773
774 if (ret_applied)
775 *ret_applied = NULL;
776
777 return 0;
778 }
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);
783
784 assert((us->flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_OBSOLETE)) == UPDATE_AVAILABLE);
785
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);
790
791 log_info("Selected update '%s' for install.", us->version);
792
793 (void) sd_notifyf(false,
794 "STATUS=Making room for '%s'.", us->version);
795
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 */
799 r = context_vacuum(
800 c,
801 /* space = */ 1,
802 /* extra_protected_version = */ us->version);
803 if (r < 0)
804 return r;
805
806 if (arg_sync)
807 sync();
808
809 (void) sd_notifyf(false,
810 "STATUS=Updating to '%s'.\n", us->version);
811
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);
814
815 for (size_t i = 0; i < c->n_transfers; i++) {
816 r = transfer_acquire_instance(c->transfers[i], us->instances[i]);
817 if (r < 0)
818 return r;
819 }
820
821 if (arg_sync)
822 sync();
823
824 for (size_t i = 0; i < c->n_transfers; i++) {
825 r = transfer_install_instance(c->transfers[i], us->instances[i], arg_root);
826 if (r < 0)
827 return r;
828 }
829
830 log_info("%s Successfully installed update '%s'.", special_glyph(SPECIAL_GLYPH_SPARKLES), us->version);
831
832 if (ret_applied)
833 *ret_applied = us;
834
835 return 1;
836 }
837
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;
841 int r;
842
843 r = sd_bus_open_system(&bus);
844 if (r < 0)
845 return log_error_errno(r, "Failed to open bus connection: %m");
846
847 r = bus_call_method(bus, bus_login_mgr, "RebootWithFlags", &error, NULL, "t",
848 (uint64_t) SD_LOGIND_ROOT_CHECK_INHIBITORS);
849 if (r < 0)
850 return log_error_errno(r, "Failed to issue reboot request: %s", bus_error_message(&error, r));
851
852 return 0;
853 }
854
855 static int process_image(
856 bool ro,
857 char **ret_mounted_dir,
858 LoopDevice **ret_loop_device) {
859
860 _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
861 _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
862 int r;
863
864 assert(ret_mounted_dir);
865 assert(ret_loop_device);
866
867 if (!arg_image)
868 return 0;
869
870 assert(!arg_root);
871
872 r = mount_image_privately_interactively(
873 arg_image,
874 (ro ? DISSECT_IMAGE_READ_ONLY : 0) |
875 DISSECT_IMAGE_FSCK |
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,
882 &mounted_dir,
883 &loop_device);
884 if (r < 0)
885 return r;
886
887 arg_root = strdup(mounted_dir);
888 if (!arg_root)
889 return log_oom();
890
891 *ret_mounted_dir = TAKE_PTR(mounted_dir);
892 *ret_loop_device = TAKE_PTR(loop_device);
893
894 return 0;
895 }
896
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;
901 const char *version;
902 int r;
903
904 assert(argc <= 2);
905 version = argc >= 2 ? argv[1] : NULL;
906
907 r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
908 if (r < 0)
909 return r;
910
911 r = context_make_online(&context, loop_device ? loop_device->node : NULL);
912 if (r < 0)
913 return r;
914
915 if (version)
916 return context_show_version(context, version);
917 else
918 return context_show_table(context);
919 }
920
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;
925 int r;
926
927 assert(argc <= 1);
928
929 r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
930 if (r < 0)
931 return r;
932
933 r = context_make_online(&context, loop_device ? loop_device->node : NULL);
934 if (r < 0)
935 return r;
936
937 if (!context->candidate) {
938 log_debug("No candidate found.");
939 return EXIT_FAILURE;
940 }
941
942 puts(context->candidate->version);
943 return EXIT_SUCCESS;
944 }
945
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;
950 int r;
951
952 assert(argc <= 1);
953
954 r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
955 if (r < 0)
956 return r;
957
958 r = context_make_offline(&context, loop_device ? loop_device->node : NULL);
959 if (r < 0)
960 return r;
961
962 return context_vacuum(context, 0, NULL);
963 }
964
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;
971 const char *version;
972 int r;
973
974 assert(argc <= 2);
975 version = argc >= 2 ? argv[1] : NULL;
976
977 if (arg_reboot) {
978 /* If automatic reboot on completion is requested, let's first determine the currently booted image */
979
980 r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version);
981 if (r < 0)
982 return log_error_errno(r, "Failed to parse /etc/os-release: %m");
983 if (!booted_version)
984 return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
985 }
986
987 r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
988 if (r < 0)
989 return r;
990
991 r = context_make_online(&context, loop_device ? loop_device->node : NULL);
992 if (r < 0)
993 return r;
994
995 r = context_apply(context, version, &applied);
996 if (r < 0)
997 return r;
998
999 if (r > 0 && arg_reboot) {
1000 assert(applied);
1001 assert(booted_version);
1002
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();
1006 }
1007
1008 log_info("Booted version is newer or identical to newly installed version, not rebooting.");
1009 }
1010
1011 return 0;
1012 }
1013
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;
1017 int r;
1018
1019 assert(argc == 1);
1020
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]);
1024
1025 r = context_make_offline(&context, NULL);
1026 if (r < 0)
1027 return r;
1028
1029 log_info("Determining installed update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
1030
1031 r = context_discover_update_sets_by_flag(context, UPDATE_INSTALLED);
1032 if (r < 0)
1033 return r;
1034 if (!context->newest_installed)
1035 return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions.");
1036
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.");
1043
1044 r = strverscmp_improved(context->newest_installed->version, booted_version);
1045 if (r > 0) {
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." : "");
1049
1050 if (streq(argv[0], "reboot"))
1051 return reboot_now();
1052
1053 return EXIT_SUCCESS;
1054 } else if (r == 0)
1055 log_info("Newest installed version '%s' matches booted version '%s'.",
1056 context->newest_installed->version, booted_version);
1057 else
1058 log_warning("Newest installed version '%s' is older than booted version '%s'.",
1059 context->newest_installed->version, booted_version);
1060
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;
1063
1064 return EXIT_SUCCESS;
1065 }
1066
1067 static int component_name_valid(const char *c) {
1068 _cleanup_free_ char *j = NULL;
1069
1070 /* See if the specified string enclosed in the directory prefix+suffix would be a valid file name */
1071
1072 if (isempty(c))
1073 return false;
1074
1075 if (string_has_cc(c, NULL))
1076 return false;
1077
1078 if (!utf8_is_valid(c))
1079 return false;
1080
1081 j = strjoin("sysupdate.", c, ".d");
1082 if (!j)
1083 return -ENOMEM;
1084
1085 return filename_is_valid(j);
1086 }
1087
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;
1095 int r;
1096
1097 assert(argc <= 1);
1098
1099 r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
1100 if (r < 0)
1101 return r;
1102
1103 STRV_FOREACH(i, l) {
1104 _cleanup_closedir_ DIR *d = NULL;
1105 _cleanup_free_ char *p = NULL;
1106
1107 r = chase_symlinks_and_opendir(*i, arg_root, CHASE_PREFIX_ROOT, &p, &d);
1108 if (r == -ENOENT)
1109 continue;
1110 if (r < 0)
1111 return log_error_errno(r, "Failed to open directory '%s': %m", *i);
1112
1113 for (;;) {
1114 _cleanup_free_ char *n = NULL;
1115 struct dirent *de;
1116 const char *e, *a;
1117
1118 de = readdir_ensure_type(d);
1119 if (!de) {
1120 if (errno != 0)
1121 return log_error_errno(errno, "Failed to enumerate directory '%s': %m", p);
1122
1123 break;
1124 }
1125
1126 if (de->d_type != DT_DIR)
1127 continue;
1128
1129 if (dot_or_dot_dot(de->d_name))
1130 continue;
1131
1132 if (streq(de->d_name, "sysupdate.d")) {
1133 has_default_component = true;
1134 continue;
1135 }
1136
1137 e = startswith(de->d_name, "sysupdate.");
1138 if (!e)
1139 continue;
1140
1141 a = endswith(e, ".d");
1142 if (!a)
1143 continue;
1144
1145 n = strndup(e, a - e);
1146 if (!n)
1147 return log_oom();
1148
1149 r = component_name_valid(n);
1150 if (r < 0)
1151 return log_error_errno(r, "Unable to validate component name: %m");
1152 if (r == 0)
1153 continue;
1154
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");
1158 }
1159 }
1160
1161 if (!has_default_component && set_isempty(names)) {
1162 log_info("No components defined.");
1163 return 0;
1164 }
1165
1166 z = set_get_strv(names);
1167 if (!z)
1168 return log_oom();
1169
1170 strv_sort(z);
1171
1172 if (has_default_component)
1173 printf("%s<default>%s\n",
1174 ansi_highlight(), ansi_normal());
1175
1176 STRV_FOREACH(i, z)
1177 puts(*i);
1178
1179 return 0;
1180 }
1181
1182 static int verb_help(int argc, char **argv, void *userdata) {
1183 _cleanup_free_ char *link = NULL;
1184 int r;
1185
1186 r = terminal_urlify_man("systemd-sysupdate", "1", &link);
1187 if (r < 0)
1188 return log_oom();
1189
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
1218 , link
1219 , ansi_underline(), ansi_normal()
1220 , ansi_highlight(), ansi_normal()
1221 );
1222
1223 return 0;
1224 }
1225
1226 static int parse_argv(int argc, char *argv[]) {
1227
1228 enum {
1229 ARG_VERSION = 0x100,
1230 ARG_NO_PAGER,
1231 ARG_NO_LEGEND,
1232 ARG_SYNC,
1233 ARG_DEFINITIONS,
1234 ARG_JSON,
1235 ARG_ROOT,
1236 ARG_IMAGE,
1237 ARG_REBOOT,
1238 ARG_VERIFY,
1239 };
1240
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 },
1255 {}
1256 };
1257
1258 int c, r;
1259
1260 assert(argc >= 0);
1261 assert(argv);
1262
1263 while ((c = getopt_long(argc, argv, "hm:C:", options, NULL)) >= 0) {
1264
1265 switch (c) {
1266
1267 case 'h':
1268 return verb_help(0, NULL, NULL);
1269
1270 case ARG_VERSION:
1271 return version();
1272
1273 case ARG_NO_PAGER:
1274 arg_pager_flags |= PAGER_DISABLE;
1275 break;
1276
1277 case ARG_NO_LEGEND:
1278 arg_legend = false;
1279 break;
1280
1281 case 'm':
1282 r = safe_atou64(optarg, &arg_instances_max);
1283 if (r < 0)
1284 return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", optarg);
1285
1286 break;
1287
1288 case ARG_SYNC:
1289 r = parse_boolean_argument("--sync=", optarg, &arg_sync);
1290 if (r < 0)
1291 return r;
1292 break;
1293
1294 case ARG_DEFINITIONS:
1295 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_definitions);
1296 if (r < 0)
1297 return r;
1298 break;
1299
1300 case ARG_JSON:
1301 r = parse_json_argument(optarg, &arg_json_format_flags);
1302 if (r <= 0)
1303 return r;
1304
1305 break;
1306
1307 case ARG_ROOT:
1308 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
1309 if (r < 0)
1310 return r;
1311 break;
1312
1313 case ARG_IMAGE:
1314 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
1315 if (r < 0)
1316 return r;
1317 break;
1318
1319 case ARG_REBOOT:
1320 arg_reboot = true;
1321 break;
1322
1323 case 'C':
1324 if (isempty(optarg)) {
1325 arg_component = mfree(arg_component);
1326 break;
1327 }
1328
1329 r = component_name_valid(optarg);
1330 if (r < 0)
1331 return log_error_errno(r, "Failed to determine if component name is valid: %m");
1332 if (r == 0)
1333 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", optarg);
1334
1335 r = free_and_strdup_warn(&arg_component, optarg);
1336 if (r < 0)
1337 return r;
1338
1339 break;
1340
1341 case ARG_VERIFY: {
1342 bool b;
1343
1344 r = parse_boolean_argument("--verify=", optarg, &b);
1345 if (r < 0)
1346 return r;
1347
1348 arg_verify = b;
1349 break;
1350 }
1351
1352 case '?':
1353 return -EINVAL;
1354
1355 default:
1356 assert_not_reached();
1357 }
1358 }
1359
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.");
1362
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=.");
1365
1366 if (arg_definitions && arg_component)
1367 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
1368
1369 return 1;
1370 }
1371
1372 static int sysupdate_main(int argc, char *argv[]) {
1373
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 },
1383 {}
1384 };
1385
1386 return dispatch_verb(argc, argv, verbs, NULL);
1387 }
1388
1389 static int run(int argc, char *argv[]) {
1390 int r;
1391
1392 log_setup();
1393
1394 r = parse_argv(argc, argv);
1395 if (r <= 0)
1396 return r;
1397
1398 return sysupdate_main(argc, argv);
1399 }
1400
1401 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);