]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/portable/portabled-image-bus.c
tree-wide: sd_bus_error_setf → set_bus_error_set
[thirdparty/systemd.git] / src / portable / portabled-image-bus.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <sys/stat.h>
5 #include <sys/types.h>
6 #include <unistd.h>
7
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-object.h"
13 #include "bus-polkit.h"
14 #include "bus-util.h"
15 #include "discover-image.h"
16 #include "fd-util.h"
17 #include "fileio.h"
18 #include "io-util.h"
19 #include "missing_capability.h"
20 #include "os-util.h"
21 #include "portable.h"
22 #include "portabled-bus.h"
23 #include "portabled-image-bus.h"
24 #include "portabled-image.h"
25 #include "portabled.h"
26 #include "process-util.h"
27 #include "strv.h"
28 #include "user-util.h"
29
30 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType);
31
32 int bus_image_common_get_os_release(
33 Manager *m,
34 sd_bus_message *message,
35 const char *name_or_path,
36 Image *image,
37 sd_bus_error *error) {
38
39 int r;
40
41 assert(name_or_path || image);
42 assert(message);
43
44 if (!m) {
45 assert(image);
46 m = image->userdata;
47 }
48
49 r = bus_image_acquire(m,
50 message,
51 name_or_path,
52 image,
53 BUS_IMAGE_AUTHENTICATE_BY_PATH,
54 "org.freedesktop.portable1.inspect-images",
55 &image,
56 error);
57 if (r < 0)
58 return r;
59 if (r == 0) /* Will call us back */
60 return 1;
61
62 if (!image->metadata_valid) {
63 r = image_read_metadata(image);
64 if (r < 0)
65 return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
66 }
67
68 return bus_reply_pair_array(message, image->os_release);
69 }
70
71 static int bus_image_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
72 return bus_image_common_get_os_release(NULL, message, NULL, userdata, error);
73 }
74
75 static int append_fd(sd_bus_message *m, PortableMetadata *d) {
76 _cleanup_fclose_ FILE *f = NULL;
77 _cleanup_free_ char *buf = NULL;
78 size_t n = 0;
79 int r;
80
81 assert(m);
82
83 if (d) {
84 assert(d->fd >= 0);
85
86 f = take_fdopen(&d->fd, "r");
87 if (!f)
88 return -errno;
89
90 r = read_full_stream(f, &buf, &n);
91 if (r < 0)
92 return r;
93 }
94
95 return sd_bus_message_append_array(m, 'y', buf, n);
96 }
97
98 int bus_image_common_get_metadata(
99 Manager *m,
100 sd_bus_message *message,
101 const char *name_or_path,
102 Image *image,
103 sd_bus_error *error) {
104
105 _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
106 _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL;
107 _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
108 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
109 _cleanup_free_ PortableMetadata **sorted = NULL;
110 /* Unused for now, but added to the DBUS methods for future-proofing */
111 uint64_t input_flags = 0;
112 size_t i;
113 int r;
114
115 assert(name_or_path || image);
116 assert(message);
117
118 if (!m) {
119 assert(image);
120 m = image->userdata;
121 }
122
123 if (sd_bus_message_is_method_call(message, NULL, "GetImageMetadataWithExtensions") ||
124 sd_bus_message_is_method_call(message, NULL, "GetMetadataWithExtensions")) {
125 r = sd_bus_message_read_strv(message, &extension_images);
126 if (r < 0)
127 return r;
128 }
129
130 r = sd_bus_message_read_strv(message, &matches);
131 if (r < 0)
132 return r;
133
134 if (sd_bus_message_is_method_call(message, NULL, "GetImageMetadataWithExtensions") ||
135 sd_bus_message_is_method_call(message, NULL, "GetMetadataWithExtensions")) {
136 r = sd_bus_message_read(message, "t", &input_flags);
137 if (r < 0)
138 return r;
139 /* Let clients know that this version doesn't support any flags */
140 if (input_flags != 0)
141 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS,
142 "Invalid 'flags' parameter '%" PRIu64 "'",
143 input_flags);
144 }
145
146 r = bus_image_acquire(m,
147 message,
148 name_or_path,
149 image,
150 BUS_IMAGE_AUTHENTICATE_BY_PATH,
151 "org.freedesktop.portable1.inspect-images",
152 &image,
153 error);
154 if (r < 0)
155 return r;
156 if (r == 0) /* Will call us back */
157 return 1;
158
159 r = portable_extract(
160 image->path,
161 matches,
162 extension_images,
163 &os_release,
164 &unit_files,
165 error);
166 if (r < 0)
167 return r;
168
169 r = portable_metadata_hashmap_to_sorted_array(unit_files, &sorted);
170 if (r < 0)
171 return r;
172
173 r = sd_bus_message_new_method_return(message, &reply);
174 if (r < 0)
175 return r;
176
177 r = sd_bus_message_append(reply, "s", image->path);
178 if (r < 0)
179 return r;
180
181 r = append_fd(reply, os_release);
182 if (r < 0)
183 return r;
184
185 r = sd_bus_message_open_container(reply, 'a', "{say}");
186 if (r < 0)
187 return r;
188
189 for (i = 0; i < hashmap_size(unit_files); i++) {
190
191 r = sd_bus_message_open_container(reply, 'e', "say");
192 if (r < 0)
193 return r;
194
195 r = sd_bus_message_append(reply, "s", sorted[i]->name);
196 if (r < 0)
197 return r;
198
199 r = append_fd(reply, sorted[i]);
200 if (r < 0)
201 return r;
202
203 r = sd_bus_message_close_container(reply);
204 if (r < 0)
205 return r;
206 }
207
208 r = sd_bus_message_close_container(reply);
209 if (r < 0)
210 return r;
211
212 return sd_bus_send(NULL, reply, NULL);
213 }
214
215 static int bus_image_method_get_metadata(sd_bus_message *message, void *userdata, sd_bus_error *error) {
216 return bus_image_common_get_metadata(NULL, message, NULL, userdata, error);
217 }
218
219 static int bus_image_method_get_state(
220 sd_bus_message *message,
221 void *userdata,
222 sd_bus_error *error) {
223
224 Image *image = userdata;
225 PortableState state;
226 int r;
227
228 assert(message);
229 assert(image);
230
231 r = portable_get_state(
232 sd_bus_message_get_bus(message),
233 image->path,
234 0,
235 &state,
236 error);
237 if (r < 0)
238 return r;
239
240 return sd_bus_reply_method_return(message, "s", portable_state_to_string(state));
241 }
242
243 int bus_image_common_attach(
244 Manager *m,
245 sd_bus_message *message,
246 const char *name_or_path,
247 Image *image,
248 sd_bus_error *error) {
249
250 _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL;
251 PortableChange *changes = NULL;
252 PortableFlags flags = 0;
253 const char *profile, *copy_mode;
254 size_t n_changes = 0;
255 int r;
256
257 assert(message);
258 assert(name_or_path || image);
259
260 if (!m) {
261 assert(image);
262 m = image->userdata;
263 }
264
265 if (sd_bus_message_is_method_call(message, NULL, "AttachImageWithExtensions") ||
266 sd_bus_message_is_method_call(message, NULL, "AttachWithExtensions")) {
267 r = sd_bus_message_read_strv(message, &extension_images);
268 if (r < 0)
269 return r;
270 }
271
272 r = sd_bus_message_read_strv(message, &matches);
273 if (r < 0)
274 return r;
275
276 r = sd_bus_message_read(message, "s", &profile);
277 if (r < 0)
278 return r;
279
280 if (sd_bus_message_is_method_call(message, NULL, "AttachImageWithExtensions") ||
281 sd_bus_message_is_method_call(message, NULL, "AttachWithExtensions")) {
282 uint64_t input_flags = 0;
283
284 r = sd_bus_message_read(message, "st", &copy_mode, &input_flags);
285 if (r < 0)
286 return r;
287 if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0)
288 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS,
289 "Invalid 'flags' parameter '%" PRIu64 "'",
290 input_flags);
291 flags |= input_flags;
292 } else {
293 int runtime;
294
295 r = sd_bus_message_read(message, "bs", &runtime, &copy_mode);
296 if (r < 0)
297 return r;
298
299 if (runtime)
300 flags |= PORTABLE_RUNTIME;
301 }
302
303 if (streq(copy_mode, "symlink"))
304 flags |= PORTABLE_PREFER_SYMLINK;
305 else if (streq(copy_mode, "copy"))
306 flags |= PORTABLE_PREFER_COPY;
307 else if (!isempty(copy_mode))
308 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
309
310 r = bus_image_acquire(m,
311 message,
312 name_or_path,
313 image,
314 BUS_IMAGE_AUTHENTICATE_ALL,
315 "org.freedesktop.portable1.attach-images",
316 &image,
317 error);
318 if (r < 0)
319 return r;
320 if (r == 0) /* Will call us back */
321 return 1;
322
323 r = portable_attach(
324 sd_bus_message_get_bus(message),
325 image->path,
326 matches,
327 profile,
328 extension_images,
329 flags,
330 &changes,
331 &n_changes,
332 error);
333 if (r < 0)
334 goto finish;
335
336 r = reply_portable_changes(message, changes, n_changes);
337
338 finish:
339 portable_changes_free(changes, n_changes);
340 return r;
341 }
342
343 static int bus_image_method_attach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
344 return bus_image_common_attach(NULL, message, NULL, userdata, error);
345 }
346
347 static int bus_image_method_detach(
348 sd_bus_message *message,
349 void *userdata,
350 sd_bus_error *error) {
351
352 _cleanup_strv_free_ char **extension_images = NULL;
353 PortableChange *changes = NULL;
354 Image *image = userdata;
355 Manager *m = image->userdata;
356 PortableFlags flags = 0;
357 size_t n_changes = 0;
358 int r;
359
360 assert(message);
361 assert(image);
362 assert(m);
363
364 if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) {
365 r = sd_bus_message_read_strv(message, &extension_images);
366 if (r < 0)
367 return r;
368 }
369
370 if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) {
371 uint64_t input_flags = 0;
372
373 r = sd_bus_message_read(message, "t", &input_flags);
374 if (r < 0)
375 return r;
376
377 if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0)
378 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS,
379 "Invalid 'flags' parameter '%" PRIu64 "'",
380 input_flags);
381 flags |= input_flags;
382 } else {
383 int runtime;
384
385 r = sd_bus_message_read(message, "b", &runtime);
386 if (r < 0)
387 return r;
388
389 if (runtime)
390 flags |= PORTABLE_RUNTIME;
391 }
392
393 r = bus_verify_polkit_async(
394 message,
395 CAP_SYS_ADMIN,
396 "org.freedesktop.portable1.attach-images",
397 NULL,
398 false,
399 UID_INVALID,
400 &m->polkit_registry,
401 error);
402 if (r < 0)
403 return r;
404 if (r == 0)
405 return 1; /* Will call us back */
406
407 r = portable_detach(
408 sd_bus_message_get_bus(message),
409 image->path,
410 extension_images,
411 flags,
412 &changes,
413 &n_changes,
414 error);
415 if (r < 0)
416 goto finish;
417
418 r = reply_portable_changes(message, changes, n_changes);
419
420 finish:
421 portable_changes_free(changes, n_changes);
422 return r;
423 }
424
425 int bus_image_common_remove(
426 Manager *m,
427 sd_bus_message *message,
428 const char *name_or_path,
429 Image *image,
430 sd_bus_error *error) {
431
432 _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
433 _cleanup_(sigkill_waitp) pid_t child = 0;
434 PortableState state;
435 int r;
436
437 assert(message);
438 assert(name_or_path || image);
439
440 if (!m) {
441 assert(image);
442 m = image->userdata;
443 }
444
445 if (m->n_operations >= OPERATIONS_MAX)
446 return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
447
448 r = bus_image_acquire(m,
449 message,
450 name_or_path,
451 image,
452 BUS_IMAGE_AUTHENTICATE_ALL,
453 "org.freedesktop.portable1.manage-images",
454 &image,
455 error);
456 if (r < 0)
457 return r;
458 if (r == 0)
459 return 1; /* Will call us back */
460
461 r = portable_get_state(
462 sd_bus_message_get_bus(message),
463 image->path,
464 0,
465 &state,
466 error);
467 if (r < 0)
468 return r;
469
470 if (state != PORTABLE_DETACHED)
471 return sd_bus_error_set_errnof(error, EBUSY, "Image '%s' is not detached, refusing.", image->path);
472
473 if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
474 return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
475
476 r = safe_fork("(sd-imgrm)", FORK_RESET_SIGNALS, &child);
477 if (r < 0)
478 return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m");
479 if (r == 0) {
480 errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
481
482 r = image_remove(image);
483 if (r < 0) {
484 (void) write(errno_pipe_fd[1], &r, sizeof(r));
485 _exit(EXIT_FAILURE);
486 }
487
488 _exit(EXIT_SUCCESS);
489 }
490
491 errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
492
493 r = operation_new(m, child, message, errno_pipe_fd[0], NULL);
494 if (r < 0)
495 return r;
496
497 child = 0;
498 errno_pipe_fd[0] = -1;
499
500 return 1;
501 }
502
503 static int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error) {
504 return bus_image_common_remove(NULL, message, NULL, userdata, error);
505 }
506
507 /* Given two PortableChange arrays, return a new array that has all elements of the first that are
508 * not also present in the second, comparing the basename of the path values. */
509 static int normalize_portable_changes(
510 const PortableChange *changes_attached,
511 size_t n_changes_attached,
512 const PortableChange *changes_detached,
513 size_t n_changes_detached,
514 PortableChange **ret_changes,
515 size_t *ret_n_changes) {
516
517 PortableChange *changes = NULL;
518 size_t n_changes = 0;
519 int r = 0;
520
521 assert(ret_n_changes);
522 assert(ret_changes);
523
524 if (n_changes_detached == 0)
525 return 0; /* Nothing to do */
526
527 changes = new0(PortableChange, n_changes_attached + n_changes_detached);
528 if (!changes)
529 return -ENOMEM;
530
531 /* Corner case: only detached, nothing attached */
532 if (n_changes_attached == 0) {
533 memcpy(changes, changes_detached, sizeof(PortableChange) * n_changes_detached);
534 *ret_changes = TAKE_PTR(changes);
535 *ret_n_changes = n_changes_detached;
536
537 return 0;
538 }
539
540 for (size_t i = 0; i < n_changes_detached; ++i) {
541 bool found = false;
542
543 for (size_t j = 0; j < n_changes_attached; ++j)
544 if (streq(basename(changes_detached[i].path), basename(changes_attached[j].path))) {
545 found = true;
546 break;
547 }
548
549 if (!found) {
550 _cleanup_free_ char *path = NULL, *source = NULL;
551
552 path = strdup(changes_detached[i].path);
553 if (!path) {
554 r = -ENOMEM;
555 goto fail;
556 }
557
558 if (changes_detached[i].source) {
559 source = strdup(changes_detached[i].source);
560 if (!source) {
561 r = -ENOMEM;
562 goto fail;
563 }
564 }
565
566 changes[n_changes++] = (PortableChange) {
567 .type_or_errno = changes_detached[i].type_or_errno,
568 .path = TAKE_PTR(path),
569 .source = TAKE_PTR(source),
570 };
571 }
572 }
573
574 *ret_n_changes = n_changes;
575 *ret_changes = TAKE_PTR(changes);
576
577 return 0;
578
579 fail:
580 portable_changes_free(changes, n_changes);
581 return r;
582 }
583
584 int bus_image_common_reattach(
585 Manager *m,
586 sd_bus_message *message,
587 const char *name_or_path,
588 Image *image,
589 sd_bus_error *error) {
590
591 PortableChange *changes_detached = NULL, *changes_attached = NULL, *changes_gone = NULL;
592 size_t n_changes_detached = 0, n_changes_attached = 0, n_changes_gone = 0;
593 _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL;
594 PortableFlags flags = PORTABLE_REATTACH;
595 const char *profile, *copy_mode;
596 int r;
597
598 assert(message);
599 assert(name_or_path || image);
600
601 if (!m) {
602 assert(image);
603 m = image->userdata;
604 }
605
606 if (sd_bus_message_is_method_call(message, NULL, "ReattachImageWithExtensions") ||
607 sd_bus_message_is_method_call(message, NULL, "ReattachWithExtensions")) {
608 r = sd_bus_message_read_strv(message, &extension_images);
609 if (r < 0)
610 return r;
611 }
612
613 r = sd_bus_message_read_strv(message, &matches);
614 if (r < 0)
615 return r;
616
617 r = sd_bus_message_read(message, "s", &profile);
618 if (r < 0)
619 return r;
620
621 if (sd_bus_message_is_method_call(message, NULL, "ReattachImageWithExtensions") ||
622 sd_bus_message_is_method_call(message, NULL, "ReattachWithExtensions")) {
623 uint64_t input_flags = 0;
624
625 r = sd_bus_message_read(message, "st", &copy_mode, &input_flags);
626 if (r < 0)
627 return r;
628
629 if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0)
630 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS,
631 "Invalid 'flags' parameter '%" PRIu64 "'",
632 input_flags);
633 flags |= input_flags;
634 } else {
635 int runtime;
636
637 r = sd_bus_message_read(message, "bs", &runtime, &copy_mode);
638 if (r < 0)
639 return r;
640
641 if (runtime)
642 flags |= PORTABLE_RUNTIME;
643 }
644
645 if (streq(copy_mode, "symlink"))
646 flags |= PORTABLE_PREFER_SYMLINK;
647 else if (streq(copy_mode, "copy"))
648 flags |= PORTABLE_PREFER_COPY;
649 else if (!isempty(copy_mode))
650 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
651
652 r = bus_image_acquire(m,
653 message,
654 name_or_path,
655 image,
656 BUS_IMAGE_AUTHENTICATE_ALL,
657 "org.freedesktop.portable1.attach-images",
658 &image,
659 error);
660 if (r < 0)
661 return r;
662 if (r == 0) /* Will call us back */
663 return 1;
664
665 r = portable_detach(
666 sd_bus_message_get_bus(message),
667 image->path,
668 extension_images,
669 flags,
670 &changes_detached,
671 &n_changes_detached,
672 error);
673 if (r < 0)
674 goto finish;
675
676 r = portable_attach(
677 sd_bus_message_get_bus(message),
678 image->path,
679 matches,
680 profile,
681 extension_images,
682 flags,
683 &changes_attached,
684 &n_changes_attached,
685 error);
686 if (r < 0)
687 goto finish;
688
689 /* We want to return the list of units really removed by the detach,
690 * and not added again by the attach */
691 r = normalize_portable_changes(changes_attached, n_changes_attached,
692 changes_detached, n_changes_detached,
693 &changes_gone, &n_changes_gone);
694 if (r < 0)
695 goto finish;
696
697 /* First, return the units that are gone (so that the caller can stop them)
698 * Then, return the units that are changed/added (so that the caller can
699 * start/restart/enable them) */
700 r = reply_portable_changes_pair(message,
701 changes_gone, n_changes_gone,
702 changes_attached, n_changes_attached);
703 if (r < 0)
704 goto finish;
705
706 finish:
707 portable_changes_free(changes_detached, n_changes_detached);
708 portable_changes_free(changes_attached, n_changes_attached);
709 portable_changes_free(changes_gone, n_changes_gone);
710 return r;
711 }
712
713 static int bus_image_method_reattach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
714 return bus_image_common_reattach(NULL, message, NULL, userdata, error);
715 }
716
717 int bus_image_common_mark_read_only(
718 Manager *m,
719 sd_bus_message *message,
720 const char *name_or_path,
721 Image *image,
722 sd_bus_error *error) {
723
724 int r, read_only;
725
726 assert(message);
727 assert(name_or_path || image);
728
729 if (!m) {
730 assert(image);
731 m = image->userdata;
732 }
733
734 r = sd_bus_message_read(message, "b", &read_only);
735 if (r < 0)
736 return r;
737
738 r = bus_image_acquire(m,
739 message,
740 name_or_path,
741 image,
742 BUS_IMAGE_AUTHENTICATE_ALL,
743 "org.freedesktop.portable1.manage-images",
744 &image,
745 error);
746 if (r < 0)
747 return r;
748 if (r == 0)
749 return 1; /* Will call us back */
750
751 r = image_read_only(image, read_only);
752 if (r < 0)
753 return r;
754
755 return sd_bus_reply_method_return(message, NULL);
756 }
757
758 static int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) {
759 return bus_image_common_mark_read_only(NULL, message, NULL, userdata, error);
760 }
761
762 int bus_image_common_set_limit(
763 Manager *m,
764 sd_bus_message *message,
765 const char *name_or_path,
766 Image *image,
767 sd_bus_error *error) {
768
769 uint64_t limit;
770 int r;
771
772 assert(message);
773 assert(name_or_path || image);
774
775 if (!m) {
776 assert(image);
777 m = image->userdata;
778 }
779
780 r = sd_bus_message_read(message, "t", &limit);
781 if (r < 0)
782 return r;
783 if (!FILE_SIZE_VALID_OR_INFINITY(limit))
784 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
785
786 r = bus_image_acquire(m,
787 message,
788 name_or_path,
789 image,
790 BUS_IMAGE_AUTHENTICATE_ALL,
791 "org.freedesktop.portable1.manage-images",
792 &image,
793 error);
794 if (r < 0)
795 return r;
796 if (r == 0)
797 return 1; /* Will call us back */
798
799 r = image_set_limit(image, limit);
800 if (r < 0)
801 return r;
802
803 return sd_bus_reply_method_return(message, NULL);
804 }
805
806 static int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
807 return bus_image_common_set_limit(NULL, message, NULL, userdata, error);
808 }
809
810 const sd_bus_vtable image_vtable[] = {
811 SD_BUS_VTABLE_START(0),
812 SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0),
813 SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0),
814 SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0),
815 SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0),
816 SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0),
817 SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0),
818 SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0),
819 SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0),
820 SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0),
821 SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0),
822 SD_BUS_METHOD_WITH_ARGS("GetOSRelease",
823 SD_BUS_NO_ARGS,
824 SD_BUS_RESULT("a{ss}", os_release),
825 bus_image_method_get_os_release,
826 SD_BUS_VTABLE_UNPRIVILEGED),
827 SD_BUS_METHOD_WITH_ARGS("GetMetadata",
828 SD_BUS_ARGS("as", matches),
829 SD_BUS_RESULT("s", image,
830 "ay", os_release,
831 "a{say}", units),
832 bus_image_method_get_metadata,
833 SD_BUS_VTABLE_UNPRIVILEGED),
834 SD_BUS_METHOD_WITH_ARGS("GetMetadataWithExtensions",
835 SD_BUS_ARGS("as", extensions,
836 "as", matches,
837 "t", flags),
838 SD_BUS_RESULT("s", image,
839 "ay", os_release,
840 "a{say}", units),
841 bus_image_method_get_metadata,
842 SD_BUS_VTABLE_UNPRIVILEGED),
843 SD_BUS_METHOD_WITH_ARGS("GetState",
844 SD_BUS_NO_ARGS,
845 SD_BUS_RESULT("s", state),
846 bus_image_method_get_state,
847 SD_BUS_VTABLE_UNPRIVILEGED),
848 SD_BUS_METHOD_WITH_ARGS("Attach",
849 SD_BUS_ARGS("as", matches,
850 "s", profile,
851 "b", runtime,
852 "s", copy_mode),
853 SD_BUS_RESULT("a(sss)", changes),
854 bus_image_method_attach,
855 SD_BUS_VTABLE_UNPRIVILEGED),
856 SD_BUS_METHOD_WITH_ARGS("AttachWithExtensions",
857 SD_BUS_ARGS("as", extensions,
858 "as", matches,
859 "s", profile,
860 "s", copy_mode,
861 "t", flags),
862 SD_BUS_RESULT("a(sss)", changes),
863 bus_image_method_attach,
864 SD_BUS_VTABLE_UNPRIVILEGED),
865 SD_BUS_METHOD_WITH_ARGS("Detach",
866 SD_BUS_ARGS("b", runtime),
867 SD_BUS_RESULT("a(sss)", changes),
868 bus_image_method_detach,
869 SD_BUS_VTABLE_UNPRIVILEGED),
870 SD_BUS_METHOD_WITH_ARGS("DetachWithExtensions",
871 SD_BUS_ARGS("as", extensions,
872 "t", flags),
873 SD_BUS_RESULT("a(sss)", changes),
874 bus_image_method_detach,
875 SD_BUS_VTABLE_UNPRIVILEGED),
876 SD_BUS_METHOD_WITH_ARGS("Reattach",
877 SD_BUS_ARGS("as", matches,
878 "s", profile,
879 "b", runtime,
880 "s", copy_mode),
881 SD_BUS_RESULT("a(sss)", changes_removed,
882 "a(sss)", changes_updated),
883 bus_image_method_reattach,
884 SD_BUS_VTABLE_UNPRIVILEGED),
885 SD_BUS_METHOD_WITH_ARGS("ReattacheWithExtensions",
886 SD_BUS_ARGS("as", extensions,
887 "as", matches,
888 "s", profile,
889 "s", copy_mode,
890 "t", flags),
891 SD_BUS_RESULT("a(sss)", changes_removed,
892 "a(sss)", changes_updated),
893 bus_image_method_reattach,
894 SD_BUS_VTABLE_UNPRIVILEGED),
895 SD_BUS_METHOD_WITH_ARGS("Remove",
896 SD_BUS_NO_ARGS,
897 SD_BUS_NO_RESULT,
898 bus_image_method_remove,
899 SD_BUS_VTABLE_UNPRIVILEGED),
900 SD_BUS_METHOD_WITH_ARGS("MarkReadOnly",
901 SD_BUS_ARGS("b", read_only),
902 SD_BUS_NO_RESULT,
903 bus_image_method_mark_read_only,
904 SD_BUS_VTABLE_UNPRIVILEGED),
905 SD_BUS_METHOD_WITH_ARGS("SetLimit",
906 SD_BUS_ARGS("t", limit),
907 SD_BUS_NO_RESULT,
908 bus_image_method_set_limit,
909 SD_BUS_VTABLE_UNPRIVILEGED),
910 SD_BUS_VTABLE_END
911 };
912
913 int bus_image_path(Image *image, char **ret) {
914 assert(image);
915 assert(ret);
916
917 if (!image->discoverable)
918 return -EINVAL;
919
920 return sd_bus_path_encode("/org/freedesktop/portable1/image", image->name, ret);
921 }
922
923 int bus_image_acquire(
924 Manager *m,
925 sd_bus_message *message,
926 const char *name_or_path,
927 Image *image,
928 ImageAcquireMode mode,
929 const char *polkit_action,
930 Image **ret,
931 sd_bus_error *error) {
932
933 _cleanup_(image_unrefp) Image *loaded = NULL;
934 Image *cached;
935 int r;
936
937 assert(m);
938 assert(message);
939 assert(name_or_path || image);
940 assert(mode >= 0);
941 assert(mode < _BUS_IMAGE_ACQUIRE_MODE_MAX);
942 assert(polkit_action || mode == BUS_IMAGE_REFUSE_BY_PATH);
943 assert(ret);
944
945 /* Acquires an 'Image' object if not acquired yet, and enforces necessary authentication while doing so. */
946
947 if (mode == BUS_IMAGE_AUTHENTICATE_ALL) {
948 r = bus_verify_polkit_async(
949 message,
950 CAP_SYS_ADMIN,
951 polkit_action,
952 NULL,
953 false,
954 UID_INVALID,
955 &m->polkit_registry,
956 error);
957 if (r < 0)
958 return r;
959 if (r == 0) { /* Will call us back */
960 *ret = NULL;
961 return 0;
962 }
963 }
964
965 /* Already passed in? */
966 if (image) {
967 *ret = image;
968 return 1;
969 }
970
971 /* Let's see if this image is already cached? */
972 cached = manager_image_cache_get(m, name_or_path);
973 if (cached) {
974 *ret = cached;
975 return 1;
976 }
977
978 if (image_name_is_valid(name_or_path)) {
979
980 /* If it's a short name, let's search for it */
981 r = image_find(IMAGE_PORTABLE, name_or_path, NULL, &loaded);
982 if (r == -ENOENT)
983 return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE, "No image '%s' found.", name_or_path);
984
985 /* other errors are handled below… */
986 } else {
987 /* Don't accept path if this is always forbidden */
988 if (mode == BUS_IMAGE_REFUSE_BY_PATH)
989 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Expected image name, not path in place of '%s'.", name_or_path);
990
991 if (!path_is_absolute(name_or_path))
992 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);
993
994 if (!path_is_normalized(name_or_path))
995 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image path '%s' is not normalized.", name_or_path);
996
997 if (mode == BUS_IMAGE_AUTHENTICATE_BY_PATH) {
998 r = bus_verify_polkit_async(
999 message,
1000 CAP_SYS_ADMIN,
1001 polkit_action,
1002 NULL,
1003 false,
1004 UID_INVALID,
1005 &m->polkit_registry,
1006 error);
1007 if (r < 0)
1008 return r;
1009 if (r == 0) { /* Will call us back */
1010 *ret = NULL;
1011 return 0;
1012 }
1013 }
1014
1015 r = image_from_path(name_or_path, &loaded);
1016 }
1017 if (r == -EMEDIUMTYPE) {
1018 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);
1019 return r;
1020 }
1021 if (r < 0)
1022 return r;
1023
1024 /* Add what we just loaded to the cache. This has as side-effect that the object stays in memory until the
1025 * cache is purged again, i.e. at least for the current event loop iteration, which is all we need, and which
1026 * means we don't actually need to ref the return object. */
1027 r = manager_image_cache_add(m, loaded);
1028 if (r < 0)
1029 return r;
1030
1031 *ret = loaded;
1032 return 1;
1033 }
1034
1035 int bus_image_object_find(
1036 sd_bus *bus,
1037 const char *path,
1038 const char *interface,
1039 void *userdata,
1040 void **found,
1041 sd_bus_error *error) {
1042
1043 _cleanup_free_ char *e = NULL;
1044 Manager *m = userdata;
1045 Image *image = NULL;
1046 int r;
1047
1048 assert(bus);
1049 assert(path);
1050 assert(interface);
1051 assert(found);
1052
1053 r = sd_bus_path_decode(path, "/org/freedesktop/portable1/image", &e);
1054 if (r < 0)
1055 return 0;
1056 if (r == 0)
1057 goto not_found;
1058
1059 r = bus_image_acquire(m, sd_bus_get_current_message(bus), e, NULL, BUS_IMAGE_REFUSE_BY_PATH, NULL, &image, error);
1060 if (r == -ENOENT)
1061 goto not_found;
1062 if (r < 0)
1063 return r;
1064
1065 *found = image;
1066 return 1;
1067
1068 not_found:
1069 *found = NULL;
1070 return 0;
1071 }
1072
1073 int bus_image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
1074 _cleanup_hashmap_free_ Hashmap *images = NULL;
1075 _cleanup_strv_free_ char **l = NULL;
1076 size_t n_allocated = 0, n = 0;
1077 Manager *m = userdata;
1078 Image *image;
1079 int r;
1080
1081 assert(bus);
1082 assert(path);
1083 assert(nodes);
1084
1085 images = hashmap_new(&image_hash_ops);
1086 if (!images)
1087 return -ENOMEM;
1088
1089 r = manager_image_cache_discover(m, images, error);
1090 if (r < 0)
1091 return r;
1092
1093 HASHMAP_FOREACH(image, images) {
1094 char *p;
1095
1096 r = bus_image_path(image, &p);
1097 if (r < 0)
1098 return r;
1099
1100 if (!GREEDY_REALLOC(l, n_allocated, n+2)) {
1101 free(p);
1102 return -ENOMEM;
1103 }
1104
1105 l[n++] = p;
1106 l[n] = NULL;
1107 }
1108
1109 *nodes = TAKE_PTR(l);
1110
1111 return 1;
1112 }
1113
1114 const BusObjectImplementation image_object = {
1115 "/org/freedesktop/portable1/image",
1116 "org.freedesktop.portable1.Image",
1117 .fallback_vtables = BUS_FALLBACK_VTABLES({image_vtable, bus_image_object_find}),
1118 .node_enumerator = bus_image_node_enumerator,
1119 };