]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/portable/portablectl.c
Merge pull request #27033 from dtardon/array-cleanup
[thirdparty/systemd.git] / src / portable / portablectl.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <getopt.h>
5
6 #include "sd-bus.h"
7
8 #include "alloc-util.h"
9 #include "build.h"
10 #include "bus-error.h"
11 #include "bus-locator.h"
12 #include "bus-unit-util.h"
13 #include "bus-wait-for-jobs.h"
14 #include "chase.h"
15 #include "constants.h"
16 #include "dirent-util.h"
17 #include "env-file.h"
18 #include "fd-util.h"
19 #include "fileio.h"
20 #include "format-table.h"
21 #include "fs-util.h"
22 #include "locale-util.h"
23 #include "main-func.h"
24 #include "os-util.h"
25 #include "pager.h"
26 #include "parse-argument.h"
27 #include "parse-util.h"
28 #include "path-util.h"
29 #include "portable.h"
30 #include "pretty-print.h"
31 #include "spawn-polkit-agent.h"
32 #include "string-util.h"
33 #include "strv.h"
34 #include "terminal-util.h"
35 #include "verbs.h"
36
37 static PagerFlags arg_pager_flags = 0;
38 static bool arg_legend = true;
39 static bool arg_ask_password = true;
40 static bool arg_quiet = false;
41 static const char *arg_profile = "default";
42 static const char* arg_copy_mode = NULL;
43 static bool arg_runtime = false;
44 static bool arg_reload = true;
45 static bool arg_cat = false;
46 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
47 static const char *arg_host = NULL;
48 static bool arg_enable = false;
49 static bool arg_now = false;
50 static bool arg_no_block = false;
51 static char **arg_extension_images = NULL;
52 static bool arg_force = false;
53
54 STATIC_DESTRUCTOR_REGISTER(arg_extension_images, strv_freep);
55
56 static bool is_portable_managed(const char *unit) {
57 return ENDSWITH_SET(unit, ".service", ".target", ".socket", ".path", ".timer");
58 }
59
60 static int determine_image(const char *image, bool permit_non_existing, char **ret) {
61 int r;
62
63 /* If the specified name is a valid image name, we pass it as-is to portabled, which will search for it in the
64 * usual search directories. Otherwise we presume it's a path, and will normalize it on the client's side
65 * (among other things, to make the path independent of the client's working directory) before passing it
66 * over. */
67
68 if (image_name_is_valid(image)) {
69 char *c;
70
71 if (!arg_quiet && laccess(image, F_OK) >= 0)
72 log_warning("Ambiguous invocation: current working directory contains file matching non-path argument '%s', ignoring. "
73 "Prefix argument with './' to force reference to file in current working directory.", image);
74
75 c = strdup(image);
76 if (!c)
77 return log_oom();
78
79 *ret = c;
80 return 0;
81 }
82
83 if (arg_transport != BUS_TRANSPORT_LOCAL)
84 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
85 "Operations on images by path not supported when connecting to remote systems.");
86
87 r = chase(image, NULL, CHASE_TRAIL_SLASH | (permit_non_existing ? CHASE_NONEXISTENT : 0), ret, NULL);
88 if (r < 0)
89 return log_error_errno(r, "Cannot normalize specified image path '%s': %m", image);
90
91 return 0;
92 }
93
94 static int attach_extensions_to_message(sd_bus_message *m, char **extensions) {
95 int r;
96
97 assert(m);
98
99 if (strv_isempty(extensions))
100 return 0;
101
102 r = sd_bus_message_open_container(m, 'a', "s");
103 if (r < 0)
104 return bus_log_create_error(r);
105
106 STRV_FOREACH(p, extensions) {
107 _cleanup_free_ char *resolved_extension_image = NULL;
108
109 r = determine_image(*p, false, &resolved_extension_image);
110 if (r < 0)
111 return r;
112
113 r = sd_bus_message_append(m, "s", resolved_extension_image);
114 if (r < 0)
115 return bus_log_create_error(r);
116 }
117
118 r = sd_bus_message_close_container(m);
119 if (r < 0)
120 return bus_log_create_error(r);
121
122 return 0;
123 }
124
125 static int extract_prefix(const char *path, char **ret) {
126 _cleanup_free_ char *name = NULL, *bn = NULL;
127 const char *underscore;
128 size_t m;
129 int r;
130
131 r = path_extract_filename(path, &bn);
132 if (r < 0)
133 return r;
134
135 underscore = strchr(bn, '_');
136 if (underscore)
137 m = underscore - bn;
138 else {
139 const char *e;
140
141 e = endswith(bn, ".raw");
142 if (!e)
143 e = strchr(bn, 0);
144
145 m = e - bn;
146 }
147
148 name = strndup(bn, m);
149 if (!name)
150 return -ENOMEM;
151
152 /* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_'
153 * which we use as delimiter for the second part of the image string, which we ignore for now. */
154 if (!in_charset(name, DIGITS LETTERS "-."))
155 return -EINVAL;
156
157 if (!filename_is_valid(name))
158 return -EINVAL;
159
160 *ret = TAKE_PTR(name);
161 return 0;
162 }
163
164 static int determine_matches(const char *image, char **l, bool allow_any, char ***ret) {
165 _cleanup_strv_free_ char **k = NULL;
166 int r;
167
168 /* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list
169 * contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly
170 * permitted. */
171
172 if (strv_isempty(l)) {
173 char *prefix;
174
175 r = extract_prefix(image, &prefix);
176 if (r < 0)
177 return log_error_errno(r, "Failed to extract prefix of image name '%s': %m", image);
178
179 if (!arg_quiet)
180 log_info("(Matching unit files with prefix '%s'.)", prefix);
181
182 r = strv_consume(&k, prefix);
183 if (r < 0)
184 return log_oom();
185
186 } else if (strv_equal(l, STRV_MAKE("-"))) {
187
188 if (!allow_any)
189 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
190 "Refusing all unit file match.");
191
192 if (!arg_quiet)
193 log_info("(Matching all unit files.)");
194 } else {
195
196 k = strv_copy(l);
197 if (!k)
198 return log_oom();
199
200 if (!arg_quiet) {
201 _cleanup_free_ char *joined = NULL;
202
203 joined = strv_join(k, "', '");
204 if (!joined)
205 return log_oom();
206
207 log_info("(Matching unit files with prefixes '%s'.)", joined);
208 }
209 }
210
211 *ret = TAKE_PTR(k);
212
213 return 0;
214 }
215
216 static int acquire_bus(sd_bus **bus) {
217 int r;
218
219 assert(bus);
220
221 if (*bus)
222 return 0;
223
224 r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, bus);
225 if (r < 0)
226 return bus_log_connect_error(r, arg_transport);
227
228 (void) sd_bus_set_allow_interactive_authorization(*bus, arg_ask_password);
229
230 return 0;
231 }
232
233 static int maybe_reload(sd_bus **bus) {
234 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
235 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
236 int r;
237
238 if (!arg_reload)
239 return 0;
240
241 r = acquire_bus(bus);
242 if (r < 0)
243 return r;
244
245 r = sd_bus_message_new_method_call(
246 *bus,
247 &m,
248 "org.freedesktop.systemd1",
249 "/org/freedesktop/systemd1",
250 "org.freedesktop.systemd1.Manager",
251 "Reload");
252 if (r < 0)
253 return bus_log_create_error(r);
254
255 /* Reloading the daemon may take long, hence set a longer timeout here */
256 r = sd_bus_call(*bus, m, DAEMON_RELOAD_TIMEOUT_SEC, &error, NULL);
257 if (r < 0)
258 return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
259
260 return 0;
261 }
262
263 static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd_bus_message **reply) {
264 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
265 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
266 uint64_t flags = arg_force ? PORTABLE_FORCE_SYSEXT : 0;
267 const char *method;
268 int r;
269
270 assert(bus);
271 assert(reply);
272
273 method = strv_isempty(arg_extension_images) ? "GetImageMetadata" : "GetImageMetadataWithExtensions";
274
275 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
276 if (r < 0)
277 return bus_log_create_error(r);
278
279 r = sd_bus_message_append(m, "s", image);
280 if (r < 0)
281 return bus_log_create_error(r);
282
283 r = attach_extensions_to_message(m, arg_extension_images);
284 if (r < 0)
285 return r;
286
287 r = sd_bus_message_append_strv(m, matches);
288 if (r < 0)
289 return bus_log_create_error(r);
290
291 if (!strv_isempty(arg_extension_images)) {
292 r = sd_bus_message_append(m, "t", flags);
293 if (r < 0)
294 return bus_log_create_error(r);
295 }
296
297 r = sd_bus_call(bus, m, 0, &error, reply);
298 if (r < 0)
299 return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r));
300
301 return 0;
302 }
303
304 static int inspect_image(int argc, char *argv[], void *userdata) {
305 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
306 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
307 _cleanup_strv_free_ char **matches = NULL;
308 _cleanup_free_ char *image = NULL;
309 bool nl = false, header = false;
310 const char *path;
311 const void *data;
312 size_t sz;
313 int r;
314
315 r = determine_image(argv[1], false, &image);
316 if (r < 0)
317 return r;
318
319 r = determine_matches(argv[1], argv + 2, true, &matches);
320 if (r < 0)
321 return r;
322
323 r = acquire_bus(&bus);
324 if (r < 0)
325 return r;
326
327 r = get_image_metadata(bus, image, matches, &reply);
328 if (r < 0)
329 return r;
330
331 r = sd_bus_message_read(reply, "s", &path);
332 if (r < 0)
333 return bus_log_parse_error(r);
334
335 r = sd_bus_message_read_array(reply, 'y', &data, &sz);
336 if (r < 0)
337 return bus_log_parse_error(r);
338
339 pager_open(arg_pager_flags);
340
341 if (arg_cat) {
342 printf("%s-- OS Release: --%s\n", ansi_highlight(), ansi_normal());
343 fwrite(data, sz, 1, stdout);
344 fflush(stdout);
345 nl = true;
346 } else {
347 _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL;
348 _cleanup_fclose_ FILE *f = NULL;
349
350 f = fmemopen_unlocked((void*) data, sz, "r");
351 if (!f)
352 return log_error_errno(errno, "Failed to open /etc/os-release buffer: %m");
353
354 r = parse_env_file(f, "/etc/os-release",
355 "PORTABLE_PRETTY_NAME", &pretty_portable,
356 "PRETTY_NAME", &pretty_os);
357 if (r < 0)
358 return log_error_errno(r, "Failed to parse /etc/os-release: %m");
359
360 printf("Image:\n\t%s\n"
361 "Portable Service:\n\t%s\n"
362 "Operating System:\n\t%s\n",
363 path,
364 strna(pretty_portable),
365 strna(pretty_os));
366 }
367
368 if (!strv_isempty(arg_extension_images)) {
369 /* If we specified any extensions, we'll first get back exactly the paths (and
370 * extension-release content) for each one of the arguments. */
371
372 r = sd_bus_message_enter_container(reply, 'a', "{say}");
373 if (r < 0)
374 return bus_log_parse_error(r);
375
376 for (size_t i = 0; i < strv_length(arg_extension_images); ++i) {
377 const char *name;
378
379 r = sd_bus_message_enter_container(reply, 'e', "say");
380 if (r < 0)
381 return bus_log_parse_error(r);
382 if (r == 0)
383 break;
384
385 r = sd_bus_message_read(reply, "s", &name);
386 if (r < 0)
387 return bus_log_parse_error(r);
388
389 r = sd_bus_message_read_array(reply, 'y', &data, &sz);
390 if (r < 0)
391 return bus_log_parse_error(r);
392
393 if (arg_cat) {
394 if (nl)
395 fputc('\n', stdout);
396
397 printf("%s-- Extension Release: %s --%s\n", ansi_highlight(), name, ansi_normal());
398 fwrite(data, sz, 1, stdout);
399 fflush(stdout);
400 nl = true;
401 } else {
402 _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL, *sysext_level = NULL,
403 *sysext_id = NULL, *sysext_version_id = NULL, *sysext_scope = NULL, *portable_prefixes = NULL,
404 *id = NULL, *version_id = NULL, *image_id = NULL, *image_version = NULL, *build_id = NULL;
405 _cleanup_fclose_ FILE *f = NULL;
406
407 f = fmemopen_unlocked((void*) data, sz, "r");
408 if (!f)
409 return log_error_errno(errno, "Failed to open extension-release buffer: %m");
410
411 r = parse_env_file(f, name,
412 "SYSEXT_ID", &sysext_id,
413 "SYSEXT_VERSION_ID", &sysext_version_id,
414 "SYSEXT_BUILD_ID", &build_id,
415 "SYSEXT_IMAGE_ID", &image_id,
416 "SYSEXT_IMAGE_VERSION", &image_version,
417 "SYSEXT_PRETTY_NAME", &pretty_os,
418 "SYSEXT_SCOPE", &sysext_scope,
419 "SYSEXT_LEVEL", &sysext_level,
420 "ID", &id,
421 "VERSION_ID", &version_id,
422 "PORTABLE_PRETTY_NAME", &pretty_portable,
423 "PORTABLE_PREFIXES", &portable_prefixes);
424 if (r < 0)
425 return log_error_errno(r, "Failed to parse extension release from '%s': %m", name);
426
427 printf("Extension:\n\t%s\n"
428 "\tExtension Scope:\n\t\t%s\n"
429 "\tExtension Compatibility Level:\n\t\t%s\n"
430 "\tExtension Compatibility OS:\n\t\t%s\n"
431 "\tExtension Compatibility OS Version:\n\t\t%s\n"
432 "\tPortable Service:\n\t\t%s\n"
433 "\tPortable Prefixes:\n\t\t%s\n"
434 "\tExtension Image:\n\t\t%s%s%s %s%s%s\n",
435 name,
436 strna(sysext_scope),
437 strna(sysext_level),
438 strna(id),
439 strna(version_id),
440 strna(pretty_portable),
441 strna(portable_prefixes),
442 strempty(pretty_os),
443 pretty_os ? " (" : "ID: ",
444 strna(sysext_id ?: image_id),
445 pretty_os ? "" : "Version: ",
446 strna(sysext_version_id ?: image_version ?: build_id),
447 pretty_os ? ")" : "");
448 }
449
450 r = sd_bus_message_exit_container(reply);
451 if (r < 0)
452 return bus_log_parse_error(r);
453 }
454
455 r = sd_bus_message_exit_container(reply);
456 if (r < 0)
457 return bus_log_parse_error(r);
458 }
459
460 r = sd_bus_message_enter_container(reply, 'a', "{say}");
461 if (r < 0)
462 return bus_log_parse_error(r);
463
464 for (;;) {
465 const char *name;
466
467 r = sd_bus_message_enter_container(reply, 'e', "say");
468 if (r < 0)
469 return bus_log_parse_error(r);
470 if (r == 0)
471 break;
472
473 r = sd_bus_message_read(reply, "s", &name);
474 if (r < 0)
475 return bus_log_parse_error(r);
476
477 r = sd_bus_message_read_array(reply, 'y', &data, &sz);
478 if (r < 0)
479 return bus_log_parse_error(r);
480
481 if (arg_cat) {
482 if (nl)
483 fputc('\n', stdout);
484
485 printf("%s-- Unit file: %s --%s\n", ansi_highlight(), name, ansi_normal());
486 fwrite(data, sz, 1, stdout);
487 fflush(stdout);
488 nl = true;
489 } else {
490 if (!header) {
491 fputs("Unit files:\n", stdout);
492 header = true;
493 }
494
495 fputc('\t', stdout);
496 fputs(name, stdout);
497 fputc('\n', stdout);
498 }
499
500 r = sd_bus_message_exit_container(reply);
501 if (r < 0)
502 return bus_log_parse_error(r);
503 }
504
505 r = sd_bus_message_exit_container(reply);
506 if (r < 0)
507 return bus_log_parse_error(r);
508
509 return 0;
510 }
511
512 static int print_changes(sd_bus_message *m) {
513 int r;
514
515 if (arg_quiet)
516 return 0;
517
518 r = sd_bus_message_enter_container(m, 'a', "(sss)");
519 if (r < 0)
520 return bus_log_parse_error(r);
521
522 for (;;) {
523 const char *type, *path, *source;
524
525 r = sd_bus_message_read(m, "(sss)", &type, &path, &source);
526 if (r < 0)
527 return bus_log_parse_error(r);
528 if (r == 0)
529 break;
530
531 if (streq(type, "symlink"))
532 log_info("Created symlink %s %s %s.", path, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), source);
533 else if (streq(type, "copy")) {
534 if (isempty(source))
535 log_info("Copied %s.", path);
536 else
537 log_info("Copied %s %s %s.", source, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), path);
538 } else if (streq(type, "unlink"))
539 log_info("Removed %s.", path);
540 else if (streq(type, "write"))
541 log_info("Written %s.", path);
542 else if (streq(type, "mkdir"))
543 log_info("Created directory %s.", path);
544 else
545 log_error("Unexpected change: %s/%s/%s", type, path, source);
546 }
547
548 r = sd_bus_message_exit_container(m);
549 if (r < 0)
550 return r;
551
552 return 0;
553 }
554
555 static int maybe_enable_disable(sd_bus *bus, const char *path, bool enable) {
556 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
557 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
558 _cleanup_strv_free_ char **names = NULL;
559 InstallChange *changes = NULL;
560 const uint64_t flags = UNIT_FILE_PORTABLE | (arg_runtime ? UNIT_FILE_RUNTIME : 0);
561 size_t n_changes = 0;
562 int r;
563
564 CLEANUP_ARRAY(changes, n_changes, install_changes_free);
565
566 if (!arg_enable)
567 return 0;
568
569 names = strv_new(path, NULL);
570 if (!names)
571 return log_oom();
572
573 r = sd_bus_message_new_method_call(
574 bus,
575 &m,
576 "org.freedesktop.systemd1",
577 "/org/freedesktop/systemd1",
578 "org.freedesktop.systemd1.Manager",
579 enable ? "EnableUnitFilesWithFlags" : "DisableUnitFilesWithFlags");
580 if (r < 0)
581 return bus_log_create_error(r);
582
583 r = sd_bus_message_append_strv(m, names);
584 if (r < 0)
585 return bus_log_create_error(r);
586
587 r = sd_bus_message_append(m, "t", flags);
588 if (r < 0)
589 return bus_log_create_error(r);
590
591 r = sd_bus_call(bus, m, 0, &error, &reply);
592 if (r < 0)
593 return log_error_errno(r, "Failed to %s the portable service %s: %s",
594 enable ? "enable" : "disable", path, bus_error_message(&error, r));
595
596 if (enable) {
597 r = sd_bus_message_skip(reply, "b");
598 if (r < 0)
599 return bus_log_parse_error(r);
600 }
601
602 (void) bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
603
604 return 0;
605 }
606
607 static int maybe_start_stop_restart(sd_bus *bus, const char *path, const char *method, BusWaitForJobs *wait) {
608 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
609 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
610 _cleanup_free_ char *name = NULL;
611 const char *job = NULL;
612 int r;
613
614 assert(STR_IN_SET(method, "StartUnit", "StopUnit", "RestartUnit"));
615
616 if (!arg_now)
617 return 0;
618
619 r = path_extract_filename(path, &name);
620 if (r < 0)
621 return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
622
623 r = bus_call_method(
624 bus,
625 bus_systemd_mgr,
626 method,
627 &error,
628 &reply,
629 "ss", name, "replace");
630 if (r < 0)
631 return log_error_errno(r, "Failed to call %s on the portable service %s: %s",
632 method,
633 path,
634 bus_error_message(&error, r));
635
636 r = sd_bus_message_read(reply, "o", &job);
637 if (r < 0)
638 return bus_log_parse_error(r);
639
640 if (!arg_quiet)
641 log_info("Queued %s to call %s on portable service %s.", job, method, name);
642
643 if (wait) {
644 r = bus_wait_for_jobs_add(wait, job);
645 if (r < 0)
646 return log_error_errno(r, "Failed to watch %s job to call %s on %s: %m",
647 job, method, name);
648 }
649
650 return 0;
651 }
652
653 static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) {
654 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
655 int r;
656
657 if (!arg_enable && !arg_now)
658 return 0;
659
660 if (!arg_no_block) {
661 r = bus_wait_for_jobs_new(bus, &wait);
662 if (r < 0)
663 return log_error_errno(r, "Could not watch jobs: %m");
664 }
665
666 r = sd_bus_message_rewind(reply, true);
667 if (r < 0)
668 return r;
669 r = sd_bus_message_enter_container(reply, 'a', "(sss)");
670 if (r < 0)
671 return bus_log_parse_error(r);
672
673 for (;;) {
674 char *type, *path, *source;
675
676 r = sd_bus_message_read(reply, "(sss)", &type, &path, &source);
677 if (r < 0)
678 return bus_log_parse_error(r);
679 if (r == 0)
680 break;
681
682 if (STR_IN_SET(type, "symlink", "copy") && is_portable_managed(path)) {
683 (void) maybe_enable_disable(bus, path, true);
684 (void) maybe_start_stop_restart(bus, path, "StartUnit", wait);
685 }
686 }
687
688 r = sd_bus_message_exit_container(reply);
689 if (r < 0)
690 return r;
691
692 if (!arg_no_block) {
693 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
694 if (r < 0)
695 return r;
696 }
697
698 return 0;
699 }
700
701 static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) {
702 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
703 int r;
704
705 if (!arg_enable && !arg_now)
706 return 0;
707
708 if (!arg_no_block) {
709 r = bus_wait_for_jobs_new(bus, &wait);
710 if (r < 0)
711 return log_error_errno(r, "Could not watch jobs: %m");
712 }
713
714 r = sd_bus_message_rewind(reply, true);
715 if (r < 0)
716 return r;
717
718 /* First we get a list of units that were definitely removed, not just re-attached,
719 * so we can also stop them if the user asked us to. */
720 r = sd_bus_message_enter_container(reply, 'a', "(sss)");
721 if (r < 0)
722 return bus_log_parse_error(r);
723
724 for (;;) {
725 char *type, *path, *source;
726
727 r = sd_bus_message_read(reply, "(sss)", &type, &path, &source);
728 if (r < 0)
729 return bus_log_parse_error(r);
730 if (r == 0)
731 break;
732
733 if (streq(type, "unlink") && is_portable_managed(path))
734 (void) maybe_start_stop_restart(bus, path, "StopUnit", wait);
735 }
736
737 r = sd_bus_message_exit_container(reply);
738 if (r < 0)
739 return r;
740
741 /* Then we get a list of units that were either added or changed, so that we can
742 * enable them and/or restart them if the user asked us to. */
743 r = sd_bus_message_enter_container(reply, 'a', "(sss)");
744 if (r < 0)
745 return bus_log_parse_error(r);
746
747 for (;;) {
748 char *type, *path, *source;
749
750 r = sd_bus_message_read(reply, "(sss)", &type, &path, &source);
751 if (r < 0)
752 return bus_log_parse_error(r);
753 if (r == 0)
754 break;
755
756 if (STR_IN_SET(type, "symlink", "copy") && is_portable_managed(path)) {
757 (void) maybe_enable_disable(bus, path, true);
758 (void) maybe_start_stop_restart(bus, path, "RestartUnit", wait);
759 }
760 }
761
762 r = sd_bus_message_exit_container(reply);
763 if (r < 0)
764 return r;
765
766 if (!arg_no_block) {
767 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
768 if (r < 0)
769 return r;
770 }
771
772 return 0;
773 }
774
775 static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) {
776 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
777 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
778 _cleanup_strv_free_ char **matches = NULL;
779 int r;
780
781 if (!arg_enable && !arg_now)
782 return 0;
783
784 r = determine_matches(argv[1], argv + 2, true, &matches);
785 if (r < 0)
786 return r;
787
788 r = bus_wait_for_jobs_new(bus, &wait);
789 if (r < 0)
790 return log_error_errno(r, "Could not watch jobs: %m");
791
792 r = get_image_metadata(bus, image, matches, &reply);
793 if (r < 0)
794 return r;
795
796 r = sd_bus_message_skip(reply, "say");
797 if (r < 0)
798 return bus_log_parse_error(r);
799
800 /* If we specified any extensions, we'll first an array of extension-release metadata. */
801 if (!strv_isempty(arg_extension_images)) {
802 r = sd_bus_message_skip(reply, "a{say}");
803 if (r < 0)
804 return bus_log_parse_error(r);
805 }
806
807 r = sd_bus_message_enter_container(reply, 'a', "{say}");
808 if (r < 0)
809 return bus_log_parse_error(r);
810
811 for (;;) {
812 const char *name;
813
814 r = sd_bus_message_enter_container(reply, 'e', "say");
815 if (r < 0)
816 return bus_log_parse_error(r);
817 if (r == 0)
818 break;
819
820 r = sd_bus_message_read(reply, "s", &name);
821 if (r < 0)
822 return bus_log_parse_error(r);
823
824 r = sd_bus_message_skip(reply, "ay");
825 if (r < 0)
826 return bus_log_parse_error(r);
827
828 r = sd_bus_message_exit_container(reply);
829 if (r < 0)
830 return bus_log_parse_error(r);
831
832 (void) maybe_start_stop_restart(bus, name, "StopUnit", wait);
833 (void) maybe_enable_disable(bus, name, false);
834 }
835
836 r = sd_bus_message_exit_container(reply);
837 if (r < 0)
838 return bus_log_parse_error(r);
839
840 /* Stopping must always block or the detach will fail if the unit is still running */
841 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
842 if (r < 0)
843 return r;
844
845 return 0;
846 }
847
848 static int attach_reattach_image(int argc, char *argv[], const char *method) {
849 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
850 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
851 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
852 _cleanup_strv_free_ char **matches = NULL;
853 _cleanup_free_ char *image = NULL;
854 int r;
855
856 assert(method);
857 assert(STR_IN_SET(method, "AttachImage", "ReattachImage", "AttachImageWithExtensions", "ReattachImageWithExtensions"));
858
859 r = determine_image(argv[1], false, &image);
860 if (r < 0)
861 return r;
862
863 r = determine_matches(argv[1], argv + 2, false, &matches);
864 if (r < 0)
865 return r;
866
867 r = acquire_bus(&bus);
868 if (r < 0)
869 return r;
870
871 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
872
873 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
874 if (r < 0)
875 return bus_log_create_error(r);
876
877 r = sd_bus_message_append(m, "s", image);
878 if (r < 0)
879 return bus_log_create_error(r);
880
881 r = attach_extensions_to_message(m, arg_extension_images);
882 if (r < 0)
883 return r;
884
885 r = sd_bus_message_append_strv(m, matches);
886 if (r < 0)
887 return bus_log_create_error(r);
888
889 r = sd_bus_message_append(m, "s", arg_profile);
890 if (r < 0)
891 return bus_log_create_error(r);
892
893 if (STR_IN_SET(method, "AttachImageWithExtensions", "ReattachImageWithExtensions")) {
894 uint64_t flags = (arg_runtime ? PORTABLE_RUNTIME : 0) | (arg_force ? PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_SYSEXT : 0);
895
896 r = sd_bus_message_append(m, "st", arg_copy_mode, flags);
897 } else
898 r = sd_bus_message_append(m, "bs", arg_runtime, arg_copy_mode);
899 if (r < 0)
900 return bus_log_create_error(r);
901
902 r = sd_bus_call(bus, m, 0, &error, &reply);
903 if (r < 0)
904 return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r));
905
906 (void) maybe_reload(&bus);
907
908 print_changes(reply);
909
910 if (STR_IN_SET(method, "AttachImage", "AttachImageWithExtensions"))
911 (void) maybe_enable_start(bus, reply);
912 else {
913 /* ReattachImage returns 2 lists - removed units first, and changed/added second */
914 print_changes(reply);
915 (void) maybe_stop_enable_restart(bus, reply);
916 }
917
918 return 0;
919 }
920
921 static int attach_image(int argc, char *argv[], void *userdata) {
922 return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) ? "AttachImage" : "AttachImageWithExtensions");
923 }
924
925 static int reattach_image(int argc, char *argv[], void *userdata) {
926 return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) ? "ReattachImage" : "ReattachImageWithExtensions");
927 }
928
929 static int detach_image(int argc, char *argv[], void *userdata) {
930 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
931 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
932 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
933 _cleanup_free_ char *image = NULL;
934 const char *method;
935 int r;
936
937 r = determine_image(argv[1], true, &image);
938 if (r < 0)
939 return r;
940
941 r = acquire_bus(&bus);
942 if (r < 0)
943 return r;
944
945 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
946
947 (void) maybe_stop_disable(bus, image, argv);
948
949 method = strv_isempty(arg_extension_images) ? "DetachImage" : "DetachImageWithExtensions";
950
951 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
952 if (r < 0)
953 return bus_log_create_error(r);
954
955 r = sd_bus_message_append(m, "s", image);
956 if (r < 0)
957 return bus_log_create_error(r);
958
959 r = attach_extensions_to_message(m, arg_extension_images);
960 if (r < 0)
961 return r;
962
963 if (strv_isempty(arg_extension_images))
964 r = sd_bus_message_append(m, "b", arg_runtime);
965 else {
966 uint64_t flags = (arg_runtime ? PORTABLE_RUNTIME : 0) | (arg_force ? PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_SYSEXT : 0);
967
968 r = sd_bus_message_append(m, "t", flags);
969 }
970 if (r < 0)
971 return bus_log_create_error(r);
972
973 r = sd_bus_call(bus, m, 0, &error, &reply);
974 if (r < 0)
975 return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r));
976
977 (void) maybe_reload(&bus);
978
979 print_changes(reply);
980 return 0;
981 }
982
983 static int list_images(int argc, char *argv[], void *userdata) {
984 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
985 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
986 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
987 _cleanup_(table_unrefp) Table *table = NULL;
988 int r;
989
990 r = acquire_bus(&bus);
991 if (r < 0)
992 return r;
993
994 r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL);
995 if (r < 0)
996 return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
997
998 table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
999 if (!table)
1000 return log_oom();
1001
1002 r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)");
1003 if (r < 0)
1004 return bus_log_parse_error(r);
1005
1006 for (;;) {
1007 const char *name, *type, *state;
1008 uint64_t crtime, mtime, usage;
1009 int ro_int;
1010
1011 r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL);
1012 if (r < 0)
1013 return bus_log_parse_error(r);
1014 if (r == 0)
1015 break;
1016
1017 r = table_add_many(table,
1018 TABLE_STRING, name,
1019 TABLE_STRING, type,
1020 TABLE_BOOLEAN, ro_int,
1021 TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL,
1022 TABLE_TIMESTAMP, crtime,
1023 TABLE_TIMESTAMP, mtime,
1024 TABLE_SIZE, usage,
1025 TABLE_STRING, state,
1026 TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL);
1027 if (r < 0)
1028 return table_log_add_error(r);
1029 }
1030
1031 r = sd_bus_message_exit_container(reply);
1032 if (r < 0)
1033 return bus_log_parse_error(r);
1034
1035 if (table_get_rows(table) > 1) {
1036 r = table_set_sort(table, (size_t) 0);
1037 if (r < 0)
1038 return table_log_sort_error(r);
1039
1040 table_set_header(table, arg_legend);
1041
1042 r = table_print(table, NULL);
1043 if (r < 0)
1044 return table_log_print_error(r);
1045 }
1046
1047 if (arg_legend) {
1048 if (table_get_rows(table) > 1)
1049 printf("\n%zu images listed.\n", table_get_rows(table) - 1);
1050 else
1051 printf("No images.\n");
1052 }
1053
1054 return 0;
1055 }
1056
1057 static int remove_image(int argc, char *argv[], void *userdata) {
1058 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1059 int r, i;
1060
1061 r = acquire_bus(&bus);
1062 if (r < 0)
1063 return r;
1064
1065 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1066
1067 for (i = 1; i < argc; i++) {
1068 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1069 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1070
1071 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "RemoveImage");
1072 if (r < 0)
1073 return bus_log_create_error(r);
1074
1075 r = sd_bus_message_append(m, "s", argv[i]);
1076 if (r < 0)
1077 return bus_log_create_error(r);
1078
1079 /* This is a slow operation, hence turn off any method call timeouts */
1080 r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
1081 if (r < 0)
1082 return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
1083 }
1084
1085 return 0;
1086 }
1087
1088 static int read_only_image(int argc, char *argv[], void *userdata) {
1089 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1090 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1091 int b = true, r;
1092
1093 if (argc > 2) {
1094 b = parse_boolean(argv[2]);
1095 if (b < 0)
1096 return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]);
1097 }
1098
1099 r = acquire_bus(&bus);
1100 if (r < 0)
1101 return r;
1102
1103 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1104
1105 r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b);
1106 if (r < 0)
1107 return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
1108
1109 return 0;
1110 }
1111
1112 static int set_limit(int argc, char *argv[], void *userdata) {
1113 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1114 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1115 uint64_t limit;
1116 int r;
1117
1118 r = acquire_bus(&bus);
1119 if (r < 0)
1120 return r;
1121
1122 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1123
1124 if (STR_IN_SET(argv[argc-1], "-", "none", "infinity"))
1125 limit = UINT64_MAX;
1126 else {
1127 r = parse_size(argv[argc-1], 1024, &limit);
1128 if (r < 0)
1129 return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]);
1130 }
1131
1132 if (argc > 2)
1133 /* With two arguments changes the quota limit of the specified image */
1134 r = bus_call_method(bus, bus_portable_mgr, "SetImageLimit", &error, NULL, "st", argv[1], limit);
1135 else
1136 /* With one argument changes the pool quota limit */
1137 r = bus_call_method(bus, bus_portable_mgr, "SetPoolLimit", &error, NULL, "t", limit);
1138
1139 if (r < 0)
1140 return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r));
1141
1142 return 0;
1143 }
1144
1145 static int is_image_attached(int argc, char *argv[], void *userdata) {
1146 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
1147 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1148 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1149 _cleanup_free_ char *image = NULL;
1150 const char *state, *method;
1151 int r;
1152
1153 r = determine_image(argv[1], true, &image);
1154 if (r < 0)
1155 return r;
1156
1157 r = acquire_bus(&bus);
1158 if (r < 0)
1159 return r;
1160
1161 method = strv_isempty(arg_extension_images) ? "GetImageState" : "GetImageStateWithExtensions";
1162
1163 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
1164 if (r < 0)
1165 return bus_log_create_error(r);
1166
1167 r = sd_bus_message_append(m, "s", image);
1168 if (r < 0)
1169 return bus_log_create_error(r);
1170
1171 r = attach_extensions_to_message(m, arg_extension_images);
1172 if (r < 0)
1173 return r;
1174
1175 if (!strv_isempty(arg_extension_images)) {
1176 r = sd_bus_message_append(m, "t", 0);
1177 if (r < 0)
1178 return bus_log_create_error(r);
1179 }
1180
1181 r = sd_bus_call(bus, m, 0, &error, &reply);
1182 if (r < 0)
1183 return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r));
1184
1185 r = sd_bus_message_read(reply, "s", &state);
1186 if (r < 0)
1187 return r;
1188
1189 if (!arg_quiet)
1190 puts(state);
1191
1192 return streq(state, "detached");
1193 }
1194
1195 static int dump_profiles(void) {
1196 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1197 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1198 _cleanup_strv_free_ char **l = NULL;
1199 int r;
1200
1201 r = acquire_bus(&bus);
1202 if (r < 0)
1203 return r;
1204
1205 r = bus_get_property_strv(bus, bus_portable_mgr, "Profiles", &error, &l);
1206 if (r < 0)
1207 return log_error_errno(r, "Failed to acquire list of profiles: %s", bus_error_message(&error, r));
1208
1209 if (arg_legend)
1210 log_info("Available unit profiles:");
1211
1212 STRV_FOREACH(i, l) {
1213 fputs(*i, stdout);
1214 fputc('\n', stdout);
1215 }
1216
1217 return 0;
1218 }
1219
1220 static int help(int argc, char *argv[], void *userdata) {
1221 _cleanup_free_ char *link = NULL;
1222 int r;
1223
1224 pager_open(arg_pager_flags);
1225
1226 r = terminal_urlify_man("portablectl", "1", &link);
1227 if (r < 0)
1228 return log_oom();
1229
1230 printf("%s [OPTIONS...] COMMAND ...\n\n"
1231 "%sAttach or detach portable services from the local system.%s\n"
1232 "\nCommands:\n"
1233 " list List available portable service images\n"
1234 " attach NAME|PATH [PREFIX...]\n"
1235 " Attach the specified portable service image\n"
1236 " detach NAME|PATH [PREFIX...]\n"
1237 " Detach the specified portable service image\n"
1238 " reattach NAME|PATH [PREFIX...]\n"
1239 " Reattach the specified portable service image\n"
1240 " inspect NAME|PATH [PREFIX...]\n"
1241 " Show details of specified portable service image\n"
1242 " is-attached NAME|PATH Query if portable service image is attached\n"
1243 " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
1244 " remove NAME|PATH... Remove a portable service image\n"
1245 " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
1246 "\nOptions:\n"
1247 " -h --help Show this help\n"
1248 " --version Show package version\n"
1249 " --no-pager Do not pipe output into a pager\n"
1250 " --no-legend Do not show the headers and footers\n"
1251 " --no-ask-password Do not ask for system passwords\n"
1252 " -H --host=[USER@]HOST Operate on remote host\n"
1253 " -M --machine=CONTAINER Operate on local container\n"
1254 " -q --quiet Suppress informational messages\n"
1255 " -p --profile=PROFILE Pick security profile for portable service\n"
1256 " --copy=copy|auto|symlink Prefer copying or symlinks if possible\n"
1257 " --runtime Attach portable service until next reboot only\n"
1258 " --no-reload Don't reload the system and service manager\n"
1259 " --cat When inspecting include unit and os-release file\n"
1260 " contents\n"
1261 " --enable Immediately enable/disable the portable service\n"
1262 " after attach/detach\n"
1263 " --now Immediately start/stop the portable service after\n"
1264 " attach/before detach\n"
1265 " --no-block Don't block waiting for attach --now to complete\n"
1266 " --extension=PATH Extend the image with an overlay\n"
1267 " --force Skip 'already active' check when attaching or\n"
1268 " detaching an image (with extensions)\n"
1269 "\nSee the %s for details.\n",
1270 program_invocation_short_name,
1271 ansi_highlight(),
1272 ansi_normal(),
1273 link);
1274
1275 return 0;
1276 }
1277
1278 static int parse_argv(int argc, char *argv[]) {
1279 int r;
1280
1281 enum {
1282 ARG_VERSION = 0x100,
1283 ARG_NO_PAGER,
1284 ARG_NO_LEGEND,
1285 ARG_NO_ASK_PASSWORD,
1286 ARG_COPY,
1287 ARG_RUNTIME,
1288 ARG_NO_RELOAD,
1289 ARG_CAT,
1290 ARG_ENABLE,
1291 ARG_NOW,
1292 ARG_NO_BLOCK,
1293 ARG_EXTENSION,
1294 ARG_FORCE,
1295 };
1296
1297 static const struct option options[] = {
1298 { "help", no_argument, NULL, 'h' },
1299 { "version", no_argument, NULL, ARG_VERSION },
1300 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1301 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
1302 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
1303 { "host", required_argument, NULL, 'H' },
1304 { "machine", required_argument, NULL, 'M' },
1305 { "quiet", no_argument, NULL, 'q' },
1306 { "profile", required_argument, NULL, 'p' },
1307 { "copy", required_argument, NULL, ARG_COPY },
1308 { "runtime", no_argument, NULL, ARG_RUNTIME },
1309 { "no-reload", no_argument, NULL, ARG_NO_RELOAD },
1310 { "cat", no_argument, NULL, ARG_CAT },
1311 { "enable", no_argument, NULL, ARG_ENABLE },
1312 { "now", no_argument, NULL, ARG_NOW },
1313 { "no-block", no_argument, NULL, ARG_NO_BLOCK },
1314 { "extension", required_argument, NULL, ARG_EXTENSION },
1315 { "force", no_argument, NULL, ARG_FORCE },
1316 {}
1317 };
1318
1319 assert(argc >= 0);
1320 assert(argv);
1321
1322 for (;;) {
1323 int c;
1324
1325 c = getopt_long(argc, argv, "hH:M:qp:", options, NULL);
1326 if (c < 0)
1327 break;
1328
1329 switch (c) {
1330
1331 case 'h':
1332 return help(0, NULL, NULL);
1333
1334 case ARG_VERSION:
1335 return version();
1336
1337 case ARG_NO_PAGER:
1338 arg_pager_flags |= PAGER_DISABLE;
1339 break;
1340
1341 case ARG_NO_LEGEND:
1342 arg_legend = false;
1343 break;
1344
1345 case ARG_NO_ASK_PASSWORD:
1346 arg_ask_password = false;
1347 break;
1348
1349 case 'H':
1350 arg_transport = BUS_TRANSPORT_REMOTE;
1351 arg_host = optarg;
1352 break;
1353
1354 case 'M':
1355 arg_transport = BUS_TRANSPORT_MACHINE;
1356 arg_host = optarg;
1357 break;
1358
1359 case 'q':
1360 arg_quiet = true;
1361 break;
1362
1363 case 'p':
1364 if (streq(optarg, "help"))
1365 return dump_profiles();
1366
1367 if (!filename_is_valid(optarg))
1368 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1369 "Unit profile name not valid: %s", optarg);
1370
1371 arg_profile = optarg;
1372 break;
1373
1374 case ARG_COPY:
1375 if (streq(optarg, "auto"))
1376 arg_copy_mode = NULL;
1377 else if (STR_IN_SET(optarg, "copy", "symlink"))
1378 arg_copy_mode = optarg;
1379 else if (streq(optarg, "help")) {
1380 puts("auto\n"
1381 "copy\n"
1382 "symlink");
1383 return 0;
1384 } else
1385 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1386 "Failed to parse --copy= argument: %s", optarg);
1387
1388 break;
1389
1390 case ARG_RUNTIME:
1391 arg_runtime = true;
1392 break;
1393
1394 case ARG_NO_RELOAD:
1395 arg_reload = false;
1396 break;
1397
1398 case ARG_CAT:
1399 arg_cat = true;
1400 break;
1401
1402 case ARG_ENABLE:
1403 arg_enable = true;
1404 break;
1405
1406 case ARG_NOW:
1407 arg_now = true;
1408 break;
1409
1410 case ARG_NO_BLOCK:
1411 arg_no_block = true;
1412 break;
1413
1414 case ARG_EXTENSION:
1415 r = strv_extend(&arg_extension_images, optarg);
1416 if (r < 0)
1417 return log_oom();
1418 break;
1419
1420 case ARG_FORCE:
1421 arg_force = true;
1422 break;
1423
1424 case '?':
1425 return -EINVAL;
1426
1427 default:
1428 assert_not_reached();
1429 }
1430 }
1431
1432 return 1;
1433 }
1434
1435 static int run(int argc, char *argv[]) {
1436 static const Verb verbs[] = {
1437 { "help", VERB_ANY, VERB_ANY, 0, help },
1438 { "list", VERB_ANY, 1, VERB_DEFAULT, list_images },
1439 { "attach", 2, VERB_ANY, 0, attach_image },
1440 { "detach", 2, VERB_ANY, 0, detach_image },
1441 { "inspect", 2, VERB_ANY, 0, inspect_image },
1442 { "is-attached", 2, 2, 0, is_image_attached },
1443 { "read-only", 2, 3, 0, read_only_image },
1444 { "remove", 2, VERB_ANY, 0, remove_image },
1445 { "set-limit", 3, 3, 0, set_limit },
1446 { "reattach", 2, VERB_ANY, 0, reattach_image },
1447 {}
1448 };
1449
1450 int r;
1451
1452 log_setup();
1453
1454 r = parse_argv(argc, argv);
1455 if (r <= 0)
1456 return r;
1457
1458 return dispatch_verb(argc, argv, verbs, NULL);
1459 }
1460
1461 DEFINE_MAIN_FUNCTION(run);