1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "alloc-util.h"
9 #include "bus-common-errors.h"
10 #include "bus-get-properties.h"
11 #include "bus-label.h"
12 #include "bus-polkit.h"
14 #include "discover-image.h"
18 #include "missing_capability.h"
20 #include "portabled-bus.h"
21 #include "portabled-image-bus.h"
22 #include "portabled-image.h"
23 #include "portabled.h"
24 #include "process-util.h"
26 #include "user-util.h"
28 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type
, image_type
, ImageType
);
30 int bus_image_common_get_os_release(
32 sd_bus_message
*message
,
33 const char *name_or_path
,
35 sd_bus_error
*error
) {
39 assert(name_or_path
|| image
);
47 r
= bus_image_acquire(m
,
51 BUS_IMAGE_AUTHENTICATE_BY_PATH
,
52 "org.freedesktop.portable1.inspect-images",
57 if (r
== 0) /* Will call us back */
60 if (!image
->metadata_valid
) {
61 r
= image_read_metadata(image
);
63 return sd_bus_error_set_errnof(error
, r
, "Failed to read image metadata: %m");
66 return bus_reply_pair_array(message
, image
->os_release
);
69 static int bus_image_method_get_os_release(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
70 return bus_image_common_get_os_release(NULL
, message
, NULL
, userdata
, error
);
73 static int append_fd(sd_bus_message
*m
, PortableMetadata
*d
) {
74 _cleanup_fclose_
FILE *f
= NULL
;
75 _cleanup_free_
char *buf
= NULL
;
83 f
= take_fdopen(&d
->fd
, "r");
87 r
= read_full_stream(f
, &buf
, &n
);
91 return sd_bus_message_append_array(m
, 'y', buf
, n
);
94 int bus_image_common_get_metadata(
96 sd_bus_message
*message
,
97 const char *name_or_path
,
99 sd_bus_error
*error
) {
101 _cleanup_(portable_metadata_unrefp
) PortableMetadata
*os_release
= NULL
;
102 _cleanup_hashmap_free_ Hashmap
*unit_files
= NULL
;
103 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
104 _cleanup_free_ PortableMetadata
**sorted
= NULL
;
105 _cleanup_strv_free_
char **matches
= NULL
;
109 assert(name_or_path
|| image
);
117 r
= sd_bus_message_read_strv(message
, &matches
);
121 r
= bus_image_acquire(m
,
125 BUS_IMAGE_AUTHENTICATE_BY_PATH
,
126 "org.freedesktop.portable1.inspect-images",
131 if (r
== 0) /* Will call us back */
134 r
= portable_extract(
143 r
= portable_metadata_hashmap_to_sorted_array(unit_files
, &sorted
);
147 r
= sd_bus_message_new_method_return(message
, &reply
);
151 r
= sd_bus_message_append(reply
, "s", image
->path
);
155 r
= append_fd(reply
, os_release
);
159 r
= sd_bus_message_open_container(reply
, 'a', "{say}");
163 for (i
= 0; i
< hashmap_size(unit_files
); i
++) {
165 r
= sd_bus_message_open_container(reply
, 'e', "say");
169 r
= sd_bus_message_append(reply
, "s", sorted
[i
]->name
);
173 r
= append_fd(reply
, sorted
[i
]);
177 r
= sd_bus_message_close_container(reply
);
182 r
= sd_bus_message_close_container(reply
);
186 return sd_bus_send(NULL
, reply
, NULL
);
189 static int bus_image_method_get_metadata(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
190 return bus_image_common_get_metadata(NULL
, message
, NULL
, userdata
, error
);
193 static int bus_image_method_get_state(
194 sd_bus_message
*message
,
196 sd_bus_error
*error
) {
198 Image
*image
= userdata
;
205 r
= portable_get_state(
206 sd_bus_message_get_bus(message
),
214 return sd_bus_reply_method_return(message
, "s", portable_state_to_string(state
));
217 int bus_image_common_attach(
219 sd_bus_message
*message
,
220 const char *name_or_path
,
222 sd_bus_error
*error
) {
224 _cleanup_strv_free_
char **matches
= NULL
;
225 PortableChange
*changes
= NULL
;
226 PortableFlags flags
= 0;
227 const char *profile
, *copy_mode
;
228 size_t n_changes
= 0;
232 assert(name_or_path
|| image
);
239 r
= sd_bus_message_read_strv(message
, &matches
);
243 r
= sd_bus_message_read(message
, "sbs", &profile
, &runtime
, ©_mode
);
247 if (streq(copy_mode
, "symlink"))
248 flags
|= PORTABLE_PREFER_SYMLINK
;
249 else if (streq(copy_mode
, "copy"))
250 flags
|= PORTABLE_PREFER_COPY
;
251 else if (!isempty(copy_mode
))
252 return sd_bus_reply_method_errorf(message
, SD_BUS_ERROR_INVALID_ARGS
, "Unknown copy mode '%s'", copy_mode
);
255 flags
|= PORTABLE_RUNTIME
;
257 r
= bus_image_acquire(m
,
261 BUS_IMAGE_AUTHENTICATE_ALL
,
262 "org.freedesktop.portable1.attach-images",
267 if (r
== 0) /* Will call us back */
271 sd_bus_message_get_bus(message
),
282 r
= reply_portable_changes(message
, changes
, n_changes
);
285 portable_changes_free(changes
, n_changes
);
289 static int bus_image_method_attach(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
290 return bus_image_common_attach(NULL
, message
, NULL
, userdata
, error
);
293 static int bus_image_method_detach(
294 sd_bus_message
*message
,
296 sd_bus_error
*error
) {
298 PortableChange
*changes
= NULL
;
299 Image
*image
= userdata
;
300 Manager
*m
= image
->userdata
;
301 size_t n_changes
= 0;
308 r
= sd_bus_message_read(message
, "b", &runtime
);
312 r
= bus_verify_polkit_async(
315 "org.freedesktop.portable1.attach-images",
324 return 1; /* Will call us back */
327 sd_bus_message_get_bus(message
),
329 runtime
? PORTABLE_RUNTIME
: 0,
336 r
= reply_portable_changes(message
, changes
, n_changes
);
339 portable_changes_free(changes
, n_changes
);
343 int bus_image_common_remove(
345 sd_bus_message
*message
,
346 const char *name_or_path
,
348 sd_bus_error
*error
) {
350 _cleanup_close_pair_
int errno_pipe_fd
[2] = { -1, -1 };
351 _cleanup_(sigkill_waitp
) pid_t child
= 0;
356 assert(name_or_path
|| image
);
363 if (m
->n_operations
>= OPERATIONS_MAX
)
364 return sd_bus_error_setf(error
, SD_BUS_ERROR_LIMITS_EXCEEDED
, "Too many ongoing operations.");
366 r
= bus_image_acquire(m
,
370 BUS_IMAGE_AUTHENTICATE_ALL
,
371 "org.freedesktop.portable1.manage-images",
377 return 1; /* Will call us back */
379 r
= portable_get_state(
380 sd_bus_message_get_bus(message
),
388 if (state
!= PORTABLE_DETACHED
)
389 return sd_bus_error_set_errnof(error
, EBUSY
, "Image '%s' is not detached, refusing.", image
->path
);
391 if (pipe2(errno_pipe_fd
, O_CLOEXEC
|O_NONBLOCK
) < 0)
392 return sd_bus_error_set_errnof(error
, errno
, "Failed to create pipe: %m");
394 r
= safe_fork("(sd-imgrm)", FORK_RESET_SIGNALS
, &child
);
396 return sd_bus_error_set_errnof(error
, r
, "Failed to fork(): %m");
398 errno_pipe_fd
[0] = safe_close(errno_pipe_fd
[0]);
400 r
= image_remove(image
);
402 (void) write(errno_pipe_fd
[1], &r
, sizeof(r
));
409 errno_pipe_fd
[1] = safe_close(errno_pipe_fd
[1]);
411 r
= operation_new(m
, child
, message
, errno_pipe_fd
[0], NULL
);
416 errno_pipe_fd
[0] = -1;
421 static int bus_image_method_remove(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
422 return bus_image_common_remove(NULL
, message
, NULL
, userdata
, error
);
425 /* Given two PortableChange arrays, return a new array that has all elements of the first that are
426 * not also present in the second, comparing the basename of the path values. */
427 static int normalize_portable_changes(
428 const PortableChange
*changes_attached
,
429 size_t n_changes_attached
,
430 const PortableChange
*changes_detached
,
431 size_t n_changes_detached
,
432 PortableChange
**ret_changes
,
433 size_t *ret_n_changes
) {
435 PortableChange
*changes
= NULL
;
436 size_t n_changes
= 0;
439 assert(ret_n_changes
);
442 if (n_changes_detached
== 0)
443 return 0; /* Nothing to do */
445 changes
= new0(PortableChange
, n_changes_attached
+ n_changes_detached
);
449 /* Corner case: only detached, nothing attached */
450 if (n_changes_attached
== 0) {
451 memcpy(changes
, changes_detached
, sizeof(PortableChange
) * n_changes_detached
);
452 *ret_changes
= TAKE_PTR(changes
);
453 *ret_n_changes
= n_changes_detached
;
458 for (size_t i
= 0; i
< n_changes_detached
; ++i
) {
461 for (size_t j
= 0; j
< n_changes_attached
; ++j
)
462 if (streq(basename(changes_detached
[i
].path
), basename(changes_attached
[j
].path
))) {
468 _cleanup_free_
char *path
= NULL
, *source
= NULL
;
470 path
= strdup(changes_detached
[i
].path
);
476 if (changes_detached
[i
].source
) {
477 source
= strdup(changes_detached
[i
].source
);
484 changes
[n_changes
++] = (PortableChange
) {
485 .type
= changes_detached
[i
].type
,
486 .path
= TAKE_PTR(path
),
487 .source
= TAKE_PTR(source
),
492 *ret_n_changes
= n_changes
;
493 *ret_changes
= TAKE_PTR(changes
);
498 portable_changes_free(changes
, n_changes
);
502 int bus_image_common_reattach(
504 sd_bus_message
*message
,
505 const char *name_or_path
,
507 sd_bus_error
*error
) {
509 PortableChange
*changes_detached
= NULL
, *changes_attached
= NULL
, *changes_gone
= NULL
;
510 size_t n_changes_detached
= 0, n_changes_attached
= 0, n_changes_gone
= 0;
511 _cleanup_strv_free_
char **matches
= NULL
;
512 PortableFlags flags
= PORTABLE_REATTACH
;
513 const char *profile
, *copy_mode
;
517 assert(name_or_path
|| image
);
524 r
= sd_bus_message_read_strv(message
, &matches
);
528 r
= sd_bus_message_read(message
, "sbs", &profile
, &runtime
, ©_mode
);
532 if (streq(copy_mode
, "symlink"))
533 flags
|= PORTABLE_PREFER_SYMLINK
;
534 else if (streq(copy_mode
, "copy"))
535 flags
|= PORTABLE_PREFER_COPY
;
536 else if (!isempty(copy_mode
))
537 return sd_bus_reply_method_errorf(message
, SD_BUS_ERROR_INVALID_ARGS
, "Unknown copy mode '%s'", copy_mode
);
540 flags
|= PORTABLE_RUNTIME
;
542 r
= bus_image_acquire(m
,
546 BUS_IMAGE_AUTHENTICATE_ALL
,
547 "org.freedesktop.portable1.attach-images",
552 if (r
== 0) /* Will call us back */
556 sd_bus_message_get_bus(message
),
566 sd_bus_message_get_bus(message
),
577 /* We want to return the list of units really removed by the detach,
578 * and not added again by the attach */
579 r
= normalize_portable_changes(changes_attached
, n_changes_attached
,
580 changes_detached
, n_changes_detached
,
581 &changes_gone
, &n_changes_gone
);
585 /* First, return the units that are gone (so that the caller can stop them)
586 * Then, return the units that are changed/added (so that the caller can
587 * start/restart/enable them) */
588 r
= reply_portable_changes_pair(message
,
589 changes_gone
, n_changes_gone
,
590 changes_attached
, n_changes_attached
);
595 portable_changes_free(changes_detached
, n_changes_detached
);
596 portable_changes_free(changes_attached
, n_changes_attached
);
597 portable_changes_free(changes_gone
, n_changes_gone
);
601 static int bus_image_method_reattach(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
602 return bus_image_common_reattach(NULL
, message
, NULL
, userdata
, error
);
605 int bus_image_common_mark_read_only(
607 sd_bus_message
*message
,
608 const char *name_or_path
,
610 sd_bus_error
*error
) {
615 assert(name_or_path
|| image
);
622 r
= sd_bus_message_read(message
, "b", &read_only
);
626 r
= bus_image_acquire(m
,
630 BUS_IMAGE_AUTHENTICATE_ALL
,
631 "org.freedesktop.portable1.manage-images",
637 return 1; /* Will call us back */
639 r
= image_read_only(image
, read_only
);
643 return sd_bus_reply_method_return(message
, NULL
);
646 static int bus_image_method_mark_read_only(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
647 return bus_image_common_mark_read_only(NULL
, message
, NULL
, userdata
, error
);
650 int bus_image_common_set_limit(
652 sd_bus_message
*message
,
653 const char *name_or_path
,
655 sd_bus_error
*error
) {
661 assert(name_or_path
|| image
);
668 r
= sd_bus_message_read(message
, "t", &limit
);
671 if (!FILE_SIZE_VALID_OR_INFINITY(limit
))
672 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "New limit out of range");
674 r
= bus_image_acquire(m
,
678 BUS_IMAGE_AUTHENTICATE_ALL
,
679 "org.freedesktop.portable1.manage-images",
685 return 1; /* Will call us back */
687 r
= image_set_limit(image
, limit
);
691 return sd_bus_reply_method_return(message
, NULL
);
694 static int bus_image_method_set_limit(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
695 return bus_image_common_set_limit(NULL
, message
, NULL
, userdata
, error
);
698 const sd_bus_vtable image_vtable
[] = {
699 SD_BUS_VTABLE_START(0),
700 SD_BUS_PROPERTY("Name", "s", NULL
, offsetof(Image
, name
), 0),
701 SD_BUS_PROPERTY("Path", "s", NULL
, offsetof(Image
, path
), 0),
702 SD_BUS_PROPERTY("Type", "s", property_get_type
, offsetof(Image
, type
), 0),
703 SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool
, offsetof(Image
, read_only
), 0),
704 SD_BUS_PROPERTY("CreationTimestamp", "t", NULL
, offsetof(Image
, crtime
), 0),
705 SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL
, offsetof(Image
, mtime
), 0),
706 SD_BUS_PROPERTY("Usage", "t", NULL
, offsetof(Image
, usage
), 0),
707 SD_BUS_PROPERTY("Limit", "t", NULL
, offsetof(Image
, limit
), 0),
708 SD_BUS_PROPERTY("UsageExclusive", "t", NULL
, offsetof(Image
, usage_exclusive
), 0),
709 SD_BUS_PROPERTY("LimitExclusive", "t", NULL
, offsetof(Image
, limit_exclusive
), 0),
710 SD_BUS_METHOD("GetOSRelease", NULL
, "a{ss}", bus_image_method_get_os_release
, SD_BUS_VTABLE_UNPRIVILEGED
),
711 SD_BUS_METHOD("GetMetadata", "as", "saya{say}", bus_image_method_get_metadata
, SD_BUS_VTABLE_UNPRIVILEGED
),
712 SD_BUS_METHOD("GetState", NULL
, "s", bus_image_method_get_state
, SD_BUS_VTABLE_UNPRIVILEGED
),
713 SD_BUS_METHOD("Attach", "assbs", "a(sss)", bus_image_method_attach
, SD_BUS_VTABLE_UNPRIVILEGED
),
714 SD_BUS_METHOD("Detach", "b", "a(sss)", bus_image_method_detach
, SD_BUS_VTABLE_UNPRIVILEGED
),
715 SD_BUS_METHOD("Reattach", "assbs", "a(sss)a(sss)", bus_image_method_reattach
, SD_BUS_VTABLE_UNPRIVILEGED
),
716 SD_BUS_METHOD("Remove", NULL
, NULL
, bus_image_method_remove
, SD_BUS_VTABLE_UNPRIVILEGED
),
717 SD_BUS_METHOD("MarkReadOnly", "b", NULL
, bus_image_method_mark_read_only
, SD_BUS_VTABLE_UNPRIVILEGED
),
718 SD_BUS_METHOD("SetLimit", "t", NULL
, bus_image_method_set_limit
, SD_BUS_VTABLE_UNPRIVILEGED
),
722 int bus_image_path(Image
*image
, char **ret
) {
726 if (!image
->discoverable
)
729 return sd_bus_path_encode("/org/freedesktop/portable1/image", image
->name
, ret
);
732 int bus_image_acquire(
734 sd_bus_message
*message
,
735 const char *name_or_path
,
737 ImageAcquireMode mode
,
738 const char *polkit_action
,
740 sd_bus_error
*error
) {
742 _cleanup_(image_unrefp
) Image
*loaded
= NULL
;
748 assert(name_or_path
|| image
);
750 assert(mode
< _BUS_IMAGE_ACQUIRE_MODE_MAX
);
751 assert(polkit_action
|| mode
== BUS_IMAGE_REFUSE_BY_PATH
);
754 /* Acquires an 'Image' object if not acquired yet, and enforces necessary authentication while doing so. */
756 if (mode
== BUS_IMAGE_AUTHENTICATE_ALL
) {
757 r
= bus_verify_polkit_async(
768 if (r
== 0) { /* Will call us back */
774 /* Already passed in? */
780 /* Let's see if this image is already cached? */
781 cached
= manager_image_cache_get(m
, name_or_path
);
787 if (image_name_is_valid(name_or_path
)) {
789 /* If it's a short name, let's search for it */
790 r
= image_find(IMAGE_PORTABLE
, name_or_path
, NULL
, &loaded
);
792 return sd_bus_error_setf(error
, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE
, "No image '%s' found.", name_or_path
);
794 /* other errors are handled below… */
796 /* Don't accept path if this is always forbidden */
797 if (mode
== BUS_IMAGE_REFUSE_BY_PATH
)
798 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Expected image name, not path in place of '%s'.", name_or_path
);
800 if (!path_is_absolute(name_or_path
))
801 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Image name '%s' is not valid or not a valid path.", name_or_path
);
803 if (!path_is_normalized(name_or_path
))
804 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Image path '%s' is not normalized.", name_or_path
);
806 if (mode
== BUS_IMAGE_AUTHENTICATE_BY_PATH
) {
807 r
= bus_verify_polkit_async(
818 if (r
== 0) { /* Will call us back */
824 r
= image_from_path(name_or_path
, &loaded
);
826 if (r
== -EMEDIUMTYPE
) {
827 sd_bus_error_setf(error
, BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE
, "Typ of image '%s' not recognized; supported image types are directories/btrfs subvolumes, block devices, and raw disk image files with suffix '.raw'.", name_or_path
);
833 /* Add what we just loaded to the cache. This has as side-effect that the object stays in memory until the
834 * cache is purged again, i.e. at least for the current event loop iteration, which is all we need, and which
835 * means we don't actually need to ref the return object. */
836 r
= manager_image_cache_add(m
, loaded
);
844 int bus_image_object_find(
847 const char *interface
,
850 sd_bus_error
*error
) {
852 _cleanup_free_
char *e
= NULL
;
853 Manager
*m
= userdata
;
862 r
= sd_bus_path_decode(path
, "/org/freedesktop/portable1/image", &e
);
868 r
= bus_image_acquire(m
, sd_bus_get_current_message(bus
), e
, NULL
, BUS_IMAGE_REFUSE_BY_PATH
, NULL
, &image
, error
);
882 int bus_image_node_enumerator(sd_bus
*bus
, const char *path
, void *userdata
, char ***nodes
, sd_bus_error
*error
) {
883 _cleanup_hashmap_free_ Hashmap
*images
= NULL
;
884 _cleanup_strv_free_
char **l
= NULL
;
885 size_t n_allocated
= 0, n
= 0;
886 Manager
*m
= userdata
;
894 images
= hashmap_new(&image_hash_ops
);
898 r
= manager_image_cache_discover(m
, images
, error
);
902 HASHMAP_FOREACH(image
, images
) {
905 r
= bus_image_path(image
, &p
);
909 if (!GREEDY_REALLOC(l
, n_allocated
, n
+2)) {
918 *nodes
= TAKE_PTR(l
);