]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/portable/portablectl.c
Merge pull request #13365 from keszybz/fix-commits-from-pr-13246
[thirdparty/systemd.git] / src / portable / portablectl.c
CommitLineData
61d0578b
LP
1/* SPDX-License-Identifier: LGPL-2.1+ */
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"
10#include "bus-util.h"
11#include "def.h"
12#include "dirent-util.h"
686d13b9 13#include "env-file.h"
61d0578b 14#include "fd-util.h"
673a1e6f 15#include "fileio.h"
61d0578b
LP
16#include "format-table.h"
17#include "fs-util.h"
18#include "locale-util.h"
19#include "machine-image.h"
c55a594e 20#include "main-func.h"
61d0578b
LP
21#include "pager.h"
22#include "parse-util.h"
23#include "path-util.h"
294bf0c3 24#include "pretty-print.h"
61d0578b
LP
25#include "spawn-polkit-agent.h"
26#include "string-util.h"
27#include "strv.h"
28#include "terminal-util.h"
29#include "verbs.h"
30
0221d68a 31static PagerFlags arg_pager_flags = 0;
61d0578b
LP
32static bool arg_legend = true;
33static bool arg_ask_password = true;
34static bool arg_quiet = false;
35static const char *arg_profile = "default";
36static const char* arg_copy_mode = NULL;
37static bool arg_runtime = false;
38static bool arg_reload = true;
39static bool arg_cat = false;
40static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
0c760322 41static const char *arg_host = NULL;
61d0578b
LP
42
43static int determine_image(const char *image, bool permit_non_existing, char **ret) {
44 int r;
45
46 /* If the specified name is a valid image name, we pass it as-is to portabled, which will search for it in the
47 * usual search directories. Otherwise we presume it's a path, and will normalize it on the client's side
48 * (among other things, to make the path independent of the client's working directory) before passing it
49 * over. */
50
51 if (image_name_is_valid(image)) {
52 char *c;
53
54 if (!arg_quiet && laccess(image, F_OK) >= 0)
55 log_warning("Ambiguous invocation: current working directory contains file matching non-path argument '%s', ignoring. "
56 "Prefix argument with './' to force reference to file in current working directory.", image);
57
58 c = strdup(image);
59 if (!c)
60 return log_oom();
61
62 *ret = c;
63 return 0;
64 }
65
baaa35ad
ZJS
66 if (arg_transport != BUS_TRANSPORT_LOCAL)
67 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
68 "Operations on images by path not supported when connecting to remote systems.");
61d0578b
LP
69
70 r = chase_symlinks(image, NULL, CHASE_TRAIL_SLASH | (permit_non_existing ? CHASE_NONEXISTENT : 0), ret);
71 if (r < 0)
72 return log_error_errno(r, "Cannot normalize specified image path '%s': %m", image);
73
74 return 0;
75}
76
77static int extract_prefix(const char *path, char **ret) {
78 _cleanup_free_ char *name = NULL;
79 const char *bn, *underscore;
80 size_t m;
81
82 bn = basename(path);
83
84 underscore = strchr(bn, '_');
85 if (underscore)
86 m = underscore - bn;
87 else {
88 const char *e;
89
90 e = endswith(bn, ".raw");
91 if (!e)
92 e = strchr(bn, 0);
93
94 m = e - bn;
95 }
96
97 name = strndup(bn, m);
98 if (!name)
99 return -ENOMEM;
100
101 /* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_'
102 * which we use as delimiter for the second part of the image string, which we ignore for now. */
103 if (!in_charset(name, DIGITS LETTERS "-."))
104 return -EINVAL;
105
106 if (!filename_is_valid(name))
107 return -EINVAL;
108
810aa425 109 *ret = TAKE_PTR(name);
61d0578b
LP
110
111 return 0;
112}
113
114static int determine_matches(const char *image, char **l, bool allow_any, char ***ret) {
66c9dfdd 115 _cleanup_strv_free_ char **k = NULL;
61d0578b
LP
116 int r;
117
118 /* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list
119 * contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly
120 * permitted. */
121
122 if (strv_isempty(l)) {
123 char *prefix;
124
125 r = extract_prefix(image, &prefix);
126 if (r < 0)
127 return log_error_errno(r, "Failed to extract prefix of image name '%s': %m", image);
128
129 if (!arg_quiet)
130 log_info("(Matching unit files with prefix '%s'.)", prefix);
131
61d0578b
LP
132 r = strv_consume(&k, prefix);
133 if (r < 0)
134 return log_oom();
135
136 } else if (strv_equal(l, STRV_MAKE("-"))) {
137
baaa35ad
ZJS
138 if (!allow_any)
139 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
140 "Refusing all unit file match.");
61d0578b
LP
141
142 if (!arg_quiet)
143 log_info("(Matching all unit files.)");
61d0578b 144 } else {
61d0578b
LP
145
146 k = strv_copy(l);
147 if (!k)
148 return log_oom();
149
21cffed7
LP
150 if (!arg_quiet) {
151 _cleanup_free_ char *joined = NULL;
152
153 joined = strv_join(k, "', '");
154 if (!joined)
155 return log_oom();
61d0578b 156
61d0578b 157 log_info("(Matching unit files with prefixes '%s'.)", joined);
21cffed7 158 }
61d0578b
LP
159 }
160
66c9dfdd 161 *ret = TAKE_PTR(k);
61d0578b
LP
162
163 return 0;
164}
165
166static int acquire_bus(sd_bus **bus) {
167 int r;
168
169 assert(bus);
170
171 if (*bus)
172 return 0;
173
174 r = bus_connect_transport(arg_transport, arg_host, false, bus);
175 if (r < 0)
176 return log_error_errno(r, "Failed to connect to bus: %m");
177
178 (void) sd_bus_set_allow_interactive_authorization(*bus, arg_ask_password);
179
180 return 0;
181}
182
183static int maybe_reload(sd_bus **bus) {
184 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
185 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
186 int r;
187
188 if (!arg_reload)
189 return 0;
190
191 r = acquire_bus(bus);
192 if (r < 0)
193 return r;
194
195 r = sd_bus_message_new_method_call(
196 *bus,
197 &m,
198 "org.freedesktop.systemd1",
199 "/org/freedesktop/systemd1",
200 "org.freedesktop.systemd1.Manager",
201 "Reload");
202 if (r < 0)
203 return bus_log_create_error(r);
204
205 /* Reloading the daemon may take long, hence set a longer timeout here */
206 r = sd_bus_call(*bus, m, DEFAULT_TIMEOUT_USEC * 2, &error, NULL);
207 if (r < 0)
208 return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
209
210 return 0;
211}
212
213static int inspect_image(int argc, char *argv[], void *userdata) {
214 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
215 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
216 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
217 _cleanup_strv_free_ char **matches = NULL;
218 _cleanup_free_ char *image = NULL;
219 bool nl = false, header = false;
220 const void *data;
221 const char *path;
222 size_t sz;
223 int r;
224
225 r = determine_image(argv[1], false, &image);
226 if (r < 0)
227 return r;
228
229 r = determine_matches(argv[1], argv + 2, true, &matches);
230 if (r < 0)
231 return r;
232
233 r = acquire_bus(&bus);
234 if (r < 0)
235 return r;
236
237 r = sd_bus_message_new_method_call(
238 bus,
239 &m,
240 "org.freedesktop.portable1",
241 "/org/freedesktop/portable1",
242 "org.freedesktop.portable1.Manager",
243 "GetImageMetadata");
244 if (r < 0)
245 return bus_log_create_error(r);
246
247 r = sd_bus_message_append(m, "s", image);
248 if (r < 0)
249 return bus_log_create_error(r);
250
251 r = sd_bus_message_append_strv(m, matches);
252 if (r < 0)
253 return bus_log_create_error(r);
254
255 r = sd_bus_call(bus, m, 0, &error, &reply);
256 if (r < 0)
257 return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r));
258
259 r = sd_bus_message_read(reply, "s", &path);
260 if (r < 0)
261 return bus_log_parse_error(r);
262
263 r = sd_bus_message_read_array(reply, 'y', &data, &sz);
264 if (r < 0)
265 return bus_log_parse_error(r);
266
0221d68a 267 (void) pager_open(arg_pager_flags);
61d0578b
LP
268
269 if (arg_cat) {
270 printf("%s-- OS Release: --%s\n", ansi_highlight(), ansi_normal());
271 fwrite(data, sz, 1, stdout);
272 fflush(stdout);
273 nl = true;
274 } else {
8d93d7d8 275 _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL;
61d0578b
LP
276 _cleanup_fclose_ FILE *f;
277
673a1e6f 278 f = fmemopen_unlocked((void*) data, sz, "re");
61d0578b
LP
279 if (!f)
280 return log_error_errno(errno, "Failed to open /etc/os-release buffer: %m");
281
aa8fbc74 282 r = parse_env_file(f, "/etc/os-release",
61d0578b 283 "PORTABLE_PRETTY_NAME", &pretty_portable,
13df9c39 284 "PRETTY_NAME", &pretty_os);
61d0578b
LP
285 if (r < 0)
286 return log_error_errno(r, "Failed to parse /etc/os-release: %m");
287
288 printf("Image:\n\t%s\n"
289 "Portable Service:\n\t%s\n"
290 "Operating System:\n\t%s\n",
291 path,
292 strna(pretty_portable),
293 strna(pretty_os));
294 }
295
296 r = sd_bus_message_enter_container(reply, 'a', "{say}");
297 if (r < 0)
298 return bus_log_parse_error(r);
299
300 for (;;) {
301 const char *name;
302
303 r = sd_bus_message_enter_container(reply, 'e', "say");
304 if (r < 0)
305 return bus_log_parse_error(r);
306 if (r == 0)
307 break;
308
309 r = sd_bus_message_read(reply, "s", &name);
310 if (r < 0)
311 return bus_log_parse_error(r);
312
313 r = sd_bus_message_read_array(reply, 'y', &data, &sz);
314 if (r < 0)
315 return bus_log_parse_error(r);
316
317 if (arg_cat) {
318 if (nl)
319 fputc('\n', stdout);
320
321 printf("%s-- Unit file: %s --%s\n", ansi_highlight(), name, ansi_normal());
322 fwrite(data, sz, 1, stdout);
323 fflush(stdout);
324 nl = true;
325 } else {
326 if (!header) {
327 fputs("Unit files:\n", stdout);
328 header = true;
329 }
330
331 fputc('\t', stdout);
332 fputs(name, stdout);
333 fputc('\n', stdout);
334 }
335
336 r = sd_bus_message_exit_container(reply);
337 if (r < 0)
338 return bus_log_parse_error(r);
339 }
340
341 r = sd_bus_message_exit_container(reply);
342 if (r < 0)
343 return bus_log_parse_error(r);
344
345 return 0;
346}
347
348static int print_changes(sd_bus_message *m) {
349 int r;
350
351 if (arg_quiet)
352 return 0;
353
354 r = sd_bus_message_enter_container(m, 'a', "(sss)");
355 if (r < 0)
356 return bus_log_parse_error(r);
357
358 for (;;) {
359 const char *type, *path, *source;
360
361 r = sd_bus_message_read(m, "(sss)", &type, &path, &source);
362 if (r < 0)
363 return bus_log_parse_error(r);
364 if (r == 0)
365 break;
366
367 if (streq(type, "symlink"))
9a6f746f 368 log_info("Created symlink %s %s %s.", path, special_glyph(SPECIAL_GLYPH_ARROW), source);
61d0578b
LP
369 else if (streq(type, "copy")) {
370 if (isempty(source))
371 log_info("Copied %s.", path);
372 else
9a6f746f 373 log_info("Copied %s %s %s.", source, special_glyph(SPECIAL_GLYPH_ARROW), path);
61d0578b
LP
374 } else if (streq(type, "unlink"))
375 log_info("Removed %s.", path);
376 else if (streq(type, "write"))
377 log_info("Written %s.", path);
378 else if (streq(type, "mkdir"))
379 log_info("Created directory %s.", path);
380 else
381 log_error("Unexpected change: %s/%s/%s", type, path, source);
382 }
383
384 r = sd_bus_message_exit_container(m);
385 if (r < 0)
386 return r;
387
388 return 0;
389}
390
391static int attach_image(int argc, char *argv[], void *userdata) {
392 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
393 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
394 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
395 _cleanup_strv_free_ char **matches = NULL;
396 _cleanup_free_ char *image = NULL;
397 int r;
398
399 r = determine_image(argv[1], false, &image);
400 if (r < 0)
401 return r;
402
403 r = determine_matches(argv[1], argv + 2, false, &matches);
404 if (r < 0)
405 return r;
406
407 r = acquire_bus(&bus);
408 if (r < 0)
409 return r;
410
411 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
412
413 r = sd_bus_message_new_method_call(
414 bus,
415 &m,
416 "org.freedesktop.portable1",
417 "/org/freedesktop/portable1",
418 "org.freedesktop.portable1.Manager",
419 "AttachImage");
420 if (r < 0)
421 return bus_log_create_error(r);
422
423 r = sd_bus_message_append(m, "s", image);
424 if (r < 0)
425 return bus_log_create_error(r);
426
427 r = sd_bus_message_append_strv(m, matches);
428 if (r < 0)
429 return bus_log_create_error(r);
430
431 r = sd_bus_message_append(m, "sbs", arg_profile, arg_runtime, arg_copy_mode);
432 if (r < 0)
433 return bus_log_create_error(r);
434
435 r = sd_bus_call(bus, m, 0, &error, &reply);
436 if (r < 0)
437 return log_error_errno(r, "Failed to attach image: %s", bus_error_message(&error, r));
438
439 (void) maybe_reload(&bus);
440
441 print_changes(reply);
442 return 0;
443}
444
445static int detach_image(int argc, char *argv[], void *userdata) {
446 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
447 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
448 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
449 _cleanup_free_ char *image = NULL;
450 int r;
451
452 r = determine_image(argv[1], true, &image);
453 if (r < 0)
454 return r;
455
456 r = acquire_bus(&bus);
457 if (r < 0)
458 return r;
459
460 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
461
462 r = sd_bus_call_method(
463 bus,
464 "org.freedesktop.portable1",
465 "/org/freedesktop/portable1",
466 "org.freedesktop.portable1.Manager",
467 "DetachImage",
468 &error,
469 &reply,
470 "sb", image, arg_runtime);
471 if (r < 0)
472 return log_error_errno(r, "Failed to detach image: %s", bus_error_message(&error, r));
473
474 (void) maybe_reload(&bus);
475
476 print_changes(reply);
477 return 0;
478}
479
480static int list_images(int argc, char *argv[], void *userdata) {
481 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
482 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
483 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
484 _cleanup_(table_unrefp) Table *table = NULL;
485 int r;
486
487 r = acquire_bus(&bus);
488 if (r < 0)
489 return r;
490
491 r = sd_bus_call_method(
492 bus,
493 "org.freedesktop.portable1",
494 "/org/freedesktop/portable1",
495 "org.freedesktop.portable1.Manager",
496 "ListImages",
497 &error,
498 &reply,
499 NULL);
500 if (r < 0)
501 return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
502
9969b542 503 table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
61d0578b
LP
504 if (!table)
505 return log_oom();
506
507 r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)");
508 if (r < 0)
509 return bus_log_parse_error(r);
510
511 for (;;) {
31d99bd1 512 const char *name, *type, *state;
61d0578b
LP
513 uint64_t crtime, mtime, usage;
514 TableCell *cell;
515 bool ro_bool;
516 int ro_int;
517
31d99bd1 518 r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL);
61d0578b
LP
519 if (r < 0)
520 return bus_log_parse_error(r);
521 if (r == 0)
522 break;
523
524 r = table_add_many(table,
525 TABLE_STRING, name,
526 TABLE_STRING, type);
527 if (r < 0)
528 return log_error_errno(r, "Failed to add row to table: %m");
529
530 ro_bool = ro_int;
531 r = table_add_cell(table, &cell, TABLE_BOOLEAN, &ro_bool);
532 if (r < 0)
533 return log_error_errno(r, "Failed to add row to table: %m");
534
535 if (ro_bool) {
536 r = table_set_color(table, cell, ansi_highlight_red());
537 if (r < 0)
538 return log_error_errno(r, "Failed to set table cell color: %m");
539 }
540
541 r = table_add_many(table,
542 TABLE_TIMESTAMP, crtime,
543 TABLE_TIMESTAMP, mtime,
544 TABLE_SIZE, usage);
545 if (r < 0)
546 return log_error_errno(r, "Failed to add row to table: %m");
547
548 r = table_add_cell(table, &cell, TABLE_STRING, state);
549 if (r < 0)
550 return log_error_errno(r, "Failed to add row to table: %m");
551
552 if (!streq(state, "detached")) {
553 r = table_set_color(table, cell, ansi_highlight_green());
554 if (r < 0)
555 return log_error_errno(r, "Failed to set table cell color: %m");
556 }
557 }
558
559 r = sd_bus_message_exit_container(reply);
560 if (r < 0)
561 return bus_log_parse_error(r);
562
563 if (table_get_rows(table) > 1) {
564 r = table_set_sort(table, (size_t) 0, (size_t) -1);
565 if (r < 0)
566 return log_error_errno(r, "Failed to sort table: %m");
567
568 table_set_header(table, arg_legend);
569
570 r = table_print(table, NULL);
571 if (r < 0)
572 return log_error_errno(r, "Failed to show table: %m");
573 }
574
575 if (arg_legend) {
576 if (table_get_rows(table) > 1)
577 printf("\n%zu images listed.\n", table_get_rows(table) - 1);
578 else
579 printf("No images.\n");
580 }
581
582 return 0;
583}
584
585static int remove_image(int argc, char *argv[], void *userdata) {
586 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
587 int r, i;
588
589 r = acquire_bus(&bus);
590 if (r < 0)
591 return r;
592
593 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
594
595 for (i = 1; i < argc; i++) {
596 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
597 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
598
599 r = sd_bus_message_new_method_call(
600 bus,
601 &m,
602 "org.freedesktop.portable1",
603 "/org/freedesktop/portable1",
604 "org.freedesktop.portable1.Manager",
605 "RemoveImage");
606 if (r < 0)
607 return bus_log_create_error(r);
608
609 r = sd_bus_message_append(m, "s", argv[i]);
610 if (r < 0)
611 return bus_log_create_error(r);
612
613 /* This is a slow operation, hence turn off any method call timeouts */
614 r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
615 if (r < 0)
616 return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
617 }
618
619 return 0;
620}
621
622static int read_only_image(int argc, char *argv[], void *userdata) {
623 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
624 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
625 int b = true, r;
626
627 if (argc > 2) {
628 b = parse_boolean(argv[2]);
629 if (b < 0)
630 return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]);
631 }
632
633 r = acquire_bus(&bus);
634 if (r < 0)
635 return r;
636
637 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
638
639 r = sd_bus_call_method(
640 bus,
641 "org.freedesktop.portable1",
642 "/org/freedesktop/portable1",
643 "org.freedesktop.portable1.Manager",
644 "MarkImageReadOnly",
645 &error,
646 NULL,
647 "sb", argv[1], b);
648 if (r < 0)
649 return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
650
651 return 0;
652}
653
654static int set_limit(int argc, char *argv[], void *userdata) {
655 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
656 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
657 uint64_t limit;
658 int r;
659
660 r = acquire_bus(&bus);
661 if (r < 0)
662 return r;
663
664 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
665
666 if (STR_IN_SET(argv[argc-1], "-", "none", "infinity"))
667 limit = (uint64_t) -1;
668 else {
669 r = parse_size(argv[argc-1], 1024, &limit);
670 if (r < 0)
671 return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]);
672 }
673
674 if (argc > 2)
675 /* With two arguments changes the quota limit of the specified image */
676 r = sd_bus_call_method(
677 bus,
678 "org.freedesktop.portable1",
679 "/org/freedesktop/portable1",
680 "org.freedesktop.portable1.Manager",
681 "SetImageLimit",
682 &error,
683 NULL,
684 "st", argv[1], limit);
685 else
686 /* With one argument changes the pool quota limit */
687 r = sd_bus_call_method(
688 bus,
689 "org.freedesktop.portable1",
690 "/org/freedesktop/portable1",
691 "org.freedesktop.portable1.Manager",
692 "SetPoolLimit",
693 &error,
694 NULL,
695 "t", limit);
696
697 if (r < 0)
698 return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r));
699
700 return 0;
701}
702
703static int is_image_attached(int argc, char *argv[], void *userdata) {
704 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
705 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
706 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
707 _cleanup_free_ char *image = NULL;
708 const char *state;
709 int r;
710
711 r = determine_image(argv[1], true, &image);
712 if (r < 0)
713 return r;
714
715 r = acquire_bus(&bus);
716 if (r < 0)
717 return r;
718
719 r = sd_bus_call_method(
720 bus,
721 "org.freedesktop.portable1",
722 "/org/freedesktop/portable1",
723 "org.freedesktop.portable1.Manager",
724 "GetImageState",
725 &error,
726 &reply,
727 "s", image);
728 if (r < 0)
729 return log_error_errno(r, "Failed to get image state: %s", bus_error_message(&error, r));
730
731 r = sd_bus_message_read(reply, "s", &state);
732 if (r < 0)
733 return r;
734
735 if (!arg_quiet)
736 puts(state);
737
738 return streq(state, "detached");
739}
740
741static int dump_profiles(void) {
742 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
743 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
744 _cleanup_strv_free_ char **l = NULL;
61d0578b
LP
745 char **i;
746 int r;
747
748 r = acquire_bus(&bus);
749 if (r < 0)
750 return r;
751
752 r = sd_bus_get_property_strv(
753 bus,
754 "org.freedesktop.portable1",
755 "/org/freedesktop/portable1",
756 "org.freedesktop.portable1.Manager",
757 "Profiles",
758 &error,
759 &l);
760 if (r < 0)
761 return log_error_errno(r, "Failed to acquire list of profiles: %s", bus_error_message(&error, r));
762
763 if (arg_legend)
764 log_info("Available unit profiles:");
765
766 STRV_FOREACH(i, l) {
767 fputs(*i, stdout);
768 fputc('\n', stdout);
769 }
770
771 return 0;
772}
773
774static int help(int argc, char *argv[], void *userdata) {
37ec0fdd
LP
775 _cleanup_free_ char *link = NULL;
776 int r;
61d0578b 777
0221d68a 778 (void) pager_open(arg_pager_flags);
61d0578b 779
37ec0fdd
LP
780 r = terminal_urlify_man("portablectl", "1", &link);
781 if (r < 0)
782 return log_oom();
783
61d0578b
LP
784 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
785 "Attach or detach portable services from the local system.\n\n"
786 " -h --help Show this help\n"
787 " --version Show package version\n"
788 " --no-pager Do not pipe output into a pager\n"
789 " --no-legend Do not show the headers and footers\n"
790 " --no-ask-password Do not ask for system passwords\n"
791 " -H --host=[USER@]HOST Operate on remote host\n"
792 " -M --machine=CONTAINER Operate on local container\n"
793 " -q --quiet Suppress informational messages\n"
794 " -p --profile=PROFILE Pick security profile for portable service\n"
795 " --copy=copy|auto|symlink Prefer copying or symlinks if possible\n"
796 " --runtime Attach portable service until next reboot only\n"
797 " --no-reload Don't reload the system and service manager\n"
798 " --cat When inspecting include unit and os-release file\n"
799 " contents\n\n"
800 "Commands:\n"
801 " list List available portable service images\n"
802 " attach NAME|PATH [PREFIX...]\n"
803 " Attach the specified portable service image\n"
804 " detach NAME|PATH Detach the specified portable service image\n"
805 " inspect NAME|PATH [PREFIX...]\n"
806 " Show details of specified portable service image\n"
807 " is-attached NAME|PATH Query if portable service image is attached\n"
808 " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
809 " remove NAME|PATH... Remove a portable service image\n"
810 " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
37ec0fdd
LP
811 "\nSee the %s for details.\n"
812 , program_invocation_short_name
813 , link
814 );
61d0578b
LP
815
816 return 0;
817}
818
819static int parse_argv(int argc, char *argv[]) {
820
821 enum {
822 ARG_VERSION = 0x100,
823 ARG_NO_PAGER,
824 ARG_NO_LEGEND,
825 ARG_NO_ASK_PASSWORD,
826 ARG_COPY,
827 ARG_RUNTIME,
828 ARG_NO_RELOAD,
829 ARG_CAT,
830 };
831
832 static const struct option options[] = {
833 { "help", no_argument, NULL, 'h' },
834 { "version", no_argument, NULL, ARG_VERSION },
835 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
836 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
837 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
838 { "host", required_argument, NULL, 'H' },
839 { "machine", required_argument, NULL, 'M' },
840 { "quiet", no_argument, NULL, 'q' },
841 { "profile", required_argument, NULL, 'p' },
842 { "copy", required_argument, NULL, ARG_COPY },
843 { "runtime", no_argument, NULL, ARG_RUNTIME },
844 { "no-reload", no_argument, NULL, ARG_NO_RELOAD },
845 { "cat", no_argument, NULL, ARG_CAT },
846 {}
847 };
848
849 assert(argc >= 0);
850 assert(argv);
851
852 for (;;) {
853 int c;
854
855 c = getopt_long(argc, argv, "hH:M:qp:", options, NULL);
856 if (c < 0)
857 break;
858
859 switch (c) {
860
861 case 'h':
37ec0fdd 862 return help(0, NULL, NULL);
61d0578b
LP
863
864 case ARG_VERSION:
865 return version();
866
867 case ARG_NO_PAGER:
0221d68a 868 arg_pager_flags |= PAGER_DISABLE;
61d0578b
LP
869 break;
870
871 case ARG_NO_LEGEND:
872 arg_legend = false;
873 break;
874
875 case ARG_NO_ASK_PASSWORD:
876 arg_ask_password = false;
877 break;
878
879 case 'H':
880 arg_transport = BUS_TRANSPORT_REMOTE;
881 arg_host = optarg;
882 break;
883
884 case 'M':
885 arg_transport = BUS_TRANSPORT_MACHINE;
886 arg_host = optarg;
887 break;
888
889 case 'q':
890 arg_quiet = true;
891 break;
892
893 case 'p':
b3c66c44
LP
894 if (streq(optarg, "help"))
895 return dump_profiles();
896
baaa35ad
ZJS
897 if (!filename_is_valid(optarg))
898 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
899 "Unit profile name not valid: %s", optarg);
61d0578b 900
61d0578b
LP
901 arg_profile = optarg;
902 break;
903
904 case ARG_COPY:
905 if (streq(optarg, "auto"))
906 arg_copy_mode = NULL;
907 else if (STR_IN_SET(optarg, "copy", "symlink"))
908 arg_copy_mode = optarg;
f524522b
LP
909 else if (streq(optarg, "help")) {
910 puts("auto\n"
911 "copy\n"
912 "symlink");
913 return 0;
baaa35ad
ZJS
914 } else
915 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
916 "Failed to parse --copy= argument: %s", optarg);
61d0578b
LP
917
918 break;
919
920 case ARG_RUNTIME:
921 arg_runtime = true;
922 break;
923
924 case ARG_NO_RELOAD:
925 arg_reload = false;
926 break;
927
928 case ARG_CAT:
929 arg_cat = true;
930 break;
931
932 case '?':
933 return -EINVAL;
934
935 default:
936 assert_not_reached("Unhandled option");
937 }
938 }
939
940 return 1;
941}
942
c55a594e 943static int run(int argc, char *argv[]) {
61d0578b
LP
944 static const Verb verbs[] = {
945 { "help", VERB_ANY, VERB_ANY, 0, help },
946 { "list", VERB_ANY, 1, VERB_DEFAULT, list_images },
947 { "attach", 2, VERB_ANY, 0, attach_image },
948 { "detach", 2, 2, 0, detach_image },
949 { "inspect", 2, VERB_ANY, 0, inspect_image },
950 { "is-attached", 2, 2, 0, is_image_attached },
951 { "read-only", 2, 3, 0, read_only_image },
952 { "remove", 2, VERB_ANY, 0, remove_image },
953 { "set-limit", 3, 3, 0, set_limit },
954 {}
955 };
956
957 int r;
958
1a043959 959 log_show_color(true);
61d0578b
LP
960 log_parse_environment();
961 log_open();
962
963 r = parse_argv(argc, argv);
964 if (r <= 0)
c55a594e 965 return r;
61d0578b 966
c55a594e 967 return dispatch_verb(argc, argv, verbs, NULL);
61d0578b 968}
c55a594e
ZJS
969
970DEFINE_MAIN_FUNCTION(run);