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 bool is_portable_managed(const char *unit
) {
49 return ENDSWITH_SET(unit
, ".service", ".target", ".socket", ".path", ".timer");
52 static int determine_image(const char *image
, bool permit_non_existing
, char **ret
) {
55 /* If the specified name is a valid image name, we pass it as-is to portabled, which will search for it in the
56 * usual search directories. Otherwise we presume it's a path, and will normalize it on the client's side
57 * (among other things, to make the path independent of the client's working directory) before passing it
60 if (image_name_is_valid(image
)) {
63 if (!arg_quiet
&& laccess(image
, F_OK
) >= 0)
64 log_warning("Ambiguous invocation: current working directory contains file matching non-path argument '%s', ignoring. "
65 "Prefix argument with './' to force reference to file in current working directory.", image
);
75 if (arg_transport
!= BUS_TRANSPORT_LOCAL
)
76 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
77 "Operations on images by path not supported when connecting to remote systems.");
79 r
= chase_symlinks(image
, NULL
, CHASE_TRAIL_SLASH
| (permit_non_existing
? CHASE_NONEXISTENT
: 0), ret
, NULL
);
81 return log_error_errno(r
, "Cannot normalize specified image path '%s': %m", image
);
86 static int extract_prefix(const char *path
, char **ret
) {
87 _cleanup_free_
char *name
= NULL
;
88 const char *bn
, *underscore
;
93 underscore
= strchr(bn
, '_');
99 e
= endswith(bn
, ".raw");
106 name
= strndup(bn
, m
);
110 /* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_'
111 * which we use as delimiter for the second part of the image string, which we ignore for now. */
112 if (!in_charset(name
, DIGITS LETTERS
"-."))
115 if (!filename_is_valid(name
))
118 *ret
= TAKE_PTR(name
);
123 static int determine_matches(const char *image
, char **l
, bool allow_any
, char ***ret
) {
124 _cleanup_strv_free_
char **k
= NULL
;
127 /* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list
128 * contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly
131 if (strv_isempty(l
)) {
134 r
= extract_prefix(image
, &prefix
);
136 return log_error_errno(r
, "Failed to extract prefix of image name '%s': %m", image
);
139 log_info("(Matching unit files with prefix '%s'.)", prefix
);
141 r
= strv_consume(&k
, prefix
);
145 } else if (strv_equal(l
, STRV_MAKE("-"))) {
148 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
149 "Refusing all unit file match.");
152 log_info("(Matching all unit files.)");
160 _cleanup_free_
char *joined
= NULL
;
162 joined
= strv_join(k
, "', '");
166 log_info("(Matching unit files with prefixes '%s'.)", joined
);
175 static int acquire_bus(sd_bus
**bus
) {
183 r
= bus_connect_transport(arg_transport
, arg_host
, false, bus
);
185 return bus_log_connect_error(r
);
187 (void) sd_bus_set_allow_interactive_authorization(*bus
, arg_ask_password
);
192 static int maybe_reload(sd_bus
**bus
) {
193 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
194 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
200 r
= acquire_bus(bus
);
204 r
= sd_bus_message_new_method_call(
207 "org.freedesktop.systemd1",
208 "/org/freedesktop/systemd1",
209 "org.freedesktop.systemd1.Manager",
212 return bus_log_create_error(r
);
214 /* Reloading the daemon may take long, hence set a longer timeout here */
215 r
= sd_bus_call(*bus
, m
, DEFAULT_TIMEOUT_USEC
* 2, &error
, NULL
);
217 return log_error_errno(r
, "Failed to reload daemon: %s", bus_error_message(&error
, r
));
222 static int inspect_image(int argc
, char *argv
[], void *userdata
) {
223 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
224 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
225 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
226 _cleanup_strv_free_
char **matches
= NULL
;
227 _cleanup_free_
char *image
= NULL
;
228 bool nl
= false, header
= false;
234 r
= determine_image(argv
[1], false, &image
);
238 r
= determine_matches(argv
[1], argv
+ 2, true, &matches
);
242 r
= acquire_bus(&bus
);
246 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, "GetImageMetadata");
248 return bus_log_create_error(r
);
250 r
= sd_bus_message_append(m
, "s", image
);
252 return bus_log_create_error(r
);
254 r
= sd_bus_message_append_strv(m
, matches
);
256 return bus_log_create_error(r
);
258 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
260 return log_error_errno(r
, "Failed to inspect image metadata: %s", bus_error_message(&error
, r
));
262 r
= sd_bus_message_read(reply
, "s", &path
);
264 return bus_log_parse_error(r
);
266 r
= sd_bus_message_read_array(reply
, 'y', &data
, &sz
);
268 return bus_log_parse_error(r
);
270 (void) pager_open(arg_pager_flags
);
273 printf("%s-- OS Release: --%s\n", ansi_highlight(), ansi_normal());
274 fwrite(data
, sz
, 1, stdout
);
278 _cleanup_free_
char *pretty_portable
= NULL
, *pretty_os
= NULL
;
279 _cleanup_fclose_
FILE *f
;
281 f
= fmemopen_unlocked((void*) data
, sz
, "re");
283 return log_error_errno(errno
, "Failed to open /etc/os-release buffer: %m");
285 r
= parse_env_file(f
, "/etc/os-release",
286 "PORTABLE_PRETTY_NAME", &pretty_portable
,
287 "PRETTY_NAME", &pretty_os
);
289 return log_error_errno(r
, "Failed to parse /etc/os-release: %m");
291 printf("Image:\n\t%s\n"
292 "Portable Service:\n\t%s\n"
293 "Operating System:\n\t%s\n",
295 strna(pretty_portable
),
299 r
= sd_bus_message_enter_container(reply
, 'a', "{say}");
301 return bus_log_parse_error(r
);
306 r
= sd_bus_message_enter_container(reply
, 'e', "say");
308 return bus_log_parse_error(r
);
312 r
= sd_bus_message_read(reply
, "s", &name
);
314 return bus_log_parse_error(r
);
316 r
= sd_bus_message_read_array(reply
, 'y', &data
, &sz
);
318 return bus_log_parse_error(r
);
324 printf("%s-- Unit file: %s --%s\n", ansi_highlight(), name
, ansi_normal());
325 fwrite(data
, sz
, 1, stdout
);
330 fputs("Unit files:\n", stdout
);
339 r
= sd_bus_message_exit_container(reply
);
341 return bus_log_parse_error(r
);
344 r
= sd_bus_message_exit_container(reply
);
346 return bus_log_parse_error(r
);
351 static int print_changes(sd_bus_message
*m
) {
357 r
= sd_bus_message_enter_container(m
, 'a', "(sss)");
359 return bus_log_parse_error(r
);
362 const char *type
, *path
, *source
;
364 r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
);
366 return bus_log_parse_error(r
);
370 if (streq(type
, "symlink"))
371 log_info("Created symlink %s %s %s.", path
, special_glyph(SPECIAL_GLYPH_ARROW
), source
);
372 else if (streq(type
, "copy")) {
374 log_info("Copied %s.", path
);
376 log_info("Copied %s %s %s.", source
, special_glyph(SPECIAL_GLYPH_ARROW
), path
);
377 } else if (streq(type
, "unlink"))
378 log_info("Removed %s.", path
);
379 else if (streq(type
, "write"))
380 log_info("Written %s.", path
);
381 else if (streq(type
, "mkdir"))
382 log_info("Created directory %s.", path
);
384 log_error("Unexpected change: %s/%s/%s", type
, path
, source
);
387 r
= sd_bus_message_exit_container(m
);
394 static int maybe_enable_disable(sd_bus
*bus
, const char *path
, bool enable
) {
395 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
396 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
397 _cleanup_strv_free_
char **names
= NULL
;
398 UnitFileChange
*changes
= NULL
;
399 const uint64_t flags
= UNIT_FILE_PORTABLE
| (arg_runtime
? UNIT_FILE_RUNTIME
: 0);
400 size_t n_changes
= 0;
406 names
= strv_new(path
, NULL
);
410 r
= sd_bus_message_new_method_call(
413 "org.freedesktop.systemd1",
414 "/org/freedesktop/systemd1",
415 "org.freedesktop.systemd1.Manager",
416 enable
? "EnableUnitFilesWithFlags" : "DisableUnitFilesWithFlags");
418 return bus_log_create_error(r
);
420 r
= sd_bus_message_append_strv(m
, names
);
422 return bus_log_create_error(r
);
424 r
= sd_bus_message_append(m
, "t", flags
);
426 return bus_log_create_error(r
);
428 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
430 return log_error_errno(r
, "Failed to %s the portable service %s: %s",
431 enable
? "enable" : "disable", path
, bus_error_message(&error
, r
));
434 r
= sd_bus_message_skip(reply
, "b");
436 return bus_log_parse_error(r
);
438 (void) bus_deserialize_and_dump_unit_file_changes(reply
, arg_quiet
, &changes
, &n_changes
);
443 static int maybe_start_stop_restart(sd_bus
*bus
, const char *path
, const char *method
, BusWaitForJobs
*wait
) {
444 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
445 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
446 char *name
= (char *)basename(path
), *job
= NULL
;
449 assert(STR_IN_SET(method
, "StartUnit", "StopUnit", "RestartUnit"));
454 r
= sd_bus_call_method(
456 "org.freedesktop.systemd1",
457 "/org/freedesktop/systemd1",
458 "org.freedesktop.systemd1.Manager",
462 "ss", name
, "replace");
464 return log_error_errno(r
, "Failed to call %s on the portable service %s: %s",
467 bus_error_message(&error
, r
));
469 r
= sd_bus_message_read(reply
, "o", &job
);
471 return bus_log_parse_error(r
);
474 log_info("Queued %s to call %s on portable service %s.", job
, method
, name
);
477 r
= bus_wait_for_jobs_add(wait
, job
);
479 return log_error_errno(r
, "Failed to watch %s job to call %s on %s: %m",
486 static int maybe_enable_start(sd_bus
*bus
, sd_bus_message
*reply
) {
487 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*wait
= NULL
;
490 if (!arg_enable
&& !arg_now
)
494 r
= bus_wait_for_jobs_new(bus
, &wait
);
496 return log_error_errno(r
, "Could not watch jobs: %m");
499 r
= sd_bus_message_rewind(reply
, true);
502 r
= sd_bus_message_enter_container(reply
, 'a', "(sss)");
504 return bus_log_parse_error(r
);
507 char *type
, *path
, *source
;
509 r
= sd_bus_message_read(reply
, "(sss)", &type
, &path
, &source
);
511 return bus_log_parse_error(r
);
515 if (STR_IN_SET(type
, "symlink", "copy") && is_portable_managed(path
)) {
516 (void) maybe_enable_disable(bus
, path
, true);
517 (void) maybe_start_stop_restart(bus
, path
, "StartUnit", wait
);
521 r
= sd_bus_message_exit_container(reply
);
526 r
= bus_wait_for_jobs(wait
, arg_quiet
, NULL
);
534 static int maybe_stop_enable_restart(sd_bus
*bus
, sd_bus_message
*reply
) {
535 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*wait
= NULL
;
538 if (!arg_enable
&& !arg_now
)
542 r
= bus_wait_for_jobs_new(bus
, &wait
);
544 return log_error_errno(r
, "Could not watch jobs: %m");
547 r
= sd_bus_message_rewind(reply
, true);
551 /* First we get a list of units that were definitely removed, not just re-attached,
552 * so we can also stop them if the user asked us to. */
553 r
= sd_bus_message_enter_container(reply
, 'a', "(sss)");
555 return bus_log_parse_error(r
);
558 char *type
, *path
, *source
;
560 r
= sd_bus_message_read(reply
, "(sss)", &type
, &path
, &source
);
562 return bus_log_parse_error(r
);
566 if (streq(type
, "unlink") && is_portable_managed(path
))
567 (void) maybe_start_stop_restart(bus
, path
, "StopUnit", wait
);
570 r
= sd_bus_message_exit_container(reply
);
574 /* Then we get a list of units that were either added or changed, so that we can
575 * enable them and/or restart them if the user asked us to. */
576 r
= sd_bus_message_enter_container(reply
, 'a', "(sss)");
578 return bus_log_parse_error(r
);
581 char *type
, *path
, *source
;
583 r
= sd_bus_message_read(reply
, "(sss)", &type
, &path
, &source
);
585 return bus_log_parse_error(r
);
589 if (STR_IN_SET(type
, "symlink", "copy") && is_portable_managed(path
)) {
590 (void) maybe_enable_disable(bus
, path
, true);
591 (void) maybe_start_stop_restart(bus
, path
, "RestartUnit", wait
);
595 r
= sd_bus_message_exit_container(reply
);
600 r
= bus_wait_for_jobs(wait
, arg_quiet
, NULL
);
608 static int maybe_stop_disable(sd_bus
*bus
, char *image
, char *argv
[]) {
609 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*wait
= NULL
;
610 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
611 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
612 _cleanup_strv_free_
char **matches
= NULL
;
615 if (!arg_enable
&& !arg_now
)
618 r
= determine_matches(argv
[1], argv
+ 2, true, &matches
);
622 r
= bus_wait_for_jobs_new(bus
, &wait
);
624 return log_error_errno(r
, "Could not watch jobs: %m");
626 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, "GetImageMetadata");
628 return bus_log_create_error(r
);
630 r
= sd_bus_message_append(m
, "s", image
);
632 return bus_log_create_error(r
);
634 r
= sd_bus_message_append_strv(m
, matches
);
636 return bus_log_create_error(r
);
638 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
640 return log_error_errno(r
, "Failed to inspect image metadata: %s", bus_error_message(&error
, r
));
642 r
= sd_bus_message_skip(reply
, "say");
644 return bus_log_parse_error(r
);
646 r
= sd_bus_message_enter_container(reply
, 'a', "{say}");
648 return bus_log_parse_error(r
);
653 r
= sd_bus_message_enter_container(reply
, 'e', "say");
655 return bus_log_parse_error(r
);
659 r
= sd_bus_message_read(reply
, "s", &name
);
661 return bus_log_parse_error(r
);
663 r
= sd_bus_message_skip(reply
, "ay");
665 return bus_log_parse_error(r
);
667 r
= sd_bus_message_exit_container(reply
);
669 return bus_log_parse_error(r
);
671 (void) maybe_start_stop_restart(bus
, name
, "StopUnit", wait
);
672 (void) maybe_enable_disable(bus
, name
, false);
675 r
= sd_bus_message_exit_container(reply
);
677 return bus_log_parse_error(r
);
679 /* Stopping must always block or the detach will fail if the unit is still running */
680 r
= bus_wait_for_jobs(wait
, arg_quiet
, NULL
);
687 static int attach_reattach_image(int argc
, char *argv
[], const char *method
) {
688 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
689 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
690 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
691 _cleanup_strv_free_
char **matches
= NULL
;
692 _cleanup_free_
char *image
= NULL
;
696 assert(STR_IN_SET(method
, "AttachImage", "ReattachImage"));
698 r
= determine_image(argv
[1], false, &image
);
702 r
= determine_matches(argv
[1], argv
+ 2, false, &matches
);
706 r
= acquire_bus(&bus
);
710 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
712 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, method
);
714 return bus_log_create_error(r
);
716 r
= sd_bus_message_append(m
, "s", image
);
718 return bus_log_create_error(r
);
720 r
= sd_bus_message_append_strv(m
, matches
);
722 return bus_log_create_error(r
);
724 r
= sd_bus_message_append(m
, "sbs", arg_profile
, arg_runtime
, arg_copy_mode
);
726 return bus_log_create_error(r
);
728 r
= sd_bus_call(bus
, m
, 0, &error
, &reply
);
730 return log_error_errno(r
, "%s failed: %s", method
, bus_error_message(&error
, r
));
732 (void) maybe_reload(&bus
);
734 print_changes(reply
);
736 if (streq(method
, "AttachImage"))
737 (void) maybe_enable_start(bus
, reply
);
739 /* ReattachImage returns 2 lists - removed units first, and changed/added second */
740 print_changes(reply
);
741 (void) maybe_stop_enable_restart(bus
, reply
);
747 static int attach_image(int argc
, char *argv
[], void *userdata
) {
748 return attach_reattach_image(argc
, argv
, "AttachImage");
751 static int reattach_image(int argc
, char *argv
[], void *userdata
) {
752 return attach_reattach_image(argc
, argv
, "ReattachImage");
755 static int detach_image(int argc
, char *argv
[], void *userdata
) {
756 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
757 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
758 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
759 _cleanup_free_
char *image
= NULL
;
762 r
= determine_image(argv
[1], true, &image
);
766 r
= acquire_bus(&bus
);
770 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
772 (void) maybe_stop_disable(bus
, image
, argv
);
774 r
= bus_call_method(bus
, bus_portable_mgr
, "DetachImage", &error
, &reply
, "sb", image
, arg_runtime
);
776 return log_error_errno(r
, "Failed to detach image: %s", bus_error_message(&error
, r
));
778 (void) maybe_reload(&bus
);
780 print_changes(reply
);
784 static int list_images(int argc
, char *argv
[], void *userdata
) {
785 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
786 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
787 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
788 _cleanup_(table_unrefp
) Table
*table
= NULL
;
791 r
= acquire_bus(&bus
);
795 r
= bus_call_method(bus
, bus_portable_mgr
, "ListImages", &error
, &reply
, NULL
);
797 return log_error_errno(r
, "Failed to list images: %s", bus_error_message(&error
, r
));
799 table
= table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
803 r
= sd_bus_message_enter_container(reply
, 'a', "(ssbtttso)");
805 return bus_log_parse_error(r
);
808 const char *name
, *type
, *state
;
809 uint64_t crtime
, mtime
, usage
;
812 r
= sd_bus_message_read(reply
, "(ssbtttso)", &name
, &type
, &ro_int
, &crtime
, &mtime
, &usage
, &state
, NULL
);
814 return bus_log_parse_error(r
);
818 r
= table_add_many(table
,
821 TABLE_BOOLEAN
, ro_int
,
822 TABLE_SET_COLOR
, ro_int
? ansi_highlight_red() : NULL
,
823 TABLE_TIMESTAMP
, crtime
,
824 TABLE_TIMESTAMP
, mtime
,
827 TABLE_SET_COLOR
, !streq(state
, "detached") ? ansi_highlight_green() : NULL
);
829 return table_log_add_error(r
);
832 r
= sd_bus_message_exit_container(reply
);
834 return bus_log_parse_error(r
);
836 if (table_get_rows(table
) > 1) {
837 r
= table_set_sort(table
, (size_t) 0, (size_t) -1);
839 return table_log_sort_error(r
);
841 table_set_header(table
, arg_legend
);
843 r
= table_print(table
, NULL
);
845 return table_log_print_error(r
);
849 if (table_get_rows(table
) > 1)
850 printf("\n%zu images listed.\n", table_get_rows(table
) - 1);
852 printf("No images.\n");
858 static int remove_image(int argc
, char *argv
[], void *userdata
) {
859 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
862 r
= acquire_bus(&bus
);
866 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
868 for (i
= 1; i
< argc
; i
++) {
869 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
870 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
872 r
= bus_message_new_method_call(bus
, &m
, bus_portable_mgr
, "RemoveImage");
874 return bus_log_create_error(r
);
876 r
= sd_bus_message_append(m
, "s", argv
[i
]);
878 return bus_log_create_error(r
);
880 /* This is a slow operation, hence turn off any method call timeouts */
881 r
= sd_bus_call(bus
, m
, USEC_INFINITY
, &error
, NULL
);
883 return log_error_errno(r
, "Could not remove image: %s", bus_error_message(&error
, r
));
889 static int read_only_image(int argc
, char *argv
[], void *userdata
) {
890 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
891 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
895 b
= parse_boolean(argv
[2]);
897 return log_error_errno(b
, "Failed to parse boolean argument: %s", argv
[2]);
900 r
= acquire_bus(&bus
);
904 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
906 r
= bus_call_method(bus
, bus_portable_mgr
, "MarkImageReadOnly", &error
, NULL
, "sb", argv
[1], b
);
908 return log_error_errno(r
, "Could not mark image read-only: %s", bus_error_message(&error
, r
));
913 static int set_limit(int argc
, char *argv
[], void *userdata
) {
914 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
915 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
919 r
= acquire_bus(&bus
);
923 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
925 if (STR_IN_SET(argv
[argc
-1], "-", "none", "infinity"))
926 limit
= (uint64_t) -1;
928 r
= parse_size(argv
[argc
-1], 1024, &limit
);
930 return log_error_errno(r
, "Failed to parse size: %s", argv
[argc
-1]);
934 /* With two arguments changes the quota limit of the specified image */
935 r
= bus_call_method(bus
, bus_portable_mgr
, "SetImageLimit", &error
, NULL
, "st", argv
[1], limit
);
937 /* With one argument changes the pool quota limit */
938 r
= bus_call_method(bus
, bus_portable_mgr
, "SetPoolLimit", &error
, NULL
, "t", limit
);
941 return log_error_errno(r
, "Could not set limit: %s", bus_error_message(&error
, r
));
946 static int is_image_attached(int argc
, char *argv
[], void *userdata
) {
947 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
948 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
949 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
950 _cleanup_free_
char *image
= NULL
;
954 r
= determine_image(argv
[1], true, &image
);
958 r
= acquire_bus(&bus
);
962 r
= bus_call_method(bus
, bus_portable_mgr
, "GetImageState", &error
, &reply
, "s", image
);
964 return log_error_errno(r
, "Failed to get image state: %s", bus_error_message(&error
, r
));
966 r
= sd_bus_message_read(reply
, "s", &state
);
973 return streq(state
, "detached");
976 static int dump_profiles(void) {
977 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
978 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
979 _cleanup_strv_free_
char **l
= NULL
;
983 r
= acquire_bus(&bus
);
987 r
= bus_get_property_strv(bus
, bus_portable_mgr
, "Profiles", &error
, &l
);
989 return log_error_errno(r
, "Failed to acquire list of profiles: %s", bus_error_message(&error
, r
));
992 log_info("Available unit profiles:");
1002 static int help(int argc
, char *argv
[], void *userdata
) {
1003 _cleanup_free_
char *link
= NULL
;
1006 (void) pager_open(arg_pager_flags
);
1008 r
= terminal_urlify_man("portablectl", "1", &link
);
1012 printf("%s [OPTIONS...] COMMAND ...\n\n"
1013 "%sAttach or detach portable services from the local system.%s\n"
1015 " list List available portable service images\n"
1016 " attach NAME|PATH [PREFIX...]\n"
1017 " Attach the specified portable service image\n"
1018 " detach NAME|PATH [PREFIX...]\n"
1019 " Detach the specified portable service image\n"
1020 " reattach NAME|PATH [PREFIX...]\n"
1021 " Reattach the specified portable service image\n"
1022 " inspect NAME|PATH [PREFIX...]\n"
1023 " Show details of specified portable service image\n"
1024 " is-attached NAME|PATH Query if portable service image is attached\n"
1025 " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
1026 " remove NAME|PATH... Remove a portable service image\n"
1027 " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
1029 " -h --help Show this help\n"
1030 " --version Show package version\n"
1031 " --no-pager Do not pipe output into a pager\n"
1032 " --no-legend Do not show the headers and footers\n"
1033 " --no-ask-password Do not ask for system passwords\n"
1034 " -H --host=[USER@]HOST Operate on remote host\n"
1035 " -M --machine=CONTAINER Operate on local container\n"
1036 " -q --quiet Suppress informational messages\n"
1037 " -p --profile=PROFILE Pick security profile for portable service\n"
1038 " --copy=copy|auto|symlink Prefer copying or symlinks if possible\n"
1039 " --runtime Attach portable service until next reboot only\n"
1040 " --no-reload Don't reload the system and service manager\n"
1041 " --cat When inspecting include unit and os-release file\n"
1043 " --enable Immediately enable/disable the portable service\n"
1044 " after attach/detach\n"
1045 " --now Immediately start/stop the portable service after\n"
1046 " attach/before detach\n"
1047 " --no-block Don't block waiting for attach --now to complete\n"
1048 "\nSee the %s for details.\n",
1049 program_invocation_short_name
,
1057 static int parse_argv(int argc
, char *argv
[]) {
1060 ARG_VERSION
= 0x100,
1063 ARG_NO_ASK_PASSWORD
,
1073 static const struct option options
[] = {
1074 { "help", no_argument
, NULL
, 'h' },
1075 { "version", no_argument
, NULL
, ARG_VERSION
},
1076 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1077 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
1078 { "no-ask-password", no_argument
, NULL
, ARG_NO_ASK_PASSWORD
},
1079 { "host", required_argument
, NULL
, 'H' },
1080 { "machine", required_argument
, NULL
, 'M' },
1081 { "quiet", no_argument
, NULL
, 'q' },
1082 { "profile", required_argument
, NULL
, 'p' },
1083 { "copy", required_argument
, NULL
, ARG_COPY
},
1084 { "runtime", no_argument
, NULL
, ARG_RUNTIME
},
1085 { "no-reload", no_argument
, NULL
, ARG_NO_RELOAD
},
1086 { "cat", no_argument
, NULL
, ARG_CAT
},
1087 { "enable", no_argument
, NULL
, ARG_ENABLE
},
1088 { "now", no_argument
, NULL
, ARG_NOW
},
1089 { "no-block", no_argument
, NULL
, ARG_NO_BLOCK
},
1099 c
= getopt_long(argc
, argv
, "hH:M:qp:", options
, NULL
);
1106 return help(0, NULL
, NULL
);
1112 arg_pager_flags
|= PAGER_DISABLE
;
1119 case ARG_NO_ASK_PASSWORD
:
1120 arg_ask_password
= false;
1124 arg_transport
= BUS_TRANSPORT_REMOTE
;
1129 arg_transport
= BUS_TRANSPORT_MACHINE
;
1138 if (streq(optarg
, "help"))
1139 return dump_profiles();
1141 if (!filename_is_valid(optarg
))
1142 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1143 "Unit profile name not valid: %s", optarg
);
1145 arg_profile
= optarg
;
1149 if (streq(optarg
, "auto"))
1150 arg_copy_mode
= NULL
;
1151 else if (STR_IN_SET(optarg
, "copy", "symlink"))
1152 arg_copy_mode
= optarg
;
1153 else if (streq(optarg
, "help")) {
1159 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1160 "Failed to parse --copy= argument: %s", optarg
);
1185 arg_no_block
= true;
1192 assert_not_reached("Unhandled option");
1199 static int run(int argc
, char *argv
[]) {
1200 static const Verb verbs
[] = {
1201 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
1202 { "list", VERB_ANY
, 1, VERB_DEFAULT
, list_images
},
1203 { "attach", 2, VERB_ANY
, 0, attach_image
},
1204 { "detach", 2, VERB_ANY
, 0, detach_image
},
1205 { "inspect", 2, VERB_ANY
, 0, inspect_image
},
1206 { "is-attached", 2, 2, 0, is_image_attached
},
1207 { "read-only", 2, 3, 0, read_only_image
},
1208 { "remove", 2, VERB_ANY
, 0, remove_image
},
1209 { "set-limit", 3, 3, 0, set_limit
},
1210 { "reattach", 2, VERB_ANY
, 0, reattach_image
},
1218 r
= parse_argv(argc
, argv
);
1222 return dispatch_verb(argc
, argv
, verbs
, NULL
);
1225 DEFINE_MAIN_FUNCTION(run
);