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