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