]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/portable/portablectl.c
update TODO
[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 if (!arg_enable)
565 return 0;
566
567 names = strv_new(path, NULL);
568 if (!names)
569 return log_oom();
570
571 r = sd_bus_message_new_method_call(
572 bus,
573 &m,
574 "org.freedesktop.systemd1",
575 "/org/freedesktop/systemd1",
576 "org.freedesktop.systemd1.Manager",
577 enable ? "EnableUnitFilesWithFlags" : "DisableUnitFilesWithFlags");
578 if (r < 0)
579 return bus_log_create_error(r);
580
581 r = sd_bus_message_append_strv(m, names);
582 if (r < 0)
583 return bus_log_create_error(r);
584
585 r = sd_bus_message_append(m, "t", flags);
586 if (r < 0)
587 return bus_log_create_error(r);
588
589 r = sd_bus_call(bus, m, 0, &error, &reply);
590 if (r < 0)
591 return log_error_errno(r, "Failed to %s the portable service %s: %s",
592 enable ? "enable" : "disable", path, bus_error_message(&error, r));
593
594 if (enable) {
595 r = sd_bus_message_skip(reply, "b");
596 if (r < 0)
597 return bus_log_parse_error(r);
598 }
599
600 (void) bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
601 install_changes_free(changes, n_changes);
602
603 return 0;
604 }
605
606 static int maybe_start_stop_restart(sd_bus *bus, const char *path, const char *method, BusWaitForJobs *wait) {
607 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
608 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
609 _cleanup_free_ char *name = NULL;
610 const char *job = NULL;
611 int r;
612
613 assert(STR_IN_SET(method, "StartUnit", "StopUnit", "RestartUnit"));
614
615 if (!arg_now)
616 return 0;
617
618 r = path_extract_filename(path, &name);
619 if (r < 0)
620 return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
621
622 r = bus_call_method(
623 bus,
624 bus_systemd_mgr,
625 method,
626 &error,
627 &reply,
628 "ss", name, "replace");
629 if (r < 0)
630 return log_error_errno(r, "Failed to call %s on the portable service %s: %s",
631 method,
632 path,
633 bus_error_message(&error, r));
634
635 r = sd_bus_message_read(reply, "o", &job);
636 if (r < 0)
637 return bus_log_parse_error(r);
638
639 if (!arg_quiet)
640 log_info("Queued %s to call %s on portable service %s.", job, method, name);
641
642 if (wait) {
643 r = bus_wait_for_jobs_add(wait, job);
644 if (r < 0)
645 return log_error_errno(r, "Failed to watch %s job to call %s on %s: %m",
646 job, method, name);
647 }
648
649 return 0;
650 }
651
652 static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) {
653 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
654 int r;
655
656 if (!arg_enable && !arg_now)
657 return 0;
658
659 if (!arg_no_block) {
660 r = bus_wait_for_jobs_new(bus, &wait);
661 if (r < 0)
662 return log_error_errno(r, "Could not watch jobs: %m");
663 }
664
665 r = sd_bus_message_rewind(reply, true);
666 if (r < 0)
667 return r;
668 r = sd_bus_message_enter_container(reply, 'a', "(sss)");
669 if (r < 0)
670 return bus_log_parse_error(r);
671
672 for (;;) {
673 char *type, *path, *source;
674
675 r = sd_bus_message_read(reply, "(sss)", &type, &path, &source);
676 if (r < 0)
677 return bus_log_parse_error(r);
678 if (r == 0)
679 break;
680
681 if (STR_IN_SET(type, "symlink", "copy") && is_portable_managed(path)) {
682 (void) maybe_enable_disable(bus, path, true);
683 (void) maybe_start_stop_restart(bus, path, "StartUnit", wait);
684 }
685 }
686
687 r = sd_bus_message_exit_container(reply);
688 if (r < 0)
689 return r;
690
691 if (!arg_no_block) {
692 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
693 if (r < 0)
694 return r;
695 }
696
697 return 0;
698 }
699
700 static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) {
701 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
702 int r;
703
704 if (!arg_enable && !arg_now)
705 return 0;
706
707 if (!arg_no_block) {
708 r = bus_wait_for_jobs_new(bus, &wait);
709 if (r < 0)
710 return log_error_errno(r, "Could not watch jobs: %m");
711 }
712
713 r = sd_bus_message_rewind(reply, true);
714 if (r < 0)
715 return r;
716
717 /* First we get a list of units that were definitely removed, not just re-attached,
718 * so we can also stop them if the user asked us to. */
719 r = sd_bus_message_enter_container(reply, 'a', "(sss)");
720 if (r < 0)
721 return bus_log_parse_error(r);
722
723 for (;;) {
724 char *type, *path, *source;
725
726 r = sd_bus_message_read(reply, "(sss)", &type, &path, &source);
727 if (r < 0)
728 return bus_log_parse_error(r);
729 if (r == 0)
730 break;
731
732 if (streq(type, "unlink") && is_portable_managed(path))
733 (void) maybe_start_stop_restart(bus, path, "StopUnit", wait);
734 }
735
736 r = sd_bus_message_exit_container(reply);
737 if (r < 0)
738 return r;
739
740 /* Then we get a list of units that were either added or changed, so that we can
741 * enable them and/or restart them if the user asked us to. */
742 r = sd_bus_message_enter_container(reply, 'a', "(sss)");
743 if (r < 0)
744 return bus_log_parse_error(r);
745
746 for (;;) {
747 char *type, *path, *source;
748
749 r = sd_bus_message_read(reply, "(sss)", &type, &path, &source);
750 if (r < 0)
751 return bus_log_parse_error(r);
752 if (r == 0)
753 break;
754
755 if (STR_IN_SET(type, "symlink", "copy") && is_portable_managed(path)) {
756 (void) maybe_enable_disable(bus, path, true);
757 (void) maybe_start_stop_restart(bus, path, "RestartUnit", wait);
758 }
759 }
760
761 r = sd_bus_message_exit_container(reply);
762 if (r < 0)
763 return r;
764
765 if (!arg_no_block) {
766 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
767 if (r < 0)
768 return r;
769 }
770
771 return 0;
772 }
773
774 static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) {
775 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
776 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
777 _cleanup_strv_free_ char **matches = NULL;
778 int r;
779
780 if (!arg_enable && !arg_now)
781 return 0;
782
783 r = determine_matches(argv[1], argv + 2, true, &matches);
784 if (r < 0)
785 return r;
786
787 r = bus_wait_for_jobs_new(bus, &wait);
788 if (r < 0)
789 return log_error_errno(r, "Could not watch jobs: %m");
790
791 r = get_image_metadata(bus, image, matches, &reply);
792 if (r < 0)
793 return r;
794
795 r = sd_bus_message_skip(reply, "say");
796 if (r < 0)
797 return bus_log_parse_error(r);
798
799 /* If we specified any extensions, we'll first an array of extension-release metadata. */
800 if (!strv_isempty(arg_extension_images)) {
801 r = sd_bus_message_skip(reply, "a{say}");
802 if (r < 0)
803 return bus_log_parse_error(r);
804 }
805
806 r = sd_bus_message_enter_container(reply, 'a', "{say}");
807 if (r < 0)
808 return bus_log_parse_error(r);
809
810 for (;;) {
811 const char *name;
812
813 r = sd_bus_message_enter_container(reply, 'e', "say");
814 if (r < 0)
815 return bus_log_parse_error(r);
816 if (r == 0)
817 break;
818
819 r = sd_bus_message_read(reply, "s", &name);
820 if (r < 0)
821 return bus_log_parse_error(r);
822
823 r = sd_bus_message_skip(reply, "ay");
824 if (r < 0)
825 return bus_log_parse_error(r);
826
827 r = sd_bus_message_exit_container(reply);
828 if (r < 0)
829 return bus_log_parse_error(r);
830
831 (void) maybe_start_stop_restart(bus, name, "StopUnit", wait);
832 (void) maybe_enable_disable(bus, name, false);
833 }
834
835 r = sd_bus_message_exit_container(reply);
836 if (r < 0)
837 return bus_log_parse_error(r);
838
839 /* Stopping must always block or the detach will fail if the unit is still running */
840 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
841 if (r < 0)
842 return r;
843
844 return 0;
845 }
846
847 static int attach_reattach_image(int argc, char *argv[], const char *method) {
848 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
849 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
850 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
851 _cleanup_strv_free_ char **matches = NULL;
852 _cleanup_free_ char *image = NULL;
853 int r;
854
855 assert(method);
856 assert(STR_IN_SET(method, "AttachImage", "ReattachImage", "AttachImageWithExtensions", "ReattachImageWithExtensions"));
857
858 r = determine_image(argv[1], false, &image);
859 if (r < 0)
860 return r;
861
862 r = determine_matches(argv[1], argv + 2, false, &matches);
863 if (r < 0)
864 return r;
865
866 r = acquire_bus(&bus);
867 if (r < 0)
868 return r;
869
870 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
871
872 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
873 if (r < 0)
874 return bus_log_create_error(r);
875
876 r = sd_bus_message_append(m, "s", image);
877 if (r < 0)
878 return bus_log_create_error(r);
879
880 r = attach_extensions_to_message(m, arg_extension_images);
881 if (r < 0)
882 return r;
883
884 r = sd_bus_message_append_strv(m, matches);
885 if (r < 0)
886 return bus_log_create_error(r);
887
888 r = sd_bus_message_append(m, "s", arg_profile);
889 if (r < 0)
890 return bus_log_create_error(r);
891
892 if (STR_IN_SET(method, "AttachImageWithExtensions", "ReattachImageWithExtensions")) {
893 uint64_t flags = (arg_runtime ? PORTABLE_RUNTIME : 0) | (arg_force ? PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_SYSEXT : 0);
894
895 r = sd_bus_message_append(m, "st", arg_copy_mode, flags);
896 } else
897 r = sd_bus_message_append(m, "bs", arg_runtime, arg_copy_mode);
898 if (r < 0)
899 return bus_log_create_error(r);
900
901 r = sd_bus_call(bus, m, 0, &error, &reply);
902 if (r < 0)
903 return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r));
904
905 (void) maybe_reload(&bus);
906
907 print_changes(reply);
908
909 if (STR_IN_SET(method, "AttachImage", "AttachImageWithExtensions"))
910 (void) maybe_enable_start(bus, reply);
911 else {
912 /* ReattachImage returns 2 lists - removed units first, and changed/added second */
913 print_changes(reply);
914 (void) maybe_stop_enable_restart(bus, reply);
915 }
916
917 return 0;
918 }
919
920 static int attach_image(int argc, char *argv[], void *userdata) {
921 return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) ? "AttachImage" : "AttachImageWithExtensions");
922 }
923
924 static int reattach_image(int argc, char *argv[], void *userdata) {
925 return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) ? "ReattachImage" : "ReattachImageWithExtensions");
926 }
927
928 static int detach_image(int argc, char *argv[], void *userdata) {
929 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
930 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
931 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
932 _cleanup_free_ char *image = NULL;
933 const char *method;
934 int r;
935
936 r = determine_image(argv[1], true, &image);
937 if (r < 0)
938 return r;
939
940 r = acquire_bus(&bus);
941 if (r < 0)
942 return r;
943
944 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
945
946 (void) maybe_stop_disable(bus, image, argv);
947
948 method = strv_isempty(arg_extension_images) ? "DetachImage" : "DetachImageWithExtensions";
949
950 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
951 if (r < 0)
952 return bus_log_create_error(r);
953
954 r = sd_bus_message_append(m, "s", image);
955 if (r < 0)
956 return bus_log_create_error(r);
957
958 r = attach_extensions_to_message(m, arg_extension_images);
959 if (r < 0)
960 return r;
961
962 if (strv_isempty(arg_extension_images))
963 r = sd_bus_message_append(m, "b", arg_runtime);
964 else {
965 uint64_t flags = (arg_runtime ? PORTABLE_RUNTIME : 0) | (arg_force ? PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_SYSEXT : 0);
966
967 r = sd_bus_message_append(m, "t", flags);
968 }
969 if (r < 0)
970 return bus_log_create_error(r);
971
972 r = sd_bus_call(bus, m, 0, &error, &reply);
973 if (r < 0)
974 return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r));
975
976 (void) maybe_reload(&bus);
977
978 print_changes(reply);
979 return 0;
980 }
981
982 static int list_images(int argc, char *argv[], void *userdata) {
983 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
984 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
985 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
986 _cleanup_(table_unrefp) Table *table = NULL;
987 int r;
988
989 r = acquire_bus(&bus);
990 if (r < 0)
991 return r;
992
993 r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL);
994 if (r < 0)
995 return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
996
997 table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
998 if (!table)
999 return log_oom();
1000
1001 r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)");
1002 if (r < 0)
1003 return bus_log_parse_error(r);
1004
1005 for (;;) {
1006 const char *name, *type, *state;
1007 uint64_t crtime, mtime, usage;
1008 int ro_int;
1009
1010 r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL);
1011 if (r < 0)
1012 return bus_log_parse_error(r);
1013 if (r == 0)
1014 break;
1015
1016 r = table_add_many(table,
1017 TABLE_STRING, name,
1018 TABLE_STRING, type,
1019 TABLE_BOOLEAN, ro_int,
1020 TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL,
1021 TABLE_TIMESTAMP, crtime,
1022 TABLE_TIMESTAMP, mtime,
1023 TABLE_SIZE, usage,
1024 TABLE_STRING, state,
1025 TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL);
1026 if (r < 0)
1027 return table_log_add_error(r);
1028 }
1029
1030 r = sd_bus_message_exit_container(reply);
1031 if (r < 0)
1032 return bus_log_parse_error(r);
1033
1034 if (table_get_rows(table) > 1) {
1035 r = table_set_sort(table, (size_t) 0);
1036 if (r < 0)
1037 return table_log_sort_error(r);
1038
1039 table_set_header(table, arg_legend);
1040
1041 r = table_print(table, NULL);
1042 if (r < 0)
1043 return table_log_print_error(r);
1044 }
1045
1046 if (arg_legend) {
1047 if (table_get_rows(table) > 1)
1048 printf("\n%zu images listed.\n", table_get_rows(table) - 1);
1049 else
1050 printf("No images.\n");
1051 }
1052
1053 return 0;
1054 }
1055
1056 static int remove_image(int argc, char *argv[], void *userdata) {
1057 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1058 int r, i;
1059
1060 r = acquire_bus(&bus);
1061 if (r < 0)
1062 return r;
1063
1064 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1065
1066 for (i = 1; i < argc; i++) {
1067 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1068 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1069
1070 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "RemoveImage");
1071 if (r < 0)
1072 return bus_log_create_error(r);
1073
1074 r = sd_bus_message_append(m, "s", argv[i]);
1075 if (r < 0)
1076 return bus_log_create_error(r);
1077
1078 /* This is a slow operation, hence turn off any method call timeouts */
1079 r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
1080 if (r < 0)
1081 return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
1082 }
1083
1084 return 0;
1085 }
1086
1087 static int read_only_image(int argc, char *argv[], void *userdata) {
1088 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1089 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1090 int b = true, r;
1091
1092 if (argc > 2) {
1093 b = parse_boolean(argv[2]);
1094 if (b < 0)
1095 return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]);
1096 }
1097
1098 r = acquire_bus(&bus);
1099 if (r < 0)
1100 return r;
1101
1102 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1103
1104 r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b);
1105 if (r < 0)
1106 return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
1107
1108 return 0;
1109 }
1110
1111 static int set_limit(int argc, char *argv[], void *userdata) {
1112 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1113 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1114 uint64_t limit;
1115 int r;
1116
1117 r = acquire_bus(&bus);
1118 if (r < 0)
1119 return r;
1120
1121 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
1122
1123 if (STR_IN_SET(argv[argc-1], "-", "none", "infinity"))
1124 limit = UINT64_MAX;
1125 else {
1126 r = parse_size(argv[argc-1], 1024, &limit);
1127 if (r < 0)
1128 return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]);
1129 }
1130
1131 if (argc > 2)
1132 /* With two arguments changes the quota limit of the specified image */
1133 r = bus_call_method(bus, bus_portable_mgr, "SetImageLimit", &error, NULL, "st", argv[1], limit);
1134 else
1135 /* With one argument changes the pool quota limit */
1136 r = bus_call_method(bus, bus_portable_mgr, "SetPoolLimit", &error, NULL, "t", limit);
1137
1138 if (r < 0)
1139 return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r));
1140
1141 return 0;
1142 }
1143
1144 static int is_image_attached(int argc, char *argv[], void *userdata) {
1145 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
1146 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1147 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1148 _cleanup_free_ char *image = NULL;
1149 const char *state, *method;
1150 int r;
1151
1152 r = determine_image(argv[1], true, &image);
1153 if (r < 0)
1154 return r;
1155
1156 r = acquire_bus(&bus);
1157 if (r < 0)
1158 return r;
1159
1160 method = strv_isempty(arg_extension_images) ? "GetImageState" : "GetImageStateWithExtensions";
1161
1162 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method);
1163 if (r < 0)
1164 return bus_log_create_error(r);
1165
1166 r = sd_bus_message_append(m, "s", image);
1167 if (r < 0)
1168 return bus_log_create_error(r);
1169
1170 r = attach_extensions_to_message(m, arg_extension_images);
1171 if (r < 0)
1172 return r;
1173
1174 if (!strv_isempty(arg_extension_images)) {
1175 r = sd_bus_message_append(m, "t", 0);
1176 if (r < 0)
1177 return bus_log_create_error(r);
1178 }
1179
1180 r = sd_bus_call(bus, m, 0, &error, &reply);
1181 if (r < 0)
1182 return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r));
1183
1184 r = sd_bus_message_read(reply, "s", &state);
1185 if (r < 0)
1186 return r;
1187
1188 if (!arg_quiet)
1189 puts(state);
1190
1191 return streq(state, "detached");
1192 }
1193
1194 static int dump_profiles(void) {
1195 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1196 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1197 _cleanup_strv_free_ char **l = NULL;
1198 int r;
1199
1200 r = acquire_bus(&bus);
1201 if (r < 0)
1202 return r;
1203
1204 r = bus_get_property_strv(bus, bus_portable_mgr, "Profiles", &error, &l);
1205 if (r < 0)
1206 return log_error_errno(r, "Failed to acquire list of profiles: %s", bus_error_message(&error, r));
1207
1208 if (arg_legend)
1209 log_info("Available unit profiles:");
1210
1211 STRV_FOREACH(i, l) {
1212 fputs(*i, stdout);
1213 fputc('\n', stdout);
1214 }
1215
1216 return 0;
1217 }
1218
1219 static int help(int argc, char *argv[], void *userdata) {
1220 _cleanup_free_ char *link = NULL;
1221 int r;
1222
1223 pager_open(arg_pager_flags);
1224
1225 r = terminal_urlify_man("portablectl", "1", &link);
1226 if (r < 0)
1227 return log_oom();
1228
1229 printf("%s [OPTIONS...] COMMAND ...\n\n"
1230 "%sAttach or detach portable services from the local system.%s\n"
1231 "\nCommands:\n"
1232 " list List available portable service images\n"
1233 " attach NAME|PATH [PREFIX...]\n"
1234 " Attach the specified portable service image\n"
1235 " detach NAME|PATH [PREFIX...]\n"
1236 " Detach the specified portable service image\n"
1237 " reattach NAME|PATH [PREFIX...]\n"
1238 " Reattach the specified portable service image\n"
1239 " inspect NAME|PATH [PREFIX...]\n"
1240 " Show details of specified portable service image\n"
1241 " is-attached NAME|PATH Query if portable service image is attached\n"
1242 " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
1243 " remove NAME|PATH... Remove a portable service image\n"
1244 " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
1245 "\nOptions:\n"
1246 " -h --help Show this help\n"
1247 " --version Show package version\n"
1248 " --no-pager Do not pipe output into a pager\n"
1249 " --no-legend Do not show the headers and footers\n"
1250 " --no-ask-password Do not ask for system passwords\n"
1251 " -H --host=[USER@]HOST Operate on remote host\n"
1252 " -M --machine=CONTAINER Operate on local container\n"
1253 " -q --quiet Suppress informational messages\n"
1254 " -p --profile=PROFILE Pick security profile for portable service\n"
1255 " --copy=copy|auto|symlink Prefer copying or symlinks if possible\n"
1256 " --runtime Attach portable service until next reboot only\n"
1257 " --no-reload Don't reload the system and service manager\n"
1258 " --cat When inspecting include unit and os-release file\n"
1259 " contents\n"
1260 " --enable Immediately enable/disable the portable service\n"
1261 " after attach/detach\n"
1262 " --now Immediately start/stop the portable service after\n"
1263 " attach/before detach\n"
1264 " --no-block Don't block waiting for attach --now to complete\n"
1265 " --extension=PATH Extend the image with an overlay\n"
1266 " --force Skip 'already active' check when attaching or\n"
1267 " detaching an image (with extensions)\n"
1268 "\nSee the %s for details.\n",
1269 program_invocation_short_name,
1270 ansi_highlight(),
1271 ansi_normal(),
1272 link);
1273
1274 return 0;
1275 }
1276
1277 static int parse_argv(int argc, char *argv[]) {
1278 int r;
1279
1280 enum {
1281 ARG_VERSION = 0x100,
1282 ARG_NO_PAGER,
1283 ARG_NO_LEGEND,
1284 ARG_NO_ASK_PASSWORD,
1285 ARG_COPY,
1286 ARG_RUNTIME,
1287 ARG_NO_RELOAD,
1288 ARG_CAT,
1289 ARG_ENABLE,
1290 ARG_NOW,
1291 ARG_NO_BLOCK,
1292 ARG_EXTENSION,
1293 ARG_FORCE,
1294 };
1295
1296 static const struct option options[] = {
1297 { "help", no_argument, NULL, 'h' },
1298 { "version", no_argument, NULL, ARG_VERSION },
1299 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1300 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
1301 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
1302 { "host", required_argument, NULL, 'H' },
1303 { "machine", required_argument, NULL, 'M' },
1304 { "quiet", no_argument, NULL, 'q' },
1305 { "profile", required_argument, NULL, 'p' },
1306 { "copy", required_argument, NULL, ARG_COPY },
1307 { "runtime", no_argument, NULL, ARG_RUNTIME },
1308 { "no-reload", no_argument, NULL, ARG_NO_RELOAD },
1309 { "cat", no_argument, NULL, ARG_CAT },
1310 { "enable", no_argument, NULL, ARG_ENABLE },
1311 { "now", no_argument, NULL, ARG_NOW },
1312 { "no-block", no_argument, NULL, ARG_NO_BLOCK },
1313 { "extension", required_argument, NULL, ARG_EXTENSION },
1314 { "force", no_argument, NULL, ARG_FORCE },
1315 {}
1316 };
1317
1318 assert(argc >= 0);
1319 assert(argv);
1320
1321 for (;;) {
1322 int c;
1323
1324 c = getopt_long(argc, argv, "hH:M:qp:", options, NULL);
1325 if (c < 0)
1326 break;
1327
1328 switch (c) {
1329
1330 case 'h':
1331 return help(0, NULL, NULL);
1332
1333 case ARG_VERSION:
1334 return version();
1335
1336 case ARG_NO_PAGER:
1337 arg_pager_flags |= PAGER_DISABLE;
1338 break;
1339
1340 case ARG_NO_LEGEND:
1341 arg_legend = false;
1342 break;
1343
1344 case ARG_NO_ASK_PASSWORD:
1345 arg_ask_password = false;
1346 break;
1347
1348 case 'H':
1349 arg_transport = BUS_TRANSPORT_REMOTE;
1350 arg_host = optarg;
1351 break;
1352
1353 case 'M':
1354 arg_transport = BUS_TRANSPORT_MACHINE;
1355 arg_host = optarg;
1356 break;
1357
1358 case 'q':
1359 arg_quiet = true;
1360 break;
1361
1362 case 'p':
1363 if (streq(optarg, "help"))
1364 return dump_profiles();
1365
1366 if (!filename_is_valid(optarg))
1367 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1368 "Unit profile name not valid: %s", optarg);
1369
1370 arg_profile = optarg;
1371 break;
1372
1373 case ARG_COPY:
1374 if (streq(optarg, "auto"))
1375 arg_copy_mode = NULL;
1376 else if (STR_IN_SET(optarg, "copy", "symlink"))
1377 arg_copy_mode = optarg;
1378 else if (streq(optarg, "help")) {
1379 puts("auto\n"
1380 "copy\n"
1381 "symlink");
1382 return 0;
1383 } else
1384 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1385 "Failed to parse --copy= argument: %s", optarg);
1386
1387 break;
1388
1389 case ARG_RUNTIME:
1390 arg_runtime = true;
1391 break;
1392
1393 case ARG_NO_RELOAD:
1394 arg_reload = false;
1395 break;
1396
1397 case ARG_CAT:
1398 arg_cat = true;
1399 break;
1400
1401 case ARG_ENABLE:
1402 arg_enable = true;
1403 break;
1404
1405 case ARG_NOW:
1406 arg_now = true;
1407 break;
1408
1409 case ARG_NO_BLOCK:
1410 arg_no_block = true;
1411 break;
1412
1413 case ARG_EXTENSION:
1414 r = strv_extend(&arg_extension_images, optarg);
1415 if (r < 0)
1416 return log_oom();
1417 break;
1418
1419 case ARG_FORCE:
1420 arg_force = true;
1421 break;
1422
1423 case '?':
1424 return -EINVAL;
1425
1426 default:
1427 assert_not_reached();
1428 }
1429 }
1430
1431 return 1;
1432 }
1433
1434 static int run(int argc, char *argv[]) {
1435 static const Verb verbs[] = {
1436 { "help", VERB_ANY, VERB_ANY, 0, help },
1437 { "list", VERB_ANY, 1, VERB_DEFAULT, list_images },
1438 { "attach", 2, VERB_ANY, 0, attach_image },
1439 { "detach", 2, VERB_ANY, 0, detach_image },
1440 { "inspect", 2, VERB_ANY, 0, inspect_image },
1441 { "is-attached", 2, 2, 0, is_image_attached },
1442 { "read-only", 2, 3, 0, read_only_image },
1443 { "remove", 2, VERB_ANY, 0, remove_image },
1444 { "set-limit", 3, 3, 0, set_limit },
1445 { "reattach", 2, VERB_ANY, 0, reattach_image },
1446 {}
1447 };
1448
1449 int r;
1450
1451 log_setup();
1452
1453 r = parse_argv(argc, argv);
1454 if (r <= 0)
1455 return r;
1456
1457 return dispatch_verb(argc, argv, verbs, NULL);
1458 }
1459
1460 DEFINE_MAIN_FUNCTION(run);