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