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