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