]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/portable/portablectl.c
Merge pull request #18375 from yuwata/cli-tools-also-read-kernel-command-line
[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 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 bus_log_connect_error(r);
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 const uint64_t flags = UNIT_FILE_PORTABLE | (arg_runtime ? UNIT_FILE_RUNTIME : 0);
396 size_t n_changes = 0;
397 int r;
398
399 if (!arg_enable)
400 return 0;
401
402 names = strv_new(path, NULL);
403 if (!names)
404 return log_oom();
405
406 r = sd_bus_message_new_method_call(
407 bus,
408 &m,
409 "org.freedesktop.systemd1",
410 "/org/freedesktop/systemd1",
411 "org.freedesktop.systemd1.Manager",
412 enable ? "EnableUnitFilesWithFlags" : "DisableUnitFilesWithFlags");
413 if (r < 0)
414 return bus_log_create_error(r);
415
416 r = sd_bus_message_append_strv(m, names);
417 if (r < 0)
418 return bus_log_create_error(r);
419
420 r = sd_bus_message_append(m, "t", flags);
421 if (r < 0)
422 return bus_log_create_error(r);
423
424 r = sd_bus_call(bus, m, 0, &error, &reply);
425 if (r < 0)
426 return log_error_errno(r, "Failed to %s the portable service %s: %s",
427 enable ? "enable" : "disable", path, bus_error_message(&error, r));
428
429 if (enable) {
430 r = sd_bus_message_skip(reply, "b");
431 if (r < 0)
432 return bus_log_parse_error(r);
433 }
434 (void) bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
435
436 return 0;
437 }
438
439 static int maybe_start_stop(sd_bus *bus, const char *path, bool start, BusWaitForJobs *wait) {
440 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
441 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
442 char *name = (char *)basename(path), *job = NULL;
443 int r;
444
445 if (!arg_now)
446 return 0;
447
448 r = sd_bus_call_method(
449 bus,
450 "org.freedesktop.systemd1",
451 "/org/freedesktop/systemd1",
452 "org.freedesktop.systemd1.Manager",
453 start ? "StartUnit" : "StopUnit",
454 &error,
455 &reply,
456 "ss", name, "replace");
457 if (r < 0)
458 return log_error_errno(r, "Failed to %s the portable service %s: %s",
459 start ? "start" : "stop",
460 path,
461 bus_error_message(&error, r));
462
463 r = sd_bus_message_read(reply, "o", &job);
464 if (r < 0)
465 return bus_log_parse_error(r);
466
467 if (!arg_quiet)
468 log_info("Queued %s to %s portable service %s.", job, start ? "start" : "stop", name);
469
470 if (wait) {
471 r = bus_wait_for_jobs_add(wait, job);
472 if (r < 0)
473 return log_error_errno(r, "Failed to watch %s job for %s %s: %m",
474 job, start ? "starting" : "stopping", name);
475 }
476
477 return 0;
478 }
479
480 static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) {
481 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
482 int r;
483
484 if (!arg_enable && !arg_now)
485 return 0;
486
487 if (!arg_no_block) {
488 r = bus_wait_for_jobs_new(bus, &wait);
489 if (r < 0)
490 return log_error_errno(r, "Could not watch jobs: %m");
491 }
492
493 r = sd_bus_message_rewind(reply, true);
494 if (r < 0)
495 return r;
496 r = sd_bus_message_enter_container(reply, 'a', "(sss)");
497 if (r < 0)
498 return bus_log_parse_error(r);
499
500 for (;;) {
501 char *type, *path, *source;
502
503 r = sd_bus_message_read(reply, "(sss)", &type, &path, &source);
504 if (r < 0)
505 return bus_log_parse_error(r);
506 if (r == 0)
507 break;
508
509 if (STR_IN_SET(type, "symlink", "copy") && ENDSWITH_SET(path, ".service", ".target", ".socket")) {
510 (void) maybe_enable_disable(bus, path, true);
511 (void) maybe_start_stop(bus, path, true, wait);
512 }
513 }
514
515 r = sd_bus_message_exit_container(reply);
516 if (r < 0)
517 return r;
518
519 if (!arg_no_block) {
520 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
521 if (r < 0)
522 return r;
523 }
524
525 return 0;
526 }
527
528 static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) {
529 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
530 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
531 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
532 _cleanup_strv_free_ char **matches = NULL;
533 int r;
534
535 if (!arg_enable && !arg_now)
536 return 0;
537
538 r = determine_matches(argv[1], argv + 2, true, &matches);
539 if (r < 0)
540 return r;
541
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 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "GetImageMetadata");
547 if (r < 0)
548 return bus_log_create_error(r);
549
550 r = sd_bus_message_append(m, "s", image);
551 if (r < 0)
552 return bus_log_create_error(r);
553
554 r = sd_bus_message_append_strv(m, matches);
555 if (r < 0)
556 return bus_log_create_error(r);
557
558 r = sd_bus_call(bus, m, 0, &error, &reply);
559 if (r < 0)
560 return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r));
561
562 r = sd_bus_message_skip(reply, "say");
563 if (r < 0)
564 return bus_log_parse_error(r);
565
566 r = sd_bus_message_enter_container(reply, 'a', "{say}");
567 if (r < 0)
568 return bus_log_parse_error(r);
569
570 for (;;) {
571 const char *name;
572
573 r = sd_bus_message_enter_container(reply, 'e', "say");
574 if (r < 0)
575 return bus_log_parse_error(r);
576 if (r == 0)
577 break;
578
579 r = sd_bus_message_read(reply, "s", &name);
580 if (r < 0)
581 return bus_log_parse_error(r);
582
583 r = sd_bus_message_skip(reply, "ay");
584 if (r < 0)
585 return bus_log_parse_error(r);
586
587 r = sd_bus_message_exit_container(reply);
588 if (r < 0)
589 return bus_log_parse_error(r);
590
591 (void) maybe_start_stop(bus, name, false, wait);
592 (void) maybe_enable_disable(bus, name, false);
593 }
594
595 r = sd_bus_message_exit_container(reply);
596 if (r < 0)
597 return bus_log_parse_error(r);
598
599 /* Stopping must always block or the detach will fail if the unit is still running */
600 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
601 if (r < 0)
602 return r;
603
604 return 0;
605 }
606
607 static int attach_image(int argc, char *argv[], void *userdata) {
608 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
609 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
610 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
611 _cleanup_strv_free_ char **matches = NULL;
612 _cleanup_free_ char *image = NULL;
613 int r;
614
615 r = determine_image(argv[1], false, &image);
616 if (r < 0)
617 return r;
618
619 r = determine_matches(argv[1], argv + 2, false, &matches);
620 if (r < 0)
621 return r;
622
623 r = acquire_bus(&bus);
624 if (r < 0)
625 return r;
626
627 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
628
629 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "AttachImage");
630 if (r < 0)
631 return bus_log_create_error(r);
632
633 r = sd_bus_message_append(m, "s", image);
634 if (r < 0)
635 return bus_log_create_error(r);
636
637 r = sd_bus_message_append_strv(m, matches);
638 if (r < 0)
639 return bus_log_create_error(r);
640
641 r = sd_bus_message_append(m, "sbs", arg_profile, arg_runtime, arg_copy_mode);
642 if (r < 0)
643 return bus_log_create_error(r);
644
645 r = sd_bus_call(bus, m, 0, &error, &reply);
646 if (r < 0)
647 return log_error_errno(r, "Failed to attach image: %s", bus_error_message(&error, r));
648
649 (void) maybe_reload(&bus);
650
651 print_changes(reply);
652
653 (void) maybe_enable_start(bus, reply);
654
655 return 0;
656 }
657
658 static int detach_image(int argc, char *argv[], void *userdata) {
659 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
660 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
661 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
662 _cleanup_free_ char *image = NULL;
663 int r;
664
665 r = determine_image(argv[1], true, &image);
666 if (r < 0)
667 return r;
668
669 r = acquire_bus(&bus);
670 if (r < 0)
671 return r;
672
673 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
674
675 (void) maybe_stop_disable(bus, image, argv);
676
677 r = bus_call_method(bus, bus_portable_mgr, "DetachImage", &error, &reply, "sb", image, arg_runtime);
678 if (r < 0)
679 return log_error_errno(r, "Failed to detach image: %s", bus_error_message(&error, r));
680
681 (void) maybe_reload(&bus);
682
683 print_changes(reply);
684 return 0;
685 }
686
687 static int list_images(int argc, char *argv[], void *userdata) {
688 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
689 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
690 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
691 _cleanup_(table_unrefp) Table *table = NULL;
692 int r;
693
694 r = acquire_bus(&bus);
695 if (r < 0)
696 return r;
697
698 r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL);
699 if (r < 0)
700 return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
701
702 table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
703 if (!table)
704 return log_oom();
705
706 r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)");
707 if (r < 0)
708 return bus_log_parse_error(r);
709
710 for (;;) {
711 const char *name, *type, *state;
712 uint64_t crtime, mtime, usage;
713 int ro_int;
714
715 r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL);
716 if (r < 0)
717 return bus_log_parse_error(r);
718 if (r == 0)
719 break;
720
721 r = table_add_many(table,
722 TABLE_STRING, name,
723 TABLE_STRING, type,
724 TABLE_BOOLEAN, ro_int,
725 TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL,
726 TABLE_TIMESTAMP, crtime,
727 TABLE_TIMESTAMP, mtime,
728 TABLE_SIZE, usage,
729 TABLE_STRING, state,
730 TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL);
731 if (r < 0)
732 return table_log_add_error(r);
733 }
734
735 r = sd_bus_message_exit_container(reply);
736 if (r < 0)
737 return bus_log_parse_error(r);
738
739 if (table_get_rows(table) > 1) {
740 r = table_set_sort(table, (size_t) 0, (size_t) -1);
741 if (r < 0)
742 return table_log_sort_error(r);
743
744 table_set_header(table, arg_legend);
745
746 r = table_print(table, NULL);
747 if (r < 0)
748 return table_log_print_error(r);
749 }
750
751 if (arg_legend) {
752 if (table_get_rows(table) > 1)
753 printf("\n%zu images listed.\n", table_get_rows(table) - 1);
754 else
755 printf("No images.\n");
756 }
757
758 return 0;
759 }
760
761 static int remove_image(int argc, char *argv[], void *userdata) {
762 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
763 int r, i;
764
765 r = acquire_bus(&bus);
766 if (r < 0)
767 return r;
768
769 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
770
771 for (i = 1; i < argc; i++) {
772 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
773 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
774
775 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "RemoveImage");
776 if (r < 0)
777 return bus_log_create_error(r);
778
779 r = sd_bus_message_append(m, "s", argv[i]);
780 if (r < 0)
781 return bus_log_create_error(r);
782
783 /* This is a slow operation, hence turn off any method call timeouts */
784 r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
785 if (r < 0)
786 return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
787 }
788
789 return 0;
790 }
791
792 static int read_only_image(int argc, char *argv[], void *userdata) {
793 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
794 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
795 int b = true, r;
796
797 if (argc > 2) {
798 b = parse_boolean(argv[2]);
799 if (b < 0)
800 return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]);
801 }
802
803 r = acquire_bus(&bus);
804 if (r < 0)
805 return r;
806
807 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
808
809 r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b);
810 if (r < 0)
811 return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
812
813 return 0;
814 }
815
816 static int set_limit(int argc, char *argv[], void *userdata) {
817 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
818 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
819 uint64_t limit;
820 int r;
821
822 r = acquire_bus(&bus);
823 if (r < 0)
824 return r;
825
826 (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
827
828 if (STR_IN_SET(argv[argc-1], "-", "none", "infinity"))
829 limit = (uint64_t) -1;
830 else {
831 r = parse_size(argv[argc-1], 1024, &limit);
832 if (r < 0)
833 return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]);
834 }
835
836 if (argc > 2)
837 /* With two arguments changes the quota limit of the specified image */
838 r = bus_call_method(bus, bus_portable_mgr, "SetImageLimit", &error, NULL, "st", argv[1], limit);
839 else
840 /* With one argument changes the pool quota limit */
841 r = bus_call_method(bus, bus_portable_mgr, "SetPoolLimit", &error, NULL, "t", limit);
842
843 if (r < 0)
844 return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r));
845
846 return 0;
847 }
848
849 static int is_image_attached(int argc, char *argv[], void *userdata) {
850 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
851 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
852 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
853 _cleanup_free_ char *image = NULL;
854 const char *state;
855 int r;
856
857 r = determine_image(argv[1], true, &image);
858 if (r < 0)
859 return r;
860
861 r = acquire_bus(&bus);
862 if (r < 0)
863 return r;
864
865 r = bus_call_method(bus, bus_portable_mgr, "GetImageState", &error, &reply, "s", image);
866 if (r < 0)
867 return log_error_errno(r, "Failed to get image state: %s", bus_error_message(&error, r));
868
869 r = sd_bus_message_read(reply, "s", &state);
870 if (r < 0)
871 return r;
872
873 if (!arg_quiet)
874 puts(state);
875
876 return streq(state, "detached");
877 }
878
879 static int dump_profiles(void) {
880 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
881 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
882 _cleanup_strv_free_ char **l = NULL;
883 char **i;
884 int r;
885
886 r = acquire_bus(&bus);
887 if (r < 0)
888 return r;
889
890 r = bus_get_property_strv(bus, bus_portable_mgr, "Profiles", &error, &l);
891 if (r < 0)
892 return log_error_errno(r, "Failed to acquire list of profiles: %s", bus_error_message(&error, r));
893
894 if (arg_legend)
895 log_info("Available unit profiles:");
896
897 STRV_FOREACH(i, l) {
898 fputs(*i, stdout);
899 fputc('\n', stdout);
900 }
901
902 return 0;
903 }
904
905 static int help(int argc, char *argv[], void *userdata) {
906 _cleanup_free_ char *link = NULL;
907 int r;
908
909 (void) pager_open(arg_pager_flags);
910
911 r = terminal_urlify_man("portablectl", "1", &link);
912 if (r < 0)
913 return log_oom();
914
915 printf("%s [OPTIONS...] COMMAND ...\n\n"
916 "%sAttach or detach portable services from the local system.%s\n"
917 "\nCommands:\n"
918 " list List available portable service images\n"
919 " attach NAME|PATH [PREFIX...]\n"
920 " Attach the specified portable service image\n"
921 " detach NAME|PATH [PREFIX...]\n"
922 " Detach the specified portable service image\n"
923 " inspect NAME|PATH [PREFIX...]\n"
924 " Show details of specified portable service image\n"
925 " is-attached NAME|PATH Query if portable service image is attached\n"
926 " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
927 " remove NAME|PATH... Remove a portable service image\n"
928 " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
929 "\nOptions:\n"
930 " -h --help Show this help\n"
931 " --version Show package version\n"
932 " --no-pager Do not pipe output into a pager\n"
933 " --no-legend Do not show the headers and footers\n"
934 " --no-ask-password Do not ask for system passwords\n"
935 " -H --host=[USER@]HOST Operate on remote host\n"
936 " -M --machine=CONTAINER Operate on local container\n"
937 " -q --quiet Suppress informational messages\n"
938 " -p --profile=PROFILE Pick security profile for portable service\n"
939 " --copy=copy|auto|symlink Prefer copying or symlinks if possible\n"
940 " --runtime Attach portable service until next reboot only\n"
941 " --no-reload Don't reload the system and service manager\n"
942 " --cat When inspecting include unit and os-release file\n"
943 " contents\n"
944 " --enable Immediately enable/disable the portable service\n"
945 " after attach/detach\n"
946 " --now Immediately start/stop the portable service after\n"
947 " attach/before detach\n"
948 " --no-block Don't block waiting for attach --now to complete\n"
949 "\nSee the %s for details.\n",
950 program_invocation_short_name,
951 ansi_highlight(),
952 ansi_normal(),
953 link);
954
955 return 0;
956 }
957
958 static int parse_argv(int argc, char *argv[]) {
959
960 enum {
961 ARG_VERSION = 0x100,
962 ARG_NO_PAGER,
963 ARG_NO_LEGEND,
964 ARG_NO_ASK_PASSWORD,
965 ARG_COPY,
966 ARG_RUNTIME,
967 ARG_NO_RELOAD,
968 ARG_CAT,
969 ARG_ENABLE,
970 ARG_NOW,
971 ARG_NO_BLOCK,
972 };
973
974 static const struct option options[] = {
975 { "help", no_argument, NULL, 'h' },
976 { "version", no_argument, NULL, ARG_VERSION },
977 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
978 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
979 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
980 { "host", required_argument, NULL, 'H' },
981 { "machine", required_argument, NULL, 'M' },
982 { "quiet", no_argument, NULL, 'q' },
983 { "profile", required_argument, NULL, 'p' },
984 { "copy", required_argument, NULL, ARG_COPY },
985 { "runtime", no_argument, NULL, ARG_RUNTIME },
986 { "no-reload", no_argument, NULL, ARG_NO_RELOAD },
987 { "cat", no_argument, NULL, ARG_CAT },
988 { "enable", no_argument, NULL, ARG_ENABLE },
989 { "now", no_argument, NULL, ARG_NOW },
990 { "no-block", no_argument, NULL, ARG_NO_BLOCK },
991 {}
992 };
993
994 assert(argc >= 0);
995 assert(argv);
996
997 for (;;) {
998 int c;
999
1000 c = getopt_long(argc, argv, "hH:M:qp:", options, NULL);
1001 if (c < 0)
1002 break;
1003
1004 switch (c) {
1005
1006 case 'h':
1007 return help(0, NULL, NULL);
1008
1009 case ARG_VERSION:
1010 return version();
1011
1012 case ARG_NO_PAGER:
1013 arg_pager_flags |= PAGER_DISABLE;
1014 break;
1015
1016 case ARG_NO_LEGEND:
1017 arg_legend = false;
1018 break;
1019
1020 case ARG_NO_ASK_PASSWORD:
1021 arg_ask_password = false;
1022 break;
1023
1024 case 'H':
1025 arg_transport = BUS_TRANSPORT_REMOTE;
1026 arg_host = optarg;
1027 break;
1028
1029 case 'M':
1030 arg_transport = BUS_TRANSPORT_MACHINE;
1031 arg_host = optarg;
1032 break;
1033
1034 case 'q':
1035 arg_quiet = true;
1036 break;
1037
1038 case 'p':
1039 if (streq(optarg, "help"))
1040 return dump_profiles();
1041
1042 if (!filename_is_valid(optarg))
1043 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1044 "Unit profile name not valid: %s", optarg);
1045
1046 arg_profile = optarg;
1047 break;
1048
1049 case ARG_COPY:
1050 if (streq(optarg, "auto"))
1051 arg_copy_mode = NULL;
1052 else if (STR_IN_SET(optarg, "copy", "symlink"))
1053 arg_copy_mode = optarg;
1054 else if (streq(optarg, "help")) {
1055 puts("auto\n"
1056 "copy\n"
1057 "symlink");
1058 return 0;
1059 } else
1060 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1061 "Failed to parse --copy= argument: %s", optarg);
1062
1063 break;
1064
1065 case ARG_RUNTIME:
1066 arg_runtime = true;
1067 break;
1068
1069 case ARG_NO_RELOAD:
1070 arg_reload = false;
1071 break;
1072
1073 case ARG_CAT:
1074 arg_cat = true;
1075 break;
1076
1077 case ARG_ENABLE:
1078 arg_enable = true;
1079 break;
1080
1081 case ARG_NOW:
1082 arg_now = true;
1083 break;
1084
1085 case ARG_NO_BLOCK:
1086 arg_no_block = true;
1087 break;
1088
1089 case '?':
1090 return -EINVAL;
1091
1092 default:
1093 assert_not_reached("Unhandled option");
1094 }
1095 }
1096
1097 return 1;
1098 }
1099
1100 static int run(int argc, char *argv[]) {
1101 static const Verb verbs[] = {
1102 { "help", VERB_ANY, VERB_ANY, 0, help },
1103 { "list", VERB_ANY, 1, VERB_DEFAULT, list_images },
1104 { "attach", 2, VERB_ANY, 0, attach_image },
1105 { "detach", 2, VERB_ANY, 0, detach_image },
1106 { "inspect", 2, VERB_ANY, 0, inspect_image },
1107 { "is-attached", 2, 2, 0, is_image_attached },
1108 { "read-only", 2, 3, 0, read_only_image },
1109 { "remove", 2, VERB_ANY, 0, remove_image },
1110 { "set-limit", 3, 3, 0, set_limit },
1111 {}
1112 };
1113
1114 int r;
1115
1116 log_setup();
1117
1118 r = parse_argv(argc, argv);
1119 if (r <= 0)
1120 return r;
1121
1122 return dispatch_verb(argc, argv, verbs, NULL);
1123 }
1124
1125 DEFINE_MAIN_FUNCTION(run);