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"
14 #include "dirent-util.h"
15 #include "discover-image.h"
19 #include "format-table.h"
21 #include "locale-util.h"
22 #include "main-func.h"
24 #include "parse-util.h"
25 #include "path-util.h"
26 #include "pretty-print.h"
27 #include "spawn-polkit-agent.h"
28 #include "string-util.h"
30 #include "terminal-util.h"
33 static PagerFlags arg_pager_flags
= 0;
34 static bool arg_legend
= true;
35 static bool arg_ask_password
= true;
36 static bool arg_quiet
= false;
37 static const char *arg_profile
= "default";
38 static const char* arg_copy_mode
= NULL
;
39 static bool arg_runtime
= false;
40 static bool arg_reload
= true;
41 static bool arg_cat
= false;
42 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
43 static const char *arg_host
= NULL
;
44 static bool arg_enable
= false;
45 static bool arg_now
= false;
46 static bool arg_no_block
= false;
48 static int determine_image(const char *image
, bool permit_non_existing
, char **ret
) {
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
56 if (image_name_is_valid(image
)) {
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
);
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.");
75 r
= chase_symlinks(image
, NULL
, CHASE_TRAIL_SLASH
| (permit_non_existing
? CHASE_NONEXISTENT
: 0), ret
, NULL
);
77 return log_error_errno(r
, "Cannot normalize specified image path '%s': %m", image
);
82 static int extract_prefix(const char *path
, char **ret
) {
83 _cleanup_free_
char *name
= NULL
;
84 const char *bn
, *underscore
;
89 underscore
= strchr(bn
, '_');
95 e
= endswith(bn
, ".raw");
102 name
= strndup(bn
, m
);
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
"-."))
111 if (!filename_is_valid(name
))
114 *ret
= TAKE_PTR(name
);
119 static int determine_matches(const char *image
, char **l
, bool allow_any
, char ***ret
) {
120 _cleanup_strv_free_
char **k
= NULL
;
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
127 if (strv_isempty(l
)) {
130 r
= extract_prefix(image
, &prefix
);
132 return log_error_errno(r
, "Failed to extract prefix of image name '%s': %m", image
);
135 log_info("(Matching unit files with prefix '%s'.)", prefix
);
137 r
= strv_consume(&k
, prefix
);
141 } else if (strv_equal(l
, STRV_MAKE("-"))) {
144 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
145 "Refusing all unit file match.");
148 log_info("(Matching all unit files.)");
156 _cleanup_free_
char *joined
= NULL
;
158 joined
= strv_join(k
, "', '");
162 log_info("(Matching unit files with prefixes '%s'.)", joined
);
171 static int acquire_bus(sd_bus
**bus
) {
179 r
= bus_connect_transport(arg_transport
, arg_host
, false, bus
);
181 return bus_log_connect_error(r
);
183 (void) sd_bus_set_allow_interactive_authorization(*bus
, arg_ask_password
);
188 static int maybe_reload(sd_bus
**bus
) {
189 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
190 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
196 r
= acquire_bus(bus
);
200 r
= sd_bus_message_new_method_call(
203 "org.freedesktop.systemd1",
204 "/org/freedesktop/systemd1",
205 "org.freedesktop.systemd1.Manager",
208 return bus_log_create_error(r
);
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
);
213 return log_error_errno(r
, "Failed to reload daemon: %s", bus_error_message(&error
, r
));
218 static int inspect_image(int argc
, char *argv
[], void *userdata
) {
219 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
220 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
221 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
222 _cleanup_strv_free_
char **matches
= NULL
;
223 _cleanup_free_
char *image
= NULL
;
224 bool nl
= false, header
= false;
230 r
= determine_image(argv
[1], false, &image
);
234 r
= determine_matches(argv
[1], argv
+ 2, true, &matches
);
238 r
= acquire_bus(&bus
);
242 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, "GetImageMetadata");
244 return bus_log_create_error(r
);
246 r
= sd_bus_message_append(m
, "s", image
);
248 return bus_log_create_error(r
);
250 r
= sd_bus_message_append_strv(m
, matches
);
252 return bus_log_create_error(r
);
254 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
256 return log_error_errno(r
, "Failed to inspect image metadata: %s", bus_error_message(&error
, r
));
258 r
= sd_bus_message_read(reply
, "s", &path
);
260 return bus_log_parse_error(r
);
262 r
= sd_bus_message_read_array(reply
, 'y', &data
, &sz
);
264 return bus_log_parse_error(r
);
266 (void) pager_open(arg_pager_flags
);
269 printf("%s-- OS Release: --%s\n", ansi_highlight(), ansi_normal());
270 fwrite(data
, sz
, 1, stdout
);
274 _cleanup_free_
char *pretty_portable
= NULL
, *pretty_os
= NULL
;
275 _cleanup_fclose_
FILE *f
;
277 f
= fmemopen_unlocked((void*) data
, sz
, "re");
279 return log_error_errno(errno
, "Failed to open /etc/os-release buffer: %m");
281 r
= parse_env_file(f
, "/etc/os-release",
282 "PORTABLE_PRETTY_NAME", &pretty_portable
,
283 "PRETTY_NAME", &pretty_os
);
285 return log_error_errno(r
, "Failed to parse /etc/os-release: %m");
287 printf("Image:\n\t%s\n"
288 "Portable Service:\n\t%s\n"
289 "Operating System:\n\t%s\n",
291 strna(pretty_portable
),
295 r
= sd_bus_message_enter_container(reply
, 'a', "{say}");
297 return bus_log_parse_error(r
);
302 r
= sd_bus_message_enter_container(reply
, 'e', "say");
304 return bus_log_parse_error(r
);
308 r
= sd_bus_message_read(reply
, "s", &name
);
310 return bus_log_parse_error(r
);
312 r
= sd_bus_message_read_array(reply
, 'y', &data
, &sz
);
314 return bus_log_parse_error(r
);
320 printf("%s-- Unit file: %s --%s\n", ansi_highlight(), name
, ansi_normal());
321 fwrite(data
, sz
, 1, stdout
);
326 fputs("Unit files:\n", stdout
);
335 r
= sd_bus_message_exit_container(reply
);
337 return bus_log_parse_error(r
);
340 r
= sd_bus_message_exit_container(reply
);
342 return bus_log_parse_error(r
);
347 static int print_changes(sd_bus_message
*m
) {
353 r
= sd_bus_message_enter_container(m
, 'a', "(sss)");
355 return bus_log_parse_error(r
);
358 const char *type
, *path
, *source
;
360 r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
);
362 return bus_log_parse_error(r
);
366 if (streq(type
, "symlink"))
367 log_info("Created symlink %s %s %s.", path
, special_glyph(SPECIAL_GLYPH_ARROW
), source
);
368 else if (streq(type
, "copy")) {
370 log_info("Copied %s.", path
);
372 log_info("Copied %s %s %s.", source
, special_glyph(SPECIAL_GLYPH_ARROW
), path
);
373 } else if (streq(type
, "unlink"))
374 log_info("Removed %s.", path
);
375 else if (streq(type
, "write"))
376 log_info("Written %s.", path
);
377 else if (streq(type
, "mkdir"))
378 log_info("Created directory %s.", path
);
380 log_error("Unexpected change: %s/%s/%s", type
, path
, source
);
383 r
= sd_bus_message_exit_container(m
);
390 static int maybe_enable_disable(sd_bus
*bus
, const char *path
, bool enable
) {
391 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
392 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
393 _cleanup_strv_free_
char **names
= NULL
;
394 UnitFileChange
*changes
= NULL
;
395 const uint64_t flags
= UNIT_FILE_PORTABLE
| (arg_runtime
? UNIT_FILE_RUNTIME
: 0);
396 size_t n_changes
= 0;
402 names
= strv_new(path
, NULL
);
406 r
= sd_bus_message_new_method_call(
409 "org.freedesktop.systemd1",
410 "/org/freedesktop/systemd1",
411 "org.freedesktop.systemd1.Manager",
412 enable
? "EnableUnitFilesWithFlags" : "DisableUnitFilesWithFlags");
414 return bus_log_create_error(r
);
416 r
= sd_bus_message_append_strv(m
, names
);
418 return bus_log_create_error(r
);
420 r
= sd_bus_message_append(m
, "t", flags
);
422 return bus_log_create_error(r
);
424 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
426 return log_error_errno(r
, "Failed to %s the portable service %s: %s",
427 enable
? "enable" : "disable", path
, bus_error_message(&error
, r
));
430 r
= sd_bus_message_skip(reply
, "b");
432 return bus_log_parse_error(r
);
434 (void) bus_deserialize_and_dump_unit_file_changes(reply
, arg_quiet
, &changes
, &n_changes
);
439 static int maybe_start_stop(sd_bus
*bus
, const char *path
, bool start
, BusWaitForJobs
*wait
) {
440 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
441 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
442 char *name
= (char *)basename(path
), *job
= NULL
;
448 r
= sd_bus_call_method(
450 "org.freedesktop.systemd1",
451 "/org/freedesktop/systemd1",
452 "org.freedesktop.systemd1.Manager",
453 start
? "StartUnit" : "StopUnit",
456 "ss", name
, "replace");
458 return log_error_errno(r
, "Failed to %s the portable service %s: %s",
459 start
? "start" : "stop",
461 bus_error_message(&error
, r
));
463 r
= sd_bus_message_read(reply
, "o", &job
);
465 return bus_log_parse_error(r
);
468 log_info("Queued %s to %s portable service %s.", job
, start
? "start" : "stop", name
);
471 r
= bus_wait_for_jobs_add(wait
, job
);
473 return log_error_errno(r
, "Failed to watch %s job for %s %s: %m",
474 job
, start
? "starting" : "stopping", name
);
480 static int maybe_enable_start(sd_bus
*bus
, sd_bus_message
*reply
) {
481 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*wait
= NULL
;
484 if (!arg_enable
&& !arg_now
)
488 r
= bus_wait_for_jobs_new(bus
, &wait
);
490 return log_error_errno(r
, "Could not watch jobs: %m");
493 r
= sd_bus_message_rewind(reply
, true);
496 r
= sd_bus_message_enter_container(reply
, 'a', "(sss)");
498 return bus_log_parse_error(r
);
501 char *type
, *path
, *source
;
503 r
= sd_bus_message_read(reply
, "(sss)", &type
, &path
, &source
);
505 return bus_log_parse_error(r
);
509 if (STR_IN_SET(type
, "symlink", "copy") && ENDSWITH_SET(path
, ".service", ".target", ".socket")) {
510 (void) maybe_enable_disable(bus
, path
, true);
511 (void) maybe_start_stop(bus
, path
, true, wait
);
515 r
= sd_bus_message_exit_container(reply
);
520 r
= bus_wait_for_jobs(wait
, arg_quiet
, NULL
);
528 static int maybe_stop_disable(sd_bus
*bus
, char *image
, char *argv
[]) {
529 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*wait
= NULL
;
530 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
531 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
532 _cleanup_strv_free_
char **matches
= NULL
;
535 if (!arg_enable
&& !arg_now
)
538 r
= determine_matches(argv
[1], argv
+ 2, true, &matches
);
542 r
= bus_wait_for_jobs_new(bus
, &wait
);
544 return log_error_errno(r
, "Could not watch jobs: %m");
546 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, "GetImageMetadata");
548 return bus_log_create_error(r
);
550 r
= sd_bus_message_append(m
, "s", image
);
552 return bus_log_create_error(r
);
554 r
= sd_bus_message_append_strv(m
, matches
);
556 return bus_log_create_error(r
);
558 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
560 return log_error_errno(r
, "Failed to inspect image metadata: %s", bus_error_message(&error
, r
));
562 r
= sd_bus_message_skip(reply
, "say");
564 return bus_log_parse_error(r
);
566 r
= sd_bus_message_enter_container(reply
, 'a', "{say}");
568 return bus_log_parse_error(r
);
573 r
= sd_bus_message_enter_container(reply
, 'e', "say");
575 return bus_log_parse_error(r
);
579 r
= sd_bus_message_read(reply
, "s", &name
);
581 return bus_log_parse_error(r
);
583 r
= sd_bus_message_skip(reply
, "ay");
585 return bus_log_parse_error(r
);
587 r
= sd_bus_message_exit_container(reply
);
589 return bus_log_parse_error(r
);
591 (void) maybe_start_stop(bus
, name
, false, wait
);
592 (void) maybe_enable_disable(bus
, name
, false);
595 r
= sd_bus_message_exit_container(reply
);
597 return bus_log_parse_error(r
);
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
);
607 static int attach_image(int argc
, char *argv
[], void *userdata
) {
608 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
609 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
610 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
611 _cleanup_strv_free_
char **matches
= NULL
;
612 _cleanup_free_
char *image
= NULL
;
615 r
= determine_image(argv
[1], false, &image
);
619 r
= determine_matches(argv
[1], argv
+ 2, false, &matches
);
623 r
= acquire_bus(&bus
);
627 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
629 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, "AttachImage");
631 return bus_log_create_error(r
);
633 r
= sd_bus_message_append(m
, "s", image
);
635 return bus_log_create_error(r
);
637 r
= sd_bus_message_append_strv(m
, matches
);
639 return bus_log_create_error(r
);
641 r
= sd_bus_message_append(m
, "sbs", arg_profile
, arg_runtime
, arg_copy_mode
);
643 return bus_log_create_error(r
);
645 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
647 return log_error_errno(r
, "Failed to attach image: %s", bus_error_message(&error
, r
));
649 (void) maybe_reload(&bus
);
651 print_changes(reply
);
653 (void) maybe_enable_start(bus
, reply
);
658 static int detach_image(int argc
, char *argv
[], void *userdata
) {
659 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
660 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
661 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
662 _cleanup_free_
char *image
= NULL
;
665 r
= determine_image(argv
[1], true, &image
);
669 r
= acquire_bus(&bus
);
673 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
675 (void) maybe_stop_disable(bus
, image
, argv
);
677 r
= bus_call_method(bus
, bus_portable_mgr
, "DetachImage", &error
, &reply
, "sb", image
, arg_runtime
);
679 return log_error_errno(r
, "Failed to detach image: %s", bus_error_message(&error
, r
));
681 (void) maybe_reload(&bus
);
683 print_changes(reply
);
687 static int list_images(int argc
, char *argv
[], void *userdata
) {
688 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
689 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
690 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
691 _cleanup_(table_unrefp
) Table
*table
= NULL
;
694 r
= acquire_bus(&bus
);
698 r
= bus_call_method(bus
, bus_portable_mgr
, "ListImages", &error
, &reply
, NULL
);
700 return log_error_errno(r
, "Failed to list images: %s", bus_error_message(&error
, r
));
702 table
= table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
706 r
= sd_bus_message_enter_container(reply
, 'a', "(ssbtttso)");
708 return bus_log_parse_error(r
);
711 const char *name
, *type
, *state
;
712 uint64_t crtime
, mtime
, usage
;
715 r
= sd_bus_message_read(reply
, "(ssbtttso)", &name
, &type
, &ro_int
, &crtime
, &mtime
, &usage
, &state
, NULL
);
717 return bus_log_parse_error(r
);
721 r
= table_add_many(table
,
724 TABLE_BOOLEAN
, ro_int
,
725 TABLE_SET_COLOR
, ro_int
? ansi_highlight_red() : NULL
,
726 TABLE_TIMESTAMP
, crtime
,
727 TABLE_TIMESTAMP
, mtime
,
730 TABLE_SET_COLOR
, !streq(state
, "detached") ? ansi_highlight_green() : NULL
);
732 return table_log_add_error(r
);
735 r
= sd_bus_message_exit_container(reply
);
737 return bus_log_parse_error(r
);
739 if (table_get_rows(table
) > 1) {
740 r
= table_set_sort(table
, (size_t) 0, (size_t) -1);
742 return table_log_sort_error(r
);
744 table_set_header(table
, arg_legend
);
746 r
= table_print(table
, NULL
);
748 return table_log_print_error(r
);
752 if (table_get_rows(table
) > 1)
753 printf("\n%zu images listed.\n", table_get_rows(table
) - 1);
755 printf("No images.\n");
761 static int remove_image(int argc
, char *argv
[], void *userdata
) {
762 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
765 r
= acquire_bus(&bus
);
769 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
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
;
775 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, "RemoveImage");
777 return bus_log_create_error(r
);
779 r
= sd_bus_message_append(m
, "s", argv
[i
]);
781 return bus_log_create_error(r
);
783 /* This is a slow operation, hence turn off any method call timeouts */
784 r
= sd_bus_call(bus
, m
, USEC_INFINITY
, &error
, NULL
);
786 return log_error_errno(r
, "Could not remove image: %s", bus_error_message(&error
, r
));
792 static int read_only_image(int argc
, char *argv
[], void *userdata
) {
793 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
794 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
798 b
= parse_boolean(argv
[2]);
800 return log_error_errno(b
, "Failed to parse boolean argument: %s", argv
[2]);
803 r
= acquire_bus(&bus
);
807 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
809 r
= bus_call_method(bus
, bus_portable_mgr
, "MarkImageReadOnly", &error
, NULL
, "sb", argv
[1], b
);
811 return log_error_errno(r
, "Could not mark image read-only: %s", bus_error_message(&error
, r
));
816 static int set_limit(int argc
, char *argv
[], void *userdata
) {
817 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
818 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
822 r
= acquire_bus(&bus
);
826 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
828 if (STR_IN_SET(argv
[argc
-1], "-", "none", "infinity"))
829 limit
= (uint64_t) -1;
831 r
= parse_size(argv
[argc
-1], 1024, &limit
);
833 return log_error_errno(r
, "Failed to parse size: %s", argv
[argc
-1]);
837 /* With two arguments changes the quota limit of the specified image */
838 r
= bus_call_method(bus
, bus_portable_mgr
, "SetImageLimit", &error
, NULL
, "st", argv
[1], limit
);
840 /* With one argument changes the pool quota limit */
841 r
= bus_call_method(bus
, bus_portable_mgr
, "SetPoolLimit", &error
, NULL
, "t", limit
);
844 return log_error_errno(r
, "Could not set limit: %s", bus_error_message(&error
, r
));
849 static int is_image_attached(int argc
, char *argv
[], void *userdata
) {
850 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
851 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
852 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
853 _cleanup_free_
char *image
= NULL
;
857 r
= determine_image(argv
[1], true, &image
);
861 r
= acquire_bus(&bus
);
865 r
= bus_call_method(bus
, bus_portable_mgr
, "GetImageState", &error
, &reply
, "s", image
);
867 return log_error_errno(r
, "Failed to get image state: %s", bus_error_message(&error
, r
));
869 r
= sd_bus_message_read(reply
, "s", &state
);
876 return streq(state
, "detached");
879 static int dump_profiles(void) {
880 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
881 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
882 _cleanup_strv_free_
char **l
= NULL
;
886 r
= acquire_bus(&bus
);
890 r
= bus_get_property_strv(bus
, bus_portable_mgr
, "Profiles", &error
, &l
);
892 return log_error_errno(r
, "Failed to acquire list of profiles: %s", bus_error_message(&error
, r
));
895 log_info("Available unit profiles:");
905 static int help(int argc
, char *argv
[], void *userdata
) {
906 _cleanup_free_
char *link
= NULL
;
909 (void) pager_open(arg_pager_flags
);
911 r
= terminal_urlify_man("portablectl", "1", &link
);
915 printf("%s [OPTIONS...] COMMAND ...\n\n"
916 "%sAttach or detach portable services from the local system.%s\n"
918 " list List available portable service images\n"
919 " attach NAME|PATH [PREFIX...]\n"
920 " Attach the specified portable service image\n"
921 " detach NAME|PATH [PREFIX...]\n"
922 " Detach the specified portable service image\n"
923 " inspect NAME|PATH [PREFIX...]\n"
924 " Show details of specified portable service image\n"
925 " is-attached NAME|PATH Query if portable service image is attached\n"
926 " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
927 " remove NAME|PATH... Remove a portable service image\n"
928 " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
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"
944 " --enable Immediately enable/disable the portable service\n"
945 " after attach/detach\n"
946 " --now Immediately start/stop the portable service after\n"
947 " attach/before detach\n"
948 " --no-block Don't block waiting for attach --now to complete\n"
949 "\nSee the %s for details.\n",
950 program_invocation_short_name
,
958 static int parse_argv(int argc
, char *argv
[]) {
974 static const struct option options
[] = {
975 { "help", no_argument
, NULL
, 'h' },
976 { "version", no_argument
, NULL
, ARG_VERSION
},
977 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
978 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
979 { "no-ask-password", no_argument
, NULL
, ARG_NO_ASK_PASSWORD
},
980 { "host", required_argument
, NULL
, 'H' },
981 { "machine", required_argument
, NULL
, 'M' },
982 { "quiet", no_argument
, NULL
, 'q' },
983 { "profile", required_argument
, NULL
, 'p' },
984 { "copy", required_argument
, NULL
, ARG_COPY
},
985 { "runtime", no_argument
, NULL
, ARG_RUNTIME
},
986 { "no-reload", no_argument
, NULL
, ARG_NO_RELOAD
},
987 { "cat", no_argument
, NULL
, ARG_CAT
},
988 { "enable", no_argument
, NULL
, ARG_ENABLE
},
989 { "now", no_argument
, NULL
, ARG_NOW
},
990 { "no-block", no_argument
, NULL
, ARG_NO_BLOCK
},
1000 c
= getopt_long(argc
, argv
, "hH:M:qp:", options
, NULL
);
1007 return help(0, NULL
, NULL
);
1013 arg_pager_flags
|= PAGER_DISABLE
;
1020 case ARG_NO_ASK_PASSWORD
:
1021 arg_ask_password
= false;
1025 arg_transport
= BUS_TRANSPORT_REMOTE
;
1030 arg_transport
= BUS_TRANSPORT_MACHINE
;
1039 if (streq(optarg
, "help"))
1040 return dump_profiles();
1042 if (!filename_is_valid(optarg
))
1043 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1044 "Unit profile name not valid: %s", optarg
);
1046 arg_profile
= optarg
;
1050 if (streq(optarg
, "auto"))
1051 arg_copy_mode
= NULL
;
1052 else if (STR_IN_SET(optarg
, "copy", "symlink"))
1053 arg_copy_mode
= optarg
;
1054 else if (streq(optarg
, "help")) {
1060 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1061 "Failed to parse --copy= argument: %s", optarg
);
1086 arg_no_block
= true;
1093 assert_not_reached("Unhandled option");
1100 static int run(int argc
, char *argv
[]) {
1101 static const Verb verbs
[] = {
1102 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
1103 { "list", VERB_ANY
, 1, VERB_DEFAULT
, list_images
},
1104 { "attach", 2, VERB_ANY
, 0, attach_image
},
1105 { "detach", 2, VERB_ANY
, 0, detach_image
},
1106 { "inspect", 2, VERB_ANY
, 0, inspect_image
},
1107 { "is-attached", 2, 2, 0, is_image_attached
},
1108 { "read-only", 2, 3, 0, read_only_image
},
1109 { "remove", 2, VERB_ANY
, 0, remove_image
},
1110 { "set-limit", 3, 3, 0, set_limit
},
1118 r
= parse_argv(argc
, argv
);
1122 return dispatch_verb(argc
, argv
, verbs
, NULL
);
1125 DEFINE_MAIN_FUNCTION(run
);