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