]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/machine/image-dbus.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / machine / image-dbus.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <sys/file.h>
22 #include <sys/mount.h>
23
24 #include "alloc-util.h"
25 #include "bus-label.h"
26 #include "bus-util.h"
27 #include "copy.h"
28 #include "dissect-image.h"
29 #include "fd-util.h"
30 #include "fileio.h"
31 #include "fs-util.h"
32 #include "image-dbus.h"
33 #include "io-util.h"
34 #include "loop-util.h"
35 #include "machine-image.h"
36 #include "mount-util.h"
37 #include "process-util.h"
38 #include "raw-clone.h"
39 #include "strv.h"
40 #include "user-util.h"
41
42 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType);
43
44 int bus_image_method_remove(
45 sd_bus_message *message,
46 void *userdata,
47 sd_bus_error *error) {
48
49 _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
50 Image *image = userdata;
51 Manager *m = image->userdata;
52 pid_t child;
53 int r;
54
55 assert(message);
56 assert(image);
57
58 if (m->n_operations >= OPERATIONS_MAX)
59 return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
60
61 r = bus_verify_polkit_async(
62 message,
63 CAP_SYS_ADMIN,
64 "org.freedesktop.machine1.manage-images",
65 NULL,
66 false,
67 UID_INVALID,
68 &m->polkit_registry,
69 error);
70 if (r < 0)
71 return r;
72 if (r == 0)
73 return 1; /* Will call us back */
74
75 if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
76 return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
77
78 child = fork();
79 if (child < 0)
80 return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
81 if (child == 0) {
82 errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
83
84 r = image_remove(image);
85 if (r < 0) {
86 (void) write(errno_pipe_fd[1], &r, sizeof(r));
87 _exit(EXIT_FAILURE);
88 }
89
90 _exit(EXIT_SUCCESS);
91 }
92
93 errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
94
95 r = operation_new(m, NULL, child, message, errno_pipe_fd[0], NULL);
96 if (r < 0) {
97 (void) sigkill_wait(child);
98 return r;
99 }
100
101 errno_pipe_fd[0] = -1;
102
103 return 1;
104 }
105
106 int bus_image_method_rename(
107 sd_bus_message *message,
108 void *userdata,
109 sd_bus_error *error) {
110
111 Image *image = userdata;
112 Manager *m = image->userdata;
113 const char *new_name;
114 int r;
115
116 assert(message);
117 assert(image);
118
119 r = sd_bus_message_read(message, "s", &new_name);
120 if (r < 0)
121 return r;
122
123 if (!image_name_is_valid(new_name))
124 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name);
125
126 r = bus_verify_polkit_async(
127 message,
128 CAP_SYS_ADMIN,
129 "org.freedesktop.machine1.manage-images",
130 NULL,
131 false,
132 UID_INVALID,
133 &m->polkit_registry,
134 error);
135 if (r < 0)
136 return r;
137 if (r == 0)
138 return 1; /* Will call us back */
139
140 r = image_rename(image, new_name);
141 if (r < 0)
142 return r;
143
144 return sd_bus_reply_method_return(message, NULL);
145 }
146
147 int bus_image_method_clone(
148 sd_bus_message *message,
149 void *userdata,
150 sd_bus_error *error) {
151
152 _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
153 Image *image = userdata;
154 Manager *m = image->userdata;
155 const char *new_name;
156 int r, read_only;
157 pid_t child;
158
159 assert(message);
160 assert(image);
161 assert(m);
162
163 if (m->n_operations >= OPERATIONS_MAX)
164 return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
165
166 r = sd_bus_message_read(message, "sb", &new_name, &read_only);
167 if (r < 0)
168 return r;
169
170 if (!image_name_is_valid(new_name))
171 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name);
172
173 r = bus_verify_polkit_async(
174 message,
175 CAP_SYS_ADMIN,
176 "org.freedesktop.machine1.manage-images",
177 NULL,
178 false,
179 UID_INVALID,
180 &m->polkit_registry,
181 error);
182 if (r < 0)
183 return r;
184 if (r == 0)
185 return 1; /* Will call us back */
186
187 if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
188 return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
189
190 child = fork();
191 if (child < 0)
192 return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
193 if (child == 0) {
194 errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
195
196 r = image_clone(image, new_name, read_only);
197 if (r < 0) {
198 (void) write(errno_pipe_fd[1], &r, sizeof(r));
199 _exit(EXIT_FAILURE);
200 }
201
202 _exit(EXIT_SUCCESS);
203 }
204
205 errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
206
207 r = operation_new(m, NULL, child, message, errno_pipe_fd[0], NULL);
208 if (r < 0) {
209 (void) sigkill_wait(child);
210 return r;
211 }
212
213 errno_pipe_fd[0] = -1;
214
215 return 1;
216 }
217
218 int bus_image_method_mark_read_only(
219 sd_bus_message *message,
220 void *userdata,
221 sd_bus_error *error) {
222
223 Image *image = userdata;
224 Manager *m = image->userdata;
225 int r, read_only;
226
227 assert(message);
228
229 r = sd_bus_message_read(message, "b", &read_only);
230 if (r < 0)
231 return r;
232
233 r = bus_verify_polkit_async(
234 message,
235 CAP_SYS_ADMIN,
236 "org.freedesktop.machine1.manage-images",
237 NULL,
238 false,
239 UID_INVALID,
240 &m->polkit_registry,
241 error);
242 if (r < 0)
243 return r;
244 if (r == 0)
245 return 1; /* Will call us back */
246
247 r = image_read_only(image, read_only);
248 if (r < 0)
249 return r;
250
251 return sd_bus_reply_method_return(message, NULL);
252 }
253
254 int bus_image_method_set_limit(
255 sd_bus_message *message,
256 void *userdata,
257 sd_bus_error *error) {
258
259 Image *image = userdata;
260 Manager *m = image->userdata;
261 uint64_t limit;
262 int r;
263
264 assert(message);
265
266 r = sd_bus_message_read(message, "t", &limit);
267 if (r < 0)
268 return r;
269 if (!FILE_SIZE_VALID_OR_INFINITY(limit))
270 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
271
272 r = bus_verify_polkit_async(
273 message,
274 CAP_SYS_ADMIN,
275 "org.freedesktop.machine1.manage-images",
276 NULL,
277 false,
278 UID_INVALID,
279 &m->polkit_registry,
280 error);
281 if (r < 0)
282 return r;
283 if (r == 0)
284 return 1; /* Will call us back */
285
286 r = image_set_limit(image, limit);
287 if (r < 0)
288 return r;
289
290 return sd_bus_reply_method_return(message, NULL);
291 }
292
293 #define EXIT_NOT_FOUND 2
294
295 static int directory_image_get_os_release(Image *image, char ***ret, sd_bus_error *error) {
296
297 _cleanup_free_ char *path = NULL;
298 int r;
299
300 assert(image);
301 assert(ret);
302
303 r = chase_symlinks("/etc/os-release", image->path, CHASE_PREFIX_ROOT, &path);
304 if (r == -ENOENT)
305 r = chase_symlinks("/usr/lib/os-release", image->path, CHASE_PREFIX_ROOT, &path);
306 if (r == -ENOENT)
307 return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Image does not contain OS release information");
308 if (r < 0)
309 return sd_bus_error_set_errnof(error, r, "Failed to resolve %s: %m", image->path);
310
311 r = load_env_file_pairs(NULL, path, NULL, ret);
312 if (r < 0)
313 return sd_bus_error_set_errnof(error, r, "Failed to open %s: %m", path);
314
315 return 0;
316 }
317
318 static int raw_image_get_os_release(Image *image, char ***ret, sd_bus_error *error) {
319 _cleanup_(rmdir_and_freep) char *t = NULL;
320 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
321 _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
322 _cleanup_(sigkill_waitp) pid_t child = 0;
323 _cleanup_close_pair_ int pair[2] = { -1, -1 };
324 _cleanup_fclose_ FILE *f = NULL;
325 _cleanup_strv_free_ char **v = NULL;
326 siginfo_t si;
327 int r;
328
329 assert(image);
330 assert(ret);
331
332 r = mkdtemp_malloc("/tmp/machined-root-XXXXXX", &t);
333 if (r < 0)
334 return sd_bus_error_set_errnof(error, r, "Failed to create temporary directory: %m");
335
336 r = loop_device_make_by_path(image->path, O_RDONLY, &d);
337 if (r < 0)
338 return sd_bus_error_set_errnof(error, r, "Failed to set up loop block device for %s: %m", image->path);
339
340 r = dissect_image(d->fd, NULL, 0, DISSECT_IMAGE_REQUIRE_ROOT, &m);
341 if (r == -ENOPKG)
342 return sd_bus_error_set_errnof(error, r, "Disk image %s not understood: %m", image->path);
343 if (r < 0)
344 return sd_bus_error_set_errnof(error, r, "Failed to dissect image %s: %m", image->path);
345
346 if (pipe2(pair, O_CLOEXEC) < 0)
347 return sd_bus_error_set_errnof(error, errno, "Failed to create communication pipe: %m");
348
349 child = raw_clone(SIGCHLD|CLONE_NEWNS);
350 if (child < 0)
351 return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
352
353 if (child == 0) {
354 int fd;
355
356 pair[0] = safe_close(pair[0]);
357
358 /* Make sure we never propagate to the host */
359 if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0)
360 _exit(EXIT_FAILURE);
361
362 r = dissected_image_mount(m, t, DISSECT_IMAGE_READ_ONLY);
363 if (r < 0)
364 _exit(EXIT_FAILURE);
365
366 r = mount_move_root(t);
367 if (r < 0)
368 _exit(EXIT_FAILURE);
369
370 fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
371 if (fd < 0 && errno == ENOENT) {
372 fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
373 if (fd < 0 && errno == ENOENT)
374 _exit(EXIT_NOT_FOUND);
375 }
376 if (fd < 0)
377 _exit(EXIT_FAILURE);
378
379 r = copy_bytes(fd, pair[1], (uint64_t) -1, 0);
380 if (r < 0)
381 _exit(EXIT_FAILURE);
382
383 _exit(EXIT_SUCCESS);
384 }
385
386 pair[1] = safe_close(pair[1]);
387
388 f = fdopen(pair[0], "re");
389 if (!f)
390 return -errno;
391
392 pair[0] = -1;
393
394 r = load_env_file_pairs(f, "os-release", NULL, &v);
395 if (r < 0)
396 return r;
397
398 r = wait_for_terminate(child, &si);
399 if (r < 0)
400 return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
401 child = 0;
402 if (si.si_code == CLD_EXITED && si.si_status == EXIT_NOT_FOUND)
403 return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Image does not contain OS release information");
404 if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
405 return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
406
407 *ret = v;
408 v = NULL;
409
410 return 0;
411 }
412
413 int bus_image_method_get_os_release(
414 sd_bus_message *message,
415 void *userdata,
416 sd_bus_error *error) {
417
418 _cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT;
419 _cleanup_strv_free_ char **v = NULL;
420 Image *image = userdata;
421 int r;
422
423 r = image_path_lock(image->path, LOCK_SH|LOCK_NB, &tree_global_lock, &tree_local_lock);
424 if (r < 0)
425 return sd_bus_error_set_errnof(error, r, "Failed to lock image: %m");
426
427 switch (image->type) {
428
429 case IMAGE_DIRECTORY:
430 case IMAGE_SUBVOLUME:
431 r = directory_image_get_os_release(image, &v, error);
432 break;
433
434 case IMAGE_RAW:
435 case IMAGE_BLOCK:
436 r = raw_image_get_os_release(image, &v, error);
437 break;
438
439 default:
440 assert_not_reached("Unknown image type");
441 }
442 if (r < 0)
443 return r;
444
445 return bus_reply_pair_array(message, v);
446 }
447
448 const sd_bus_vtable image_vtable[] = {
449 SD_BUS_VTABLE_START(0),
450 SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0),
451 SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0),
452 SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0),
453 SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0),
454 SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0),
455 SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0),
456 SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0),
457 SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0),
458 SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0),
459 SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0),
460 SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, SD_BUS_VTABLE_UNPRIVILEGED),
461 SD_BUS_METHOD("Rename", "s", NULL, bus_image_method_rename, SD_BUS_VTABLE_UNPRIVILEGED),
462 SD_BUS_METHOD("Clone", "sb", NULL, bus_image_method_clone, SD_BUS_VTABLE_UNPRIVILEGED),
463 SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
464 SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED),
465 SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_image_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
466 SD_BUS_VTABLE_END
467 };
468
469 static int image_flush_cache(sd_event_source *s, void *userdata) {
470 Manager *m = userdata;
471 Image *i;
472
473 assert(s);
474 assert(m);
475
476 while ((i = hashmap_steal_first(m->image_cache)))
477 image_unref(i);
478
479 return 0;
480 }
481
482 int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
483 _cleanup_free_ char *e = NULL;
484 Manager *m = userdata;
485 Image *image = NULL;
486 const char *p;
487 int r;
488
489 assert(bus);
490 assert(path);
491 assert(interface);
492 assert(found);
493
494 p = startswith(path, "/org/freedesktop/machine1/image/");
495 if (!p)
496 return 0;
497
498 e = bus_label_unescape(p);
499 if (!e)
500 return -ENOMEM;
501
502 image = hashmap_get(m->image_cache, e);
503 if (image) {
504 *found = image;
505 return 1;
506 }
507
508 r = hashmap_ensure_allocated(&m->image_cache, &string_hash_ops);
509 if (r < 0)
510 return r;
511
512 if (!m->image_cache_defer_event) {
513 r = sd_event_add_defer(m->event, &m->image_cache_defer_event, image_flush_cache, m);
514 if (r < 0)
515 return r;
516
517 r = sd_event_source_set_priority(m->image_cache_defer_event, SD_EVENT_PRIORITY_IDLE);
518 if (r < 0)
519 return r;
520 }
521
522 r = sd_event_source_set_enabled(m->image_cache_defer_event, SD_EVENT_ONESHOT);
523 if (r < 0)
524 return r;
525
526 r = image_find(e, &image);
527 if (r <= 0)
528 return r;
529
530 image->userdata = m;
531
532 r = hashmap_put(m->image_cache, image->name, image);
533 if (r < 0) {
534 image_unref(image);
535 return r;
536 }
537
538 *found = image;
539 return 1;
540 }
541
542 char *image_bus_path(const char *name) {
543 _cleanup_free_ char *e = NULL;
544
545 assert(name);
546
547 e = bus_label_escape(name);
548 if (!e)
549 return NULL;
550
551 return strappend("/org/freedesktop/machine1/image/", e);
552 }
553
554 int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
555 _cleanup_(image_hashmap_freep) Hashmap *images = NULL;
556 _cleanup_strv_free_ char **l = NULL;
557 Image *image;
558 Iterator i;
559 int r;
560
561 assert(bus);
562 assert(path);
563 assert(nodes);
564
565 images = hashmap_new(&string_hash_ops);
566 if (!images)
567 return -ENOMEM;
568
569 r = image_discover(images);
570 if (r < 0)
571 return r;
572
573 HASHMAP_FOREACH(image, images, i) {
574 char *p;
575
576 p = image_bus_path(image->name);
577 if (!p)
578 return -ENOMEM;
579
580 r = strv_consume(&l, p);
581 if (r < 0)
582 return r;
583 }
584
585 *nodes = l;
586 l = NULL;
587
588 return 1;
589 }