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