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