]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/portable/portabled-image-bus.c
6c4cb6ec9de8acf96011a71a1a5326a92730f294
[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] = PIPE_EBADF;
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] = -EBADF;
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
578 assert(ret_n_changes);
579 assert(ret_changes);
580
581 if (n_changes_detached == 0)
582 return 0; /* Nothing to do */
583
584 changes = new0(PortableChange, n_changes_attached + n_changes_detached);
585 if (!changes)
586 return -ENOMEM;
587
588 CLEANUP_ARRAY(changes, n_changes, portable_changes_free);
589
590 /* Corner case: only detached, nothing attached */
591 if (n_changes_attached == 0) {
592 memcpy(changes, changes_detached, sizeof(PortableChange) * n_changes_detached);
593 *ret_changes = TAKE_PTR(changes);
594 *ret_n_changes = n_changes_detached;
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 return -ENOMEM;
613
614 if (changes_detached[i].source) {
615 source = strdup(changes_detached[i].source);
616 if (!source)
617 return -ENOMEM;
618 }
619
620 changes[n_changes++] = (PortableChange) {
621 .type_or_errno = changes_detached[i].type_or_errno,
622 .path = TAKE_PTR(path),
623 .source = TAKE_PTR(source),
624 };
625 }
626 }
627
628 *ret_n_changes = n_changes;
629 *ret_changes = TAKE_PTR(changes);
630
631 return 0;
632 }
633
634 int bus_image_common_reattach(
635 Manager *m,
636 sd_bus_message *message,
637 const char *name_or_path,
638 Image *image,
639 sd_bus_error *error) {
640
641 PortableChange *changes_detached = NULL, *changes_attached = NULL, *changes_gone = NULL;
642 size_t n_changes_detached = 0, n_changes_attached = 0, n_changes_gone = 0;
643 _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL;
644 PortableFlags flags = PORTABLE_REATTACH;
645 const char *profile, *copy_mode;
646 int r;
647
648 assert(message);
649 assert(name_or_path || image);
650
651 if (!m) {
652 assert(image);
653 m = image->userdata;
654 }
655
656 if (sd_bus_message_is_method_call(message, NULL, "ReattachImageWithExtensions") ||
657 sd_bus_message_is_method_call(message, NULL, "ReattachWithExtensions")) {
658 r = sd_bus_message_read_strv(message, &extension_images);
659 if (r < 0)
660 return r;
661 }
662
663 r = sd_bus_message_read_strv(message, &matches);
664 if (r < 0)
665 return r;
666
667 r = sd_bus_message_read(message, "s", &profile);
668 if (r < 0)
669 return r;
670
671 if (sd_bus_message_is_method_call(message, NULL, "ReattachImageWithExtensions") ||
672 sd_bus_message_is_method_call(message, NULL, "ReattachWithExtensions")) {
673 uint64_t input_flags = 0;
674
675 r = sd_bus_message_read(message, "st", &copy_mode, &input_flags);
676 if (r < 0)
677 return r;
678
679 if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0)
680 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS,
681 "Invalid 'flags' parameter '%" PRIu64 "'",
682 input_flags);
683 flags |= input_flags;
684 } else {
685 int runtime;
686
687 r = sd_bus_message_read(message, "bs", &runtime, &copy_mode);
688 if (r < 0)
689 return r;
690
691 if (runtime)
692 flags |= PORTABLE_RUNTIME;
693 }
694
695 if (streq(copy_mode, "symlink"))
696 flags |= PORTABLE_PREFER_SYMLINK;
697 else if (streq(copy_mode, "copy"))
698 flags |= PORTABLE_PREFER_COPY;
699 else if (!isempty(copy_mode))
700 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
701
702 r = bus_image_acquire(m,
703 message,
704 name_or_path,
705 image,
706 BUS_IMAGE_AUTHENTICATE_ALL,
707 "org.freedesktop.portable1.attach-images",
708 &image,
709 error);
710 if (r < 0)
711 return r;
712 if (r == 0) /* Will call us back */
713 return 1;
714
715 r = portable_detach(
716 sd_bus_message_get_bus(message),
717 image->path,
718 extension_images,
719 flags,
720 &changes_detached,
721 &n_changes_detached,
722 error);
723 if (r < 0)
724 goto finish;
725
726 r = portable_attach(
727 sd_bus_message_get_bus(message),
728 image->path,
729 matches,
730 profile,
731 extension_images,
732 flags,
733 &changes_attached,
734 &n_changes_attached,
735 error);
736 if (r < 0)
737 goto finish;
738
739 /* We want to return the list of units really removed by the detach,
740 * and not added again by the attach */
741 r = normalize_portable_changes(changes_attached, n_changes_attached,
742 changes_detached, n_changes_detached,
743 &changes_gone, &n_changes_gone);
744 if (r < 0)
745 goto finish;
746
747 /* First, return the units that are gone (so that the caller can stop them)
748 * Then, return the units that are changed/added (so that the caller can
749 * start/restart/enable them) */
750 r = reply_portable_changes_pair(message,
751 changes_gone, n_changes_gone,
752 changes_attached, n_changes_attached);
753 if (r < 0)
754 goto finish;
755
756 finish:
757 portable_changes_free(changes_detached, n_changes_detached);
758 portable_changes_free(changes_attached, n_changes_attached);
759 portable_changes_free(changes_gone, n_changes_gone);
760 return r;
761 }
762
763 static int bus_image_method_reattach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
764 return bus_image_common_reattach(NULL, message, NULL, userdata, error);
765 }
766
767 int bus_image_common_mark_read_only(
768 Manager *m,
769 sd_bus_message *message,
770 const char *name_or_path,
771 Image *image,
772 sd_bus_error *error) {
773
774 int r, read_only;
775
776 assert(message);
777 assert(name_or_path || image);
778
779 if (!m) {
780 assert(image);
781 m = image->userdata;
782 }
783
784 r = sd_bus_message_read(message, "b", &read_only);
785 if (r < 0)
786 return r;
787
788 r = bus_image_acquire(m,
789 message,
790 name_or_path,
791 image,
792 BUS_IMAGE_AUTHENTICATE_ALL,
793 "org.freedesktop.portable1.manage-images",
794 &image,
795 error);
796 if (r < 0)
797 return r;
798 if (r == 0)
799 return 1; /* Will call us back */
800
801 r = image_read_only(image, read_only);
802 if (r < 0)
803 return r;
804
805 return sd_bus_reply_method_return(message, NULL);
806 }
807
808 static int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) {
809 return bus_image_common_mark_read_only(NULL, message, NULL, userdata, error);
810 }
811
812 int bus_image_common_set_limit(
813 Manager *m,
814 sd_bus_message *message,
815 const char *name_or_path,
816 Image *image,
817 sd_bus_error *error) {
818
819 uint64_t limit;
820 int r;
821
822 assert(message);
823 assert(name_or_path || image);
824
825 if (!m) {
826 assert(image);
827 m = image->userdata;
828 }
829
830 r = sd_bus_message_read(message, "t", &limit);
831 if (r < 0)
832 return r;
833 if (!FILE_SIZE_VALID_OR_INFINITY(limit))
834 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
835
836 r = bus_image_acquire(m,
837 message,
838 name_or_path,
839 image,
840 BUS_IMAGE_AUTHENTICATE_ALL,
841 "org.freedesktop.portable1.manage-images",
842 &image,
843 error);
844 if (r < 0)
845 return r;
846 if (r == 0)
847 return 1; /* Will call us back */
848
849 r = image_set_limit(image, limit);
850 if (r < 0)
851 return r;
852
853 return sd_bus_reply_method_return(message, NULL);
854 }
855
856 static int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
857 return bus_image_common_set_limit(NULL, message, NULL, userdata, error);
858 }
859
860 const sd_bus_vtable image_vtable[] = {
861 SD_BUS_VTABLE_START(0),
862 SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0),
863 SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0),
864 SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0),
865 SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0),
866 SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0),
867 SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0),
868 SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0),
869 SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0),
870 SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0),
871 SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0),
872 SD_BUS_METHOD_WITH_ARGS("GetOSRelease",
873 SD_BUS_NO_ARGS,
874 SD_BUS_RESULT("a{ss}", os_release),
875 bus_image_method_get_os_release,
876 SD_BUS_VTABLE_UNPRIVILEGED),
877 SD_BUS_METHOD_WITH_ARGS("GetMetadata",
878 SD_BUS_ARGS("as", matches),
879 SD_BUS_RESULT("s", image,
880 "ay", os_release,
881 "a{say}", units),
882 bus_image_method_get_metadata,
883 SD_BUS_VTABLE_UNPRIVILEGED),
884 SD_BUS_METHOD_WITH_ARGS("GetMetadataWithExtensions",
885 SD_BUS_ARGS("as", extensions,
886 "as", matches,
887 "t", flags),
888 SD_BUS_RESULT("s", image,
889 "ay", os_release,
890 "a{say}", extensions,
891 "a{say}", units),
892 bus_image_method_get_metadata,
893 SD_BUS_VTABLE_UNPRIVILEGED),
894 SD_BUS_METHOD_WITH_ARGS("GetState",
895 SD_BUS_NO_ARGS,
896 SD_BUS_RESULT("s", state),
897 bus_image_method_get_state,
898 SD_BUS_VTABLE_UNPRIVILEGED),
899 SD_BUS_METHOD_WITH_ARGS("GetStateWithExtensions",
900 SD_BUS_ARGS("as", extensions,
901 "t", flags),
902 SD_BUS_RESULT("s", state),
903 bus_image_method_get_state,
904 SD_BUS_VTABLE_UNPRIVILEGED),
905 SD_BUS_METHOD_WITH_ARGS("Attach",
906 SD_BUS_ARGS("as", matches,
907 "s", profile,
908 "b", runtime,
909 "s", copy_mode),
910 SD_BUS_RESULT("a(sss)", changes),
911 bus_image_method_attach,
912 SD_BUS_VTABLE_UNPRIVILEGED),
913 SD_BUS_METHOD_WITH_ARGS("AttachWithExtensions",
914 SD_BUS_ARGS("as", extensions,
915 "as", matches,
916 "s", profile,
917 "s", copy_mode,
918 "t", flags),
919 SD_BUS_RESULT("a(sss)", changes),
920 bus_image_method_attach,
921 SD_BUS_VTABLE_UNPRIVILEGED),
922 SD_BUS_METHOD_WITH_ARGS("Detach",
923 SD_BUS_ARGS("b", runtime),
924 SD_BUS_RESULT("a(sss)", changes),
925 bus_image_method_detach,
926 SD_BUS_VTABLE_UNPRIVILEGED),
927 SD_BUS_METHOD_WITH_ARGS("DetachWithExtensions",
928 SD_BUS_ARGS("as", extensions,
929 "t", flags),
930 SD_BUS_RESULT("a(sss)", changes),
931 bus_image_method_detach,
932 SD_BUS_VTABLE_UNPRIVILEGED),
933 SD_BUS_METHOD_WITH_ARGS("Reattach",
934 SD_BUS_ARGS("as", matches,
935 "s", profile,
936 "b", runtime,
937 "s", copy_mode),
938 SD_BUS_RESULT("a(sss)", changes_removed,
939 "a(sss)", changes_updated),
940 bus_image_method_reattach,
941 SD_BUS_VTABLE_UNPRIVILEGED),
942 SD_BUS_METHOD_WITH_ARGS("ReattacheWithExtensions",
943 SD_BUS_ARGS("as", extensions,
944 "as", matches,
945 "s", profile,
946 "s", copy_mode,
947 "t", flags),
948 SD_BUS_RESULT("a(sss)", changes_removed,
949 "a(sss)", changes_updated),
950 bus_image_method_reattach,
951 SD_BUS_VTABLE_UNPRIVILEGED),
952 SD_BUS_METHOD_WITH_ARGS("Remove",
953 SD_BUS_NO_ARGS,
954 SD_BUS_NO_RESULT,
955 bus_image_method_remove,
956 SD_BUS_VTABLE_UNPRIVILEGED),
957 SD_BUS_METHOD_WITH_ARGS("MarkReadOnly",
958 SD_BUS_ARGS("b", read_only),
959 SD_BUS_NO_RESULT,
960 bus_image_method_mark_read_only,
961 SD_BUS_VTABLE_UNPRIVILEGED),
962 SD_BUS_METHOD_WITH_ARGS("SetLimit",
963 SD_BUS_ARGS("t", limit),
964 SD_BUS_NO_RESULT,
965 bus_image_method_set_limit,
966 SD_BUS_VTABLE_UNPRIVILEGED),
967 SD_BUS_VTABLE_END
968 };
969
970 int bus_image_path(Image *image, char **ret) {
971 assert(image);
972 assert(ret);
973
974 if (!image->discoverable)
975 return -EINVAL;
976
977 return sd_bus_path_encode("/org/freedesktop/portable1/image", image->name, ret);
978 }
979
980 int bus_image_acquire(
981 Manager *m,
982 sd_bus_message *message,
983 const char *name_or_path,
984 Image *image,
985 ImageAcquireMode mode,
986 const char *polkit_action,
987 Image **ret,
988 sd_bus_error *error) {
989
990 _cleanup_(image_unrefp) Image *loaded = NULL;
991 Image *cached;
992 int r;
993
994 assert(m);
995 assert(message);
996 assert(name_or_path || image);
997 assert(mode >= 0);
998 assert(mode < _BUS_IMAGE_ACQUIRE_MODE_MAX);
999 assert(polkit_action || mode == BUS_IMAGE_REFUSE_BY_PATH);
1000 assert(ret);
1001
1002 /* Acquires an 'Image' object if not acquired yet, and enforces necessary authentication while doing so. */
1003
1004 if (mode == BUS_IMAGE_AUTHENTICATE_ALL) {
1005 r = bus_verify_polkit_async(
1006 message,
1007 CAP_SYS_ADMIN,
1008 polkit_action,
1009 NULL,
1010 false,
1011 UID_INVALID,
1012 &m->polkit_registry,
1013 error);
1014 if (r < 0)
1015 return r;
1016 if (r == 0) { /* Will call us back */
1017 *ret = NULL;
1018 return 0;
1019 }
1020 }
1021
1022 /* Already passed in? */
1023 if (image) {
1024 *ret = image;
1025 return 1;
1026 }
1027
1028 /* Let's see if this image is already cached? */
1029 cached = manager_image_cache_get(m, name_or_path);
1030 if (cached) {
1031 *ret = cached;
1032 return 1;
1033 }
1034
1035 if (image_name_is_valid(name_or_path)) {
1036
1037 /* If it's a short name, let's search for it */
1038 r = image_find(IMAGE_PORTABLE, name_or_path, NULL, &loaded);
1039 if (r == -ENOENT)
1040 return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE,
1041 "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,
1048 "Expected image name, not path in place of '%s'.", name_or_path);
1049
1050 if (!path_is_absolute(name_or_path))
1051 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
1052 "Image name '%s' is not valid or not a valid path.", name_or_path);
1053
1054 if (!path_is_normalized(name_or_path))
1055 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
1056 "Image path '%s' is not normalized.", name_or_path);
1057
1058 if (mode == BUS_IMAGE_AUTHENTICATE_BY_PATH) {
1059 r = bus_verify_polkit_async(
1060 message,
1061 CAP_SYS_ADMIN,
1062 polkit_action,
1063 NULL,
1064 false,
1065 UID_INVALID,
1066 &m->polkit_registry,
1067 error);
1068 if (r < 0)
1069 return r;
1070 if (r == 0) { /* Will call us back */
1071 *ret = NULL;
1072 return 0;
1073 }
1074 }
1075
1076 r = image_from_path(name_or_path, &loaded);
1077 }
1078 if (r == -EMEDIUMTYPE) {
1079 sd_bus_error_setf(error, BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE,
1080 "Type of image '%s' not recognized; supported image types are directories/btrfs subvolumes, block devices, and raw disk image files with suffix '.raw'.",
1081 name_or_path);
1082 return r;
1083 }
1084 if (r < 0)
1085 return r;
1086
1087 /* Add what we just loaded to the cache. This has as side-effect that the object stays in memory until the
1088 * cache is purged again, i.e. at least for the current event loop iteration, which is all we need, and which
1089 * means we don't actually need to ref the return object. */
1090 r = manager_image_cache_add(m, loaded);
1091 if (r < 0)
1092 return r;
1093
1094 *ret = loaded;
1095 return 1;
1096 }
1097
1098 int bus_image_object_find(
1099 sd_bus *bus,
1100 const char *path,
1101 const char *interface,
1102 void *userdata,
1103 void **found,
1104 sd_bus_error *error) {
1105
1106 _cleanup_free_ char *e = NULL;
1107 Manager *m = userdata;
1108 Image *image = NULL;
1109 int r;
1110
1111 assert(bus);
1112 assert(path);
1113 assert(interface);
1114 assert(found);
1115
1116 r = sd_bus_path_decode(path, "/org/freedesktop/portable1/image", &e);
1117 if (r < 0)
1118 return 0;
1119 if (r == 0)
1120 goto not_found;
1121 if (isempty(e))
1122 /* The path is "/org/freedesktop/portable1/image" itself */
1123 goto not_found;
1124
1125 r = bus_image_acquire(m, sd_bus_get_current_message(bus), e, NULL, BUS_IMAGE_REFUSE_BY_PATH, NULL, &image, error);
1126 if (r == -ENOENT)
1127 goto not_found;
1128 if (r < 0)
1129 return r;
1130
1131 *found = image;
1132 return 1;
1133
1134 not_found:
1135 *found = NULL;
1136 return 0;
1137 }
1138
1139 int bus_image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
1140 _cleanup_hashmap_free_ Hashmap *images = NULL;
1141 _cleanup_strv_free_ char **l = NULL;
1142 Manager *m = userdata;
1143 size_t n = 0;
1144 Image *image;
1145 int r;
1146
1147 assert(bus);
1148 assert(path);
1149 assert(nodes);
1150
1151 images = hashmap_new(&image_hash_ops);
1152 if (!images)
1153 return -ENOMEM;
1154
1155 r = manager_image_cache_discover(m, images, error);
1156 if (r < 0)
1157 return r;
1158
1159 HASHMAP_FOREACH(image, images) {
1160 char *p;
1161
1162 r = bus_image_path(image, &p);
1163 if (r < 0)
1164 return r;
1165
1166 if (!GREEDY_REALLOC(l, n+2)) {
1167 free(p);
1168 return -ENOMEM;
1169 }
1170
1171 l[n++] = p;
1172 l[n] = NULL;
1173 }
1174
1175 *nodes = TAKE_PTR(l);
1176
1177 return 1;
1178 }
1179
1180 const BusObjectImplementation image_object = {
1181 "/org/freedesktop/portable1/image",
1182 "org.freedesktop.portable1.Image",
1183 .fallback_vtables = BUS_FALLBACK_VTABLES({image_vtable, bus_image_object_find}),
1184 .node_enumerator = bus_image_node_enumerator,
1185 };