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