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