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