]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/portable/portablectl.c
Merge pull request #17185 from yuwata/ethtool-update
[thirdparty/systemd.git] / src / portable / portablectl.c
CommitLineData
61d0578b
LP
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
3#include <errno.h>
4#include <getopt.h>
5
6#include "sd-bus.h"
7
8#include "alloc-util.h"
9#include "bus-error.h"
9b71e4ab 10#include "bus-locator.h"
e2c1ddcc 11#include "bus-unit-util.h"
31c33315 12#include "bus-wait-for-jobs.h"
61d0578b
LP
13#include "def.h"
14#include "dirent-util.h"
686d13b9 15#include "env-file.h"
61d0578b 16#include "fd-util.h"
673a1e6f 17#include "fileio.h"
61d0578b
LP
18#include "format-table.h"
19#include "fs-util.h"
20#include "locale-util.h"
21#include "machine-image.h"
c55a594e 22#include "main-func.h"
61d0578b
LP
23#include "pager.h"
24#include "parse-util.h"
25#include "path-util.h"
294bf0c3 26#include "pretty-print.h"
61d0578b
LP
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
0221d68a 33static PagerFlags arg_pager_flags = 0;
61d0578b
LP
34static bool arg_legend = true;
35static bool arg_ask_password = true;
36static bool arg_quiet = false;
37static const char *arg_profile = "default";
38static const char* arg_copy_mode = NULL;
39static bool arg_runtime = false;
40static bool arg_reload = true;
41static bool arg_cat = false;
42static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
0c760322 43static const char *arg_host = NULL;
e2c1ddcc
LB
44static bool arg_enable = false;
45static bool arg_now = false;
31c33315 46static bool arg_no_block = false;
61d0578b
LP
47
48static 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
baaa35ad
ZJS
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.");
61d0578b 74
a5648b80 75 r = chase_symlinks(image, NULL, CHASE_TRAIL_SLASH | (permit_non_existing ? CHASE_NONEXISTENT : 0), ret, NULL);
61d0578b
LP
76 if (r < 0)
77 return log_error_errno(r, "Cannot normalize specified image path '%s': %m", image);
78
79 return 0;
80}
81
82static 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
810aa425 114 *ret = TAKE_PTR(name);
61d0578b
LP
115
116 return 0;
117}
118
119static int determine_matches(const char *image, char **l, bool allow_any, char ***ret) {
66c9dfdd 120 _cleanup_strv_free_ char **k = NULL;
61d0578b
LP
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
61d0578b
LP
137 r = strv_consume(&k, prefix);
138 if (r < 0)
139 return log_oom();
140
141 } else if (strv_equal(l, STRV_MAKE("-"))) {
142
baaa35ad
ZJS
143 if (!allow_any)
144 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
145 "Refusing all unit file match.");
61d0578b
LP
146
147 if (!arg_quiet)
148 log_info("(Matching all unit files.)");
61d0578b 149 } else {
61d0578b
LP
150
151 k = strv_copy(l);
152 if (!k)
153 return log_oom();
154
21cffed7
LP
155 if (!arg_quiet) {
156 _cleanup_free_ char *joined = NULL;
157
158 joined = strv_join(k, "', '");
159 if (!joined)
160 return log_oom();
61d0578b 161
61d0578b 162 log_info("(Matching unit files with prefixes '%s'.)", joined);
21cffed7 163 }
61d0578b
LP
164 }
165
66c9dfdd 166 *ret = TAKE_PTR(k);
61d0578b
LP
167
168 return 0;
169}
170
171static 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)
ab4a88eb 181 return bus_log_connect_error(r);
61d0578b
LP
182
183 (void) sd_bus_set_allow_interactive_authorization(*bus, arg_ask_password);
184
185 return 0;
186}
187
188static 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
218static 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
bf5c8177 242 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "GetImageMetadata");
61d0578b
LP
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
0221d68a 266 (void) pager_open(arg_pager_flags);
61d0578b
LP
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 {
8d93d7d8 274 _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL;
61d0578b
LP
275 _cleanup_fclose_ FILE *f;
276
673a1e6f 277 f = fmemopen_unlocked((void*) data, sz, "re");
61d0578b
LP
278 if (!f)
279 return log_error_errno(errno, "Failed to open /etc/os-release buffer: %m");
280
aa8fbc74 281 r = parse_env_file(f, "/etc/os-release",
61d0578b 282 "PORTABLE_PRETTY_NAME", &pretty_portable,
13df9c39 283 "PRETTY_NAME", &pretty_os);
61d0578b
LP
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
347static 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"))
9a6f746f 367 log_info("Created symlink %s %s %s.", path, special_glyph(SPECIAL_GLYPH_ARROW), source);
61d0578b
LP
368 else if (streq(type, "copy")) {
369 if (isempty(source))
370 log_info("Copied %s.", path);
371 else
9a6f746f 372 log_info("Copied %s %s %s.", source, special_glyph(SPECIAL_GLYPH_ARROW), path);
61d0578b
LP
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
e2c1ddcc
LB
390static 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;
83654007 395 const uint64_t flags = UNIT_FILE_PORTABLE | (arg_runtime ? UNIT_FILE_RUNTIME : 0);
e2c1ddcc
LB
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",
83654007 412 enable ? "EnableUnitFilesWithFlags" : "DisableUnitFilesWithFlags");
e2c1ddcc
LB
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
83654007 420 r = sd_bus_message_append(m, "t", flags);
e2c1ddcc
LB
421 if (r < 0)
422 return bus_log_create_error(r);
423
e2c1ddcc
LB
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
31c33315 439static int maybe_start_stop(sd_bus *bus, const char *path, bool start, BusWaitForJobs *wait) {
e2c1ddcc
LB
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
31c33315
LB
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
e2c1ddcc
LB
477 return 0;
478}
479
480static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) {
31c33315 481 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
e2c1ddcc
LB
482 int r;
483
484 if (!arg_enable && !arg_now)
485 return 0;
486
31c33315
LB
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
e2c1ddcc
LB
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);
31c33315 511 (void) maybe_start_stop(bus, path, true, wait);
e2c1ddcc
LB
512 }
513 }
514
515 r = sd_bus_message_exit_container(reply);
516 if (r < 0)
517 return r;
518
31c33315
LB
519 if (!arg_no_block) {
520 r = bus_wait_for_jobs(wait, arg_quiet, NULL);
521 if (r < 0)
522 return r;
523 }
524
e2c1ddcc
LB
525 return 0;
526}
527
528static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) {
31c33315 529 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL;
e2c1ddcc
LB
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
31c33315
LB
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
bf5c8177 546 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "GetImageMetadata");
e2c1ddcc
LB
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
31c33315 591 (void) maybe_start_stop(bus, name, false, wait);
e2c1ddcc
LB
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
31c33315
LB
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
e2c1ddcc
LB
604 return 0;
605}
606
61d0578b
LP
607static 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
bf5c8177 629 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "AttachImage");
61d0578b
LP
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);
e2c1ddcc
LB
652
653 (void) maybe_enable_start(bus, reply);
654
61d0578b
LP
655 return 0;
656}
657
658static 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
e2c1ddcc
LB
675 (void) maybe_stop_disable(bus, image, argv);
676
bf5c8177 677 r = bus_call_method(bus, bus_portable_mgr, "DetachImage", &error, &reply, "sb", image, arg_runtime);
61d0578b
LP
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
687static 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
bf5c8177 698 r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL);
61d0578b
LP
699 if (r < 0)
700 return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
701
9969b542 702 table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
61d0578b
LP
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 (;;) {
31d99bd1 711 const char *name, *type, *state;
61d0578b 712 uint64_t crtime, mtime, usage;
61d0578b
LP
713 int ro_int;
714
31d99bd1 715 r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL);
61d0578b
LP
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,
964a7745
YW
723 TABLE_STRING, type,
724 TABLE_BOOLEAN, ro_int,
725 TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL,
61d0578b
LP
726 TABLE_TIMESTAMP, crtime,
727 TABLE_TIMESTAMP, mtime,
964a7745
YW
728 TABLE_SIZE, usage,
729 TABLE_STRING, state,
730 TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL);
61d0578b 731 if (r < 0)
964a7745 732 return table_log_add_error(r);
61d0578b
LP
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)
df83eb54 742 return table_log_sort_error(r);
61d0578b
LP
743
744 table_set_header(table, arg_legend);
745
746 r = table_print(table, NULL);
747 if (r < 0)
4b6607d9 748 return table_log_print_error(r);
61d0578b
LP
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
761static 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
bf5c8177 775 r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "RemoveImage");
61d0578b
LP
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
792static 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
bf5c8177 809 r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b);
61d0578b
LP
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
816static 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 */
bf5c8177 838 r = bus_call_method(bus, bus_portable_mgr, "SetImageLimit", &error, NULL, "st", argv[1], limit);
61d0578b
LP
839 else
840 /* With one argument changes the pool quota limit */
bf5c8177 841 r = bus_call_method(bus, bus_portable_mgr, "SetPoolLimit", &error, NULL, "t", limit);
61d0578b
LP
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
849static 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
bf5c8177 865 r = bus_call_method(bus, bus_portable_mgr, "GetImageState", &error, &reply, "s", image);
61d0578b
LP
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
879static 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;
61d0578b
LP
883 char **i;
884 int r;
885
886 r = acquire_bus(&bus);
887 if (r < 0)
888 return r;
889
bf5c8177 890 r = bus_get_property_strv(bus, bus_portable_mgr, "Profiles", &error, &l);
61d0578b
LP
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
905static int help(int argc, char *argv[], void *userdata) {
37ec0fdd
LP
906 _cleanup_free_ char *link = NULL;
907 int r;
61d0578b 908
0221d68a 909 (void) pager_open(arg_pager_flags);
61d0578b 910
37ec0fdd
LP
911 r = terminal_urlify_man("portablectl", "1", &link);
912 if (r < 0)
913 return log_oom();
914
353b2baa
LP
915 printf("%s [OPTIONS...] COMMAND ...\n\n"
916 "%sAttach or detach portable services from the local system.%s\n"
917 "\nCommands:\n"
e1fac8a6
ZJS
918 " list List available portable service images\n"
919 " attach NAME|PATH [PREFIX...]\n"
920 " Attach the specified portable service image\n"
e2c1ddcc
LB
921 " detach NAME|PATH [PREFIX...]\n"
922 " Detach the specified portable service image\n"
e1fac8a6
ZJS
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"
353b2baa 929 "\nOptions:\n"
61d0578b
LP
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"
e1fac8a6 943 " contents\n"
e2c1ddcc
LB
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"
31c33315 948 " --no-block Don't block waiting for attach --now to complete\n"
37ec0fdd
LP
949 "\nSee the %s for details.\n"
950 , program_invocation_short_name
353b2baa 951 , ansi_highlight()
ce2529b4 952 , ansi_normal()
37ec0fdd
LP
953 , link
954 );
61d0578b
LP
955
956 return 0;
957}
958
959static int parse_argv(int argc, char *argv[]) {
960
961 enum {
962 ARG_VERSION = 0x100,
963 ARG_NO_PAGER,
964 ARG_NO_LEGEND,
965 ARG_NO_ASK_PASSWORD,
966 ARG_COPY,
967 ARG_RUNTIME,
968 ARG_NO_RELOAD,
969 ARG_CAT,
e2c1ddcc
LB
970 ARG_ENABLE,
971 ARG_NOW,
31c33315 972 ARG_NO_BLOCK,
61d0578b
LP
973 };
974
975 static const struct option options[] = {
976 { "help", no_argument, NULL, 'h' },
977 { "version", no_argument, NULL, ARG_VERSION },
978 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
979 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
980 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
981 { "host", required_argument, NULL, 'H' },
982 { "machine", required_argument, NULL, 'M' },
983 { "quiet", no_argument, NULL, 'q' },
984 { "profile", required_argument, NULL, 'p' },
985 { "copy", required_argument, NULL, ARG_COPY },
986 { "runtime", no_argument, NULL, ARG_RUNTIME },
987 { "no-reload", no_argument, NULL, ARG_NO_RELOAD },
988 { "cat", no_argument, NULL, ARG_CAT },
e2c1ddcc
LB
989 { "enable", no_argument, NULL, ARG_ENABLE },
990 { "now", no_argument, NULL, ARG_NOW },
31c33315 991 { "no-block", no_argument, NULL, ARG_NO_BLOCK },
61d0578b
LP
992 {}
993 };
994
995 assert(argc >= 0);
996 assert(argv);
997
998 for (;;) {
999 int c;
1000
1001 c = getopt_long(argc, argv, "hH:M:qp:", options, NULL);
1002 if (c < 0)
1003 break;
1004
1005 switch (c) {
1006
1007 case 'h':
37ec0fdd 1008 return help(0, NULL, NULL);
61d0578b
LP
1009
1010 case ARG_VERSION:
1011 return version();
1012
1013 case ARG_NO_PAGER:
0221d68a 1014 arg_pager_flags |= PAGER_DISABLE;
61d0578b
LP
1015 break;
1016
1017 case ARG_NO_LEGEND:
1018 arg_legend = false;
1019 break;
1020
1021 case ARG_NO_ASK_PASSWORD:
1022 arg_ask_password = false;
1023 break;
1024
1025 case 'H':
1026 arg_transport = BUS_TRANSPORT_REMOTE;
1027 arg_host = optarg;
1028 break;
1029
1030 case 'M':
1031 arg_transport = BUS_TRANSPORT_MACHINE;
1032 arg_host = optarg;
1033 break;
1034
1035 case 'q':
1036 arg_quiet = true;
1037 break;
1038
1039 case 'p':
b3c66c44
LP
1040 if (streq(optarg, "help"))
1041 return dump_profiles();
1042
baaa35ad
ZJS
1043 if (!filename_is_valid(optarg))
1044 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1045 "Unit profile name not valid: %s", optarg);
61d0578b 1046
61d0578b
LP
1047 arg_profile = optarg;
1048 break;
1049
1050 case ARG_COPY:
1051 if (streq(optarg, "auto"))
1052 arg_copy_mode = NULL;
1053 else if (STR_IN_SET(optarg, "copy", "symlink"))
1054 arg_copy_mode = optarg;
f524522b
LP
1055 else if (streq(optarg, "help")) {
1056 puts("auto\n"
1057 "copy\n"
1058 "symlink");
1059 return 0;
baaa35ad
ZJS
1060 } else
1061 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1062 "Failed to parse --copy= argument: %s", optarg);
61d0578b
LP
1063
1064 break;
1065
1066 case ARG_RUNTIME:
1067 arg_runtime = true;
1068 break;
1069
1070 case ARG_NO_RELOAD:
1071 arg_reload = false;
1072 break;
1073
1074 case ARG_CAT:
1075 arg_cat = true;
1076 break;
1077
e2c1ddcc
LB
1078 case ARG_ENABLE:
1079 arg_enable = true;
1080 break;
1081
1082 case ARG_NOW:
1083 arg_now = true;
1084 break;
1085
31c33315
LB
1086 case ARG_NO_BLOCK:
1087 arg_no_block = true;
1088 break;
1089
61d0578b
LP
1090 case '?':
1091 return -EINVAL;
1092
1093 default:
1094 assert_not_reached("Unhandled option");
1095 }
1096 }
1097
1098 return 1;
1099}
1100
c55a594e 1101static int run(int argc, char *argv[]) {
61d0578b
LP
1102 static const Verb verbs[] = {
1103 { "help", VERB_ANY, VERB_ANY, 0, help },
1104 { "list", VERB_ANY, 1, VERB_DEFAULT, list_images },
1105 { "attach", 2, VERB_ANY, 0, attach_image },
e2c1ddcc 1106 { "detach", 2, VERB_ANY, 0, detach_image },
61d0578b
LP
1107 { "inspect", 2, VERB_ANY, 0, inspect_image },
1108 { "is-attached", 2, 2, 0, is_image_attached },
1109 { "read-only", 2, 3, 0, read_only_image },
1110 { "remove", 2, VERB_ANY, 0, remove_image },
1111 { "set-limit", 3, 3, 0, set_limit },
1112 {}
1113 };
1114
1115 int r;
1116
41d1f469 1117 log_setup_cli();
61d0578b
LP
1118
1119 r = parse_argv(argc, argv);
1120 if (r <= 0)
c55a594e 1121 return r;
61d0578b 1122
c55a594e 1123 return dispatch_verb(argc, argv, verbs, NULL);
61d0578b 1124}
c55a594e
ZJS
1125
1126DEFINE_MAIN_FUNCTION(run);