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