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