1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "alloc-util.h"
10 #include "bus-locator.h"
11 #include "bus-unit-util.h"
13 #include "bus-wait-for-jobs.h"
18 #include "format-table.h"
21 #include "main-func.h"
24 #include "parse-argument.h"
25 #include "parse-util.h"
26 #include "path-util.h"
27 #include "polkit-agent.h"
29 #include "pretty-print.h"
30 #include "string-util.h"
34 static PagerFlags arg_pager_flags
= 0;
35 static bool arg_legend
= true;
36 static bool arg_ask_password
= true;
37 static bool arg_quiet
= false;
38 static const char *arg_profile
= "default";
39 static const char *arg_copy_mode
= NULL
;
40 static bool arg_runtime
= false;
41 static bool arg_reload
= true;
42 static bool arg_cat
= false;
43 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
44 static const char *arg_host
= NULL
;
45 static bool arg_enable
= false;
46 static bool arg_now
= false;
47 static bool arg_no_block
= false;
48 static char **arg_extension_images
= NULL
;
49 static bool arg_force
= false;
50 static bool arg_clean
= false;
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
&& access_nofollow(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(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
, const char *method
, char **extensions
) {
98 /* The new methods also have flags parameters that are independent of the extensions */
99 if (strv_isempty(extensions
) && !endswith(method
, "WithExtensions"))
102 r
= sd_bus_message_open_container(m
, 'a', "s");
104 return bus_log_create_error(r
);
106 STRV_FOREACH(p
, extensions
) {
107 _cleanup_free_
char *resolved_extension_image
= NULL
;
111 startswith_strv(method
, STRV_MAKE("Get", "Detach")),
112 &resolved_extension_image
);
116 r
= sd_bus_message_append(m
, "s", resolved_extension_image
);
118 return bus_log_create_error(r
);
121 r
= sd_bus_message_close_container(m
);
123 return bus_log_create_error(r
);
128 static int extract_prefix(const char *path
, char **ret
) {
129 _cleanup_free_
char *name
= NULL
, *bn
= NULL
;
130 const char *underscore
;
134 r
= path_extract_filename(path
, &bn
);
138 underscore
= strchr(bn
, '_');
144 e
= endswith(bn
, ".raw");
151 name
= strndup(bn
, m
);
155 /* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_'
156 * which we use as delimiter for the second part of the image string, which we ignore for now. */
157 if (!in_charset(name
, DIGITS LETTERS
"-."))
160 if (!filename_is_valid(name
))
163 *ret
= TAKE_PTR(name
);
167 static int determine_matches(const char *image
, char **l
, bool allow_any
, char ***ret
) {
168 _cleanup_strv_free_
char **k
= NULL
;
171 /* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list
172 * contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly
175 if (strv_isempty(l
)) {
178 r
= extract_prefix(image
, &prefix
);
180 return log_error_errno(r
, "Failed to extract prefix of image name '%s': %m", image
);
183 log_info("(Matching unit files with prefix '%s'.)", prefix
);
185 r
= strv_consume(&k
, prefix
);
189 } else if (strv_equal(l
, STRV_MAKE("-"))) {
192 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
193 "Refusing all unit file match.");
196 log_info("(Matching all unit files.)");
204 _cleanup_free_
char *joined
= NULL
;
206 joined
= strv_join(k
, "', '");
210 log_info("(Matching unit files with prefixes '%s'.)", joined
);
219 static int acquire_bus(sd_bus
**bus
) {
227 r
= bus_connect_transport(arg_transport
, arg_host
, RUNTIME_SCOPE_SYSTEM
, bus
);
229 return bus_log_connect_error(r
, arg_transport
, RUNTIME_SCOPE_SYSTEM
);
231 (void) sd_bus_set_allow_interactive_authorization(*bus
, arg_ask_password
);
236 static int maybe_reload(sd_bus
**bus
) {
242 r
= acquire_bus(bus
);
246 return bus_service_manager_reload(*bus
);
249 static int get_image_metadata(sd_bus
*bus
, const char *image
, char **matches
, sd_bus_message
**reply
) {
250 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
251 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
252 uint64_t flags
= arg_force
? PORTABLE_FORCE_EXTENSION
: 0;
259 method
= strv_isempty(arg_extension_images
) && !arg_force
? "GetImageMetadata" : "GetImageMetadataWithExtensions";
261 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, method
);
263 return bus_log_create_error(r
);
265 r
= sd_bus_message_append(m
, "s", image
);
267 return bus_log_create_error(r
);
269 r
= attach_extensions_to_message(m
, method
, arg_extension_images
);
273 r
= sd_bus_message_append_strv(m
, matches
);
275 return bus_log_create_error(r
);
277 if (streq(method
, "GetImageMetadataWithExtensions")) {
278 r
= sd_bus_message_append(m
, "t", flags
);
280 return bus_log_create_error(r
);
283 r
= sd_bus_call(bus
, m
, 0, &error
, reply
);
285 return log_error_errno(r
, "Failed to inspect image metadata: %s", bus_error_message(&error
, r
));
290 static int inspect_image(int argc
, char *argv
[], void *userdata
) {
291 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
292 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
293 _cleanup_strv_free_
char **matches
= NULL
;
294 _cleanup_free_
char *image
= NULL
;
295 bool nl
= false, header
= false;
301 r
= determine_image(argv
[1], false, &image
);
305 r
= determine_matches(argv
[1], argv
+ 2, true, &matches
);
309 r
= acquire_bus(&bus
);
313 r
= get_image_metadata(bus
, image
, matches
, &reply
);
317 r
= sd_bus_message_read(reply
, "s", &path
);
319 return bus_log_parse_error(r
);
321 r
= sd_bus_message_read_array(reply
, 'y', &data
, &sz
);
323 return bus_log_parse_error(r
);
325 pager_open(arg_pager_flags
);
328 printf("%s-- OS Release: --%s\n", ansi_highlight(), ansi_normal());
329 fwrite(data
, sz
, 1, stdout
);
333 _cleanup_free_
char *pretty_portable
= NULL
, *pretty_os
= NULL
;
334 _cleanup_fclose_
FILE *f
= NULL
;
336 f
= fmemopen_unlocked((void*) data
, sz
, "r");
338 return log_error_errno(errno
, "Failed to open /etc/os-release buffer: %m");
340 r
= parse_env_file(f
, "/etc/os-release",
341 "PORTABLE_PRETTY_NAME", &pretty_portable
,
342 "PRETTY_NAME", &pretty_os
);
344 return log_error_errno(r
, "Failed to parse /etc/os-release: %m");
346 printf("Image:\n\t%s\n"
347 "Portable Service:\n\t%s\n"
348 "Operating System:\n\t%s\n",
350 strna(pretty_portable
),
354 if (!strv_isempty(arg_extension_images
)) {
355 /* If we specified any extensions, we'll first get back exactly the paths (and
356 * extension-release content) for each one of the arguments. */
358 r
= sd_bus_message_enter_container(reply
, 'a', "{say}");
360 return bus_log_parse_error(r
);
362 for (size_t i
= 0; i
< strv_length(arg_extension_images
); ++i
) {
365 r
= sd_bus_message_enter_container(reply
, 'e', "say");
367 return bus_log_parse_error(r
);
371 r
= sd_bus_message_read(reply
, "s", &name
);
373 return bus_log_parse_error(r
);
375 r
= sd_bus_message_read_array(reply
, 'y', &data
, &sz
);
377 return bus_log_parse_error(r
);
383 printf("%s-- Extension Release: %s --%s\n", ansi_highlight(), name
, ansi_normal());
384 fwrite(data
, sz
, 1, stdout
);
388 _cleanup_free_
char *pretty_portable
= NULL
, *sysext_pretty_os
= NULL
,
389 *sysext_level
= NULL
, *sysext_id
= NULL
,
390 *sysext_version_id
= NULL
, *sysext_scope
= NULL
,
391 *portable_prefixes
= NULL
, *id
= NULL
, *version_id
= NULL
,
392 *sysext_image_id
= NULL
, *sysext_image_version
= NULL
,
393 *sysext_build_id
= NULL
, *confext_pretty_os
= NULL
,
394 *confext_level
= NULL
, *confext_id
= NULL
,
395 *confext_version_id
= NULL
, *confext_scope
= NULL
,
396 *confext_image_id
= NULL
, *confext_image_version
= NULL
,
397 *confext_build_id
= NULL
;
398 _cleanup_fclose_
FILE *f
= NULL
;
400 f
= fmemopen_unlocked((void*) data
, sz
, "r");
402 return log_error_errno(errno
, "Failed to open extension-release buffer: %m");
404 r
= parse_env_file(f
, name
,
405 "SYSEXT_ID", &sysext_id
,
406 "SYSEXT_VERSION_ID", &sysext_version_id
,
407 "SYSEXT_BUILD_ID", &sysext_build_id
,
408 "SYSEXT_IMAGE_ID", &sysext_image_id
,
409 "SYSEXT_IMAGE_VERSION", &sysext_image_version
,
410 "SYSEXT_SCOPE", &sysext_scope
,
411 "SYSEXT_LEVEL", &sysext_level
,
412 "SYSEXT_PRETTY_NAME", &sysext_pretty_os
,
413 "CONFEXT_ID", &confext_id
,
414 "CONFEXT_VERSION_ID", &confext_version_id
,
415 "CONFEXT_BUILD_ID", &confext_build_id
,
416 "CONFEXT_IMAGE_ID", &confext_image_id
,
417 "CONFEXT_IMAGE_VERSION", &confext_image_version
,
418 "CONFEXT_SCOPE", &confext_scope
,
419 "CONFEXT_LEVEL", &confext_level
,
420 "CONFEXT_PRETTY_NAME", &confext_pretty_os
,
422 "VERSION_ID", &version_id
,
423 "PORTABLE_PRETTY_NAME", &pretty_portable
,
424 "PORTABLE_PREFIXES", &portable_prefixes
);
426 return log_error_errno(r
, "Failed to parse extension release from '%s': %m", name
);
428 printf("Extension:\n\t%s\n"
429 "\tExtension Scope:\n\t\t%s\n"
430 "\tExtension Compatibility Level:\n\t\t%s\n"
431 "\tExtension Compatibility OS:\n\t\t%s\n"
432 "\tExtension Compatibility OS Version:\n\t\t%s\n"
433 "\tPortable Service:\n\t\t%s\n"
434 "\tPortable Prefixes:\n\t\t%s\n"
435 "\tExtension Image:\n\t\t%s%s%s %s%s%s\n",
437 strna(sysext_scope
?: confext_scope
),
438 strna(sysext_level
?: confext_level
),
441 strna(pretty_portable
),
442 strna(portable_prefixes
),
443 strempty(sysext_pretty_os
?: confext_pretty_os
),
444 (sysext_pretty_os
?: confext_pretty_os
) ? " (" : "ID: ",
445 strna(sysext_id
?: sysext_image_id
?: confext_id
?: confext_image_id
),
446 (sysext_pretty_os
?: confext_pretty_os
) ? "" : "Version: ",
447 strna(sysext_version_id
?: sysext_image_version
?: sysext_build_id
?: confext_version_id
?: confext_image_version
?: confext_build_id
),
448 (sysext_pretty_os
?: confext_pretty_os
) ? ")" : "");
451 r
= sd_bus_message_exit_container(reply
);
453 return bus_log_parse_error(r
);
456 r
= sd_bus_message_exit_container(reply
);
458 return bus_log_parse_error(r
);
461 r
= sd_bus_message_enter_container(reply
, 'a', "{say}");
463 return bus_log_parse_error(r
);
468 r
= sd_bus_message_enter_container(reply
, 'e', "say");
470 return bus_log_parse_error(r
);
474 r
= sd_bus_message_read(reply
, "s", &name
);
476 return bus_log_parse_error(r
);
478 r
= sd_bus_message_read_array(reply
, 'y', &data
, &sz
);
480 return bus_log_parse_error(r
);
486 printf("%s-- Unit file: %s --%s\n", ansi_highlight(), name
, ansi_normal());
487 fwrite(data
, sz
, 1, stdout
);
492 fputs("Unit files:\n", stdout
);
501 r
= sd_bus_message_exit_container(reply
);
503 return bus_log_parse_error(r
);
506 r
= sd_bus_message_exit_container(reply
);
508 return bus_log_parse_error(r
);
513 static int print_changes(sd_bus_message
*m
) {
519 r
= sd_bus_message_enter_container(m
, 'a', "(sss)");
521 return bus_log_parse_error(r
);
524 const char *type
, *path
, *source
;
526 r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
);
528 return bus_log_parse_error(r
);
532 if (streq(type
, "symlink"))
533 log_info("Created symlink %s %s %s.", path
, glyph(GLYPH_ARROW_RIGHT
), source
);
534 else if (streq(type
, "copy")) {
536 log_info("Copied %s.", path
);
538 log_info("Copied %s %s %s.", source
, glyph(GLYPH_ARROW_RIGHT
), path
);
539 } else if (streq(type
, "unlink"))
540 log_info("Removed %s.", path
);
541 else if (streq(type
, "write"))
542 log_info("Written %s.", path
);
543 else if (streq(type
, "mkdir"))
544 log_info("Created directory %s.", path
);
546 log_error("Unexpected change: %s/%s/%s", type
, path
, source
);
549 r
= sd_bus_message_exit_container(m
);
556 static int maybe_enable_disable(sd_bus
*bus
, const char *path
, bool enable
) {
557 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
558 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
559 _cleanup_strv_free_
char **names
= NULL
;
560 const uint64_t flags
= UNIT_FILE_PORTABLE
| (arg_runtime
? UNIT_FILE_RUNTIME
: 0);
566 names
= strv_new(path
, NULL
);
570 r
= bus_message_new_method_call(
574 enable
? "EnableUnitFilesWithFlags" : "DisableUnitFilesWithFlags");
576 return bus_log_create_error(r
);
578 r
= sd_bus_message_append_strv(m
, names
);
580 return bus_log_create_error(r
);
582 r
= sd_bus_message_append(m
, "t", flags
);
584 return bus_log_create_error(r
);
586 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
588 return log_error_errno(r
, "Failed to %s the portable service %s: %s",
589 enable
? "enable" : "disable", path
, bus_error_message(&error
, r
));
592 r
= sd_bus_message_skip(reply
, "b");
594 return bus_log_parse_error(r
);
597 (void) bus_deserialize_and_dump_unit_file_changes(reply
, arg_quiet
);
602 static int maybe_start_stop_restart(sd_bus
*bus
, const char *path
, const char *method
, BusWaitForJobs
*wait
) {
603 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
604 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
605 _cleanup_free_
char *name
= NULL
;
606 const char *job
= NULL
;
609 assert(STR_IN_SET(method
, "StartUnit", "StopUnit", "RestartUnit"));
614 r
= path_extract_filename(path
, &name
);
616 return log_error_errno(r
, "Failed to extract file name from '%s': %m", path
);
624 "ss", name
, "replace");
626 return log_error_errno(r
, "Failed to call %s on the portable service %s: %s",
629 bus_error_message(&error
, r
));
631 r
= sd_bus_message_read(reply
, "o", &job
);
633 return bus_log_parse_error(r
);
636 log_info("Queued %s to call %s on portable service %s.", job
, method
, name
);
639 r
= bus_wait_for_jobs_add(wait
, job
);
641 return log_error_errno(r
, "Failed to watch %s job to call %s on %s: %m",
648 static int maybe_enable_start(sd_bus
*bus
, sd_bus_message
*reply
) {
649 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*wait
= NULL
;
652 if (!arg_enable
&& !arg_now
)
656 r
= bus_wait_for_jobs_new(bus
, &wait
);
658 return log_error_errno(r
, "Could not watch jobs: %m");
661 r
= sd_bus_message_rewind(reply
, true);
664 r
= sd_bus_message_enter_container(reply
, 'a', "(sss)");
666 return bus_log_parse_error(r
);
669 char *type
, *path
, *source
;
671 r
= sd_bus_message_read(reply
, "(sss)", &type
, &path
, &source
);
673 return bus_log_parse_error(r
);
677 if (STR_IN_SET(type
, "symlink", "copy") && is_portable_managed(path
)) {
678 (void) maybe_enable_disable(bus
, path
, true);
679 (void) maybe_start_stop_restart(bus
, path
, "StartUnit", wait
);
683 r
= sd_bus_message_exit_container(reply
);
688 r
= bus_wait_for_jobs(wait
, arg_quiet
, NULL
);
696 static int maybe_stop_enable_restart(sd_bus
*bus
, sd_bus_message
*reply
) {
697 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*wait
= NULL
;
700 if (!arg_enable
&& !arg_now
)
704 r
= bus_wait_for_jobs_new(bus
, &wait
);
706 return log_error_errno(r
, "Could not watch jobs: %m");
709 r
= sd_bus_message_rewind(reply
, true);
713 /* First we get a list of units that were definitely removed, not just re-attached,
714 * so we can also stop them if the user asked us to. */
715 r
= sd_bus_message_enter_container(reply
, 'a', "(sss)");
717 return bus_log_parse_error(r
);
720 char *type
, *path
, *source
;
722 r
= sd_bus_message_read(reply
, "(sss)", &type
, &path
, &source
);
724 return bus_log_parse_error(r
);
728 if (streq(type
, "unlink") && is_portable_managed(path
))
729 (void) maybe_start_stop_restart(bus
, path
, "StopUnit", wait
);
732 r
= sd_bus_message_exit_container(reply
);
736 /* Then we get a list of units that were either added or changed, so that we can
737 * enable them and/or restart them if the user asked us to. */
738 r
= sd_bus_message_enter_container(reply
, 'a', "(sss)");
740 return bus_log_parse_error(r
);
743 char *type
, *path
, *source
;
745 r
= sd_bus_message_read(reply
, "(sss)", &type
, &path
, &source
);
747 return bus_log_parse_error(r
);
751 if (STR_IN_SET(type
, "symlink", "copy") && is_portable_managed(path
)) {
752 (void) maybe_enable_disable(bus
, path
, true);
753 (void) maybe_start_stop_restart(bus
, path
, "RestartUnit", wait
);
757 r
= sd_bus_message_exit_container(reply
);
762 r
= bus_wait_for_jobs(wait
, arg_quiet
, NULL
);
770 static int maybe_clean_units(sd_bus
*bus
, char **units
) {
778 STRV_FOREACH(name
, units
) {
779 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
780 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
782 r
= bus_message_new_method_call(bus
, &m
, bus_systemd_mgr
, "CleanUnit");
784 return bus_log_create_error(r
);
786 r
= sd_bus_message_append(m
, "s", *name
);
788 return bus_log_create_error(r
);
790 r
= sd_bus_message_append_strv(m
, STRV_MAKE("all", "fdstore"));
792 return bus_log_create_error(r
);
794 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
796 return log_error_errno(
798 "Failed to call CleanUnit on portable service %s: %s",
800 bus_error_message(&error
, r
));
806 static int maybe_stop_disable_clean(sd_bus
*bus
, char *image
, char *argv
[]) {
807 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*wait
= NULL
;
808 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
809 _cleanup_strv_free_
char **matches
= NULL
, **units
= NULL
;
812 if (!arg_enable
&& !arg_now
&& !arg_clean
)
815 r
= determine_matches(argv
[1], argv
+ 2, true, &matches
);
819 r
= bus_wait_for_jobs_new(bus
, &wait
);
821 return log_error_errno(r
, "Could not watch jobs: %m");
823 r
= get_image_metadata(bus
, image
, matches
, &reply
);
827 r
= sd_bus_message_skip(reply
, "say");
829 return bus_log_parse_error(r
);
831 /* If we specified any extensions or --force (which makes the request go through the new
832 * WithExtensions calls), we'll first get an array of extension-release metadata. */
833 if (!strv_isempty(arg_extension_images
) || arg_force
) {
834 r
= sd_bus_message_skip(reply
, "a{say}");
836 return bus_log_parse_error(r
);
839 r
= sd_bus_message_enter_container(reply
, 'a', "{say}");
841 return bus_log_parse_error(r
);
846 r
= sd_bus_message_enter_container(reply
, 'e', "say");
848 return bus_log_parse_error(r
);
852 r
= sd_bus_message_read(reply
, "s", &name
);
854 return bus_log_parse_error(r
);
856 r
= sd_bus_message_skip(reply
, "ay");
858 return bus_log_parse_error(r
);
860 r
= sd_bus_message_exit_container(reply
);
862 return bus_log_parse_error(r
);
864 (void) maybe_start_stop_restart(bus
, name
, "StopUnit", wait
);
865 (void) maybe_enable_disable(bus
, name
, false);
867 r
= strv_extend(&units
, name
);
872 r
= sd_bus_message_exit_container(reply
);
874 return bus_log_parse_error(r
);
876 /* Stopping must always block or the detach will fail if the unit is still running */
877 r
= bus_wait_for_jobs(wait
, arg_quiet
, NULL
);
881 /* Need to ensure all units are stopped before calling CleanUnit, as files might be in use. */
882 (void) maybe_clean_units(bus
, units
);
887 static int attach_reattach_image(int argc
, char *argv
[], const char *method
) {
888 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
889 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
890 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
891 _cleanup_strv_free_
char **matches
= NULL
;
892 _cleanup_free_
char *image
= NULL
;
896 assert(STR_IN_SET(method
, "AttachImage", "ReattachImage", "AttachImageWithExtensions", "ReattachImageWithExtensions"));
898 r
= determine_image(argv
[1], false, &image
);
902 r
= determine_matches(argv
[1], argv
+ 2, false, &matches
);
906 r
= acquire_bus(&bus
);
910 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
912 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, method
);
914 return bus_log_create_error(r
);
916 r
= sd_bus_message_append(m
, "s", image
);
918 return bus_log_create_error(r
);
920 r
= attach_extensions_to_message(m
, method
, arg_extension_images
);
924 r
= sd_bus_message_append_strv(m
, matches
);
926 return bus_log_create_error(r
);
928 r
= sd_bus_message_append(m
, "s", arg_profile
);
930 return bus_log_create_error(r
);
932 if (STR_IN_SET(method
, "AttachImageWithExtensions", "ReattachImageWithExtensions")) {
933 uint64_t flags
= (arg_runtime
? PORTABLE_RUNTIME
: 0) | (arg_force
? PORTABLE_FORCE_ATTACH
| PORTABLE_FORCE_EXTENSION
: 0);
935 r
= sd_bus_message_append(m
, "st", arg_copy_mode
, flags
);
937 r
= sd_bus_message_append(m
, "bs", arg_runtime
, arg_copy_mode
);
939 return bus_log_create_error(r
);
941 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
943 return log_error_errno(r
, "%s failed: %s", method
, bus_error_message(&error
, r
));
945 (void) maybe_reload(&bus
);
947 print_changes(reply
);
949 if (STR_IN_SET(method
, "AttachImage", "AttachImageWithExtensions"))
950 (void) maybe_enable_start(bus
, reply
);
952 /* ReattachImage returns 2 lists - removed units first, and changed/added second */
953 print_changes(reply
);
954 (void) maybe_stop_enable_restart(bus
, reply
);
960 static int attach_image(int argc
, char *argv
[], void *userdata
) {
961 return attach_reattach_image(argc
, argv
, strv_isempty(arg_extension_images
) && !arg_force
? "AttachImage" : "AttachImageWithExtensions");
964 static int reattach_image(int argc
, char *argv
[], void *userdata
) {
965 return attach_reattach_image(argc
, argv
, strv_isempty(arg_extension_images
) && !arg_force
? "ReattachImage" : "ReattachImageWithExtensions");
968 static int detach_image(int argc
, char *argv
[], void *userdata
) {
969 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
970 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
971 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
972 _cleanup_free_
char *image
= NULL
;
976 r
= determine_image(argv
[1], true, &image
);
980 r
= acquire_bus(&bus
);
984 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
986 (void) maybe_stop_disable_clean(bus
, image
, argv
);
988 method
= strv_isempty(arg_extension_images
) && !arg_force
? "DetachImage" : "DetachImageWithExtensions";
990 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, method
);
992 return bus_log_create_error(r
);
994 r
= sd_bus_message_append(m
, "s", image
);
996 return bus_log_create_error(r
);
998 r
= attach_extensions_to_message(m
, method
, arg_extension_images
);
1002 if (streq(method
, "DetachImage"))
1003 r
= sd_bus_message_append(m
, "b", arg_runtime
);
1005 uint64_t flags
= (arg_runtime
? PORTABLE_RUNTIME
: 0) | (arg_force
? PORTABLE_FORCE_ATTACH
| PORTABLE_FORCE_EXTENSION
: 0);
1007 r
= sd_bus_message_append(m
, "t", flags
);
1010 return bus_log_create_error(r
);
1012 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
1014 return log_error_errno(r
, "%s failed: %s", method
, bus_error_message(&error
, r
));
1016 (void) maybe_reload(&bus
);
1018 print_changes(reply
);
1022 static int list_images(int argc
, char *argv
[], void *userdata
) {
1023 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1024 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1025 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1026 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1029 r
= acquire_bus(&bus
);
1033 r
= bus_call_method(bus
, bus_portable_mgr
, "ListImages", &error
, &reply
, NULL
);
1035 return log_error_errno(r
, "Failed to list images: %s", bus_error_message(&error
, r
));
1037 table
= table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
1041 r
= sd_bus_message_enter_container(reply
, 'a', "(ssbtttso)");
1043 return bus_log_parse_error(r
);
1046 const char *name
, *type
, *state
;
1047 uint64_t crtime
, mtime
, usage
;
1050 r
= sd_bus_message_read(reply
, "(ssbtttso)", &name
, &type
, &ro_int
, &crtime
, &mtime
, &usage
, &state
, NULL
);
1052 return bus_log_parse_error(r
);
1056 r
= table_add_many(table
,
1059 TABLE_BOOLEAN
, ro_int
,
1060 TABLE_SET_COLOR
, ro_int
? ansi_highlight_red() : NULL
,
1061 TABLE_TIMESTAMP
, crtime
,
1062 TABLE_TIMESTAMP
, mtime
,
1064 TABLE_STRING
, state
,
1065 TABLE_SET_COLOR
, !streq(state
, "detached") ? ansi_highlight_green() : NULL
);
1067 return table_log_add_error(r
);
1070 r
= sd_bus_message_exit_container(reply
);
1072 return bus_log_parse_error(r
);
1074 if (!table_isempty(table
)) {
1075 r
= table_set_sort(table
, (size_t) 0);
1077 return table_log_sort_error(r
);
1079 table_set_header(table
, arg_legend
);
1081 r
= table_print(table
, NULL
);
1083 return table_log_print_error(r
);
1087 if (table_isempty(table
))
1088 printf("No images.\n");
1090 printf("\n%zu images listed.\n", table_get_rows(table
) - 1);
1096 static int remove_image(int argc
, char *argv
[], void *userdata
) {
1097 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1100 r
= acquire_bus(&bus
);
1104 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
1106 for (i
= 1; i
< argc
; i
++) {
1107 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1108 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
1110 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, "RemoveImage");
1112 return bus_log_create_error(r
);
1114 r
= sd_bus_message_append(m
, "s", argv
[i
]);
1116 return bus_log_create_error(r
);
1118 /* This is a slow operation, hence turn off any method call timeouts */
1119 r
= sd_bus_call(bus
, m
, USEC_INFINITY
, &error
, NULL
);
1121 return log_error_errno(r
, "Could not remove image: %s", bus_error_message(&error
, r
));
1127 static int read_only_image(int argc
, char *argv
[], void *userdata
) {
1128 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1129 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1133 b
= parse_boolean(argv
[2]);
1135 return log_error_errno(b
, "Failed to parse boolean argument: %s", argv
[2]);
1138 r
= acquire_bus(&bus
);
1142 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
1144 r
= bus_call_method(bus
, bus_portable_mgr
, "MarkImageReadOnly", &error
, NULL
, "sb", argv
[1], b
);
1146 return log_error_errno(r
, "Could not mark image read-only: %s", bus_error_message(&error
, r
));
1151 static int set_limit(int argc
, char *argv
[], void *userdata
) {
1152 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1153 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1157 r
= acquire_bus(&bus
);
1161 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
1163 if (STR_IN_SET(argv
[argc
-1], "-", "none", "infinity"))
1166 r
= parse_size(argv
[argc
-1], 1024, &limit
);
1168 return log_error_errno(r
, "Failed to parse size: %s", argv
[argc
-1]);
1172 /* With two arguments changes the quota limit of the specified image */
1173 r
= bus_call_method(bus
, bus_portable_mgr
, "SetImageLimit", &error
, NULL
, "st", argv
[1], limit
);
1175 /* With one argument changes the pool quota limit */
1176 r
= bus_call_method(bus
, bus_portable_mgr
, "SetPoolLimit", &error
, NULL
, "t", limit
);
1179 return log_error_errno(r
, "Could not set limit: %s", bus_error_message(&error
, r
));
1184 static int is_image_attached(int argc
, char *argv
[], void *userdata
) {
1185 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
1186 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1187 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1188 _cleanup_free_
char *image
= NULL
;
1189 const char *state
, *method
;
1192 r
= determine_image(argv
[1], true, &image
);
1196 r
= acquire_bus(&bus
);
1200 method
= strv_isempty(arg_extension_images
) ? "GetImageState" : "GetImageStateWithExtensions";
1202 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, method
);
1204 return bus_log_create_error(r
);
1206 r
= sd_bus_message_append(m
, "s", image
);
1208 return bus_log_create_error(r
);
1210 r
= attach_extensions_to_message(m
, method
, arg_extension_images
);
1214 if (!strv_isempty(arg_extension_images
)) {
1215 r
= sd_bus_message_append(m
, "t", UINT64_C(0));
1217 return bus_log_create_error(r
);
1220 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
1222 return log_error_errno(r
, "%s failed: %s", method
, bus_error_message(&error
, r
));
1224 r
= sd_bus_message_read(reply
, "s", &state
);
1231 return streq(state
, "detached");
1234 static int dump_profiles(void) {
1235 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1236 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1237 _cleanup_strv_free_
char **l
= NULL
;
1240 r
= acquire_bus(&bus
);
1244 r
= bus_get_property_strv(bus
, bus_portable_mgr
, "Profiles", &error
, &l
);
1246 return log_error_errno(r
, "Failed to acquire list of profiles: %s", bus_error_message(&error
, r
));
1249 log_info("Available unit profiles:");
1251 STRV_FOREACH(i
, l
) {
1253 fputc('\n', stdout
);
1259 static int help(int argc
, char *argv
[], void *userdata
) {
1260 _cleanup_free_
char *link
= NULL
;
1263 pager_open(arg_pager_flags
);
1265 r
= terminal_urlify_man("portablectl", "1", &link
);
1269 printf("%s [OPTIONS...] COMMAND ...\n\n"
1270 "%sAttach or detach portable services from the local system.%s\n"
1272 " list List available portable service images\n"
1273 " attach NAME|PATH [PREFIX...]\n"
1274 " Attach the specified portable service image\n"
1275 " detach NAME|PATH [PREFIX...]\n"
1276 " Detach the specified portable service image\n"
1277 " reattach NAME|PATH [PREFIX...]\n"
1278 " Reattach the specified portable service image\n"
1279 " inspect NAME|PATH [PREFIX...]\n"
1280 " Show details of specified portable service image\n"
1281 " is-attached NAME|PATH Query if portable service image is attached\n"
1282 " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
1283 " remove NAME|PATH... Remove a portable service image\n"
1284 " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
1286 " -h --help Show this help\n"
1287 " --version Show package version\n"
1288 " --no-pager Do not pipe output into a pager\n"
1289 " --no-legend Do not show the headers and footers\n"
1290 " --no-ask-password Do not ask for system passwords\n"
1291 " -H --host=[USER@]HOST Operate on remote host\n"
1292 " -M --machine=CONTAINER Operate on local container\n"
1293 " -q --quiet Suppress informational messages\n"
1294 " -p --profile=PROFILE Pick security profile for portable service\n"
1295 " --copy=copy|auto|symlink|mixed\n"
1296 " Pick copying or symlinking of resources\n"
1297 " --runtime Attach portable service until next reboot only\n"
1298 " --no-reload Don't reload the system and service manager\n"
1299 " --cat When inspecting include unit and os-release file\n"
1301 " --enable Immediately enable/disable the portable service\n"
1302 " after attach/detach\n"
1303 " --now Immediately start/stop the portable service after\n"
1304 " attach/before detach\n"
1305 " --no-block Don't block waiting for attach --now to complete\n"
1306 " --extension=PATH Extend the image with an overlay\n"
1307 " --force Skip 'already active' check when attaching or\n"
1308 " detaching an image (with extensions)\n"
1309 " --clean When detaching, also remove configuration, state,\n"
1310 " cache, logs or runtime data of the portable\n"
1312 "\nSee the %s for details.\n",
1313 program_invocation_short_name
,
1321 static int parse_argv(int argc
, char *argv
[]) {
1324 ARG_VERSION
= 0x100,
1327 ARG_NO_ASK_PASSWORD
,
1340 static const struct option options
[] = {
1341 { "help", no_argument
, NULL
, 'h' },
1342 { "version", no_argument
, NULL
, ARG_VERSION
},
1343 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1344 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
1345 { "no-ask-password", no_argument
, NULL
, ARG_NO_ASK_PASSWORD
},
1346 { "host", required_argument
, NULL
, 'H' },
1347 { "machine", required_argument
, NULL
, 'M' },
1348 { "quiet", no_argument
, NULL
, 'q' },
1349 { "profile", required_argument
, NULL
, 'p' },
1350 { "copy", required_argument
, NULL
, ARG_COPY
},
1351 { "runtime", no_argument
, NULL
, ARG_RUNTIME
},
1352 { "no-reload", no_argument
, NULL
, ARG_NO_RELOAD
},
1353 { "cat", no_argument
, NULL
, ARG_CAT
},
1354 { "enable", no_argument
, NULL
, ARG_ENABLE
},
1355 { "now", no_argument
, NULL
, ARG_NOW
},
1356 { "no-block", no_argument
, NULL
, ARG_NO_BLOCK
},
1357 { "extension", required_argument
, NULL
, ARG_EXTENSION
},
1358 { "force", no_argument
, NULL
, ARG_FORCE
},
1359 { "clean", no_argument
, NULL
, ARG_CLEAN
},
1368 while ((c
= getopt_long(argc
, argv
, "hH:M:qp:", options
, NULL
)) >= 0)
1373 return help(0, NULL
, NULL
);
1379 arg_pager_flags
|= PAGER_DISABLE
;
1386 case ARG_NO_ASK_PASSWORD
:
1387 arg_ask_password
= false;
1391 arg_transport
= BUS_TRANSPORT_REMOTE
;
1396 r
= parse_machine_argument(optarg
, &arg_host
, &arg_transport
);
1406 if (streq(optarg
, "help"))
1407 return dump_profiles();
1409 if (!filename_is_valid(optarg
))
1410 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1411 "Unit profile name not valid: %s", optarg
);
1413 arg_profile
= optarg
;
1417 if (streq(optarg
, "auto"))
1418 arg_copy_mode
= NULL
;
1419 else if (STR_IN_SET(optarg
, "copy", "symlink", "mixed"))
1420 arg_copy_mode
= optarg
;
1421 else if (streq(optarg
, "help")) {
1428 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1429 "Failed to parse --copy= argument: %s", optarg
);
1454 arg_no_block
= true;
1458 r
= strv_extend(&arg_extension_images
, optarg
);
1475 assert_not_reached();
1481 static int run(int argc
, char *argv
[]) {
1482 static const Verb verbs
[] = {
1483 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
1484 { "list", VERB_ANY
, 1, VERB_DEFAULT
, list_images
},
1485 { "attach", 2, VERB_ANY
, 0, attach_image
},
1486 { "detach", 2, VERB_ANY
, 0, detach_image
},
1487 { "inspect", 2, VERB_ANY
, 0, inspect_image
},
1488 { "is-attached", 2, 2, 0, is_image_attached
},
1489 { "read-only", 2, 3, 0, read_only_image
},
1490 { "remove", 2, VERB_ANY
, 0, remove_image
},
1491 { "set-limit", 3, 3, 0, set_limit
},
1492 { "reattach", 2, VERB_ANY
, 0, reattach_image
},
1500 r
= parse_argv(argc
, argv
);
1504 return dispatch_verb(argc
, argv
, verbs
, NULL
);
1507 DEFINE_MAIN_FUNCTION(run
);