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