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