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