1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "alloc-util.h"
10 #include "bus-locator.h"
11 #include "bus-unit-util.h"
12 #include "bus-wait-for-jobs.h"
13 #include "chase-symlinks.h"
15 #include "dirent-util.h"
19 #include "format-table.h"
21 #include "locale-util.h"
22 #include "main-func.h"
25 #include "parse-argument.h"
26 #include "parse-util.h"
27 #include "path-util.h"
29 #include "pretty-print.h"
30 #include "spawn-polkit-agent.h"
31 #include "string-util.h"
33 #include "terminal-util.h"
36 static PagerFlags arg_pager_flags
= 0;
37 static bool arg_legend
= true;
38 static bool arg_ask_password
= true;
39 static bool arg_quiet
= false;
40 static const char *arg_profile
= "default";
41 static const char* arg_copy_mode
= NULL
;
42 static bool arg_runtime
= false;
43 static bool arg_reload
= true;
44 static bool arg_cat
= false;
45 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
46 static const char *arg_host
= NULL
;
47 static bool arg_enable
= false;
48 static bool arg_now
= false;
49 static bool arg_no_block
= false;
50 static char **arg_extension_images
= NULL
;
52 STATIC_DESTRUCTOR_REGISTER(arg_extension_images
, strv_freep
);
54 static bool is_portable_managed(const char *unit
) {
55 return ENDSWITH_SET(unit
, ".service", ".target", ".socket", ".path", ".timer");
58 static int determine_image(const char *image
, bool permit_non_existing
, char **ret
) {
61 /* If the specified name is a valid image name, we pass it as-is to portabled, which will search for it in the
62 * usual search directories. Otherwise we presume it's a path, and will normalize it on the client's side
63 * (among other things, to make the path independent of the client's working directory) before passing it
66 if (image_name_is_valid(image
)) {
69 if (!arg_quiet
&& laccess(image
, F_OK
) >= 0)
70 log_warning("Ambiguous invocation: current working directory contains file matching non-path argument '%s', ignoring. "
71 "Prefix argument with './' to force reference to file in current working directory.", image
);
81 if (arg_transport
!= BUS_TRANSPORT_LOCAL
)
82 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
83 "Operations on images by path not supported when connecting to remote systems.");
85 r
= chase_symlinks(image
, NULL
, CHASE_TRAIL_SLASH
| (permit_non_existing
? CHASE_NONEXISTENT
: 0), ret
, NULL
);
87 return log_error_errno(r
, "Cannot normalize specified image path '%s': %m", image
);
92 static int attach_extensions_to_message(sd_bus_message
*m
, char **extensions
) {
97 if (strv_isempty(extensions
))
100 r
= sd_bus_message_open_container(m
, 'a', "s");
102 return bus_log_create_error(r
);
104 STRV_FOREACH(p
, extensions
) {
105 _cleanup_free_
char *resolved_extension_image
= NULL
;
107 r
= determine_image(*p
, false, &resolved_extension_image
);
111 r
= sd_bus_message_append(m
, "s", resolved_extension_image
);
113 return bus_log_create_error(r
);
116 r
= sd_bus_message_close_container(m
);
118 return bus_log_create_error(r
);
123 static int extract_prefix(const char *path
, char **ret
) {
124 _cleanup_free_
char *name
= NULL
;
125 const char *bn
, *underscore
;
130 underscore
= strchr(bn
, '_');
136 e
= endswith(bn
, ".raw");
143 name
= strndup(bn
, m
);
147 /* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_'
148 * which we use as delimiter for the second part of the image string, which we ignore for now. */
149 if (!in_charset(name
, DIGITS LETTERS
"-."))
152 if (!filename_is_valid(name
))
155 *ret
= TAKE_PTR(name
);
160 static int determine_matches(const char *image
, char **l
, bool allow_any
, char ***ret
) {
161 _cleanup_strv_free_
char **k
= NULL
;
164 /* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list
165 * contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly
168 if (strv_isempty(l
)) {
171 r
= extract_prefix(image
, &prefix
);
173 return log_error_errno(r
, "Failed to extract prefix of image name '%s': %m", image
);
176 log_info("(Matching unit files with prefix '%s'.)", prefix
);
178 r
= strv_consume(&k
, prefix
);
182 } else if (strv_equal(l
, STRV_MAKE("-"))) {
185 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
186 "Refusing all unit file match.");
189 log_info("(Matching all unit files.)");
197 _cleanup_free_
char *joined
= NULL
;
199 joined
= strv_join(k
, "', '");
203 log_info("(Matching unit files with prefixes '%s'.)", joined
);
212 static int acquire_bus(sd_bus
**bus
) {
220 r
= bus_connect_transport(arg_transport
, arg_host
, false, bus
);
222 return bus_log_connect_error(r
, arg_transport
);
224 (void) sd_bus_set_allow_interactive_authorization(*bus
, arg_ask_password
);
229 static int maybe_reload(sd_bus
**bus
) {
230 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
231 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
237 r
= acquire_bus(bus
);
241 r
= sd_bus_message_new_method_call(
244 "org.freedesktop.systemd1",
245 "/org/freedesktop/systemd1",
246 "org.freedesktop.systemd1.Manager",
249 return bus_log_create_error(r
);
251 /* Reloading the daemon may take long, hence set a longer timeout here */
252 r
= sd_bus_call(*bus
, m
, DAEMON_RELOAD_TIMEOUT_SEC
, &error
, NULL
);
254 return log_error_errno(r
, "Failed to reload daemon: %s", bus_error_message(&error
, r
));
259 static int get_image_metadata(sd_bus
*bus
, const char *image
, char **matches
, sd_bus_message
**reply
) {
260 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
261 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
269 method
= strv_isempty(arg_extension_images
) ? "GetImageMetadata" : "GetImageMetadataWithExtensions";
271 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, method
);
273 return bus_log_create_error(r
);
275 r
= sd_bus_message_append(m
, "s", image
);
277 return bus_log_create_error(r
);
279 r
= attach_extensions_to_message(m
, arg_extension_images
);
283 r
= sd_bus_message_append_strv(m
, matches
);
285 return bus_log_create_error(r
);
287 if (!strv_isempty(arg_extension_images
)) {
288 r
= sd_bus_message_append(m
, "t", flags
);
290 return bus_log_create_error(r
);
293 r
= sd_bus_call(bus
, m
, 0, &error
, reply
);
295 return log_error_errno(r
, "Failed to inspect image metadata: %s", bus_error_message(&error
, r
));
300 static int inspect_image(int argc
, char *argv
[], void *userdata
) {
301 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
302 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
303 _cleanup_strv_free_
char **matches
= NULL
;
304 _cleanup_free_
char *image
= NULL
;
305 bool nl
= false, header
= false;
311 r
= determine_image(argv
[1], false, &image
);
315 r
= determine_matches(argv
[1], argv
+ 2, true, &matches
);
319 r
= acquire_bus(&bus
);
323 r
= get_image_metadata(bus
, image
, matches
, &reply
);
327 r
= sd_bus_message_read(reply
, "s", &path
);
329 return bus_log_parse_error(r
);
331 r
= sd_bus_message_read_array(reply
, 'y', &data
, &sz
);
333 return bus_log_parse_error(r
);
335 pager_open(arg_pager_flags
);
338 printf("%s-- OS Release: --%s\n", ansi_highlight(), ansi_normal());
339 fwrite(data
, sz
, 1, stdout
);
343 _cleanup_free_
char *pretty_portable
= NULL
, *pretty_os
= NULL
;
344 _cleanup_fclose_
FILE *f
= NULL
;
346 f
= fmemopen_unlocked((void*) data
, sz
, "re");
348 return log_error_errno(errno
, "Failed to open /etc/os-release buffer: %m");
350 r
= parse_env_file(f
, "/etc/os-release",
351 "PORTABLE_PRETTY_NAME", &pretty_portable
,
352 "PRETTY_NAME", &pretty_os
);
354 return log_error_errno(r
, "Failed to parse /etc/os-release: %m");
356 printf("Image:\n\t%s\n"
357 "Portable Service:\n\t%s\n"
358 "Operating System:\n\t%s\n",
360 strna(pretty_portable
),
364 if (!strv_isempty(arg_extension_images
)) {
365 /* If we specified any extensions, we'll first get back exactly the paths (and
366 * extension-release content) for each one of the arguments. */
368 r
= sd_bus_message_enter_container(reply
, 'a', "{say}");
370 return bus_log_parse_error(r
);
372 for (size_t i
= 0; i
< strv_length(arg_extension_images
); ++i
) {
375 r
= sd_bus_message_enter_container(reply
, 'e', "say");
377 return bus_log_parse_error(r
);
381 r
= sd_bus_message_read(reply
, "s", &name
);
383 return bus_log_parse_error(r
);
385 r
= sd_bus_message_read_array(reply
, 'y', &data
, &sz
);
387 return bus_log_parse_error(r
);
393 printf("%s-- Extension Release: %s --%s\n", ansi_highlight(), name
, ansi_normal());
394 fwrite(data
, sz
, 1, stdout
);
398 _cleanup_free_
char *pretty_portable
= NULL
, *pretty_os
= NULL
, *sysext_level
= NULL
,
399 *id
= NULL
, *version_id
= NULL
, *sysext_scope
= NULL
, *portable_prefixes
= NULL
;
400 _cleanup_fclose_
FILE *f
= NULL
;
402 f
= fmemopen_unlocked((void*) data
, sz
, "re");
404 return log_error_errno(errno
, "Failed to open extension-release buffer: %m");
406 r
= parse_env_file(f
, name
,
408 "VERSION_ID", &version_id
,
409 "SYSEXT_SCOPE", &sysext_scope
,
410 "SYSEXT_LEVEL", &sysext_level
,
411 "PORTABLE_PRETTY_NAME", &pretty_portable
,
412 "PORTABLE_PREFIXES", &portable_prefixes
,
413 "PRETTY_NAME", &pretty_os
);
415 return log_error_errno(r
, "Failed to parse extension release from '%s': %m", name
);
417 printf("Extension:\n\t%s\n"
418 "\tExtension Scope:\n\t\t%s\n"
419 "\tExtension Compatibility Level:\n\t\t%s\n"
420 "\tPortable Service:\n\t\t%s\n"
421 "\tPortable Prefixes:\n\t\t%s\n"
422 "\tOperating System:\n\t\t%s (%s %s)\n",
426 strna(pretty_portable
),
427 strna(portable_prefixes
),
433 r
= sd_bus_message_exit_container(reply
);
435 return bus_log_parse_error(r
);
438 r
= sd_bus_message_exit_container(reply
);
440 return bus_log_parse_error(r
);
443 r
= sd_bus_message_enter_container(reply
, 'a', "{say}");
445 return bus_log_parse_error(r
);
450 r
= sd_bus_message_enter_container(reply
, 'e', "say");
452 return bus_log_parse_error(r
);
456 r
= sd_bus_message_read(reply
, "s", &name
);
458 return bus_log_parse_error(r
);
460 r
= sd_bus_message_read_array(reply
, 'y', &data
, &sz
);
462 return bus_log_parse_error(r
);
468 printf("%s-- Unit file: %s --%s\n", ansi_highlight(), name
, ansi_normal());
469 fwrite(data
, sz
, 1, stdout
);
474 fputs("Unit files:\n", stdout
);
483 r
= sd_bus_message_exit_container(reply
);
485 return bus_log_parse_error(r
);
488 r
= sd_bus_message_exit_container(reply
);
490 return bus_log_parse_error(r
);
495 static int print_changes(sd_bus_message
*m
) {
501 r
= sd_bus_message_enter_container(m
, 'a', "(sss)");
503 return bus_log_parse_error(r
);
506 const char *type
, *path
, *source
;
508 r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
);
510 return bus_log_parse_error(r
);
514 if (streq(type
, "symlink"))
515 log_info("Created symlink %s %s %s.", path
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), source
);
516 else if (streq(type
, "copy")) {
518 log_info("Copied %s.", path
);
520 log_info("Copied %s %s %s.", source
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), path
);
521 } else if (streq(type
, "unlink"))
522 log_info("Removed %s.", path
);
523 else if (streq(type
, "write"))
524 log_info("Written %s.", path
);
525 else if (streq(type
, "mkdir"))
526 log_info("Created directory %s.", path
);
528 log_error("Unexpected change: %s/%s/%s", type
, path
, source
);
531 r
= sd_bus_message_exit_container(m
);
538 static int maybe_enable_disable(sd_bus
*bus
, const char *path
, bool enable
) {
539 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
540 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
541 _cleanup_strv_free_
char **names
= NULL
;
542 UnitFileChange
*changes
= NULL
;
543 const uint64_t flags
= UNIT_FILE_PORTABLE
| (arg_runtime
? UNIT_FILE_RUNTIME
: 0);
544 size_t n_changes
= 0;
550 names
= strv_new(path
, NULL
);
554 r
= sd_bus_message_new_method_call(
557 "org.freedesktop.systemd1",
558 "/org/freedesktop/systemd1",
559 "org.freedesktop.systemd1.Manager",
560 enable
? "EnableUnitFilesWithFlags" : "DisableUnitFilesWithFlags");
562 return bus_log_create_error(r
);
564 r
= sd_bus_message_append_strv(m
, names
);
566 return bus_log_create_error(r
);
568 r
= sd_bus_message_append(m
, "t", flags
);
570 return bus_log_create_error(r
);
572 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
574 return log_error_errno(r
, "Failed to %s the portable service %s: %s",
575 enable
? "enable" : "disable", path
, bus_error_message(&error
, r
));
578 r
= sd_bus_message_skip(reply
, "b");
580 return bus_log_parse_error(r
);
583 (void) bus_deserialize_and_dump_unit_file_changes(reply
, arg_quiet
, &changes
, &n_changes
);
584 unit_file_changes_free(changes
, n_changes
);
589 static int maybe_start_stop_restart(sd_bus
*bus
, const char *path
, const char *method
, BusWaitForJobs
*wait
) {
590 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
591 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
592 char *name
= (char *)basename(path
), *job
= NULL
;
595 assert(STR_IN_SET(method
, "StartUnit", "StopUnit", "RestartUnit"));
606 "ss", name
, "replace");
608 return log_error_errno(r
, "Failed to call %s on the portable service %s: %s",
611 bus_error_message(&error
, r
));
613 r
= sd_bus_message_read(reply
, "o", &job
);
615 return bus_log_parse_error(r
);
618 log_info("Queued %s to call %s on portable service %s.", job
, method
, name
);
621 r
= bus_wait_for_jobs_add(wait
, job
);
623 return log_error_errno(r
, "Failed to watch %s job to call %s on %s: %m",
630 static int maybe_enable_start(sd_bus
*bus
, sd_bus_message
*reply
) {
631 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*wait
= NULL
;
634 if (!arg_enable
&& !arg_now
)
638 r
= bus_wait_for_jobs_new(bus
, &wait
);
640 return log_error_errno(r
, "Could not watch jobs: %m");
643 r
= sd_bus_message_rewind(reply
, true);
646 r
= sd_bus_message_enter_container(reply
, 'a', "(sss)");
648 return bus_log_parse_error(r
);
651 char *type
, *path
, *source
;
653 r
= sd_bus_message_read(reply
, "(sss)", &type
, &path
, &source
);
655 return bus_log_parse_error(r
);
659 if (STR_IN_SET(type
, "symlink", "copy") && is_portable_managed(path
)) {
660 (void) maybe_enable_disable(bus
, path
, true);
661 (void) maybe_start_stop_restart(bus
, path
, "StartUnit", wait
);
665 r
= sd_bus_message_exit_container(reply
);
670 r
= bus_wait_for_jobs(wait
, arg_quiet
, NULL
);
678 static int maybe_stop_enable_restart(sd_bus
*bus
, sd_bus_message
*reply
) {
679 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*wait
= NULL
;
682 if (!arg_enable
&& !arg_now
)
686 r
= bus_wait_for_jobs_new(bus
, &wait
);
688 return log_error_errno(r
, "Could not watch jobs: %m");
691 r
= sd_bus_message_rewind(reply
, true);
695 /* First we get a list of units that were definitely removed, not just re-attached,
696 * so we can also stop them if the user asked us to. */
697 r
= sd_bus_message_enter_container(reply
, 'a', "(sss)");
699 return bus_log_parse_error(r
);
702 char *type
, *path
, *source
;
704 r
= sd_bus_message_read(reply
, "(sss)", &type
, &path
, &source
);
706 return bus_log_parse_error(r
);
710 if (streq(type
, "unlink") && is_portable_managed(path
))
711 (void) maybe_start_stop_restart(bus
, path
, "StopUnit", wait
);
714 r
= sd_bus_message_exit_container(reply
);
718 /* Then we get a list of units that were either added or changed, so that we can
719 * enable them and/or restart them if the user asked us to. */
720 r
= sd_bus_message_enter_container(reply
, 'a', "(sss)");
722 return bus_log_parse_error(r
);
725 char *type
, *path
, *source
;
727 r
= sd_bus_message_read(reply
, "(sss)", &type
, &path
, &source
);
729 return bus_log_parse_error(r
);
733 if (STR_IN_SET(type
, "symlink", "copy") && is_portable_managed(path
)) {
734 (void) maybe_enable_disable(bus
, path
, true);
735 (void) maybe_start_stop_restart(bus
, path
, "RestartUnit", wait
);
739 r
= sd_bus_message_exit_container(reply
);
744 r
= bus_wait_for_jobs(wait
, arg_quiet
, NULL
);
752 static int maybe_stop_disable(sd_bus
*bus
, char *image
, char *argv
[]) {
753 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*wait
= NULL
;
754 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
755 _cleanup_strv_free_
char **matches
= NULL
;
758 if (!arg_enable
&& !arg_now
)
761 r
= determine_matches(argv
[1], argv
+ 2, true, &matches
);
765 r
= bus_wait_for_jobs_new(bus
, &wait
);
767 return log_error_errno(r
, "Could not watch jobs: %m");
769 r
= get_image_metadata(bus
, image
, matches
, &reply
);
773 r
= sd_bus_message_skip(reply
, "say");
775 return bus_log_parse_error(r
);
777 /* If we specified any extensions, we'll first an array of extension-release metadata. */
778 if (!strv_isempty(arg_extension_images
)) {
779 r
= sd_bus_message_skip(reply
, "a{say}");
781 return bus_log_parse_error(r
);
784 r
= sd_bus_message_enter_container(reply
, 'a', "{say}");
786 return bus_log_parse_error(r
);
791 r
= sd_bus_message_enter_container(reply
, 'e', "say");
793 return bus_log_parse_error(r
);
797 r
= sd_bus_message_read(reply
, "s", &name
);
799 return bus_log_parse_error(r
);
801 r
= sd_bus_message_skip(reply
, "ay");
803 return bus_log_parse_error(r
);
805 r
= sd_bus_message_exit_container(reply
);
807 return bus_log_parse_error(r
);
809 (void) maybe_start_stop_restart(bus
, name
, "StopUnit", wait
);
810 (void) maybe_enable_disable(bus
, name
, false);
813 r
= sd_bus_message_exit_container(reply
);
815 return bus_log_parse_error(r
);
817 /* Stopping must always block or the detach will fail if the unit is still running */
818 r
= bus_wait_for_jobs(wait
, arg_quiet
, NULL
);
825 static int attach_reattach_image(int argc
, char *argv
[], const char *method
) {
826 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
827 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
828 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
829 _cleanup_strv_free_
char **matches
= NULL
;
830 _cleanup_free_
char *image
= NULL
;
834 assert(STR_IN_SET(method
, "AttachImage", "ReattachImage", "AttachImageWithExtensions", "ReattachImageWithExtensions"));
836 r
= determine_image(argv
[1], false, &image
);
840 r
= determine_matches(argv
[1], argv
+ 2, false, &matches
);
844 r
= acquire_bus(&bus
);
848 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
850 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, method
);
852 return bus_log_create_error(r
);
854 r
= sd_bus_message_append(m
, "s", image
);
856 return bus_log_create_error(r
);
858 r
= attach_extensions_to_message(m
, arg_extension_images
);
862 r
= sd_bus_message_append_strv(m
, matches
);
864 return bus_log_create_error(r
);
866 r
= sd_bus_message_append(m
, "s", arg_profile
);
868 return bus_log_create_error(r
);
870 if (STR_IN_SET(method
, "AttachImageWithExtensions", "ReattachImageWithExtensions")) {
871 uint64_t flags
= arg_runtime
? PORTABLE_RUNTIME
: 0;
873 r
= sd_bus_message_append(m
, "st", arg_copy_mode
, flags
);
875 r
= sd_bus_message_append(m
, "bs", arg_runtime
, arg_copy_mode
);
877 return bus_log_create_error(r
);
879 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
881 return log_error_errno(r
, "%s failed: %s", method
, bus_error_message(&error
, r
));
883 (void) maybe_reload(&bus
);
885 print_changes(reply
);
887 if (STR_IN_SET(method
, "AttachImage", "AttachImageWithExtensions"))
888 (void) maybe_enable_start(bus
, reply
);
890 /* ReattachImage returns 2 lists - removed units first, and changed/added second */
891 print_changes(reply
);
892 (void) maybe_stop_enable_restart(bus
, reply
);
898 static int attach_image(int argc
, char *argv
[], void *userdata
) {
899 return attach_reattach_image(argc
, argv
, strv_isempty(arg_extension_images
) ? "AttachImage" : "AttachImageWithExtensions");
902 static int reattach_image(int argc
, char *argv
[], void *userdata
) {
903 return attach_reattach_image(argc
, argv
, strv_isempty(arg_extension_images
) ? "ReattachImage" : "ReattachImageWithExtensions");
906 static int detach_image(int argc
, char *argv
[], void *userdata
) {
907 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
908 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
909 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
910 _cleanup_free_
char *image
= NULL
;
914 r
= determine_image(argv
[1], true, &image
);
918 r
= acquire_bus(&bus
);
922 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
924 (void) maybe_stop_disable(bus
, image
, argv
);
926 method
= strv_isempty(arg_extension_images
) ? "DetachImage" : "DetachImageWithExtensions";
928 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, method
);
930 return bus_log_create_error(r
);
932 r
= sd_bus_message_append(m
, "s", image
);
934 return bus_log_create_error(r
);
936 r
= attach_extensions_to_message(m
, arg_extension_images
);
940 if (strv_isempty(arg_extension_images
))
941 r
= sd_bus_message_append(m
, "b", arg_runtime
);
943 uint64_t flags
= arg_runtime
? PORTABLE_RUNTIME
: 0;
945 r
= sd_bus_message_append(m
, "t", flags
);
948 return bus_log_create_error(r
);
950 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
952 return log_error_errno(r
, "%s failed: %s", method
, bus_error_message(&error
, r
));
954 (void) maybe_reload(&bus
);
956 print_changes(reply
);
960 static int list_images(int argc
, char *argv
[], void *userdata
) {
961 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
962 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
963 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
964 _cleanup_(table_unrefp
) Table
*table
= NULL
;
967 r
= acquire_bus(&bus
);
971 r
= bus_call_method(bus
, bus_portable_mgr
, "ListImages", &error
, &reply
, NULL
);
973 return log_error_errno(r
, "Failed to list images: %s", bus_error_message(&error
, r
));
975 table
= table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
979 r
= sd_bus_message_enter_container(reply
, 'a', "(ssbtttso)");
981 return bus_log_parse_error(r
);
984 const char *name
, *type
, *state
;
985 uint64_t crtime
, mtime
, usage
;
988 r
= sd_bus_message_read(reply
, "(ssbtttso)", &name
, &type
, &ro_int
, &crtime
, &mtime
, &usage
, &state
, NULL
);
990 return bus_log_parse_error(r
);
994 r
= table_add_many(table
,
997 TABLE_BOOLEAN
, ro_int
,
998 TABLE_SET_COLOR
, ro_int
? ansi_highlight_red() : NULL
,
999 TABLE_TIMESTAMP
, crtime
,
1000 TABLE_TIMESTAMP
, mtime
,
1002 TABLE_STRING
, state
,
1003 TABLE_SET_COLOR
, !streq(state
, "detached") ? ansi_highlight_green() : NULL
);
1005 return table_log_add_error(r
);
1008 r
= sd_bus_message_exit_container(reply
);
1010 return bus_log_parse_error(r
);
1012 if (table_get_rows(table
) > 1) {
1013 r
= table_set_sort(table
, (size_t) 0);
1015 return table_log_sort_error(r
);
1017 table_set_header(table
, arg_legend
);
1019 r
= table_print(table
, NULL
);
1021 return table_log_print_error(r
);
1025 if (table_get_rows(table
) > 1)
1026 printf("\n%zu images listed.\n", table_get_rows(table
) - 1);
1028 printf("No images.\n");
1034 static int remove_image(int argc
, char *argv
[], void *userdata
) {
1035 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1038 r
= acquire_bus(&bus
);
1042 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
1044 for (i
= 1; i
< argc
; i
++) {
1045 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1046 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
1048 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, "RemoveImage");
1050 return bus_log_create_error(r
);
1052 r
= sd_bus_message_append(m
, "s", argv
[i
]);
1054 return bus_log_create_error(r
);
1056 /* This is a slow operation, hence turn off any method call timeouts */
1057 r
= sd_bus_call(bus
, m
, USEC_INFINITY
, &error
, NULL
);
1059 return log_error_errno(r
, "Could not remove image: %s", bus_error_message(&error
, r
));
1065 static int read_only_image(int argc
, char *argv
[], void *userdata
) {
1066 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1067 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1071 b
= parse_boolean(argv
[2]);
1073 return log_error_errno(b
, "Failed to parse boolean argument: %s", argv
[2]);
1076 r
= acquire_bus(&bus
);
1080 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
1082 r
= bus_call_method(bus
, bus_portable_mgr
, "MarkImageReadOnly", &error
, NULL
, "sb", argv
[1], b
);
1084 return log_error_errno(r
, "Could not mark image read-only: %s", bus_error_message(&error
, r
));
1089 static int set_limit(int argc
, char *argv
[], void *userdata
) {
1090 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1091 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1095 r
= acquire_bus(&bus
);
1099 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
1101 if (STR_IN_SET(argv
[argc
-1], "-", "none", "infinity"))
1104 r
= parse_size(argv
[argc
-1], 1024, &limit
);
1106 return log_error_errno(r
, "Failed to parse size: %s", argv
[argc
-1]);
1110 /* With two arguments changes the quota limit of the specified image */
1111 r
= bus_call_method(bus
, bus_portable_mgr
, "SetImageLimit", &error
, NULL
, "st", argv
[1], limit
);
1113 /* With one argument changes the pool quota limit */
1114 r
= bus_call_method(bus
, bus_portable_mgr
, "SetPoolLimit", &error
, NULL
, "t", limit
);
1117 return log_error_errno(r
, "Could not set limit: %s", bus_error_message(&error
, r
));
1122 static int is_image_attached(int argc
, char *argv
[], void *userdata
) {
1123 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
1124 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1125 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1126 _cleanup_free_
char *image
= NULL
;
1127 const char *state
, *method
;
1130 r
= determine_image(argv
[1], true, &image
);
1134 r
= acquire_bus(&bus
);
1138 method
= strv_isempty(arg_extension_images
) ? "GetImageState" : "GetImageStateWithExtensions";
1140 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, method
);
1142 return bus_log_create_error(r
);
1144 r
= sd_bus_message_append(m
, "s", image
);
1146 return bus_log_create_error(r
);
1148 r
= attach_extensions_to_message(m
, arg_extension_images
);
1152 if (!strv_isempty(arg_extension_images
)) {
1153 r
= sd_bus_message_append(m
, "t", 0);
1155 return bus_log_create_error(r
);
1158 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
1160 return log_error_errno(r
, "%s failed: %s", method
, bus_error_message(&error
, r
));
1162 r
= sd_bus_message_read(reply
, "s", &state
);
1169 return streq(state
, "detached");
1172 static int dump_profiles(void) {
1173 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1174 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1175 _cleanup_strv_free_
char **l
= NULL
;
1178 r
= acquire_bus(&bus
);
1182 r
= bus_get_property_strv(bus
, bus_portable_mgr
, "Profiles", &error
, &l
);
1184 return log_error_errno(r
, "Failed to acquire list of profiles: %s", bus_error_message(&error
, r
));
1187 log_info("Available unit profiles:");
1189 STRV_FOREACH(i
, l
) {
1191 fputc('\n', stdout
);
1197 static int help(int argc
, char *argv
[], void *userdata
) {
1198 _cleanup_free_
char *link
= NULL
;
1201 pager_open(arg_pager_flags
);
1203 r
= terminal_urlify_man("portablectl", "1", &link
);
1207 printf("%s [OPTIONS...] COMMAND ...\n\n"
1208 "%sAttach or detach portable services from the local system.%s\n"
1210 " list List available portable service images\n"
1211 " attach NAME|PATH [PREFIX...]\n"
1212 " Attach the specified portable service image\n"
1213 " detach NAME|PATH [PREFIX...]\n"
1214 " Detach the specified portable service image\n"
1215 " reattach NAME|PATH [PREFIX...]\n"
1216 " Reattach the specified portable service image\n"
1217 " inspect NAME|PATH [PREFIX...]\n"
1218 " Show details of specified portable service image\n"
1219 " is-attached NAME|PATH Query if portable service image is attached\n"
1220 " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
1221 " remove NAME|PATH... Remove a portable service image\n"
1222 " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
1224 " -h --help Show this help\n"
1225 " --version Show package version\n"
1226 " --no-pager Do not pipe output into a pager\n"
1227 " --no-legend Do not show the headers and footers\n"
1228 " --no-ask-password Do not ask for system passwords\n"
1229 " -H --host=[USER@]HOST Operate on remote host\n"
1230 " -M --machine=CONTAINER Operate on local container\n"
1231 " -q --quiet Suppress informational messages\n"
1232 " -p --profile=PROFILE Pick security profile for portable service\n"
1233 " --copy=copy|auto|symlink Prefer copying or symlinks if possible\n"
1234 " --runtime Attach portable service until next reboot only\n"
1235 " --no-reload Don't reload the system and service manager\n"
1236 " --cat When inspecting include unit and os-release file\n"
1238 " --enable Immediately enable/disable the portable service\n"
1239 " after attach/detach\n"
1240 " --now Immediately start/stop the portable service after\n"
1241 " attach/before detach\n"
1242 " --no-block Don't block waiting for attach --now to complete\n"
1243 " --extension=PATH Extend the image with an overlay\n"
1244 "\nSee the %s for details.\n",
1245 program_invocation_short_name
,
1253 static int parse_argv(int argc
, char *argv
[]) {
1257 ARG_VERSION
= 0x100,
1260 ARG_NO_ASK_PASSWORD
,
1271 static const struct option options
[] = {
1272 { "help", no_argument
, NULL
, 'h' },
1273 { "version", no_argument
, NULL
, ARG_VERSION
},
1274 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1275 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
1276 { "no-ask-password", no_argument
, NULL
, ARG_NO_ASK_PASSWORD
},
1277 { "host", required_argument
, NULL
, 'H' },
1278 { "machine", required_argument
, NULL
, 'M' },
1279 { "quiet", no_argument
, NULL
, 'q' },
1280 { "profile", required_argument
, NULL
, 'p' },
1281 { "copy", required_argument
, NULL
, ARG_COPY
},
1282 { "runtime", no_argument
, NULL
, ARG_RUNTIME
},
1283 { "no-reload", no_argument
, NULL
, ARG_NO_RELOAD
},
1284 { "cat", no_argument
, NULL
, ARG_CAT
},
1285 { "enable", no_argument
, NULL
, ARG_ENABLE
},
1286 { "now", no_argument
, NULL
, ARG_NOW
},
1287 { "no-block", no_argument
, NULL
, ARG_NO_BLOCK
},
1288 { "extension", required_argument
, NULL
, ARG_EXTENSION
},
1298 c
= getopt_long(argc
, argv
, "hH:M:qp:", options
, NULL
);
1305 return help(0, NULL
, NULL
);
1311 arg_pager_flags
|= PAGER_DISABLE
;
1318 case ARG_NO_ASK_PASSWORD
:
1319 arg_ask_password
= false;
1323 arg_transport
= BUS_TRANSPORT_REMOTE
;
1328 arg_transport
= BUS_TRANSPORT_MACHINE
;
1337 if (streq(optarg
, "help"))
1338 return dump_profiles();
1340 if (!filename_is_valid(optarg
))
1341 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1342 "Unit profile name not valid: %s", optarg
);
1344 arg_profile
= optarg
;
1348 if (streq(optarg
, "auto"))
1349 arg_copy_mode
= NULL
;
1350 else if (STR_IN_SET(optarg
, "copy", "symlink"))
1351 arg_copy_mode
= optarg
;
1352 else if (streq(optarg
, "help")) {
1358 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1359 "Failed to parse --copy= argument: %s", optarg
);
1384 arg_no_block
= true;
1388 r
= strv_extend(&arg_extension_images
, optarg
);
1397 assert_not_reached();
1404 static int run(int argc
, char *argv
[]) {
1405 static const Verb verbs
[] = {
1406 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
1407 { "list", VERB_ANY
, 1, VERB_DEFAULT
, list_images
},
1408 { "attach", 2, VERB_ANY
, 0, attach_image
},
1409 { "detach", 2, VERB_ANY
, 0, detach_image
},
1410 { "inspect", 2, VERB_ANY
, 0, inspect_image
},
1411 { "is-attached", 2, 2, 0, is_image_attached
},
1412 { "read-only", 2, 3, 0, read_only_image
},
1413 { "remove", 2, VERB_ANY
, 0, remove_image
},
1414 { "set-limit", 3, 3, 0, set_limit
},
1415 { "reattach", 2, VERB_ANY
, 0, reattach_image
},
1423 r
= parse_argv(argc
, argv
);
1427 return dispatch_verb(argc
, argv
, verbs
, NULL
);
1430 DEFINE_MAIN_FUNCTION(run
);