]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/portable/portabled-image-bus.c
pkgconfig: define variables relative to ${prefix}/${rootprefix}/${sysconfdir}
[thirdparty/systemd.git] / src / portable / portabled-image-bus.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include "alloc-util.h"
4 #include "bus-common-errors.h"
5 #include "bus-label.h"
6 #include "bus-util.h"
7 #include "fd-util.h"
8 #include "fileio.h"
9 #include "io-util.h"
10 #include "machine-image.h"
11 #include "portable.h"
12 #include "portabled-bus.h"
13 #include "portabled-image-bus.h"
14 #include "portabled-image.h"
15 #include "portabled.h"
16 #include "process-util.h"
17 #include "strv.h"
18 #include "user-util.h"
19
20 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType);
21
22 int bus_image_common_get_os_release(
23 Manager *m,
24 sd_bus_message *message,
25 const char *name_or_path,
26 Image *image,
27 sd_bus_error *error) {
28
29 int r;
30
31 assert(name_or_path || image);
32 assert(message);
33
34 if (!m) {
35 assert(image);
36 m = image->userdata;
37 }
38
39 r = bus_image_acquire(m,
40 message,
41 name_or_path,
42 image,
43 BUS_IMAGE_AUTHENTICATE_BY_PATH,
44 "org.freedesktop.portable1.inspect-images",
45 &image,
46 error);
47 if (r < 0)
48 return r;
49 if (r == 0) /* Will call us back */
50 return 1;
51
52 if (!image->metadata_valid) {
53 r = image_read_metadata(image);
54 if (r < 0)
55 return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
56 }
57
58 return bus_reply_pair_array(message, image->os_release);
59 }
60
61 static int bus_image_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
62 return bus_image_common_get_os_release(NULL, message, NULL, userdata, error);
63 }
64
65 static int append_fd(sd_bus_message *m, PortableMetadata *d) {
66 _cleanup_fclose_ FILE *f = NULL;
67 _cleanup_free_ char *buf = NULL;
68 size_t n;
69 int r;
70
71 assert(m);
72 assert(d);
73 assert(d->fd >= 0);
74
75 f = fdopen(d->fd, "re");
76 if (!f)
77 return -errno;
78
79 d->fd = -1;
80
81 r = read_full_stream(f, &buf, &n);
82 if (r < 0)
83 return r;
84
85 return sd_bus_message_append_array(m, 'y', buf, n);
86 }
87
88 int bus_image_common_get_metadata(
89 Manager *m,
90 sd_bus_message *message,
91 const char *name_or_path,
92 Image *image,
93 sd_bus_error *error) {
94
95 _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
96 _cleanup_(portable_metadata_hashmap_unrefp) Hashmap *unit_files = NULL;
97 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
98 _cleanup_free_ PortableMetadata **sorted = NULL;
99 _cleanup_strv_free_ char **matches = NULL;
100 size_t i;
101 int r;
102
103 assert(name_or_path || image);
104 assert(message);
105
106 if (!m) {
107 assert(image);
108 m = image->userdata;
109 }
110
111 r = sd_bus_message_read_strv(message, &matches);
112 if (r < 0)
113 return r;
114
115 r = bus_image_acquire(m,
116 message,
117 name_or_path,
118 image,
119 BUS_IMAGE_AUTHENTICATE_BY_PATH,
120 "org.freedesktop.portable1.inspect-images",
121 &image,
122 error);
123 if (r < 0)
124 return r;
125 if (r == 0) /* Will call us back */
126 return 1;
127
128 r = portable_extract(
129 image->path,
130 matches,
131 &os_release,
132 &unit_files,
133 error);
134 if (r < 0)
135 return r;
136
137 r = portable_metadata_hashmap_to_sorted_array(unit_files, &sorted);
138 if (r < 0)
139 return r;
140
141 r = sd_bus_message_new_method_return(message, &reply);
142 if (r < 0)
143 return r;
144
145 r = sd_bus_message_append(reply, "s", image->path);
146 if (r < 0)
147 return r;
148
149 r = append_fd(reply, os_release);
150 if (r < 0)
151 return r;
152
153 r = sd_bus_message_open_container(reply, 'a', "{say}");
154 if (r < 0)
155 return r;
156
157 for (i = 0; i < hashmap_size(unit_files); i++) {
158
159 r = sd_bus_message_open_container(reply, 'e', "say");
160 if (r < 0)
161 return r;
162
163 r = sd_bus_message_append(reply, "s", sorted[i]->name);
164 if (r < 0)
165 return r;
166
167 r = append_fd(reply, sorted[i]);
168 if (r < 0)
169 return r;
170
171 r = sd_bus_message_close_container(reply);
172 if (r < 0)
173 return r;
174 }
175
176 r = sd_bus_message_close_container(reply);
177 if (r < 0)
178 return r;
179
180 return sd_bus_send(NULL, reply, NULL);
181 }
182
183 static int bus_image_method_get_metadata(sd_bus_message *message, void *userdata, sd_bus_error *error) {
184 return bus_image_common_get_metadata(NULL, message, NULL, userdata, error);
185 }
186
187 static int bus_image_method_get_state(
188 sd_bus_message *message,
189 void *userdata,
190 sd_bus_error *error) {
191
192 Image *image = userdata;
193 PortableState state;
194 int r;
195
196 assert(message);
197 assert(image);
198
199 r = portable_get_state(
200 sd_bus_message_get_bus(message),
201 image->path,
202 0,
203 &state,
204 error);
205 if (r < 0)
206 return r;
207
208 return sd_bus_reply_method_return(message, "s", portable_state_to_string(state));
209 }
210
211 int bus_image_common_attach(
212 Manager *m,
213 sd_bus_message *message,
214 const char *name_or_path,
215 Image *image,
216 sd_bus_error *error) {
217
218 _cleanup_strv_free_ char **matches = NULL;
219 PortableChange *changes = NULL;
220 PortableFlags flags = 0;
221 const char *profile, *copy_mode;
222 size_t n_changes = 0;
223 int runtime, r;
224
225 assert(message);
226 assert(name_or_path || image);
227
228 if (!m) {
229 assert(image);
230 m = image->userdata;
231 }
232
233 r = sd_bus_message_read_strv(message, &matches);
234 if (r < 0)
235 return r;
236
237 r = sd_bus_message_read(message, "sbs", &profile, &runtime, &copy_mode);
238 if (r < 0)
239 return r;
240
241 if (streq(copy_mode, "symlink"))
242 flags |= PORTABLE_PREFER_SYMLINK;
243 else if (streq(copy_mode, "copy"))
244 flags |= PORTABLE_PREFER_COPY;
245 else if (!isempty(copy_mode))
246 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
247
248 if (runtime)
249 flags |= PORTABLE_RUNTIME;
250
251 r = bus_image_acquire(m,
252 message,
253 name_or_path,
254 image,
255 BUS_IMAGE_AUTHENTICATE_ALL,
256 "org.freedesktop.portable1.attach-images",
257 &image,
258 error);
259 if (r < 0)
260 return r;
261 if (r == 0) /* Will call us back */
262 return 1;
263
264 r = portable_attach(
265 sd_bus_message_get_bus(message),
266 image->path,
267 matches,
268 profile,
269 flags,
270 &changes,
271 &n_changes,
272 error);
273 if (r < 0)
274 goto finish;
275
276 r = reply_portable_changes(message, changes, n_changes);
277
278 finish:
279 portable_changes_free(changes, n_changes);
280 return r;
281 }
282
283 static int bus_image_method_attach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
284 return bus_image_common_attach(NULL, message, NULL, userdata, error);
285 }
286
287 static int bus_image_method_detach(
288 sd_bus_message *message,
289 void *userdata,
290 sd_bus_error *error) {
291
292 PortableChange *changes = NULL;
293 Image *image = userdata;
294 Manager *m = image->userdata;
295 size_t n_changes = 0;
296 int r, runtime;
297
298 assert(message);
299 assert(image);
300 assert(m);
301
302 r = sd_bus_message_read(message, "b", &runtime);
303 if (r < 0)
304 return r;
305
306 r = bus_verify_polkit_async(
307 message,
308 CAP_SYS_ADMIN,
309 "org.freedesktop.portable1.attach-images",
310 NULL,
311 false,
312 UID_INVALID,
313 &m->polkit_registry,
314 error);
315 if (r < 0)
316 return r;
317 if (r == 0)
318 return 1; /* Will call us back */
319
320 r = portable_detach(
321 sd_bus_message_get_bus(message),
322 image->path,
323 runtime ? PORTABLE_RUNTIME : 0,
324 &changes,
325 &n_changes,
326 error);
327 if (r < 0)
328 goto finish;
329
330 r = reply_portable_changes(message, changes, n_changes);
331
332 finish:
333 portable_changes_free(changes, n_changes);
334 return r;
335 }
336
337 int bus_image_common_remove(
338 Manager *m,
339 sd_bus_message *message,
340 const char *name_or_path,
341 Image *image,
342 sd_bus_error *error) {
343
344 _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
345 _cleanup_(sigkill_waitp) pid_t child = 0;
346 PortableState state;
347 int r;
348
349 assert(message);
350 assert(name_or_path || image);
351
352 if (!m) {
353 assert(image);
354 m = image->userdata;
355 }
356
357 if (m->n_operations >= OPERATIONS_MAX)
358 return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
359
360 r = bus_image_acquire(m,
361 message,
362 name_or_path,
363 image,
364 BUS_IMAGE_AUTHENTICATE_ALL,
365 "org.freedesktop.portable1.manage-images",
366 &image,
367 error);
368 if (r < 0)
369 return r;
370 if (r == 0)
371 return 1; /* Will call us back */
372
373 r = portable_get_state(
374 sd_bus_message_get_bus(message),
375 image->path,
376 0,
377 &state,
378 error);
379 if (r < 0)
380 return r;
381
382 if (state != PORTABLE_DETACHED)
383 return sd_bus_error_set_errnof(error, EBUSY, "Image '%s' is not detached, refusing.", image->path);
384
385 if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
386 return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
387
388 r = safe_fork("(sd-imgrm)", FORK_RESET_SIGNALS, &child);
389 if (r < 0)
390 return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m");
391 if (r == 0) {
392 errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
393
394 r = image_remove(image);
395 if (r < 0) {
396 (void) write(errno_pipe_fd[1], &r, sizeof(r));
397 _exit(EXIT_FAILURE);
398 }
399
400 _exit(EXIT_SUCCESS);
401 }
402
403 errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
404
405 r = operation_new(m, child, message, errno_pipe_fd[0], NULL);
406 if (r < 0)
407 return r;
408
409 child = 0;
410 errno_pipe_fd[0] = -1;
411
412 return 1;
413 }
414
415 static int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error) {
416 return bus_image_common_remove(NULL, message, NULL, userdata, error);
417 }
418
419 int bus_image_common_mark_read_only(
420 Manager *m,
421 sd_bus_message *message,
422 const char *name_or_path,
423 Image *image,
424 sd_bus_error *error) {
425
426 int r, read_only;
427
428 assert(message);
429 assert(name_or_path || image);
430
431 if (!m) {
432 assert(image);
433 m = image->userdata;
434 }
435
436 r = sd_bus_message_read(message, "b", &read_only);
437 if (r < 0)
438 return r;
439
440 r = bus_image_acquire(m,
441 message,
442 name_or_path,
443 image,
444 BUS_IMAGE_AUTHENTICATE_ALL,
445 "org.freedesktop.portable1.manage-images",
446 &image,
447 error);
448 if (r < 0)
449 return r;
450 if (r == 0)
451 return 1; /* Will call us back */
452
453 r = image_read_only(image, read_only);
454 if (r < 0)
455 return r;
456
457 return sd_bus_reply_method_return(message, NULL);
458 }
459
460 static int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) {
461 return bus_image_common_mark_read_only(NULL, message, NULL, userdata, error);
462 }
463
464 int bus_image_common_set_limit(
465 Manager *m,
466 sd_bus_message *message,
467 const char *name_or_path,
468 Image *image,
469 sd_bus_error *error) {
470
471 uint64_t limit;
472 int r;
473
474 assert(message);
475 assert(name_or_path || image);
476
477 if (!m) {
478 assert(image);
479 m = image->userdata;
480 }
481
482 r = sd_bus_message_read(message, "t", &limit);
483 if (r < 0)
484 return r;
485 if (!FILE_SIZE_VALID_OR_INFINITY(limit))
486 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
487
488 r = bus_image_acquire(m,
489 message,
490 name_or_path,
491 image,
492 BUS_IMAGE_AUTHENTICATE_ALL,
493 "org.freedesktop.portable1.manage-images",
494 &image,
495 error);
496 if (r < 0)
497 return r;
498 if (r == 0)
499 return 1; /* Will call us back */
500
501 r = image_set_limit(image, limit);
502 if (r < 0)
503 return r;
504
505 return sd_bus_reply_method_return(message, NULL);
506 }
507
508 static int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
509 return bus_image_common_set_limit(NULL, message, NULL, userdata, error);
510 }
511
512 const sd_bus_vtable image_vtable[] = {
513 SD_BUS_VTABLE_START(0),
514 SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0),
515 SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0),
516 SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0),
517 SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0),
518 SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0),
519 SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0),
520 SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0),
521 SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0),
522 SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0),
523 SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0),
524 SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_image_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
525 SD_BUS_METHOD("GetMedatadata", "as", "saya{say}", bus_image_method_get_metadata, SD_BUS_VTABLE_UNPRIVILEGED),
526 SD_BUS_METHOD("GetState", NULL, "s", bus_image_method_get_state, SD_BUS_VTABLE_UNPRIVILEGED),
527 SD_BUS_METHOD("Attach", "assbs", "a(sss)", bus_image_method_attach, SD_BUS_VTABLE_UNPRIVILEGED),
528 SD_BUS_METHOD("Detach", "b", "a(sss)", bus_image_method_detach, SD_BUS_VTABLE_UNPRIVILEGED),
529 SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, SD_BUS_VTABLE_UNPRIVILEGED),
530 SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
531 SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED),
532 SD_BUS_VTABLE_END
533 };
534
535 int bus_image_path(Image *image, char **ret) {
536 assert(image);
537 assert(ret);
538
539 if (!image->discoverable)
540 return -EINVAL;
541
542 return sd_bus_path_encode("/org/freedesktop/portable1/image", image->name, ret);
543 }
544
545 int bus_image_acquire(
546 Manager *m,
547 sd_bus_message *message,
548 const char *name_or_path,
549 Image *image,
550 ImageAcquireMode mode,
551 const char *polkit_action,
552 Image **ret,
553 sd_bus_error *error) {
554
555 _cleanup_(image_unrefp) Image *loaded = NULL;
556 Image *cached;
557 int r;
558
559 assert(m);
560 assert(message);
561 assert(name_or_path || image);
562 assert(mode >= 0);
563 assert(mode < _BUS_IMAGE_ACQUIRE_MODE_MAX);
564 assert(polkit_action || mode == BUS_IMAGE_REFUSE_BY_PATH);
565 assert(ret);
566
567 /* Acquires an 'Image' object if not acquired yet, and enforces necessary authentication while doing so. */
568
569 if (mode == BUS_IMAGE_AUTHENTICATE_ALL) {
570 r = bus_verify_polkit_async(
571 message,
572 CAP_SYS_ADMIN,
573 polkit_action,
574 NULL,
575 false,
576 UID_INVALID,
577 &m->polkit_registry,
578 error);
579 if (r < 0)
580 return r;
581 if (r == 0) { /* Will call us back */
582 *ret = NULL;
583 return 0;
584 }
585 }
586
587 /* Already passed in? */
588 if (image) {
589 *ret = image;
590 return 1;
591 }
592
593 /* Let's see if this image is already cached? */
594 cached = manager_image_cache_get(m, name_or_path);
595 if (cached) {
596 *ret = cached;
597 return 1;
598 }
599
600 if (image_name_is_valid(name_or_path)) {
601
602 /* If it's a short name, let's search for it */
603 r = image_find(IMAGE_PORTABLE, name_or_path, &loaded);
604 if (r == -ENOENT)
605 return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE, "No image '%s' found.", name_or_path);
606
607 /* other errors are handled below… */
608 } else {
609 /* Don't accept path if this is always forbidden */
610 if (mode == BUS_IMAGE_REFUSE_BY_PATH)
611 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Expected image name, not path in place of '%s'.", name_or_path);
612
613 if (!path_is_absolute(name_or_path))
614 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is not valid or not a valid path.", name_or_path);
615
616 if (!path_is_normalized(name_or_path))
617 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image path '%s' is not normalized.", name_or_path);
618
619 if (mode == BUS_IMAGE_AUTHENTICATE_BY_PATH) {
620 r = bus_verify_polkit_async(
621 message,
622 CAP_SYS_ADMIN,
623 polkit_action,
624 NULL,
625 false,
626 UID_INVALID,
627 &m->polkit_registry,
628 error);
629 if (r < 0)
630 return r;
631 if (r == 0) { /* Will call us back */
632 *ret = NULL;
633 return 0;
634 }
635 }
636
637 r = image_from_path(name_or_path, &loaded);
638 }
639 if (r == -EMEDIUMTYPE) {
640 sd_bus_error_setf(error, BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE, "Typ of image '%s' not recognized; supported image types are directories/btrfs subvolumes, block devices, and raw disk image files with suffix '.raw'.", name_or_path);
641 return r;
642 }
643 if (r < 0)
644 return r;
645
646 /* Add what we just loaded to the cache. This has as side-effect that the object stays in memory until the
647 * cache is purged again, i.e. at least for the current event loop iteration, which is all we need, and which
648 * means we don't actually need to ref the return object. */
649 r = manager_image_cache_add(m, loaded);
650 if (r < 0)
651 return r;
652
653 *ret = loaded;
654 return 1;
655 }
656
657 int bus_image_object_find(
658 sd_bus *bus,
659 const char *path,
660 const char *interface,
661 void *userdata,
662 void **found,
663 sd_bus_error *error) {
664
665 _cleanup_free_ char *e = NULL;
666 Manager *m = userdata;
667 Image *image = NULL;
668 int r;
669
670 assert(bus);
671 assert(path);
672 assert(interface);
673 assert(found);
674
675 r = sd_bus_path_decode(path, "/org/freedesktop/portable1/image", &e);
676 if (r < 0)
677 return 0;
678 if (r == 0)
679 goto not_found;
680
681 r = bus_image_acquire(m, sd_bus_get_current_message(bus), e, NULL, BUS_IMAGE_REFUSE_BY_PATH, NULL, &image, error);
682 if (r == -ENOENT)
683 goto not_found;
684 if (r < 0)
685 return r;
686
687 *found = image;
688 return 1;
689
690 not_found:
691 *found = NULL;
692 return 0;
693 }
694
695 int bus_image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
696 _cleanup_(image_hashmap_freep) Hashmap *images = NULL;
697 _cleanup_strv_free_ char **l = NULL;
698 size_t n_allocated = 0, n = 0;
699 Manager *m = userdata;
700 Image *image;
701 Iterator i;
702 int r;
703
704 assert(bus);
705 assert(path);
706 assert(nodes);
707
708 images = hashmap_new(&string_hash_ops);
709 if (!images)
710 return -ENOMEM;
711
712 r = manager_image_cache_discover(m, images, error);
713 if (r < 0)
714 return r;
715
716 HASHMAP_FOREACH(image, images, i) {
717 char *p;
718
719 r = bus_image_path(image, &p);
720 if (r < 0)
721 return r;
722
723 if (!GREEDY_REALLOC(l, n_allocated, n+2)) {
724 free(p);
725 return -ENOMEM;
726 }
727
728 l[n++] = p;
729 l[n] = NULL;
730 }
731
732 *nodes = TAKE_PTR(l);
733
734 return 1;
735 }