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